Merge branch 'release/0.5.1' into main
This commit is contained in:
commit
2e3bae047d
668 changed files with 12193 additions and 2199 deletions
3
.github/renovate.json
vendored
3
.github/renovate.json
vendored
|
|
@ -7,7 +7,8 @@
|
||||||
"PR-Dependencies"
|
"PR-Dependencies"
|
||||||
],
|
],
|
||||||
"ignoreDeps" : [
|
"ignoreDeps" : [
|
||||||
"string:app_name"
|
"string:app_name",
|
||||||
|
"gradle"
|
||||||
],
|
],
|
||||||
"packageRules" : [
|
"packageRules" : [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
|
@ -36,7 +36,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Assemble debug Gplay APK
|
- name: Assemble debug Gplay APK
|
||||||
|
|
|
||||||
2
.github/workflows/build_enterprise.yml
vendored
2
.github/workflows/build_enterprise.yml
vendored
|
|
@ -44,7 +44,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Assemble debug Gplay Enterprise APK
|
- name: Assemble debug Gplay Enterprise APK
|
||||||
|
|
|
||||||
2
.github/workflows/generate_github_pages.yml
vendored
2
.github/workflows/generate_github_pages.yml
vendored
|
|
@ -19,7 +19,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Set up Python 3.12
|
- name: Set up Python 3.12
|
||||||
|
|
|
||||||
14
.github/workflows/gradle-wrapper-update.yml
vendored
14
.github/workflows/gradle-wrapper-update.yml
vendored
|
|
@ -1,18 +1,26 @@
|
||||||
name: Update Gradle Wrapper
|
name: Update Gradle Wrapper
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 0 * * *"
|
- cron: "0 0 * * *"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update-gradle-wrapper:
|
update-gradle-wrapper:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
# Skip in forks
|
||||||
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-java@v4
|
||||||
|
name: Use JDK 17
|
||||||
|
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
|
||||||
|
with:
|
||||||
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
|
java-version: '17'
|
||||||
- name: Update Gradle Wrapper
|
- name: Update Gradle Wrapper
|
||||||
uses: gradle-update/update-gradle-wrapper-action@v1
|
uses: gradle-update/update-gradle-wrapper-action@v1
|
||||||
# Skip in forks
|
|
||||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
|
||||||
target-branch: develop
|
target-branch: develop
|
||||||
|
labels: PR-Build
|
||||||
|
|
|
||||||
15
.github/workflows/gradle-wrapper-validation.yml
vendored
15
.github/workflows/gradle-wrapper-validation.yml
vendored
|
|
@ -1,15 +0,0 @@
|
||||||
name: "Validate Gradle Wrapper"
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
merge_group:
|
|
||||||
push:
|
|
||||||
branches: [ main, develop ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
validation:
|
|
||||||
name: "Validation"
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
# No concurrency required, this is a prerequisite to other actions and should run every time.
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: gradle/wrapper-validation-action@v3
|
|
||||||
2
.github/workflows/maestro.yml
vendored
2
.github/workflows/maestro.yml
vendored
|
|
@ -39,7 +39,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Assemble debug APK
|
- name: Assemble debug APK
|
||||||
|
|
|
||||||
4
.github/workflows/nightlyReports.yml
vendored
4
.github/workflows/nightlyReports.yml
vendored
|
|
@ -27,7 +27,7 @@ jobs:
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
|
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
cache-read-only: false
|
cache-read-only: false
|
||||||
|
|
||||||
|
|
@ -67,7 +67,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Dependency analysis
|
- name: Dependency analysis
|
||||||
|
|
|
||||||
12
.github/workflows/quality.yml
vendored
12
.github/workflows/quality.yml
vendored
|
|
@ -52,7 +52,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Set up Python 3.12
|
- name: Set up Python 3.12
|
||||||
|
|
@ -90,7 +90,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Run Konsist tests
|
- name: Run Konsist tests
|
||||||
|
|
@ -130,7 +130,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Build Gplay Debug
|
- name: Build Gplay Debug
|
||||||
|
|
@ -174,7 +174,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Run Detekt
|
- name: Run Detekt
|
||||||
|
|
@ -214,7 +214,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Run Ktlint check
|
- name: Run Ktlint check
|
||||||
|
|
@ -254,7 +254,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Run Knit
|
- name: Run Knit
|
||||||
|
|
|
||||||
2
.github/workflows/recordScreenshots.yml
vendored
2
.github/workflows/recordScreenshots.yml
vendored
|
|
@ -39,7 +39,7 @@ jobs:
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
# Add gradle cache, this should speed up the process
|
# Add gradle cache, this should speed up the process
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Record screenshots
|
- name: Record screenshots
|
||||||
|
|
|
||||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
|
@ -25,7 +25,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
- name: Create app bundle
|
- name: Create app bundle
|
||||||
env:
|
env:
|
||||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||||
|
|
@ -61,7 +61,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
- name: Create Enterprise app bundle
|
- name: Create Enterprise app bundle
|
||||||
env:
|
env:
|
||||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||||
|
|
@ -89,7 +89,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
- name: Create APKs
|
- name: Create APKs
|
||||||
env:
|
env:
|
||||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||||
|
|
|
||||||
2
.github/workflows/sonar.yml
vendored
2
.github/workflows/sonar.yml
vendored
|
|
@ -33,7 +33,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Build Gplay Debug
|
- name: Build Gplay Debug
|
||||||
|
|
|
||||||
2
.github/workflows/sync-localazy.yml
vendored
2
.github/workflows/sync-localazy.yml
vendored
|
|
@ -18,7 +18,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
- name: Set up Python 3.12
|
- name: Set up Python 3.12
|
||||||
|
|
|
||||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
|
@ -52,7 +52,7 @@ jobs:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
|
|
||||||
|
|
|
||||||
2
.idea/kotlinc.xml
generated
2
.idea/kotlinc.xml
generated
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="version" value="1.9.24" />
|
<option name="version" value="1.9.25" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
77
CHANGES.md
77
CHANGES.md
|
|
@ -1,3 +1,80 @@
|
||||||
|
Changes in Element X v0.5.0 (2024-07-24)
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
### 🙌 Improvements
|
||||||
|
* Add icon for "Mark as read" and "Mark as unread" actions. by @bmarty in https://github.com/element-hq/element-x-android/pull/3144
|
||||||
|
* Add support for Picture In Picture for Element Call by @bmarty in https://github.com/element-hq/element-x-android/pull/3159
|
||||||
|
* Set pin grace period to 2 minutes by @bmarty in https://github.com/element-hq/element-x-android/pull/3172
|
||||||
|
* Unify the way we decide whether a room is a DM or a group room by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3100
|
||||||
|
* Subscribe to `RoomListItems` in the visible range by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3169
|
||||||
|
* Improve pip and add feature flag. by @bmarty in https://github.com/element-hq/element-x-android/pull/3199
|
||||||
|
* Open Source licenses: add color for links. by @bmarty in https://github.com/element-hq/element-x-android/pull/3215
|
||||||
|
* Cancel ringing call notification on call cancellation by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3047
|
||||||
|
|
||||||
|
### 🐛 Bugfixes
|
||||||
|
* Fix `MainActionButton` layout for long texts by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3158
|
||||||
|
* Always follow the desired theme for Pin, Incoming Call and Element Call screens by @bmarty in https://github.com/element-hq/element-x-android/pull/3165
|
||||||
|
* Fix empty screen issue after clearing the cache by @bmarty in https://github.com/element-hq/element-x-android/pull/3163
|
||||||
|
* Restore intentional mentions in the markdown/plain text editor by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3193
|
||||||
|
* Fix crash in the room list after a forced log out in background by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3180
|
||||||
|
* Clear existing notification when a room is marked as read by @bmarty in https://github.com/element-hq/element-x-android/pull/3203
|
||||||
|
* Fix crash when Pin code screen is displayed by @bmarty in https://github.com/element-hq/element-x-android/pull/3205
|
||||||
|
* Fix pillification not working for non formatted message bodies by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3201
|
||||||
|
* Update grammar on Matrix Ids to be more spec compliant and render error instead of infinite loading in room member list screen by @bmarty in https://github.com/element-hq/element-x-android/pull/3206
|
||||||
|
* Reduce the risk of text truncation in buttons. by @bmarty in https://github.com/element-hq/element-x-android/pull/3209
|
||||||
|
* Ensure that the manual dark theme is rendering correctly regarding -night resource and keyboard by @bmarty in https://github.com/element-hq/element-x-android/pull/3216
|
||||||
|
* Fix rendering issue of SunsetPage in dark mode by @bmarty in https://github.com/element-hq/element-x-android/pull/3217
|
||||||
|
* Fix linkification not working for `Spanned` strings in text messages by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3233
|
||||||
|
* Edit : fallback to room.edit when timeline item is not found. by @ganfra in https://github.com/element-hq/element-x-android/pull/3239
|
||||||
|
|
||||||
|
### 🗣 Translations
|
||||||
|
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3156
|
||||||
|
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3192
|
||||||
|
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3232
|
||||||
|
|
||||||
|
### 🧱 Build
|
||||||
|
* Remove Showkase processor not found warning from Danger by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3148
|
||||||
|
* Set targetSDK to 34 by @bmarty in https://github.com/element-hq/element-x-android/pull/3149
|
||||||
|
* Add a local copy of `inplace-fix.py` and `fix-pg-map-id.py` by @bmarty in https://github.com/element-hq/element-x-android/pull/3167
|
||||||
|
* Only add private SSH keys and clone submodules in the original repo by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3225
|
||||||
|
* Fix CI for forks by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3226
|
||||||
|
|
||||||
|
### Dependency upgrades
|
||||||
|
* Update dependency io.element.android:compound-android to v0.0.7 by @renovate in https://github.com/element-hq/element-x-android/pull/3143
|
||||||
|
* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.31 by @renovate in https://github.com/element-hq/element-x-android/pull/3145
|
||||||
|
* Update dependency com.squareup:kotlinpoet to v1.18.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3150
|
||||||
|
* Update dependency org.robolectric:robolectric to v4.13 by @renovate in https://github.com/element-hq/element-x-android/pull/3157
|
||||||
|
* Update plugin dependencycheck to v10.0.2 by @renovate in https://github.com/element-hq/element-x-android/pull/3154
|
||||||
|
* Update wysiwyg to v2.37.5 by @renovate in https://github.com/element-hq/element-x-android/pull/3162
|
||||||
|
* Update plugin sonarqube to v5.1.0.4882 by @renovate in https://github.com/element-hq/element-x-android/pull/3139
|
||||||
|
* Update dependency org.jsoup:jsoup to v1.18.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3171
|
||||||
|
* Update dependency com.google.firebase:firebase-bom to v33.1.2 by @renovate in https://github.com/element-hq/element-x-android/pull/3178
|
||||||
|
* Update telephoto to v0.12.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3191
|
||||||
|
* Update dependency com.google.truth:truth to v1.4.4 by @renovate in https://github.com/element-hq/element-x-android/pull/3187
|
||||||
|
* Update dependency com.squareup:kotlinpoet to v1.18.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3194
|
||||||
|
* Update dependency io.mockk:mockk to v1.13.12 by @renovate in https://github.com/element-hq/element-x-android/pull/3198
|
||||||
|
* Update dependency io.sentry:sentry-android to v7.12.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3200
|
||||||
|
* Update plugin dependencycheck to v10.0.3 by @renovate in https://github.com/element-hq/element-x-android/pull/3204
|
||||||
|
* Update dependency gradle to v8.9 by @renovate in https://github.com/element-hq/element-x-android/pull/3177
|
||||||
|
* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.32 by @renovate in https://github.com/element-hq/element-x-android/pull/3202
|
||||||
|
* Update coil to v2.7.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3210
|
||||||
|
* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.33 by @renovate in https://github.com/element-hq/element-x-android/pull/3220
|
||||||
|
* Update wysiwyg to v2.37.7 by @renovate in https://github.com/element-hq/element-x-android/pull/3218
|
||||||
|
* Update telephoto to v0.12.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3230
|
||||||
|
* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.34 by @renovate in https://github.com/element-hq/element-x-android/pull/3237
|
||||||
|
|
||||||
|
### Others
|
||||||
|
* Reduce delay when selecting room list filters by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3160
|
||||||
|
* Add `--alignment-preserved true` when signing APK for F-Droid. by @bmarty in https://github.com/element-hq/element-x-android/pull/3161
|
||||||
|
* Ensure that all callback plugins are invoked. by @bmarty in https://github.com/element-hq/element-x-android/pull/3146
|
||||||
|
* Add generated screen to show open source licenses in Gplay variant by @bmarty in https://github.com/element-hq/element-x-android/pull/3207
|
||||||
|
* Performance : improve time to open a room. by @ganfra in https://github.com/element-hq/element-x-android/pull/3186
|
||||||
|
* Add logging to help debug forced logout issues by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3208
|
||||||
|
* Use the right filename for log files so they're sorted in rageshakes by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3219
|
||||||
|
* Compose : add immutability to some Reaction classes by @ganfra in https://github.com/element-hq/element-x-android/pull/3224
|
||||||
|
* Fix stickers display text on room summary by @surakin in https://github.com/element-hq/element-x-android/pull/3221
|
||||||
|
* Rework FakeMatrixRoom so that it contains only lambdas. by @bmarty in https://github.com/element-hq/element-x-android/pull/3229
|
||||||
|
|
||||||
Changes in Element X v0.4.16 (2024-07-05)
|
Changes in Element X v0.4.16 (2024-07-05)
|
||||||
=========================================
|
=========================================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
[](https://github.com/element-hq/element-x-android/actions/workflows/build.yml?query=branch%3Adevelop)
|
[](https://github.com/element-hq/element-x-android/actions/workflows/build.yml?query=branch%3Adevelop)
|
||||||
[](https://sonarcloud.io/summary/new_code?id=vector-im_element-x-android)
|
[](https://sonarcloud.io/summary/new_code?id=element-x-android)
|
||||||
[](https://sonarcloud.io/summary/new_code?id=vector-im_element-x-android)
|
[](https://sonarcloud.io/summary/new_code?id=element-x-android)
|
||||||
[](https://sonarcloud.io/summary/new_code?id=vector-im_element-x-android)
|
[](https://sonarcloud.io/summary/new_code?id=element-x-android)
|
||||||
[](https://codecov.io/github/vector-im/element-x-android)
|
[](https://codecov.io/github/vector-im/element-x-android)
|
||||||
[](https://matrix.to/#/#element-x-android:matrix.org)
|
[](https://matrix.to/#/#element-x-android:matrix.org)
|
||||||
[](https://localazy.com/p/element)
|
[](https://localazy.com/p/element)
|
||||||
|
|
|
||||||
2
app/proguard-rules.pro
vendored
2
app/proguard-rules.pro
vendored
|
|
@ -40,3 +40,5 @@
|
||||||
-keepclassmembers class android.view.JavaViewSpy {
|
-keepclassmembers class android.view.JavaViewSpy {
|
||||||
static int windowAttachCount(android.view.View);
|
static int windowAttachCount(android.view.View);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-keep class io.element.android.x.di.** { *; }
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
<locale android:name="in"/>
|
<locale android:name="in"/>
|
||||||
<locale android:name="it"/>
|
<locale android:name="it"/>
|
||||||
<locale android:name="ka"/>
|
<locale android:name="ka"/>
|
||||||
|
<locale android:name="nl"/>
|
||||||
<locale android:name="pl"/>
|
<locale android:name="pl"/>
|
||||||
<locale android:name="pt"/>
|
<locale android:name="pt"/>
|
||||||
<locale android:name="pt_BR"/>
|
<locale android:name="pt_BR"/>
|
||||||
|
|
@ -21,6 +22,7 @@
|
||||||
<locale android:name="sk"/>
|
<locale android:name="sk"/>
|
||||||
<locale android:name="sv"/>
|
<locale android:name="sv"/>
|
||||||
<locale android:name="uk"/>
|
<locale android:name="uk"/>
|
||||||
|
<locale android:name="uz"/>
|
||||||
<locale android:name="zh-CN"/>
|
<locale android:name="zh-CN"/>
|
||||||
<locale android:name="zh-TW"/>
|
<locale android:name="zh-TW"/>
|
||||||
</locale-config>
|
</locale-config>
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ object TimelineConfig {
|
||||||
StateEventType.ROOM_GUEST_ACCESS,
|
StateEventType.ROOM_GUEST_ACCESS,
|
||||||
StateEventType.ROOM_HISTORY_VISIBILITY,
|
StateEventType.ROOM_HISTORY_VISIBILITY,
|
||||||
StateEventType.ROOM_JOIN_RULES,
|
StateEventType.ROOM_JOIN_RULES,
|
||||||
StateEventType.ROOM_PINNED_EVENTS,
|
|
||||||
StateEventType.ROOM_POWER_LEVELS,
|
StateEventType.ROOM_POWER_LEVELS,
|
||||||
StateEventType.ROOM_SERVER_ACL,
|
StateEventType.ROOM_SERVER_ACL,
|
||||||
StateEventType.ROOM_TOMBSTONE,
|
StateEventType.ROOM_TOMBSTONE,
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ dependencies {
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.deeplink)
|
implementation(projects.libraries.deeplink)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
implementation(projects.libraries.oidc.api)
|
||||||
implementation(projects.libraries.preferences.api)
|
implementation(projects.libraries.preferences.api)
|
||||||
implementation(projects.libraries.push.api)
|
implementation(projects.libraries.push.api)
|
||||||
implementation(projects.libraries.pushproviders.api)
|
implementation(projects.libraries.pushproviders.api)
|
||||||
|
|
@ -66,6 +67,7 @@ dependencies {
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
|
testImplementation(projects.libraries.oidc.impl)
|
||||||
testImplementation(projects.libraries.push.test)
|
testImplementation(projects.libraries.push.test)
|
||||||
testImplementation(projects.libraries.pushproviders.test)
|
testImplementation(projects.libraries.pushproviders.test)
|
||||||
testImplementation(projects.features.networkmonitor.test)
|
testImplementation(projects.features.networkmonitor.test)
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,6 @@ import io.element.android.appnav.intent.ResolvedIntent
|
||||||
import io.element.android.appnav.root.RootNavStateFlowFactory
|
import io.element.android.appnav.root.RootNavStateFlowFactory
|
||||||
import io.element.android.appnav.root.RootPresenter
|
import io.element.android.appnav.root.RootPresenter
|
||||||
import io.element.android.appnav.root.RootView
|
import io.element.android.appnav.root.RootView
|
||||||
import io.element.android.features.login.api.oidc.OidcAction
|
|
||||||
import io.element.android.features.login.api.oidc.OidcActionFlow
|
|
||||||
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
|
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
|
||||||
import io.element.android.features.signedout.api.SignedOutEntryPoint
|
import io.element.android.features.signedout.api.SignedOutEntryPoint
|
||||||
import io.element.android.features.viewfolder.api.ViewFolderEntryPoint
|
import io.element.android.features.viewfolder.api.ViewFolderEntryPoint
|
||||||
|
|
@ -58,6 +56,8 @@ import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||||
|
import io.element.android.libraries.oidc.api.OidcAction
|
||||||
|
import io.element.android.libraries.oidc.api.OidcActionFlow
|
||||||
import io.element.android.libraries.sessionstorage.api.LoggedInState
|
import io.element.android.libraries.sessionstorage.api.LoggedInState
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,12 @@
|
||||||
package io.element.android.appnav.intent
|
package io.element.android.appnav.intent
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import io.element.android.features.login.api.oidc.OidcAction
|
|
||||||
import io.element.android.features.login.api.oidc.OidcIntentResolver
|
|
||||||
import io.element.android.libraries.deeplink.DeeplinkData
|
import io.element.android.libraries.deeplink.DeeplinkData
|
||||||
import io.element.android.libraries.deeplink.DeeplinkParser
|
import io.element.android.libraries.deeplink.DeeplinkParser
|
||||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||||
|
import io.element.android.libraries.oidc.api.OidcAction
|
||||||
|
import io.element.android.libraries.oidc.api.OidcIntentResolver
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,6 @@ import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.login.api.oidc.OidcAction
|
|
||||||
import io.element.android.features.login.impl.oidc.DefaultOidcIntentResolver
|
|
||||||
import io.element.android.features.login.impl.oidc.OidcUrlParser
|
|
||||||
import io.element.android.libraries.deeplink.DeepLinkCreator
|
import io.element.android.libraries.deeplink.DeepLinkCreator
|
||||||
import io.element.android.libraries.deeplink.DeeplinkData
|
import io.element.android.libraries.deeplink.DeeplinkData
|
||||||
import io.element.android.libraries.deeplink.DeeplinkParser
|
import io.element.android.libraries.deeplink.DeeplinkParser
|
||||||
|
|
@ -33,6 +30,9 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
||||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||||
|
import io.element.android.libraries.oidc.api.OidcAction
|
||||||
|
import io.element.android.libraries.oidc.impl.DefaultOidcIntentResolver
|
||||||
|
import io.element.android.libraries.oidc.impl.OidcUrlParser
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
import org.junit.Assert.assertThrows
|
import org.junit.Assert.assertThrows
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ allprojects {
|
||||||
config.from(files("$rootDir/tools/detekt/detekt.yml"))
|
config.from(files("$rootDir/tools/detekt/detekt.yml"))
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
detektPlugins("io.nlopez.compose.rules:detekt:0.4.5")
|
detektPlugins("io.nlopez.compose.rules:detekt:0.4.10")
|
||||||
}
|
}
|
||||||
|
|
||||||
// KtLint
|
// KtLint
|
||||||
|
|
@ -129,11 +129,11 @@ dependencyAnalysis {
|
||||||
// To run a sonar analysis:
|
// To run a sonar analysis:
|
||||||
// Run './gradlew sonar -Dsonar.login=<SONAR_LOGIN>'
|
// Run './gradlew sonar -Dsonar.login=<SONAR_LOGIN>'
|
||||||
// The SONAR_LOGIN is stored in passbolt as Token Sonar Cloud Bma
|
// The SONAR_LOGIN is stored in passbolt as Token Sonar Cloud Bma
|
||||||
// Sonar result can be found here: https://sonarcloud.io/project/overview?id=vector-im_element-x-android
|
// Sonar result can be found here: https://sonarcloud.io/project/overview?id=element-x-android
|
||||||
sonar {
|
sonar {
|
||||||
properties {
|
properties {
|
||||||
property("sonar.projectName", "element-x-android")
|
property("sonar.projectName", "element-x-android")
|
||||||
property("sonar.projectKey", "vector-im_element-x-android")
|
property("sonar.projectKey", "element-x-android")
|
||||||
property("sonar.host.url", "https://sonarcloud.io")
|
property("sonar.host.url", "https://sonarcloud.io")
|
||||||
property("sonar.projectVersion", "1.0") // TODO project(":app").android.defaultConfig.versionName)
|
property("sonar.projectVersion", "1.0") // TODO project(":app").android.defaultConfig.versionName)
|
||||||
property("sonar.sourceEncoding", "UTF-8")
|
property("sonar.sourceEncoding", "UTF-8")
|
||||||
|
|
@ -141,7 +141,7 @@ sonar {
|
||||||
property("sonar.links.ci", "https://github.com/element-hq/element-x-android/actions")
|
property("sonar.links.ci", "https://github.com/element-hq/element-x-android/actions")
|
||||||
property("sonar.links.scm", "https://github.com/element-hq/element-x-android/")
|
property("sonar.links.scm", "https://github.com/element-hq/element-x-android/")
|
||||||
property("sonar.links.issue", "https://github.com/element-hq/element-x-android/issues")
|
property("sonar.links.issue", "https://github.com/element-hq/element-x-android/issues")
|
||||||
property("sonar.organization", "new_vector_ltd_organization")
|
property("sonar.organization", "element-hq")
|
||||||
property("sonar.login", if (project.hasProperty("SONAR_LOGIN")) project.property("SONAR_LOGIN")!! else "invalid")
|
property("sonar.login", if (project.hasProperty("SONAR_LOGIN")) project.property("SONAR_LOGIN")!! else "invalid")
|
||||||
|
|
||||||
// exclude source code from analyses separated by a colon (:)
|
// exclude source code from analyses separated by a colon (:)
|
||||||
|
|
|
||||||
2
fastlane/metadata/android/en-US/changelogs/40005010.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40005010.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: Element Call improvements and bug fixes.
|
||||||
|
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_analytics_settings_help_us_improve">"Deel anonieme gebruiksgegevens om ons te helpen problemen te identificeren."</string>
|
||||||
|
<string name="screen_analytics_settings_read_terms">"Je kunt al onze voorwaarden %1$s lezen."</string>
|
||||||
|
<string name="screen_analytics_settings_read_terms_content_link">"hier"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_analytics_settings_help_us_improve">"Udostępniaj anonimowe dane dotyczące użytkowania, aby pomóc nam identyfikować problemy."</string>
|
<string name="screen_analytics_settings_help_us_improve">"Udostępniaj anonimowe dane użytkowania, aby pomóc nam identyfikować problemy."</string>
|
||||||
<string name="screen_analytics_settings_read_terms">"Możesz przeczytać wszystkie nasze warunki %1$s."</string>
|
<string name="screen_analytics_settings_read_terms">"Przeczytaj nasze warunki użytkowania %1$s."</string>
|
||||||
<string name="screen_analytics_settings_read_terms_content_link">"tutaj"</string>
|
<string name="screen_analytics_settings_read_terms_content_link">"tutaj"</string>
|
||||||
<string name="screen_analytics_settings_share_data">"Udostępniaj dane analityczne"</string>
|
<string name="screen_analytics_settings_share_data">"Udostępniaj dane analityczne"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_analytics_settings_help_us_improve">"Muammolarni aniqlashda yordam berish uchun anonim foydalanish maʼlumotlarini baham koʻring."</string>
|
||||||
|
<string name="screen_analytics_settings_read_terms">"Siz bizning barcha shartlarimizni o\'qishingiz mumkin%1$s."</string>
|
||||||
|
<string name="screen_analytics_settings_read_terms_content_link">"Bu yerga"</string>
|
||||||
|
<string name="screen_analytics_settings_share_data">"Analitik ma\'lumotlarni ulashish"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_analytics_prompt_data_usage">"We zullen geen persoonlijke gegevens registreren of er een profiel van maken"</string>
|
||||||
|
<string name="screen_analytics_prompt_help_us_improve">"Deel anonieme gebruiksgegevens om ons te helpen problemen te identificeren."</string>
|
||||||
|
<string name="screen_analytics_prompt_read_terms">"Je kunt al onze voorwaarden %1$s lezen."</string>
|
||||||
|
<string name="screen_analytics_prompt_read_terms_content_link">"hier"</string>
|
||||||
|
<string name="screen_analytics_prompt_settings">"Je kunt dit op elk moment uitschakelen"</string>
|
||||||
|
<string name="screen_analytics_prompt_third_party_sharing">"We delen je gegevens niet met derden"</string>
|
||||||
|
<string name="screen_analytics_prompt_title">"Help %1$s te verbeteren"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_analytics_prompt_data_usage">"Nie będziemy rejestrować ani profilować żadnych danych osobistych"</string>
|
<string name="screen_analytics_prompt_data_usage">"Nie będziemy rejestrować ani profilować żadnych danych osobistych"</string>
|
||||||
<string name="screen_analytics_prompt_help_us_improve">"Udostępniaj anonimowe dane dotyczące użytkowania, aby pomóc nam identyfikować problemy."</string>
|
<string name="screen_analytics_prompt_help_us_improve">"Udostępniaj anonimowe dane użytkowania, aby pomóc nam identyfikować problemy."</string>
|
||||||
<string name="screen_analytics_prompt_read_terms">"Możesz przeczytać wszystkie nasze warunki %1$s."</string>
|
<string name="screen_analytics_prompt_read_terms">"Przeczytaj nasze warunki użytkowania %1$s."</string>
|
||||||
<string name="screen_analytics_prompt_read_terms_content_link">"tutaj"</string>
|
<string name="screen_analytics_prompt_read_terms_content_link">"tutaj"</string>
|
||||||
<string name="screen_analytics_prompt_settings">"Możesz to wyłączyć w dowolnym momencie"</string>
|
<string name="screen_analytics_prompt_settings">"Możesz to wyłączyć w dowolnym momencie"</string>
|
||||||
<string name="screen_analytics_prompt_third_party_sharing">"Nie będziemy udostępniać Twoich danych podmiotom trzecim"</string>
|
<string name="screen_analytics_prompt_third_party_sharing">"Nie będziemy udostępniać Twoich danych stronom trzecim"</string>
|
||||||
<string name="screen_analytics_prompt_title">"Pomóż nam ulepszyć %1$s"</string>
|
<string name="screen_analytics_prompt_title">"Pomóż nam ulepszyć %1$s"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_analytics_prompt_data_usage">"Biz hech qanday shaxsiy ma\'lumotlarni yozmaymiz yoki profilga kiritmaymiz"</string>
|
||||||
|
<string name="screen_analytics_prompt_help_us_improve">"Muammolarni aniqlashda yordam berish uchun anonim foydalanish maʼlumotlarini baham koʻring."</string>
|
||||||
|
<string name="screen_analytics_prompt_read_terms">"Siz bizning barcha shartlarimizni o\'qishingiz mumkin%1$s."</string>
|
||||||
|
<string name="screen_analytics_prompt_read_terms_content_link">"Bu yerga"</string>
|
||||||
|
<string name="screen_analytics_prompt_settings">"Buni istalgan vaqtda oʻchirib qoʻyishingiz mumkin"</string>
|
||||||
|
<string name="screen_analytics_prompt_third_party_sharing">"Biz sizning ma\'lumotlaringizni uchinchi tomonlar bilan baham ko\'rmaymiz"</string>
|
||||||
|
<string name="screen_analytics_prompt_title">"Yaxshilashga yordam bering%1$s"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<string name="screen_analytics_prompt_help_us_improve">"共享匿名使用数据以帮助我们排查问题。"</string>
|
<string name="screen_analytics_prompt_help_us_improve">"共享匿名使用数据以帮助我们排查问题。"</string>
|
||||||
<string name="screen_analytics_prompt_read_terms">"您可以阅读我们的所有条款 %1$s。"</string>
|
<string name="screen_analytics_prompt_read_terms">"您可以阅读我们的所有条款 %1$s。"</string>
|
||||||
<string name="screen_analytics_prompt_read_terms_content_link">"此处"</string>
|
<string name="screen_analytics_prompt_read_terms_content_link">"此处"</string>
|
||||||
<string name="screen_analytics_prompt_settings">"你可以随时关闭此功能"</string>
|
<string name="screen_analytics_prompt_settings">"可以随时关闭此功能"</string>
|
||||||
<string name="screen_analytics_prompt_third_party_sharing">"我们不会与第三方共享您的数据"</string>
|
<string name="screen_analytics_prompt_third_party_sharing">"我们不会与第三方共享您的数据"</string>
|
||||||
<string name="screen_analytics_prompt_title">"帮助改进 %1$s"</string>
|
<string name="screen_analytics_prompt_title">"帮助改进 %1$s"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,10 @@
|
||||||
|
|
||||||
package io.element.android.features.call.impl.pip
|
package io.element.android.features.call.impl.pip
|
||||||
|
|
||||||
|
import io.element.android.features.call.impl.utils.PipController
|
||||||
|
|
||||||
sealed interface PictureInPictureEvents {
|
sealed interface PictureInPictureEvents {
|
||||||
|
data class SetPipController(val pipController: PipController) : PictureInPictureEvents
|
||||||
data object EnterPictureInPicture : PictureInPictureEvents
|
data object EnterPictureInPicture : PictureInPictureEvents
|
||||||
|
data class OnPictureInPictureModeChanged(val isInPip: Boolean) : PictureInPictureEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,17 +16,17 @@
|
||||||
|
|
||||||
package io.element.android.features.call.impl.pip
|
package io.element.android.features.call.impl.pip
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.PictureInPictureParams
|
|
||||||
import android.os.Build
|
|
||||||
import android.util.Rational
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import io.element.android.features.call.impl.utils.PipController
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.lang.ref.WeakReference
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private val loggerTag = LoggerTag("PiP")
|
private val loggerTag = LoggerTag("PiP")
|
||||||
|
|
@ -35,71 +35,69 @@ class PictureInPicturePresenter @Inject constructor(
|
||||||
pipSupportProvider: PipSupportProvider,
|
pipSupportProvider: PipSupportProvider,
|
||||||
) : Presenter<PictureInPictureState> {
|
) : Presenter<PictureInPictureState> {
|
||||||
private val isPipSupported = pipSupportProvider.isPipSupported()
|
private val isPipSupported = pipSupportProvider.isPipSupported()
|
||||||
private var isInPictureInPicture = mutableStateOf(false)
|
private var pipView: PipView? = null
|
||||||
private var hostActivity: WeakReference<Activity>? = null
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): PictureInPictureState {
|
override fun present(): PictureInPictureState {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
var isInPictureInPicture by remember { mutableStateOf(false) }
|
||||||
|
var pipController by remember { mutableStateOf<PipController?>(null) }
|
||||||
|
|
||||||
fun handleEvent(event: PictureInPictureEvents) {
|
fun handleEvent(event: PictureInPictureEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
PictureInPictureEvents.EnterPictureInPicture -> switchToPip()
|
is PictureInPictureEvents.SetPipController -> {
|
||||||
|
pipController = event.pipController
|
||||||
|
}
|
||||||
|
PictureInPictureEvents.EnterPictureInPicture -> {
|
||||||
|
coroutineScope.launch {
|
||||||
|
switchToPip(pipController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PictureInPictureEvents.OnPictureInPictureModeChanged -> {
|
||||||
|
Timber.tag(loggerTag.value).d("onPictureInPictureModeChanged: ${event.isInPip}")
|
||||||
|
isInPictureInPicture = event.isInPip
|
||||||
|
if (event.isInPip) {
|
||||||
|
pipController?.enterPip()
|
||||||
|
} else {
|
||||||
|
pipController?.exitPip()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return PictureInPictureState(
|
return PictureInPictureState(
|
||||||
supportPip = isPipSupported,
|
supportPip = isPipSupported,
|
||||||
isInPictureInPicture = isInPictureInPicture.value,
|
isInPictureInPicture = isInPictureInPicture,
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onCreate(activity: Activity) {
|
fun setPipView(pipView: PipView?) {
|
||||||
if (isPipSupported) {
|
if (isPipSupported) {
|
||||||
Timber.tag(loggerTag.value).d("onCreate: Setting PiP params")
|
Timber.tag(loggerTag.value).d("Setting PiP params")
|
||||||
hostActivity = WeakReference(activity)
|
this.pipView = pipView
|
||||||
hostActivity?.get()?.setPictureInPictureParams(getPictureInPictureParams())
|
pipView?.setPipParams()
|
||||||
} else {
|
} else {
|
||||||
Timber.tag(loggerTag.value).d("onCreate: PiP is not supported")
|
Timber.tag(loggerTag.value).d("setPipView: PiP is not supported")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onDestroy() {
|
|
||||||
Timber.tag(loggerTag.value).d("onDestroy")
|
|
||||||
hostActivity?.clear()
|
|
||||||
hostActivity = null
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
private fun getPictureInPictureParams(): PictureInPictureParams {
|
|
||||||
return PictureInPictureParams.Builder()
|
|
||||||
// Portrait for calls seems more appropriate
|
|
||||||
.setAspectRatio(Rational(3, 5))
|
|
||||||
.apply {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
setAutoEnterEnabled(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enters Picture-in-Picture mode.
|
* Enters Picture-in-Picture mode, if allowed by Element Call.
|
||||||
*/
|
*/
|
||||||
private fun switchToPip() {
|
private suspend fun switchToPip(pipController: PipController?) {
|
||||||
if (isPipSupported) {
|
if (isPipSupported) {
|
||||||
Timber.tag(loggerTag.value).d("Switch to PiP mode")
|
if (pipController == null) {
|
||||||
hostActivity?.get()?.enterPictureInPictureMode(getPictureInPictureParams())
|
Timber.tag(loggerTag.value).w("webPipApi is not available")
|
||||||
?.also { Timber.tag(loggerTag.value).d("Switch to PiP mode result: $it") }
|
}
|
||||||
|
if (pipController == null || pipController.canEnterPip()) {
|
||||||
|
Timber.tag(loggerTag.value).d("Switch to PiP mode")
|
||||||
|
pipView?.enterPipMode()
|
||||||
|
?.also { Timber.tag(loggerTag.value).d("Switch to PiP mode result: $it") }
|
||||||
|
} else {
|
||||||
|
Timber.tag(loggerTag.value).w("Cannot enter PiP mode, hangup the call")
|
||||||
|
pipView?.hangUp()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
|
|
||||||
Timber.tag(loggerTag.value).d("onPictureInPictureModeChanged: $isInPictureInPictureMode")
|
|
||||||
isInPictureInPicture.value = isInPictureInPictureMode
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onUserLeaveHint() {
|
|
||||||
Timber.tag(loggerTag.value).d("onUserLeaveHint")
|
|
||||||
switchToPip()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,6 @@ import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.libraries.core.bool.orFalse
|
import io.element.android.libraries.core.bool.orFalse
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.di.ApplicationContext
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
interface PipSupportProvider {
|
interface PipSupportProvider {
|
||||||
|
|
@ -37,15 +34,10 @@ interface PipSupportProvider {
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultPipSupportProvider @Inject constructor(
|
class DefaultPipSupportProvider @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val featureFlagService: FeatureFlagService,
|
|
||||||
) : PipSupportProvider {
|
) : PipSupportProvider {
|
||||||
override fun isPipSupported(): Boolean {
|
override fun isPipSupported(): Boolean {
|
||||||
val isSupportedByTheOs = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
val isSupportedByTheOs = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
||||||
context.packageManager?.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).orFalse()
|
context.packageManager?.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).orFalse()
|
||||||
return if (isSupportedByTheOs) {
|
return isSupportedByTheOs
|
||||||
runBlocking { featureFlagService.isFeatureEnabled(FeatureFlags.PictureInPicture) }
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.lockscreen.impl.unlock.signout
|
package io.element.android.features.call.impl.pip
|
||||||
|
|
||||||
interface SignOut {
|
interface PipView {
|
||||||
suspend operator fun invoke(): String?
|
fun setPipParams()
|
||||||
|
fun enterPipMode(): Boolean
|
||||||
|
fun hangUp()
|
||||||
}
|
}
|
||||||
|
|
@ -40,6 +40,7 @@ import io.element.android.features.call.impl.pip.PictureInPictureEvents
|
||||||
import io.element.android.features.call.impl.pip.PictureInPictureState
|
import io.element.android.features.call.impl.pip.PictureInPictureState
|
||||||
import io.element.android.features.call.impl.pip.PictureInPictureStateProvider
|
import io.element.android.features.call.impl.pip.PictureInPictureStateProvider
|
||||||
import io.element.android.features.call.impl.pip.aPictureInPictureState
|
import io.element.android.features.call.impl.pip.aPictureInPictureState
|
||||||
|
import io.element.android.features.call.impl.utils.WebViewPipController
|
||||||
import io.element.android.features.call.impl.utils.WebViewWidgetMessageInterceptor
|
import io.element.android.features.call.impl.utils.WebViewWidgetMessageInterceptor
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||||
|
|
@ -95,9 +96,9 @@ internal fun CallScreenView(
|
||||||
}
|
}
|
||||||
CallWebView(
|
CallWebView(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
.consumeWindowInsets(padding)
|
.consumeWindowInsets(padding)
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
url = state.urlState,
|
url = state.urlState,
|
||||||
userAgent = state.userAgent,
|
userAgent = state.userAgent,
|
||||||
onPermissionsRequest = { request ->
|
onPermissionsRequest = { request ->
|
||||||
|
|
@ -108,6 +109,8 @@ internal fun CallScreenView(
|
||||||
onWebViewCreate = { webView ->
|
onWebViewCreate = { webView ->
|
||||||
val interceptor = WebViewWidgetMessageInterceptor(webView)
|
val interceptor = WebViewWidgetMessageInterceptor(webView)
|
||||||
state.eventSink(CallScreenEvents.SetupMessageChannels(interceptor))
|
state.eventSink(CallScreenEvents.SetupMessageChannels(interceptor))
|
||||||
|
val pipController = WebViewPipController(webView)
|
||||||
|
pipState.eventSink(PictureInPictureEvents.SetPipController(pipController))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
when (state.urlState) {
|
when (state.urlState) {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package io.element.android.features.call.impl.ui
|
package io.element.android.features.call.impl.ui
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.app.PictureInPictureParams
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.media.AudioAttributes
|
import android.media.AudioAttributes
|
||||||
|
|
@ -24,19 +25,30 @@ import android.media.AudioFocusRequest
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Rational
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.webkit.PermissionRequest
|
import android.webkit.PermissionRequest
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
|
import androidx.core.app.PictureInPictureModeChangedInfo
|
||||||
import androidx.core.content.IntentCompat
|
import androidx.core.content.IntentCompat
|
||||||
|
import androidx.core.util.Consumer
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallType
|
||||||
import io.element.android.features.call.impl.DefaultElementCallEntryPoint
|
import io.element.android.features.call.impl.DefaultElementCallEntryPoint
|
||||||
import io.element.android.features.call.impl.di.CallBindings
|
import io.element.android.features.call.impl.di.CallBindings
|
||||||
|
import io.element.android.features.call.impl.pip.PictureInPictureEvents
|
||||||
import io.element.android.features.call.impl.pip.PictureInPicturePresenter
|
import io.element.android.features.call.impl.pip.PictureInPicturePresenter
|
||||||
|
import io.element.android.features.call.impl.pip.PictureInPictureState
|
||||||
|
import io.element.android.features.call.impl.pip.PipView
|
||||||
import io.element.android.features.call.impl.services.CallForegroundService
|
import io.element.android.features.call.impl.services.CallForegroundService
|
||||||
import io.element.android.features.call.impl.utils.CallIntentDataParser
|
import io.element.android.features.call.impl.utils.CallIntentDataParser
|
||||||
import io.element.android.libraries.architecture.bindings
|
import io.element.android.libraries.architecture.bindings
|
||||||
|
|
@ -45,7 +57,10 @@ import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ElementCallActivity : AppCompatActivity(), CallScreenNavigator {
|
class ElementCallActivity :
|
||||||
|
AppCompatActivity(),
|
||||||
|
CallScreenNavigator,
|
||||||
|
PipView {
|
||||||
@Inject lateinit var callIntentDataParser: CallIntentDataParser
|
@Inject lateinit var callIntentDataParser: CallIntentDataParser
|
||||||
@Inject lateinit var presenterFactory: CallScreenPresenter.Factory
|
@Inject lateinit var presenterFactory: CallScreenPresenter.Factory
|
||||||
@Inject lateinit var appPreferencesStore: AppPreferencesStore
|
@Inject lateinit var appPreferencesStore: AppPreferencesStore
|
||||||
|
|
@ -86,13 +101,14 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator {
|
||||||
updateUiMode(resources.configuration)
|
updateUiMode(resources.configuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
pictureInPicturePresenter.onCreate(this)
|
pictureInPicturePresenter.setPipView(this)
|
||||||
|
|
||||||
audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
|
audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
|
||||||
requestAudioFocus()
|
requestAudioFocus()
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val pipState = pictureInPicturePresenter.present()
|
val pipState = pictureInPicturePresenter.present()
|
||||||
|
ListenToAndroidEvents(pipState)
|
||||||
ElementThemeApp(appPreferencesStore) {
|
ElementThemeApp(appPreferencesStore) {
|
||||||
val state = presenter.present()
|
val state = presenter.present()
|
||||||
eventSink = state.eventSink
|
eventSink = state.eventSink
|
||||||
|
|
@ -108,21 +124,38 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ListenToAndroidEvents(pipState: PictureInPictureState) {
|
||||||
|
val pipEventSink by rememberUpdatedState(pipState.eventSink)
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
val onUserLeaveHintListener = Runnable {
|
||||||
|
pipEventSink(PictureInPictureEvents.EnterPictureInPicture)
|
||||||
|
}
|
||||||
|
addOnUserLeaveHintListener(onUserLeaveHintListener)
|
||||||
|
onDispose {
|
||||||
|
removeOnUserLeaveHintListener(onUserLeaveHintListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
val onPictureInPictureModeChangedListener = Consumer { _: PictureInPictureModeChangedInfo ->
|
||||||
|
pipEventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(isInPictureInPictureMode))
|
||||||
|
if (!isInPictureInPictureMode && !lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
||||||
|
Timber.d("Exiting PiP mode: Hangup the call")
|
||||||
|
eventSink?.invoke(CallScreenEvents.Hangup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addOnPictureInPictureModeChangedListener(onPictureInPictureModeChangedListener)
|
||||||
|
onDispose {
|
||||||
|
removeOnPictureInPictureModeChangedListener(onPictureInPictureModeChangedListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
super.onConfigurationChanged(newConfig)
|
super.onConfigurationChanged(newConfig)
|
||||||
updateUiMode(newConfig)
|
updateUiMode(newConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) {
|
|
||||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
|
||||||
pictureInPicturePresenter.onPictureInPictureModeChanged(isInPictureInPictureMode)
|
|
||||||
|
|
||||||
if (!isInPictureInPictureMode && !lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
|
||||||
Timber.d("Exiting PiP mode: Hangup the call")
|
|
||||||
eventSink?.invoke(CallScreenEvents.Hangup)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
setCallType(intent)
|
setCallType(intent)
|
||||||
|
|
@ -140,16 +173,11 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUserLeaveHint() {
|
|
||||||
super.onUserLeaveHint()
|
|
||||||
pictureInPicturePresenter.onUserLeaveHint()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
releaseAudioFocus()
|
releaseAudioFocus()
|
||||||
CallForegroundService.stop(this)
|
CallForegroundService.stop(this)
|
||||||
pictureInPicturePresenter.onDestroy()
|
pictureInPicturePresenter.setPipView(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun finish() {
|
override fun finish() {
|
||||||
|
|
@ -249,6 +277,33 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
override fun setPipParams() {
|
||||||
|
setPictureInPictureParams(getPictureInPictureParams())
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
override fun enterPipMode(): Boolean {
|
||||||
|
return enterPictureInPictureMode(getPictureInPictureParams())
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
private fun getPictureInPictureParams(): PictureInPictureParams {
|
||||||
|
return PictureInPictureParams.Builder()
|
||||||
|
// Portrait for calls seems more appropriate
|
||||||
|
.setAspectRatio(Rational(3, 5))
|
||||||
|
.apply {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
setAutoEnterEnabled(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hangUp() {
|
||||||
|
eventSink?.invoke(CallScreenEvents.Hangup)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun mapWebkitPermissions(permissions: Array<String>): List<String> {
|
internal fun mapWebkitPermissions(permissions: Array<String>): List<String> {
|
||||||
|
|
|
||||||
|
|
@ -14,15 +14,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.lockscreen.impl.unlock
|
package io.element.android.features.call.impl.utils
|
||||||
|
|
||||||
import io.element.android.features.lockscreen.impl.unlock.signout.SignOut
|
interface PipController {
|
||||||
import io.element.android.tests.testutils.simulateLongTask
|
suspend fun canEnterPip(): Boolean
|
||||||
|
fun enterPip()
|
||||||
class FakeSignOut(
|
fun exitPip()
|
||||||
var lambda: () -> String? = { null }
|
|
||||||
) : SignOut {
|
|
||||||
override suspend fun invoke(): String? = simulateLongTask {
|
|
||||||
lambda()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.call.impl.utils
|
||||||
|
|
||||||
|
import android.webkit.WebView
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
class WebViewPipController(
|
||||||
|
private val webView: WebView,
|
||||||
|
) : PipController {
|
||||||
|
override suspend fun canEnterPip(): Boolean {
|
||||||
|
return suspendCoroutine { continuation ->
|
||||||
|
webView.evaluateJavascript("controls.canEnterPip()") { result ->
|
||||||
|
// Note if the method is not available, it will return "null"
|
||||||
|
continuation.resume(result == "true" || result == "null")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enterPip() {
|
||||||
|
webView.evaluateJavascript("controls.enablePip()", null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitPip() {
|
||||||
|
webView.evaluateJavascript("controls.disablePip()", null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="call_foreground_service_channel_title_android">"Actieve oproep"</string>
|
||||||
|
<string name="call_foreground_service_message_android">"Tik om terug te gaan naar het gesprek"</string>
|
||||||
|
<string name="call_foreground_service_title_android">"☎️ In gesprek"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -3,4 +3,5 @@
|
||||||
<string name="call_foreground_service_channel_title_android">"Połączenie w trakcie"</string>
|
<string name="call_foreground_service_channel_title_android">"Połączenie w trakcie"</string>
|
||||||
<string name="call_foreground_service_message_android">"Stuknij, aby wrócić do rozmowy"</string>
|
<string name="call_foreground_service_message_android">"Stuknij, aby wrócić do rozmowy"</string>
|
||||||
<string name="call_foreground_service_title_android">"☎️ Rozmowa w toku"</string>
|
<string name="call_foreground_service_title_android">"☎️ Rozmowa w toku"</string>
|
||||||
|
<string name="screen_incoming_call_subtitle_android">"Przychodzące połączenie Element"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,5 @@
|
||||||
<string name="call_foreground_service_channel_title_android">"Pågående samtal"</string>
|
<string name="call_foreground_service_channel_title_android">"Pågående samtal"</string>
|
||||||
<string name="call_foreground_service_message_android">"Tryck för att återgå till samtalet"</string>
|
<string name="call_foreground_service_message_android">"Tryck för att återgå till samtalet"</string>
|
||||||
<string name="call_foreground_service_title_android">"☎️ Samtal pågår"</string>
|
<string name="call_foreground_service_title_android">"☎️ Samtal pågår"</string>
|
||||||
|
<string name="screen_incoming_call_subtitle_android">"Inkommande Element Call"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="call_foreground_service_channel_title_android">"Davom etayotgan qo\'ng\'iroq"</string>
|
||||||
|
<string name="call_foreground_service_message_android">"Qo\'ng\'iroqqa qaytish uchun bosing"</string>
|
||||||
|
<string name="call_foreground_service_title_android">"☎️ Qo‘ng‘iroq davom etmoqda"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -3,4 +3,5 @@
|
||||||
<string name="call_foreground_service_channel_title_android">"通话进行中"</string>
|
<string name="call_foreground_service_channel_title_android">"通话进行中"</string>
|
||||||
<string name="call_foreground_service_message_android">"点按即可返回通话"</string>
|
<string name="call_foreground_service_message_android">"点按即可返回通话"</string>
|
||||||
<string name="call_foreground_service_title_android">"☎️ 通话中"</string>
|
<string name="call_foreground_service_title_android">"☎️ 通话中"</string>
|
||||||
|
<string name="screen_incoming_call_subtitle_android">"Element 来电"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.call.impl.pip
|
||||||
|
|
||||||
|
import io.element.android.features.call.impl.utils.PipController
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
|
||||||
|
class FakePipController(
|
||||||
|
private val canEnterPipResult: () -> Boolean = { lambdaError() },
|
||||||
|
private val enterPipResult: () -> Unit = { lambdaError() },
|
||||||
|
private val exitPipResult: () -> Unit = { lambdaError() },
|
||||||
|
) : PipController {
|
||||||
|
override suspend fun canEnterPip(): Boolean = canEnterPipResult()
|
||||||
|
|
||||||
|
override fun enterPip() = enterPipResult()
|
||||||
|
|
||||||
|
override fun exitPip() = exitPipResult()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.call.impl.pip
|
||||||
|
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
|
||||||
|
class FakePipView(
|
||||||
|
private val setPipParamsResult: () -> Unit = { lambdaError() },
|
||||||
|
private val enterPipModeResult: () -> Boolean = { lambdaError() },
|
||||||
|
private val handUpResult: () -> Unit = { lambdaError() }
|
||||||
|
) : PipView {
|
||||||
|
override fun setPipParams() = setPipParamsResult()
|
||||||
|
override fun enterPipMode(): Boolean = enterPipModeResult()
|
||||||
|
override fun hangUp() = handUpResult()
|
||||||
|
}
|
||||||
|
|
@ -16,23 +16,16 @@
|
||||||
|
|
||||||
package io.element.android.features.call.impl.pip
|
package io.element.android.features.call.impl.pip
|
||||||
|
|
||||||
import android.os.Build.VERSION_CODES
|
|
||||||
import app.cash.molecule.RecompositionMode
|
import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.call.impl.ui.ElementCallActivity
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.robolectric.Robolectric
|
|
||||||
import org.robolectric.RobolectricTestRunner
|
|
||||||
import org.robolectric.annotation.Config
|
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner::class)
|
|
||||||
class PictureInPicturePresenterTest {
|
class PictureInPicturePresenterTest {
|
||||||
@Test
|
@Test
|
||||||
@Config(sdk = [VERSION_CODES.O, VERSION_CODES.S])
|
|
||||||
fun `when pip is not supported, the state value supportPip is false`() = runTest {
|
fun `when pip is not supported, the state value supportPip is false`() = runTest {
|
||||||
val presenter = createPictureInPicturePresenter(supportPip = false)
|
val presenter = createPictureInPicturePresenter(supportPip = false)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -41,68 +34,119 @@ class PictureInPicturePresenterTest {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.supportPip).isFalse()
|
assertThat(initialState.supportPip).isFalse()
|
||||||
}
|
}
|
||||||
presenter.onDestroy()
|
presenter.setPipView(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(sdk = [VERSION_CODES.O, VERSION_CODES.S])
|
|
||||||
fun `when pip is supported, the state value supportPip is true`() = runTest {
|
fun `when pip is supported, the state value supportPip is true`() = runTest {
|
||||||
val presenter = createPictureInPicturePresenter(supportPip = true)
|
val presenter = createPictureInPicturePresenter(
|
||||||
|
supportPip = true,
|
||||||
|
pipView = FakePipView(setPipParamsResult = { }),
|
||||||
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.supportPip).isTrue()
|
assertThat(initialState.supportPip).isTrue()
|
||||||
}
|
}
|
||||||
presenter.onDestroy()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(sdk = [VERSION_CODES.S])
|
|
||||||
fun `when entering pip is supported, the state value isInPictureInPicture is true`() = runTest {
|
fun `when entering pip is supported, the state value isInPictureInPicture is true`() = runTest {
|
||||||
val presenter = createPictureInPicturePresenter(supportPip = true)
|
val enterPipModeResult = lambdaRecorder<Boolean> { true }
|
||||||
|
val presenter = createPictureInPicturePresenter(
|
||||||
|
supportPip = true,
|
||||||
|
pipView = FakePipView(
|
||||||
|
setPipParamsResult = { },
|
||||||
|
enterPipModeResult = enterPipModeResult,
|
||||||
|
),
|
||||||
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.isInPictureInPicture).isFalse()
|
assertThat(initialState.isInPictureInPicture).isFalse()
|
||||||
initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture)
|
initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture)
|
||||||
presenter.onPictureInPictureModeChanged(true)
|
enterPipModeResult.assertions().isCalledOnce()
|
||||||
|
initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(true))
|
||||||
val pipState = awaitItem()
|
val pipState = awaitItem()
|
||||||
assertThat(pipState.isInPictureInPicture).isTrue()
|
assertThat(pipState.isInPictureInPicture).isTrue()
|
||||||
// User stops pip
|
// User stops pip
|
||||||
presenter.onPictureInPictureModeChanged(false)
|
initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(false))
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.isInPictureInPicture).isFalse()
|
assertThat(finalState.isInPictureInPicture).isFalse()
|
||||||
}
|
}
|
||||||
presenter.onDestroy()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(sdk = [VERSION_CODES.S])
|
fun `with webPipApi, when entering pip is supported, but web deny it, the call is finished`() = runTest {
|
||||||
fun `when onUserLeaveHint is called, the state value isInPictureInPicture becomes true`() = runTest {
|
val handUpResult = lambdaRecorder<Unit> { }
|
||||||
val presenter = createPictureInPicturePresenter(supportPip = true)
|
val presenter = createPictureInPicturePresenter(
|
||||||
|
supportPip = true,
|
||||||
|
pipView = FakePipView(
|
||||||
|
setPipParamsResult = { },
|
||||||
|
handUpResult = handUpResult
|
||||||
|
),
|
||||||
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.isInPictureInPicture).isFalse()
|
initialState.eventSink(PictureInPictureEvents.SetPipController(FakePipController(canEnterPipResult = { false })))
|
||||||
presenter.onUserLeaveHint()
|
initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture)
|
||||||
presenter.onPictureInPictureModeChanged(true)
|
handUpResult.assertions().isCalledOnce()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `with webPipApi, when entering pip is supported, and web allows it, the state value isInPictureInPicture is true`() = runTest {
|
||||||
|
val enterPipModeResult = lambdaRecorder<Boolean> { true }
|
||||||
|
val enterPipResult = lambdaRecorder<Unit> { }
|
||||||
|
val exitPipResult = lambdaRecorder<Unit> { }
|
||||||
|
val presenter = createPictureInPicturePresenter(
|
||||||
|
supportPip = true,
|
||||||
|
pipView = FakePipView(
|
||||||
|
setPipParamsResult = { },
|
||||||
|
enterPipModeResult = enterPipModeResult
|
||||||
|
),
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
initialState.eventSink(
|
||||||
|
PictureInPictureEvents.SetPipController(
|
||||||
|
FakePipController(
|
||||||
|
canEnterPipResult = { true },
|
||||||
|
enterPipResult = enterPipResult,
|
||||||
|
exitPipResult = exitPipResult,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture)
|
||||||
|
enterPipModeResult.assertions().isCalledOnce()
|
||||||
|
enterPipResult.assertions().isNeverCalled()
|
||||||
|
initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(true))
|
||||||
val pipState = awaitItem()
|
val pipState = awaitItem()
|
||||||
assertThat(pipState.isInPictureInPicture).isTrue()
|
assertThat(pipState.isInPictureInPicture).isTrue()
|
||||||
|
enterPipResult.assertions().isCalledOnce()
|
||||||
|
// User stops pip
|
||||||
|
exitPipResult.assertions().isNeverCalled()
|
||||||
|
initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(false))
|
||||||
|
val finalState = awaitItem()
|
||||||
|
assertThat(finalState.isInPictureInPicture).isFalse()
|
||||||
|
exitPipResult.assertions().isCalledOnce()
|
||||||
}
|
}
|
||||||
presenter.onDestroy()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createPictureInPicturePresenter(
|
private fun createPictureInPicturePresenter(
|
||||||
supportPip: Boolean = true,
|
supportPip: Boolean = true,
|
||||||
|
pipView: PipView? = FakePipView()
|
||||||
): PictureInPicturePresenter {
|
): PictureInPicturePresenter {
|
||||||
val activity = Robolectric.buildActivity(ElementCallActivity::class.java)
|
|
||||||
return PictureInPicturePresenter(
|
return PictureInPicturePresenter(
|
||||||
pipSupportProvider = FakePipSupportProvider(supportPip),
|
pipSupportProvider = FakePipSupportProvider(supportPip),
|
||||||
).apply {
|
).apply {
|
||||||
onCreate(activity.get())
|
setPipView(pipView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ class CallScreenViewTest {
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back when pip is not supported hangs up`() {
|
fun `clicking on back when pip is not supported hangs up`() {
|
||||||
val eventsRecorder = EventsRecorder<CallScreenEvents>()
|
val eventsRecorder = EventsRecorder<CallScreenEvents>()
|
||||||
val pipEventsRecorder = EventsRecorder<PictureInPictureEvents>(expectEvents = false)
|
val pipEventsRecorder = EventsRecorder<PictureInPictureEvents>()
|
||||||
rule.setCallScreenView(
|
rule.setCallScreenView(
|
||||||
aCallScreenState(
|
aCallScreenState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
|
|
@ -51,6 +51,8 @@ class CallScreenViewTest {
|
||||||
eventsRecorder.assertSize(2)
|
eventsRecorder.assertSize(2)
|
||||||
eventsRecorder.assertTrue(0) { it is CallScreenEvents.SetupMessageChannels }
|
eventsRecorder.assertTrue(0) { it is CallScreenEvents.SetupMessageChannels }
|
||||||
eventsRecorder.assertTrue(1) { it == CallScreenEvents.Hangup }
|
eventsRecorder.assertTrue(1) { it == CallScreenEvents.Hangup }
|
||||||
|
pipEventsRecorder.assertSize(1)
|
||||||
|
pipEventsRecorder.assertTrue(0) { it is PictureInPictureEvents.SetPipController }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -69,7 +71,9 @@ class CallScreenViewTest {
|
||||||
rule.pressBack()
|
rule.pressBack()
|
||||||
eventsRecorder.assertSize(1)
|
eventsRecorder.assertSize(1)
|
||||||
eventsRecorder.assertTrue(0) { it is CallScreenEvents.SetupMessageChannels }
|
eventsRecorder.assertTrue(0) { it is CallScreenEvents.SetupMessageChannels }
|
||||||
pipEventsRecorder.assertSingle(PictureInPictureEvents.EnterPictureInPicture)
|
pipEventsRecorder.assertSize(2)
|
||||||
|
pipEventsRecorder.assertTrue(0) { it is PictureInPictureEvents.SetPipController }
|
||||||
|
pipEventsRecorder.assertTrue(1) { it == PictureInPictureEvents.EnterPictureInPicture }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||||
|
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
||||||
import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver
|
import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver
|
||||||
import io.element.android.libraries.network.useragent.UserAgentProvider
|
import io.element.android.libraries.network.useragent.UserAgentProvider
|
||||||
import io.element.android.services.analytics.api.ScreenTracker
|
import io.element.android.services.analytics.api.ScreenTracker
|
||||||
|
|
@ -43,11 +44,13 @@ import io.element.android.services.analytics.test.FakeScreenTracker
|
||||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.consumeItemsUntilTimeout
|
import io.element.android.tests.testutils.consumeItemsUntilTimeout
|
||||||
|
import io.element.android.tests.testutils.lambda.assert
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.lambda.value
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.cancelAndJoin
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
|
|
@ -86,8 +89,9 @@ class CallScreenPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - with CallType RoomCall sets call as active, loads URL, runs WidgetDriver and notifies the other clients a call started`() = runTest {
|
fun `present - with CallType RoomCall sets call as active, loads URL, runs WidgetDriver and notifies the other clients a call started`() = runTest {
|
||||||
val sendCallNotificationIfNeededLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
val sendCallNotificationIfNeededLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||||
|
val syncService = FakeSyncService(MutableStateFlow(SyncState.Running))
|
||||||
val fakeRoom = FakeMatrixRoom(sendCallNotificationIfNeededResult = sendCallNotificationIfNeededLambda)
|
val fakeRoom = FakeMatrixRoom(sendCallNotificationIfNeededResult = sendCallNotificationIfNeededLambda)
|
||||||
val client = FakeMatrixClient().apply {
|
val client = FakeMatrixClient(syncService = syncService).apply {
|
||||||
givenGetRoomResult(A_ROOM_ID, fakeRoom)
|
givenGetRoomResult(A_ROOM_ID, fakeRoom)
|
||||||
}
|
}
|
||||||
val widgetDriver = FakeMatrixWidgetDriver()
|
val widgetDriver = FakeMatrixWidgetDriver()
|
||||||
|
|
@ -216,7 +220,12 @@ class CallScreenPresenterTest {
|
||||||
fun `present - automatically starts the Matrix client sync when on RoomCall`() = runTest {
|
fun `present - automatically starts the Matrix client sync when on RoomCall`() = runTest {
|
||||||
val navigator = FakeCallScreenNavigator()
|
val navigator = FakeCallScreenNavigator()
|
||||||
val widgetDriver = FakeMatrixWidgetDriver()
|
val widgetDriver = FakeMatrixWidgetDriver()
|
||||||
val matrixClient = FakeMatrixClient()
|
val syncStateFlow = MutableStateFlow(SyncState.Idle)
|
||||||
|
val startSyncLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||||
|
val syncService = FakeSyncService(syncStateFlow = syncStateFlow).apply {
|
||||||
|
this.startSyncLambda = startSyncLambda
|
||||||
|
}
|
||||||
|
val matrixClient = FakeMatrixClient(syncService = syncService)
|
||||||
val presenter = createCallScreenPresenter(
|
val presenter = createCallScreenPresenter(
|
||||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
||||||
widgetDriver = widgetDriver,
|
widgetDriver = widgetDriver,
|
||||||
|
|
@ -230,7 +239,7 @@ class CallScreenPresenterTest {
|
||||||
}.test {
|
}.test {
|
||||||
consumeItemsUntilTimeout()
|
consumeItemsUntilTimeout()
|
||||||
|
|
||||||
assertThat(matrixClient.syncService().syncState.value).isEqualTo(SyncState.Running)
|
assert(startSyncLambda).isCalledOnce()
|
||||||
|
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
|
|
@ -240,7 +249,12 @@ class CallScreenPresenterTest {
|
||||||
fun `present - automatically stops the Matrix client sync on dispose`() = runTest {
|
fun `present - automatically stops the Matrix client sync on dispose`() = runTest {
|
||||||
val navigator = FakeCallScreenNavigator()
|
val navigator = FakeCallScreenNavigator()
|
||||||
val widgetDriver = FakeMatrixWidgetDriver()
|
val widgetDriver = FakeMatrixWidgetDriver()
|
||||||
val matrixClient = FakeMatrixClient()
|
val syncStateFlow = MutableStateFlow(SyncState.Running)
|
||||||
|
val stopSyncLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||||
|
val syncService = FakeSyncService(syncStateFlow = syncStateFlow).apply {
|
||||||
|
this.stopSyncLambda = stopSyncLambda
|
||||||
|
}
|
||||||
|
val matrixClient = FakeMatrixClient(syncService = syncService)
|
||||||
val presenter = createCallScreenPresenter(
|
val presenter = createCallScreenPresenter(
|
||||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
||||||
widgetDriver = widgetDriver,
|
widgetDriver = widgetDriver,
|
||||||
|
|
@ -262,7 +276,7 @@ class CallScreenPresenterTest {
|
||||||
|
|
||||||
job.cancelAndJoin()
|
job.cancelAndJoin()
|
||||||
|
|
||||||
assertThat(matrixClient.syncService().syncState.value).isEqualTo(SyncState.Terminated)
|
assert(stopSyncLambda).isCalledOnce()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TestScope.createCallScreenPresenter(
|
private fun TestScope.createCallScreenPresenter(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_create_room_action_create_room">"Nieuwe kamer"</string>
|
||||||
|
<string name="screen_create_room_add_people_title">"Mensen uitnodigen"</string>
|
||||||
|
<string name="screen_create_room_error_creating_room">"Er is een fout opgetreden bij het aanmaken van de kamer"</string>
|
||||||
|
<string name="screen_create_room_private_option_description">"Berichten in deze kamer zijn versleuteld. Versleuteling kan achteraf niet worden uitgeschakeld."</string>
|
||||||
|
<string name="screen_create_room_private_option_title">"Privé kamer (alleen op uitnodiging)"</string>
|
||||||
|
<string name="screen_create_room_public_option_description">"Berichten zijn niet versleuteld en iedereen kan ze lezen. Je kunt versleuteling later inschakelen."</string>
|
||||||
|
<string name="screen_create_room_public_option_title">"Openbare kamer (iedereen)"</string>
|
||||||
|
<string name="screen_create_room_room_name_label">"Naam van de kamer"</string>
|
||||||
|
<string name="screen_create_room_title">"Creëer een kamer"</string>
|
||||||
|
<string name="screen_create_room_topic_label">"Onderwerp (optioneel)"</string>
|
||||||
|
<string name="screen_start_chat_error_starting_chat">"Er is een fout opgetreden bij het starten van een chat"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -2,11 +2,11 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_create_room_action_create_room">"Nowy pokój"</string>
|
<string name="screen_create_room_action_create_room">"Nowy pokój"</string>
|
||||||
<string name="screen_create_room_add_people_title">"Zaproś znajomych"</string>
|
<string name="screen_create_room_add_people_title">"Zaproś znajomych"</string>
|
||||||
<string name="screen_create_room_error_creating_room">"Wystąpił błąd podczas tworzenia pokoju"</string>
|
<string name="screen_create_room_error_creating_room">"Wystąpił błąd w trakcie tworzenia pokoju"</string>
|
||||||
<string name="screen_create_room_private_option_description">"Wiadomości w tym pokoju są szyfrowane. Szyfrowania nie można później wyłączyć."</string>
|
<string name="screen_create_room_private_option_description">"Wiadomości w tym pokoju są szyfrowane. Szyfrowania nie można później wyłączyć."</string>
|
||||||
<string name="screen_create_room_private_option_title">"Pokój prywatny (tylko zaproszenie)"</string>
|
<string name="screen_create_room_private_option_title">"Pokój prywatny (tylko zaproszenie)"</string>
|
||||||
<string name="screen_create_room_public_option_description">"Wiadomości nie są szyfrowane i każdy może je odczytać. Możesz aktywować szyfrowanie później."</string>
|
<string name="screen_create_room_public_option_description">"Wiadomości nie są szyfrowane i każdy może je odczytać. Możesz aktywować szyfrowanie później."</string>
|
||||||
<string name="screen_create_room_public_option_title">"Pokój publiczny (każdy)"</string>
|
<string name="screen_create_room_public_option_title">"Pokój publiczny (wszyscy)"</string>
|
||||||
<string name="screen_create_room_room_name_label">"Nazwa pokoju"</string>
|
<string name="screen_create_room_room_name_label">"Nazwa pokoju"</string>
|
||||||
<string name="screen_create_room_title">"Utwórz pokój"</string>
|
<string name="screen_create_room_title">"Utwórz pokój"</string>
|
||||||
<string name="screen_create_room_topic_label">"Temat (opcjonalnie)"</string>
|
<string name="screen_create_room_topic_label">"Temat (opcjonalnie)"</string>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_create_room_action_create_room">"Yangi xona"</string>
|
||||||
|
<string name="screen_create_room_add_people_title">"Odamlarni taklif qiling"</string>
|
||||||
|
<string name="screen_create_room_error_creating_room">"Xonani yaratishda xatolik yuz berdi"</string>
|
||||||
|
<string name="screen_create_room_private_option_description">"Bu xonadagi xabarlar shifrlangan. Keyinchalik shifrlashni o‘chirib bo‘lmaydi."</string>
|
||||||
|
<string name="screen_create_room_private_option_title">"Shaxsiy xona (faqat taklif)"</string>
|
||||||
|
<string name="screen_create_room_public_option_description">"Xabarlar shifrlanmagan va har kim ularni o\'qiy oladi. Keyinchalik shifrlashni yoqishingiz mumkin."</string>
|
||||||
|
<string name="screen_create_room_public_option_title">"Jamoat xonasi (har kim)"</string>
|
||||||
|
<string name="screen_create_room_room_name_label">"Xona nomi"</string>
|
||||||
|
<string name="screen_create_room_title">"Xonani yaratish"</string>
|
||||||
|
<string name="screen_create_room_topic_label">"Mavzu (ixtiyoriy)"</string>
|
||||||
|
<string name="screen_start_chat_error_starting_chat">"Suhbatni boshlashda xatolik yuz berdi"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<string name="screen_create_room_error_creating_room">"创建房间时出错"</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_description">"此聊天室中的消息已加密。加密无法禁用。"</string>
|
||||||
<string name="screen_create_room_private_option_title">"私人房间(仅限受邀者)"</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_description">"消息未加密,任何人都可以查看。可以稍后启用加密。"</string>
|
||||||
<string name="screen_create_room_public_option_title">"公共房间(任何人)"</string>
|
<string name="screen_create_room_public_option_title">"公共房间(任何人)"</string>
|
||||||
<string name="screen_create_room_room_name_label">"房间名称"</string>
|
<string name="screen_create_room_room_name_label">"房间名称"</string>
|
||||||
<string name="screen_create_room_title">"创建房间"</string>
|
<string name="screen_create_room_title">"创建房间"</string>
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,9 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor(
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object EnterRecoveryKey : NavTarget
|
data object EnterRecoveryKey : NavTarget
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data object ResetIdentity : NavTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
|
|
@ -85,6 +88,10 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor(
|
||||||
override fun onDone() {
|
override fun onDone() {
|
||||||
plugins<Callback>().forEach { it.onDone() }
|
plugins<Callback>().forEach { it.onDone() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResetKey() {
|
||||||
|
backstack.push(NavTarget.ResetIdentity)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
@ -94,6 +101,16 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor(
|
||||||
.callback(secureBackupEntryPointCallback)
|
.callback(secureBackupEntryPointCallback)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
is NavTarget.ResetIdentity -> {
|
||||||
|
secureBackupEntryPoint.nodeBuilder(this, buildContext)
|
||||||
|
.params(SecureBackupEntryPoint.Params(SecureBackupEntryPoint.InitialTarget.ResetIdentity))
|
||||||
|
.callback(object : SecureBackupEntryPoint.Callback {
|
||||||
|
override fun onDone() {
|
||||||
|
plugins<Callback>().forEach { it.onDone() }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,10 @@ class DefaultFtueService @Inject constructor(
|
||||||
} else {
|
} else {
|
||||||
getNextStep(FtueStep.AnalyticsOptIn)
|
getNextStep(FtueStep.AnalyticsOptIn)
|
||||||
}
|
}
|
||||||
FtueStep.AnalyticsOptIn -> null
|
FtueStep.AnalyticsOptIn -> {
|
||||||
|
updateState()
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun isAnyStepIncomplete(): Boolean {
|
private suspend fun isAnyStepIncomplete(): Boolean {
|
||||||
|
|
|
||||||
11
features/ftue/impl/src/main/res/values-nl/translations.xml
Normal file
11
features/ftue/impl/src/main/res/values-nl/translations.xml
Normal file
|
|
@ -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_notification_optin_subtitle">"Je kunt je instellingen later wijzigen."</string>
|
||||||
|
<string name="screen_notification_optin_title">"Sta meldingen toe en mis nooit meer een bericht"</string>
|
||||||
|
<string name="screen_welcome_bullet_1">"Oproepen, peilingen, zoekopdrachten en meer zullen later dit jaar worden toegevoegd."</string>
|
||||||
|
<string name="screen_welcome_bullet_2">"Berichtgeschiedenis voor versleutelde kamers is nog niet beschikbaar."</string>
|
||||||
|
<string name="screen_welcome_bullet_3">"We horen graag van je, laat ons weten wat je ervan vindt via de instellingenpagina."</string>
|
||||||
|
<string name="screen_welcome_button">"Aan de slag!"</string>
|
||||||
|
<string name="screen_welcome_subtitle">"Dit is wat je moet weten:"</string>
|
||||||
|
<string name="screen_welcome_title">"Welkom bij %1$s!"</string>
|
||||||
|
</resources>
|
||||||
11
features/ftue/impl/src/main/res/values-uz/translations.xml
Normal file
11
features/ftue/impl/src/main/res/values-uz/translations.xml
Normal file
|
|
@ -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_notification_optin_subtitle">"Sozlamalaringizni keyinroq o\'zgartirishingiz mumkin."</string>
|
||||||
|
<string name="screen_notification_optin_title">"Bildirishnomalarga ruxsat bering va hech qachon xabarni o\'tkazib yubormang"</string>
|
||||||
|
<string name="screen_welcome_bullet_1">"Qo\'ng\'iroqlar, so\'ro\'vlar, qidiruv va boshqalar shu yil oxirida qo\'shiladi."</string>
|
||||||
|
<string name="screen_welcome_bullet_2">"Shifrlangan xonalar uchun xabarlar tarixi hali mavjud emas."</string>
|
||||||
|
<string name="screen_welcome_bullet_3">"Biz sizdan eshitishni istardik, sozlamalar sahifasi orqali fikringizni bildiring."</string>
|
||||||
|
<string name="screen_welcome_button">"Qani ketdik!"</string>
|
||||||
|
<string name="screen_welcome_subtitle">"Buni bilishingiz kerak:"</string>
|
||||||
|
<string name="screen_welcome_title">"%1$sga Xush kelibsiz!"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -33,6 +33,7 @@ import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
import io.element.android.libraries.architecture.runUpdatingState
|
import io.element.android.libraries.architecture.runUpdatingState
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||||
import io.element.android.libraries.push.api.notifications.NotificationCleaner
|
import io.element.android.libraries.push.api.notifications.NotificationCleaner
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
@ -107,7 +108,7 @@ class AcceptDeclineInvitePresenter @Inject constructor(
|
||||||
) = launch {
|
) = launch {
|
||||||
acceptedAction.runUpdatingState {
|
acceptedAction.runUpdatingState {
|
||||||
joinRoom(
|
joinRoom(
|
||||||
roomId = roomId,
|
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
||||||
serverNames = emptyList(),
|
serverNames = emptyList(),
|
||||||
trigger = JoinedRoom.Trigger.Invite,
|
trigger = JoinedRoom.Trigger.Invite,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invites_decline_chat_message">"Weet je zeker dat je de uitnodiging om toe te treden tot %1$s wilt weigeren?"</string>
|
||||||
|
<string name="screen_invites_decline_chat_title">"Uitnodiging weigeren"</string>
|
||||||
|
<string name="screen_invites_decline_direct_chat_message">"Weet je zeker dat je deze privéchat met %1$s wilt weigeren?"</string>
|
||||||
|
<string name="screen_invites_decline_direct_chat_title">"Chat weigeren"</string>
|
||||||
|
<string name="screen_invites_empty_list">"Geen uitnodigingen"</string>
|
||||||
|
<string name="screen_invites_invited_you">"%1$s (%2$s) heeft je uitgenodigd"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_invites_decline_chat_message">"Czy na pewno chcesz odrzucić zaproszenie do dołączenia do %1$s?"</string>
|
<string name="screen_invites_decline_chat_message">"Czy na pewno chcesz odrzucić zaproszenie dołączenia do %1$s?"</string>
|
||||||
<string name="screen_invites_decline_chat_title">"Odrzuć zaproszenie"</string>
|
<string name="screen_invites_decline_chat_title">"Odrzuć zaproszenie"</string>
|
||||||
<string name="screen_invites_decline_direct_chat_message">"Czy na pewno chcesz odrzucić rozmowę prywatną z %1$s?"</string>
|
<string name="screen_invites_decline_direct_chat_message">"Czy na pewno chcesz odrzucić rozmowę prywatną z %1$s?"</string>
|
||||||
<string name="screen_invites_decline_direct_chat_title">"Odrzuć czat"</string>
|
<string name="screen_invites_decline_direct_chat_title">"Odrzuć czat"</string>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invites_decline_chat_message">"Haqiqatan ham qo\'shilish taklifini rad qilmoqchimisiz%1$s ?"</string>
|
||||||
|
<string name="screen_invites_decline_chat_title">"Taklifni rad etish"</string>
|
||||||
|
<string name="screen_invites_decline_direct_chat_message">"Haqiqatan ham bu shaxsiy chatni rad qilmoqchimisiz%1$s ?"</string>
|
||||||
|
<string name="screen_invites_decline_direct_chat_title">"Chatni rad etish"</string>
|
||||||
|
<string name="screen_invites_empty_list">"Takliflar yo\'q"</string>
|
||||||
|
<string name="screen_invites_invited_you">"%1$s(%2$s ) sizni taklif qildi"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -23,7 +23,9 @@ import io.element.android.features.invite.api.response.InviteData
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
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.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
|
|
@ -178,8 +180,8 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - accepting invite error flow`() = runTest {
|
fun `present - accepting invite error flow`() = runTest {
|
||||||
val joinRoomFailure = lambdaRecorder { roomId: RoomId, _: List<String>, _: JoinedRoom.Trigger ->
|
val joinRoomFailure = lambdaRecorder { roomIdOrAlias: RoomIdOrAlias, _: List<String>, _: JoinedRoom.Trigger ->
|
||||||
Result.failure<Unit>(RuntimeException("Failed to join room $roomId"))
|
Result.failure<Unit>(RuntimeException("Failed to join room $roomIdOrAlias"))
|
||||||
}
|
}
|
||||||
val presenter = createAcceptDeclineInvitePresenter(joinRoomLambda = joinRoomFailure)
|
val presenter = createAcceptDeclineInvitePresenter(joinRoomLambda = joinRoomFailure)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
|
|
@ -208,7 +210,7 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
assert(joinRoomFailure)
|
assert(joinRoomFailure)
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
.with(
|
.with(
|
||||||
value(A_ROOM_ID),
|
value(A_ROOM_ID.toRoomIdOrAlias()),
|
||||||
value(emptyList<String>()),
|
value(emptyList<String>()),
|
||||||
value(JoinedRoom.Trigger.Invite)
|
value(JoinedRoom.Trigger.Invite)
|
||||||
)
|
)
|
||||||
|
|
@ -222,7 +224,7 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
val fakeNotificationCleaner = FakeNotificationCleaner(
|
val fakeNotificationCleaner = FakeNotificationCleaner(
|
||||||
clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda
|
clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda
|
||||||
)
|
)
|
||||||
val joinRoomSuccess = lambdaRecorder { _: RoomId, _: List<String>, _: JoinedRoom.Trigger ->
|
val joinRoomSuccess = lambdaRecorder { _: RoomIdOrAlias, _: List<String>, _: JoinedRoom.Trigger ->
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
}
|
}
|
||||||
val presenter = createAcceptDeclineInvitePresenter(
|
val presenter = createAcceptDeclineInvitePresenter(
|
||||||
|
|
@ -248,7 +250,7 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
assert(joinRoomSuccess)
|
assert(joinRoomSuccess)
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
.with(
|
.with(
|
||||||
value(A_ROOM_ID),
|
value(A_ROOM_ID.toRoomIdOrAlias()),
|
||||||
value(emptyList<String>()),
|
value(emptyList<String>()),
|
||||||
value(JoinedRoom.Trigger.Invite)
|
value(JoinedRoom.Trigger.Invite)
|
||||||
)
|
)
|
||||||
|
|
@ -271,7 +273,7 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
|
|
||||||
private fun createAcceptDeclineInvitePresenter(
|
private fun createAcceptDeclineInvitePresenter(
|
||||||
client: MatrixClient = FakeMatrixClient(),
|
client: MatrixClient = FakeMatrixClient(),
|
||||||
joinRoomLambda: (RoomId, List<String>, JoinedRoom.Trigger) -> Result<Unit> = { _, _, _ ->
|
joinRoomLambda: (RoomIdOrAlias, List<String>, JoinedRoom.Trigger) -> Result<Unit> = { _, _, _ ->
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
},
|
},
|
||||||
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),
|
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
value = ContentState.Loading(roomIdOrAlias)
|
value = ContentState.Loading(roomIdOrAlias)
|
||||||
val result = matrixClient.getRoomPreviewFromRoomId(roomId, serverNames)
|
val result = matrixClient.getRoomPreview(roomIdOrAlias, serverNames)
|
||||||
value = result.fold(
|
value = result.fold(
|
||||||
onSuccess = { roomPreview ->
|
onSuccess = { roomPreview ->
|
||||||
roomPreview.toContentState()
|
roomPreview.toContentState()
|
||||||
|
|
@ -153,7 +153,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
private fun CoroutineScope.joinRoom(joinAction: MutableState<AsyncAction<Unit>>) = launch {
|
private fun CoroutineScope.joinRoom(joinAction: MutableState<AsyncAction<Unit>>) = launch {
|
||||||
joinAction.runUpdatingState {
|
joinAction.runUpdatingState {
|
||||||
joinRoom.invoke(
|
joinRoom.invoke(
|
||||||
roomId = roomId,
|
roomIdOrAlias = roomIdOrAlias,
|
||||||
serverNames = serverNames,
|
serverNames = serverNames,
|
||||||
trigger = trigger
|
trigger = trigger
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,11 @@ data class JoinRoomState(
|
||||||
val eventSink: (JoinRoomEvents) -> Unit
|
val eventSink: (JoinRoomEvents) -> Unit
|
||||||
) {
|
) {
|
||||||
val joinAuthorisationStatus = when (contentState) {
|
val joinAuthorisationStatus = when (contentState) {
|
||||||
|
// Use the join authorisation status from the loaded content state
|
||||||
is ContentState.Loaded -> contentState.joinAuthorisationStatus
|
is ContentState.Loaded -> contentState.joinAuthorisationStatus
|
||||||
|
// Assume that if the room is unknown, the user can join it
|
||||||
|
is ContentState.UnknownRoom -> JoinAuthorisationStatus.CanJoin
|
||||||
|
// Otherwise assume that the user can't join the room
|
||||||
else -> JoinAuthorisationStatus.Unknown
|
else -> JoinAuthorisationStatus.Unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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_join_room_join_action">"Dołącz do pokoju"</string>
|
||||||
|
<string name="screen_join_room_knock_action">"Zapukaj, by dołączyć"</string>
|
||||||
|
<string name="screen_join_room_space_not_supported_description">"%1$s jeszcze nie obsługuje przestrzeni. Uzyskaj dostęp do przestrzeni w wersji web."</string>
|
||||||
|
<string name="screen_join_room_space_not_supported_title">"Przestrzenie nie są jeszcze obsługiwane"</string>
|
||||||
|
<string name="screen_join_room_subtitle_knock">"Kliknij przycisk poniżej, aby powiadomić administratora pokoju. Po zatwierdzeniu będziesz mógł dołączyć do rozmowy."</string>
|
||||||
|
<string name="screen_join_room_subtitle_no_preview">"Musisz być członkiem tego pokoju, aby wyświetlić historię wiadomości."</string>
|
||||||
|
<string name="screen_join_room_title_knock">"Chcesz dołączyć do tego pokoju?"</string>
|
||||||
|
<string name="screen_join_room_title_no_preview">"Podgląd nie jest dostępny"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -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_join_room_join_action">"Gå med i rummet"</string>
|
||||||
|
<string name="screen_join_room_knock_action">"Knacka för att gå med"</string>
|
||||||
|
<string name="screen_join_room_space_not_supported_description">"%1$s stöder inte utrymmen än. Du kan komma åt utrymmen på webben."</string>
|
||||||
|
<string name="screen_join_room_space_not_supported_title">"Utrymmen stöds inte ännu"</string>
|
||||||
|
<string name="screen_join_room_subtitle_knock">"Klicka på knappen nedan så kommer en rumsadministratör att meddelas. Du kommer att kunna gå med i konversationen när den har godkänts."</string>
|
||||||
|
<string name="screen_join_room_subtitle_no_preview">"Du måste vara medlem i det här rummet för att se meddelandehistoriken."</string>
|
||||||
|
<string name="screen_join_room_title_knock">"Vill du gå med i det här rummet?"</string>
|
||||||
|
<string name="screen_join_room_title_no_preview">"Förhandsgranskning är inte tillgänglig"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -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_join_room_join_action">"Приєднатися до кімнати"</string>
|
||||||
|
<string name="screen_join_room_knock_action">"Постукати, щоб приєднатися"</string>
|
||||||
|
<string name="screen_join_room_space_not_supported_description">"%1$s ще не підтримує простори. Ви можете отримати доступ до них в вебверсії."</string>
|
||||||
|
<string name="screen_join_room_space_not_supported_title">"Простори поки що не підтримуються"</string>
|
||||||
|
<string name="screen_join_room_subtitle_knock">"Натисніть кнопку нижче, і адміністратор кімнати отримає сповіщення. Ви зможете приєднатися до розмови після схвалення."</string>
|
||||||
|
<string name="screen_join_room_subtitle_no_preview">"Ви мусите бути учасником цієї кімнати, щоб переглядати історію повідомлень."</string>
|
||||||
|
<string name="screen_join_room_title_knock">"Хочете приєднатися до цієї кімнати?"</string>
|
||||||
|
<string name="screen_join_room_title_no_preview">"Попередній перегляд недоступний"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -29,6 +29,7 @@ import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
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
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||||
|
|
@ -180,7 +181,7 @@ class JoinRoomPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - when room is joined with success, all the parameters are provided`() = runTest {
|
fun `present - when room is joined with success, all the parameters are provided`() = runTest {
|
||||||
val aTrigger = JoinedRoom.Trigger.MobilePermalink
|
val aTrigger = JoinedRoom.Trigger.MobilePermalink
|
||||||
val joinRoomLambda = lambdaRecorder { _: RoomId, _: List<String>, _: JoinedRoom.Trigger ->
|
val joinRoomLambda = lambdaRecorder { _: RoomIdOrAlias, _: List<String>, _: JoinedRoom.Trigger ->
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
}
|
}
|
||||||
val presenter = createJoinRoomPresenter(
|
val presenter = createJoinRoomPresenter(
|
||||||
|
|
@ -201,7 +202,7 @@ class JoinRoomPresenterTest {
|
||||||
}
|
}
|
||||||
joinRoomLambda.assertions()
|
joinRoomLambda.assertions()
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
.with(value(A_ROOM_ID), value(A_SERVER_LIST), value(aTrigger))
|
.with(value(A_ROOM_ID.toRoomIdOrAlias()), value(A_SERVER_LIST), value(aTrigger))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -366,7 +367,7 @@ class JoinRoomPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - when room is not known RoomPreview is loaded`() = runTest {
|
fun `present - when room is not known RoomPreview is loaded`() = runTest {
|
||||||
val client = FakeMatrixClient(
|
val client = FakeMatrixClient(
|
||||||
getRoomPreviewFromRoomIdResult = { _, _ ->
|
getRoomPreviewResult = { _, _ ->
|
||||||
Result.success(
|
Result.success(
|
||||||
RoomPreview(
|
RoomPreview(
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
|
|
@ -411,7 +412,7 @@ class JoinRoomPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - when room is not known RoomPreview is loaded with error`() = runTest {
|
fun `present - when room is not known RoomPreview is loaded with error`() = runTest {
|
||||||
val client = FakeMatrixClient(
|
val client = FakeMatrixClient(
|
||||||
getRoomPreviewFromRoomIdResult = { _, _ ->
|
getRoomPreviewResult = { _, _ ->
|
||||||
Result.failure(AN_EXCEPTION)
|
Result.failure(AN_EXCEPTION)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -449,7 +450,7 @@ class JoinRoomPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - when room is not known RoomPreview is loaded with error 403`() = runTest {
|
fun `present - when room is not known RoomPreview is loaded with error 403`() = runTest {
|
||||||
val client = FakeMatrixClient(
|
val client = FakeMatrixClient(
|
||||||
getRoomPreviewFromRoomIdResult = { _, _ ->
|
getRoomPreviewResult = { _, _ ->
|
||||||
Result.failure(Exception("403"))
|
Result.failure(Exception("403"))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -474,7 +475,7 @@ class JoinRoomPresenterTest {
|
||||||
serverNames: List<String> = emptyList(),
|
serverNames: List<String> = emptyList(),
|
||||||
trigger: JoinedRoom.Trigger = JoinedRoom.Trigger.Invite,
|
trigger: JoinedRoom.Trigger = JoinedRoom.Trigger.Invite,
|
||||||
matrixClient: MatrixClient = FakeMatrixClient(),
|
matrixClient: MatrixClient = FakeMatrixClient(),
|
||||||
joinRoomLambda: (RoomId, List<String>, JoinedRoom.Trigger) -> Result<Unit> = { _, _, _ ->
|
joinRoomLambda: (RoomIdOrAlias, List<String>, JoinedRoom.Trigger) -> Result<Unit> = { _, _, _ ->
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
},
|
},
|
||||||
knockRoom: KnockRoom = FakeKnockRoom(),
|
knockRoom: KnockRoom = FakeKnockRoom(),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="leave_conversation_alert_subtitle">"Weet je zeker dat je dit gesprek wilt verlaten? Dit gesprek is niet openbaar en je kunt niet opnieuw deelnemen zonder een uitnodiging."</string>
|
||||||
|
<string name="leave_room_alert_empty_subtitle">"Weet je zeker dat je deze kamer wilt verlaten? Je bent de enige persoon hier. Als je weggaat, kan er in de toekomst niemand meer toetreden, ook jij niet."</string>
|
||||||
|
<string name="leave_room_alert_private_subtitle">"Weet je zeker dat je deze kamer wilt verlaten? Deze kamer is niet openbaar en je kunt niet opnieuw deelnemen zonder een uitnodiging."</string>
|
||||||
|
<string name="leave_room_alert_subtitle">"Weet je zeker dat je de kamer wilt verlaten?"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="leave_room_alert_empty_subtitle">"Bu xonani tark etmoqchi ekanligingizga ishonchingiz komilmi? Siz bu yerda yagona odamsiz. Agar siz tark etsangiz, kelajakda hech kim qo\'shila olmaydi, jumladan siz ham."</string>
|
||||||
|
<string name="leave_room_alert_private_subtitle">"Bu xonani tark etmoqchi ekanligingizga ishonchingiz komilmi? Bu xona ochiq emas va siz taklifsiz qayta qo‘shila olmaysiz."</string>
|
||||||
|
<string name="leave_room_alert_subtitle">"Xonani tark etmoqchi ekanligingizga ishonchingiz komilmi?"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="leave_conversation_alert_subtitle">"您确定要离开此对话吗?此对话不公开,未经邀请您将无法重新加入。"</string>
|
<string name="leave_conversation_alert_subtitle">"您确定要离开此对话吗?此对话不公开,未经邀请您将无法重新加入。"</string>
|
||||||
<string name="leave_room_alert_empty_subtitle">"你确定要离开这个房间吗?这里只有你一个人,如果你走了,包括你在内的所有人都无法进入此房间。"</string>
|
<string name="leave_room_alert_empty_subtitle">"确定要离开这个房间吗?这里只有你一个人。如果你离开此房间,包括你在内的所有人都将无法进入。"</string>
|
||||||
<string name="leave_room_alert_private_subtitle">"你确定要离开这个房间吗?这个房间不是公开的,如果没有邀请,你将无法重新加入。"</string>
|
<string name="leave_room_alert_private_subtitle">"确定要离开这个房间吗?此房间不公开,没有邀请你将无法重新加入。"</string>
|
||||||
<string name="leave_room_alert_subtitle">"你确定要离开房间吗?"</string>
|
<string name="leave_room_alert_subtitle">"确定要离开房间吗?"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ dependencies {
|
||||||
implementation(projects.libraries.featureflag.api)
|
implementation(projects.libraries.featureflag.api)
|
||||||
implementation(projects.libraries.cryptography.api)
|
implementation(projects.libraries.cryptography.api)
|
||||||
implementation(projects.libraries.preferences.api)
|
implementation(projects.libraries.preferences.api)
|
||||||
|
implementation(projects.features.logout.api)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
implementation(projects.libraries.sessionStorage.api)
|
implementation(projects.libraries.sessionStorage.api)
|
||||||
implementation(projects.services.appnavstate.api)
|
implementation(projects.services.appnavstate.api)
|
||||||
|
|
@ -59,4 +60,5 @@ dependencies {
|
||||||
testImplementation(projects.libraries.featureflag.test)
|
testImplementation(projects.libraries.featureflag.test)
|
||||||
testImplementation(projects.libraries.sessionStorage.test)
|
testImplementation(projects.libraries.sessionStorage.test)
|
||||||
testImplementation(projects.services.appnavstate.test)
|
testImplementation(projects.services.appnavstate.test)
|
||||||
|
testImplementation(projects.features.logout.test)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockMana
|
||||||
import io.element.android.features.lockscreen.impl.pin.PinCodeManager
|
import io.element.android.features.lockscreen.impl.pin.PinCodeManager
|
||||||
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
|
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
|
||||||
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
|
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
|
||||||
import io.element.android.features.lockscreen.impl.unlock.signout.SignOut
|
import io.element.android.features.logout.api.LogoutUseCase
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
|
|
@ -41,7 +41,7 @@ import javax.inject.Inject
|
||||||
class PinUnlockPresenter @Inject constructor(
|
class PinUnlockPresenter @Inject constructor(
|
||||||
private val pinCodeManager: PinCodeManager,
|
private val pinCodeManager: PinCodeManager,
|
||||||
private val biometricUnlockManager: BiometricUnlockManager,
|
private val biometricUnlockManager: BiometricUnlockManager,
|
||||||
private val signOut: SignOut,
|
private val logoutUseCase: LogoutUseCase,
|
||||||
private val coroutineScope: CoroutineScope,
|
private val coroutineScope: CoroutineScope,
|
||||||
private val pinUnlockHelper: PinUnlockHelper,
|
private val pinUnlockHelper: PinUnlockHelper,
|
||||||
) : Presenter<PinUnlockState> {
|
) : Presenter<PinUnlockState> {
|
||||||
|
|
@ -179,7 +179,7 @@ class PinUnlockPresenter @Inject constructor(
|
||||||
|
|
||||||
private fun CoroutineScope.signOut(signOutAction: MutableState<AsyncData<String?>>) = launch {
|
private fun CoroutineScope.signOut(signOutAction: MutableState<AsyncData<String?>>) = launch {
|
||||||
suspend {
|
suspend {
|
||||||
signOut()
|
logoutUseCase.logout(ignoreSdkError = true)
|
||||||
}.runCatchingUpdatingState(signOutAction)
|
}.runCatchingUpdatingState(signOutAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ package io.element.android.features.lockscreen.impl.unlock.di
|
||||||
|
|
||||||
import com.squareup.anvil.annotations.ContributesTo
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
import io.element.android.features.lockscreen.impl.unlock.activity.PinUnlockActivity
|
import io.element.android.features.lockscreen.impl.unlock.activity.PinUnlockActivity
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
|
||||||
@ContributesTo(AppScope::class)
|
@ContributesTo(SessionScope::class)
|
||||||
interface PinUnlockBindings {
|
interface PinUnlockBindings {
|
||||||
fun inject(activity: PinUnlockActivity)
|
fun inject(activity: PinUnlockActivity)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2024 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.lockscreen.impl.unlock.signout
|
|
||||||
|
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
|
||||||
import io.element.android.libraries.di.AppScope
|
|
||||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
|
||||||
class DefaultSignOut @Inject constructor(
|
|
||||||
private val authenticationService: MatrixAuthenticationService,
|
|
||||||
private val matrixClientProvider: MatrixClientProvider,
|
|
||||||
) : SignOut {
|
|
||||||
override suspend fun invoke(): String? {
|
|
||||||
val currentSession = authenticationService.getLatestSessionId()
|
|
||||||
return if (currentSession != null) {
|
|
||||||
matrixClientProvider.getOrRestore(currentSession)
|
|
||||||
.getOrThrow()
|
|
||||||
.logout(ignoreSdkError = true)
|
|
||||||
} else {
|
|
||||||
error("No session to sign out")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_app_lock_biometric_authentication">"biometrische authenticatie"</string>
|
||||||
|
<string name="screen_app_lock_biometric_unlock">"biometrische ontgrendeling"</string>
|
||||||
|
<string name="screen_app_lock_biometric_unlock_title_android">"Ontgrendelen met biometrie"</string>
|
||||||
|
<string name="screen_app_lock_forgot_pin">"Pincode vergeten?"</string>
|
||||||
|
<string name="screen_app_lock_settings_change_pin">"Pincode wijzigen"</string>
|
||||||
|
<string name="screen_app_lock_settings_enable_biometric_unlock">"Biometrische ontgrendeling toestaan"</string>
|
||||||
|
<string name="screen_app_lock_settings_remove_pin">"Pincode verwijderen"</string>
|
||||||
|
<string name="screen_app_lock_settings_remove_pin_alert_message">"Weet je zeker dat je de pincode wilt verwijderen?"</string>
|
||||||
|
<string name="screen_app_lock_settings_remove_pin_alert_title">"Pincode verwijderen?"</string>
|
||||||
|
<string name="screen_app_lock_setup_biometric_unlock_allow_title">"%1$s toestaan"</string>
|
||||||
|
<string name="screen_app_lock_setup_biometric_unlock_skip">"Ik gebruik liever een pincode"</string>
|
||||||
|
<string name="screen_app_lock_setup_biometric_unlock_subtitle">"Bespaar jezelf tijd en gebruik %1$s om de app elke keer te ontgrendelen"</string>
|
||||||
|
<string name="screen_app_lock_setup_choose_pin">"Kies je pincode"</string>
|
||||||
|
<string name="screen_app_lock_setup_confirm_pin">"Bevestig pincode"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_context">"Vergrendel %1$s om je chats extra te beveiligen.
|
||||||
|
|
||||||
|
Kies iets dat je kunt onthouden. Als je deze pincode vergeet, word je uitgelogd bij de app."</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_forbidden_dialog_content">"Vanwege veiligheidsredenen kun je dit niet als je pincode kiezen"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_forbidden_dialog_title">"Kies een andere pincode"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Voer dezelfde pincode twee keer in"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"Pincodes komen niet overeen"</string>
|
||||||
|
<string name="screen_app_lock_signout_alert_message">"Je moet opnieuw inloggen en een nieuwe pincode aanmaken om verder te gaan"</string>
|
||||||
|
<string name="screen_app_lock_signout_alert_title">"Je wordt uitgelogd"</string>
|
||||||
|
<plurals name="screen_app_lock_subtitle">
|
||||||
|
<item quantity="one">"Je hebt %1$d poging om te ontgrendelen"</item>
|
||||||
|
<item quantity="other">"Je hebt %1$d pogingen om te ontgrendelen"</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="screen_app_lock_subtitle_wrong_pin">
|
||||||
|
<item quantity="one">"Verkeerde pincode. Je hebt nog %1$d kans"</item>
|
||||||
|
<item quantity="other">"Verkeerde pincode. Je hebt nog %1$d kansen"</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="screen_app_lock_use_biometric_android">"Biometrie gebruiken"</string>
|
||||||
|
<string name="screen_app_lock_use_pin_android">"Pincode gebruiken"</string>
|
||||||
|
<string name="screen_signout_in_progress_dialog_content">"Uitloggen…"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -16,11 +16,11 @@
|
||||||
<string name="screen_app_lock_setup_confirm_pin">"Potwierdź PIN"</string>
|
<string name="screen_app_lock_setup_confirm_pin">"Potwierdź PIN"</string>
|
||||||
<string name="screen_app_lock_setup_pin_context">"Zablokuj %1$s, aby zwiększyć bezpieczeństwo swoich czatów.
|
<string name="screen_app_lock_setup_pin_context">"Zablokuj %1$s, aby zwiększyć bezpieczeństwo swoich czatów.
|
||||||
|
|
||||||
Wybierz coś łatwego do zapamiętania. Jeśli zapomnisz tego PINU, zostaniesz wylogowany z aplikacji."</string>
|
Wybierz coś łatwego do zapamiętania. Jeśli zapomnisz ten PIN, zostaniesz wylogowany z aplikacji."</string>
|
||||||
<string name="screen_app_lock_setup_pin_forbidden_dialog_content">"Nie możesz wybrać tego PINU ze względów bezpieczeństwa"</string>
|
<string name="screen_app_lock_setup_pin_forbidden_dialog_content">"Nie możesz wybrać tego PIN\'u ze względów bezpieczeństwa"</string>
|
||||||
<string name="screen_app_lock_setup_pin_forbidden_dialog_title">"Wybierz inny kod PIN"</string>
|
<string name="screen_app_lock_setup_pin_forbidden_dialog_title">"Wybierz inny kod PIN"</string>
|
||||||
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Wprowadź ten sam kod PIN dwa razy"</string>
|
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Wprowadź ten sam kod PIN dwa razy"</string>
|
||||||
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"PINY nie pasują do siebie"</string>
|
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"PIN\'y nie pasują do siebie"</string>
|
||||||
<string name="screen_app_lock_signout_alert_message">"Aby kontynuować, zaloguj się ponownie i utwórz nowy kod PIN"</string>
|
<string name="screen_app_lock_signout_alert_message">"Aby kontynuować, zaloguj się ponownie i utwórz nowy kod PIN"</string>
|
||||||
<string name="screen_app_lock_signout_alert_title">"Trwa wylogowywanie"</string>
|
<string name="screen_app_lock_signout_alert_title">"Trwa wylogowywanie"</string>
|
||||||
<plurals name="screen_app_lock_subtitle">
|
<plurals name="screen_app_lock_subtitle">
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,27 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_app_lock_biometric_authentication">"autenticação por biometria"</string>
|
||||||
|
<string name="screen_app_lock_biometric_unlock">"desbloqueio por biometria"</string>
|
||||||
|
<string name="screen_app_lock_biometric_unlock_title_android">"Desbloquear com biometria"</string>
|
||||||
<string name="screen_app_lock_forgot_pin">"Esqueceu o PIN?"</string>
|
<string name="screen_app_lock_forgot_pin">"Esqueceu o PIN?"</string>
|
||||||
<string name="screen_app_lock_settings_change_pin">"Mudar código de PIN"</string>
|
<string name="screen_app_lock_settings_change_pin">"Alterar código de PIN"</string>
|
||||||
<string name="screen_app_lock_settings_enable_biometric_unlock">"Permitir desbloqueio biométrico"</string>
|
<string name="screen_app_lock_settings_enable_biometric_unlock">"Permitir desbloqueio biométrico"</string>
|
||||||
<string name="screen_app_lock_settings_remove_pin">"Remover PIN"</string>
|
<string name="screen_app_lock_settings_remove_pin">"Remover PIN"</string>
|
||||||
<string name="screen_app_lock_settings_remove_pin_alert_message">"Tem certeza de que quer remover o PIN?"</string>
|
<string name="screen_app_lock_settings_remove_pin_alert_message">"Tem certeza de que quer remover o PIN?"</string>
|
||||||
<string name="screen_app_lock_settings_remove_pin_alert_title">"Remover PIN?"</string>
|
<string name="screen_app_lock_settings_remove_pin_alert_title">"Remover PIN?"</string>
|
||||||
|
<string name="screen_app_lock_setup_biometric_unlock_allow_title">"Permitir %1$s"</string>
|
||||||
|
<string name="screen_app_lock_setup_biometric_unlock_skip">"Prefiro usar o PIN"</string>
|
||||||
|
<string name="screen_app_lock_setup_biometric_unlock_subtitle">"Poupe tempo e use %1$s para desbloquear o aplicativo todas as vezes"</string>
|
||||||
<string name="screen_app_lock_setup_choose_pin">"Escolher PIN"</string>
|
<string name="screen_app_lock_setup_choose_pin">"Escolher PIN"</string>
|
||||||
<string name="screen_app_lock_setup_confirm_pin">"Confirmar PIN"</string>
|
<string name="screen_app_lock_setup_confirm_pin">"Confirmar PIN"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_context">"Bloqueie o %1$s para adicionar uma segurança extra às suas conversas.
|
||||||
|
|
||||||
|
Escolha algo memorável. Se você esquecer este PIN, você será desconectado do app."</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_forbidden_dialog_content">"Você não pode escolher este PIN por razões de segurança"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_forbidden_dialog_title">"Escolha um PIN diferente"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Por favor, insira o mesmo PIN duas vezes"</string>
|
||||||
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"Os PINs não correspondem"</string>
|
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"Os PINs não correspondem"</string>
|
||||||
|
<string name="screen_app_lock_signout_alert_message">"Você terá que fazer login novamente e criar um novo PIN para prosseguir"</string>
|
||||||
<string name="screen_app_lock_signout_alert_title">"Você está sendo desconectado"</string>
|
<string name="screen_app_lock_signout_alert_title">"Você está sendo desconectado"</string>
|
||||||
<plurals name="screen_app_lock_subtitle">
|
<plurals name="screen_app_lock_subtitle">
|
||||||
<item quantity="one">"Você tem %1$d tentativa de debloqueio"</item>
|
<item quantity="one">"Você tem %1$d tentativa de debloqueio"</item>
|
||||||
|
|
@ -18,5 +31,7 @@
|
||||||
<item quantity="one">"PIN incorreto. Você tem mais %1$d chance"</item>
|
<item quantity="one">"PIN incorreto. Você tem mais %1$d chance"</item>
|
||||||
<item quantity="other">"PIN incorreto. Você tem mais %1$d chances"</item>
|
<item quantity="other">"PIN incorreto. Você tem mais %1$d chances"</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<string name="screen_app_lock_use_biometric_android">"Usar biometria"</string>
|
||||||
|
<string name="screen_app_lock_use_pin_android">"Usar PIN"</string>
|
||||||
<string name="screen_signout_in_progress_dialog_content">"Saindo…"</string>
|
<string name="screen_signout_in_progress_dialog_content">"Saindo…"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_signout_in_progress_dialog_content">"Chiqish…"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2024 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.lockscreen.impl.unlock
|
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import io.element.android.features.lockscreen.impl.unlock.signout.DefaultSignOut
|
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
|
||||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
|
||||||
import io.element.android.tests.testutils.lambda.assert
|
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class DefaultSignOutTest {
|
|
||||||
private val matrixClient = FakeMatrixClient()
|
|
||||||
private val authenticationService = FakeMatrixAuthenticationService()
|
|
||||||
private val matrixClientProvider = FakeMatrixClientProvider(getClient = { Result.success(matrixClient) })
|
|
||||||
private val sut = DefaultSignOut(authenticationService, matrixClientProvider)
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `when no active session then it throws`() = runTest {
|
|
||||||
authenticationService.getLatestSessionIdLambda = { null }
|
|
||||||
val result = runCatching { sut.invoke() }
|
|
||||||
assertThat(result.isFailure).isTrue()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `with one active session and successful logout on client`() = runTest {
|
|
||||||
val logoutLambda = lambdaRecorder<Boolean, String?> { _: Boolean -> null }
|
|
||||||
authenticationService.getLatestSessionIdLambda = { matrixClient.sessionId }
|
|
||||||
matrixClient.logoutLambda = logoutLambda
|
|
||||||
val result = runCatching { sut.invoke() }
|
|
||||||
assertThat(result.isSuccess).isTrue()
|
|
||||||
assert(logoutLambda).isCalledOnce()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `with one active session and and failed logout on client`() = runTest {
|
|
||||||
val logoutLambda = lambdaRecorder<Boolean, String?> { _: Boolean -> error("Failed to logout") }
|
|
||||||
authenticationService.getLatestSessionIdLambda = { matrixClient.sessionId }
|
|
||||||
matrixClient.logoutLambda = logoutLambda
|
|
||||||
val result = runCatching { sut.invoke() }
|
|
||||||
assertThat(result.isFailure).isTrue()
|
|
||||||
assert(logoutLambda).isCalledOnce()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -28,7 +28,7 @@ import io.element.android.features.lockscreen.impl.pin.PinCodeManager
|
||||||
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
|
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
|
||||||
import io.element.android.features.lockscreen.impl.pin.model.assertText
|
import io.element.android.features.lockscreen.impl.pin.model.assertText
|
||||||
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
|
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
|
||||||
import io.element.android.features.lockscreen.impl.unlock.signout.SignOut
|
import io.element.android.features.logout.test.FakeLogoutUseCase
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.tests.testutils.lambda.assert
|
import io.element.android.tests.testutils.lambda.assert
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
|
|
@ -106,9 +106,9 @@ class PinUnlockPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - forgot pin flow`() = runTest {
|
fun `present - forgot pin flow`() = runTest {
|
||||||
val signOutLambda = lambdaRecorder<String?> { null }
|
val signOutLambda = lambdaRecorder<Boolean, String> { "" }
|
||||||
val signOut = FakeSignOut(signOutLambda)
|
val signOut = FakeLogoutUseCase(signOutLambda)
|
||||||
val presenter = createPinUnlockPresenter(this, signOut = signOut)
|
val presenter = createPinUnlockPresenter(this, logoutUseCase = signOut)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -135,7 +135,7 @@ class PinUnlockPresenterTest {
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.signOutAction).isInstanceOf(AsyncData.Success::class.java)
|
assertThat(state.signOutAction).isInstanceOf(AsyncData.Success::class.java)
|
||||||
}
|
}
|
||||||
assert(signOutLambda).isCalledOnce().withNoParameter()
|
assert(signOutLambda).isCalledOnce()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,7 +147,7 @@ class PinUnlockPresenterTest {
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
biometricUnlockManager: BiometricUnlockManager = FakeBiometricUnlockManager(),
|
biometricUnlockManager: BiometricUnlockManager = FakeBiometricUnlockManager(),
|
||||||
callback: PinCodeManager.Callback = DefaultPinCodeManagerCallback(),
|
callback: PinCodeManager.Callback = DefaultPinCodeManagerCallback(),
|
||||||
signOut: SignOut = FakeSignOut(),
|
logoutUseCase: FakeLogoutUseCase = FakeLogoutUseCase(logoutLambda = { "" }),
|
||||||
): PinUnlockPresenter {
|
): PinUnlockPresenter {
|
||||||
val pinCodeManager = aPinCodeManager().apply {
|
val pinCodeManager = aPinCodeManager().apply {
|
||||||
addCallback(callback)
|
addCallback(callback)
|
||||||
|
|
@ -156,7 +156,7 @@ class PinUnlockPresenterTest {
|
||||||
return PinUnlockPresenter(
|
return PinUnlockPresenter(
|
||||||
pinCodeManager = pinCodeManager,
|
pinCodeManager = pinCodeManager,
|
||||||
biometricUnlockManager = biometricUnlockManager,
|
biometricUnlockManager = biometricUnlockManager,
|
||||||
signOut = signOut,
|
logoutUseCase = logoutUseCase,
|
||||||
coroutineScope = scope,
|
coroutineScope = scope,
|
||||||
pinUnlockHelper = PinUnlockHelper(biometricUnlockManager, pinCodeManager),
|
pinUnlockHelper = PinUnlockHelper(biometricUnlockManager, pinCodeManager),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ dependencies {
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
implementation(projects.libraries.permissions.api)
|
implementation(projects.libraries.permissions.api)
|
||||||
implementation(projects.libraries.qrcode)
|
implementation(projects.libraries.qrcode)
|
||||||
|
implementation(projects.libraries.oidc.api)
|
||||||
implementation(libs.androidx.browser)
|
implementation(libs.androidx.browser)
|
||||||
implementation(platform(libs.network.retrofit.bom))
|
implementation(platform(libs.network.retrofit.bom))
|
||||||
implementation(libs.network.retrofit)
|
implementation(libs.network.retrofit)
|
||||||
|
|
@ -65,6 +66,7 @@ dependencies {
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
|
testImplementation(projects.libraries.oidc.impl)
|
||||||
testImplementation(projects.libraries.permissions.test)
|
testImplementation(projects.libraries.permissions.test)
|
||||||
testImplementation(projects.tests.testutils)
|
testImplementation(projects.tests.testutils)
|
||||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,7 @@ import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.login.api.LoginFlowType
|
import io.element.android.features.login.api.LoginFlowType
|
||||||
import io.element.android.features.login.api.oidc.OidcAction
|
|
||||||
import io.element.android.features.login.api.oidc.OidcActionFlow
|
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||||
import io.element.android.features.login.impl.oidc.CustomTabAvailabilityChecker
|
|
||||||
import io.element.android.features.login.impl.oidc.customtab.CustomTabHandler
|
|
||||||
import io.element.android.features.login.impl.oidc.webview.OidcNode
|
|
||||||
import io.element.android.features.login.impl.qrcode.QrCodeLoginFlowNode
|
import io.element.android.features.login.impl.qrcode.QrCodeLoginFlowNode
|
||||||
import io.element.android.features.login.impl.screens.changeaccountprovider.ChangeAccountProviderNode
|
import io.element.android.features.login.impl.screens.changeaccountprovider.ChangeAccountProviderNode
|
||||||
import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderNode
|
import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderNode
|
||||||
|
|
@ -56,6 +51,9 @@ import io.element.android.libraries.architecture.createNode
|
||||||
import io.element.android.libraries.architecture.inputs
|
import io.element.android.libraries.architecture.inputs
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
||||||
|
import io.element.android.libraries.oidc.api.OidcAction
|
||||||
|
import io.element.android.libraries.oidc.api.OidcActionFlow
|
||||||
|
import io.element.android.libraries.oidc.api.OidcEntryPoint
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
@ -64,11 +62,10 @@ import kotlinx.parcelize.Parcelize
|
||||||
class LoginFlowNode @AssistedInject constructor(
|
class LoginFlowNode @AssistedInject constructor(
|
||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val customTabAvailabilityChecker: CustomTabAvailabilityChecker,
|
|
||||||
private val customTabHandler: CustomTabHandler,
|
|
||||||
private val accountProviderDataSource: AccountProviderDataSource,
|
private val accountProviderDataSource: AccountProviderDataSource,
|
||||||
private val defaultLoginUserStory: DefaultLoginUserStory,
|
private val defaultLoginUserStory: DefaultLoginUserStory,
|
||||||
private val oidcActionFlow: OidcActionFlow,
|
private val oidcActionFlow: OidcActionFlow,
|
||||||
|
private val oidcEntryPoint: OidcEntryPoint,
|
||||||
) : BaseFlowNode<LoginFlowNode.NavTarget>(
|
) : BaseFlowNode<LoginFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
backstack = BackStack(
|
||||||
initialElement = NavTarget.Root,
|
initialElement = NavTarget.Root,
|
||||||
|
|
@ -146,11 +143,11 @@ class LoginFlowNode @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
val callback = object : ConfirmAccountProviderNode.Callback {
|
val callback = object : ConfirmAccountProviderNode.Callback {
|
||||||
override fun onOidcDetails(oidcDetails: OidcDetails) {
|
override fun onOidcDetails(oidcDetails: OidcDetails) {
|
||||||
if (customTabAvailabilityChecker.supportCustomTab()) {
|
if (oidcEntryPoint.canUseCustomTab()) {
|
||||||
// In this case open a Chrome Custom tab
|
// In this case open a Chrome Custom tab
|
||||||
activity?.let {
|
activity?.let {
|
||||||
customChromeTabStarted = true
|
customChromeTabStarted = true
|
||||||
customTabHandler.open(it, darkTheme, oidcDetails.url)
|
oidcEntryPoint.openUrlInCustomTab(it, darkTheme, oidcDetails.url)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback to WebView mode
|
// Fallback to WebView mode
|
||||||
|
|
@ -201,8 +198,7 @@ class LoginFlowNode @AssistedInject constructor(
|
||||||
createNode<LoginPasswordNode>(buildContext, plugins = listOf(callback))
|
createNode<LoginPasswordNode>(buildContext, plugins = listOf(callback))
|
||||||
}
|
}
|
||||||
is NavTarget.OidcView -> {
|
is NavTarget.OidcView -> {
|
||||||
val input = OidcNode.Inputs(navTarget.oidcDetails)
|
oidcEntryPoint.createFallbackWebViewNode(this, buildContext, navTarget.oidcDetails.url)
|
||||||
createNode<OidcNode>(buildContext, plugins = listOf(input))
|
|
||||||
}
|
}
|
||||||
is NavTarget.WaitList -> {
|
is NavTarget.WaitList -> {
|
||||||
val inputs = WaitListNode.Inputs(
|
val inputs = WaitListNode.Inputs(
|
||||||
|
|
|
||||||
|
|
@ -27,15 +27,15 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.features.login.api.oidc.OidcAction
|
|
||||||
import io.element.android.features.login.impl.DefaultLoginUserStory
|
import io.element.android.features.login.impl.DefaultLoginUserStory
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||||
import io.element.android.features.login.impl.error.ChangeServerError
|
import io.element.android.features.login.impl.error.ChangeServerError
|
||||||
import io.element.android.features.login.impl.oidc.customtab.DefaultOidcActionFlow
|
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||||
|
import io.element.android.libraries.oidc.api.OidcAction
|
||||||
|
import io.element.android.libraries.oidc.api.OidcActionFlow
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor(
|
||||||
@Assisted private val params: Params,
|
@Assisted private val params: Params,
|
||||||
private val accountProviderDataSource: AccountProviderDataSource,
|
private val accountProviderDataSource: AccountProviderDataSource,
|
||||||
private val authenticationService: MatrixAuthenticationService,
|
private val authenticationService: MatrixAuthenticationService,
|
||||||
private val defaultOidcActionFlow: DefaultOidcActionFlow,
|
private val oidcActionFlow: OidcActionFlow,
|
||||||
private val defaultLoginUserStory: DefaultLoginUserStory,
|
private val defaultLoginUserStory: DefaultLoginUserStory,
|
||||||
) : Presenter<ConfirmAccountProviderState> {
|
) : Presenter<ConfirmAccountProviderState> {
|
||||||
data class Params(
|
data class Params(
|
||||||
|
|
@ -65,7 +65,7 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
defaultOidcActionFlow.collect { oidcAction ->
|
oidcActionFlow.collect { oidcAction ->
|
||||||
if (oidcAction != null) {
|
if (oidcAction != null) {
|
||||||
onOidcAction(oidcAction, loginFlowAction)
|
onOidcAction(oidcAction, loginFlowAction)
|
||||||
}
|
}
|
||||||
|
|
@ -133,6 +133,6 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defaultOidcActionFlow.reset()
|
oidcActionFlow.reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,20 @@
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_title">"Die Verbindung ist nicht sicher"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_title">"Die Verbindung ist nicht sicher"</string>
|
||||||
<string name="screen_qr_code_login_device_code_subtitle">"Du wirst aufgefordert, die beiden unten abgebildeten Ziffern einzugeben."</string>
|
<string name="screen_qr_code_login_device_code_subtitle">"Du wirst aufgefordert, die beiden unten abgebildeten Ziffern einzugeben."</string>
|
||||||
<string name="screen_qr_code_login_device_code_title">"Trage die unten angezeigte Zahl auf einem anderen Device ein"</string>
|
<string name="screen_qr_code_login_device_code_title">"Trage die unten angezeigte Zahl auf einem anderen Device ein"</string>
|
||||||
|
<string name="screen_qr_code_login_device_not_signed_in_scan_state_description">"Melde dich auf deinem anderen Gerät an und versuche es dann noch einmal oder verwende ein anderes Gerät, das bereits angemeldet ist."</string>
|
||||||
|
<string name="screen_qr_code_login_device_not_signed_in_scan_state_subtitle">"Anderes Gerät ist nicht angemeldet"</string>
|
||||||
|
<string name="screen_qr_code_login_error_cancelled_subtitle">"Die Anmeldung wurde auf dem anderen Gerät abgebrochen."</string>
|
||||||
|
<string name="screen_qr_code_login_error_cancelled_title">"Anmeldeanfrage abgebrochen"</string>
|
||||||
|
<string name="screen_qr_code_login_error_declined_subtitle">"Die Anmeldung auf dem anderen Gerät wurde abgelehnt."</string>
|
||||||
|
<string name="screen_qr_code_login_error_declined_title">"Anmelden abgelehnt"</string>
|
||||||
|
<string name="screen_qr_code_login_error_expired_subtitle">"Die Anmeldung ist abgelaufen. Bitte versuchen Sie es erneut."</string>
|
||||||
|
<string name="screen_qr_code_login_error_expired_title">"Die Anmeldung wurde nicht rechtzeitig abgeschlossen"</string>
|
||||||
|
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Dein anderes Gerät unterstützt die Anmeldung bei %s mit einem QR-Code nicht.
|
||||||
|
|
||||||
|
Versuche, dich manuell anzumelden, oder scanne den QR-Code mit einem anderen Gerät."</string>
|
||||||
|
<string name="screen_qr_code_login_error_linking_not_suported_title">"QR-Code wird nicht unterstützt"</string>
|
||||||
|
<string name="screen_qr_code_login_error_sliding_sync_not_supported_subtitle">"Ihr Kontoanbieter unterstützt %1$s nicht."</string>
|
||||||
|
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$swird nicht unterstützt"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_button_title">"Bereit zum Scannen"</string>
|
<string name="screen_qr_code_login_initial_state_button_title">"Bereit zum Scannen"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_1">"%1$s auf einem Desktop-Gerät öffnen"</string>
|
<string name="screen_qr_code_login_initial_state_item_1">"%1$s auf einem Desktop-Gerät öffnen"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_2">"Klick auf deinen Avatar"</string>
|
<string name="screen_qr_code_login_initial_state_item_2">"Klick auf deinen Avatar"</string>
|
||||||
|
|
|
||||||
44
features/login/impl/src/main/res/values-nl/translations.xml
Normal file
44
features/login/impl/src/main/res/values-nl/translations.xml
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_account_provider_change">"Wijzig accountprovider"</string>
|
||||||
|
<string name="screen_account_provider_form_hint">"Homeserver-adres"</string>
|
||||||
|
<string name="screen_account_provider_form_notice">"Voer een zoekterm of een domeinnaam in."</string>
|
||||||
|
<string name="screen_account_provider_form_subtitle">"Zoek naar een bedrijf, community of privéserver."</string>
|
||||||
|
<string name="screen_account_provider_form_title">"Vind een accountprovider"</string>
|
||||||
|
<string name="screen_account_provider_signin_subtitle">"Dit is waar je gesprekken zullen worden bewaard — net zoals je een e-mailprovider zou gebruiken om je e-mails te bewaren."</string>
|
||||||
|
<string name="screen_account_provider_signin_title">"Je staat op het punt om je aan te melden bij %s"</string>
|
||||||
|
<string name="screen_account_provider_signup_subtitle">"Dit is waar je gesprekken zullen worden bewaard — net zoals je een e-mailprovider zou gebruiken om je e-mails te bewaren."</string>
|
||||||
|
<string name="screen_account_provider_signup_title">"Je staat op het punt een account aan te maken op %s"</string>
|
||||||
|
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org is een grote, gratis server op het openbare Matrix-netwerk voor veilige, gedecentraliseerde communicatie, beheerd door de Matrix.org Foundation."</string>
|
||||||
|
<string name="screen_change_account_provider_other">"Anders"</string>
|
||||||
|
<string name="screen_change_account_provider_subtitle">"Gebruik een andere accountprovider, zoals je eigen privéserver of een zakelijke account."</string>
|
||||||
|
<string name="screen_change_account_provider_title">"Wijzig accountprovider"</string>
|
||||||
|
<string name="screen_change_server_error_invalid_homeserver">"We konden deze homeserver niet bereiken. Controleer of je de homeserver-URL juist hebt ingevoerd. Als de URL juist is, neem dan contact op met de beheerder van je homeserver voor verdere hulp."</string>
|
||||||
|
<string name="screen_change_server_error_no_sliding_sync_message">"Deze server ondersteunt op dit moment geen sliding sync."</string>
|
||||||
|
<string name="screen_change_server_form_header">"Homeserver-URL"</string>
|
||||||
|
<string name="screen_change_server_form_notice">"Je kunt alleen verbinding maken met een bestaande server die sliding sync ondersteunt. De beheerder van de homeserver moet dit configureren. %1$s"</string>
|
||||||
|
<string name="screen_change_server_subtitle">"Wat is het adres van je server?"</string>
|
||||||
|
<string name="screen_change_server_title">"Selecteer je server"</string>
|
||||||
|
<string name="screen_login_error_deactivated_account">"Dit account is gedeactiveerd."</string>
|
||||||
|
<string name="screen_login_error_invalid_credentials">"Onjuiste gebruikersnaam en/of wachtwoord"</string>
|
||||||
|
<string name="screen_login_error_invalid_user_id">"Dit is geen geldige gebruikers-ID. Verwacht formaat: \'@user:homeserver.org\'"</string>
|
||||||
|
<string name="screen_login_error_refresh_tokens">"Deze server is geconfigureerd om verversingstokens te gebruiken. Deze worden niet ondersteund bij inloggen met een wachtwoord."</string>
|
||||||
|
<string name="screen_login_error_unsupported_authentication">"De geselecteerde homeserver ondersteunt geen wachtwoord of OIDC aanmelding. Neem contact op met je beheerder of kies een andere homeserver."</string>
|
||||||
|
<string name="screen_login_form_header">"Vul je gegevens in"</string>
|
||||||
|
<string name="screen_login_subtitle">"Matrix is een open netwerk voor veilige, gedecentraliseerde communicatie."</string>
|
||||||
|
<string name="screen_login_title">"Welkom terug!"</string>
|
||||||
|
<string name="screen_login_title_with_homeserver">"Inloggen bij %1$s"</string>
|
||||||
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Probeer het opnieuw"</string>
|
||||||
|
<string name="screen_server_confirmation_change_server">"Accountprovider wijzigen"</string>
|
||||||
|
<string name="screen_server_confirmation_message_login_element_dot_io">"Een privéserver voor medewerkers van Element."</string>
|
||||||
|
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix is een open netwerk voor veilige, gedecentraliseerde communicatie."</string>
|
||||||
|
<string name="screen_server_confirmation_message_register">"Dit is waar je gesprekken zullen worden bewaard — net zoals je een e-mailprovider zou gebruiken om je e-mails te bewaren."</string>
|
||||||
|
<string name="screen_server_confirmation_title_login">"Je staat op het punt je aan te melden bij %1$s"</string>
|
||||||
|
<string name="screen_server_confirmation_title_register">"Je staat op het punt een account aan te maken op %1$s"</string>
|
||||||
|
<string name="screen_waitlist_message">"Er is momenteel veel vraag naar %1$s op %2$s. Kom over een paar dagen terug naar de app en probeer het opnieuw.
|
||||||
|
|
||||||
|
Bedankt voor je geduld!"</string>
|
||||||
|
<string name="screen_waitlist_message_success">"Welkom bij %1$s!"</string>
|
||||||
|
<string name="screen_waitlist_title">"Je bent er bijna."</string>
|
||||||
|
<string name="screen_waitlist_title_success">"Je bent binnen."</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
<string name="screen_account_provider_form_subtitle">"Szukaj serwera firmowego, społeczności lub prywatnego."</string>
|
<string name="screen_account_provider_form_subtitle">"Szukaj serwera firmowego, społeczności lub prywatnego."</string>
|
||||||
<string name="screen_account_provider_form_title">"Znajdź dostawcę konta"</string>
|
<string name="screen_account_provider_form_title">"Znajdź dostawcę konta"</string>
|
||||||
<string name="screen_account_provider_signin_subtitle">"Tutaj będą przechowywane Twoje konwersacje - w podobnej formie jak wiadomości widnieją na skrzynce e-mail."</string>
|
<string name="screen_account_provider_signin_subtitle">"Tutaj będą przechowywane Twoje konwersacje - w podobnej formie jak wiadomości widnieją na skrzynce e-mail."</string>
|
||||||
<string name="screen_account_provider_signin_title">"Zamierzasz się zalogować %s"</string>
|
<string name="screen_account_provider_signin_title">"Zamierzasz zalogować się do %s"</string>
|
||||||
<string name="screen_account_provider_signup_subtitle">"Tutaj będą przechowywane Twoje konwersacje - w podobnej formie jak wiadomości widnieją na skrzynce e-mail."</string>
|
<string name="screen_account_provider_signup_subtitle">"Tutaj będą przechowywane Twoje konwersacje - w podobnej formie jak wiadomości widnieją na skrzynce e-mail."</string>
|
||||||
<string name="screen_account_provider_signup_title">"Zamierzasz założyć konto na %s"</string>
|
<string name="screen_account_provider_signup_title">"Zamierzasz założyć konto na %s"</string>
|
||||||
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org jest ogromnym i darmowym serwerem na publicznej sieci Matrix zapewniający bezpieczną i zdecentralizowaną komunikację zarządzaną przez Fundację Matrix.org."</string>
|
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org jest ogromnym i darmowym serwerem na publicznej sieci Matrix zapewniający bezpieczną i zdecentralizowaną komunikację zarządzaną przez Fundację Matrix.org."</string>
|
||||||
|
|
@ -14,20 +14,64 @@
|
||||||
<string name="screen_change_account_provider_subtitle">"Użyj innego dostawcy konta, takiego jak własny serwer lub konta służbowego."</string>
|
<string name="screen_change_account_provider_subtitle">"Użyj innego dostawcy konta, takiego jak własny serwer lub konta służbowego."</string>
|
||||||
<string name="screen_change_account_provider_title">"Zmień dostawcę konta"</string>
|
<string name="screen_change_account_provider_title">"Zmień dostawcę konta"</string>
|
||||||
<string name="screen_change_server_error_invalid_homeserver">"Nie mogliśmy połączyć się z tym serwerem domowym. Sprawdź, czy adres URL serwera został wprowadzony poprawnie. Jeśli adres URL jest poprawny, skontaktuj się z administratorem serwera w celu uzyskania dalszej pomocy."</string>
|
<string name="screen_change_server_error_invalid_homeserver">"Nie mogliśmy połączyć się z tym serwerem domowym. Sprawdź, czy adres URL serwera został wprowadzony poprawnie. Jeśli adres URL jest poprawny, skontaktuj się z administratorem serwera w celu uzyskania dalszej pomocy."</string>
|
||||||
|
<string name="screen_change_server_error_invalid_well_known">"Sliding sync nie jest dostępny z powodu problemu w znanym pliku:
|
||||||
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"Ten serwer obecnie nie obsługuje technologii Sliding Sync."</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"Ten serwer obecnie nie obsługuje technologii Sliding Sync."</string>
|
||||||
<string name="screen_change_server_form_header">"Adres URL serwera domowego"</string>
|
<string name="screen_change_server_form_header">"URL serwera domowego"</string>
|
||||||
<string name="screen_change_server_form_notice">"Możesz połączyć się tylko z serwerem, który obsługuje technologię Sliding Sync. Administrator serwera domowego będzie musiał ją skonfigurować. %1$s"</string>
|
<string name="screen_change_server_form_notice">"Możesz połączyć się tylko z serwerem, który obsługuje technologię Sliding Sync. Administrator serwera domowego będzie musiał ją skonfigurować. %1$s"</string>
|
||||||
<string name="screen_change_server_subtitle">"Jaki jest adres Twojego serwera?"</string>
|
<string name="screen_change_server_subtitle">"Jaki jest adres Twojego serwera?"</string>
|
||||||
<string name="screen_change_server_title">"Wybierz swój serwer"</string>
|
<string name="screen_change_server_title">"Wybierz swój serwer"</string>
|
||||||
<string name="screen_login_error_deactivated_account">"To konto zostało dezaktywowane."</string>
|
<string name="screen_login_error_deactivated_account">"To konto zostało dezaktywowane."</string>
|
||||||
<string name="screen_login_error_invalid_credentials">"Nieprawidłowa nazwa użytkownika i/lub hasło"</string>
|
<string name="screen_login_error_invalid_credentials">"Nieprawidłowa nazwa użytkownika i/lub hasło"</string>
|
||||||
<string name="screen_login_error_invalid_user_id">"To nie jest prawidłowy identyfikator użytkownika. Oczekiwany format: \'@user:homeserver.org\'"</string>
|
<string name="screen_login_error_invalid_user_id">"To nie jest prawidłowy identyfikator użytkownika. Oczekiwany format: \'@user:homeserver.org\'"</string>
|
||||||
|
<string name="screen_login_error_refresh_tokens">"Ten serwer został skonfigurowany do korzystania z tokenów odświeżania. Nie są one obsługiwane, gdy korzystasz z hasła."</string>
|
||||||
<string name="screen_login_error_unsupported_authentication">"Wybrany serwer domowy nie obsługuje uwierzytelniania hasłem, ani OIDC. Skontaktuj się z jego administratorem lub wybierz inny serwer domowy."</string>
|
<string name="screen_login_error_unsupported_authentication">"Wybrany serwer domowy nie obsługuje uwierzytelniania hasłem, ani OIDC. Skontaktuj się z jego administratorem lub wybierz inny serwer domowy."</string>
|
||||||
<string name="screen_login_form_header">"Wprowadź swoje dane"</string>
|
<string name="screen_login_form_header">"Wprowadź swoje dane"</string>
|
||||||
<string name="screen_login_subtitle">"Matrix to otwarta sieć do bezpiecznej i zdecentralizowanej komunikacji."</string>
|
<string name="screen_login_subtitle">"Matrix to otwarta sieć do bezpiecznej i zdecentralizowanej komunikacji."</string>
|
||||||
<string name="screen_login_title">"Witaj ponownie!"</string>
|
<string name="screen_login_title">"Witaj ponownie!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Zaloguj się do %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Zaloguj się do %1$s"</string>
|
||||||
|
<string name="screen_qr_code_login_connecting_subtitle">"Nawiązanie bezpiecznego połączenia"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Nie udało się nawiązać bezpiecznego połączenia z nowym urządzeniem. Twoje istniejące urządzenia są nadal bezpieczne i nie musisz się o nie martwić."</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Co teraz?"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Spróbuj zalogować się ponownie za pomocą kodu QR, jeśli byłby to problem z siecią"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Jeśli napotkasz ten sam problem, użyj innej sieci Wi-FI lub danych mobilnych"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"Jeśli to nie zadziała, zaloguj się ręcznie"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_title">"Połączenie nie jest bezpieczne"</string>
|
||||||
|
<string name="screen_qr_code_login_device_code_subtitle">"Zostaniesz poproszony o wprowadzenie dwóch cyfr widocznych na tym urządzeniu."</string>
|
||||||
|
<string name="screen_qr_code_login_device_code_title">"Wprowadź numer poniżej na innym urządzeniu"</string>
|
||||||
|
<string name="screen_qr_code_login_device_not_signed_in_scan_state_description">"Zaloguj się na drugie urządzenie lub użyj tego, które jest już zalogowane, a następnie spróbuj ponownie."</string>
|
||||||
|
<string name="screen_qr_code_login_device_not_signed_in_scan_state_subtitle">"Drugie urządzenie nie jest zalogowane"</string>
|
||||||
|
<string name="screen_qr_code_login_error_cancelled_subtitle">"Logowanie zostało anulowane na drugim urządzeniu."</string>
|
||||||
|
<string name="screen_qr_code_login_error_cancelled_title">"Prośba o logowanie została anulowana"</string>
|
||||||
|
<string name="screen_qr_code_login_error_declined_subtitle">"Logowanie zostało odrzucone na drugim urządzeniu."</string>
|
||||||
|
<string name="screen_qr_code_login_error_declined_title">"Logowanie odrzucone"</string>
|
||||||
|
<string name="screen_qr_code_login_error_expired_subtitle">"Logowanie wygasło. Spróbuj ponownie."</string>
|
||||||
|
<string name="screen_qr_code_login_error_expired_title">"Logowanie nie zostało ukończone na czas"</string>
|
||||||
|
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Twoje drugie urządzenie nie wspiera logowania się do %s za pomocą kodu QR.
|
||||||
|
|
||||||
|
Spróbuj zalogować się ręcznie lub zeskanuj kod QR na innym urządzeniu."</string>
|
||||||
|
<string name="screen_qr_code_login_error_linking_not_suported_title">"Kod QR nie jest wspierany"</string>
|
||||||
|
<string name="screen_qr_code_login_error_sliding_sync_not_supported_subtitle">"Twój dostawca konta nie obsługuje %1$s."</string>
|
||||||
|
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$s nie jest wspierany"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_button_title">"Gotowy do skanowania"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_1">"Otwórz %1$s na urządzeniu stacjonarnym"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_2">"Kliknij na swój awatar"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_3">"Wybierz %1$s"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_3_action">"“Powiąż nowe urządzenie”"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_4">"Zeskanuj kod QR za pomocą tego urządzenia"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_title">"Otwórz %1$s na innym urządzeniu, aby uzyskać kod QR"</string>
|
||||||
|
<string name="screen_qr_code_login_invalid_scan_state_description">"Użyj kodu QR widocznego na drugim urządzeniu."</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Spróbuj ponownie"</string>
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Spróbuj ponownie"</string>
|
||||||
|
<string name="screen_qr_code_login_invalid_scan_state_subtitle">"Błędny kod QR"</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_button">"Przejdź do ustawień aparatu"</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_state_description">"Musisz przyznać uprawnienia %1$s do korzystania z kamery, aby kontynuować."</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_state_title">"Zezwól na dostęp do kamery, aby zeskanować kod QR"</string>
|
||||||
|
<string name="screen_qr_code_login_scanning_state_title">"Skanuj kod QR"</string>
|
||||||
|
<string name="screen_qr_code_login_start_over_button">"Zacznij od nowa"</string>
|
||||||
|
<string name="screen_qr_code_login_unknown_error_description">"Wystąpił nieoczekiwany błąd. Spróbuj ponownie."</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_loading">"Oczekiwanie na drugie urządzenie"</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_subtitle">"Twój dostawca konta może poprosić o podany kod, aby zweryfikować logowanie."</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_title">"Twój kod weryfikacyjny"</string>
|
||||||
<string name="screen_server_confirmation_change_server">"Zmień dostawcę konta"</string>
|
<string name="screen_server_confirmation_change_server">"Zmień dostawcę konta"</string>
|
||||||
<string name="screen_server_confirmation_message_login_element_dot_io">"Serwer prywatny dla pracowników Element."</string>
|
<string name="screen_server_confirmation_message_login_element_dot_io">"Serwer prywatny dla pracowników Element."</string>
|
||||||
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix to otwarta sieć do bezpiecznej i zdecentralizowanej komunikacji."</string>
|
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix to otwarta sieć do bezpiecznej i zdecentralizowanej komunikacji."</string>
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,48 @@
|
||||||
<string name="screen_login_subtitle">"Matrix är ett öppet nätverk för säker, decentraliserad kommunikation."</string>
|
<string name="screen_login_subtitle">"Matrix är ett öppet nätverk för säker, decentraliserad kommunikation."</string>
|
||||||
<string name="screen_login_title">"Välkommen tillbaka!"</string>
|
<string name="screen_login_title">"Välkommen tillbaka!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Logga in på %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Logga in på %1$s"</string>
|
||||||
|
<string name="screen_qr_code_login_connecting_subtitle">"Upprättar en säker anslutning"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"En säker anslutning kunde inte göras till den nya enheten. Dina befintliga enheter är fortfarande säkra och du behöver inte oroa dig för dem."</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Nu då?"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Pröva att logga in igen med en QR-kod ifall detta skulle vara ett nätverksproblem"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Om du stöter på samma problem, prova ett annat wifi-nätverk eller använd din mobildata istället för wifi"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"Om det inte fungerar, logga in manuellt"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_title">"Anslutningen är inte säker"</string>
|
||||||
|
<string name="screen_qr_code_login_device_code_subtitle">"Du kommer att bli ombedd att ange de två siffrorna som visas på den här enheten."</string>
|
||||||
|
<string name="screen_qr_code_login_device_code_title">"Ange numret nedan på din andra enhet"</string>
|
||||||
|
<string name="screen_qr_code_login_device_not_signed_in_scan_state_description">"Logga in på din andra enhet och försök sedan igen, eller använd en annan enhet som redan är inloggad."</string>
|
||||||
|
<string name="screen_qr_code_login_device_not_signed_in_scan_state_subtitle">"Den andra enheten är inte inloggad"</string>
|
||||||
|
<string name="screen_qr_code_login_error_cancelled_subtitle">"Inloggningen avbröts på den andra enheten."</string>
|
||||||
|
<string name="screen_qr_code_login_error_cancelled_title">"Inloggningsförfrågan avbröts"</string>
|
||||||
|
<string name="screen_qr_code_login_error_declined_subtitle">"Inloggningen avvisades på den andra enheten."</string>
|
||||||
|
<string name="screen_qr_code_login_error_declined_title">"Inloggning avvisad"</string>
|
||||||
|
<string name="screen_qr_code_login_error_expired_subtitle">"Inloggningen har löpt ut. Vänligen försök igen."</string>
|
||||||
|
<string name="screen_qr_code_login_error_expired_title">"Inloggningen slutfördes inte i tid"</string>
|
||||||
|
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Din andra enhet stöder inte inloggning i %s med en QR-kod.
|
||||||
|
|
||||||
|
Prova att logga in manuellt eller skanna QR-koden med en annan enhet."</string>
|
||||||
|
<string name="screen_qr_code_login_error_linking_not_suported_title">"QR-kod stöds inte"</string>
|
||||||
|
<string name="screen_qr_code_login_error_sliding_sync_not_supported_subtitle">"Din kontoleverantör stöder inte %1$s."</string>
|
||||||
|
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$s stöds inte"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_button_title">"Redo att skanna"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_1">"Öppna %1$s på en skrivbordsenhet"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_2">"Klicka på din avatar"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_3">"Välj %1$s"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_3_action">"”Länka ny enhet”"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_4">"Skanna QR-koden med den här enheten"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_title">"Öppna %1$s på en annan enhet för att få QR-koden"</string>
|
||||||
|
<string name="screen_qr_code_login_invalid_scan_state_description">"Använd QR-koden som visas på den andra enheten."</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Försök igen"</string>
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Försök igen"</string>
|
||||||
|
<string name="screen_qr_code_login_invalid_scan_state_subtitle">"Fel QR-kod"</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_button">"Gå till kamerainställningar"</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_state_description">"Du måste ge tillstånd för %1$s att använda enhetens kamera för att kunna fortsätta."</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_state_title">"Tillåt kameraåtkomst för att skanna QR-koden"</string>
|
||||||
|
<string name="screen_qr_code_login_scanning_state_title">"Skanna QR-koden"</string>
|
||||||
|
<string name="screen_qr_code_login_start_over_button">"Börja om"</string>
|
||||||
|
<string name="screen_qr_code_login_unknown_error_description">"Ett oväntat fel inträffade. Vänligen försök igen."</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_loading">"Väntar på din andra enhet"</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_subtitle">"Din kontoleverantör kan be om följande kod för att verifiera inloggningen."</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_title">"Din verifieringskod"</string>
|
||||||
<string name="screen_server_confirmation_change_server">"Byt kontoleverantör"</string>
|
<string name="screen_server_confirmation_change_server">"Byt kontoleverantör"</string>
|
||||||
<string name="screen_server_confirmation_message_login_element_dot_io">"En privat server för Element-anställda."</string>
|
<string name="screen_server_confirmation_message_login_element_dot_io">"En privat server för Element-anställda."</string>
|
||||||
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix är ett öppet nätverk för säker, decentraliserad kommunikation."</string>
|
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix är ett öppet nätverk för säker, decentraliserad kommunikation."</string>
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,48 @@
|
||||||
<string name="screen_login_subtitle">"Matrix — це відкрита мережа для безпечної, децентралізованої комунікації."</string>
|
<string name="screen_login_subtitle">"Matrix — це відкрита мережа для безпечної, децентралізованої комунікації."</string>
|
||||||
<string name="screen_login_title">"З поверненням!"</string>
|
<string name="screen_login_title">"З поверненням!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Увійти в %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Увійти в %1$s"</string>
|
||||||
|
<string name="screen_qr_code_login_connecting_subtitle">"Встановлення безпечного з\'єднання"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Не вдалося встановити безпечне з\'єднання з новим пристроєм. Ваші існуючі пристрої все ще в безпеці, і вам не потрібно про них турбуватися."</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Що тепер?"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Спробуйте увійти ще раз за допомогою QR-коду, якщо це була проблема з мережею"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Якщо ви зіткнулися з тією ж проблемою, спробуйте іншу мережу Wi-Fi або використовуйте мобільний інтернет замість Wi-Fi"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"Якщо це не спрацює, увійдіть вручну"</string>
|
||||||
|
<string name="screen_qr_code_login_connection_note_secure_state_title">"З\'єднання не є безпечним"</string>
|
||||||
|
<string name="screen_qr_code_login_device_code_subtitle">"Вас попросять ввести дві цифри, показані на цьому пристрої."</string>
|
||||||
|
<string name="screen_qr_code_login_device_code_title">"Введіть номер нижче на іншому пристрої"</string>
|
||||||
|
<string name="screen_qr_code_login_device_not_signed_in_scan_state_description">"Увійдіть на іншому пристрої та спробуйте ще раз або скористайтеся іншим пристроєм, що вже в обліковому записі."</string>
|
||||||
|
<string name="screen_qr_code_login_device_not_signed_in_scan_state_subtitle">"Інший пристрій не ввійшов"</string>
|
||||||
|
<string name="screen_qr_code_login_error_cancelled_subtitle">"Вхід було скасовано на іншому пристрої."</string>
|
||||||
|
<string name="screen_qr_code_login_error_cancelled_title">"Запит на вхід скасовано"</string>
|
||||||
|
<string name="screen_qr_code_login_error_declined_subtitle">"Вхід був відхилений на іншому пристрої."</string>
|
||||||
|
<string name="screen_qr_code_login_error_declined_title">"Вхід відхилено"</string>
|
||||||
|
<string name="screen_qr_code_login_error_expired_subtitle">"Термін входу сплив. Будь ласка, спробуйте ще раз."</string>
|
||||||
|
<string name="screen_qr_code_login_error_expired_title">"Вхід не було завершено вчасно"</string>
|
||||||
|
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Ваш інший пристрій не підтримує вхід у %s за допомогою QR-коду.
|
||||||
|
|
||||||
|
Спробуйте ввійти вручну або відскануйте QR-код за допомогою іншого пристрою."</string>
|
||||||
|
<string name="screen_qr_code_login_error_linking_not_suported_title">"QR-код не підтримується"</string>
|
||||||
|
<string name="screen_qr_code_login_error_sliding_sync_not_supported_subtitle">"Постачальник вашого облікового запису не підтримує %1$s."</string>
|
||||||
|
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$s не підтримується"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_button_title">"Готовий до сканування"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_1">"Відкрийте %1$s на комп\'ютері"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_2">"Натисніть на свою аватарку"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_3">"Оберіть %1$s"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_3_action">"“Підключити новий пристрій”"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_item_4">"Відскануйте QR-код цим пристроєм"</string>
|
||||||
|
<string name="screen_qr_code_login_initial_state_title">"Відкрийте %1$s на іншому пристрої, щоб отримати QR-код"</string>
|
||||||
|
<string name="screen_qr_code_login_invalid_scan_state_description">"Використовуйте QR-код, показаний на іншому пристрої."</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Спробуйте ще раз"</string>
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Спробуйте ще раз"</string>
|
||||||
|
<string name="screen_qr_code_login_invalid_scan_state_subtitle">"Неправильний QR-код"</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_button">"Перейти до налаштувань камери"</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_state_description">"Вам потрібно дати дозвіл %1$s на використання камери вашого пристрою, щоб продовжити."</string>
|
||||||
|
<string name="screen_qr_code_login_no_camera_permission_state_title">"Надайте доступ до камери, щоб сканувати QR-код"</string>
|
||||||
|
<string name="screen_qr_code_login_scanning_state_title">"Відскануйте QR-код"</string>
|
||||||
|
<string name="screen_qr_code_login_start_over_button">"Почати спочатку"</string>
|
||||||
|
<string name="screen_qr_code_login_unknown_error_description">"Сталася несподівана помилка. Будь ласка, спробуйте ще раз."</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_loading">"Чекаємо на ваш інший пристрій"</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_subtitle">"Постачальник облікового запису може попросити вас ввести код нижче для підтвердження входу."</string>
|
||||||
|
<string name="screen_qr_code_login_verify_code_title">"Ваш код підтвердження"</string>
|
||||||
<string name="screen_server_confirmation_change_server">"Змінити провайдера облікового запису"</string>
|
<string name="screen_server_confirmation_change_server">"Змінити провайдера облікового запису"</string>
|
||||||
<string name="screen_server_confirmation_message_login_element_dot_io">"Приватний сервер для співробітників Element."</string>
|
<string name="screen_server_confirmation_message_login_element_dot_io">"Приватний сервер для співробітників Element."</string>
|
||||||
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix — це відкрита мережа для безпечної, децентралізованої комунікації."</string>
|
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix — це відкрита мережа для безпечної, децентралізованої комунікації."</string>
|
||||||
|
|
|
||||||
42
features/login/impl/src/main/res/values-uz/translations.xml
Normal file
42
features/login/impl/src/main/res/values-uz/translations.xml
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_account_provider_change">"Hisob provayderini o\'zgartiring"</string>
|
||||||
|
<string name="screen_account_provider_form_hint">"Uy server manzili"</string>
|
||||||
|
<string name="screen_account_provider_form_notice">"Qidiruv so\'zini yoki domen manzilini kiriting."</string>
|
||||||
|
<string name="screen_account_provider_form_subtitle">"Kompaniya, jamoa yoki shaxsiy serverni qidiring."</string>
|
||||||
|
<string name="screen_account_provider_form_title">"Hisob provayderini toping"</string>
|
||||||
|
<string name="screen_account_provider_signin_subtitle">"Bu sizning suhbatlaringiz yashaydigan joy - xuddi siz elektron pochta xabarlaringizni saqlash uchun elektron pochta provayderidan foydalanganingiz kabi."</string>
|
||||||
|
<string name="screen_account_provider_signin_title">"Siz %sga kirmoqchisiz"</string>
|
||||||
|
<string name="screen_account_provider_signup_subtitle">"Bu sizning suhbatlaringiz yashaydigan joy - xuddi siz elektron pochta xabarlaringizni saqlash uchun elektron pochta provayderidan foydalanganingiz kabi."</string>
|
||||||
|
<string name="screen_account_provider_signup_title">"Siz %sda hisob yaratmoqchisiz"</string>
|
||||||
|
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org - bu Matrix.org Jamg\'armasi tomonidan boshqariladigan xavfsiz, markazlashtirilmagan aloqa uchun ommaviy Matrix tarmog\'idagi katta, bepul server."</string>
|
||||||
|
<string name="screen_change_account_provider_other">"Boshqa"</string>
|
||||||
|
<string name="screen_change_account_provider_subtitle">"Shaxsiy serveringiz yoki ishchi hisob qaydnomangiz kabi boshqa hisob provayderidan foydalaning."</string>
|
||||||
|
<string name="screen_change_account_provider_title">"Hisob provayderini o\'zgartiring"</string>
|
||||||
|
<string name="screen_change_server_error_invalid_homeserver">"Bu uy serveriga kira olmadik. Iltimos, uy serverining URL manzilini to\'ri kiritganingizni tekshiring. Agar URL toʻgʻri boʻlsa, qoʻshimcha yordam olish uchun uy serveri administratoriga murojaat qiling."</string>
|
||||||
|
<string name="screen_change_server_error_no_sliding_sync_message">"Hozirda bu server siljish sinxronlashni qo‘llab-quvvatlamaydi."</string>
|
||||||
|
<string name="screen_change_server_form_header">"Uy serverining URL manzili"</string>
|
||||||
|
<string name="screen_change_server_form_notice">"Siz faqat siljish sinxronlashni qo\'llab-quvvatlaydigan mavjud serverga ulanishingiz mumkin. Uy serveringiz administratori uni sozlashi kerak.%1$s"</string>
|
||||||
|
<string name="screen_change_server_subtitle">"Serveringizning manzili nima?"</string>
|
||||||
|
<string name="screen_change_server_title">"Serveringizni tanlang"</string>
|
||||||
|
<string name="screen_login_error_deactivated_account">"Bu hisob o‘chirilgan."</string>
|
||||||
|
<string name="screen_login_error_invalid_credentials">"Notog\'ri foydalanuvchi nomi va/yoki parol"</string>
|
||||||
|
<string name="screen_login_error_invalid_user_id">"Bu haqiqiy foydalanuvchi identifikatori emas. Kutilayotgan format: \'@user:homeserver.org\'"</string>
|
||||||
|
<string name="screen_login_error_unsupported_authentication">"Tanlangan uy serveri parol yoki OIDC loginni qo\'lab-quvvatlamaydi. Iltimos, administratoringizga murojaat qiling yoki boshqa uy serverini tanlang."</string>
|
||||||
|
<string name="screen_login_form_header">"Tafsilotlaringizni kiriting"</string>
|
||||||
|
<string name="screen_login_subtitle">"Matrix xavfsiz, markazlashmagan aloqa uchun ochiq tarmoqdir."</string>
|
||||||
|
<string name="screen_login_title">"Qaytib kelganingizdan xursandmiz!"</string>
|
||||||
|
<string name="screen_login_title_with_homeserver">"Kirish%1$s"</string>
|
||||||
|
<string name="screen_server_confirmation_change_server">"Hisob provayderini o\'zgartiring"</string>
|
||||||
|
<string name="screen_server_confirmation_message_login_element_dot_io">"Element xodimlari uchun shaxsiy server."</string>
|
||||||
|
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix xavfsiz, markazlashmagan aloqa uchun ochiq tarmoqdir."</string>
|
||||||
|
<string name="screen_server_confirmation_message_register">"Bu sizning suhbatlaringiz yashaydigan joy - xuddi siz elektron pochta xabarlaringizni saqlash uchun elektron pochta provayderidan foydalanganingiz kabi."</string>
|
||||||
|
<string name="screen_server_confirmation_title_login">"Siz tizimga kirmoqchisiz%1$s"</string>
|
||||||
|
<string name="screen_server_confirmation_title_register">"Hisob yaratmoqchisiz%1$s"</string>
|
||||||
|
<string name="screen_waitlist_message">"Hozirgi paytda %2$sga %1$sda talab yuqori. Bir necha kundan keyin ilovaga qayting va qaytadan urining.
|
||||||
|
|
||||||
|
Sabr-toqatingiz uchun rahmat!"</string>
|
||||||
|
<string name="screen_waitlist_message_success">"%1$sga Xush kelibsiz!"</string>
|
||||||
|
<string name="screen_waitlist_title">"Siz deyarli keldingiz."</string>
|
||||||
|
<string name="screen_waitlist_title_success">"Siz kirdingiz."</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -13,12 +13,12 @@
|
||||||
<string name="screen_change_account_provider_other">"其他"</string>
|
<string name="screen_change_account_provider_other">"其他"</string>
|
||||||
<string name="screen_change_account_provider_subtitle">"使用其他帐户提供者,例如您自己的私人服务器或工作帐户。"</string>
|
<string name="screen_change_account_provider_subtitle">"使用其他帐户提供者,例如您自己的私人服务器或工作帐户。"</string>
|
||||||
<string name="screen_change_account_provider_title">"更改账户提供者"</string>
|
<string name="screen_change_account_provider_title">"更改账户提供者"</string>
|
||||||
<string name="screen_change_server_error_invalid_homeserver">"我们无法访问此主服务器。请检查您输入的主服务器网址是否正确。如果 URL 正确,请联系您的主服务器管理员寻求进一步帮助。"</string>
|
<string name="screen_change_server_error_invalid_homeserver">"我们无法访问此服务器。请检查您输入的服务器网址是否正确。如果 URL 正确,请联系您的服务器管理员寻求进一步帮助。"</string>
|
||||||
<string name="screen_change_server_error_invalid_well_known">"由于 Well Known 文件中的问题,Sliding Sync 不可用:
|
<string name="screen_change_server_error_invalid_well_known">"由于 Well Known 文件中的问题,Sliding Sync 不可用:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"该服务器目前不支持sliding sync。"</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"该服务器目前不支持 Sliding Sync。"</string>
|
||||||
<string name="screen_change_server_form_header">"主服务器网址"</string>
|
<string name="screen_change_server_form_header">"服务器网址"</string>
|
||||||
<string name="screen_change_server_form_notice">"您只能连接到支持sliding sync的现有服务器。您的主服务器管理员需要对其进行配置。%1$s"</string>
|
<string name="screen_change_server_form_notice">"您只能连接到支持 Sliding Sync 的现有服务器。您的服务器管理员需要对其进行配置。%1$s"</string>
|
||||||
<string name="screen_change_server_subtitle">"您的服务器地址是什么?"</string>
|
<string name="screen_change_server_subtitle">"您的服务器地址是什么?"</string>
|
||||||
<string name="screen_change_server_title">"选择服务器"</string>
|
<string name="screen_change_server_title">"选择服务器"</string>
|
||||||
<string name="screen_login_error_deactivated_account">"该账户已被停用。"</string>
|
<string name="screen_login_error_deactivated_account">"该账户已被停用。"</string>
|
||||||
|
|
@ -34,11 +34,13 @@
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"无法与新设备建立安全连接。您现有的设备仍然安全,无需担心。"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"无法与新设备建立安全连接。您现有的设备仍然安全,无需担心。"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"现在怎么办?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"现在怎么办?"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"如果这是网络问题,请尝试使用二维码再次登录"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"如果这是网络问题,请尝试使用二维码再次登录"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"如果你遇到同样的问题,请尝试使用不同的 WiFi 网络或使用你的移动数据代替 WiFi"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"如果遇到同样的问题,请尝试使用不同的 WiFi 网络或使用移动数据代替 WiFi"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"如果不起作用,请手动登录"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"如果不起作用,请手动登录"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_title">"连接不安全"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_title">"连接不安全"</string>
|
||||||
<string name="screen_qr_code_login_device_code_subtitle">"您会被要求输入此设备上显示的两位数。"</string>
|
<string name="screen_qr_code_login_device_code_subtitle">"您会被要求输入此设备上显示的两位数。"</string>
|
||||||
<string name="screen_qr_code_login_device_code_title">"在您的其他设备上输入下面的数字"</string>
|
<string name="screen_qr_code_login_device_code_title">"在您的其他设备上输入下面的数字"</string>
|
||||||
|
<string name="screen_qr_code_login_device_not_signed_in_scan_state_description">"在其他设备登录后重试,或使用另一个已登录的设备。"</string>
|
||||||
|
<string name="screen_qr_code_login_device_not_signed_in_scan_state_subtitle">"其他设备未登录"</string>
|
||||||
<string name="screen_qr_code_login_error_cancelled_subtitle">"登录被另一台设备取消"</string>
|
<string name="screen_qr_code_login_error_cancelled_subtitle">"登录被另一台设备取消"</string>
|
||||||
<string name="screen_qr_code_login_error_cancelled_title">"登录请求已取消"</string>
|
<string name="screen_qr_code_login_error_cancelled_title">"登录请求已取消"</string>
|
||||||
<string name="screen_qr_code_login_error_declined_subtitle">"其它设备未接受请求"</string>
|
<string name="screen_qr_code_login_error_declined_subtitle">"其它设备未接受请求"</string>
|
||||||
|
|
@ -74,8 +76,8 @@
|
||||||
<string name="screen_server_confirmation_message_login_element_dot_io">"专为 Element 员工提供的私人服务器。"</string>
|
<string name="screen_server_confirmation_message_login_element_dot_io">"专为 Element 员工提供的私人服务器。"</string>
|
||||||
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix 是一个用于安全、去中心化通信的开放网络。"</string>
|
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix 是一个用于安全、去中心化通信的开放网络。"</string>
|
||||||
<string name="screen_server_confirmation_message_register">"这是您的对话将进行的地方,就像您使用电子邮件提供商来保存电子邮件一样。"</string>
|
<string name="screen_server_confirmation_message_register">"这是您的对话将进行的地方,就像您使用电子邮件提供商来保存电子邮件一样。"</string>
|
||||||
<string name="screen_server_confirmation_title_login">"你即将登录 %1$s"</string>
|
<string name="screen_server_confirmation_title_login">"即将登录 %1$s"</string>
|
||||||
<string name="screen_server_confirmation_title_register">"你即将在 %1$s 上创建一个账户"</string>
|
<string name="screen_server_confirmation_title_register">"即将在 %1$s 上创建一个账户"</string>
|
||||||
<string name="screen_waitlist_message">"目前 %1$s 上 %2$s 的负载很大。过几天再回来试试吧。
|
<string name="screen_waitlist_message">"目前 %1$s 上 %2$s 的负载很大。过几天再回来试试吧。
|
||||||
|
|
||||||
感谢您的耐心!"</string>
|
感谢您的耐心!"</string>
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,8 @@ import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.login.api.oidc.OidcAction
|
|
||||||
import io.element.android.features.login.impl.DefaultLoginUserStory
|
import io.element.android.features.login.impl.DefaultLoginUserStory
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||||
import io.element.android.features.login.impl.oidc.customtab.DefaultOidcActionFlow
|
|
||||||
import io.element.android.features.login.impl.util.defaultAccountProvider
|
import io.element.android.features.login.impl.util.defaultAccountProvider
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||||
|
|
@ -31,6 +29,8 @@ import io.element.android.libraries.matrix.test.A_HOMESERVER
|
||||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_OIDC
|
import io.element.android.libraries.matrix.test.A_HOMESERVER_OIDC
|
||||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||||
|
import io.element.android.libraries.oidc.api.OidcAction
|
||||||
|
import io.element.android.libraries.oidc.impl.customtab.DefaultOidcActionFlow
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.waitForPredicate
|
import io.element.android.tests.testutils.waitForPredicate
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -274,7 +274,7 @@ class ConfirmAccountProviderPresenterTest {
|
||||||
params = params,
|
params = params,
|
||||||
accountProviderDataSource = accountProviderDataSource,
|
accountProviderDataSource = accountProviderDataSource,
|
||||||
authenticationService = matrixAuthenticationService,
|
authenticationService = matrixAuthenticationService,
|
||||||
defaultOidcActionFlow = defaultOidcActionFlow,
|
oidcActionFlow = defaultOidcActionFlow,
|
||||||
defaultLoginUserStory = defaultLoginUserStory,
|
defaultLoginUserStory = defaultLoginUserStory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.logout.api
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to trigger a log out of the current user from any part of the app.
|
||||||
|
*/
|
||||||
|
interface LogoutUseCase {
|
||||||
|
/**
|
||||||
|
* Log out the current user and then perform any needed cleanup tasks.
|
||||||
|
* @param ignoreSdkError if true, the SDK error will be ignored and the user will be logged out anyway.
|
||||||
|
* @return the session id of the logged out user.
|
||||||
|
*/
|
||||||
|
suspend fun logout(ignoreSdkError: Boolean): String
|
||||||
|
|
||||||
|
interface Factory {
|
||||||
|
fun create(sessionId: String): LogoutUseCase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.logout.impl
|
||||||
|
|
||||||
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import io.element.android.features.logout.api.LogoutUseCase
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||||
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
|
||||||
|
class DefaultLogoutUseCase @AssistedInject constructor(
|
||||||
|
@Assisted private val sessionId: String,
|
||||||
|
private val matrixClientProvider: MatrixClientProvider,
|
||||||
|
) : LogoutUseCase {
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory : LogoutUseCase.Factory {
|
||||||
|
override fun create(sessionId: String): DefaultLogoutUseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun logout(ignoreSdkError: Boolean): String {
|
||||||
|
val matrixClient = matrixClientProvider.getOrRestore(SessionId(sessionId)).getOrThrow()
|
||||||
|
matrixClient.logout(ignoreSdkError = ignoreSdkError)
|
||||||
|
return sessionId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.logout.impl
|
||||||
|
|
||||||
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import io.element.android.features.logout.api.LogoutUseCase
|
||||||
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.matrix.api.user.CurrentSessionIdHolder
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@ContributesTo(SessionScope::class)
|
||||||
|
object SessionLogoutModule {
|
||||||
|
@Provides
|
||||||
|
fun provideLogoutUseCase(
|
||||||
|
currentSessionIdHolder: CurrentSessionIdHolder,
|
||||||
|
factory: DefaultLogoutUseCase.Factory,
|
||||||
|
): LogoutUseCase {
|
||||||
|
return factory.create(currentSessionIdHolder.current.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
18
features/logout/impl/src/main/res/values-nl/translations.xml
Normal file
18
features/logout/impl/src/main/res/values-nl/translations.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_signout_confirmation_dialog_content">"Weet je zeker dat je je wilt uitloggen?"</string>
|
||||||
|
<string name="screen_signout_confirmation_dialog_submit">"Uitloggen"</string>
|
||||||
|
<string name="screen_signout_confirmation_dialog_title">"Uitloggen"</string>
|
||||||
|
<string name="screen_signout_in_progress_dialog_content">"Uitloggen…"</string>
|
||||||
|
<string name="screen_signout_key_backup_disabled_subtitle">"Je staat op het punt uit te loggen bij je laatste sessie. Als je je nu uitlogt, verlies je de toegang tot je versleutelde berichten."</string>
|
||||||
|
<string name="screen_signout_key_backup_disabled_title">"Je hebt de back-up uitgeschakeld"</string>
|
||||||
|
<string name="screen_signout_key_backup_offline_subtitle">"De backup van je sleutels was nog bezig toen je offline ging. Maak opnieuw verbinding zodat er een back-up van je sleutels kan worden gemaakt voordat je uitlogt."</string>
|
||||||
|
<string name="screen_signout_key_backup_offline_title">"De backup van je sleutels is nog bezig"</string>
|
||||||
|
<string name="screen_signout_key_backup_ongoing_subtitle">"Wacht tot dit voltooid is voordat je uitlogt."</string>
|
||||||
|
<string name="screen_signout_key_backup_ongoing_title">"De backup van je sleutels is nog bezig"</string>
|
||||||
|
<string name="screen_signout_preference_item">"Uitloggen"</string>
|
||||||
|
<string name="screen_signout_recovery_disabled_subtitle">"Je staat op het punt uit te loggen bij je laatste sessie. Als je je nu uitlogt, verlies je de toegang tot je versleutelde berichten."</string>
|
||||||
|
<string name="screen_signout_recovery_disabled_title">"Herstelmogelijkheid niet ingesteld"</string>
|
||||||
|
<string name="screen_signout_save_recovery_key_subtitle">"Je staat op het punt uit te loggen bij je laatste sessie. Als je je nu uitlogt, kan het dat je de toegang tot je versleutelde berichten verliest."</string>
|
||||||
|
<string name="screen_signout_save_recovery_key_title">"Heb je je herstelsleutel opgeslagen?"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_signout_confirmation_dialog_content">"Czy na pewno chcesz się wylogować?"</string>
|
<string name="screen_signout_confirmation_dialog_content">"Czy na pewno chcesz się wylogować?"</string>
|
||||||
<string name="screen_signout_confirmation_dialog_submit">"Wyloguj się"</string>
|
<string name="screen_signout_confirmation_dialog_submit">"Wyloguj"</string>
|
||||||
<string name="screen_signout_confirmation_dialog_title">"Wyloguj się"</string>
|
<string name="screen_signout_confirmation_dialog_title">"Wyloguj"</string>
|
||||||
<string name="screen_signout_in_progress_dialog_content">"Wylogowywanie…"</string>
|
<string name="screen_signout_in_progress_dialog_content">"Wylogowywanie…"</string>
|
||||||
<string name="screen_signout_key_backup_disabled_subtitle">"Zamierzasz wylogować się ze swojej ostatniej sesji. Jeśli wylogujesz się teraz, stracisz dostęp do swoich wiadomości szyfrowanych."</string>
|
<string name="screen_signout_key_backup_disabled_subtitle">"Zamierzasz wylogować się ze swojej ostatniej sesji. Jeśli wylogujesz się teraz, stracisz dostęp do swoich wiadomości szyfrowanych."</string>
|
||||||
<string name="screen_signout_key_backup_disabled_title">"Wyłączyłeś backup"</string>
|
<string name="screen_signout_key_backup_disabled_title">"Wyłączyłeś backup"</string>
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
<string name="screen_signout_key_backup_offline_title">"Twoje klucze są nadal archiwizowane"</string>
|
<string name="screen_signout_key_backup_offline_title">"Twoje klucze są nadal archiwizowane"</string>
|
||||||
<string name="screen_signout_key_backup_ongoing_subtitle">"Zanim się wylogujesz, poczekaj na zakończenie operacji."</string>
|
<string name="screen_signout_key_backup_ongoing_subtitle">"Zanim się wylogujesz, poczekaj na zakończenie operacji."</string>
|
||||||
<string name="screen_signout_key_backup_ongoing_title">"Twoje klucze są nadal archiwizowane"</string>
|
<string name="screen_signout_key_backup_ongoing_title">"Twoje klucze są nadal archiwizowane"</string>
|
||||||
<string name="screen_signout_preference_item">"Wyloguj się"</string>
|
<string name="screen_signout_preference_item">"Wyloguj"</string>
|
||||||
<string name="screen_signout_recovery_disabled_subtitle">"Zamierzasz wylogować się ze swojej ostatniej sesji. Jeśli wylogujesz się teraz, stracisz dostęp do swoich wiadomości szyfrowanych."</string>
|
<string name="screen_signout_recovery_disabled_subtitle">"Zamierzasz wylogować się ze swojej ostatniej sesji. Jeśli wylogujesz się teraz, stracisz dostęp do swoich wiadomości szyfrowanych."</string>
|
||||||
<string name="screen_signout_recovery_disabled_title">"Nie ustawiono przywracania"</string>
|
<string name="screen_signout_recovery_disabled_title">"Nie ustawiono przywracania"</string>
|
||||||
<string name="screen_signout_save_recovery_key_subtitle">"Zamierzasz wylogować się ze swojej ostatniej sesji. Jeśli wylogujesz się teraz, stracisz dostęp do swoich wiadomości szyfrowanych."</string>
|
<string name="screen_signout_save_recovery_key_subtitle">"Zamierzasz wylogować się ze swojej ostatniej sesji. Jeśli wylogujesz się teraz, stracisz dostęp do swoich wiadomości szyfrowanych."</string>
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,9 @@
|
||||||
<string name="screen_signout_confirmation_dialog_submit">"Sair"</string>
|
<string name="screen_signout_confirmation_dialog_submit">"Sair"</string>
|
||||||
<string name="screen_signout_confirmation_dialog_title">"Sair"</string>
|
<string name="screen_signout_confirmation_dialog_title">"Sair"</string>
|
||||||
<string name="screen_signout_in_progress_dialog_content">"Saindo…"</string>
|
<string name="screen_signout_in_progress_dialog_content">"Saindo…"</string>
|
||||||
|
<string name="screen_signout_key_backup_disabled_title">"Você desativou o backup"</string>
|
||||||
|
<string name="screen_signout_key_backup_ongoing_title">"O backup das suas chaves ainda está em andamento"</string>
|
||||||
<string name="screen_signout_preference_item">"Sair"</string>
|
<string name="screen_signout_preference_item">"Sair"</string>
|
||||||
|
<string name="screen_signout_recovery_disabled_title">"A recuperação não está configurada"</string>
|
||||||
|
<string name="screen_signout_save_recovery_key_title">"Você salvou sua chave de recuperação?"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
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