Merge branch 'release/26.03.0'
This commit is contained in:
commit
01aeca7121
1265 changed files with 5114 additions and 3964 deletions
24
.github/workflows/build.yml
vendored
24
.github/workflows/build.yml
vendored
|
|
@ -25,6 +25,20 @@ jobs:
|
|||
group: ${{ github.ref == 'refs/heads/develop' && format('build-develop-{0}-{1}', matrix.variant, github.sha) || format('build-{0}-{1}', matrix.variant, github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||
with:
|
||||
# This might remove tools that are actually needed, if set to "true" but frees about 6 GB
|
||||
tool-cache: true
|
||||
# All of these default to true, but we should only need the 'android' one (and maybe swap-storage?)
|
||||
android: false
|
||||
dotnet: true
|
||||
haskell: true
|
||||
# This takes way too long to run (~2 minutes) and it saves only ~5.5GB
|
||||
large-packages: false
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
|
|
@ -60,16 +74,6 @@ jobs:
|
|||
path: |
|
||||
app/build/outputs/apk/gplay/debug/*-universal-debug.apk
|
||||
app/build/outputs/apk/fdroid/debug/*-universal-debug.apk
|
||||
- name: Upload x86_64 APK for Maestro
|
||||
if: ${{ matrix.variant == 'debug' }}
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: elementx-apk-maestro
|
||||
path: |
|
||||
app/build/outputs/apk/gplay/debug/app-gplay-x86_64-debug.apk
|
||||
retention-days: 5
|
||||
overwrite: true
|
||||
if-no-files-found: error
|
||||
- uses: rnkdsh/action-upload-diawi@4e1421305be7cfc510d05f47850262eeaf345108 # v1.5.12
|
||||
id: diawi
|
||||
# Do not fail the whole build if Diawi upload fails
|
||||
|
|
|
|||
14
.github/workflows/build_enterprise.yml
vendored
14
.github/workflows/build_enterprise.yml
vendored
|
|
@ -27,6 +27,20 @@ jobs:
|
|||
group: ${{ github.ref == 'refs/heads/develop' && format('build-develop-enterprise-{0}-{1}', matrix.variant, github.sha) || format('build-enterprise-{0}-{1}', matrix.variant, github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||
with:
|
||||
# This might remove tools that are actually needed, if set to "true" but frees about 6 GB
|
||||
tool-cache: true
|
||||
# All of these default to true, but we should only need the 'android' one (and maybe swap-storage?)
|
||||
android: false
|
||||
dotnet: true
|
||||
haskell: true
|
||||
# This takes way too long to run (~2 minutes) and it saves only ~5.5GB
|
||||
large-packages: false
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
|
|
|
|||
38
.github/workflows/maestro-local.yml
vendored
38
.github/workflows/maestro-local.yml
vendored
|
|
@ -18,11 +18,24 @@ jobs:
|
|||
build-apk:
|
||||
name: Build APK
|
||||
runs-on: ubuntu-latest
|
||||
# Allow one per PR.
|
||||
concurrency:
|
||||
group: ${{ format('maestro-{0}', github.ref) }}
|
||||
group: ${{ format('maestro-build-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||
with:
|
||||
# This might remove tools that are actually needed, if set to "true" but frees about 6 GB
|
||||
tool-cache: true
|
||||
# All of these default to true, but we should only need the 'android' one (and maybe swap-storage?)
|
||||
android: false
|
||||
dotnet: true
|
||||
haskell: true
|
||||
# This takes way too long to run (~2 minutes) and it saves only ~5.5GB
|
||||
large-packages: false
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
|
|
@ -57,10 +70,10 @@ jobs:
|
|||
name: Maestro test suite
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ build-apk ]
|
||||
# Allow one per PR.
|
||||
# Allow only one to run at a time, since they use the same environment.
|
||||
# Otherwise, tests running in parallel can break each other.
|
||||
concurrency:
|
||||
group: ${{ format('maestro-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
group: maestro-test
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
|
||||
|
|
@ -110,6 +123,21 @@ jobs:
|
|||
retention-days: 5
|
||||
overwrite: true
|
||||
if-no-files-found: error
|
||||
- name: Update summary (success)
|
||||
if: steps.maestro_test.outcome == 'success'
|
||||
run: |
|
||||
echo "### Maestro tests worked :rocket:!"
|
||||
- name: Update summary (failure)
|
||||
if: steps.maestro_test.outcome != 'success'
|
||||
run: |
|
||||
LOG_FILE=$(find ~/.maestro/tests/ -name maestro.log)
|
||||
echo "Log file: $LOG_FILE"
|
||||
LOG_LINES="$(tail -n 30 $LOG_FILE)"
|
||||
echo "### :x: Maestro tests failed...
|
||||
|
||||
\`\`\`
|
||||
$LOG_LINES
|
||||
\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
- name: Fail the workflow in case of error in test
|
||||
if: steps.maestro_test.outcome != 'success'
|
||||
run: |
|
||||
|
|
|
|||
14
.github/workflows/nightly.yml
vendored
14
.github/workflows/nightly.yml
vendored
|
|
@ -16,6 +16,20 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'element-hq/element-x-android' }}
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||
with:
|
||||
# This might remove tools that are actually needed, if set to "true" but frees about 6 GB
|
||||
tool-cache: true
|
||||
# All of these default to true, but we should only need the 'android' one (and maybe swap-storage?)
|
||||
android: false
|
||||
dotnet: true
|
||||
haskell: true
|
||||
# This takes way too long to run (~2 minutes) and it saves only ~5.5GB
|
||||
large-packages: false
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v5
|
||||
|
|
|
|||
14
.github/workflows/nightlyReports.yml
vendored
14
.github/workflows/nightlyReports.yml
vendored
|
|
@ -17,6 +17,20 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'element-hq/element-x-android' }}
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||
with:
|
||||
# This might remove tools that are actually needed, if set to "true" but frees about 6 GB
|
||||
tool-cache: true
|
||||
# All of these default to true, but we should only need the 'android' one (and maybe swap-storage?)
|
||||
android: false
|
||||
dotnet: true
|
||||
haskell: true
|
||||
# This takes way too long to run (~2 minutes) and it saves only ~5.5GB
|
||||
large-packages: false
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- name: ⏬ Checkout with LFS
|
||||
uses: nschloe/action-cached-lfs-checkout@f46300cd8952454b9f0a21a3d133d4bd5684cfc2 # v1.2.3
|
||||
|
||||
|
|
|
|||
14
.github/workflows/quality.yml
vendored
14
.github/workflows/quality.yml
vendored
|
|
@ -17,6 +17,20 @@ jobs:
|
|||
name: Search for forbidden patterns
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||
with:
|
||||
# This might remove tools that are actually needed, if set to "true" but frees about 6 GB
|
||||
tool-cache: true
|
||||
# All of these default to true, but we should only need the 'android' one (and maybe swap-storage?)
|
||||
android: false
|
||||
dotnet: true
|
||||
haskell: true
|
||||
# This takes way too long to run (~2 minutes) and it saves only ~5.5GB
|
||||
large-packages: false
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
|
|
|
|||
14
.github/workflows/recordScreenshots.yml
vendored
14
.github/workflows/recordScreenshots.yml
vendored
|
|
@ -17,6 +17,20 @@ jobs:
|
|||
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'Record-Screenshots'
|
||||
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||
with:
|
||||
# This might remove tools that are actually needed, if set to "true" but frees about 6 GB
|
||||
tool-cache: true
|
||||
# All of these default to true, but we should only need the 'android' one (and maybe swap-storage?)
|
||||
android: false
|
||||
dotnet: true
|
||||
haskell: true
|
||||
# This takes way too long to run (~2 minutes) and it saves only ~5.5GB
|
||||
large-packages: false
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- name: Remove Record-Screenshots label
|
||||
if: github.event.label.name == 'Record-Screenshots'
|
||||
uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 # v1.3.0
|
||||
|
|
|
|||
28
.github/workflows/release.yml
vendored
28
.github/workflows/release.yml
vendored
|
|
@ -18,6 +18,20 @@ jobs:
|
|||
group: ${{ format('build-release-main-gplay-{0}', github.sha) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||
with:
|
||||
# This might remove tools that are actually needed, if set to "true" but frees about 6 GB
|
||||
tool-cache: true
|
||||
# All of these default to true, but we should only need the 'android' one (and maybe swap-storage?)
|
||||
android: false
|
||||
dotnet: true
|
||||
haskell: true
|
||||
# This takes way too long to run (~2 minutes) and it saves only ~5.5GB
|
||||
large-packages: false
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v5
|
||||
|
|
@ -88,6 +102,20 @@ jobs:
|
|||
group: ${{ format('build-release-main-fdroid-{0}', github.sha) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||
with:
|
||||
# This might remove tools that are actually needed, if set to "true" but frees about 6 GB
|
||||
tool-cache: true
|
||||
# All of these default to true, but we should only need the 'android' one (and maybe swap-storage?)
|
||||
android: false
|
||||
dotnet: true
|
||||
haskell: true
|
||||
# This takes way too long to run (~2 minutes) and it saves only ~5.5GB
|
||||
large-packages: false
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v5
|
||||
|
|
|
|||
|
|
@ -8,6 +8,13 @@
|
|||
# Please see LICENSE in the repository root for full details.
|
||||
#
|
||||
|
||||
# First we disable the onboarding flow on Chrome, which is a source of issues
|
||||
# (see https://stackoverflow.com/a/64629745)
|
||||
echo "Disabling Chrome onboarding flow"
|
||||
adb shell am set-debug-app --persistent com.android.chrome
|
||||
adb shell 'echo "chrome --disable-fre --no-default-browser-check --no-first-run" > /data/local/tmp/chrome-command-line'
|
||||
adb shell am start -n com.android.chrome/com.google.android.apps.chrome.Main
|
||||
|
||||
adb install -r $1
|
||||
echo "Starting the screen recording..."
|
||||
adb push .github/workflows/scripts/maestro/local-recording.sh /data/local/tmp/
|
||||
|
|
|
|||
14
.github/workflows/sonar.yml
vendored
14
.github/workflows/sonar.yml
vendored
|
|
@ -22,6 +22,20 @@ jobs:
|
|||
group: ${{ format('sonar-{0}', github.ref) }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }}
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||
with:
|
||||
# This might remove tools that are actually needed, if set to "true" but frees about 6 GB
|
||||
tool-cache: true
|
||||
# All of these default to true, but we should only need the 'android' one (and maybe swap-storage?)
|
||||
android: false
|
||||
dotnet: true
|
||||
haskell: true
|
||||
# This takes way too long to run (~2 minutes) and it saves only ~5.5GB
|
||||
large-packages: false
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
|
|
|
|||
14
.github/workflows/tests.yml
vendored
14
.github/workflows/tests.yml
vendored
|
|
@ -22,6 +22,20 @@ jobs:
|
|||
group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('unit-tests-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||
with:
|
||||
# This might remove tools that are actually needed, if set to "true" but frees about 6 GB
|
||||
tool-cache: true
|
||||
# All of these default to true, but we should only need the 'android' one (and maybe swap-storage?)
|
||||
android: false
|
||||
dotnet: true
|
||||
haskell: true
|
||||
# This takes way too long to run (~2 minutes) and it saves only ~5.5GB
|
||||
large-packages: false
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
# Increase swapfile size to prevent screenshot tests getting terminated
|
||||
# https://github.com/actions/runner-images/discussions/7188#discussioncomment-6750749
|
||||
- name: 💽 Increase swapfile size
|
||||
|
|
|
|||
|
|
@ -8,27 +8,6 @@ appId: ${MAESTRO_APP_ID}
|
|||
- tapOn:
|
||||
id: "login-continue"
|
||||
## MAS page
|
||||
## Conditional workflow to pass the Chrome first launch welcome page.
|
||||
- retry:
|
||||
maxRetries: 3
|
||||
commands:
|
||||
- runFlow:
|
||||
when:
|
||||
visible: 'Use without an account'
|
||||
commands:
|
||||
- tapOn: "Use without an account"
|
||||
## For older chrome versions
|
||||
- runFlow:
|
||||
when:
|
||||
visible: 'Accept & continue'
|
||||
commands:
|
||||
- tapOn: "Accept & continue"
|
||||
- runFlow:
|
||||
when:
|
||||
visible: 'No thanks'
|
||||
commands:
|
||||
- tapOn: "No thanks"
|
||||
## Working when running Maestro locally, but not on the CI yet.
|
||||
- retry:
|
||||
maxRetries: 3
|
||||
commands:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
appId: ${MAESTRO_APP_ID}
|
||||
---
|
||||
- extendedWaitUntil:
|
||||
visible: "Enter recovery key"
|
||||
timeout: 30000
|
||||
- takeScreenshot: build/maestro/150-Verify
|
||||
- tapOn: "Enter recovery key"
|
||||
- tapOn:
|
||||
|
|
@ -7,7 +10,10 @@ appId: ${MAESTRO_APP_ID}
|
|||
- inputText: ${MAESTRO_RECOVERY_KEY}
|
||||
- hideKeyboard
|
||||
- tapOn: "Continue"
|
||||
- extendedWaitUntil:
|
||||
visible: "Device verified"
|
||||
timeout: 30000
|
||||
- retry:
|
||||
maxRetries: 3
|
||||
commands:
|
||||
- extendedWaitUntil:
|
||||
visible: "Device verified"
|
||||
timeout: 30000
|
||||
- tapOn: "Continue"
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ appId: ${MAESTRO_APP_ID}
|
|||
---
|
||||
- extendedWaitUntil:
|
||||
visible: "Be in your element"
|
||||
timeout: 10000
|
||||
timeout: 30000
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ appId: ${MAESTRO_APP_ID}
|
|||
---
|
||||
- extendedWaitUntil:
|
||||
visible: "Confirm your identity"
|
||||
timeout: 20000
|
||||
timeout: 60000
|
||||
|
|
|
|||
74
CHANGES.md
74
CHANGES.md
|
|
@ -1,3 +1,77 @@
|
|||
Changes in Element X v26.02.0
|
||||
=============================
|
||||
|
||||
<!-- Release notes generated using configuration in .github/release.yml at v26.02.0 -->
|
||||
|
||||
## What's Changed
|
||||
### ✨ Features
|
||||
* When a background SDK task fails, react in the client by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6166
|
||||
* Enable space feature flags by default by @ganfra in https://github.com/element-hq/element-x-android/pull/6171
|
||||
### 🙌 Improvements
|
||||
* Improve space management with pagination and partial failure handling by @ganfra in https://github.com/element-hq/element-x-android/pull/6099
|
||||
* Iterate on QrCode login error buttons by @bmarty in https://github.com/element-hq/element-x-android/pull/6101
|
||||
* Update icon shown for world_readable rooms by @richvdh in https://github.com/element-hq/element-x-android/pull/6111
|
||||
* QRCode login: treat not found error as expired error. by @bmarty in https://github.com/element-hq/element-x-android/pull/6161
|
||||
* Iterate on Space related UI by @ganfra in https://github.com/element-hq/element-x-android/pull/6150
|
||||
### 🔒 Security
|
||||
* Ensure aspect ratio of images in the timeline is restricted by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6168
|
||||
### 🐛 Bugfixes
|
||||
* Ensure that Element Call activity is not closed when using an external link by @bmarty in https://github.com/element-hq/element-x-android/pull/6114
|
||||
* Refresh a Space's room list after creating a room in it by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6135
|
||||
* When creating a DM, set room history visibility to `invited` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6138
|
||||
* Fix back navigation after creating a room in a space by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6134
|
||||
* Fix `LinkifyHelper` index out of bounds with parenthesis by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6140
|
||||
* Change role screen won't be dismissed until changes take effect by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6141
|
||||
### 🗣 Translations
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/6122
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/6155
|
||||
### 🧱 Build
|
||||
* Try fixing Maestro tests (again) by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6149
|
||||
* Add a stale bot for X-Needs-Info issues. by @bmarty in https://github.com/element-hq/element-x-android/pull/6153
|
||||
* [Release script] Ensure that the release version will match the next Monday date by @bmarty in https://github.com/element-hq/element-x-android/pull/6152
|
||||
### 🚧 In development 🚧
|
||||
* Add Space Filters feature for Room List by @ganfra in https://github.com/element-hq/element-x-android/pull/6136
|
||||
* Add history sharing badges to room details by @kaylendog in https://github.com/element-hq/element-x-android/pull/6132
|
||||
### Dependency upgrades
|
||||
* Update dependency androidx.work:work-runtime-ktx to v2.11.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6105
|
||||
* Update metro to v0.10.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6106
|
||||
* Update camera to v1.5.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6103
|
||||
* Update activity to v1.12.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6104
|
||||
* Update dependency com.posthog:posthog-android to v3.30.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6120
|
||||
* Update kotlin by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6102
|
||||
* Update roborazzi to v1.58.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6124
|
||||
* Update kover by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6139
|
||||
* Update dependency com.posthog:posthog-android to v3.31.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6145
|
||||
* Update kotlin by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6142
|
||||
* Update media3 to v1.9.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6151
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.02.6 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6144
|
||||
* Update firebaseAppDistribution to v5.2.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6146
|
||||
* Update dependency com.google.firebase:firebase-bom to v34.9.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6148
|
||||
* Update dependency io.sentry:sentry-android to v8.32.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6157
|
||||
* Update metro to v0.10.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6164
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.2.10 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6169
|
||||
* chore(deps): update plugin paparazzi to v2.0.0-alpha04 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6048
|
||||
* fix(deps): update dependency org.jetbrains.kotlinx:kover-gradle-plugin to v0.9.7 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6173
|
||||
* fix(deps): update haze to v1.7.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6175
|
||||
### Others
|
||||
* Improve favorite wording and icon of room by @bmarty in https://github.com/element-hq/element-x-android/pull/6097
|
||||
* Add special flow for leaving a space as the last owner by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6112
|
||||
* Remove `runBlocking` in `ThreadedMessagesNode` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6108
|
||||
* Revert "Add "call.pro.element.io" in the list of known hosts for Element Call." by @bmarty in https://github.com/element-hq/element-x-android/pull/6118
|
||||
* Refactor room list filtering to use Rust SDK by @ganfra in https://github.com/element-hq/element-x-android/pull/6117
|
||||
* Ensure http 429 are retried 3 times before failing. by @bmarty in https://github.com/element-hq/element-x-android/pull/6119
|
||||
* Remove `JoinRule.Private` from the codebase by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6129
|
||||
* Fix voice message recording not starting after permission is granted by @kknappe in https://github.com/element-hq/element-x-android/pull/6109
|
||||
* Use correct bg color. by @bmarty in https://github.com/element-hq/element-x-android/pull/6165
|
||||
* Document "Developer options" and remove outdated instructions by @MadLittleMods in https://github.com/element-hq/element-x-android/pull/6162
|
||||
* Update SpaceFilterButton selected state color by @ganfra in https://github.com/element-hq/element-x-android/pull/6178
|
||||
|
||||
## New Contributors
|
||||
* @kknappe made their first contribution in https://github.com/element-hq/element-x-android/pull/6109
|
||||
* @MadLittleMods made their first contribution in https://github.com/element-hq/element-x-android/pull/6162
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.01.2...v26.02.0
|
||||
|
||||
Changes in Element X v26.01.2
|
||||
=============================
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,6 @@ import io.element.android.libraries.di.SessionScope
|
|||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.MAIN_SPACE
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
|
@ -113,6 +112,10 @@ import kotlin.time.Duration.Companion.seconds
|
|||
import kotlin.time.toKotlinDuration
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom as JoinedRoomAnalyticsEvent
|
||||
|
||||
// The maximum number of room nodes that should be kept in the backstack at the same time.
|
||||
// Having 5 rooms in the backstack seems reasonable and shouldn't grow the saved state size too much.
|
||||
private const val MAX_ROOM_NODE_COUNT = 5
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
@AssistedInject
|
||||
class LoggedInFlowNode(
|
||||
|
|
@ -211,8 +214,6 @@ class LoggedInFlowNode(
|
|||
onCreate = {
|
||||
analyticsRoomListStateWatcher.start()
|
||||
appNavigationStateService.onNavigateToSession(id, matrixClient.sessionId)
|
||||
// TODO We do not support Space yet, so directly navigate to main space
|
||||
appNavigationStateService.onNavigateToSpace(id, MAIN_SPACE)
|
||||
loggedInFlowProcessor.observeEvents(sessionCoroutineScope)
|
||||
matrixClient.sessionVerificationService.setListener(verificationListener)
|
||||
mediaPreviewConfigMigration()
|
||||
|
|
@ -242,7 +243,6 @@ class LoggedInFlowNode(
|
|||
}
|
||||
},
|
||||
onDestroy = {
|
||||
appNavigationStateService.onLeavingSpace(id)
|
||||
appNavigationStateService.onLeavingSession(id)
|
||||
loggedInFlowProcessor.stopObserving()
|
||||
matrixClient.sessionVerificationService.setListener(null)
|
||||
|
|
@ -327,12 +327,13 @@ class LoggedInFlowNode(
|
|||
NavTarget.Home -> {
|
||||
val callback = object : HomeEntryPoint.Callback {
|
||||
override fun navigateToRoom(roomId: RoomId, joinedRoom: JoinedRoom?) {
|
||||
backstack.push(
|
||||
NavTarget.Room(
|
||||
lifecycleScope.launch {
|
||||
attachRoom(
|
||||
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
||||
initialElement = RoomNavigationTarget.Root(joinedRoom = joinedRoom)
|
||||
initialElement = RoomNavigationTarget.Root(joinedRoom = joinedRoom),
|
||||
clearBackstack = false,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun navigateToSettings() {
|
||||
|
|
@ -356,7 +357,13 @@ class LoggedInFlowNode(
|
|||
}
|
||||
|
||||
override fun navigateToRoomSettings(roomId: RoomId) {
|
||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), initialElement = RoomNavigationTarget.Details))
|
||||
lifecycleScope.launch {
|
||||
attachRoom(
|
||||
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
||||
initialElement = RoomNavigationTarget.Details,
|
||||
clearBackstack = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun navigateToBugReport() {
|
||||
|
|
@ -372,7 +379,9 @@ class LoggedInFlowNode(
|
|||
is NavTarget.Room -> {
|
||||
val joinedRoomCallback = object : JoinedRoomLoadedFlowNode.Callback {
|
||||
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>) {
|
||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), serverNames))
|
||||
lifecycleScope.launch {
|
||||
attachRoom(roomIdOrAlias = roomId.toRoomIdOrAlias(), serverNames = serverNames, clearBackstack = false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) {
|
||||
|
|
@ -382,16 +391,25 @@ class LoggedInFlowNode(
|
|||
Timber.e("User link clicked: ${data.userId}.")
|
||||
}
|
||||
is PermalinkData.RoomLink -> {
|
||||
val target = NavTarget.Room(
|
||||
roomIdOrAlias = data.roomIdOrAlias,
|
||||
serverNames = data.viaParameters,
|
||||
trigger = JoinedRoomAnalyticsEvent.Trigger.Timeline,
|
||||
initialElement = RoomNavigationTarget.Root(data.eventId),
|
||||
)
|
||||
if (pushToBackstack) {
|
||||
backstack.push(target)
|
||||
lifecycleScope.launch {
|
||||
attachRoom(
|
||||
roomIdOrAlias = data.roomIdOrAlias,
|
||||
serverNames = data.viaParameters,
|
||||
trigger = JoinedRoomAnalyticsEvent.Trigger.Timeline,
|
||||
initialElement = RoomNavigationTarget.Root(data.eventId),
|
||||
clearBackstack = false
|
||||
)
|
||||
}
|
||||
} else {
|
||||
backstack.replace(target)
|
||||
backstack.replace(
|
||||
NavTarget.Room(
|
||||
roomIdOrAlias = data.roomIdOrAlias,
|
||||
serverNames = data.viaParameters,
|
||||
trigger = JoinedRoomAnalyticsEvent.Trigger.Timeline,
|
||||
initialElement = RoomNavigationTarget.Root(data.eventId),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
is PermalinkData.FallbackLink,
|
||||
|
|
@ -417,7 +435,9 @@ class LoggedInFlowNode(
|
|||
is NavTarget.UserProfile -> {
|
||||
val callback = object : UserProfileEntryPoint.Callback {
|
||||
override fun navigateToRoom(roomId: RoomId) {
|
||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias()))
|
||||
lifecycleScope.launch {
|
||||
attachRoom(roomIdOrAlias = roomId.toRoomIdOrAlias(), clearBackstack = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
userProfileEntryPoint.createNode(
|
||||
|
|
@ -446,11 +466,22 @@ class LoggedInFlowNode(
|
|||
}
|
||||
|
||||
override fun navigateToRoomNotificationSettings(roomId: RoomId) {
|
||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), initialElement = RoomNavigationTarget.NotificationSettings))
|
||||
lifecycleScope.launch {
|
||||
attachRoom(
|
||||
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
||||
initialElement = RoomNavigationTarget.NotificationSettings,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun navigateToEvent(roomId: RoomId, eventId: EventId) {
|
||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), initialElement = RoomNavigationTarget.Root(eventId)))
|
||||
lifecycleScope.launch {
|
||||
attachRoom(
|
||||
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
||||
initialElement = RoomNavigationTarget.Root(eventId),
|
||||
clearBackstack = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val inputs = PreferencesEntryPoint.Params(navTarget.initialElement)
|
||||
|
|
@ -481,7 +512,13 @@ class LoggedInFlowNode(
|
|||
is NavTarget.CreateSpace -> {
|
||||
val callback = object : CreateRoomEntryPoint.Callback {
|
||||
override fun onRoomCreated(roomId: RoomId) {
|
||||
backstack.replace(NavTarget.Room(roomIdOrAlias = RoomIdOrAlias.Id(roomId), serverNames = emptyList()))
|
||||
lifecycleScope.launch {
|
||||
attachRoom(
|
||||
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
||||
serverNames = emptyList(),
|
||||
clearBackstack = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
createRoomEntryPoint
|
||||
|
|
@ -518,13 +555,13 @@ class LoggedInFlowNode(
|
|||
buildContext = buildContext,
|
||||
callback = object : RoomDirectoryEntryPoint.Callback {
|
||||
override fun navigateToRoom(roomDescription: RoomDescription) {
|
||||
backstack.push(
|
||||
NavTarget.Room(
|
||||
lifecycleScope.launch {
|
||||
attachRoom(
|
||||
roomIdOrAlias = roomDescription.roomId.toRoomIdOrAlias(),
|
||||
roomDescription = roomDescription,
|
||||
trigger = JoinedRoomAnalyticsEvent.Trigger.RoomDirectory,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -541,7 +578,7 @@ class LoggedInFlowNode(
|
|||
|
||||
// Navigate to the room if the text/media was shared to a single one
|
||||
roomIds.singleOrNull()?.let { roomId ->
|
||||
sessionCoroutineScope.launch {
|
||||
lifecycleScope.launch {
|
||||
// Wait until the incoming share screen is removed
|
||||
backstack.elements.first { it.lastOrNull()?.key?.navTarget !is NavTarget.IncomingShare }
|
||||
|
||||
|
|
@ -572,8 +609,9 @@ class LoggedInFlowNode(
|
|||
roomIdOrAlias: RoomIdOrAlias,
|
||||
serverNames: List<String> = emptyList(),
|
||||
trigger: JoinedRoomAnalyticsEvent.Trigger? = null,
|
||||
eventId: EventId? = null,
|
||||
clearBackstack: Boolean,
|
||||
roomDescription: RoomDescription? = null,
|
||||
initialElement: RoomNavigationTarget = RoomNavigationTarget.Root(),
|
||||
clearBackstack: Boolean = false,
|
||||
): RoomFlowNode {
|
||||
waitForNavTargetAttached { navTarget ->
|
||||
navTarget is NavTarget.Home
|
||||
|
|
@ -582,8 +620,9 @@ class LoggedInFlowNode(
|
|||
val roomNavTarget = NavTarget.Room(
|
||||
roomIdOrAlias = roomIdOrAlias,
|
||||
serverNames = serverNames,
|
||||
roomDescription = roomDescription,
|
||||
trigger = trigger,
|
||||
initialElement = RoomNavigationTarget.Root(eventId = eventId)
|
||||
initialElement = initialElement,
|
||||
)
|
||||
backstack.accept(AttachRoomOperation(roomNavTarget, clearBackstack))
|
||||
}
|
||||
|
|
@ -593,8 +632,7 @@ class LoggedInFlowNode(
|
|||
return waitForChildAttached<RoomFlowNode, NavTarget> {
|
||||
it is NavTarget.Room &&
|
||||
it.roomIdOrAlias == roomIdOrAlias &&
|
||||
it.initialElement is RoomNavigationTarget.Root &&
|
||||
it.initialElement.eventId == eventId
|
||||
it.initialElement == initialElement
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -640,7 +678,7 @@ class LoggedInFlowNode(
|
|||
) { contentModifier ->
|
||||
Box(modifier = contentModifier) {
|
||||
val ftueState by ftueService.state.collectAsState()
|
||||
BackstackView()
|
||||
BackstackView(transitionHandler = rememberLoggedInFlowTransitionHandler(backstack))
|
||||
if (ftueState is FtueState.Complete) {
|
||||
PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent)
|
||||
}
|
||||
|
|
@ -655,6 +693,15 @@ private class AttachRoomOperation(
|
|||
val roomTarget: LoggedInFlowNode.NavTarget.Room,
|
||||
val clearBackstack: Boolean,
|
||||
) : BackStackOperation<LoggedInFlowNode.NavTarget> {
|
||||
/**
|
||||
* Returns a list containing last [count] elements that match [predicate] while preserving other elements.
|
||||
*/
|
||||
private fun <T> List<T>.keepingLast(count: Int, predicate: (T) -> Boolean): List<T> {
|
||||
val matchingIndices = indices.filter { predicate(this[it]) }
|
||||
val indicesToRemove = matchingIndices.dropLast(count).toSet()
|
||||
return filterIndexed { index, _ -> index !in indicesToRemove }
|
||||
}
|
||||
|
||||
override fun isApplicable(elements: NavElements<LoggedInFlowNode.NavTarget, BackStack.State>) = true
|
||||
|
||||
override fun invoke(elements: BackStackElements<LoggedInFlowNode.NavTarget>): BackStackElements<LoggedInFlowNode.NavTarget> {
|
||||
|
|
@ -677,16 +724,34 @@ private class AttachRoomOperation(
|
|||
val roomNavTarget = it.key.navTarget as? LoggedInFlowNode.NavTarget.Room
|
||||
roomNavTarget?.roomIdOrAlias == roomTarget.roomIdOrAlias
|
||||
}
|
||||
|
||||
// Make sure the backstack of rooms can't grow indefinitely when opening permalinks.
|
||||
val roomElementCount = elements.count { it.key.navTarget is LoggedInFlowNode.NavTarget.Room }
|
||||
|
||||
Timber.d("Current room nodes: $roomElementCount/$MAX_ROOM_NODE_COUNT")
|
||||
// Crate a new list keeping all the elements, but for Room ones just keep the last MAX_ROOM_NODE_COUNT
|
||||
val currentElements = elements.keepingLast(MAX_ROOM_NODE_COUNT) { element ->
|
||||
element.key.navTarget is LoggedInFlowNode.NavTarget.Room
|
||||
}
|
||||
|
||||
// If the room already existed, remove it from the stack and add a new node at the end
|
||||
if (existingRoomElement != null) {
|
||||
elements.mapNotNull { element ->
|
||||
currentElements.mapNotNull { element ->
|
||||
if (element == existingRoomElement) {
|
||||
null
|
||||
} else {
|
||||
element.transitionTo(STASHED, this)
|
||||
}
|
||||
} + existingRoomElement.transitionTo(ACTIVE, this)
|
||||
} + // Always create a new element, otherwise we wouldn't be navigating to the target event id or child node
|
||||
BackStackElement(
|
||||
key = NavKey(roomTarget),
|
||||
fromState = CREATED,
|
||||
targetState = ACTIVE,
|
||||
operation = this
|
||||
)
|
||||
} else {
|
||||
Push<LoggedInFlowNode.NavTarget>(roomTarget).invoke(elements)
|
||||
// Otherwise, just push the new node to the end of the backstack
|
||||
Push<LoggedInFlowNode.NavTarget>(roomTarget).invoke(currentElements)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.appnav
|
||||
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.Transition
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.navigation.transition.ModifierTransitionHandler
|
||||
import com.bumble.appyx.core.navigation.transition.TransitionDescriptor
|
||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||
import com.bumble.appyx.navmodel.backstack.transitionhandler.rememberBackstackFader
|
||||
import com.bumble.appyx.navmodel.backstack.transitionhandler.rememberBackstackSlider
|
||||
|
||||
/**
|
||||
* A TransitionHandler that uses fade transition when Placeholder is being removed,
|
||||
* and slide transition for all other cases.
|
||||
*/
|
||||
class LoggedInFlowTransitionHandler(
|
||||
private val backstack: BackStack<LoggedInFlowNode.NavTarget>,
|
||||
private val slider: ModifierTransitionHandler<LoggedInFlowNode.NavTarget, BackStack.State>,
|
||||
private val fader: ModifierTransitionHandler<LoggedInFlowNode.NavTarget, BackStack.State>,
|
||||
) : ModifierTransitionHandler<LoggedInFlowNode.NavTarget, BackStack.State>() {
|
||||
override fun createModifier(
|
||||
modifier: Modifier,
|
||||
transition: Transition<BackStack.State>,
|
||||
descriptor: TransitionDescriptor<LoggedInFlowNode.NavTarget, BackStack.State>
|
||||
): Modifier {
|
||||
val isPlaceholderBeingRemoved = backstack.elements.value.any { element ->
|
||||
element.key.navTarget == LoggedInFlowNode.NavTarget.Placeholder &&
|
||||
element.targetState != BackStack.State.ACTIVE
|
||||
}
|
||||
val handler = if (isPlaceholderBeingRemoved) fader else slider
|
||||
return handler.createModifier(modifier, transition, descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberLoggedInFlowTransitionHandler(
|
||||
backstack: BackStack<LoggedInFlowNode.NavTarget>,
|
||||
): ModifierTransitionHandler<LoggedInFlowNode.NavTarget, BackStack.State> {
|
||||
val slider = rememberBackstackSlider<LoggedInFlowNode.NavTarget>(
|
||||
transitionSpec = { spring(stiffness = Spring.StiffnessMediumLow) },
|
||||
)
|
||||
val fader = rememberBackstackFader<LoggedInFlowNode.NavTarget>(
|
||||
transitionSpec = { spring(stiffness = Spring.StiffnessMediumLow) },
|
||||
)
|
||||
return remember(backstack, slider, fader) {
|
||||
LoggedInFlowTransitionHandler(backstack, slider, fader)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.appnav
|
||||
|
||||
import com.bumble.appyx.core.navigation.NavElements
|
||||
import com.bumble.appyx.core.navigation.Operation
|
||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* Replaces all the current elements with the provided [navElements], keeping their [BackStack.State] too.
|
||||
*/
|
||||
@Parcelize
|
||||
class ReplaceAllOperation<NavTarget : Any>(
|
||||
private val navElements: NavElements<NavTarget, BackStack.State>
|
||||
) : Operation<NavTarget, BackStack.State> {
|
||||
override fun isApplicable(elements: NavElements<NavTarget, BackStack.State>): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun invoke(existing: NavElements<NavTarget, BackStack.State>): NavElements<NavTarget, BackStack.State> {
|
||||
return navElements
|
||||
}
|
||||
}
|
||||
|
|
@ -16,9 +16,12 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.navigation.NavElements
|
||||
import com.bumble.appyx.core.navigation.NavKey
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.bumble.appyx.core.state.MutableSavedStateMap
|
||||
import com.bumble.appyx.core.state.SavedStateMap
|
||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||
import com.bumble.appyx.navmodel.backstack.operation.pop
|
||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||
|
|
@ -33,6 +36,7 @@ import io.element.android.appnav.di.MatrixSessionCache
|
|||
import io.element.android.appnav.intent.IntentResolver
|
||||
import io.element.android.appnav.intent.ResolvedIntent
|
||||
import io.element.android.appnav.room.RoomFlowNode
|
||||
import io.element.android.appnav.room.RoomNavigationTarget
|
||||
import io.element.android.appnav.root.RootNavStateFlowFactory
|
||||
import io.element.android.appnav.root.RootPresenter
|
||||
import io.element.android.appnav.root.RootView
|
||||
|
|
@ -49,6 +53,7 @@ import io.element.android.libraries.architecture.createNode
|
|||
import io.element.android.libraries.architecture.waitForChildAttached
|
||||
import io.element.android.libraries.core.uri.ensureProtocol
|
||||
import io.element.android.libraries.deeplink.api.DeeplinkData
|
||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
|
|
@ -66,7 +71,9 @@ import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction
|
|||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.api.watchers.AnalyticsColdStartWatcher
|
||||
import io.element.android.services.appnavstate.api.ROOM_OPENED_FROM_NOTIFICATION
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -92,19 +99,27 @@ class RootFlowNode(
|
|||
private val announcementService: AnnouncementService,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val analyticsColdStartWatcher: AnalyticsColdStartWatcher,
|
||||
@AppCoroutineScope private val appCoroutineScope: CoroutineScope,
|
||||
) : BaseFlowNode<RootFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.SplashScreen,
|
||||
savedStateMap = buildContext.savedStateMap,
|
||||
savedStateMap = null,
|
||||
),
|
||||
buildContext = buildContext,
|
||||
plugins = plugins
|
||||
) {
|
||||
override fun onBuilt() {
|
||||
analyticsColdStartWatcher.start()
|
||||
matrixSessionCache.restoreWithSavedState(buildContext.savedStateMap)
|
||||
appCoroutineScope.launch {
|
||||
matrixSessionCache.restoreWithSavedState(buildContext.savedStateMap)
|
||||
if (buildContext.savedStateMap != null) {
|
||||
restoreSavedState(buildContext.savedStateMap)
|
||||
observeNavState(true)
|
||||
} else {
|
||||
observeNavState(false)
|
||||
}
|
||||
}
|
||||
super.onBuilt()
|
||||
observeNavState()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(state: MutableSavedStateMap) {
|
||||
|
|
@ -113,25 +128,68 @@ class RootFlowNode(
|
|||
navStateFlowFactory.saveIntoSavedState(state)
|
||||
}
|
||||
|
||||
private fun observeNavState() {
|
||||
navStateFlowFactory.create(buildContext.savedStateMap).distinctUntilChanged().onEach { navState ->
|
||||
Timber.v("navState=$navState")
|
||||
when (navState.loggedInState) {
|
||||
is LoggedInState.LoggedIn -> {
|
||||
if (navState.loggedInState.isTokenValid) {
|
||||
tryToRestoreLatestSession(
|
||||
onSuccess = { sessionId -> switchToLoggedInFlow(sessionId, navState.cacheIndex) },
|
||||
onFailure = { switchToNotLoggedInFlow(null) }
|
||||
)
|
||||
} else {
|
||||
switchToSignedOutFlow(SessionId(navState.loggedInState.sessionId))
|
||||
private fun observeNavState(skipFirst: Boolean) {
|
||||
navStateFlowFactory.create(buildContext.savedStateMap)
|
||||
.distinctUntilChanged()
|
||||
.drop(if (skipFirst) 1 else 0)
|
||||
.onEach { navState ->
|
||||
Timber.v("navState=$navState")
|
||||
when (navState.loggedInState) {
|
||||
is LoggedInState.LoggedIn -> {
|
||||
if (navState.loggedInState.isTokenValid) {
|
||||
val sessionId = SessionId(navState.loggedInState.sessionId)
|
||||
if (matrixSessionCache.getOrNull(sessionId) != null) {
|
||||
switchToLoggedInFlow(sessionId, navState.cacheIndex)
|
||||
} else {
|
||||
tryToRestoreLatestSession(
|
||||
onSuccess = { sessionId -> switchToLoggedInFlow(sessionId, navState.cacheIndex) },
|
||||
onFailure = { switchToNotLoggedInFlow(null) }
|
||||
)
|
||||
}
|
||||
} else {
|
||||
switchToSignedOutFlow(SessionId(navState.loggedInState.sessionId))
|
||||
}
|
||||
}
|
||||
LoggedInState.NotLoggedIn -> {
|
||||
switchToNotLoggedInFlow(null)
|
||||
}
|
||||
}
|
||||
LoggedInState.NotLoggedIn -> {
|
||||
switchToNotLoggedInFlow(null)
|
||||
}
|
||||
}
|
||||
}.launchIn(lifecycleScope)
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the saved state for navigation in the current backstack.
|
||||
*
|
||||
* **WARNING:** this is an unsafe operation abusing the internals of the Appyx library, but it's the only way allow async state
|
||||
* restoration and not having to block the main thread when the app starts.
|
||||
*
|
||||
* Modify with utmost care and double check any possible Appyx updates that might break this.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun restoreSavedState(savedStateMap: SavedStateMap?) {
|
||||
if (savedStateMap == null) return
|
||||
|
||||
// 'NavModel' is the key used for storing the nav model state data in the map in Appyx
|
||||
val savedElements = buildContext.savedStateMap?.get("NavModel") as? NavElements<NavTarget, BackStack.State>
|
||||
if (savedElements != null) {
|
||||
backstack.accept(ReplaceAllOperation(savedElements))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the saved state for navigation in the [navTarget].
|
||||
*
|
||||
* **WARNING:** this is an unsafe operation abusing the internals of the Appyx library, but it's the only way allow async state
|
||||
* restoration and not having to block the main thread when the app starts.
|
||||
*
|
||||
* Modify with utmost care and double check any possible Appyx updates that might break this.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun extractSavedStateForNavTarget(navTarget: NavTarget, savedStateMap: SavedStateMap?): SavedStateMap? {
|
||||
// 'ChildrenState' is the key used for storing the children state data in the map in Appyx
|
||||
val childrenState = savedStateMap?.get("ChildrenState") as? Map<NavKey<NavTarget>, SavedStateMap> ?: return null
|
||||
return childrenState.entries.find { (key, _) -> key.navTarget == navTarget }?.value
|
||||
}
|
||||
|
||||
private fun switchToLoggedInFlow(sessionId: SessionId, navId: Int) {
|
||||
|
|
@ -243,6 +301,13 @@ class RootFlowNode(
|
|||
backstack.push(NavTarget.NotLoggedInFlow(null))
|
||||
}
|
||||
}
|
||||
val savedNavState = extractSavedStateForNavTarget(navTarget, this.buildContext.savedStateMap)
|
||||
val buildContext = if (savedNavState != null) {
|
||||
Timber.d("Creating a $navTarget with restored saved state")
|
||||
buildContext.copy(savedStateMap = savedNavState)
|
||||
} else {
|
||||
buildContext.copy(savedStateMap = savedNavState)
|
||||
}
|
||||
createNode<LoggedInAppScopeFlowNode>(buildContext, plugins = listOf(inputs, callback))
|
||||
}
|
||||
is NavTarget.NotLoggedInFlow -> {
|
||||
|
|
@ -430,7 +495,7 @@ class RootFlowNode(
|
|||
roomIdOrAlias = permalinkData.roomIdOrAlias,
|
||||
trigger = JoinedRoom.Trigger.MobilePermalink,
|
||||
serverNames = permalinkData.viaParameters,
|
||||
eventId = focusedEventId,
|
||||
initialElement = RoomNavigationTarget.Root(eventId = focusedEventId),
|
||||
clearBackstack = true
|
||||
).maybeAttachThread(permalinkData.threadId, permalinkData.eventId)
|
||||
}
|
||||
|
|
@ -454,7 +519,7 @@ class RootFlowNode(
|
|||
is DeeplinkData.Room -> {
|
||||
loggedInFlowNode.attachRoom(
|
||||
roomIdOrAlias = deeplinkData.roomId.toRoomIdOrAlias(),
|
||||
eventId = if (deeplinkData.threadId != null) deeplinkData.threadId?.asEventId() else deeplinkData.eventId,
|
||||
initialElement = RoomNavigationTarget.Root(eventId = deeplinkData.threadId?.asEventId() ?: deeplinkData.eventId),
|
||||
clearBackstack = true,
|
||||
).maybeAttachThread(deeplinkData.threadId, deeplinkData.eventId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
|||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analyticsproviders.api.AnalyticsUserData
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import timber.log.Timber
|
||||
|
|
@ -77,20 +76,18 @@ class MatrixSessionCache(
|
|||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun restoreWithSavedState(state: SavedStateMap?) {
|
||||
suspend fun restoreWithSavedState(state: SavedStateMap?) {
|
||||
Timber.d("Restore state")
|
||||
if (state == null || sessionIdsToMatrixSession.isNotEmpty()) {
|
||||
Timber.w("Restore with non-empty map")
|
||||
Timber.w("No need to restore saved state")
|
||||
return
|
||||
}
|
||||
val sessionIds = state[SAVE_INSTANCE_KEY] as? Array<SessionId>
|
||||
Timber.d("Restore matrix session keys = ${sessionIds?.map { it.value }}")
|
||||
if (sessionIds.isNullOrEmpty()) return
|
||||
// Not ideal but should only happens in case of process recreation. This ensure we restore all the active sessions before restoring the node graphs.
|
||||
runBlocking {
|
||||
sessionIds.forEach { sessionId ->
|
||||
getOrRestore(sessionId)
|
||||
}
|
||||
sessionIds.forEach { sessionId ->
|
||||
getOrRestore(sessionId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 6207ddc1cb7dac7fdb6212c0158497a1d9752c75
|
||||
Subproject commit 1fd0d297d944186e3af2773e1c5db2938d60f74b
|
||||
2
fastlane/metadata/android/en-US/changelogs/202603000.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/202603000.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Main changes in this version: bug fixes and improvements.
|
||||
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_space_announcement_item1">"Ver espaços que criaste ou nos quais entraste"</string>
|
||||
<string name="screen_space_announcement_item2">"Aceitar ou recusar convites para espaços"</string>
|
||||
<string name="screen_space_announcement_item3">"Descobrir todas as salas dos seus espaços nas quais podes entrar"</string>
|
||||
<string name="screen_space_announcement_item4">"Entrar em espaços públicos"</string>
|
||||
<string name="screen_space_announcement_item5">"Deixar todos os espaços em que entraste"</string>
|
||||
<string name="screen_space_announcement_notice">"Em breve, será possível filtrar, criar e gerir espaços."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Eis a versão beta dos Espaços! Nesta primeira versão, podes:"</string>
|
||||
<string name="screen_space_announcement_title">"Apresentamos os Espaços"</string>
|
||||
</resources>
|
||||
|
|
@ -29,6 +29,7 @@ class DeclineCallBroadcastReceiver : BroadcastReceiver() {
|
|||
companion object {
|
||||
const val EXTRA_NOTIFICATION_DATA = "EXTRA_NOTIFICATION_DATA"
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var activeCallManager: ActiveCallManager
|
||||
|
||||
|
|
@ -40,7 +41,13 @@ class DeclineCallBroadcastReceiver : BroadcastReceiver() {
|
|||
?: return
|
||||
context.bindings<CallBindings>().inject(this)
|
||||
appCoroutineScope.launch {
|
||||
activeCallManager.hungUpCall(callType = CallType.RoomCall(notificationData.sessionId, notificationData.roomId))
|
||||
activeCallManager.hangUpCall(
|
||||
callType = CallType.RoomCall(
|
||||
sessionId = notificationData.sessionId,
|
||||
roomId = notificationData.roomId,
|
||||
),
|
||||
notificationData = notificationData,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ class CallScreenPresenter(
|
|||
)
|
||||
}
|
||||
onDispose {
|
||||
appCoroutineScope.launch { activeCallManager.hungUpCall(callType) }
|
||||
appCoroutineScope.launch { activeCallManager.hangUpCall(callType) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ class IncomingCallActivity : AppCompatActivity() {
|
|||
private fun onCancel() {
|
||||
val activeCall = activeCallManager.activeCall.value ?: return
|
||||
appCoroutineScope.launch {
|
||||
activeCallManager.hungUpCall(callType = activeCall.callType)
|
||||
activeCallManager.hangUpCall(callType = activeCall.callType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@ import io.element.android.libraries.matrix.api.core.SessionId
|
|||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
/**
|
||||
* Ref: https://www.figma.com/design/0MMNu7cTOzLOlWb7ctTkv3/Element-X?node-id=16501-5740
|
||||
*/
|
||||
@Composable
|
||||
internal fun IncomingCallScreen(
|
||||
notificationData: CallNotificationData,
|
||||
|
|
@ -94,11 +97,8 @@ internal fun IncomingCallScreen(
|
|||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 24.dp, end = 24.dp, bottom = 64.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
modifier = Modifier.padding(bottom = 64.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(48.dp),
|
||||
) {
|
||||
ActionButton(
|
||||
size = 64.dp,
|
||||
|
|
@ -108,7 +108,6 @@ internal fun IncomingCallScreen(
|
|||
backgroundColor = ElementTheme.colors.iconSuccessPrimary,
|
||||
borderColor = ElementTheme.colors.borderSuccessSubtle
|
||||
)
|
||||
|
||||
ActionButton(
|
||||
size = 64.dp,
|
||||
onClick = onCancel,
|
||||
|
|
@ -143,7 +142,7 @@ private fun ActionButton(
|
|||
onClick = onClick,
|
||||
colors = IconButtonDefaults.filledIconButtonColors(
|
||||
containerColor = backgroundColor,
|
||||
contentColor = Color.White,
|
||||
contentColor = ElementTheme.colors.iconOnSolidPrimary,
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
|
|
|
|||
|
|
@ -72,10 +72,14 @@ interface ActiveCallManager {
|
|||
suspend fun registerIncomingCall(notificationData: CallNotificationData)
|
||||
|
||||
/**
|
||||
* Called when the active call has been hung up. It will remove any existing UI and the active call.
|
||||
* @param callType The type of call that the user hung up, either an external url one or a room one.
|
||||
* Called to hang up the active call. It will hang up the call and remove any existing UI and the active call.
|
||||
* @param callType The type of call that the user hangs up, either an external url one or a room one.
|
||||
* @param notificationData The data for the incoming call notification.
|
||||
*/
|
||||
suspend fun hungUpCall(callType: CallType)
|
||||
suspend fun hangUpCall(
|
||||
callType: CallType,
|
||||
notificationData: CallNotificationData? = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* Called after the user joined a call. It will remove any existing UI and set the call state as [CallState.InCall].
|
||||
|
|
@ -192,12 +196,28 @@ class DefaultActiveCallManager(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun hungUpCall(callType: CallType) = mutex.withLock {
|
||||
Timber.tag(tag).d("Hung up call: $callType")
|
||||
override suspend fun hangUpCall(
|
||||
callType: CallType,
|
||||
notificationData: CallNotificationData?,
|
||||
) = mutex.withLock {
|
||||
Timber.tag(tag).d("Hang up call: $callType")
|
||||
cancelIncomingCallNotification()
|
||||
val currentActiveCall = activeCall.value ?: run {
|
||||
// activeCall.value can be null if the application has been killed while the call was ringing
|
||||
// Build a currentActiveCall with the provided parameters.
|
||||
notificationData?.let {
|
||||
ActiveCall(
|
||||
callType = callType,
|
||||
callState = CallState.Ringing(
|
||||
notificationData = notificationData,
|
||||
)
|
||||
)
|
||||
}
|
||||
} ?: run {
|
||||
Timber.tag(tag).w("No active call, ignoring hang up")
|
||||
return@withLock
|
||||
}
|
||||
|
||||
if (currentActiveCall.callType != callType) {
|
||||
Timber.tag(tag).w("Call type $callType does not match the active call type, ignoring")
|
||||
return@withLock
|
||||
|
|
@ -208,9 +228,13 @@ class DefaultActiveCallManager(
|
|||
matrixClientProvider.getOrRestore(notificationData.sessionId).getOrNull()
|
||||
?.getRoom(notificationData.roomId)
|
||||
?.declineCall(notificationData.eventId)
|
||||
?.onFailure {
|
||||
Timber.e(it, "Failed to decline incoming call")
|
||||
}
|
||||
?: run {
|
||||
Timber.tag(tag).d("Couldn't find session or room to decline call for incoming call")
|
||||
}
|
||||
}
|
||||
|
||||
cancelIncomingCallNotification()
|
||||
if (activeWakeLock?.isHeld == true) {
|
||||
Timber.tag(tag).d("Releasing partial wakelock after hang up")
|
||||
activeWakeLock.release()
|
||||
|
|
@ -221,7 +245,6 @@ class DefaultActiveCallManager(
|
|||
|
||||
override suspend fun joinedCall(callType: CallType) = mutex.withLock {
|
||||
Timber.tag(tag).d("Joined call: $callType")
|
||||
|
||||
cancelIncomingCallNotification()
|
||||
if (activeWakeLock?.isHeld == true) {
|
||||
Timber.tag(tag).d("Releasing partial wakelock after joining call")
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import android.webkit.JavascriptInterface
|
|||
import android.webkit.WebView
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.getSystemService
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -82,6 +83,13 @@ class WebViewAudioManager(
|
|||
AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
|
||||
)
|
||||
|
||||
private val audioDeviceComparator = Comparator<AudioDeviceInfo> { a, b ->
|
||||
// If the device type is not in the wantedDeviceTypes list, we give it a high index, (i.e. low priority)
|
||||
val indexOfA = wantedDeviceTypes.indexOf(a.type).let { if (it == -1) Int.MAX_VALUE else it }
|
||||
val indexOfB = wantedDeviceTypes.indexOf(b.type).let { if (it == -1) Int.MAX_VALUE else it }
|
||||
indexOfA.compareTo(indexOfB)
|
||||
}
|
||||
|
||||
private val audioManager = webView.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
|
||||
/**
|
||||
|
|
@ -134,7 +142,7 @@ class WebViewAudioManager(
|
|||
if (validNewDevices.isEmpty()) return
|
||||
|
||||
// We need to calculate the available devices ourselves, since calling `listAudioDevices` will return an outdated list
|
||||
val audioDevices = (listAudioDevices() + validNewDevices).distinctBy { it.id }
|
||||
val audioDevices = (listAudioDevices() + validNewDevices).distinctBy { it.id }.sortedWith(audioDeviceComparator)
|
||||
setAvailableAudioDevices(audioDevices.map(SerializableAudioDevice::fromAudioDeviceInfo))
|
||||
// This should automatically switch to a new device if it has a higher priority than the current one
|
||||
selectDefaultAudioDevice(audioDevices)
|
||||
|
|
@ -294,7 +302,7 @@ class WebViewAudioManager(
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the list of available audio devices.
|
||||
* Returns the list of available audio devices, sorted by likelihood of it being used for communication.
|
||||
*
|
||||
* On Android 11 ([Build.VERSION_CODES.R]) and lower, it returns the list of output devices as a fallback.
|
||||
*/
|
||||
|
|
@ -304,7 +312,7 @@ class WebViewAudioManager(
|
|||
} else {
|
||||
val rawAudioDevices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
|
||||
rawAudioDevices.filter { it.type in wantedDeviceTypes && it.isSink }
|
||||
}
|
||||
}.sortedWith(audioDeviceComparator)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -323,19 +331,12 @@ class WebViewAudioManager(
|
|||
}
|
||||
|
||||
/**
|
||||
* Selects the default audio device based on the available devices.
|
||||
* Selects the default audio device based on the sorted available devices.
|
||||
*
|
||||
* @param availableDevices The list of available audio devices to select from. If not provided, it will use the current list of audio devices.
|
||||
*/
|
||||
private fun selectDefaultAudioDevice(availableDevices: List<AudioDeviceInfo> = listAudioDevices()) {
|
||||
val selectedDevice = availableDevices
|
||||
.minByOrNull {
|
||||
wantedDeviceTypes.indexOf(it.type).let { index ->
|
||||
// If the device type is not in the wantedDeviceTypes list, we give it a low priority
|
||||
if (index == -1) Int.MAX_VALUE else index
|
||||
}
|
||||
}
|
||||
|
||||
val selectedDevice = availableDevices.firstOrNull()
|
||||
expectedNewCommunicationDeviceId = selectedDevice?.id
|
||||
audioManager.selectAudioDevice(selectedDevice)
|
||||
|
||||
|
|
@ -385,10 +386,18 @@ class WebViewAudioManager(
|
|||
currentDeviceId = device?.id
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
if (device != null) {
|
||||
Timber.d("Setting communication device: ${device.id} - ${deviceName(device.type, device.productName.toString())}")
|
||||
setCommunicationDevice(device)
|
||||
runCatchingExceptions {
|
||||
Timber.d("Setting communication device: ${device.id} - ${deviceName(device.type, device.productName.toString())}")
|
||||
setCommunicationDevice(device)
|
||||
}.onFailure {
|
||||
Timber.e(it, "Could not set communication device.")
|
||||
}
|
||||
} else {
|
||||
audioManager.clearCommunicationDevice()
|
||||
runCatchingExceptions {
|
||||
clearCommunicationDevice()
|
||||
}.onFailure {
|
||||
Timber.e(it, "Could not clear communication device.")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// On Android 11 and lower, we don't have the concept of communication devices
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ class DefaultActiveCallManagerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `hungUpCall - removes existing call if the CallType matches`() = runTest {
|
||||
fun `hangUpCall - removes existing call if the CallType matches`() = runTest {
|
||||
setupShadowPowerManager()
|
||||
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
|
||||
val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
|
||||
|
|
@ -165,7 +165,7 @@ class DefaultActiveCallManagerTest {
|
|||
assertThat(manager.activeCall.value).isNotNull()
|
||||
assertThat(manager.activeWakeLock?.isHeld).isTrue()
|
||||
|
||||
manager.hungUpCall(CallType.RoomCall(notificationData.sessionId, notificationData.roomId))
|
||||
manager.hangUpCall(CallType.RoomCall(notificationData.sessionId, notificationData.roomId))
|
||||
assertThat(manager.activeCall.value).isNull()
|
||||
assertThat(manager.activeWakeLock?.isHeld).isFalse()
|
||||
|
||||
|
|
@ -192,13 +192,41 @@ class DefaultActiveCallManagerTest {
|
|||
val notificationData = aCallNotificationData(roomId = A_ROOM_ID)
|
||||
manager.registerIncomingCall(notificationData)
|
||||
|
||||
manager.hungUpCall(CallType.RoomCall(notificationData.sessionId, notificationData.roomId))
|
||||
manager.hangUpCall(CallType.RoomCall(notificationData.sessionId, notificationData.roomId))
|
||||
|
||||
coVerify {
|
||||
room.declineCall(notificationEventId = notificationData.eventId)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Decline event - Hangup on a unknown call should send a decline event`() = runTest {
|
||||
setupShadowPowerManager()
|
||||
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
|
||||
|
||||
val room = mockk<JoinedRoom>(relaxed = true)
|
||||
|
||||
val matrixClient = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(A_ROOM_ID, room)
|
||||
}
|
||||
val clientProvider = FakeMatrixClientProvider({ Result.success(matrixClient) })
|
||||
|
||||
val manager = createActiveCallManager(
|
||||
matrixClientProvider = clientProvider,
|
||||
notificationManagerCompat = notificationManagerCompat
|
||||
)
|
||||
|
||||
val notificationData = aCallNotificationData(roomId = A_ROOM_ID)
|
||||
// Do not register the incoming call, so the manager doesn't know about it
|
||||
manager.hangUpCall(
|
||||
callType = CallType.RoomCall(notificationData.sessionId, notificationData.roomId),
|
||||
notificationData = notificationData,
|
||||
)
|
||||
coVerify {
|
||||
room.declineCall(notificationEventId = notificationData.eventId)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `Decline event - Declining from another session should stop ringing`() = runTest {
|
||||
|
|
@ -269,7 +297,7 @@ class DefaultActiveCallManagerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `hungUpCall - does nothing if the CallType doesn't match`() = runTest {
|
||||
fun `hangUpCall - does nothing if the CallType doesn't match`() = runTest {
|
||||
setupShadowPowerManager()
|
||||
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
|
||||
val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
|
||||
|
|
@ -278,11 +306,12 @@ class DefaultActiveCallManagerTest {
|
|||
assertThat(manager.activeCall.value).isNotNull()
|
||||
assertThat(manager.activeWakeLock?.isHeld).isTrue()
|
||||
|
||||
manager.hungUpCall(CallType.ExternalUrl("https://example.com"))
|
||||
manager.hangUpCall(CallType.ExternalUrl("https://example.com"))
|
||||
assertThat(manager.activeCall.value).isNotNull()
|
||||
assertThat(manager.activeWakeLock?.isHeld).isTrue()
|
||||
|
||||
verify(exactly = 0) { notificationManagerCompat.cancel(notificationId) }
|
||||
// The notification is always cancelled do not block the user
|
||||
verify(exactly = 1) { notificationManagerCompat.cancel(notificationId) }
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
|
||||
class FakeActiveCallManager(
|
||||
var registerIncomingCallResult: (CallNotificationData) -> Unit = {},
|
||||
var hungUpCallResult: (CallType) -> Unit = {},
|
||||
var hangUpCallResult: (CallType, CallNotificationData?) -> Unit = { _, _ -> },
|
||||
var joinedCallResult: (CallType) -> Unit = {},
|
||||
) : ActiveCallManager {
|
||||
override val activeCall = MutableStateFlow<ActiveCall?>(null)
|
||||
|
|
@ -26,8 +26,8 @@ class FakeActiveCallManager(
|
|||
registerIncomingCallResult(notificationData)
|
||||
}
|
||||
|
||||
override suspend fun hungUpCall(callType: CallType) = simulateLongTask {
|
||||
hungUpCallResult(callType)
|
||||
override suspend fun hangUpCall(callType: CallType, notificationData: CallNotificationData?) = simulateLongTask {
|
||||
hangUpCallResult(callType, notificationData)
|
||||
}
|
||||
|
||||
override suspend fun joinedCall(callType: CallType) = simulateLongTask {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@
|
|||
<string name="screen_create_room_private_option_description">"Толькі запрошаныя людзі могуць атрымаць доступ да гэтага пакоя. Усе паведамленні абаронены end-to-end шыфраваннем."</string>
|
||||
<string name="screen_create_room_public_option_description">"Любы можа знайсці гэты пакой.
|
||||
Вы можаце змяніць гэта ў любы час у наладах пакоя."</string>
|
||||
<string name="screen_create_room_public_option_title">"Публічны пакой (для ўсіх)"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Папрасіце далучыцца"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Хто заўгодна"</string>
|
||||
<string name="screen_create_room_topic_label">"Тэма (неабавязкова)"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@
|
|||
<string name="screen_create_room_private_option_description">"Само поканени хора имат достъп до тази стая. Всички съобщения са шифровани от край до край."</string>
|
||||
<string name="screen_create_room_public_option_description">"Всеки може да намери тази стая.
|
||||
Можете да промените това по всяко време в настройките на стаята."</string>
|
||||
<string name="screen_create_room_public_option_title">"Публична стая (всеки)"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Всеки може да се присъедини към тази стая"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Всеки"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"За да бъде тази стая видима в директорията на общодостъпните стаи, ще ви е необходим адрес на стаята."</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Видимост на стаята"</string>
|
||||
<string name="screen_create_room_topic_label">"Тема за разговор (незадължително)"</string>
|
||||
|
|
|
|||
|
|
@ -8,19 +8,19 @@
|
|||
<string name="screen_create_room_new_room_title">"Nová místnost"</string>
|
||||
<string name="screen_create_room_new_space_title">"Nový prostor"</string>
|
||||
<string name="screen_create_room_private_option_description">"Do této místnosti mohou vstoupit pouze pozvaní."</string>
|
||||
<string name="screen_create_room_private_option_title">"Soukromý"</string>
|
||||
<string name="screen_create_room_private_option_title">"Soukromé"</string>
|
||||
<string name="screen_create_room_public_option_description">"Tuto místnost může najít kdokoli.
|
||||
To můžete kdykoli změnit v nastavení místnosti."</string>
|
||||
<string name="screen_create_room_public_option_short_description">"Vstoupit může kdokoli."</string>
|
||||
<string name="screen_create_room_public_option_title">"Veřejná místnost"</string>
|
||||
<string name="screen_create_room_public_option_title">"Veřejné"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Kdokoli může požádat o vstup do místnosti, ale správce nebo moderátor bude muset žádost přijmout."</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Povolit žádost o vstup"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_restricted_option_description">"Kdokoli v %1$s může vstoupit, ale všichni ostatní si musí o přístup požádat."</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_restricted_option_title">"Požádat o vstup"</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_description">"Vstoupit mohou pouze pozvaní lidé."</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_title">"Soukromý"</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_title">"Soukromé"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Vstoupit může kdokoli."</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Kdokoliv"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Veřejné"</string>
|
||||
<string name="screen_create_room_room_access_section_restricted_option_description">"Kdokoli může vstoupit do %1$s."</string>
|
||||
<string name="screen_create_room_room_access_section_restricted_option_title">"Standard"</string>
|
||||
<string name="screen_create_room_room_access_section_title">"Kdo má přístup"</string>
|
||||
|
|
@ -28,7 +28,8 @@ To můžete kdykoli změnit v nastavení místnosti."</string>
|
|||
<string name="screen_create_room_room_address_section_title">"Adresa"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Viditelnost místnosti"</string>
|
||||
<string name="screen_create_room_space_selection_no_space_description">"(bez prostoru)"</string>
|
||||
<string name="screen_create_room_space_selection_no_space_title">"Domov"</string>
|
||||
<string name="screen_create_room_space_selection_no_space_option">"Nepřidávejte do prostoru"</string>
|
||||
<string name="screen_create_room_space_selection_no_space_title">"Není vybrán žádný prostor"</string>
|
||||
<string name="screen_create_room_space_selection_sheet_title">"Přidat do prostoru"</string>
|
||||
<string name="screen_create_room_topic_label">"Téma (nepovinné)"</string>
|
||||
<string name="screen_create_room_topic_placeholder">"Přidat popis…"</string>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@
|
|||
<string name="screen_create_room_private_option_description">"Dim ond pobl wahoddwyd all gael mynediad i\'r ystafell hon. Mae pob neges wedi\'i hamgryptio o\'r dechrau i\'r diwedd."</string>
|
||||
<string name="screen_create_room_public_option_description">"Gall unrhyw un ddod o hyd i\'r ystafell hon.
|
||||
Gallwch newid hyn unrhyw bryd yng ngosodiadau ystafell."</string>
|
||||
<string name="screen_create_room_public_option_title">"Ystafell gyhoeddus"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Gall unrhyw un ofyn am gael ymuno â\'r ystafell ond bydd rhaid i weinyddwr neu gymedrolwr dderbyn y cais"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Gofyn i gael ymuno"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Gall unrhyw un ymuno â\'r ystafell hon"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Unrhyw un"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Er mwyn i\'r ystafell hon fod yn weladwy yn y cyfeiriadur ystafelloedd cyhoeddus, bydd angen cyfeiriad ystafell arnoch."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Cyfeiriad yr ystafell"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Gwelededd yr ystafell"</string>
|
||||
|
|
|
|||
|
|
@ -8,19 +8,15 @@
|
|||
<string name="screen_create_room_new_room_title">"Nyt rum"</string>
|
||||
<string name="screen_create_room_new_space_title">"Ny gruppe"</string>
|
||||
<string name="screen_create_room_private_option_description">"Kun inviterede personer kan deltage."</string>
|
||||
<string name="screen_create_room_private_option_title">"Privat"</string>
|
||||
<string name="screen_create_room_public_option_description">"Alle kan finde dette rum.
|
||||
Du kan ændre dette når som helst i rummets indstillinger."</string>
|
||||
<string name="screen_create_room_public_option_short_description">"Alle kan deltage."</string>
|
||||
<string name="screen_create_room_public_option_title">"Offentlig"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Alle kan bede om at deltage i rummet, men en administrator eller en moderator skal acceptere anmodningen"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Tillad at man kan anmode om deltagelse"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_restricted_option_description">"Enhver i %1$s kan deltage, men alle andre skal anmode om adgang."</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_restricted_option_title">"Anmod om at deltage"</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_description">"Kun inviterede brugere kan deltage."</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_title">"Privat"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Alle kan deltage i dette rum"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Offentlig"</string>
|
||||
<string name="screen_create_room_room_access_section_restricted_option_description">"Alle i %1$s kan deltage."</string>
|
||||
<string name="screen_create_room_room_access_section_restricted_option_title">"Standard"</string>
|
||||
<string name="screen_create_room_room_access_section_title">"Hvem har adgang"</string>
|
||||
|
|
|
|||
|
|
@ -8,19 +8,15 @@
|
|||
<string name="screen_create_room_new_room_title">"Neuer Chat"</string>
|
||||
<string name="screen_create_room_new_space_title">"Neuer Space"</string>
|
||||
<string name="screen_create_room_private_option_description">"Nur eingeladene Personen haben Zutritt zu diesem Chat."</string>
|
||||
<string name="screen_create_room_private_option_title">"Privat"</string>
|
||||
<string name="screen_create_room_public_option_description">"Jeder kann diesen Chat finden.
|
||||
Du kannst dies jederzeit in den Einstellungen des Chats ändern."</string>
|
||||
<string name="screen_create_room_public_option_short_description">"Jeder kann beitreten."</string>
|
||||
<string name="screen_create_room_public_option_title">"Öffentlicher Chatroom"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Jeder kann den Beitritt zum Chat erbitten, aber ein Admin oder Moderator muss die Anfrage akzeptieren."</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Anfrage zum Beitritt zulassen"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_restricted_option_description">"Jeder in %1$s kann beitreten, aber alle anderen müssen den Beitritt anfragen."</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_restricted_option_title">"Beitritt anfragen"</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_description">"Nur eingeladene Personen können beitreten."</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_title">"Privat"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Jeder darf diesem Chat beitreten."</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Jeder"</string>
|
||||
<string name="screen_create_room_room_access_section_restricted_option_description">"Jeder in %1$s kann beitreten."</string>
|
||||
<string name="screen_create_room_room_access_section_restricted_option_title">"Standard"</string>
|
||||
<string name="screen_create_room_room_access_section_title">"Wer hat Zugang"</string>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@
|
|||
<string name="screen_create_room_private_option_description">"Μόνο τα άτομα που έχουν προσκληθεί μπορούν να έχουν πρόσβαση σε αυτή την αίθουσα. Όλα τα μηνύματα είναι κρυπτογραφημένα από άκρο σε άκρο."</string>
|
||||
<string name="screen_create_room_public_option_description">"Ο καθένας μπορεί να βρει αυτή την αίθουσα.
|
||||
Αυτό μπορείτε να το αλλάξετε ανά πάσα στιγμή στις ρυθμίσεις της αίθουσας."</string>
|
||||
<string name="screen_create_room_public_option_title">"Δημόσιο δωμάτιο"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Οποιοσδήποτε μπορεί να ζητήσει να συμμετάσχει στην αίθουσα, αλλά ένας διαχειριστής ή ένας συντονιστής θα πρέπει να αποδεχτεί το αίτημα"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Αίτημα συμμετοχής"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Οποιοσδήποτε μπορεί να συμμετάσχει σε αυτή την αίθουσα"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Οποιοσδήποτε"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Για να είναι ορατή αυτή η αίθουσα στον δημόσιο κατάλογο αιθουσών, θα χρειαστείτε μια διεύθυνση αίθουσας."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Διεύθυνση δωματίου"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Ορατότητα αίθουσας"</string>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@
|
|||
<string name="screen_create_room_private_option_description">"Solo las personas invitadas pueden acceder a esta sala. Todos los mensajes están cifrados de extremo a extremo."</string>
|
||||
<string name="screen_create_room_public_option_description">"Cualquiera puede encontrar esta sala.
|
||||
Puedes cambiar esto en cualquier momento en los ajustes de la sala."</string>
|
||||
<string name="screen_create_room_public_option_title">"Sala pública (cualquiera)"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Cualquiera puede solicitar unirse a la sala, pero un administrador o un moderador tendrá que aceptar la solicitud"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Solicitud para unirse"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Cualquiera puede unirse a esta sala"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Cualquiera"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Para que esta sala sea visible en el directorio de salas públicas, necesitarás una dirección de sala."</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Visibilidad de la sala"</string>
|
||||
<string name="screen_create_room_topic_label">"Tema (opcional)"</string>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<string name="screen_create_room_public_option_description">"Kõik saavad seda jututuba leida.
|
||||
Sa võid seda jututoa seadistustest alati muuta."</string>
|
||||
<string name="screen_create_room_public_option_short_description">"Kõik võivad selle jututoaga liituda."</string>
|
||||
<string name="screen_create_room_public_option_title">"Avalik jututuba"</string>
|
||||
<string name="screen_create_room_public_option_title">"Avalik"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Kõik võivad paluda selle jututoaga liitumist, kuid peakasutaja või moderaator peavad selle kinnitama."</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Luba küsida liitumisvõimalust"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_restricted_option_description">"Kõik „%1$s“ kogukonna liikmed võivad liituda, kuid kõik teised peavad liitumiseks küsima luba."</string>
|
||||
|
|
@ -20,7 +20,7 @@ Sa võid seda jututoa seadistustest alati muuta."</string>
|
|||
<string name="screen_create_room_room_access_section_private_option_description">"Ligipääs siia jututuppa on vaid kutse alusel."</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_title">"Privaatne"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Kõik võivad selle jututoaga liituda."</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Kõik kasutajad"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Avalik"</string>
|
||||
<string name="screen_create_room_room_access_section_restricted_option_description">"Liituda võivad kõik „%1$s“ kogukonna liikmed."</string>
|
||||
<string name="screen_create_room_room_access_section_restricted_option_title">"Standardne"</string>
|
||||
<string name="screen_create_room_room_access_section_title">"Kellel on ligipääs"</string>
|
||||
|
|
@ -28,7 +28,8 @@ Sa võid seda jututoa seadistustest alati muuta."</string>
|
|||
<string name="screen_create_room_room_address_section_title">"Aadress"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Jututoa nähtavus"</string>
|
||||
<string name="screen_create_room_space_selection_no_space_description">"(kogukonda pole)"</string>
|
||||
<string name="screen_create_room_space_selection_no_space_title">"Avaleht"</string>
|
||||
<string name="screen_create_room_space_selection_no_space_option">"Ära lisa kogukonda"</string>
|
||||
<string name="screen_create_room_space_selection_no_space_title">"Ühtegi kogukonda pole valitud"</string>
|
||||
<string name="screen_create_room_space_selection_sheet_title">"Lisa kogukonda"</string>
|
||||
<string name="screen_create_room_topic_label">"Teema (kui soovid lisada)"</string>
|
||||
<string name="screen_create_room_topic_placeholder">"Lisa kirjeldus…"</string>
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@
|
|||
<string name="screen_create_room_private_option_description">"Gonbidatutako jendea soilik sar daiteke gelara. Mezu guztiak daude ertzetik ertzera zifratuta."</string>
|
||||
<string name="screen_create_room_public_option_description">"Edonork aurki dezake gela hau.
|
||||
Gelaren ezarpenetan aldatu dezakezu hobespena."</string>
|
||||
<string name="screen_create_room_public_option_title">"Gela publikoa"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Edonor sar daiteke gela honetara"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Edonork"</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Gelaren helbidea"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Gelaren ikusgarritasuna"</string>
|
||||
<string name="screen_create_room_topic_label">"Mintzagaia (aukerakoa)"</string>
|
||||
|
|
|
|||
|
|
@ -4,13 +4,10 @@
|
|||
<string name="screen_create_room_add_people_title">"دعوت افراد"</string>
|
||||
<string name="screen_create_room_error_creating_room">"هنگام ایجاد اتاق خطایی رخ داد"</string>
|
||||
<string name="screen_create_room_private_option_description">"تنها افراد دعوت شده میتوانند به این اتاق دسترسی داشته باشند. همهٔ پیامها رمزنگاری سرتاسری شدهاند."</string>
|
||||
<string name="screen_create_room_private_option_title">"اتاق خصوصی"</string>
|
||||
<string name="screen_create_room_public_option_description">"هرکسی میتواند اتاق را بیابد.
|
||||
میتوانید بعداً در تظیمات اتاق عوضش کنید."</string>
|
||||
<string name="screen_create_room_public_option_title">"اتاق عمومی (هرکسی)"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"درخواست دعوت"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"هرکسی میتواند به این اتاق بپیوندد"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"هرکسی"</string>
|
||||
<string name="screen_create_room_room_address_section_title">"نشانی اتاق"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"نمایانی اتاق"</string>
|
||||
<string name="screen_create_room_topic_label">"موضوع (اختیاری)"</string>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@
|
|||
<string name="screen_create_room_private_option_description">"Vain kutsutut henkilöt pääsevät tähän huoneeseen. Kaikki viestit ovat päästä päähän salattuja."</string>
|
||||
<string name="screen_create_room_public_option_description">"Kuka tahansa voi löytää tämän huoneen.
|
||||
Voit muuttaa tämän milloin tahansa huoneen asetuksista."</string>
|
||||
<string name="screen_create_room_public_option_title">"Julkinen huone"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Kuka tahansa voi pyytää saada liittyä huoneeseen, mutta ylläpitäjän tai valvojan on hyväksyttävä pyyntö"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Pyydä liittymistä"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Kuka tahansa voi liittyä tähän huoneeseen"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Kuka tahansa"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Jotta tämä huone näkyisi julkisessa huonehakemistossa, tarvitset huoneen osoitteen."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Huoneen osoite"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Huoneen näkyvyys"</string>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<string name="screen_create_room_public_option_description">"N’importe qui peut trouver ce salon.
|
||||
Vous pouvez modifier cela à tout moment dans les paramètres du salon."</string>
|
||||
<string name="screen_create_room_public_option_short_description">"Tout le monde peut joindre"</string>
|
||||
<string name="screen_create_room_public_option_title">"Salon public"</string>
|
||||
<string name="screen_create_room_public_option_title">"Public"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Tout le monde peut demander à joindre, mais un administrateur ou un modérateur devra accepter la demande"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Autoriser la demande à joindre"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_restricted_option_description">"Tout membre de %1$s peut joindre, mais les autres doivent demander un accès."</string>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ To možete u svakom trenutku promijeniti u postavkama sobe."</string>
|
|||
<string name="screen_create_room_room_access_section_knocking_option_description">"Svatko može zatražiti pridruživanje sobi, ali administrator ili moderator morat će prihvatiti zahtjev."</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Zatraži pridruživanje"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Svatko se može pridružiti ovoj sobi"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Svatko"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Da bi ova soba bila vidljiva u javnom direktoriju soba, trebat će vam adresa sobe."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Adresa sobe"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Vidljivost sobe"</string>
|
||||
|
|
|
|||
|
|
@ -8,19 +8,15 @@
|
|||
<string name="screen_create_room_new_room_title">"Új szoba"</string>
|
||||
<string name="screen_create_room_new_space_title">"Új tér"</string>
|
||||
<string name="screen_create_room_private_option_description">"Csak a meghívottak léphetnek be."</string>
|
||||
<string name="screen_create_room_private_option_title">"Privát"</string>
|
||||
<string name="screen_create_room_public_option_description">"Bárki megtalálhatja ezt a szobát.
|
||||
Ezt bármikor módosíthatja a szobabeállításokban."</string>
|
||||
<string name="screen_create_room_public_option_short_description">"Bárki csatlakozhat."</string>
|
||||
<string name="screen_create_room_public_option_title">"Nyilvános szoba"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Bárki kérheti, hogy csatlakozhasson a szobához, de egy adminisztrátornak vagy moderátornak el kell fogadnia a kérést."</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Csatlakozás kérése"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_restricted_option_description">"Bárki csatlakozhat innen: %1$s, de mindenki másnak hozzáférést kell kérnie."</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_restricted_option_title">"Csatlakozás kérése"</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_description">"Csak a meghívottak léphetnek be."</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_title">"Privát"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Bárki csatlakozhat."</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Nyilvános"</string>
|
||||
<string name="screen_create_room_room_access_section_restricted_option_description">"Bárki csatlakozhat innen: %1$s."</string>
|
||||
<string name="screen_create_room_room_access_section_restricted_option_title">"Szokásos"</string>
|
||||
<string name="screen_create_room_room_access_section_title">"Hozzáférésre jogosultak"</string>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@
|
|||
<string name="screen_create_room_private_option_description">"Hanya orang-orang yang diundang dapat mengakses ruangan ini. Semua pesan terenkripsi secara ujung ke ujung."</string>
|
||||
<string name="screen_create_room_public_option_description">"Siapa pun dapat mencari ruangan ini.
|
||||
Anda dapat mengubah ini kapan pun dalam pengaturan ruangan."</string>
|
||||
<string name="screen_create_room_public_option_title">"Ruangan publik"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Siapa pun dapat meminta untuk bergabung dengan ruangan tetapi administrator atau moderator harus menerima permintaan tersebut"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Minta untuk bergabung"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Siapa pun dapat bergabung dengan ruangan ini"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Siapa pun"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Supaya ruangan ini terlihat di direktori ruangan publik, Anda memerlukan alamat ruangan."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Alamat ruangan"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Keterlihatan ruangan"</string>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@
|
|||
<string name="screen_create_room_private_option_description">"Solo le persone invitate possono accedere a questa stanza. Tutti i messaggi sono cifrati end-to-end."</string>
|
||||
<string name="screen_create_room_public_option_description">"Chiunque può trovare questa stanza.
|
||||
Puoi modificarlo in qualsiasi momento nelle impostazioni della stanza."</string>
|
||||
<string name="screen_create_room_public_option_title">"Stanza pubblica"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Chiunque può chiedere di entrare nella stanza, ma un amministratore o un moderatore dovrà accettare la richiesta"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Chiedi di entrare"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Chiunque può entrare in questa stanza"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Chiunque"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Affinché questa stanza sia visibile nell\'elenco delle stanze pubbliche, è necessario un indirizzo della stanza."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Indirizzo della stanza"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Visibilità della stanza"</string>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,5 @@
|
|||
<string name="screen_create_room_private_option_description">"ამ ოთახში შეტყობინებები დაშიფრულია. შემდგომ დაშიფვრის გამორთვა შეუძლებელია."</string>
|
||||
<string name="screen_create_room_public_option_description">"ყველას ამ ოთახის მოძებნა შეუძლია.
|
||||
თქვენ ნებისმიერ დროს შეგიძლიათ ამის შეცვლა ოთახის პარამეტრებში."</string>
|
||||
<string name="screen_create_room_public_option_title">"საჯარო ოთახი"</string>
|
||||
<string name="screen_create_room_topic_label">"თემა (სურვილისამებრ)"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@
|
|||
<string name="screen_create_room_private_option_description">"초대받은 사람만 이 방에 액세스할 수 있습니다. 모든 메시지는 종단 간 암호화됩니다."</string>
|
||||
<string name="screen_create_room_public_option_description">"누구나 이 방을 찾을 수 있습니다.
|
||||
방 설정에서 언제든지 변경할 수 있습니다."</string>
|
||||
<string name="screen_create_room_public_option_title">"공개 방 (모두)"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"누구나 방에 참여 요청을 할 수 있지만, 관리자나 운영자가 요청을 수락해야 합니다."</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"참가 요청"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"누구나 이 방에 참여할 수 있습니다."</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"누구나"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"이 방이 공개 방 디렉토리에 표시되려면 방 주소가 필요합니다."</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"방 표시 여부"</string>
|
||||
<string name="screen_create_room_topic_label">"주제 (선택)"</string>
|
||||
|
|
|
|||
|
|
@ -7,17 +7,13 @@
|
|||
<string name="screen_create_room_new_room_title">"Nytt rom"</string>
|
||||
<string name="screen_create_room_new_space_title">"Nytt område"</string>
|
||||
<string name="screen_create_room_private_option_description">"Bare inviterte personer kan bli med."</string>
|
||||
<string name="screen_create_room_private_option_title">"Privat"</string>
|
||||
<string name="screen_create_room_public_option_description">"Alle kan finne dette rommet.
|
||||
Du kan endre dette når som helst i rominnstillingene."</string>
|
||||
<string name="screen_create_room_public_option_short_description">"Alle kan bli med."</string>
|
||||
<string name="screen_create_room_public_option_title">"Offentlig rom"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Alle kan be om å få bli med, men en administrator eller moderator må godta forespørselen."</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Be om å bli med"</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_description">"Bare inviterte personer kan bli med."</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_title">"Privat"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Alle kan bli med."</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Alle"</string>
|
||||
<string name="screen_create_room_room_access_section_title">"Hvem har tilgang"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Du trenger en adresse for å gjøre den synlig i den offentlige katalogen."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Adresse"</string>
|
||||
|
|
|
|||
|
|
@ -6,10 +6,8 @@
|
|||
<string name="screen_create_room_private_option_description">"Alleen uitgenodigde personen hebben toegang tot deze kamer. Alle berichten zijn end-to-end versleuteld."</string>
|
||||
<string name="screen_create_room_public_option_description">"Iedereen kan deze kamer vinden.
|
||||
Je kunt dit op elk gewenst moment wijzigen in de kamerinstellingen."</string>
|
||||
<string name="screen_create_room_public_option_title">"Openbare kamer"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Iedereen kan vragen om toe te treden tot de kamer, maar een beheerder of moderator moet het verzoek accepteren"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Vraag om toe te treden"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Iedereen kan toetreden tot deze kamer"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Iedereen"</string>
|
||||
<string name="screen_create_room_topic_label">"Onderwerp (optioneel)"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@
|
|||
<string name="screen_create_room_private_option_description">"Tylko zaproszone osoby mogą dołączyć do tego pokoju. Wszystkie wiadomości są szyfrowane end-to-end."</string>
|
||||
<string name="screen_create_room_public_option_description">"Każdy może znaleźć ten pokój.
|
||||
Możesz to zmienić w ustawieniach pokoju."</string>
|
||||
<string name="screen_create_room_public_option_title">"Pokój publiczny"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Każdy może poprosić o dołączenie do pokoju, ale administrator lub moderator będzie musiał zatwierdzić prośbę"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Poproś o dołączenie"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Każdy może dołączyć do tego pokoju"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Wszyscy"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Aby ten pokój był widoczny w katalogu pomieszczeń publicznych, będziesz potrzebował adres pokoju."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Adres pokoju"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Widoczność pomieszczenia"</string>
|
||||
|
|
|
|||
|
|
@ -8,19 +8,15 @@
|
|||
<string name="screen_create_room_new_room_title">"Nova sala"</string>
|
||||
<string name="screen_create_room_new_space_title">"Novo espaço"</string>
|
||||
<string name="screen_create_room_private_option_description">"Apenas pessoas convidadas podem entrar."</string>
|
||||
<string name="screen_create_room_private_option_title">"Privada"</string>
|
||||
<string name="screen_create_room_public_option_description">"Qualquer um pode encontrar esta sala.
|
||||
Você pode mudar isso a qualquer momento nas configurações da sala."</string>
|
||||
<string name="screen_create_room_public_option_short_description">"Qualquer um pode entrar."</string>
|
||||
<string name="screen_create_room_public_option_title">"Publica"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Qualquer um pode pedir para entrar, mas um administrador ou moderador deve aceitar a solicitação"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Permitir pedir para entrar"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_restricted_option_description">"Qualquer um em %1$s pode entrar, mas todos os outros devem pedir acesso."</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_restricted_option_title">"Pedir para entrar"</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_description">"Apenas pessoas convidadas podem entrar."</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_title">"Privada"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Qualquer um pode entrar."</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Qualquer pessoa"</string>
|
||||
<string name="screen_create_room_room_access_section_restricted_option_description">"Qualquer um em %1$s pode participar."</string>
|
||||
<string name="screen_create_room_room_access_section_restricted_option_title">"Normal"</string>
|
||||
<string name="screen_create_room_room_access_section_title">"Quem tem acesso"</string>
|
||||
|
|
|
|||
|
|
@ -4,14 +4,11 @@
|
|||
<string name="screen_create_room_add_people_title">"Convidar pessoas"</string>
|
||||
<string name="screen_create_room_error_creating_room">"Ocorreu um erro ao criar a sala"</string>
|
||||
<string name="screen_create_room_private_option_description">"Apenas as pessoas convidadas podem entrar."</string>
|
||||
<string name="screen_create_room_private_option_title">"Privada"</string>
|
||||
<string name="screen_create_room_public_option_description">"Qualquer um pode encontrar esta sala.
|
||||
Pode alterar esta opção nas definições da sala."</string>
|
||||
<string name="screen_create_room_public_option_title">"Sala pública"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Qualquer pessoa pode pedir para entrar, mas um administrador ou um moderador terá de aceitar o pedido."</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Pedir para participar"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Qualquer pessoa pode entrar."</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Pública"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Para que esta sala seja visível no diretório público, precisa de ter um endereço."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Endereço"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Visibilidade da sala"</string>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@
|
|||
<string name="screen_create_room_private_option_description">"Doar persoanele invitate pot accesa această cameră. Toate mesajele sunt criptate end-to-end."</string>
|
||||
<string name="screen_create_room_public_option_description">"Oricine poate găsi această cameră.
|
||||
Puteți modifica acest lucru oricând în setări."</string>
|
||||
<string name="screen_create_room_public_option_title">"Cameră publică"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Oricine poate cere să se alăture camerei, dar un administrator sau un moderator va trebui să accepte cererea"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Cereți să vă alăturați"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Oricine se poate alătura acestei camere"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Oricine"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Pentru ca această cameră să fie vizibilă în directorul de camere publice, veți avea nevoie de o adresă de cameră."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Adresa camerei"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Vizibilitatea camerei"</string>
|
||||
|
|
|
|||
|
|
@ -8,17 +8,13 @@
|
|||
<string name="screen_create_room_new_room_title">"Новая комната"</string>
|
||||
<string name="screen_create_room_new_space_title">"Новое пространство"</string>
|
||||
<string name="screen_create_room_private_option_description">"Присоединиться могут только приглашенные."</string>
|
||||
<string name="screen_create_room_private_option_title">"Частный"</string>
|
||||
<string name="screen_create_room_public_option_description">"Любой желающий может найти эту комнату.
|
||||
Вы можете изменить это в любое время в настройках комнаты."</string>
|
||||
<string name="screen_create_room_public_option_short_description">"Присоединиться может любой."</string>
|
||||
<string name="screen_create_room_public_option_title">"Общедоступная комната"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Любой желающий может подать заявку на присоединение к комнате, но администратор или модератор должен будет принять запрос."</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Разрешить запрос на присоединение"</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_description">"Присоединиться могут только приглашенные."</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_title">"Частный"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Присоединиться может любой желающий."</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Публичный"</string>
|
||||
<string name="screen_create_room_room_access_section_title">"Кто имеет доступ"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Вам понадобится адрес комнаты, чтобы сделать ее видимой в каталоге."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Адрес"</string>
|
||||
|
|
|
|||
|
|
@ -8,19 +8,15 @@
|
|||
<string name="screen_create_room_new_room_title">"Nová miestnosť"</string>
|
||||
<string name="screen_create_room_new_space_title">"Nový priestor"</string>
|
||||
<string name="screen_create_room_private_option_description">"Pripojiť sa môžu iba pozvaní ľudia."</string>
|
||||
<string name="screen_create_room_private_option_title">"Súkromná"</string>
|
||||
<string name="screen_create_room_public_option_description">"Túto miestnosť môže nájsť ktokoľvek.
|
||||
Môžete to kedykoľvek zmeniť v nastaveniach miestnosti."</string>
|
||||
<string name="screen_create_room_public_option_short_description">"Pripojiť sa môže ktokoľvek."</string>
|
||||
<string name="screen_create_room_public_option_title">"Verejná miestnosť"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"O pripojenie sa môže požiadať ktokoľvek, ale žiadosť musí schváliť správca alebo moderátor."</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Povoliť požiadať o vstup"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_restricted_option_description">"Ktokoľvek v %1$s sa môžu pripojiť, ale všetci ostatní musia požiadať o prístup."</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_restricted_option_title">"Požiadať o pripojenie"</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_description">"Pripojiť sa môžu iba pozvaní ľudia."</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_title">"Súkromná"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Pripojiť sa môže ktokoľvek."</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Ktokoľvek"</string>
|
||||
<string name="screen_create_room_room_access_section_restricted_option_description">"Ktokoľvek v %1$s sa môže pripojiť."</string>
|
||||
<string name="screen_create_room_room_access_section_restricted_option_title">"Štandardná"</string>
|
||||
<string name="screen_create_room_room_access_section_title">"Kto má prístup"</string>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@
|
|||
<string name="screen_create_room_private_option_description">"Endast inbjudna personer kan gå med."</string>
|
||||
<string name="screen_create_room_public_option_description">"Vem som helst kan hitta det här rummet.
|
||||
Du kan ändra detta när som helst i rumsinställningarna."</string>
|
||||
<string name="screen_create_room_public_option_title">"Offentligt rum"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Vem som helst kan be om att gå med men en administratör eller en moderator måste acceptera begäran"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Tillåt att be om att gå med"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Vem som helst kan gå med."</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Offentligt"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Du behöver en adress för att den ska synas i den offentliga katalogen."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Adress"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Rumssynlighet"</string>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@
|
|||
<string name="screen_create_room_private_option_description">"Bu odaya yalnızca davet edilen kişiler erişebilir. Tüm mesajlar uçtan uca şifrelenir."</string>
|
||||
<string name="screen_create_room_public_option_description">"Bu odayı herkes bulabilir.
|
||||
Bunu istediğiniz zaman oda ayarlarından değiştirebilirsiniz."</string>
|
||||
<string name="screen_create_room_public_option_title">"Herkese açık oda"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Herkes odaya katılmayı isteyebilir ancak bir yönetici veya moderatörün isteği kabul etmesi gerekecektir"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Katılmak için sor"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Bu odaya herkes katılabilir"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Herkes"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Bu odanın genel oda dizininde görünür olması için bir oda adresine ihtiyacınız olacaktır."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Oda adresi"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Oda görünürlüğü"</string>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@
|
|||
<string name="screen_create_room_private_option_description">"Лише запрошені люди мають доступ до цієї кімнати. Усі повідомлення захищені наскрізним шифруванням."</string>
|
||||
<string name="screen_create_room_public_option_description">"Будь-хто може знайти цю кімнату.
|
||||
Ви можете змінити це в будь-який час у налаштуваннях кімнати."</string>
|
||||
<string name="screen_create_room_public_option_title">"Публічна кімната"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Будь-хто може попросити приєднатися до кімнати, але адміністратор або модератор повинен буде прийняти запит"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Запросити приєднатися"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Будь-хто може приєднатися до цієї кімнати"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Кожний"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Щоб цю кімнату було видно в каталозі загальнодоступних кімнат, вам знадобиться її адреса."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Адреса кімнати"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Видимість кімнати"</string>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@
|
|||
<string name="screen_create_room_private_option_description">"Faqat taklif etilgan shaxslargina bu xonaga kira oladi. Barcha xabarlar boshdan-oxirigacha shifrlanadi."</string>
|
||||
<string name="screen_create_room_public_option_description">"Bu xonani har kim topishi mumkin.
|
||||
Buni xona sozlamalaridan istalgan vaqtda oʻzgartirishingiz mumkin."</string>
|
||||
<string name="screen_create_room_public_option_title">"Jamoat xonasi (har kim)"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Xonaga qo‘shilishni istalgan kishi so‘rashi mumkin, lekin administrator yoki moderator so‘rovni qabul qilishi kerak"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Qo‘shilishni so‘rang"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Bu xonaga istalgan kishi qo‘shilishi mumkin"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Har kim"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Ushbu xona ommaviy xonalar ro‘yxatida ko‘rinishi uchun sizga xona manzili kerak bo‘ladi."</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Xonaning ko‘rinishi"</string>
|
||||
<string name="screen_create_room_topic_label">"Mavzu (ixtiyoriy)"</string>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@
|
|||
<string name="screen_create_room_private_option_description">"僅被邀請的人才能存取此聊天室。所有訊息均會端到端加密。"</string>
|
||||
<string name="screen_create_room_public_option_description">"任何人都可以找到此聊天室。
|
||||
您隨時都可以在聊天室設定中變更此設定。"</string>
|
||||
<string name="screen_create_room_public_option_title">"公開聊天室"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"任何人都可以要求加入聊天室,但管理員或版主必須接受該請求"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"要求加入"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"任何人都可以加入此聊天室"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"任何人"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"為了讓此聊天室在公開聊天室目錄中可見,您需要聊天室地址。"</string>
|
||||
<string name="screen_create_room_room_address_section_title">"聊天室地址"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"聊天室能見度"</string>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@
|
|||
<string name="screen_create_room_private_option_description">"只有受邀用户才能访问此聊天室。所有消息均经过端到端加密。"</string>
|
||||
<string name="screen_create_room_public_option_description">"任何人都能找到此聊天室。
|
||||
你可以随时在聊天室设置中更改。"</string>
|
||||
<string name="screen_create_room_public_option_title">"公开聊天室"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"任何人都可以请求加入房间,但必须由管理员或审核人接受"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"请求加入"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"任何人都可以加入此房间"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"任何人"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"要使该房间在公开房间目录中可见,您需要一个房间地址。"</string>
|
||||
<string name="screen_create_room_room_address_section_title">"房间地址"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"房间可见性"</string>
|
||||
|
|
|
|||
|
|
@ -35,6 +35,11 @@ interface EnterpriseService {
|
|||
|
||||
fun bugReportUrlFlow(sessionId: SessionId?): Flow<BugReportUrl>
|
||||
|
||||
/**
|
||||
* Gets Notification Channel to use for the noisy notifications of the provided session.
|
||||
*/
|
||||
fun getNoisyNotificationChannelId(sessionId: SessionId): String?
|
||||
|
||||
companion object {
|
||||
const val ANY_ACCOUNT_PROVIDER = "*"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,4 +43,6 @@ class DefaultEnterpriseService : EnterpriseService {
|
|||
override fun bugReportUrlFlow(sessionId: SessionId?): Flow<BugReportUrl> {
|
||||
return flowOf(BugReportUrl.UseDefault)
|
||||
}
|
||||
|
||||
override fun getNoisyNotificationChannelId(sessionId: SessionId): String? = null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,4 +98,10 @@ class DefaultEnterpriseServiceTest {
|
|||
awaitComplete()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getNoisyNotificationChannelId returns null`() = runTest {
|
||||
val defaultEnterpriseService = DefaultEnterpriseService()
|
||||
assertThat(defaultEnterpriseService.getNoisyNotificationChannelId(A_SESSION_ID)).isNull()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ class FakeEnterpriseService(
|
|||
private val overrideBrandColorResult: (SessionId?, String?) -> Unit = { _, _ -> lambdaError() },
|
||||
private val firebasePushGatewayResult: () -> String? = { lambdaError() },
|
||||
private val unifiedPushDefaultPushGatewayResult: () -> String? = { lambdaError() },
|
||||
private val getNoisyNotificationChannelIdResult: (SessionId?) -> String? = { lambdaError() },
|
||||
) : EnterpriseService {
|
||||
private val brandColorState = MutableStateFlow(initialBrandColor)
|
||||
private val semanticColorsState = MutableStateFlow(initialSemanticColors)
|
||||
|
|
@ -69,4 +70,8 @@ class FakeEnterpriseService(
|
|||
override fun bugReportUrlFlow(sessionId: SessionId?): Flow<BugReportUrl> {
|
||||
return bugReportUrlMutableFlow.asStateFlow()
|
||||
}
|
||||
|
||||
override fun getNoisyNotificationChannelId(sessionId: SessionId): String? {
|
||||
return getNoisyNotificationChannelIdResult(sessionId)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
<string name="banner_battery_optimization_content_android">"Desativa as otimizações de bateria para esta aplicação, de modo a garantir que todas as notificações chegam."</string>
|
||||
<string name="banner_battery_optimization_submit_android">"Desativar otimizações"</string>
|
||||
<string name="banner_battery_optimization_title_android">"As notificações não chegam?"</string>
|
||||
<string name="banner_new_sound_message">"O toque de notificação foi atualizado — mais claro, mais rápido e menos perturbador."</string>
|
||||
<string name="banner_new_sound_title">"Atualizámos os seus sons"</string>
|
||||
<string name="banner_set_up_recovery_content">"Recupera a tua identidade criptográfica e o histórico de mensagens com uma chave de recuperação se tiveres perdido todos os teus dispositivos existentes."</string>
|
||||
<string name="banner_set_up_recovery_submit">"Configurar recuperação"</string>
|
||||
<string name="banner_set_up_recovery_title">"Configurar a recuperação"</string>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import extension.buildConfigFieldStr
|
||||
import extension.setupDependencyInjection
|
||||
import extension.testCommonDependencies
|
||||
|
||||
|
|
@ -23,6 +24,30 @@ android {
|
|||
isIncludeAndroidResources = true
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
val elementClassicPackageKey = "elementClassicPackage"
|
||||
val elementClassicPackage = "im.vector.app"
|
||||
val elementClassicPackageDebug = "$elementClassicPackage.debug"
|
||||
val elementClassicPackageNightly = "$elementClassicPackage.nightly"
|
||||
getByName("release") {
|
||||
manifestPlaceholders[elementClassicPackageKey] = elementClassicPackage
|
||||
buildConfigFieldStr(elementClassicPackageKey, elementClassicPackage)
|
||||
}
|
||||
getByName("debug") {
|
||||
manifestPlaceholders[elementClassicPackageKey] = elementClassicPackageDebug
|
||||
buildConfigFieldStr(elementClassicPackageKey, elementClassicPackageDebug)
|
||||
}
|
||||
register("nightly") {
|
||||
matchingFallbacks += listOf("release")
|
||||
manifestPlaceholders[elementClassicPackageKey] = elementClassicPackageNightly
|
||||
buildConfigFieldStr(elementClassicPackageKey, elementClassicPackageNightly)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupDependencyInjection()
|
||||
|
|
|
|||
|
|
@ -13,8 +13,9 @@
|
|||
<intent>
|
||||
<action android:name="android.support.customtabs.action.CustomTabsService" />
|
||||
</intent>
|
||||
|
||||
<!-- To be able to start the service exported by Element Classic -->
|
||||
<package android:name="${elementClassicPackage}" />
|
||||
</queries>
|
||||
|
||||
<!-- Permission to read data from Element classic -->
|
||||
<uses-permission android:name="im.vector.app.READ_DATA" />
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -20,9 +20,8 @@ import android.os.Messenger
|
|||
import android.os.RemoteException
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.features.login.impl.BuildConfig
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.core.meta.BuildType
|
||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
|
@ -44,7 +43,11 @@ sealed interface ElementClassicConnectionState {
|
|||
object Idle : ElementClassicConnectionState
|
||||
object ElementClassicNotFound : ElementClassicConnectionState
|
||||
object ElementClassicReadyNoSession : ElementClassicConnectionState
|
||||
data class ElementClassicReady(val userId: UserId) : ElementClassicConnectionState
|
||||
data class ElementClassicReady(
|
||||
val userId: UserId,
|
||||
val secrets: String,
|
||||
) : ElementClassicConnectionState
|
||||
|
||||
data class Error(val error: String) : ElementClassicConnectionState
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +59,6 @@ class DefaultElementClassicConnection(
|
|||
private val context: Context,
|
||||
@AppCoroutineScope
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val buildMeta: BuildMeta,
|
||||
) : ElementClassicConnection {
|
||||
// Messenger for communicating with the service.
|
||||
private var messenger: Messenger? = null
|
||||
|
|
@ -101,7 +103,7 @@ class DefaultElementClassicConnection(
|
|||
// applications replace our component.
|
||||
try {
|
||||
val intentService = Intent()
|
||||
intentService.setComponent(getElementClassicComponent(buildMeta))
|
||||
intentService.setComponent(getElementClassicComponent())
|
||||
if (context.bindService(intentService, serviceConnection, BIND_AUTO_CREATE)) {
|
||||
Timber.tag(loggerTag.value).d("Binding returned true")
|
||||
} else {
|
||||
|
|
@ -198,17 +200,8 @@ class DefaultElementClassicConnection(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getElementClassicComponent(buildMeta: BuildMeta) = ComponentName(
|
||||
buildString {
|
||||
append(ELEMENT_CLASSIC_APP_ID)
|
||||
append(
|
||||
when (buildMeta.buildType) {
|
||||
BuildType.DEBUG -> ELEMENT_CLASSIC_APP_ID_DEBUG_SUFFIX
|
||||
BuildType.NIGHTLY -> ELEMENT_CLASSIC_APP_ID_NIGHTLY_SUFFIX
|
||||
BuildType.RELEASE -> ELEMENT_CLASSIC_APP_ID_RELEASE_SUFFIX
|
||||
}
|
||||
)
|
||||
},
|
||||
private fun getElementClassicComponent() = ComponentName(
|
||||
BuildConfig.elementClassicPackage,
|
||||
ELEMENT_CLASSIC_SERVICE_FULL_CLASS_NAME,
|
||||
)
|
||||
|
||||
|
|
@ -220,9 +213,14 @@ class DefaultElementClassicConnection(
|
|||
if (error != null) {
|
||||
ElementClassicConnectionState.Error(error)
|
||||
} else {
|
||||
val userId = getString(KEY_USER_ID_STR)?.let(::UserId)
|
||||
val userId = getString(KEY_USER_ID_STR)?.takeIf { it.isNotEmpty() }?.let(::UserId)
|
||||
if (userId != null) {
|
||||
ElementClassicConnectionState.ElementClassicReady(userId)
|
||||
val secrets = getString(KEY_SECRETS_STR)?.takeIf { it.isNotEmpty() }
|
||||
if (secrets == null) {
|
||||
ElementClassicConnectionState.Error("No secrets received from Element Classic")
|
||||
} else {
|
||||
ElementClassicConnectionState.ElementClassicReady(userId, secrets)
|
||||
}
|
||||
} else {
|
||||
ElementClassicConnectionState.ElementClassicReadyNoSession
|
||||
}
|
||||
|
|
@ -232,18 +230,31 @@ class DefaultElementClassicConnection(
|
|||
|
||||
// Everything in this companion object must match what is defined in Element Classic
|
||||
private companion object {
|
||||
const val ELEMENT_CLASSIC_SERVICE_FULL_CLASS_NAME = "im.vector.app.features.importer.ImporterService"
|
||||
|
||||
// Command to the service to get the data.
|
||||
const val MSG_GET_DATA = 1
|
||||
|
||||
const val ELEMENT_CLASSIC_APP_ID = "im.vector.app"
|
||||
const val ELEMENT_CLASSIC_APP_ID_DEBUG_SUFFIX = ".debug"
|
||||
const val ELEMENT_CLASSIC_APP_ID_NIGHTLY_SUFFIX = ".nightly"
|
||||
const val ELEMENT_CLASSIC_APP_ID_RELEASE_SUFFIX = ""
|
||||
|
||||
const val ELEMENT_CLASSIC_SERVICE_FULL_CLASS_NAME = "im.vector.app.features.importer.ImporterService"
|
||||
|
||||
// Keys for the bundle returned from the service
|
||||
const val KEY_ERROR_STR = "error"
|
||||
const val KEY_USER_ID_STR = "userId"
|
||||
|
||||
/**
|
||||
* Key to extract the secrets from the bundle, as a Json string.
|
||||
* Json will have this format:
|
||||
* {
|
||||
* "cross_signing" : {
|
||||
* "master_key" : "z8RUxnaAGu___REDACTED___k+BQL9o",
|
||||
* "user_signing_key" : "baJHzA___REDACTED___xMLbSUAXw9QUzqms",
|
||||
* "self_signing_key" : "DU0CvLtR2G/___REDACTED___dV/MONNq4nsQhM"
|
||||
* },
|
||||
* "backup" : {
|
||||
* "algorithm" : "m.megolm_backup.v1.curve25519-aes-sha2",
|
||||
* "key" : "VzncmQ+UOV___REDACTED___patxDz7m0Nc",
|
||||
* "backup_version" : "1"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
const val KEY_SECRETS_STR = "secrets"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.google.common.truth.Truth.assertThat
|
|||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.test.A_SECRET
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||
|
|
@ -114,7 +115,7 @@ class LoginWithClassicPresenterTest {
|
|||
presenter.test {
|
||||
skipItems(2)
|
||||
elementClassicConnection.emitState(
|
||||
ElementClassicConnectionState.ElementClassicReady(userId = A_USER_ID)
|
||||
ElementClassicConnectionState.ElementClassicReady(userId = A_USER_ID, secrets = A_SECRET)
|
||||
)
|
||||
val readyState = awaitItem()
|
||||
assertThat(readyState.canLoginWithClassic).isTrue()
|
||||
|
|
@ -140,7 +141,7 @@ class LoginWithClassicPresenterTest {
|
|||
presenter.test {
|
||||
skipItems(2)
|
||||
elementClassicConnection.emitState(
|
||||
ElementClassicConnectionState.ElementClassicReady(userId = A_USER_ID)
|
||||
ElementClassicConnectionState.ElementClassicReady(userId = A_USER_ID, secrets = A_SECRET)
|
||||
)
|
||||
val readyState = awaitItem()
|
||||
assertThat(readyState.canLoginWithClassic).isTrue()
|
||||
|
|
@ -175,7 +176,7 @@ class LoginWithClassicPresenterTest {
|
|||
presenter.test {
|
||||
skipItems(2)
|
||||
elementClassicConnection.emitState(
|
||||
ElementClassicConnectionState.ElementClassicReady(userId = A_USER_ID)
|
||||
ElementClassicConnectionState.ElementClassicReady(userId = A_USER_ID, secrets = A_SECRET)
|
||||
)
|
||||
// No new item, because canLoginWithClassic is still false
|
||||
}
|
||||
|
|
@ -192,7 +193,7 @@ class LoginWithClassicPresenterTest {
|
|||
skipItems(1)
|
||||
// Note: it should not happen IRL
|
||||
elementClassicConnection.emitState(
|
||||
ElementClassicConnectionState.ElementClassicReady(userId = A_USER_ID)
|
||||
ElementClassicConnectionState.ElementClassicReady(userId = A_USER_ID, secrets = A_SECRET)
|
||||
)
|
||||
// No new item, because canLoginWithClassic is still false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ dependencies {
|
|||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.libraries.recentemojis.api)
|
||||
implementation(projects.libraries.roomselect.api)
|
||||
implementation(projects.libraries.audio.api)
|
||||
implementation(projects.libraries.voiceplayer.api)
|
||||
implementation(projects.libraries.voicerecorder.api)
|
||||
implementation(projects.libraries.mediaplayer.api)
|
||||
|
|
@ -95,6 +96,7 @@ dependencies {
|
|||
testImplementation(projects.libraries.mediapickers.test)
|
||||
testImplementation(projects.libraries.permissions.test)
|
||||
testImplementation(projects.libraries.preferences.test)
|
||||
testImplementation(projects.libraries.audio.test)
|
||||
testImplementation(projects.libraries.voicerecorder.test)
|
||||
testImplementation(projects.libraries.mediaplayer.test)
|
||||
testImplementation(projects.libraries.mediaviewer.test)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ import io.element.android.features.messages.api.MessageComposerContext
|
|||
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerEvent
|
||||
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerPresenter
|
||||
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerState
|
||||
import io.element.android.libraries.audio.api.AudioFocus
|
||||
import io.element.android.libraries.audio.api.AudioFocusRequester
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
|
|
@ -58,6 +60,7 @@ class DefaultVoiceMessageComposerPresenter(
|
|||
@Assisted private val timelineMode: Timeline.Mode,
|
||||
private val voiceRecorder: VoiceRecorder,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val audioFocus: AudioFocus,
|
||||
mediaSenderFactory: MediaSenderFactory,
|
||||
private val player: VoiceMessageComposerPlayer,
|
||||
private val messageComposerContext: MessageComposerContext,
|
||||
|
|
@ -246,8 +249,14 @@ class DefaultVoiceMessageComposerPresenter(
|
|||
|
||||
private fun CoroutineScope.startRecording() = launch {
|
||||
try {
|
||||
audioFocus.requestAudioFocus(AudioFocusRequester.RecordVoiceMessage) {
|
||||
// something else grabbed focus (phone call, etc) - finish gracefully
|
||||
// so the user keeps their partial recording
|
||||
sessionCoroutineScope.finishRecording()
|
||||
}
|
||||
voiceRecorder.startRecord()
|
||||
} catch (e: SecurityException) {
|
||||
audioFocus.releaseAudioFocus()
|
||||
Timber.e(e, "Voice message error")
|
||||
analyticsService.trackError(VoiceMessageException.PermissionMissing("Expected permission to record but none", e))
|
||||
}
|
||||
|
|
@ -255,10 +264,12 @@ class DefaultVoiceMessageComposerPresenter(
|
|||
|
||||
private fun CoroutineScope.finishRecording() = launch {
|
||||
voiceRecorder.stopRecord()
|
||||
audioFocus.releaseAudioFocus()
|
||||
}
|
||||
|
||||
private fun CoroutineScope.cancelRecording() = launch {
|
||||
voiceRecorder.stopRecord(cancelled = true)
|
||||
audioFocus.releaseAudioFocus()
|
||||
}
|
||||
|
||||
private fun CoroutineScope.deleteRecording() = launch {
|
||||
|
|
|
|||
|
|
@ -1238,7 +1238,7 @@ class MessagesPresenterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `present - shows a "world_readable" icon if the room is encrypted and history is world_readable`() = runTest {
|
||||
fun `present - shows a 'world_readable' icon if the room is encrypted and history is world_readable`() = runTest {
|
||||
val presenter = createMessagesPresenter(
|
||||
joinedRoom = FakeJoinedRoom(
|
||||
baseRoom = FakeBaseRoom(
|
||||
|
|
|
|||
|
|
@ -19,12 +19,15 @@ import io.element.android.features.messages.api.timeline.voicemessages.composer.
|
|||
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerState
|
||||
import io.element.android.features.messages.impl.messagecomposer.aReplyMode
|
||||
import io.element.android.features.messages.test.FakeMessageComposerContext
|
||||
import io.element.android.libraries.audio.api.AudioFocus
|
||||
import io.element.android.libraries.audio.api.AudioFocusRequester
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.media.AudioInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
|
||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
|
||||
import io.element.android.libraries.mediaplayer.test.FakeAudioFocus
|
||||
import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer
|
||||
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig
|
||||
import io.element.android.libraries.mediaupload.impl.DefaultMediaSender
|
||||
|
|
@ -80,6 +83,12 @@ class DefaultVoiceMessageComposerPresenterTest {
|
|||
timelineMode = Timeline.Mode.Live,
|
||||
mediaOptimizationConfigProvider = { MediaOptimizationConfig(compressImages = true, videoCompressionPreset = VideoCompressionPreset.STANDARD) },
|
||||
)
|
||||
private val requestAudioFocusResult = lambdaRecorder<AudioFocusRequester, () -> Unit, Unit> { _, _ -> }
|
||||
private val releaseAudioFocusResult = lambdaRecorder<Unit> { }
|
||||
private val audioFocus: AudioFocus = FakeAudioFocus(
|
||||
requestAudioFocusResult = requestAudioFocusResult,
|
||||
releaseAudioFocusResult = releaseAudioFocusResult,
|
||||
)
|
||||
private val messageComposerContext = FakeMessageComposerContext()
|
||||
|
||||
companion object {
|
||||
|
|
@ -159,6 +168,61 @@ class DefaultVoiceMessageComposerPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - recording requests audio focus and releases on stop`() = runTest {
|
||||
val presenter = createDefaultVoiceMessageComposerPresenter()
|
||||
presenter.test {
|
||||
awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start))
|
||||
val recordingState = awaitItem()
|
||||
requestAudioFocusResult.assertions().isCalledOnce()
|
||||
releaseAudioFocusResult.assertions().isNeverCalled()
|
||||
|
||||
recordingState.eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Stop))
|
||||
awaitItem()
|
||||
releaseAudioFocusResult.assertions().isCalledOnce()
|
||||
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - cancelling recording releases audio focus`() = runTest {
|
||||
val presenter = createDefaultVoiceMessageComposerPresenter()
|
||||
presenter.test {
|
||||
awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start))
|
||||
awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Cancel))
|
||||
awaitItem()
|
||||
requestAudioFocusResult.assertions().isCalledOnce()
|
||||
releaseAudioFocusResult.assertions().isCalledOnce()
|
||||
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - audio focus loss during recording finishes gracefully`() = runTest {
|
||||
var onFocusLost: (() -> Unit)? = null
|
||||
val testAudioFocus = FakeAudioFocus(
|
||||
requestAudioFocusResult = { _, callback -> onFocusLost = callback },
|
||||
releaseAudioFocusResult = { },
|
||||
)
|
||||
val presenter = createDefaultVoiceMessageComposerPresenter(audioFocus = testAudioFocus)
|
||||
presenter.test {
|
||||
awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start))
|
||||
awaitItem()
|
||||
|
||||
// simulate focus loss (phone call, etc)
|
||||
onFocusLost?.invoke()
|
||||
advanceUntilIdle()
|
||||
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.voiceMessageState).isEqualTo(aPreviewState())
|
||||
voiceRecorder.assertCalls(started = 1, stopped = 1)
|
||||
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - abort recording`() = runTest {
|
||||
val presenter = createDefaultVoiceMessageComposerPresenter()
|
||||
|
|
@ -647,12 +711,14 @@ class DefaultVoiceMessageComposerPresenterTest {
|
|||
private fun TestScope.createDefaultVoiceMessageComposerPresenter(
|
||||
permissionsPresenter: PermissionsPresenter = createFakePermissionsPresenter(),
|
||||
voiceRecorder: VoiceRecorder = this@DefaultVoiceMessageComposerPresenterTest.voiceRecorder,
|
||||
audioFocus: AudioFocus = this@DefaultVoiceMessageComposerPresenterTest.audioFocus,
|
||||
): DefaultVoiceMessageComposerPresenter {
|
||||
return DefaultVoiceMessageComposerPresenter(
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
timelineMode = Timeline.Mode.Live,
|
||||
voiceRecorder = voiceRecorder,
|
||||
analyticsService = analyticsService,
|
||||
audioFocus = audioFocus,
|
||||
mediaSenderFactory = { mediaSender },
|
||||
player = VoiceMessageComposerPlayer(FakeMediaPlayer(), this),
|
||||
messageComposerContext = messageComposerContext,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ android {
|
|||
dependencies {
|
||||
api(projects.features.messages.impl)
|
||||
implementation(projects.libraries.matrix.test)
|
||||
implementation(projects.libraries.audio.test)
|
||||
implementation(projects.libraries.mediaplayer.test)
|
||||
implementation(projects.libraries.mediaupload.test)
|
||||
implementation(projects.libraries.mediaviewer.api)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import io.element.android.features.messages.impl.voicemessages.composer.VoiceMes
|
|||
import io.element.android.features.messages.test.FakeMessageComposerContext
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
import io.element.android.libraries.mediaplayer.test.FakeAudioFocus
|
||||
import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer
|
||||
import io.element.android.libraries.mediaupload.api.MediaSender
|
||||
import io.element.android.libraries.mediaupload.impl.DefaultMediaSender
|
||||
|
|
@ -38,6 +39,10 @@ class FakeDefaultVoiceMessageComposerPresenterFactory(
|
|||
timelineMode = timelineMode,
|
||||
voiceRecorder = FakeVoiceRecorder(),
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
audioFocus = FakeAudioFocus(
|
||||
requestAudioFocusResult = { _, _ -> },
|
||||
releaseAudioFocusResult = { },
|
||||
),
|
||||
mediaSenderFactory = { mediaSender },
|
||||
player = VoiceMessageComposerPlayer(
|
||||
mediaPlayer = FakeMediaPlayer(),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
<string name="screen_advanced_settings_element_call_base_url_validation_error">"URL inválido, certifica-te de que incluis o protocolo (http/https) e o endereço correto."</string>
|
||||
<string name="screen_advanced_settings_hide_invite_avatars_toggle_title">"Ocultar avatares nos pedidos de acesso a salas"</string>
|
||||
<string name="screen_advanced_settings_hide_timeline_media_toggle_title">"Ocultar pré-visualizações de multimédia na cronologia"</string>
|
||||
<string name="screen_advanced_settings_labs">"Experiências"</string>
|
||||
<string name="screen_advanced_settings_media_compression_description">"Carrega fotos e vídeos mais rapidamente e reduz a utilização de dados"</string>
|
||||
<string name="screen_advanced_settings_media_compression_title">"Otimiza a qualidade da mídia"</string>
|
||||
<string name="screen_advanced_settings_moderation_and_safety_section_title">"Moderação e Segurança"</string>
|
||||
|
|
@ -43,6 +44,11 @@
|
|||
<string name="screen_edit_profile_error_title">"Não foi possível atualizar o perfil"</string>
|
||||
<string name="screen_edit_profile_title">"Editar perfil"</string>
|
||||
<string name="screen_edit_profile_updating_details">"A atualizar o perfil…"</string>
|
||||
<string name="screen_labs_enable_threads">"Ativar respostas em tópicos"</string>
|
||||
<string name="screen_labs_enable_threads_description">"A aplicação será reiniciada para aplicar esta alteração."</string>
|
||||
<string name="screen_labs_header_description">"Experimenta as nossas mais recentes ideias ainda em desenvolvimento. Estas funcionalidades não estão finalizadas e por isso podem ser instáveis ou acabarem alteradas."</string>
|
||||
<string name="screen_labs_header_title">"E que tal umas experiências?"</string>
|
||||
<string name="screen_labs_title">"Experiências"</string>
|
||||
<string name="screen_notification_settings_additional_settings_section_title">"Configurações adicionais"</string>
|
||||
<string name="screen_notification_settings_calls_label">"Chamadas de áudio e vídeo"</string>
|
||||
<string name="screen_notification_settings_configuration_mismatch">"Incompatibilidade de configuração"</string>
|
||||
|
|
|
|||
|
|
@ -429,7 +429,7 @@ class DefaultBugReporterTest {
|
|||
assertThat((param as WriteToFilesConfiguration.Enabled).directory).endsWith("/cache/logs/server.org")
|
||||
assertThat((param as WriteToFilesConfiguration.Enabled).filenamePrefix).isEqualTo("logs")
|
||||
assertThat((param as WriteToFilesConfiguration.Enabled).numberOfFiles).isEqualTo(168)
|
||||
assertThat((param as WriteToFilesConfiguration.Enabled).filenameSuffix).isEqualTo("log")
|
||||
assertThat((param as WriteToFilesConfiguration.Enabled).filenameSuffix).isEqualTo(".log")
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
|
@ -491,7 +491,7 @@ class DefaultBugReporterTest {
|
|||
assertThat((param as WriteToFilesConfiguration.Enabled).directory).endsWith("/cache/logs")
|
||||
assertThat((param as WriteToFilesConfiguration.Enabled).filenamePrefix).isEqualTo("logs")
|
||||
assertThat((param as WriteToFilesConfiguration.Enabled).numberOfFiles).isEqualTo(168)
|
||||
assertThat((param as WriteToFilesConfiguration.Enabled).filenameSuffix).isEqualTo("log")
|
||||
assertThat((param as WriteToFilesConfiguration.Enabled).filenameSuffix).isEqualTo(".log")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_room_change_permissions_administrators">"Administrador"</string>
|
||||
<string name="screen_room_change_permissions_ban_people">"Banir pessoas"</string>
|
||||
<string name="screen_room_change_permissions_change_settings">"Alterar configurações"</string>
|
||||
<string name="screen_room_change_permissions_delete_messages">"Remover mensagens"</string>
|
||||
<string name="screen_room_change_permissions_everyone">"Membro"</string>
|
||||
<string name="screen_room_change_permissions_everyone">"Participante"</string>
|
||||
<string name="screen_room_change_permissions_invite_people">"Convidar pessoas"</string>
|
||||
<string name="screen_room_change_permissions_member_moderation">"Gerir membros"</string>
|
||||
<string name="screen_room_change_permissions_manage_space">"Gerir espaço"</string>
|
||||
<string name="screen_room_change_permissions_manage_space_rooms">"Gerir salas"</string>
|
||||
<string name="screen_room_change_permissions_member_moderation">"Gerir participantes"</string>
|
||||
<string name="screen_room_change_permissions_messages_and_content">"Mensagens e conteúdo"</string>
|
||||
<string name="screen_room_change_permissions_moderators">"Moderador"</string>
|
||||
<string name="screen_room_change_permissions_remove_people">"Remover pessoas"</string>
|
||||
|
|
@ -14,6 +17,7 @@
|
|||
<string name="screen_room_change_permissions_room_name">"Altera o nome da sala"</string>
|
||||
<string name="screen_room_change_permissions_room_topic">"Alterar a descrição da sala"</string>
|
||||
<string name="screen_room_change_permissions_send_messages">"Enviar mensagens"</string>
|
||||
<string name="screen_room_change_permissions_title">"Permissões"</string>
|
||||
<string name="screen_room_change_role_administrators_title">"Editar Administradores"</string>
|
||||
<string name="screen_room_change_role_confirm_add_admin_description">"Não poderás desfazer esta ação. Estás a promover o utilizador para ter o mesmo nível de poder que tu."</string>
|
||||
<string name="screen_room_change_role_confirm_add_admin_title">"Adicionar administrador?"</string>
|
||||
|
|
@ -34,6 +38,12 @@
|
|||
<string name="screen_room_change_role_unsaved_changes_description">"Tens alterações por guardar."</string>
|
||||
<string name="screen_room_change_role_unsaved_changes_title">"Guardar alterações?"</string>
|
||||
<string name="screen_room_member_list_banned_empty">"Não há nenhum utilizador banido."</string>
|
||||
<plurals name="screen_room_member_list_banned_header_title">
|
||||
<item quantity="one">"%1$d banido"</item>
|
||||
<item quantity="other">"%1$d banidos"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_member_list_empty_search_subtitle">"Verifica a ortografia ou tenta uma nova pesquisa"</string>
|
||||
<string name="screen_room_member_list_empty_search_title">"Sem resultados para “%1$s”"</string>
|
||||
<plurals name="screen_room_member_list_header_title">
|
||||
<item quantity="one">"%1$d pessoa"</item>
|
||||
<item quantity="other">"%1$d pessoas"</item>
|
||||
|
|
@ -41,10 +51,15 @@
|
|||
<string name="screen_room_member_list_manage_member_remove_confirmation_ban">"Remover e banir participante"</string>
|
||||
<string name="screen_room_member_list_manage_member_remove_confirmation_kick">"Remover apenas"</string>
|
||||
<string name="screen_room_member_list_manage_member_unban_action">"Anular banimento"</string>
|
||||
<string name="screen_room_member_list_manage_member_unban_message">"Poderão juntar-se novamente a esta sala se forem convidados."</string>
|
||||
<string name="screen_room_member_list_manage_member_unban_message">"Poderão entrar novamente nesta sala se forem convidados."</string>
|
||||
<string name="screen_room_member_list_manage_member_unban_title">"Desbanir da sala"</string>
|
||||
<string name="screen_room_member_list_mode_banned">"Banidos"</string>
|
||||
<string name="screen_room_member_list_mode_members">"Participantes"</string>
|
||||
<plurals name="screen_room_member_list_pending_header_title">
|
||||
<item quantity="one">"%1$d convidado"</item>
|
||||
<item quantity="other">"%1$d convidados"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_member_list_pending_status">"Pendente"</string>
|
||||
<string name="screen_room_member_list_role_administrator">"Administrador"</string>
|
||||
<string name="screen_room_member_list_role_moderator">"Moderador"</string>
|
||||
<string name="screen_room_member_list_role_owner">"Dono / Dona"</string>
|
||||
|
|
@ -59,10 +74,12 @@
|
|||
<string name="screen_room_roles_and_permissions_messages_and_content">"Mensagens e conteúdo"</string>
|
||||
<string name="screen_room_roles_and_permissions_moderators">"Moderadores"</string>
|
||||
<string name="screen_room_roles_and_permissions_owners">"Donos"</string>
|
||||
<string name="screen_room_roles_and_permissions_permissions_header">"Permissões"</string>
|
||||
<string name="screen_room_roles_and_permissions_reset">"Repor permissões"</string>
|
||||
<string name="screen_room_roles_and_permissions_reset_confirm_description">"Ao repores as permissões, perderás as configurações atuais."</string>
|
||||
<string name="screen_room_roles_and_permissions_reset_confirm_title">"Repor as permissões?"</string>
|
||||
<string name="screen_room_roles_and_permissions_roles_header">"Cargos"</string>
|
||||
<string name="screen_room_roles_and_permissions_room_details">"Detalhes da sala"</string>
|
||||
<string name="screen_room_roles_and_permissions_space_details">"Detalhes do espaço"</string>
|
||||
<string name="screen_room_roles_and_permissions_title">"Cargos e permissões"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -88,7 +88,6 @@
|
|||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Хората могат да се присъединят само ако са поканени"</string>
|
||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Само с покана"</string>
|
||||
<string name="screen_security_and_privacy_room_access_section_header">"Достъп до стаята"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Членове на пространството"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Пространствата в момента не се поддържат"</string>
|
||||
<string name="screen_security_and_privacy_room_directory_visibility_toggle_title">"Видима в директорията на обществените стаи"</string>
|
||||
<string name="screen_security_and_privacy_room_history_section_header">"Кой може да чете историята"</string>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="crypto_history_sharing_room_info_hidden_badge_content">"Noví členové nevidí historii"</string>
|
||||
<string name="crypto_history_sharing_room_info_shared_badge_content">"Noví členové vidí historii"</string>
|
||||
<string name="crypto_history_sharing_room_info_world_readable_badge_content">"Každý může vidět historii"</string>
|
||||
<string name="screen_edit_room_address_room_address_section_footer">"Budete potřebovat adresu místnosti, aby byla viditelná v adresáři místností."</string>
|
||||
<string name="screen_edit_room_address_title">"Upravit adresu"</string>
|
||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Při aktualizaci nastavení oznámení došlo k chybě."</string>
|
||||
|
|
|
|||
|
|
@ -135,7 +135,6 @@ Nid ydym yn argymell galluogi amgryptio ar gyfer ystafelloedd y gall unrhyw un d
|
|||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Dim ond os cawn nhw wahoddiad gall pobl ymuno"</string>
|
||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Gwahoddiad yn unig"</string>
|
||||
<string name="screen_security_and_privacy_room_access_section_header">"Mynediad ystafell"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Aelodau gofod"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Nid yw gofodau\'n cael eu cefnogi ar hyn o bryd"</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_footer">"Bydd angen cyfeiriad ystafell arnoch i\'w wneud yn weladwy yn y cyfeiriadur."</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_header">"Cyfeiriad yr ystafell"</string>
|
||||
|
|
|
|||
|
|
@ -150,7 +150,6 @@ Vi anbefaler ikke at aktivere kryptering for rum, som alle kan finde og deltage
|
|||
<string name="screen_security_and_privacy_room_access_section_header">"Adgang"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Alle i autoriserede grupper kan deltage."</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Alle i %1$s kan deltage."</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Medlemmer af gruppen"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Grupper understøttes ikke i øjeblikket"</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_footer">"Du skal bruge en adresse for at gøre det synligt i det offentlige register."</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_header">"Adresse"</string>
|
||||
|
|
|
|||
|
|
@ -150,7 +150,6 @@ Wir empfehlen keine Verschlüsselung für Chats zu aktivieren, die jeder finden
|
|||
<string name="screen_security_and_privacy_room_access_section_header">"Zugang"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Jeder in autorisierten Spaces kann beitreten."</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Jeder in %1$s kann beitreten."</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Spacemitglieder"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Spaces werden zur Zeit nicht unterstützt."</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_footer">"Du benötigst eine Chat-Adresse, um den Chat im öffentlichen Verzeichnis sichtbar zu machen."</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_header">"Adresse"</string>
|
||||
|
|
|
|||
|
|
@ -120,7 +120,6 @@
|
|||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Τα άτομα μπορούν να συμμετάσχουν μόνο εάν έχουν προσκληθεί"</string>
|
||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Μόνο πρόσκληση"</string>
|
||||
<string name="screen_security_and_privacy_room_access_section_header">"Πρόσβαση στην αίθουσα"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Μέλη χώρου"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Οι χώροι δεν υποστηρίζονται προς το παρόν"</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_footer">"Θα χρειαστείτε μια διεύθυνση αίθουσας για να την κάνετε ορατή στον κατάλογο."</string>
|
||||
<string name="screen_security_and_privacy_room_directory_visibility_section_footer">"Επιστρέψτε την εύρεση αυτής της αίθουσας με αναζήτηση στον κατάλογο %1$s δημοσίων αιθουσών"</string>
|
||||
|
|
|
|||
|
|
@ -118,7 +118,6 @@ No recomendamos habilitar el cifrado para las salas que cualquiera pueda encontr
|
|||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Las personas solo pueden unirse si están invitadas"</string>
|
||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Solo por invitación"</string>
|
||||
<string name="screen_security_and_privacy_room_access_section_header">"Acceso a la sala"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Miembros del espacio"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"No se admiten los espacios por el momento."</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_footer">"Necesitarás una dirección de sala para que sea visible en el directorio."</string>
|
||||
<string name="screen_security_and_privacy_room_directory_visibility_section_footer">"Permite encontrar esta sala buscando en el directorio de salas públicas de %1$s"</string>
|
||||
|
|
|
|||
|
|
@ -106,7 +106,6 @@
|
|||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Gonbidatutako pertsonak bakarrik sartu ahal izango dira"</string>
|
||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Gonbidapen bidez"</string>
|
||||
<string name="screen_security_and_privacy_room_access_section_header">"Gelarako sarbidea"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Guneko kideak"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Gaur-gaurkoz ez da guneekin bateragarria"</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_header">"Gelaren helbidea"</string>
|
||||
<string name="screen_security_and_privacy_room_directory_visibility_toggle_title">"Gela publikoen direktorioan ikusgai"</string>
|
||||
|
|
|
|||
|
|
@ -123,7 +123,6 @@
|
|||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"افراد فقط در صورت دعوت میتوانند بپیوندند"</string>
|
||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"فقط دعوتی"</string>
|
||||
<string name="screen_security_and_privacy_room_access_section_header">"دسترسی اتاق"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"اعضای فضا"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"در حال حاضر فضاها پشتیبانی نمیشوند"</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_header">"نشانی اتاق"</string>
|
||||
<string name="screen_security_and_privacy_room_history_anyone_option_title">"هرکسی"</string>
|
||||
|
|
|
|||
|
|
@ -132,7 +132,6 @@ Emme suosittele salauksen ottamista käyttöön huoneissa, jotka kuka tahansa vo
|
|||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Vain kutsutut henkilöt voivat liittyä."</string>
|
||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Vain kutsutut"</string>
|
||||
<string name="screen_security_and_privacy_room_access_section_header">"Pääsy"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Tilan jäsenet"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Tiloja ei tällä hetkellä tueta"</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_footer">"Tarvitset osoitteen, jotta se näkyy julkisessa hakemistossa."</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_header">"Osoite"</string>
|
||||
|
|
|
|||
|
|
@ -151,7 +151,6 @@ Ne preporučujemo omogućavanje šifriranja za sobe koje svatko može pronaći i
|
|||
<string name="screen_security_and_privacy_room_access_section_header">"Pristup"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Svatko tko se nalazi u ovlaštenim prostorima može se pridružiti."</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Svatko u %1$s može se pridružiti."</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Članovi prostora"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Prostori trenutačno nisu podržani"</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_footer">"Trebat će vam adresa kako bi bila vidljiva u javnom direktoriju."</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_header">"Adresa"</string>
|
||||
|
|
|
|||
|
|
@ -150,7 +150,6 @@ Nem javasoljuk a titkosítás engedélyezését az olyan szobákban, amelyeket b
|
|||
<string name="screen_security_and_privacy_room_access_section_header">"Hozzáférés"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Bárki csatlakozhat, az engedélyezett terekből."</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Bárki csatlakozhatnak innen: %1$s."</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"A tér tagjai"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"A terek jelenleg nem támogatottak"</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_footer">"Szüksége lesz egy szobacímre, hogy láthatóvá tegye a szobakatalógusban."</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_header">"Cím"</string>
|
||||
|
|
|
|||
|
|
@ -117,7 +117,6 @@ Kami tidak menyarankan untuk mengaktifkan enkripsi untuk ruangan yang dapat dite
|
|||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Orang hanya dapat bergabung jika mereka diundang"</string>
|
||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Hanya undangan"</string>
|
||||
<string name="screen_security_and_privacy_room_access_section_header">"Akses ruangan"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Anggota space"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Space saat ini tidak didukung"</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_footer">"Anda akan memerlukan alamat ruangan untuk membuatnya terlihat dalam direktori."</string>
|
||||
<string name="screen_security_and_privacy_room_directory_visibility_section_footer">"Izinkan ruangan ini ditemukan dengan mencari direktori ruangan %1$s publik"</string>
|
||||
|
|
|
|||
|
|
@ -143,7 +143,6 @@ Non consigliamo di attivare la crittografia per le stanze che chiunque può trov
|
|||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Solo le persone invitate possono entrare."</string>
|
||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Solo su invito"</string>
|
||||
<string name="screen_security_and_privacy_room_access_section_header">"Accesso"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Membri dello spazio"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Gli spazi non sono attualmente supportati"</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_footer">"Per renderlo visibile nell\'elenco pubblico, avrai bisogno di un indirizzo."</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_header">"Indirizzo"</string>
|
||||
|
|
|
|||
|
|
@ -124,7 +124,6 @@
|
|||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"초대받은 사용자만 가입할 수 있습니다."</string>
|
||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"초대 전용"</string>
|
||||
<string name="screen_security_and_privacy_room_access_section_header">"방 액세스"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"스페이스 멤버들"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"스페이스는 현재 지원되지 않습니다"</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_footer">"디렉토리에 표시하려면 방 주소가 필요합니다."</string>
|
||||
<string name="screen_security_and_privacy_room_directory_visibility_section_footer">"%1$s 공개 방 디렉토리에서 이 방을 검색할 수 있도록 허용합니다"</string>
|
||||
|
|
|
|||
|
|
@ -149,7 +149,6 @@ Vi anbefaler ikke å aktivere kryptering for rom som hvem som helst kan finne og
|
|||
<string name="screen_security_and_privacy_room_access_section_header">"Tilgang"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Alle i autoriserte områder kan bli med."</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Alle i %1$s kan bli med."</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Medlemmer av område"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Områder støttes ikke for øyeblikket"</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_footer">"Du trenger en adresse for å gjøre den synlig i den offentlige katalogen."</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_header">"Adresse"</string>
|
||||
|
|
|
|||
|
|
@ -130,7 +130,6 @@ Odradzamy włączanie szyfrowania dla pokoi, które każdy może znaleźć i do
|
|||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Tylko osoby z zaproszeniem mogą dołączyć"</string>
|
||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Tylko zaproszenie"</string>
|
||||
<string name="screen_security_and_privacy_room_access_section_header">"Dostęp do pokoju"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Członkowie przestrzeni"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Przestrzenie nie są obecnie wspierane"</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_footer">"Aby pokój był widoczny w katalogu, potrzebny jest adres pokoju."</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_header">"Adres pokoju"</string>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue