Merge branch 'release/0.5.1' into main
This commit is contained in:
commit
19a9616681
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"
|
||||
],
|
||||
"ignoreDeps" : [
|
||||
"string:app_name"
|
||||
"string:app_name",
|
||||
"gradle"
|
||||
],
|
||||
"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
|
||||
java-version: '17'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- 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
|
||||
java-version: '17'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- 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
|
||||
java-version: '17'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- 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
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
update-gradle-wrapper:
|
||||
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:
|
||||
- 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
|
||||
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:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
repo-token: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
|
||||
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
|
||||
java-version: '17'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Assemble debug APK
|
||||
|
|
|
|||
4
.github/workflows/nightlyReports.yml
vendored
4
.github/workflows/nightlyReports.yml
vendored
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
java-version: '17'
|
||||
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: false
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ jobs:
|
|||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- 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
|
||||
java-version: '17'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Set up Python 3.12
|
||||
|
|
@ -90,7 +90,7 @@ jobs:
|
|||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Run Konsist tests
|
||||
|
|
@ -130,7 +130,7 @@ jobs:
|
|||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Build Gplay Debug
|
||||
|
|
@ -174,7 +174,7 @@ jobs:
|
|||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Run Detekt
|
||||
|
|
@ -214,7 +214,7 @@ jobs:
|
|||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Run Ktlint check
|
||||
|
|
@ -254,7 +254,7 @@ jobs:
|
|||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Run Knit
|
||||
|
|
|
|||
2
.github/workflows/recordScreenshots.yml
vendored
2
.github/workflows/recordScreenshots.yml
vendored
|
|
@ -39,7 +39,7 @@ jobs:
|
|||
java-version: '17'
|
||||
# Add gradle cache, this should speed up the process
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- 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
|
||||
java-version: '17'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
- name: Create app bundle
|
||||
env:
|
||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||
|
|
@ -61,7 +61,7 @@ jobs:
|
|||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
- name: Create Enterprise app bundle
|
||||
env:
|
||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||
|
|
@ -89,7 +89,7 @@ jobs:
|
|||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
- name: Create APKs
|
||||
env:
|
||||
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
|
||||
java-version: '17'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- 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
|
||||
java-version: '17'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- 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
|
||||
java-version: '17'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
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"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.9.24" />
|
||||
<option name="version" value="1.9.25" />
|
||||
</component>
|
||||
</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)
|
||||
=========================================
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[](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=vector-im_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=element-x-android)
|
||||
[](https://sonarcloud.io/summary/new_code?id=element-x-android)
|
||||
[](https://codecov.io/github/vector-im/element-x-android)
|
||||
[](https://matrix.to/#/#element-x-android:matrix.org)
|
||||
[](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 {
|
||||
static int windowAttachCount(android.view.View);
|
||||
}
|
||||
|
||||
-keep class io.element.android.x.di.** { *; }
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
<locale android:name="in"/>
|
||||
<locale android:name="it"/>
|
||||
<locale android:name="ka"/>
|
||||
<locale android:name="nl"/>
|
||||
<locale android:name="pl"/>
|
||||
<locale android:name="pt"/>
|
||||
<locale android:name="pt_BR"/>
|
||||
|
|
@ -21,6 +22,7 @@
|
|||
<locale android:name="sk"/>
|
||||
<locale android:name="sv"/>
|
||||
<locale android:name="uk"/>
|
||||
<locale android:name="uz"/>
|
||||
<locale android:name="zh-CN"/>
|
||||
<locale android:name="zh-TW"/>
|
||||
</locale-config>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ object TimelineConfig {
|
|||
StateEventType.ROOM_GUEST_ACCESS,
|
||||
StateEventType.ROOM_HISTORY_VISIBILITY,
|
||||
StateEventType.ROOM_JOIN_RULES,
|
||||
StateEventType.ROOM_PINNED_EVENTS,
|
||||
StateEventType.ROOM_POWER_LEVELS,
|
||||
StateEventType.ROOM_SERVER_ACL,
|
||||
StateEventType.ROOM_TOMBSTONE,
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ dependencies {
|
|||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.deeplink)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.oidc.api)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.libraries.push.api)
|
||||
implementation(projects.libraries.pushproviders.api)
|
||||
|
|
@ -66,6 +67,7 @@ dependencies {
|
|||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.oidc.impl)
|
||||
testImplementation(projects.libraries.push.test)
|
||||
testImplementation(projects.libraries.pushproviders.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.RootPresenter
|
||||
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.signedout.api.SignedOutEntryPoint
|
||||
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.toRoomIdOrAlias
|
||||
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 kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
|
|
|||
|
|
@ -17,12 +17,12 @@
|
|||
package io.element.android.appnav.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.DeeplinkParser
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
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 javax.inject.Inject
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,6 @@ import android.content.Intent
|
|||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
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.DeeplinkData
|
||||
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_THREAD_ID
|
||||
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 org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ allprojects {
|
|||
config.from(files("$rootDir/tools/detekt/detekt.yml"))
|
||||
}
|
||||
dependencies {
|
||||
detektPlugins("io.nlopez.compose.rules:detekt:0.4.5")
|
||||
detektPlugins("io.nlopez.compose.rules:detekt:0.4.10")
|
||||
}
|
||||
|
||||
// KtLint
|
||||
|
|
@ -129,11 +129,11 @@ dependencyAnalysis {
|
|||
// To run a sonar analysis:
|
||||
// Run './gradlew sonar -Dsonar.login=<SONAR_LOGIN>'
|
||||
// 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 {
|
||||
properties {
|
||||
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.projectVersion", "1.0") // TODO project(":app").android.defaultConfig.versionName)
|
||||
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.scm", "https://github.com/element-hq/element-x-android/")
|
||||
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")
|
||||
|
||||
// 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"?>
|
||||
<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_read_terms">"Możesz przeczytać wszystkie nasze warunki %1$s."</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">"Przeczytaj nasze warunki użytkowania %1$s."</string>
|
||||
<string name="screen_analytics_settings_read_terms_content_link">"tutaj"</string>
|
||||
<string name="screen_analytics_settings_share_data">"Udostępniaj dane analityczne"</string>
|
||||
</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"?>
|
||||
<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_help_us_improve">"Udostępniaj anonimowe dane dotyczące 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_help_us_improve">"Udostępniaj anonimowe dane użytkowania, aby pomóc nam identyfikować problemy."</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_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>
|
||||
</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_read_terms">"您可以阅读我们的所有条款 %1$s。"</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_title">"帮助改进 %1$s"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@
|
|||
|
||||
package io.element.android.features.call.impl.pip
|
||||
|
||||
import io.element.android.features.call.impl.utils.PipController
|
||||
|
||||
sealed interface PictureInPictureEvents {
|
||||
data class SetPipController(val pipController: PipController) : PictureInPictureEvents
|
||||
data object EnterPictureInPicture : PictureInPictureEvents
|
||||
data class OnPictureInPictureModeChanged(val isInPip: Boolean) : PictureInPictureEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,17 +16,17 @@
|
|||
|
||||
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.getValue
|
||||
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.core.log.logger.LoggerTag
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.lang.ref.WeakReference
|
||||
import javax.inject.Inject
|
||||
|
||||
private val loggerTag = LoggerTag("PiP")
|
||||
|
|
@ -35,71 +35,69 @@ class PictureInPicturePresenter @Inject constructor(
|
|||
pipSupportProvider: PipSupportProvider,
|
||||
) : Presenter<PictureInPictureState> {
|
||||
private val isPipSupported = pipSupportProvider.isPipSupported()
|
||||
private var isInPictureInPicture = mutableStateOf(false)
|
||||
private var hostActivity: WeakReference<Activity>? = null
|
||||
private var pipView: PipView? = null
|
||||
|
||||
@Composable
|
||||
override fun present(): PictureInPictureState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var isInPictureInPicture by remember { mutableStateOf(false) }
|
||||
var pipController by remember { mutableStateOf<PipController?>(null) }
|
||||
|
||||
fun handleEvent(event: PictureInPictureEvents) {
|
||||
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(
|
||||
supportPip = isPipSupported,
|
||||
isInPictureInPicture = isInPictureInPicture.value,
|
||||
isInPictureInPicture = isInPictureInPicture,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
fun onCreate(activity: Activity) {
|
||||
fun setPipView(pipView: PipView?) {
|
||||
if (isPipSupported) {
|
||||
Timber.tag(loggerTag.value).d("onCreate: Setting PiP params")
|
||||
hostActivity = WeakReference(activity)
|
||||
hostActivity?.get()?.setPictureInPictureParams(getPictureInPictureParams())
|
||||
Timber.tag(loggerTag.value).d("Setting PiP params")
|
||||
this.pipView = pipView
|
||||
pipView?.setPipParams()
|
||||
} 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) {
|
||||
Timber.tag(loggerTag.value).d("Switch to PiP mode")
|
||||
hostActivity?.get()?.enterPictureInPictureMode(getPictureInPictureParams())
|
||||
?.also { Timber.tag(loggerTag.value).d("Switch to PiP mode result: $it") }
|
||||
if (pipController == null) {
|
||||
Timber.tag(loggerTag.value).w("webPipApi is not available")
|
||||
}
|
||||
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.di.AppScope
|
||||
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
|
||||
|
||||
interface PipSupportProvider {
|
||||
|
|
@ -37,15 +34,10 @@ interface PipSupportProvider {
|
|||
@ContributesBinding(AppScope::class)
|
||||
class DefaultPipSupportProvider @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
) : PipSupportProvider {
|
||||
override fun isPipSupported(): Boolean {
|
||||
val isSupportedByTheOs = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
||||
context.packageManager?.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).orFalse()
|
||||
return if (isSupportedByTheOs) {
|
||||
runBlocking { featureFlagService.isFeatureEnabled(FeatureFlags.PictureInPicture) }
|
||||
} else {
|
||||
false
|
||||
}
|
||||
return isSupportedByTheOs
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.lockscreen.impl.unlock.signout
|
||||
package io.element.android.features.call.impl.pip
|
||||
|
||||
interface SignOut {
|
||||
suspend operator fun invoke(): String?
|
||||
interface PipView {
|
||||
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.PictureInPictureStateProvider
|
||||
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.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
|
|
@ -95,9 +96,9 @@ internal fun CallScreenView(
|
|||
}
|
||||
CallWebView(
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.fillMaxSize(),
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.fillMaxSize(),
|
||||
url = state.urlState,
|
||||
userAgent = state.userAgent,
|
||||
onPermissionsRequest = { request ->
|
||||
|
|
@ -108,6 +109,8 @@ internal fun CallScreenView(
|
|||
onWebViewCreate = { webView ->
|
||||
val interceptor = WebViewWidgetMessageInterceptor(webView)
|
||||
state.eventSink(CallScreenEvents.SetupMessageChannels(interceptor))
|
||||
val pipController = WebViewPipController(webView)
|
||||
pipState.eventSink(PictureInPictureEvents.SetPipController(pipController))
|
||||
}
|
||||
)
|
||||
when (state.urlState) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package io.element.android.features.call.impl.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.app.PictureInPictureParams
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.media.AudioAttributes
|
||||
|
|
@ -24,19 +25,30 @@ import android.media.AudioFocusRequest
|
|||
import android.media.AudioManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Rational
|
||||
import android.view.WindowManager
|
||||
import android.webkit.PermissionRequest
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
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.rememberUpdatedState
|
||||
import androidx.core.app.PictureInPictureModeChangedInfo
|
||||
import androidx.core.content.IntentCompat
|
||||
import androidx.core.util.Consumer
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import io.element.android.features.call.api.CallType
|
||||
import io.element.android.features.call.impl.DefaultElementCallEntryPoint
|
||||
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.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.utils.CallIntentDataParser
|
||||
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 javax.inject.Inject
|
||||
|
||||
class ElementCallActivity : AppCompatActivity(), CallScreenNavigator {
|
||||
class ElementCallActivity :
|
||||
AppCompatActivity(),
|
||||
CallScreenNavigator,
|
||||
PipView {
|
||||
@Inject lateinit var callIntentDataParser: CallIntentDataParser
|
||||
@Inject lateinit var presenterFactory: CallScreenPresenter.Factory
|
||||
@Inject lateinit var appPreferencesStore: AppPreferencesStore
|
||||
|
|
@ -86,13 +101,14 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator {
|
|||
updateUiMode(resources.configuration)
|
||||
}
|
||||
|
||||
pictureInPicturePresenter.onCreate(this)
|
||||
pictureInPicturePresenter.setPipView(this)
|
||||
|
||||
audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
|
||||
requestAudioFocus()
|
||||
|
||||
setContent {
|
||||
val pipState = pictureInPicturePresenter.present()
|
||||
ListenToAndroidEvents(pipState)
|
||||
ElementThemeApp(appPreferencesStore) {
|
||||
val state = presenter.present()
|
||||
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) {
|
||||
super.onConfigurationChanged(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) {
|
||||
super.onNewIntent(intent)
|
||||
setCallType(intent)
|
||||
|
|
@ -140,16 +173,11 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onUserLeaveHint() {
|
||||
super.onUserLeaveHint()
|
||||
pictureInPicturePresenter.onUserLeaveHint()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
releaseAudioFocus()
|
||||
CallForegroundService.stop(this)
|
||||
pictureInPicturePresenter.onDestroy()
|
||||
pictureInPicturePresenter.setPipView(null)
|
||||
}
|
||||
|
||||
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> {
|
||||
|
|
|
|||
|
|
@ -14,15 +14,10 @@
|
|||
* 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
|
||||
import io.element.android.tests.testutils.simulateLongTask
|
||||
|
||||
class FakeSignOut(
|
||||
var lambda: () -> String? = { null }
|
||||
) : SignOut {
|
||||
override suspend fun invoke(): String? = simulateLongTask {
|
||||
lambda()
|
||||
}
|
||||
interface PipController {
|
||||
suspend fun canEnterPip(): Boolean
|
||||
fun enterPip()
|
||||
fun exitPip()
|
||||
}
|
||||
|
|
@ -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_message_android">"Stuknij, aby wrócić do rozmowy"</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>
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@
|
|||
<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_title_android">"☎️ Samtal pågår"</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Inkommande Element Call"</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="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_message_android">"点按即可返回通话"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ 通话中"</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Element 来电"</string>
|
||||
</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
|
||||
|
||||
import android.os.Build.VERSION_CODES
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
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 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 {
|
||||
@Test
|
||||
@Config(sdk = [VERSION_CODES.O, VERSION_CODES.S])
|
||||
fun `when pip is not supported, the state value supportPip is false`() = runTest {
|
||||
val presenter = createPictureInPicturePresenter(supportPip = false)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
|
|
@ -41,68 +34,119 @@ class PictureInPicturePresenterTest {
|
|||
val initialState = awaitItem()
|
||||
assertThat(initialState.supportPip).isFalse()
|
||||
}
|
||||
presenter.onDestroy()
|
||||
presenter.setPipView(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = [VERSION_CODES.O, VERSION_CODES.S])
|
||||
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) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.supportPip).isTrue()
|
||||
}
|
||||
presenter.onDestroy()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = [VERSION_CODES.S])
|
||||
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) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.isInPictureInPicture).isFalse()
|
||||
initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture)
|
||||
presenter.onPictureInPictureModeChanged(true)
|
||||
enterPipModeResult.assertions().isCalledOnce()
|
||||
initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(true))
|
||||
val pipState = awaitItem()
|
||||
assertThat(pipState.isInPictureInPicture).isTrue()
|
||||
// User stops pip
|
||||
presenter.onPictureInPictureModeChanged(false)
|
||||
initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(false))
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.isInPictureInPicture).isFalse()
|
||||
}
|
||||
presenter.onDestroy()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(sdk = [VERSION_CODES.S])
|
||||
fun `when onUserLeaveHint is called, the state value isInPictureInPicture becomes true`() = runTest {
|
||||
val presenter = createPictureInPicturePresenter(supportPip = true)
|
||||
fun `with webPipApi, when entering pip is supported, but web deny it, the call is finished`() = runTest {
|
||||
val handUpResult = lambdaRecorder<Unit> { }
|
||||
val presenter = createPictureInPicturePresenter(
|
||||
supportPip = true,
|
||||
pipView = FakePipView(
|
||||
setPipParamsResult = { },
|
||||
handUpResult = handUpResult
|
||||
),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.isInPictureInPicture).isFalse()
|
||||
presenter.onUserLeaveHint()
|
||||
presenter.onPictureInPictureModeChanged(true)
|
||||
initialState.eventSink(PictureInPictureEvents.SetPipController(FakePipController(canEnterPipResult = { false })))
|
||||
initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture)
|
||||
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()
|
||||
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(
|
||||
supportPip: Boolean = true,
|
||||
pipView: PipView? = FakePipView()
|
||||
): PictureInPicturePresenter {
|
||||
val activity = Robolectric.buildActivity(ElementCallActivity::class.java)
|
||||
return PictureInPicturePresenter(
|
||||
pipSupportProvider = FakePipSupportProvider(supportPip),
|
||||
).apply {
|
||||
onCreate(activity.get())
|
||||
setPipView(pipView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class CallScreenViewTest {
|
|||
@Test
|
||||
fun `clicking on back when pip is not supported hangs up`() {
|
||||
val eventsRecorder = EventsRecorder<CallScreenEvents>()
|
||||
val pipEventsRecorder = EventsRecorder<PictureInPictureEvents>(expectEvents = false)
|
||||
val pipEventsRecorder = EventsRecorder<PictureInPictureEvents>()
|
||||
rule.setCallScreenView(
|
||||
aCallScreenState(
|
||||
eventSink = eventsRecorder
|
||||
|
|
@ -51,6 +51,8 @@ class CallScreenViewTest {
|
|||
eventsRecorder.assertSize(2)
|
||||
eventsRecorder.assertTrue(0) { it is CallScreenEvents.SetupMessageChannels }
|
||||
eventsRecorder.assertTrue(1) { it == CallScreenEvents.Hangup }
|
||||
pipEventsRecorder.assertSize(1)
|
||||
pipEventsRecorder.assertTrue(0) { it is PictureInPictureEvents.SetPipController }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -69,7 +71,9 @@ class CallScreenViewTest {
|
|||
rule.pressBack()
|
||||
eventsRecorder.assertSize(1)
|
||||
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.FakeMatrixClientProvider
|
||||
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.network.useragent.UserAgentProvider
|
||||
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.tests.testutils.WarmUpRule
|
||||
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.value
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
|
|
@ -86,8 +89,9 @@ class CallScreenPresenterTest {
|
|||
@Test
|
||||
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 syncService = FakeSyncService(MutableStateFlow(SyncState.Running))
|
||||
val fakeRoom = FakeMatrixRoom(sendCallNotificationIfNeededResult = sendCallNotificationIfNeededLambda)
|
||||
val client = FakeMatrixClient().apply {
|
||||
val client = FakeMatrixClient(syncService = syncService).apply {
|
||||
givenGetRoomResult(A_ROOM_ID, fakeRoom)
|
||||
}
|
||||
val widgetDriver = FakeMatrixWidgetDriver()
|
||||
|
|
@ -216,7 +220,12 @@ class CallScreenPresenterTest {
|
|||
fun `present - automatically starts the Matrix client sync when on RoomCall`() = runTest {
|
||||
val navigator = FakeCallScreenNavigator()
|
||||
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(
|
||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
||||
widgetDriver = widgetDriver,
|
||||
|
|
@ -230,7 +239,7 @@ class CallScreenPresenterTest {
|
|||
}.test {
|
||||
consumeItemsUntilTimeout()
|
||||
|
||||
assertThat(matrixClient.syncService().syncState.value).isEqualTo(SyncState.Running)
|
||||
assert(startSyncLambda).isCalledOnce()
|
||||
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
|
|
@ -240,7 +249,12 @@ class CallScreenPresenterTest {
|
|||
fun `present - automatically stops the Matrix client sync on dispose`() = runTest {
|
||||
val navigator = FakeCallScreenNavigator()
|
||||
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(
|
||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
||||
widgetDriver = widgetDriver,
|
||||
|
|
@ -262,7 +276,7 @@ class CallScreenPresenterTest {
|
|||
|
||||
job.cancelAndJoin()
|
||||
|
||||
assertThat(matrixClient.syncService().syncState.value).isEqualTo(SyncState.Terminated)
|
||||
assert(stopSyncLambda).isCalledOnce()
|
||||
}
|
||||
|
||||
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">
|
||||
<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_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_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_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_title">"Utwórz pokój"</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_private_option_description">"此聊天室中的消息已加密。加密无法禁用。"</string>
|
||||
<string name="screen_create_room_private_option_title">"私人房间(仅限受邀者)"</string>
|
||||
<string name="screen_create_room_public_option_description">"消息未加密,任何人都可以查看。你可以稍后启用加密。"</string>
|
||||
<string name="screen_create_room_public_option_description">"消息未加密,任何人都可以查看。可以稍后启用加密。"</string>
|
||||
<string name="screen_create_room_public_option_title">"公共房间(任何人)"</string>
|
||||
<string name="screen_create_room_room_name_label">"房间名称"</string>
|
||||
<string name="screen_create_room_title">"创建房间"</string>
|
||||
|
|
|
|||
|
|
@ -58,6 +58,9 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor(
|
|||
|
||||
@Parcelize
|
||||
data object EnterRecoveryKey : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object ResetIdentity : NavTarget
|
||||
}
|
||||
|
||||
interface Callback : Plugin {
|
||||
|
|
@ -85,6 +88,10 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor(
|
|||
override fun onDone() {
|
||||
plugins<Callback>().forEach { it.onDone() }
|
||||
}
|
||||
|
||||
override fun onResetKey() {
|
||||
backstack.push(NavTarget.ResetIdentity)
|
||||
}
|
||||
})
|
||||
.build()
|
||||
}
|
||||
|
|
@ -94,6 +101,16 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor(
|
|||
.callback(secureBackupEntryPointCallback)
|
||||
.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 {
|
||||
getNextStep(FtueStep.AnalyticsOptIn)
|
||||
}
|
||||
FtueStep.AnalyticsOptIn -> null
|
||||
FtueStep.AnalyticsOptIn -> {
|
||||
updateState()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
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.matrix.api.MatrixClient
|
||||
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.push.api.notifications.NotificationCleaner
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -107,7 +108,7 @@ class AcceptDeclineInvitePresenter @Inject constructor(
|
|||
) = launch {
|
||||
acceptedAction.runUpdatingState {
|
||||
joinRoom(
|
||||
roomId = roomId,
|
||||
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
||||
serverNames = emptyList(),
|
||||
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"?>
|
||||
<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_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>
|
||||
|
|
|
|||
|
|
@ -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.matrix.api.MatrixClient
|
||||
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.toRoomIdOrAlias
|
||||
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_SESSION_ID
|
||||
|
|
@ -178,8 +180,8 @@ class AcceptDeclineInvitePresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - accepting invite error flow`() = runTest {
|
||||
val joinRoomFailure = lambdaRecorder { roomId: RoomId, _: List<String>, _: JoinedRoom.Trigger ->
|
||||
Result.failure<Unit>(RuntimeException("Failed to join room $roomId"))
|
||||
val joinRoomFailure = lambdaRecorder { roomIdOrAlias: RoomIdOrAlias, _: List<String>, _: JoinedRoom.Trigger ->
|
||||
Result.failure<Unit>(RuntimeException("Failed to join room $roomIdOrAlias"))
|
||||
}
|
||||
val presenter = createAcceptDeclineInvitePresenter(joinRoomLambda = joinRoomFailure)
|
||||
presenter.test {
|
||||
|
|
@ -208,7 +210,7 @@ class AcceptDeclineInvitePresenterTest {
|
|||
assert(joinRoomFailure)
|
||||
.isCalledOnce()
|
||||
.with(
|
||||
value(A_ROOM_ID),
|
||||
value(A_ROOM_ID.toRoomIdOrAlias()),
|
||||
value(emptyList<String>()),
|
||||
value(JoinedRoom.Trigger.Invite)
|
||||
)
|
||||
|
|
@ -222,7 +224,7 @@ class AcceptDeclineInvitePresenterTest {
|
|||
val fakeNotificationCleaner = FakeNotificationCleaner(
|
||||
clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda
|
||||
)
|
||||
val joinRoomSuccess = lambdaRecorder { _: RoomId, _: List<String>, _: JoinedRoom.Trigger ->
|
||||
val joinRoomSuccess = lambdaRecorder { _: RoomIdOrAlias, _: List<String>, _: JoinedRoom.Trigger ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val presenter = createAcceptDeclineInvitePresenter(
|
||||
|
|
@ -248,7 +250,7 @@ class AcceptDeclineInvitePresenterTest {
|
|||
assert(joinRoomSuccess)
|
||||
.isCalledOnce()
|
||||
.with(
|
||||
value(A_ROOM_ID),
|
||||
value(A_ROOM_ID.toRoomIdOrAlias()),
|
||||
value(emptyList<String>()),
|
||||
value(JoinedRoom.Trigger.Invite)
|
||||
)
|
||||
|
|
@ -271,7 +273,7 @@ class AcceptDeclineInvitePresenterTest {
|
|||
|
||||
private fun createAcceptDeclineInvitePresenter(
|
||||
client: MatrixClient = FakeMatrixClient(),
|
||||
joinRoomLambda: (RoomId, List<String>, JoinedRoom.Trigger) -> Result<Unit> = { _, _, _ ->
|
||||
joinRoomLambda: (RoomIdOrAlias, List<String>, JoinedRoom.Trigger) -> Result<Unit> = { _, _, _ ->
|
||||
Result.success(Unit)
|
||||
},
|
||||
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
}
|
||||
else -> {
|
||||
value = ContentState.Loading(roomIdOrAlias)
|
||||
val result = matrixClient.getRoomPreviewFromRoomId(roomId, serverNames)
|
||||
val result = matrixClient.getRoomPreview(roomIdOrAlias, serverNames)
|
||||
value = result.fold(
|
||||
onSuccess = { roomPreview ->
|
||||
roomPreview.toContentState()
|
||||
|
|
@ -153,7 +153,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
private fun CoroutineScope.joinRoom(joinAction: MutableState<AsyncAction<Unit>>) = launch {
|
||||
joinAction.runUpdatingState {
|
||||
joinRoom.invoke(
|
||||
roomId = roomId,
|
||||
roomIdOrAlias = roomIdOrAlias,
|
||||
serverNames = serverNames,
|
||||
trigger = trigger
|
||||
)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,11 @@ data class JoinRoomState(
|
|||
val eventSink: (JoinRoomEvents) -> Unit
|
||||
) {
|
||||
val joinAuthorisationStatus = when (contentState) {
|
||||
// Use the join authorisation status from the loaded content state
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.core.RoomAlias
|
||||
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.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
|
|
@ -180,7 +181,7 @@ class JoinRoomPresenterTest {
|
|||
@Test
|
||||
fun `present - when room is joined with success, all the parameters are provided`() = runTest {
|
||||
val aTrigger = JoinedRoom.Trigger.MobilePermalink
|
||||
val joinRoomLambda = lambdaRecorder { _: RoomId, _: List<String>, _: JoinedRoom.Trigger ->
|
||||
val joinRoomLambda = lambdaRecorder { _: RoomIdOrAlias, _: List<String>, _: JoinedRoom.Trigger ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val presenter = createJoinRoomPresenter(
|
||||
|
|
@ -201,7 +202,7 @@ class JoinRoomPresenterTest {
|
|||
}
|
||||
joinRoomLambda.assertions()
|
||||
.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
|
||||
fun `present - when room is not known RoomPreview is loaded`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewFromRoomIdResult = { _, _ ->
|
||||
getRoomPreviewResult = { _, _ ->
|
||||
Result.success(
|
||||
RoomPreview(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -411,7 +412,7 @@ class JoinRoomPresenterTest {
|
|||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded with error`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewFromRoomIdResult = { _, _ ->
|
||||
getRoomPreviewResult = { _, _ ->
|
||||
Result.failure(AN_EXCEPTION)
|
||||
}
|
||||
)
|
||||
|
|
@ -449,7 +450,7 @@ class JoinRoomPresenterTest {
|
|||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded with error 403`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewFromRoomIdResult = { _, _ ->
|
||||
getRoomPreviewResult = { _, _ ->
|
||||
Result.failure(Exception("403"))
|
||||
}
|
||||
)
|
||||
|
|
@ -474,7 +475,7 @@ class JoinRoomPresenterTest {
|
|||
serverNames: List<String> = emptyList(),
|
||||
trigger: JoinedRoom.Trigger = JoinedRoom.Trigger.Invite,
|
||||
matrixClient: MatrixClient = FakeMatrixClient(),
|
||||
joinRoomLambda: (RoomId, List<String>, JoinedRoom.Trigger) -> Result<Unit> = { _, _, _ ->
|
||||
joinRoomLambda: (RoomIdOrAlias, List<String>, JoinedRoom.Trigger) -> Result<Unit> = { _, _, _ ->
|
||||
Result.success(Unit)
|
||||
},
|
||||
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"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="leave_conversation_alert_subtitle">"您确定要离开此对话吗?此对话不公开,未经邀请您将无法重新加入。"</string>
|
||||
<string name="leave_room_alert_empty_subtitle">"你确定要离开这个房间吗?这里只有你一个人,如果你走了,包括你在内的所有人都无法进入此房间。"</string>
|
||||
<string name="leave_room_alert_private_subtitle">"你确定要离开这个房间吗?这个房间不是公开的,如果没有邀请,你将无法重新加入。"</string>
|
||||
<string name="leave_room_alert_subtitle">"你确定要离开房间吗?"</string>
|
||||
<string name="leave_room_alert_empty_subtitle">"确定要离开这个房间吗?这里只有你一个人。如果你离开此房间,包括你在内的所有人都将无法进入。"</string>
|
||||
<string name="leave_room_alert_private_subtitle">"确定要离开这个房间吗?此房间不公开,没有邀请你将无法重新加入。"</string>
|
||||
<string name="leave_room_alert_subtitle">"确定要离开房间吗?"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ dependencies {
|
|||
implementation(projects.libraries.featureflag.api)
|
||||
implementation(projects.libraries.cryptography.api)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.features.logout.api)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.libraries.sessionStorage.api)
|
||||
implementation(projects.services.appnavstate.api)
|
||||
|
|
@ -59,4 +60,5 @@ dependencies {
|
|||
testImplementation(projects.libraries.featureflag.test)
|
||||
testImplementation(projects.libraries.sessionStorage.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.model.PinEntry
|
||||
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.Presenter
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
|
|
@ -41,7 +41,7 @@ import javax.inject.Inject
|
|||
class PinUnlockPresenter @Inject constructor(
|
||||
private val pinCodeManager: PinCodeManager,
|
||||
private val biometricUnlockManager: BiometricUnlockManager,
|
||||
private val signOut: SignOut,
|
||||
private val logoutUseCase: LogoutUseCase,
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val pinUnlockHelper: PinUnlockHelper,
|
||||
) : Presenter<PinUnlockState> {
|
||||
|
|
@ -179,7 +179,7 @@ class PinUnlockPresenter @Inject constructor(
|
|||
|
||||
private fun CoroutineScope.signOut(signOutAction: MutableState<AsyncData<String?>>) = launch {
|
||||
suspend {
|
||||
signOut()
|
||||
logoutUseCase.logout(ignoreSdkError = true)
|
||||
}.runCatchingUpdatingState(signOutAction)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ package io.element.android.features.lockscreen.impl.unlock.di
|
|||
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
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 {
|
||||
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_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>
|
||||
<string name="screen_app_lock_setup_pin_forbidden_dialog_content">"Nie możesz wybrać tego PINU ze względów bezpieczeństwa"</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 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_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_title">"Trwa wylogowywanie"</string>
|
||||
<plurals name="screen_app_lock_subtitle">
|
||||
|
|
|
|||
|
|
@ -1,14 +1,27 @@
|
|||
<?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">"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_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_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_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_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_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>
|
||||
<plurals name="screen_app_lock_subtitle">
|
||||
<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="other">"PIN incorreto. Você tem mais %1$d chances"</item>
|
||||
</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>
|
||||
</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.assertText
|
||||
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.tests.testutils.lambda.assert
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
|
|
@ -106,9 +106,9 @@ class PinUnlockPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - forgot pin flow`() = runTest {
|
||||
val signOutLambda = lambdaRecorder<String?> { null }
|
||||
val signOut = FakeSignOut(signOutLambda)
|
||||
val presenter = createPinUnlockPresenter(this, signOut = signOut)
|
||||
val signOutLambda = lambdaRecorder<Boolean, String> { "" }
|
||||
val signOut = FakeLogoutUseCase(signOutLambda)
|
||||
val presenter = createPinUnlockPresenter(this, logoutUseCase = signOut)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -135,7 +135,7 @@ class PinUnlockPresenterTest {
|
|||
awaitItem().also { state ->
|
||||
assertThat(state.signOutAction).isInstanceOf(AsyncData.Success::class.java)
|
||||
}
|
||||
assert(signOutLambda).isCalledOnce().withNoParameter()
|
||||
assert(signOutLambda).isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,7 +147,7 @@ class PinUnlockPresenterTest {
|
|||
scope: CoroutineScope,
|
||||
biometricUnlockManager: BiometricUnlockManager = FakeBiometricUnlockManager(),
|
||||
callback: PinCodeManager.Callback = DefaultPinCodeManagerCallback(),
|
||||
signOut: SignOut = FakeSignOut(),
|
||||
logoutUseCase: FakeLogoutUseCase = FakeLogoutUseCase(logoutLambda = { "" }),
|
||||
): PinUnlockPresenter {
|
||||
val pinCodeManager = aPinCodeManager().apply {
|
||||
addCallback(callback)
|
||||
|
|
@ -156,7 +156,7 @@ class PinUnlockPresenterTest {
|
|||
return PinUnlockPresenter(
|
||||
pinCodeManager = pinCodeManager,
|
||||
biometricUnlockManager = biometricUnlockManager,
|
||||
signOut = signOut,
|
||||
logoutUseCase = logoutUseCase,
|
||||
coroutineScope = scope,
|
||||
pinUnlockHelper = PinUnlockHelper(biometricUnlockManager, pinCodeManager),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ dependencies {
|
|||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.libraries.permissions.api)
|
||||
implementation(projects.libraries.qrcode)
|
||||
implementation(projects.libraries.oidc.api)
|
||||
implementation(libs.androidx.browser)
|
||||
implementation(platform(libs.network.retrofit.bom))
|
||||
implementation(libs.network.retrofit)
|
||||
|
|
@ -65,6 +66,7 @@ dependencies {
|
|||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.oidc.impl)
|
||||
testImplementation(projects.libraries.permissions.test)
|
||||
testImplementation(projects.tests.testutils)
|
||||
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.compound.theme.ElementTheme
|
||||
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.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.screens.changeaccountprovider.ChangeAccountProviderNode
|
||||
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.di.AppScope
|
||||
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.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
|
@ -64,11 +62,10 @@ import kotlinx.parcelize.Parcelize
|
|||
class LoginFlowNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val customTabAvailabilityChecker: CustomTabAvailabilityChecker,
|
||||
private val customTabHandler: CustomTabHandler,
|
||||
private val accountProviderDataSource: AccountProviderDataSource,
|
||||
private val defaultLoginUserStory: DefaultLoginUserStory,
|
||||
private val oidcActionFlow: OidcActionFlow,
|
||||
private val oidcEntryPoint: OidcEntryPoint,
|
||||
) : BaseFlowNode<LoginFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.Root,
|
||||
|
|
@ -146,11 +143,11 @@ class LoginFlowNode @AssistedInject constructor(
|
|||
)
|
||||
val callback = object : ConfirmAccountProviderNode.Callback {
|
||||
override fun onOidcDetails(oidcDetails: OidcDetails) {
|
||||
if (customTabAvailabilityChecker.supportCustomTab()) {
|
||||
if (oidcEntryPoint.canUseCustomTab()) {
|
||||
// In this case open a Chrome Custom tab
|
||||
activity?.let {
|
||||
customChromeTabStarted = true
|
||||
customTabHandler.open(it, darkTheme, oidcDetails.url)
|
||||
oidcEntryPoint.openUrlInCustomTab(it, darkTheme, oidcDetails.url)
|
||||
}
|
||||
} else {
|
||||
// Fallback to WebView mode
|
||||
|
|
@ -201,8 +198,7 @@ class LoginFlowNode @AssistedInject constructor(
|
|||
createNode<LoginPasswordNode>(buildContext, plugins = listOf(callback))
|
||||
}
|
||||
is NavTarget.OidcView -> {
|
||||
val input = OidcNode.Inputs(navTarget.oidcDetails)
|
||||
createNode<OidcNode>(buildContext, plugins = listOf(input))
|
||||
oidcEntryPoint.createFallbackWebViewNode(this, buildContext, navTarget.oidcDetails.url)
|
||||
}
|
||||
is NavTarget.WaitList -> {
|
||||
val inputs = WaitListNode.Inputs(
|
||||
|
|
|
|||
|
|
@ -27,15 +27,15 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
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.accountprovider.AccountProviderDataSource
|
||||
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.Presenter
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
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.launch
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor(
|
|||
@Assisted private val params: Params,
|
||||
private val accountProviderDataSource: AccountProviderDataSource,
|
||||
private val authenticationService: MatrixAuthenticationService,
|
||||
private val defaultOidcActionFlow: DefaultOidcActionFlow,
|
||||
private val oidcActionFlow: OidcActionFlow,
|
||||
private val defaultLoginUserStory: DefaultLoginUserStory,
|
||||
) : Presenter<ConfirmAccountProviderState> {
|
||||
data class Params(
|
||||
|
|
@ -65,7 +65,7 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor(
|
|||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
defaultOidcActionFlow.collect { oidcAction ->
|
||||
oidcActionFlow.collect { oidcAction ->
|
||||
if (oidcAction != null) {
|
||||
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_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_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_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>
|
||||
|
|
|
|||
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_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_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_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>
|
||||
|
|
@ -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_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_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_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_subtitle">"Jaki jest adres Twojego serwera?"</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_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_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_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_title">"Witaj ponownie!"</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_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_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>
|
||||
|
|
|
|||
|
|
@ -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_title">"Välkommen tillbaka!"</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_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_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>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,48 @@
|
|||
<string name="screen_login_subtitle">"Matrix — це відкрита мережа для безпечної, децентралізованої комунікації."</string>
|
||||
<string name="screen_login_title">"З поверненням!"</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_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_message_login_element_dot_io">"Приватний сервер для співробітників Element."</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_subtitle">"使用其他帐户提供者,例如您自己的私人服务器或工作帐户。"</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 不可用:
|
||||
%1$s"</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_notice">"您只能连接到支持sliding sync的现有服务器。您的主服务器管理员需要对其进行配置。%1$s"</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_notice">"您只能连接到支持 Sliding Sync 的现有服务器。您的服务器管理员需要对其进行配置。%1$s"</string>
|
||||
<string name="screen_change_server_subtitle">"您的服务器地址是什么?"</string>
|
||||
<string name="screen_change_server_title">"选择服务器"</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_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_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_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>
|
||||
|
|
@ -74,8 +76,8 @@
|
|||
<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_register">"这是您的对话将进行的地方,就像您使用电子邮件提供商来保存电子邮件一样。"</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_login">"即将登录 %1$s"</string>
|
||||
<string name="screen_server_confirmation_title_register">"即将在 %1$s 上创建一个账户"</string>
|
||||
<string name="screen_waitlist_message">"目前 %1$s 上 %2$s 的负载很大。过几天再回来试试吧。
|
||||
|
||||
感谢您的耐心!"</string>
|
||||
|
|
|
|||
|
|
@ -20,10 +20,8 @@ import app.cash.molecule.RecompositionMode
|
|||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
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.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.libraries.architecture.AsyncData
|
||||
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_THROWABLE
|
||||
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.waitForPredicate
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -274,7 +274,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||
params = params,
|
||||
accountProviderDataSource = accountProviderDataSource,
|
||||
authenticationService = matrixAuthenticationService,
|
||||
defaultOidcActionFlow = defaultOidcActionFlow,
|
||||
oidcActionFlow = defaultOidcActionFlow,
|
||||
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"?>
|
||||
<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_submit">"Wyloguj się"</string>
|
||||
<string name="screen_signout_confirmation_dialog_title">"Wyloguj się"</string>
|
||||
<string name="screen_signout_confirmation_dialog_submit">"Wyloguj"</string>
|
||||
<string name="screen_signout_confirmation_dialog_title">"Wyloguj"</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_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_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_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_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>
|
||||
|
|
|
|||
|
|
@ -4,5 +4,9 @@
|
|||
<string name="screen_signout_confirmation_dialog_submit">"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_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_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>
|
||||
|
|
|
|||
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