Merge branch 'develop' into feature/fga/user_moderation_bottomsheet
This commit is contained in:
commit
03e1926e14
250 changed files with 3339 additions and 2742 deletions
10
.github/workflows/maestro-local.yml
vendored
10
.github/workflows/maestro-local.yml
vendored
|
|
@ -11,7 +11,7 @@ env:
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||||
ARCH: x86_64
|
ARCH: x86_64
|
||||||
DEVICE: pixel_7_pro
|
DEVICE: pixel_7_pro
|
||||||
API_LEVEL: 35
|
API_LEVEL: 33
|
||||||
TARGET: google_apis
|
TARGET: google_apis
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
@ -56,7 +56,7 @@ jobs:
|
||||||
maestro-cloud:
|
maestro-cloud:
|
||||||
name: Maestro test suite
|
name: Maestro test suite
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build-apk]
|
needs: [ build-apk ]
|
||||||
# Allow one per PR.
|
# Allow one per PR.
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ format('maestro-{0}', github.ref) }}
|
group: ${{ format('maestro-{0}', github.ref) }}
|
||||||
|
|
@ -80,6 +80,7 @@ jobs:
|
||||||
- name: Install maestro
|
- name: Install maestro
|
||||||
run: curl -fsSL "https://get.maestro.mobile.dev" | bash
|
run: curl -fsSL "https://get.maestro.mobile.dev" | bash
|
||||||
- name: Run Maestro tests in emulator
|
- name: Run Maestro tests in emulator
|
||||||
|
id: maestro_test
|
||||||
uses: reactivecircus/android-emulator-runner@v2
|
uses: reactivecircus/android-emulator-runner@v2
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
env:
|
env:
|
||||||
|
|
@ -109,3 +110,8 @@ jobs:
|
||||||
retention-days: 5
|
retention-days: 5
|
||||||
overwrite: true
|
overwrite: true
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
- name: Fail the workflow in case of error in test
|
||||||
|
if: steps.maestro_test.outcome != 'success'
|
||||||
|
run: |
|
||||||
|
echo "Maestro tests failed. Please check the logs."
|
||||||
|
exit 1
|
||||||
|
|
|
||||||
28
.github/workflows/post-release.yml
vendored
Normal file
28
.github/workflows/post-release.yml
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
name: Post-release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
post-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Skip in forks
|
||||||
|
if: github.repository == 'element-hq/element-x-android'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Trigger pipeline
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.ENTERPRISE_ACTIONS_TOKEN }}
|
||||||
|
script: |
|
||||||
|
const tag = context.ref.replace('refs/tags/', '');
|
||||||
|
const inputs = { git_tag: tag };
|
||||||
|
await github.rest.actions.createWorkflowDispatch({
|
||||||
|
owner: 'element-hq',
|
||||||
|
repo: 'element-enterprise',
|
||||||
|
workflow_id: 'pipeline-android.yml',
|
||||||
|
ref: 'main',
|
||||||
|
inputs: inputs
|
||||||
|
});
|
||||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
|
@ -82,7 +82,7 @@ jobs:
|
||||||
|
|
||||||
# https://github.com/codecov/codecov-action
|
# https://github.com/codecov/codecov-action
|
||||||
- name: ☂️ Upload coverage reports to codecov
|
- name: ☂️ Upload coverage reports to codecov
|
||||||
uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2
|
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||||
with:
|
with:
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
|
||||||
2
.idea/kotlinc.xml
generated
2
.idea/kotlinc.xml
generated
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="version" value="2.1.20" />
|
<option name="version" value="2.1.21" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
appId: ${MAESTRO_APP_ID}
|
appId: ${MAESTRO_APP_ID}
|
||||||
|
androidWebViewHierarchy: devtools
|
||||||
---
|
---
|
||||||
## Check that all env variables required in the whole test suite are declared (to fail faster)
|
## Check that all env variables required in the whole test suite are declared (to fail faster)
|
||||||
- runScript: ./scripts/checkEnv.js
|
- runScript: ./scripts/checkEnv.js
|
||||||
|
|
|
||||||
|
|
@ -7,22 +7,39 @@ appId: ${MAESTRO_APP_ID}
|
||||||
- runFlow: ../assertions/assertLoginDisplayed.yaml
|
- runFlow: ../assertions/assertLoginDisplayed.yaml
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "login-continue"
|
id: "login-continue"
|
||||||
|
## MAS page
|
||||||
|
## Conditional workflow to pass the Chrome first launch welcome page.
|
||||||
|
- runFlow:
|
||||||
|
when:
|
||||||
|
visible: 'Use without an account'
|
||||||
|
commands:
|
||||||
|
- tapOn: "Use without an account"
|
||||||
|
## For older chrome versions
|
||||||
|
- runFlow:
|
||||||
|
when:
|
||||||
|
visible: 'Accept & continue'
|
||||||
|
commands:
|
||||||
|
- tapOn: "Accept & continue"
|
||||||
|
- runFlow:
|
||||||
|
when:
|
||||||
|
visible: 'No thanks'
|
||||||
|
commands:
|
||||||
|
- tapOn: "No thanks"
|
||||||
|
## Working when running Maestro locally, but not on the CI yet.
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible:
|
||||||
|
id: "form-1"
|
||||||
|
timeout: 10000
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "login-email_username"
|
id: "form-1"
|
||||||
- inputText: ${MAESTRO_USERNAME}
|
- inputText: ${MAESTRO_USERNAME}
|
||||||
- pressKey: Enter
|
- pressKey: Enter
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "login-password"
|
id: "form-3"
|
||||||
- inputText: "wrong-password"
|
|
||||||
- pressKey: Enter
|
|
||||||
- tapOn: "Continue"
|
|
||||||
- tapOn: "OK"
|
|
||||||
- tapOn:
|
|
||||||
id: "login-password"
|
|
||||||
- eraseText: 20
|
|
||||||
- inputText: ${MAESTRO_PASSWORD}
|
- inputText: ${MAESTRO_PASSWORD}
|
||||||
- pressKey: Enter
|
- pressKey: Enter
|
||||||
- tapOn: "Continue"
|
- tapOn: "Continue"
|
||||||
|
## Back to native world
|
||||||
- runFlow: ../assertions/assertSessionVerificationDisplayed.yaml
|
- runFlow: ../assertions/assertSessionVerificationDisplayed.yaml
|
||||||
- runFlow: ./verifySession.yaml
|
- runFlow: ./verifySession.yaml
|
||||||
- runFlow: ../assertions/assertAnalyticsDisplayed.yaml
|
- runFlow: ../assertions/assertAnalyticsDisplayed.yaml
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,6 @@ appId: ${MAESTRO_APP_ID}
|
||||||
- hideKeyboard
|
- hideKeyboard
|
||||||
- tapOn: "Continue"
|
- tapOn: "Continue"
|
||||||
- extendedWaitUntil:
|
- extendedWaitUntil:
|
||||||
visible: "Device verified"
|
visible: "Verification complete"
|
||||||
timeout: 30000
|
timeout: 30000
|
||||||
- tapOn: "Continue"
|
- tapOn: "Continue"
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ appId: ${MAESTRO_APP_ID}
|
||||||
- tapOn:
|
- tapOn:
|
||||||
text: ${MAESTRO_INVITEE1_MXID}
|
text: ${MAESTRO_INVITEE1_MXID}
|
||||||
index: 1
|
index: 1
|
||||||
|
- tapOn: "Send invite"
|
||||||
- takeScreenshot: build/maestro/330-createAndDeleteDM
|
- takeScreenshot: build/maestro/330-createAndDeleteDM
|
||||||
- tapOn: "maestroelement2"
|
- tapOn: "maestroelement2"
|
||||||
- scroll
|
- scroll
|
||||||
- tapOn: "Leave conversation"
|
- tapOn: "Leave room"
|
||||||
- tapOn: "Leave"
|
- tapOn: "Leave"
|
||||||
|
|
|
||||||
13
.maestro/tests/roomList/timeline/call/call.yaml
Normal file
13
.maestro/tests/roomList/timeline/call/call.yaml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
appId: ${MAESTRO_APP_ID}
|
||||||
|
---
|
||||||
|
- tapOn: "Start a call"
|
||||||
|
- takeScreenshot: build/maestro/700-Call
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible: "maestroelement"
|
||||||
|
timeout: 10000
|
||||||
|
- takeScreenshot: build/maestro/710-Call
|
||||||
|
# Hangup
|
||||||
|
- tapOn: "End call"
|
||||||
|
- extendedWaitUntil:
|
||||||
|
visible: "MyRoom"
|
||||||
|
timeout: 10000
|
||||||
|
|
@ -6,5 +6,6 @@ appId: ${MAESTRO_APP_ID}
|
||||||
- runFlow: messages/text.yaml
|
- runFlow: messages/text.yaml
|
||||||
- runFlow: messages/location.yaml
|
- runFlow: messages/location.yaml
|
||||||
- runFlow: messages/poll.yaml
|
- runFlow: messages/poll.yaml
|
||||||
|
- runFlow: call/call.yaml
|
||||||
- back
|
- back
|
||||||
- runFlow: ../../assertions/assertHomeDisplayed.yaml
|
- runFlow: ../../assertions/assertHomeDisplayed.yaml
|
||||||
|
|
|
||||||
47
CHANGES.md
47
CHANGES.md
|
|
@ -1,3 +1,50 @@
|
||||||
|
<!-- Release notes generated using configuration in .github/release.yml at v25.05.4 -->
|
||||||
|
|
||||||
|
Changes in Element X v25.05.4
|
||||||
|
=============================
|
||||||
|
|
||||||
|
Rust SDK: https://github.com/matrix-org/matrix-rust-sdk/releases/tag/matrix-sdk-ffi%2F20250521
|
||||||
|
|
||||||
|
## What's Changed
|
||||||
|
### 🙌 Improvements
|
||||||
|
* Change (report room) : check if server supports the report room api by @ganfra in https://github.com/element-hq/element-x-android/pull/4718
|
||||||
|
### 🐛 Bugfixes
|
||||||
|
* Improve audio focus management by @bmarty in https://github.com/element-hq/element-x-android/pull/4707
|
||||||
|
* When transcoding a video fails, send it as a file by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4257
|
||||||
|
* Disable mutliple click (parallel or serial) on a room by @bmarty in https://github.com/element-hq/element-x-android/pull/4683
|
||||||
|
* Fix generic mime type used when externally sharing several files by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4715
|
||||||
|
* Fix issues on JoinedRoom / BaseRoom by @bmarty in https://github.com/element-hq/element-x-android/pull/4724
|
||||||
|
* Use the right live timeline instance in `RustRoomFactory` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4745
|
||||||
|
### 🗣 Translations
|
||||||
|
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/4739
|
||||||
|
### 🧱 Build
|
||||||
|
* Ensure the CI is marked as failed when Maestro test is failing by @bmarty in https://github.com/element-hq/element-x-android/pull/4700
|
||||||
|
* Trigger pipeline build when a release tag is pushed by @bmarty in https://github.com/element-hq/element-x-android/pull/4741
|
||||||
|
* Fix compilation issues. by @bmarty in https://github.com/element-hq/element-x-android/pull/4750
|
||||||
|
### 📄 Documentation
|
||||||
|
* README.md: fix broken link by @richvdh in https://github.com/element-hq/element-x-android/pull/4728
|
||||||
|
### Dependency upgrades
|
||||||
|
* chore(config): migrate renovate config by @renovate in https://github.com/element-hq/element-x-android/pull/4688
|
||||||
|
* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.5.13 by @renovate in https://github.com/element-hq/element-x-android/pull/4716
|
||||||
|
* fix(deps): update dependency io.sentry:sentry-android to v8.12.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4717
|
||||||
|
* chore(deps): update plugin sonarqube to v6.2.0.5505 by @renovate in https://github.com/element-hq/element-x-android/pull/4725
|
||||||
|
* fix(deps): update dependency com.posthog:posthog-android to v3.15.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4723
|
||||||
|
* fix(deps): update dependency com.squareup.retrofit2:retrofit-bom to v2.12.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4727
|
||||||
|
* chore(deps): update codecov/codecov-action action to v5.4.3 by @renovate in https://github.com/element-hq/element-x-android/pull/4730
|
||||||
|
* fix(deps): update kotlin by @renovate in https://github.com/element-hq/element-x-android/pull/4713
|
||||||
|
* fix(deps): update dependency com.squareup.retrofit2:retrofit-bom to v3 by @renovate in https://github.com/element-hq/element-x-android/pull/4729
|
||||||
|
* fix(deps): update kotlinpoet to v2.2.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4732
|
||||||
|
* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.5.21 by @renovate in https://github.com/element-hq/element-x-android/pull/4759
|
||||||
|
### Others
|
||||||
|
* Remove event cache feature flag by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4719
|
||||||
|
* Check homeserver when login using qr code by @bmarty in https://github.com/element-hq/element-x-android/pull/4708
|
||||||
|
* Merge on boarding module to login module by @bmarty in https://github.com/element-hq/element-x-android/pull/4746
|
||||||
|
* Allow configuration to provide multiple account providers. by @bmarty in https://github.com/element-hq/element-x-android/pull/4742
|
||||||
|
* Reduce API of JoinedRoom, caller must use the Timeline API from liveTimeline instead by @bmarty in https://github.com/element-hq/element-x-android/pull/4731
|
||||||
|
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.05.3...v25.05.4
|
||||||
|
|
||||||
Changes in Element X v25.05.3
|
Changes in Element X v25.05.3
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ Just clone the project and open it in Android Studio. Make sure to select the
|
||||||
`app` configuration when building (as we also have sample apps in the project).
|
`app` configuration when building (as we also have sample apps in the project).
|
||||||
|
|
||||||
To build against a local copy of the Rust SDK, see the [Developer
|
To build against a local copy of the Rust SDK, see the [Developer
|
||||||
onboarding](docs/_developer_onboarding.md#build-the-sdk-locally) instructions.
|
onboarding](docs/_developer_onboarding.md#building-the-sdk-locally) instructions.
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,17 @@
|
||||||
android:value='androidx.startup' />
|
android:value='androidx.startup' />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Using launchMode singleTask to avoid multiple instances of the Activity
|
||||||
|
when the app is already open. This is important for incoming share (see
|
||||||
|
https://github.com/element-hq/element-x-android/issues/4074) and for opening
|
||||||
|
the application from a mobile.element.io link.
|
||||||
|
-->
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|uiMode"
|
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|uiMode"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTask"
|
||||||
android:theme="@style/Theme.ElementX.Splash"
|
android:theme="@style/Theme.ElementX.Splash"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
@ -54,6 +60,9 @@
|
||||||
android:host="open"
|
android:host="open"
|
||||||
android:scheme="elementx" />
|
android:scheme="elementx" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<!--
|
||||||
|
Oidc redirection
|
||||||
|
-->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
|
@ -80,6 +89,21 @@
|
||||||
<!-- Matching asset file: https://staging.element.io/.well-known/assetlinks.json -->
|
<!-- Matching asset file: https://staging.element.io/.well-known/assetlinks.json -->
|
||||||
<data android:host="staging.element.io" />
|
<data android:host="staging.element.io" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<!--
|
||||||
|
Element mobile links
|
||||||
|
Example: https://mobile.element.io/element?account_provider=example.org&login_hint=mxid:@alice:example.org
|
||||||
|
-->
|
||||||
|
<intent-filter android:autoVerify="true">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="https" />
|
||||||
|
<!-- Matching asset file: https://mobile.element.io/.well-known/assetlinks.json -->
|
||||||
|
<data android:host="mobile.element.io" />
|
||||||
|
<data android:path="/element" />
|
||||||
|
</intent-filter>
|
||||||
<!--
|
<!--
|
||||||
matrix.to links
|
matrix.to links
|
||||||
Note: On Android 12 and higher clicking a web link (that is not an Android App Link) always shows content in a web browser
|
Note: On Android 12 and higher clicking a web link (that is not an Android App Link) always shows content in a web browser
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,4 @@ package io.element.android.appconfig
|
||||||
object MatrixConfiguration {
|
object MatrixConfiguration {
|
||||||
const val MATRIX_TO_PERMALINK_BASE_URL: String = "https://matrix.to/#/"
|
const val MATRIX_TO_PERMALINK_BASE_URL: String = "https://matrix.to/#/"
|
||||||
val clientPermalinkBaseUrl: String? = null
|
val clientPermalinkBaseUrl: String? = null
|
||||||
|
|
||||||
// TODO remove this when report is fixed
|
|
||||||
const val CAN_REPORT_ROOM = false
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ dependencies {
|
||||||
testImplementation(libs.molecule.runtime)
|
testImplementation(libs.molecule.runtime)
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
|
testImplementation(projects.features.login.test)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.libraries.oidc.impl)
|
testImplementation(projects.libraries.oidc.impl)
|
||||||
testImplementation(projects.libraries.preferences.test)
|
testImplementation(projects.libraries.preferences.test)
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,15 @@ import com.bumble.appyx.core.node.Node
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
import com.bumble.appyx.core.plugin.plugins
|
import com.bumble.appyx.core.plugin.plugins
|
||||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.features.login.api.LoginEntryPoint
|
import io.element.android.features.login.api.LoginEntryPoint
|
||||||
import io.element.android.features.login.api.LoginFlowType
|
import io.element.android.features.login.api.LoginParams
|
||||||
import io.element.android.features.onboarding.api.OnBoardingEntryPoint
|
|
||||||
import io.element.android.libraries.architecture.BackstackView
|
import io.element.android.libraries.architecture.BackstackView
|
||||||
import io.element.android.libraries.architecture.BaseFlowNode
|
import io.element.android.libraries.architecture.BaseFlowNode
|
||||||
|
import io.element.android.libraries.architecture.NodeInputs
|
||||||
|
import io.element.android.libraries.architecture.inputs
|
||||||
import io.element.android.libraries.designsystem.utils.ForceOrientationInMobileDevices
|
import io.element.android.libraries.designsystem.utils.ForceOrientationInMobileDevices
|
||||||
import io.element.android.libraries.designsystem.utils.ScreenOrientation
|
import io.element.android.libraries.designsystem.utils.ScreenOrientation
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
|
|
@ -39,21 +39,26 @@ import kotlinx.parcelize.Parcelize
|
||||||
class NotLoggedInFlowNode @AssistedInject constructor(
|
class NotLoggedInFlowNode @AssistedInject constructor(
|
||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val onBoardingEntryPoint: OnBoardingEntryPoint,
|
|
||||||
private val loginEntryPoint: LoginEntryPoint,
|
private val loginEntryPoint: LoginEntryPoint,
|
||||||
private val notLoggedInImageLoaderFactory: NotLoggedInImageLoaderFactory,
|
private val notLoggedInImageLoaderFactory: NotLoggedInImageLoaderFactory,
|
||||||
) : BaseFlowNode<NotLoggedInFlowNode.NavTarget>(
|
) : BaseFlowNode<NotLoggedInFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
backstack = BackStack(
|
||||||
initialElement = NavTarget.OnBoarding,
|
initialElement = NavTarget.Root,
|
||||||
savedStateMap = buildContext.savedStateMap
|
savedStateMap = buildContext.savedStateMap
|
||||||
),
|
),
|
||||||
buildContext = buildContext,
|
buildContext = buildContext,
|
||||||
plugins = plugins,
|
plugins = plugins,
|
||||||
) {
|
) {
|
||||||
|
data class Params(
|
||||||
|
val loginParams: LoginParams?,
|
||||||
|
) : NodeInputs
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun onOpenBugReport()
|
fun onOpenBugReport()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val inputs = inputs<Params>()
|
||||||
|
|
||||||
override fun onBuilt() {
|
override fun onBuilt() {
|
||||||
super.onBuilt()
|
super.onBuilt()
|
||||||
lifecycle.subscribe(
|
lifecycle.subscribe(
|
||||||
|
|
@ -65,42 +70,28 @@ class NotLoggedInFlowNode @AssistedInject constructor(
|
||||||
|
|
||||||
sealed interface NavTarget : Parcelable {
|
sealed interface NavTarget : Parcelable {
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object OnBoarding : NavTarget
|
data object Root : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
data class LoginFlow(val type: LoginFlowType) : NavTarget
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||||
return when (navTarget) {
|
return when (navTarget) {
|
||||||
NavTarget.OnBoarding -> {
|
NavTarget.Root -> {
|
||||||
val callback = object : OnBoardingEntryPoint.Callback {
|
val callback = object : LoginEntryPoint.Callback {
|
||||||
override fun onSignUp() {
|
|
||||||
backstack.push(NavTarget.LoginFlow(type = LoginFlowType.SIGN_UP))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSignIn() {
|
|
||||||
backstack.push(NavTarget.LoginFlow(type = LoginFlowType.SIGN_IN_MANUAL))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSignInWithQrCode() {
|
|
||||||
backstack.push(NavTarget.LoginFlow(type = LoginFlowType.SIGN_IN_QR_CODE))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onReportProblem() {
|
override fun onReportProblem() {
|
||||||
plugins<Callback>().forEach { it.onOpenBugReport() }
|
plugins<Callback>().forEach { it.onOpenBugReport() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onBoardingEntryPoint
|
loginEntryPoint
|
||||||
.nodeBuilder(this, buildContext)
|
.nodeBuilder(this, buildContext)
|
||||||
|
.params(
|
||||||
|
LoginEntryPoint.Params(
|
||||||
|
accountProvider = inputs.loginParams?.accountProvider,
|
||||||
|
loginHint = inputs.loginParams?.loginHint,
|
||||||
|
)
|
||||||
|
)
|
||||||
.callback(callback)
|
.callback(callback)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
is NavTarget.LoginFlow -> {
|
|
||||||
loginEntryPoint.nodeBuilder(this, buildContext)
|
|
||||||
.params(LoginEntryPoint.Params(flowType = navTarget.type))
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ import io.element.android.appnav.intent.ResolvedIntent
|
||||||
import io.element.android.appnav.root.RootNavStateFlowFactory
|
import io.element.android.appnav.root.RootNavStateFlowFactory
|
||||||
import io.element.android.appnav.root.RootPresenter
|
import io.element.android.appnav.root.RootPresenter
|
||||||
import io.element.android.appnav.root.RootView
|
import io.element.android.appnav.root.RootView
|
||||||
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
|
import io.element.android.features.login.api.LoginParams
|
||||||
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
|
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
|
||||||
import io.element.android.features.signedout.api.SignedOutEntryPoint
|
import io.element.android.features.signedout.api.SignedOutEntryPoint
|
||||||
import io.element.android.features.viewfolder.api.ViewFolderEntryPoint
|
import io.element.android.features.viewfolder.api.ViewFolderEntryPoint
|
||||||
|
|
@ -40,6 +42,7 @@ import io.element.android.libraries.architecture.BackstackView
|
||||||
import io.element.android.libraries.architecture.BaseFlowNode
|
import io.element.android.libraries.architecture.BaseFlowNode
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
import io.element.android.libraries.architecture.waitForChildAttached
|
import io.element.android.libraries.architecture.waitForChildAttached
|
||||||
|
import io.element.android.libraries.core.uri.ensureProtocol
|
||||||
import io.element.android.libraries.deeplink.DeeplinkData
|
import io.element.android.libraries.deeplink.DeeplinkData
|
||||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
|
|
@ -61,6 +64,7 @@ class RootFlowNode @AssistedInject constructor(
|
||||||
@Assisted val buildContext: BuildContext,
|
@Assisted val buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val authenticationService: MatrixAuthenticationService,
|
private val authenticationService: MatrixAuthenticationService,
|
||||||
|
private val enterpriseService: EnterpriseService,
|
||||||
private val navStateFlowFactory: RootNavStateFlowFactory,
|
private val navStateFlowFactory: RootNavStateFlowFactory,
|
||||||
private val matrixSessionCache: MatrixSessionCache,
|
private val matrixSessionCache: MatrixSessionCache,
|
||||||
private val presenter: RootPresenter,
|
private val presenter: RootPresenter,
|
||||||
|
|
@ -99,14 +103,14 @@ class RootFlowNode @AssistedInject constructor(
|
||||||
if (navState.loggedInState.isTokenValid) {
|
if (navState.loggedInState.isTokenValid) {
|
||||||
tryToRestoreLatestSession(
|
tryToRestoreLatestSession(
|
||||||
onSuccess = { sessionId -> switchToLoggedInFlow(sessionId, navState.cacheIndex) },
|
onSuccess = { sessionId -> switchToLoggedInFlow(sessionId, navState.cacheIndex) },
|
||||||
onFailure = { switchToNotLoggedInFlow() }
|
onFailure = { switchToNotLoggedInFlow(null) }
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
switchToSignedOutFlow(SessionId(navState.loggedInState.sessionId))
|
switchToSignedOutFlow(SessionId(navState.loggedInState.sessionId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LoggedInState.NotLoggedIn -> {
|
LoggedInState.NotLoggedIn -> {
|
||||||
switchToNotLoggedInFlow()
|
switchToNotLoggedInFlow(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -117,9 +121,9 @@ class RootFlowNode @AssistedInject constructor(
|
||||||
backstack.safeRoot(NavTarget.LoggedInFlow(sessionId, navId))
|
backstack.safeRoot(NavTarget.LoggedInFlow(sessionId, navId))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun switchToNotLoggedInFlow() {
|
private fun switchToNotLoggedInFlow(params: LoginParams?) {
|
||||||
matrixSessionCache.removeAll()
|
matrixSessionCache.removeAll()
|
||||||
backstack.safeRoot(NavTarget.NotLoggedInFlow)
|
backstack.safeRoot(NavTarget.NotLoggedInFlow(params))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun switchToSignedOutFlow(sessionId: SessionId) {
|
private fun switchToSignedOutFlow(sessionId: SessionId) {
|
||||||
|
|
@ -175,7 +179,9 @@ class RootFlowNode @AssistedInject constructor(
|
||||||
data object SplashScreen : NavTarget
|
data object SplashScreen : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object NotLoggedInFlow : NavTarget
|
data class NotLoggedInFlow(
|
||||||
|
val params: LoginParams?
|
||||||
|
) : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class LoggedInFlow(
|
data class LoggedInFlow(
|
||||||
|
|
@ -211,13 +217,16 @@ class RootFlowNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
createNode<LoggedInAppScopeFlowNode>(buildContext, plugins = listOf(inputs, callback))
|
createNode<LoggedInAppScopeFlowNode>(buildContext, plugins = listOf(inputs, callback))
|
||||||
}
|
}
|
||||||
NavTarget.NotLoggedInFlow -> {
|
is NavTarget.NotLoggedInFlow -> {
|
||||||
val callback = object : NotLoggedInFlowNode.Callback {
|
val callback = object : NotLoggedInFlowNode.Callback {
|
||||||
override fun onOpenBugReport() {
|
override fun onOpenBugReport() {
|
||||||
backstack.push(NavTarget.BugReport)
|
backstack.push(NavTarget.BugReport)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
createNode<NotLoggedInFlowNode>(buildContext, plugins = listOf(callback))
|
val params = NotLoggedInFlowNode.Params(
|
||||||
|
loginParams = navTarget.params,
|
||||||
|
)
|
||||||
|
createNode<NotLoggedInFlowNode>(buildContext, plugins = listOf(params, callback))
|
||||||
}
|
}
|
||||||
is NavTarget.SignedOutFlow -> {
|
is NavTarget.SignedOutFlow -> {
|
||||||
signedOutEntryPoint.nodeBuilder(this, buildContext)
|
signedOutEntryPoint.nodeBuilder(this, buildContext)
|
||||||
|
|
@ -272,18 +281,36 @@ class RootFlowNode @AssistedInject constructor(
|
||||||
val resolvedIntent = intentResolver.resolve(intent) ?: return
|
val resolvedIntent = intentResolver.resolve(intent) ?: return
|
||||||
when (resolvedIntent) {
|
when (resolvedIntent) {
|
||||||
is ResolvedIntent.Navigation -> navigateTo(resolvedIntent.deeplinkData)
|
is ResolvedIntent.Navigation -> navigateTo(resolvedIntent.deeplinkData)
|
||||||
|
is ResolvedIntent.Login -> onLoginLink(resolvedIntent.params)
|
||||||
is ResolvedIntent.Oidc -> onOidcAction(resolvedIntent.oidcAction)
|
is ResolvedIntent.Oidc -> onOidcAction(resolvedIntent.oidcAction)
|
||||||
is ResolvedIntent.Permalink -> navigateTo(resolvedIntent.permalinkData)
|
is ResolvedIntent.Permalink -> navigateTo(resolvedIntent.permalinkData)
|
||||||
is ResolvedIntent.IncomingShare -> onIncomingShare(resolvedIntent.intent)
|
is ResolvedIntent.IncomingShare -> onIncomingShare(resolvedIntent.intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun onLoginLink(params: LoginParams) {
|
||||||
|
// Is there a session already?
|
||||||
|
val latestSessionId = authenticationService.getLatestSessionId()
|
||||||
|
if (latestSessionId == null) {
|
||||||
|
// No session, open login
|
||||||
|
if (enterpriseService.isAllowedToConnectToHomeserver(params.accountProvider.ensureProtocol())) {
|
||||||
|
switchToNotLoggedInFlow(params)
|
||||||
|
} else {
|
||||||
|
Timber.w("Login link ignored, we are not allowed to connect to the homeserver")
|
||||||
|
switchToNotLoggedInFlow(null)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Just ignore the login link if we already have a session
|
||||||
|
Timber.w("Login link ignored, we already have a session")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun onIncomingShare(intent: Intent) {
|
private suspend fun onIncomingShare(intent: Intent) {
|
||||||
// Is there a session already?
|
// Is there a session already?
|
||||||
val latestSessionId = authenticationService.getLatestSessionId()
|
val latestSessionId = authenticationService.getLatestSessionId()
|
||||||
if (latestSessionId == null) {
|
if (latestSessionId == null) {
|
||||||
// No session, open login
|
// No session, open login
|
||||||
switchToNotLoggedInFlow()
|
switchToNotLoggedInFlow(null)
|
||||||
} else {
|
} else {
|
||||||
attachSession(latestSessionId)
|
attachSession(latestSessionId)
|
||||||
.attachIncomingShare(intent)
|
.attachIncomingShare(intent)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@
|
||||||
package io.element.android.appnav.intent
|
package io.element.android.appnav.intent
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import io.element.android.features.login.api.LoginIntentResolver
|
||||||
|
import io.element.android.features.login.api.LoginParams
|
||||||
import io.element.android.libraries.deeplink.DeeplinkData
|
import io.element.android.libraries.deeplink.DeeplinkData
|
||||||
import io.element.android.libraries.deeplink.DeeplinkParser
|
import io.element.android.libraries.deeplink.DeeplinkParser
|
||||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||||
|
|
@ -21,11 +23,13 @@ sealed interface ResolvedIntent {
|
||||||
data class Navigation(val deeplinkData: DeeplinkData) : ResolvedIntent
|
data class Navigation(val deeplinkData: DeeplinkData) : ResolvedIntent
|
||||||
data class Oidc(val oidcAction: OidcAction) : ResolvedIntent
|
data class Oidc(val oidcAction: OidcAction) : ResolvedIntent
|
||||||
data class Permalink(val permalinkData: PermalinkData) : ResolvedIntent
|
data class Permalink(val permalinkData: PermalinkData) : ResolvedIntent
|
||||||
|
data class Login(val params: LoginParams) : ResolvedIntent
|
||||||
data class IncomingShare(val intent: Intent) : ResolvedIntent
|
data class IncomingShare(val intent: Intent) : ResolvedIntent
|
||||||
}
|
}
|
||||||
|
|
||||||
class IntentResolver @Inject constructor(
|
class IntentResolver @Inject constructor(
|
||||||
private val deeplinkParser: DeeplinkParser,
|
private val deeplinkParser: DeeplinkParser,
|
||||||
|
private val loginIntentResolver: LoginIntentResolver,
|
||||||
private val oidcIntentResolver: OidcIntentResolver,
|
private val oidcIntentResolver: OidcIntentResolver,
|
||||||
private val permalinkParser: PermalinkParser,
|
private val permalinkParser: PermalinkParser,
|
||||||
) {
|
) {
|
||||||
|
|
@ -40,10 +44,17 @@ class IntentResolver @Inject constructor(
|
||||||
val oidcAction = oidcIntentResolver.resolve(intent)
|
val oidcAction = oidcIntentResolver.resolve(intent)
|
||||||
if (oidcAction != null) return ResolvedIntent.Oidc(oidcAction)
|
if (oidcAction != null) return ResolvedIntent.Oidc(oidcAction)
|
||||||
|
|
||||||
// External link clicked? (matrix.to, element.io, etc.)
|
val actionViewData = intent
|
||||||
val permalinkData = intent
|
|
||||||
.takeIf { it.action == Intent.ACTION_VIEW }
|
.takeIf { it.action == Intent.ACTION_VIEW }
|
||||||
?.dataString
|
?.dataString
|
||||||
|
|
||||||
|
// Mobile configuration link clicked? (mobile.element.io)
|
||||||
|
val mobileLoginData = actionViewData
|
||||||
|
?.let { loginIntentResolver.parse(it) }
|
||||||
|
if (mobileLoginData != null) return ResolvedIntent.Login(mobileLoginData)
|
||||||
|
|
||||||
|
// External link clicked? (matrix.to, element.io, etc.)
|
||||||
|
val permalinkData = actionViewData
|
||||||
?.let { permalinkParser.parse(it) }
|
?.let { permalinkParser.parse(it) }
|
||||||
?.takeIf { it !is PermalinkData.FallbackLink }
|
?.takeIf { it !is PermalinkData.FallbackLink }
|
||||||
if (permalinkData != null) return ResolvedIntent.Permalink(permalinkData)
|
if (permalinkData != null) return ResolvedIntent.Permalink(permalinkData)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="banner_migrate_to_native_sliding_sync_action">"Abmelden und aktualisieren"</string>
|
<string name="banner_migrate_to_native_sliding_sync_action">"Abmelden und aktualisieren"</string>
|
||||||
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"%1$sunterstützt das alte Protokoll nicht mehr. Bitte melden Sie sich ab und wieder an, um die App weiter nutzen zu können."</string>
|
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"%1$s unterstützt das alte Protokoll nicht mehr. Bitte melden Sie sich ab und wieder an, um die App weiter nutzen zu können."</string>
|
||||||
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Dein Homeserver unterstützt das alte Protokoll nicht mehr. Bitte logge dich aus und melde dich wieder an, um die App weiter zu nutzen."</string>
|
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Dein Homeserver unterstützt das alte Protokoll nicht mehr. Bitte logge dich aus und melde dich wieder an, um die App weiter zu nutzen."</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.login.api.LoginParams
|
||||||
|
import io.element.android.features.login.test.FakeLoginIntentResolver
|
||||||
import io.element.android.libraries.deeplink.DeepLinkCreator
|
import io.element.android.libraries.deeplink.DeepLinkCreator
|
||||||
import io.element.android.libraries.deeplink.DeeplinkData
|
import io.element.android.libraries.deeplink.DeeplinkData
|
||||||
import io.element.android.libraries.deeplink.DeeplinkParser
|
import io.element.android.libraries.deeplink.DeeplinkParser
|
||||||
|
|
@ -165,6 +167,7 @@ class IntentResolverTest {
|
||||||
userId = UserId("@alice:matrix.org")
|
userId = UserId("@alice:matrix.org")
|
||||||
)
|
)
|
||||||
val sut = createIntentResolver(
|
val sut = createIntentResolver(
|
||||||
|
loginIntentResolverResult = { null },
|
||||||
permalinkParserResult = { permalinkData }
|
permalinkParserResult = { permalinkData }
|
||||||
)
|
)
|
||||||
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||||
|
|
@ -182,7 +185,8 @@ class IntentResolverTest {
|
||||||
@Test
|
@Test
|
||||||
fun `test resolve external permalink, FallbackLink should be ignored`() {
|
fun `test resolve external permalink, FallbackLink should be ignored`() {
|
||||||
val sut = createIntentResolver(
|
val sut = createIntentResolver(
|
||||||
permalinkParserResult = { PermalinkData.FallbackLink(Uri.parse("https://matrix.org")) }
|
permalinkParserResult = { PermalinkData.FallbackLink(Uri.parse("https://matrix.org")) },
|
||||||
|
loginIntentResolverResult = { null },
|
||||||
)
|
)
|
||||||
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||||
action = Intent.ACTION_VIEW
|
action = Intent.ACTION_VIEW
|
||||||
|
|
@ -231,7 +235,8 @@ class IntentResolverTest {
|
||||||
@Test
|
@Test
|
||||||
fun `test resolve invalid`() {
|
fun `test resolve invalid`() {
|
||||||
val sut = createIntentResolver(
|
val sut = createIntentResolver(
|
||||||
permalinkParserResult = { PermalinkData.FallbackLink(Uri.parse("https://matrix.org")) }
|
permalinkParserResult = { PermalinkData.FallbackLink(Uri.parse("https://matrix.org")) },
|
||||||
|
loginIntentResolverResult = { null },
|
||||||
)
|
)
|
||||||
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||||
action = Intent.ACTION_VIEW
|
action = Intent.ACTION_VIEW
|
||||||
|
|
@ -241,11 +246,29 @@ class IntentResolverTest {
|
||||||
assertThat(result).isNull()
|
assertThat(result).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test resolve login param`() {
|
||||||
|
val aLoginParams = LoginParams("accountProvider", null)
|
||||||
|
val sut = createIntentResolver(
|
||||||
|
loginIntentResolverResult = { aLoginParams },
|
||||||
|
)
|
||||||
|
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||||
|
action = Intent.ACTION_VIEW
|
||||||
|
data = "".toUri()
|
||||||
|
}
|
||||||
|
val result = sut.resolve(intent)
|
||||||
|
assertThat(result).isEqualTo(ResolvedIntent.Login(aLoginParams))
|
||||||
|
}
|
||||||
|
|
||||||
private fun createIntentResolver(
|
private fun createIntentResolver(
|
||||||
permalinkParserResult: (String) -> PermalinkData = { lambdaError() }
|
permalinkParserResult: (String) -> PermalinkData = { lambdaError() },
|
||||||
|
loginIntentResolverResult: (String) -> LoginParams? = { lambdaError() },
|
||||||
): IntentResolver {
|
): IntentResolver {
|
||||||
return IntentResolver(
|
return IntentResolver(
|
||||||
deeplinkParser = DeeplinkParser(),
|
deeplinkParser = DeeplinkParser(),
|
||||||
|
loginIntentResolver = FakeLoginIntentResolver(
|
||||||
|
parseResult = loginIntentResolverResult,
|
||||||
|
),
|
||||||
oidcIntentResolver = DefaultOidcIntentResolver(
|
oidcIntentResolver = DefaultOidcIntentResolver(
|
||||||
oidcUrlParser = DefaultOidcUrlParser(
|
oidcUrlParser = DefaultOidcUrlParser(
|
||||||
oidcRedirectUrlProvider = FakeOidcRedirectUrlProvider(),
|
oidcRedirectUrlProvider = FakeOidcRedirectUrlProvider(),
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit c754703e720bcb20f5dfa1ea0dcd32976825f71c
|
Subproject commit d3dffc97bf8b39386ace2db7d857bbff05c73c18
|
||||||
2
fastlane/metadata/android/en-US/changelogs/202505040.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/202505040.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: bug fixes and improvements.
|
||||||
|
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||||
|
|
@ -13,7 +13,7 @@ import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
interface EnterpriseService {
|
interface EnterpriseService {
|
||||||
val isEnterpriseBuild: Boolean
|
val isEnterpriseBuild: Boolean
|
||||||
suspend fun isEnterpriseUser(sessionId: SessionId): Boolean
|
suspend fun isEnterpriseUser(sessionId: SessionId): Boolean
|
||||||
fun defaultHomeserver(): String?
|
fun defaultHomeserverList(): List<String>
|
||||||
suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String): Boolean
|
suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String): Boolean
|
||||||
|
|
||||||
fun semanticColorsLight(): SemanticColors
|
fun semanticColorsLight(): SemanticColors
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ class DefaultEnterpriseService @Inject constructor() : EnterpriseService {
|
||||||
|
|
||||||
override suspend fun isEnterpriseUser(sessionId: SessionId) = false
|
override suspend fun isEnterpriseUser(sessionId: SessionId) = false
|
||||||
|
|
||||||
override fun defaultHomeserver() = null
|
override fun defaultHomeserverList(): List<String> = emptyList()
|
||||||
override suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String) = true
|
override suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String) = true
|
||||||
|
|
||||||
override fun semanticColorsLight(): SemanticColors = compoundColorsLight
|
override fun semanticColorsLight(): SemanticColors = compoundColorsLight
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ class DefaultEnterpriseServiceTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `defaultHomeserver should return null`() {
|
fun `defaultHomeserverList should return empty list`() {
|
||||||
val defaultEnterpriseService = DefaultEnterpriseService()
|
val defaultEnterpriseService = DefaultEnterpriseService()
|
||||||
assertThat<String?>(defaultEnterpriseService.defaultHomeserver()).isNull()
|
assertThat(defaultEnterpriseService.defaultHomeserverList()).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import io.element.android.tests.testutils.simulateLongTask
|
||||||
class FakeEnterpriseService(
|
class FakeEnterpriseService(
|
||||||
override val isEnterpriseBuild: Boolean = false,
|
override val isEnterpriseBuild: Boolean = false,
|
||||||
private val isEnterpriseUserResult: (SessionId) -> Boolean = { lambdaError() },
|
private val isEnterpriseUserResult: (SessionId) -> Boolean = { lambdaError() },
|
||||||
private val defaultHomeserverResult: () -> String? = { A_FAKE_HOMESERVER },
|
private val defaultHomeserverListResult: () -> List<String> = { emptyList() },
|
||||||
private val isAllowedToConnectToHomeserverResult: (String) -> Boolean = { lambdaError() },
|
private val isAllowedToConnectToHomeserverResult: (String) -> Boolean = { lambdaError() },
|
||||||
private val semanticColorsLightResult: () -> SemanticColors = { lambdaError() },
|
private val semanticColorsLightResult: () -> SemanticColors = { lambdaError() },
|
||||||
private val semanticColorsDarkResult: () -> SemanticColors = { lambdaError() },
|
private val semanticColorsDarkResult: () -> SemanticColors = { lambdaError() },
|
||||||
|
|
@ -27,8 +27,8 @@ class FakeEnterpriseService(
|
||||||
isEnterpriseUserResult(sessionId)
|
isEnterpriseUserResult(sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun defaultHomeserver(): String? {
|
override fun defaultHomeserverList(): List<String> {
|
||||||
return defaultHomeserverResult()
|
return defaultHomeserverListResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String): Boolean = simulateLongTask {
|
override suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String): Boolean = simulateLongTask {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import androidx.compose.runtime.setValue
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||||
import io.element.android.appconfig.MatrixConfiguration
|
|
||||||
import io.element.android.features.invite.api.SeenInvitesStore
|
import io.element.android.features.invite.api.SeenInvitesStore
|
||||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
|
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
|
||||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||||
|
|
@ -100,6 +99,8 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
val hideInviteAvatars by remember {
|
val hideInviteAvatars by remember {
|
||||||
appPreferencesStore.getHideInviteAvatarsFlow()
|
appPreferencesStore.getHideInviteAvatarsFlow()
|
||||||
}.collectAsState(initial = false)
|
}.collectAsState(initial = false)
|
||||||
|
val canReportRoom by produceState(false) { value = matrixClient.canReportRoom() }
|
||||||
|
|
||||||
val contentState by produceState<ContentState>(
|
val contentState by produceState<ContentState>(
|
||||||
initialValue = ContentState.Loading,
|
initialValue = ContentState.Loading,
|
||||||
key1 = roomInfo,
|
key1 = roomInfo,
|
||||||
|
|
@ -212,7 +213,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
applicationName = buildMeta.applicationName,
|
applicationName = buildMeta.applicationName,
|
||||||
knockMessage = knockMessage,
|
knockMessage = knockMessage,
|
||||||
hideInviteAvatars = hideInviteAvatars,
|
hideInviteAvatars = hideInviteAvatars,
|
||||||
canReportRoom = MatrixConfiguration.CAN_REPORT_ROOM,
|
canReportRoom = canReportRoom,
|
||||||
eventSink = ::handleEvents
|
eventSink = ::handleEvents
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,14 +25,6 @@
|
||||||
<string name="screen_knock_requests_list_empty_state_title">"Dim cais i ymuno yn disgwyl"</string>
|
<string name="screen_knock_requests_list_empty_state_title">"Dim cais i ymuno yn disgwyl"</string>
|
||||||
<string name="screen_knock_requests_list_initial_loading_title">"Yn llwytho ceisiadau i ymuno…"</string>
|
<string name="screen_knock_requests_list_initial_loading_title">"Yn llwytho ceisiadau i ymuno…"</string>
|
||||||
<string name="screen_knock_requests_list_title">"Ceisiadau i ymuno"</string>
|
<string name="screen_knock_requests_list_title">"Ceisiadau i ymuno"</string>
|
||||||
<plurals name="screen_room_multiple_knock_requests_title">
|
|
||||||
<item quantity="zero">"Dyw %1$s na +%2$d arall eisiau ymuno â\'r ystafell hon"</item>
|
|
||||||
<item quantity="one">"Mae %1$s +%2$d arall eisiau ymuno â\'r ystafell hon"</item>
|
|
||||||
<item quantity="two">"Mae %1$s +%2$d arall eisiau ymuno â\'r ystafell hon"</item>
|
|
||||||
<item quantity="few">"Mae %1$s +%2$d arall eisiau ymuno â\'r ystafell hon"</item>
|
|
||||||
<item quantity="many">"Mae %1$s +%2$d arall eisiau ymuno â\'r ystafell hon"</item>
|
|
||||||
<item quantity="other">"Mae %1$s +%2$d arall eisiau ymuno â\'r ystafell hon"</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Gweld y cyfan"</string>
|
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Gweld y cyfan"</string>
|
||||||
<string name="screen_room_single_knock_request_accept_button_title">"Derbyn"</string>
|
<string name="screen_room_single_knock_request_accept_button_title">"Derbyn"</string>
|
||||||
<string name="screen_room_single_knock_request_title">"Mae %1$s eisiau ymuno â\'r ystafell hon"</string>
|
<string name="screen_room_single_knock_request_title">"Mae %1$s eisiau ymuno â\'r ystafell hon"</string>
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ class SendLocationPresenter @Inject constructor(
|
||||||
when (mode) {
|
when (mode) {
|
||||||
SendLocationState.Mode.PinLocation -> {
|
SendLocationState.Mode.PinLocation -> {
|
||||||
val geoUri = event.cameraPosition.toGeoUri()
|
val geoUri = event.cameraPosition.toGeoUri()
|
||||||
room.sendLocation(
|
room.liveTimeline.sendLocation(
|
||||||
body = generateBody(geoUri),
|
body = generateBody(geoUri),
|
||||||
geoUri = geoUri,
|
geoUri = geoUri,
|
||||||
description = null,
|
description = null,
|
||||||
|
|
@ -119,7 +119,7 @@ class SendLocationPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
SendLocationState.Mode.SenderLocation -> {
|
SendLocationState.Mode.SenderLocation -> {
|
||||||
val geoUri = event.toGeoUri()
|
val geoUri = event.toGeoUri()
|
||||||
room.sendLocation(
|
room.liveTimeline.sendLocation(
|
||||||
body = generateBody(geoUri),
|
body = generateBody(geoUri),
|
||||||
geoUri = geoUri,
|
geoUri = geoUri,
|
||||||
description = null,
|
description = null,
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTran
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||||
|
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
|
||||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
|
|
@ -266,7 +267,9 @@ class SendLocationPresenterTest {
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
}
|
}
|
||||||
val joinedRoom = FakeJoinedRoom(
|
val joinedRoom = FakeJoinedRoom(
|
||||||
sendLocationResult = sendLocationResult,
|
liveTimeline = FakeTimeline().apply {
|
||||||
|
sendLocationLambda = sendLocationResult
|
||||||
|
},
|
||||||
)
|
)
|
||||||
val sendLocationPresenter = createSendLocationPresenter(joinedRoom)
|
val sendLocationPresenter = createSendLocationPresenter(joinedRoom)
|
||||||
fakePermissionsPresenter.givenState(
|
fakePermissionsPresenter.givenState(
|
||||||
|
|
@ -327,7 +330,9 @@ class SendLocationPresenterTest {
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
}
|
}
|
||||||
val joinedRoom = FakeJoinedRoom(
|
val joinedRoom = FakeJoinedRoom(
|
||||||
sendLocationResult = sendLocationResult,
|
liveTimeline = FakeTimeline().apply {
|
||||||
|
sendLocationLambda = sendLocationResult
|
||||||
|
},
|
||||||
)
|
)
|
||||||
val sendLocationPresenter = createSendLocationPresenter(joinedRoom)
|
val sendLocationPresenter = createSendLocationPresenter(joinedRoom)
|
||||||
fakePermissionsPresenter.givenState(
|
fakePermissionsPresenter.givenState(
|
||||||
|
|
@ -388,7 +393,9 @@ class SendLocationPresenterTest {
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
}
|
}
|
||||||
val joinedRoom = FakeJoinedRoom(
|
val joinedRoom = FakeJoinedRoom(
|
||||||
sendLocationResult = sendLocationResult,
|
liveTimeline = FakeTimeline().apply {
|
||||||
|
sendLocationLambda = sendLocationResult
|
||||||
|
},
|
||||||
)
|
)
|
||||||
val sendLocationPresenter = createSendLocationPresenter(joinedRoom)
|
val sendLocationPresenter = createSendLocationPresenter(joinedRoom)
|
||||||
fakePermissionsPresenter.givenState(
|
fakePermissionsPresenter.givenState(
|
||||||
|
|
|
||||||
|
|
@ -24,22 +24,6 @@ Dewiswch rywbeth cofiadwy. Os byddwch chi\'n anghofio\'r PIN hwn, byddwch chi\'n
|
||||||
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"Nid yw\'r PINau\'n cyfateb"</string>
|
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"Nid yw\'r PINau\'n cyfateb"</string>
|
||||||
<string name="screen_app_lock_signout_alert_message">"Bydd angen i chi ail-fewngofnodi a chreu PIN newydd i barhau"</string>
|
<string name="screen_app_lock_signout_alert_message">"Bydd angen i chi ail-fewngofnodi a chreu PIN newydd i barhau"</string>
|
||||||
<string name="screen_app_lock_signout_alert_title">"Rydych chi\'n cael eich allgofnodi"</string>
|
<string name="screen_app_lock_signout_alert_title">"Rydych chi\'n cael eich allgofnodi"</string>
|
||||||
<plurals name="screen_app_lock_subtitle">
|
|
||||||
<item quantity="zero">"Does gennych %1$d ceisiadau i ddatgloi"</item>
|
|
||||||
<item quantity="one">"Mae gennych %1$d cais i ddatgloi"</item>
|
|
||||||
<item quantity="two">"Mae gennych %1$d gais i ddatgloi"</item>
|
|
||||||
<item quantity="few">"Mae gennych %1$d chais i ddatgloi"</item>
|
|
||||||
<item quantity="many">"Mae gennych %1$d chais i ddatgloi"</item>
|
|
||||||
<item quantity="other">"Mae gennych %1$d cais i ddatgloi"</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="screen_app_lock_subtitle_wrong_pin">
|
|
||||||
<item quantity="zero">"PIN anghywir. Does gennych %1$d cais arall"</item>
|
|
||||||
<item quantity="one">"PIN anghywir. Mae gennych %1$d cais arall"</item>
|
|
||||||
<item quantity="two">"PIN anghywir. Mae gennych %1$d gais arall"</item>
|
|
||||||
<item quantity="few">"PIN anghywir. Mae gennych %1$d chais arall"</item>
|
|
||||||
<item quantity="many">"PIN anghywir. Mae gennych %1$d chais arall"</item>
|
|
||||||
<item quantity="other">"PIN anghywir. Mae gennych %1$d cais arall"</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="screen_app_lock_use_biometric_android">"Defnyddio biometreg"</string>
|
<string name="screen_app_lock_use_biometric_android">"Defnyddio biometreg"</string>
|
||||||
<string name="screen_app_lock_use_pin_android">"Defnyddio PIN"</string>
|
<string name="screen_app_lock_use_pin_android">"Defnyddio PIN"</string>
|
||||||
<string name="screen_signout_in_progress_dialog_content">"Yn allgofnodi…"</string>
|
<string name="screen_signout_in_progress_dialog_content">"Yn allgofnodi…"</string>
|
||||||
|
|
|
||||||
|
|
@ -7,28 +7,26 @@
|
||||||
|
|
||||||
package io.element.android.features.login.api
|
package io.element.android.features.login.api
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
|
|
||||||
interface LoginEntryPoint : FeatureEntryPoint {
|
interface LoginEntryPoint : FeatureEntryPoint {
|
||||||
data class Params(
|
data class Params(
|
||||||
val flowType: LoginFlowType
|
val accountProvider: String?,
|
||||||
|
val loginHint: String?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
interface Callback : Plugin {
|
||||||
|
fun onReportProblem()
|
||||||
|
}
|
||||||
|
|
||||||
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
||||||
|
|
||||||
interface NodeBuilder {
|
interface NodeBuilder {
|
||||||
fun params(params: Params): NodeBuilder
|
fun params(params: Params): NodeBuilder
|
||||||
|
fun callback(callback: Callback): NodeBuilder
|
||||||
fun build(): Node
|
fun build(): Node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
enum class LoginFlowType : Parcelable {
|
|
||||||
SIGN_IN_MANUAL,
|
|
||||||
SIGN_IN_QR_CODE,
|
|
||||||
SIGN_UP
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.login.api
|
||||||
|
|
||||||
|
interface LoginIntentResolver {
|
||||||
|
fun parse(uriString: String): LoginParams?
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.login.api
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters to start the login flow, when the application is opened
|
||||||
|
* from a mobile.element.io link.
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class LoginParams(
|
||||||
|
val accountProvider: String,
|
||||||
|
val loginHint: String?
|
||||||
|
) : Parcelable
|
||||||
|
|
@ -29,9 +29,11 @@ setupAnvil(componentMergingStrategy = ComponentMergingStrategy.KSP)
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.appconfig)
|
implementation(projects.appconfig)
|
||||||
implementation(projects.features.enterprise.api)
|
implementation(projects.features.enterprise.api)
|
||||||
|
implementation(projects.features.rageshake.api)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
|
implementation(projects.libraries.featureflag.api)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.libraries.network)
|
implementation(projects.libraries.network)
|
||||||
|
|
@ -56,7 +58,9 @@ dependencies {
|
||||||
testImplementation(libs.test.robolectric)
|
testImplementation(libs.test.robolectric)
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
|
testImplementation(projects.features.login.test)
|
||||||
testImplementation(projects.features.enterprise.test)
|
testImplementation(projects.features.enterprise.test)
|
||||||
|
testImplementation(projects.libraries.featureflag.test)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.libraries.oidc.impl)
|
testImplementation(projects.libraries.oidc.impl)
|
||||||
testImplementation(projects.libraries.permissions.test)
|
testImplementation(projects.libraries.permissions.test)
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,15 @@ class DefaultLoginEntryPoint @Inject constructor() : LoginEntryPoint {
|
||||||
|
|
||||||
return object : LoginEntryPoint.NodeBuilder {
|
return object : LoginEntryPoint.NodeBuilder {
|
||||||
override fun params(params: LoginEntryPoint.Params): LoginEntryPoint.NodeBuilder {
|
override fun params(params: LoginEntryPoint.Params): LoginEntryPoint.NodeBuilder {
|
||||||
plugins += LoginFlowNode.Inputs(flowType = params.flowType)
|
plugins += LoginFlowNode.Params(
|
||||||
|
accountProvider = params.accountProvider,
|
||||||
|
loginHint = params.loginHint,
|
||||||
|
)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun callback(callback: LoginEntryPoint.Callback): LoginEntryPoint.NodeBuilder {
|
||||||
|
plugins += callback
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.login.impl
|
||||||
|
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
|
import io.element.android.features.login.api.LoginIntentResolver
|
||||||
|
import io.element.android.features.login.api.LoginParams
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
class DefaultLoginIntentResolver @Inject constructor() : LoginIntentResolver {
|
||||||
|
override fun parse(uriString: String): LoginParams? {
|
||||||
|
val uri = uriString.toUri()
|
||||||
|
if (uri.host != "mobile.element.io") return null
|
||||||
|
if (uri.path?.startsWith("/element")?.not() == true) return null
|
||||||
|
val accountProvider = uri.getQueryParameter("account_provider") ?: return null
|
||||||
|
val loginHint = uri.getQueryParameter("login_hint")
|
||||||
|
return LoginParams(
|
||||||
|
accountProvider = accountProvider,
|
||||||
|
loginHint = loginHint,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,7 @@ import com.bumble.appyx.core.lifecycle.subscribe
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
|
import com.bumble.appyx.core.plugin.plugins
|
||||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.singleTop
|
import com.bumble.appyx.navmodel.backstack.operation.singleTop
|
||||||
|
|
@ -25,13 +26,14 @@ import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.login.api.LoginFlowType
|
import io.element.android.features.login.api.LoginEntryPoint
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||||
import io.element.android.features.login.impl.qrcode.QrCodeLoginFlowNode
|
import io.element.android.features.login.impl.qrcode.QrCodeLoginFlowNode
|
||||||
import io.element.android.features.login.impl.screens.changeaccountprovider.ChangeAccountProviderNode
|
import io.element.android.features.login.impl.screens.changeaccountprovider.ChangeAccountProviderNode
|
||||||
import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderNode
|
import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderNode
|
||||||
import io.element.android.features.login.impl.screens.createaccount.CreateAccountNode
|
import io.element.android.features.login.impl.screens.createaccount.CreateAccountNode
|
||||||
import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordNode
|
import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordNode
|
||||||
|
import io.element.android.features.login.impl.screens.onboarding.OnBoardingNode
|
||||||
import io.element.android.features.login.impl.screens.searchaccountprovider.SearchAccountProviderNode
|
import io.element.android.features.login.impl.screens.searchaccountprovider.SearchAccountProviderNode
|
||||||
import io.element.android.libraries.architecture.BackstackView
|
import io.element.android.libraries.architecture.BackstackView
|
||||||
import io.element.android.libraries.architecture.BaseFlowNode
|
import io.element.android.libraries.architecture.BaseFlowNode
|
||||||
|
|
@ -57,20 +59,19 @@ class LoginFlowNode @AssistedInject constructor(
|
||||||
private val oidcEntryPoint: OidcEntryPoint,
|
private val oidcEntryPoint: OidcEntryPoint,
|
||||||
) : BaseFlowNode<LoginFlowNode.NavTarget>(
|
) : BaseFlowNode<LoginFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
backstack = BackStack(
|
||||||
initialElement = NavTarget.Root,
|
initialElement = NavTarget.OnBoarding,
|
||||||
savedStateMap = buildContext.savedStateMap,
|
savedStateMap = buildContext.savedStateMap,
|
||||||
),
|
),
|
||||||
buildContext = buildContext,
|
buildContext = buildContext,
|
||||||
plugins = plugins,
|
plugins = plugins,
|
||||||
) {
|
) {
|
||||||
private var activity: Activity? = null
|
data class Params(
|
||||||
private var darkTheme: Boolean = false
|
val accountProvider: String?,
|
||||||
|
val loginHint: String?,
|
||||||
data class Inputs(
|
|
||||||
val flowType: LoginFlowType,
|
|
||||||
) : NodeInputs
|
) : NodeInputs
|
||||||
|
|
||||||
private val inputs: Inputs = inputs()
|
private var activity: Activity? = null
|
||||||
|
private var darkTheme: Boolean = false
|
||||||
|
|
||||||
private var customChromeTabStarted = false
|
private var customChromeTabStarted = false
|
||||||
|
|
||||||
|
|
@ -96,10 +97,15 @@ class LoginFlowNode @AssistedInject constructor(
|
||||||
|
|
||||||
sealed interface NavTarget : Parcelable {
|
sealed interface NavTarget : Parcelable {
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object Root : NavTarget
|
data object OnBoarding : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object ConfirmAccountProvider : NavTarget
|
data object QrCode : NavTarget
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class ConfirmAccountProvider(
|
||||||
|
val isAccountCreation: Boolean,
|
||||||
|
) : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object ChangeAccountProvider : NavTarget
|
data object ChangeAccountProvider : NavTarget
|
||||||
|
|
@ -119,29 +125,57 @@ class LoginFlowNode @AssistedInject constructor(
|
||||||
|
|
||||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||||
return when (navTarget) {
|
return when (navTarget) {
|
||||||
NavTarget.Root -> {
|
NavTarget.OnBoarding -> {
|
||||||
if (inputs.flowType == LoginFlowType.SIGN_IN_QR_CODE) {
|
val callback = object : OnBoardingNode.Callback {
|
||||||
createNode<QrCodeLoginFlowNode>(buildContext)
|
override fun onSignUp() {
|
||||||
} else {
|
backstack.push(
|
||||||
resolve(NavTarget.ConfirmAccountProvider, buildContext)
|
NavTarget.ConfirmAccountProvider(isAccountCreation = true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSignIn() {
|
||||||
|
backstack.push(
|
||||||
|
NavTarget.ConfirmAccountProvider(isAccountCreation = false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSignInWithQrCode() {
|
||||||
|
backstack.push(NavTarget.QrCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReportProblem() {
|
||||||
|
plugins<LoginEntryPoint.Callback>().forEach { it.onReportProblem() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOidcDetails(oidcDetails: OidcDetails) {
|
||||||
|
navigateToMas(oidcDetails)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateAccountContinue(url: String) {
|
||||||
|
backstack.push(NavTarget.CreateAccount(url))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoginPasswordNeeded() {
|
||||||
|
backstack.push(NavTarget.LoginPassword)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
val params = inputs<Params>()
|
||||||
|
val inputs = OnBoardingNode.Params(
|
||||||
|
accountProvider = params.accountProvider,
|
||||||
|
loginHint = params.loginHint,
|
||||||
|
)
|
||||||
|
createNode<OnBoardingNode>(buildContext, listOf(callback, inputs))
|
||||||
}
|
}
|
||||||
NavTarget.ConfirmAccountProvider -> {
|
NavTarget.QrCode -> {
|
||||||
|
createNode<QrCodeLoginFlowNode>(buildContext)
|
||||||
|
}
|
||||||
|
is NavTarget.ConfirmAccountProvider -> {
|
||||||
val inputs = ConfirmAccountProviderNode.Inputs(
|
val inputs = ConfirmAccountProviderNode.Inputs(
|
||||||
isAccountCreation = inputs.flowType == LoginFlowType.SIGN_UP,
|
isAccountCreation = navTarget.isAccountCreation,
|
||||||
)
|
)
|
||||||
val callback = object : ConfirmAccountProviderNode.Callback {
|
val callback = object : ConfirmAccountProviderNode.Callback {
|
||||||
override fun onOidcDetails(oidcDetails: OidcDetails) {
|
override fun onOidcDetails(oidcDetails: OidcDetails) {
|
||||||
if (oidcEntryPoint.canUseCustomTab()) {
|
navigateToMas(oidcDetails)
|
||||||
// In this case open a Chrome Custom tab
|
|
||||||
activity?.let {
|
|
||||||
customChromeTabStarted = true
|
|
||||||
oidcEntryPoint.openUrlInCustomTab(it, darkTheme, oidcDetails.url)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Fallback to WebView mode
|
|
||||||
backstack.push(NavTarget.OidcView(oidcDetails))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateAccountContinue(url: String) {
|
override fun onCreateAccountContinue(url: String) {
|
||||||
|
|
@ -162,7 +196,10 @@ class LoginFlowNode @AssistedInject constructor(
|
||||||
val callback = object : ChangeAccountProviderNode.Callback {
|
val callback = object : ChangeAccountProviderNode.Callback {
|
||||||
override fun onDone() {
|
override fun onDone() {
|
||||||
// Go back to the Account Provider screen
|
// Go back to the Account Provider screen
|
||||||
backstack.singleTop(NavTarget.ConfirmAccountProvider)
|
val confirmAccountProvider = backstack.elements.value.firstOrNull {
|
||||||
|
it.key.navTarget is NavTarget.ConfirmAccountProvider
|
||||||
|
}?.key?.navTarget ?: NavTarget.ConfirmAccountProvider(isAccountCreation = false)
|
||||||
|
backstack.singleTop(confirmAccountProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOtherClick() {
|
override fun onOtherClick() {
|
||||||
|
|
@ -176,7 +213,10 @@ class LoginFlowNode @AssistedInject constructor(
|
||||||
val callback = object : SearchAccountProviderNode.Callback {
|
val callback = object : SearchAccountProviderNode.Callback {
|
||||||
override fun onDone() {
|
override fun onDone() {
|
||||||
// Go back to the Account Provider screen
|
// Go back to the Account Provider screen
|
||||||
backstack.singleTop(NavTarget.ConfirmAccountProvider)
|
val confirmAccountProvider = backstack.elements.value.firstOrNull {
|
||||||
|
it.key.navTarget is NavTarget.ConfirmAccountProvider
|
||||||
|
}?.key?.navTarget ?: NavTarget.ConfirmAccountProvider(isAccountCreation = false)
|
||||||
|
backstack.singleTop(confirmAccountProvider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,6 +237,19 @@ class LoginFlowNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun navigateToMas(oidcDetails: OidcDetails) {
|
||||||
|
if (oidcEntryPoint.canUseCustomTab()) {
|
||||||
|
// In this case open a Chrome Custom tab
|
||||||
|
activity?.let {
|
||||||
|
customChromeTabStarted = true
|
||||||
|
oidcEntryPoint.openUrlInCustomTab(it, darkTheme, oidcDetails.url)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback to WebView mode
|
||||||
|
backstack.push(NavTarget.OidcView(oidcDetails))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
activity = requireNotNull(LocalActivity.current)
|
activity = requireNotNull(LocalActivity.current)
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,15 @@ import javax.inject.Inject
|
||||||
class AccountProviderDataSource @Inject constructor(
|
class AccountProviderDataSource @Inject constructor(
|
||||||
enterpriseService: EnterpriseService,
|
enterpriseService: EnterpriseService,
|
||||||
) {
|
) {
|
||||||
private val defaultAccountProvider = (enterpriseService.defaultHomeserver() ?: AuthenticationConfig.MATRIX_ORG_URL).let { url ->
|
private val defaultAccountProvider = (enterpriseService.defaultHomeserverList().firstOrNull() ?: AuthenticationConfig.MATRIX_ORG_URL)
|
||||||
AccountProvider(
|
.let { url ->
|
||||||
url = url,
|
AccountProvider(
|
||||||
subtitle = null,
|
url = url,
|
||||||
isPublic = url == AuthenticationConfig.MATRIX_ORG_URL,
|
subtitle = null,
|
||||||
isMatrixOrg = url == AuthenticationConfig.MATRIX_ORG_URL,
|
isPublic = url == AuthenticationConfig.MATRIX_ORG_URL,
|
||||||
)
|
isMatrixOrg = url == AuthenticationConfig.MATRIX_ORG_URL,
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private val accountProvider: MutableStateFlow<AccountProvider> = MutableStateFlow(
|
private val accountProvider: MutableStateFlow<AccountProvider> = MutableStateFlow(
|
||||||
defaultAccountProvider
|
defaultAccountProvider
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,10 @@ open class AccountProviderProvider : PreviewParameterProvider<AccountProvider> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun anAccountProvider() = AccountProvider(
|
fun anAccountProvider(
|
||||||
url = AuthenticationConfig.MATRIX_ORG_URL,
|
url: String = AuthenticationConfig.MATRIX_ORG_URL,
|
||||||
|
) = AccountProvider(
|
||||||
|
url = url,
|
||||||
subtitle = "Matrix.org is an open network for secure, decentralized communication.",
|
subtitle = "Matrix.org is an open network for secure, decentralized communication.",
|
||||||
isPublic = true,
|
isPublic = true,
|
||||||
isMatrixOrg = true,
|
isMatrixOrg = true,
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,10 @@ class ChangeServerPresenter @Inject constructor(
|
||||||
) = launch {
|
) = launch {
|
||||||
suspend {
|
suspend {
|
||||||
if (enterpriseService.isAllowedToConnectToHomeserver(data.url).not()) {
|
if (enterpriseService.isAllowedToConnectToHomeserver(data.url).not()) {
|
||||||
throw UnauthorizedAccountProviderException(data)
|
throw UnauthorizedAccountProviderException(
|
||||||
|
unauthorisedAccountProviderTitle = data.title,
|
||||||
|
authorisedAccountProviderTitles = enterpriseService.defaultHomeserverList(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
authenticationService.setHomeserver(data.url).map {
|
authenticationService.setHomeserver(data.url).map {
|
||||||
authenticationService.getHomeserverDetails().value!!
|
authenticationService.getHomeserverDetails().value!!
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
package io.element.android.features.login.impl.changeserver
|
package io.element.android.features.login.impl.changeserver
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.login.impl.accountprovider.anAccountProvider
|
|
||||||
import io.element.android.features.login.impl.error.ChangeServerError
|
import io.element.android.features.login.impl.error.ChangeServerError
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
@ -19,7 +18,14 @@ open class ChangeServerStateProvider : PreviewParameterProvider<ChangeServerStat
|
||||||
aChangeServerState(),
|
aChangeServerState(),
|
||||||
aChangeServerState(changeServerAction = AsyncData.Failure(ChangeServerError.Error(CommonStrings.error_unknown))),
|
aChangeServerState(changeServerAction = AsyncData.Failure(ChangeServerError.Error(CommonStrings.error_unknown))),
|
||||||
aChangeServerState(changeServerAction = AsyncData.Failure(ChangeServerError.SlidingSyncAlert)),
|
aChangeServerState(changeServerAction = AsyncData.Failure(ChangeServerError.SlidingSyncAlert)),
|
||||||
aChangeServerState(changeServerAction = AsyncData.Failure(ChangeServerError.UnauthorizedAccountProvider(anAccountProvider()))),
|
aChangeServerState(
|
||||||
|
changeServerAction = AsyncData.Failure(
|
||||||
|
ChangeServerError.UnauthorizedAccountProvider(
|
||||||
|
unauthorisedAccountProviderTitle = "example.com",
|
||||||
|
authorisedAccountProviderTitles = listOf("element.io", "element.org"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ fun ChangeServerView(
|
||||||
content = stringResource(
|
content = stringResource(
|
||||||
id = R.string.screen_change_server_error_unauthorized_homeserver,
|
id = R.string.screen_change_server_error_unauthorized_homeserver,
|
||||||
LocalBuildMeta.current.applicationName,
|
LocalBuildMeta.current.applicationName,
|
||||||
error.accountProvider.title,
|
error.unauthorisedAccountProviderTitle,
|
||||||
),
|
),
|
||||||
onSubmit = {
|
onSubmit = {
|
||||||
eventSink.invoke(ChangeServerEvents.ClearError)
|
eventSink.invoke(ChangeServerEvents.ClearError)
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,7 @@
|
||||||
|
|
||||||
package io.element.android.features.login.impl.changeserver
|
package io.element.android.features.login.impl.changeserver
|
||||||
|
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
|
||||||
|
|
||||||
class UnauthorizedAccountProviderException(
|
class UnauthorizedAccountProviderException(
|
||||||
val accountProvider: AccountProvider,
|
val unauthorisedAccountProviderTitle: String,
|
||||||
|
val authorisedAccountProviderTitles: List<String>,
|
||||||
) : Exception()
|
) : Exception()
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import androidx.annotation.StringRes
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import io.element.android.features.login.impl.R
|
import io.element.android.features.login.impl.R
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
|
||||||
import io.element.android.features.login.impl.changeserver.UnauthorizedAccountProviderException
|
import io.element.android.features.login.impl.changeserver.UnauthorizedAccountProviderException
|
||||||
import io.element.android.libraries.matrix.api.auth.AuthenticationException
|
import io.element.android.libraries.matrix.api.auth.AuthenticationException
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
@ -26,7 +25,8 @@ sealed class ChangeServerError : Throwable() {
|
||||||
}
|
}
|
||||||
|
|
||||||
data class UnauthorizedAccountProvider(
|
data class UnauthorizedAccountProvider(
|
||||||
val accountProvider: AccountProvider,
|
val unauthorisedAccountProviderTitle: String,
|
||||||
|
val authorisedAccountProviderTitles: List<String>,
|
||||||
) : ChangeServerError()
|
) : ChangeServerError()
|
||||||
|
|
||||||
data object SlidingSyncAlert : ChangeServerError()
|
data object SlidingSyncAlert : ChangeServerError()
|
||||||
|
|
@ -35,7 +35,10 @@ sealed class ChangeServerError : Throwable() {
|
||||||
fun from(error: Throwable): ChangeServerError = when (error) {
|
fun from(error: Throwable): ChangeServerError = when (error) {
|
||||||
is AuthenticationException.SlidingSyncVersion -> SlidingSyncAlert
|
is AuthenticationException.SlidingSyncVersion -> SlidingSyncAlert
|
||||||
is AuthenticationException.Oidc -> Error(messageStr = error.message)
|
is AuthenticationException.Oidc -> Error(messageStr = error.message)
|
||||||
is UnauthorizedAccountProviderException -> UnauthorizedAccountProvider(error.accountProvider)
|
is UnauthorizedAccountProviderException -> UnauthorizedAccountProvider(
|
||||||
|
unauthorisedAccountProviderTitle = error.unauthorisedAccountProviderTitle,
|
||||||
|
authorisedAccountProviderTitles = error.authorisedAccountProviderTitles,
|
||||||
|
)
|
||||||
else -> Error(messageId = R.string.screen_change_server_error_invalid_homeserver)
|
else -> Error(messageId = R.string.screen_change_server_error_invalid_homeserver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.login.impl.login
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import io.element.android.features.login.impl.DefaultLoginUserStory
|
||||||
|
import io.element.android.features.login.impl.error.ChangeServerError
|
||||||
|
import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderPresenter
|
||||||
|
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
||||||
|
import io.element.android.features.login.impl.screens.onboarding.OnBoardingPresenter
|
||||||
|
import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
|
||||||
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
|
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||||
|
import io.element.android.libraries.matrix.api.auth.OidcPrompt
|
||||||
|
import io.element.android.libraries.oidc.api.OidcAction
|
||||||
|
import io.element.android.libraries.oidc.api.OidcActionFlow
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is responsible for managing the login flow, including handling OIDC actions and
|
||||||
|
* submitting login requests.
|
||||||
|
* It's an helper to avoid code duplication. It is used by [OnBoardingPresenter] and [ConfirmAccountProviderPresenter].
|
||||||
|
*/
|
||||||
|
class LoginHelper @Inject constructor(
|
||||||
|
private val oidcActionFlow: OidcActionFlow,
|
||||||
|
private val authenticationService: MatrixAuthenticationService,
|
||||||
|
private val defaultLoginUserStory: DefaultLoginUserStory,
|
||||||
|
private val webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever,
|
||||||
|
) {
|
||||||
|
private val loginModeState: MutableState<AsyncData<LoginMode>> = mutableStateOf(AsyncData.Uninitialized)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun collectLoginMode(): State<AsyncData<LoginMode>> {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
oidcActionFlow.collect { oidcAction ->
|
||||||
|
if (oidcAction != null) {
|
||||||
|
onOidcAction(oidcAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return loginModeState
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearError() {
|
||||||
|
loginModeState.value = AsyncData.Uninitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submit(
|
||||||
|
coroutineScope: CoroutineScope,
|
||||||
|
isAccountCreation: Boolean,
|
||||||
|
homeserverUrl: String,
|
||||||
|
loginHint: String?,
|
||||||
|
) = coroutineScope.launch {
|
||||||
|
suspend {
|
||||||
|
authenticationService.setHomeserver(homeserverUrl).map {
|
||||||
|
val matrixHomeServerDetails = authenticationService.getHomeserverDetails().value!!
|
||||||
|
if (matrixHomeServerDetails.supportsOidcLogin) {
|
||||||
|
// Retrieve the details right now
|
||||||
|
val oidcPrompt = if (isAccountCreation) OidcPrompt.Create else OidcPrompt.Login
|
||||||
|
LoginMode.Oidc(
|
||||||
|
authenticationService.getOidcUrl(prompt = oidcPrompt, loginHint = loginHint).getOrThrow()
|
||||||
|
)
|
||||||
|
} else if (isAccountCreation) {
|
||||||
|
val url = webClientUrlForAuthenticationRetriever.retrieve(homeserverUrl)
|
||||||
|
LoginMode.AccountCreation(url)
|
||||||
|
} else if (matrixHomeServerDetails.supportsPasswordLogin) {
|
||||||
|
LoginMode.PasswordLogin
|
||||||
|
} else {
|
||||||
|
error("Unsupported login flow")
|
||||||
|
}
|
||||||
|
}.getOrThrow()
|
||||||
|
}.runCatchingUpdatingState(
|
||||||
|
state = loginModeState,
|
||||||
|
errorTransform = {
|
||||||
|
when (it) {
|
||||||
|
is AccountCreationNotSupported -> it
|
||||||
|
else -> ChangeServerError.from(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun onOidcAction(oidcAction: OidcAction) {
|
||||||
|
loginModeState.value = AsyncData.Loading()
|
||||||
|
when (oidcAction) {
|
||||||
|
OidcAction.GoBack -> {
|
||||||
|
authenticationService.cancelOidcLogin()
|
||||||
|
.onSuccess {
|
||||||
|
loginModeState.value = AsyncData.Uninitialized
|
||||||
|
}
|
||||||
|
.onFailure { failure ->
|
||||||
|
loginModeState.value = AsyncData.Failure(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is OidcAction.Success -> {
|
||||||
|
authenticationService.loginWithOidc(oidcAction.url)
|
||||||
|
.onSuccess { _ ->
|
||||||
|
defaultLoginUserStory.setLoginFlowIsDone(true)
|
||||||
|
}
|
||||||
|
.onFailure { failure ->
|
||||||
|
loginModeState.value = AsyncData.Failure(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
oidcActionFlow.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.login.impl.login
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
||||||
|
|
||||||
|
sealed interface LoginMode {
|
||||||
|
data object PasswordLogin : LoginMode
|
||||||
|
data class Oidc(val oidcDetails: OidcDetails) : LoginMode
|
||||||
|
data class AccountCreation(val url: String) : LoginMode
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.login.impl.login
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import io.element.android.features.login.impl.R
|
||||||
|
import io.element.android.features.login.impl.dialogs.SlidingSyncNotSupportedDialog
|
||||||
|
import io.element.android.features.login.impl.error.ChangeServerError
|
||||||
|
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
||||||
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||||
|
import io.element.android.libraries.designsystem.theme.LocalBuildMeta
|
||||||
|
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LoginModeView(
|
||||||
|
loginMode: AsyncData<LoginMode>,
|
||||||
|
onClearError: () -> Unit,
|
||||||
|
onLearnMoreClick: () -> Unit,
|
||||||
|
onOidcDetails: (OidcDetails) -> Unit,
|
||||||
|
onNeedLoginPassword: () -> Unit,
|
||||||
|
onCreateAccountContinue: (url: String) -> Unit
|
||||||
|
) {
|
||||||
|
when (loginMode) {
|
||||||
|
is AsyncData.Failure -> {
|
||||||
|
when (val error = loginMode.error) {
|
||||||
|
is ChangeServerError -> {
|
||||||
|
when (error) {
|
||||||
|
is ChangeServerError.Error -> {
|
||||||
|
ErrorDialog(
|
||||||
|
content = error.message(),
|
||||||
|
onSubmit = onClearError,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is ChangeServerError.SlidingSyncAlert -> {
|
||||||
|
SlidingSyncNotSupportedDialog(
|
||||||
|
onLearnMoreClick = {
|
||||||
|
onLearnMoreClick()
|
||||||
|
onClearError()
|
||||||
|
},
|
||||||
|
onDismiss = onClearError,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is ChangeServerError.UnauthorizedAccountProvider -> {
|
||||||
|
ErrorDialog(
|
||||||
|
content = stringResource(
|
||||||
|
id = R.string.screen_change_server_error_unauthorized_homeserver,
|
||||||
|
LocalBuildMeta.current.applicationName,
|
||||||
|
error.unauthorisedAccountProviderTitle,
|
||||||
|
),
|
||||||
|
onSubmit = onClearError,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is AccountCreationNotSupported -> {
|
||||||
|
ErrorDialog(
|
||||||
|
content = stringResource(CommonStrings.error_account_creation_not_possible),
|
||||||
|
onSubmit = onClearError,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
ErrorDialog(
|
||||||
|
content = stringResource(CommonStrings.error_unknown),
|
||||||
|
onSubmit = onClearError,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is AsyncData.Loading -> Unit // The Continue button shows the loading state
|
||||||
|
is AsyncData.Success -> {
|
||||||
|
when (val loginModeData = loginMode.data) {
|
||||||
|
is LoginMode.Oidc -> onOidcDetails(loginModeData.oidcDetails)
|
||||||
|
LoginMode.PasswordLogin -> onNeedLoginPassword()
|
||||||
|
is LoginMode.AccountCreation -> onCreateAccountContinue(loginModeData.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AsyncData.Uninitialized -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,29 +8,39 @@
|
||||||
package io.element.android.features.login.impl.screens.changeaccountprovider
|
package io.element.android.features.login.impl.screens.changeaccountprovider
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import io.element.android.appconfig.AuthenticationConfig
|
import io.element.android.appconfig.AuthenticationConfig
|
||||||
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||||
import io.element.android.features.login.impl.changeserver.ChangeServerState
|
import io.element.android.features.login.impl.changeserver.ChangeServerState
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import io.element.android.libraries.core.uri.ensureProtocol
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ChangeAccountProviderPresenter @Inject constructor(
|
class ChangeAccountProviderPresenter @Inject constructor(
|
||||||
private val changeServerPresenter: Presenter<ChangeServerState>,
|
private val changeServerPresenter: Presenter<ChangeServerState>,
|
||||||
|
private val enterpriseService: EnterpriseService,
|
||||||
) : Presenter<ChangeAccountProviderState> {
|
) : Presenter<ChangeAccountProviderState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): ChangeAccountProviderState {
|
override fun present(): ChangeAccountProviderState {
|
||||||
|
val staticAccountProviderList = remember {
|
||||||
|
enterpriseService.defaultHomeserverList()
|
||||||
|
.map { it.ensureProtocol() }
|
||||||
|
.ifEmpty { listOf(AuthenticationConfig.MATRIX_ORG_URL) }
|
||||||
|
.map { url ->
|
||||||
|
AccountProvider(
|
||||||
|
url = url,
|
||||||
|
subtitle = null,
|
||||||
|
isPublic = url == AuthenticationConfig.MATRIX_ORG_URL,
|
||||||
|
isMatrixOrg = url == AuthenticationConfig.MATRIX_ORG_URL,
|
||||||
|
isValid = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val changeServerState = changeServerPresenter.present()
|
val changeServerState = changeServerPresenter.present()
|
||||||
return ChangeAccountProviderState(
|
return ChangeAccountProviderState(
|
||||||
// Just matrix.org by default for now
|
accountProviders = staticAccountProviderList,
|
||||||
accountProviders = listOf(
|
|
||||||
AccountProvider(
|
|
||||||
url = AuthenticationConfig.MATRIX_ORG_URL,
|
|
||||||
subtitle = null,
|
|
||||||
isPublic = true,
|
|
||||||
isMatrixOrg = true,
|
|
||||||
isValid = true,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
changeServerState = changeServerState,
|
changeServerState = changeServerState,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||||
import io.element.android.features.login.impl.changeserver.ChangeServerState
|
import io.element.android.features.login.impl.changeserver.ChangeServerState
|
||||||
|
|
||||||
// Do not use default value, so no member get forgotten in the presenters.
|
// Do not use default value, so no member get forgotten in the presenters.
|
||||||
data class ChangeAccountProviderState constructor(
|
data class ChangeAccountProviderState(
|
||||||
val accountProviders: List<AccountProvider>,
|
val accountProviders: List<AccountProvider>,
|
||||||
val changeServerState: ChangeServerState,
|
val changeServerState: ChangeServerState,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,38 +8,20 @@
|
||||||
package io.element.android.features.login.impl.screens.confirmaccountprovider
|
package io.element.android.features.login.impl.screens.confirmaccountprovider
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.features.login.impl.DefaultLoginUserStory
|
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||||
import io.element.android.features.login.impl.error.ChangeServerError
|
import io.element.android.features.login.impl.login.LoginHelper
|
||||||
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
|
||||||
import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
|
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
|
||||||
import io.element.android.libraries.matrix.api.auth.OidcPrompt
|
|
||||||
import io.element.android.libraries.oidc.api.OidcAction
|
|
||||||
import io.element.android.libraries.oidc.api.OidcActionFlow
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class ConfirmAccountProviderPresenter @AssistedInject constructor(
|
class ConfirmAccountProviderPresenter @AssistedInject constructor(
|
||||||
@Assisted private val params: Params,
|
@Assisted private val params: Params,
|
||||||
private val accountProviderDataSource: AccountProviderDataSource,
|
private val accountProviderDataSource: AccountProviderDataSource,
|
||||||
private val authenticationService: MatrixAuthenticationService,
|
private val loginHelper: LoginHelper,
|
||||||
private val oidcActionFlow: OidcActionFlow,
|
|
||||||
private val defaultLoginUserStory: DefaultLoginUserStory,
|
|
||||||
private val webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever,
|
|
||||||
) : Presenter<ConfirmAccountProviderState> {
|
) : Presenter<ConfirmAccountProviderState> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val isAccountCreation: Boolean,
|
val isAccountCreation: Boolean,
|
||||||
|
|
@ -55,91 +37,27 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor(
|
||||||
val accountProvider by accountProviderDataSource.flow.collectAsState()
|
val accountProvider by accountProviderDataSource.flow.collectAsState()
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
val loginFlowAction: MutableState<AsyncData<LoginFlow>> = remember {
|
val loginMode by loginHelper.collectLoginMode()
|
||||||
mutableStateOf(AsyncData.Uninitialized)
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
oidcActionFlow.collect { oidcAction ->
|
|
||||||
if (oidcAction != null) {
|
|
||||||
onOidcAction(oidcAction, loginFlowAction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun handleEvents(event: ConfirmAccountProviderEvents) {
|
fun handleEvents(event: ConfirmAccountProviderEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
ConfirmAccountProviderEvents.Continue -> {
|
ConfirmAccountProviderEvents.Continue -> {
|
||||||
localCoroutineScope.submit(accountProvider.url, loginFlowAction)
|
loginHelper.submit(
|
||||||
|
coroutineScope = localCoroutineScope,
|
||||||
|
isAccountCreation = params.isAccountCreation,
|
||||||
|
homeserverUrl = accountProvider.url,
|
||||||
|
loginHint = null,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ConfirmAccountProviderEvents.ClearError -> loginFlowAction.value = AsyncData.Uninitialized
|
ConfirmAccountProviderEvents.ClearError -> loginHelper.clearError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ConfirmAccountProviderState(
|
return ConfirmAccountProviderState(
|
||||||
accountProvider = accountProvider,
|
accountProvider = accountProvider,
|
||||||
isAccountCreation = params.isAccountCreation,
|
isAccountCreation = params.isAccountCreation,
|
||||||
loginFlow = loginFlowAction.value,
|
loginMode = loginMode,
|
||||||
eventSink = ::handleEvents
|
eventSink = ::handleEvents
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.submit(
|
|
||||||
homeserverUrl: String,
|
|
||||||
loginFlowAction: MutableState<AsyncData<LoginFlow>>,
|
|
||||||
) = launch {
|
|
||||||
suspend {
|
|
||||||
authenticationService.setHomeserver(homeserverUrl).map {
|
|
||||||
val matrixHomeServerDetails = authenticationService.getHomeserverDetails().value!!
|
|
||||||
if (matrixHomeServerDetails.supportsOidcLogin) {
|
|
||||||
// Retrieve the details right now
|
|
||||||
val oidcPrompt = if (params.isAccountCreation) OidcPrompt.Create else OidcPrompt.Login
|
|
||||||
LoginFlow.OidcFlow(authenticationService.getOidcUrl(oidcPrompt).getOrThrow())
|
|
||||||
} else if (params.isAccountCreation) {
|
|
||||||
val url = webClientUrlForAuthenticationRetriever.retrieve(homeserverUrl)
|
|
||||||
LoginFlow.AccountCreationFlow(url)
|
|
||||||
} else if (matrixHomeServerDetails.supportsPasswordLogin) {
|
|
||||||
LoginFlow.PasswordLogin
|
|
||||||
} else {
|
|
||||||
error("Unsupported login flow")
|
|
||||||
}
|
|
||||||
}.getOrThrow()
|
|
||||||
}.runCatchingUpdatingState(
|
|
||||||
state = loginFlowAction,
|
|
||||||
errorTransform = {
|
|
||||||
when (it) {
|
|
||||||
is AccountCreationNotSupported -> it
|
|
||||||
else -> ChangeServerError.from(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun onOidcAction(
|
|
||||||
oidcAction: OidcAction,
|
|
||||||
loginFlowAction: MutableState<AsyncData<LoginFlow>>,
|
|
||||||
) {
|
|
||||||
loginFlowAction.value = AsyncData.Loading()
|
|
||||||
when (oidcAction) {
|
|
||||||
OidcAction.GoBack -> {
|
|
||||||
authenticationService.cancelOidcLogin()
|
|
||||||
.onSuccess {
|
|
||||||
loginFlowAction.value = AsyncData.Uninitialized
|
|
||||||
}
|
|
||||||
.onFailure { failure ->
|
|
||||||
loginFlowAction.value = AsyncData.Failure(failure)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is OidcAction.Success -> {
|
|
||||||
authenticationService.loginWithOidc(oidcAction.url)
|
|
||||||
.onSuccess { _ ->
|
|
||||||
defaultLoginUserStory.setLoginFlowIsDone(true)
|
|
||||||
}
|
|
||||||
.onFailure { failure ->
|
|
||||||
loginFlowAction.value = AsyncData.Failure(failure)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
oidcActionFlow.reset()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,21 +8,15 @@
|
||||||
package io.element.android.features.login.impl.screens.confirmaccountprovider
|
package io.element.android.features.login.impl.screens.confirmaccountprovider
|
||||||
|
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||||
|
import io.element.android.features.login.impl.login.LoginMode
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
|
||||||
|
|
||||||
// Do not use default value, so no member get forgotten in the presenters.
|
// Do not use default value, so no member get forgotten in the presenters.
|
||||||
data class ConfirmAccountProviderState(
|
data class ConfirmAccountProviderState(
|
||||||
val accountProvider: AccountProvider,
|
val accountProvider: AccountProvider,
|
||||||
val isAccountCreation: Boolean,
|
val isAccountCreation: Boolean,
|
||||||
val loginFlow: AsyncData<LoginFlow>,
|
val loginMode: AsyncData<LoginMode>,
|
||||||
val eventSink: (ConfirmAccountProviderEvents) -> Unit
|
val eventSink: (ConfirmAccountProviderEvents) -> Unit
|
||||||
) {
|
) {
|
||||||
val submitEnabled: Boolean get() = accountProvider.url.isNotEmpty() && (loginFlow is AsyncData.Uninitialized || loginFlow is AsyncData.Loading)
|
val submitEnabled: Boolean get() = accountProvider.url.isNotEmpty() && (loginMode is AsyncData.Uninitialized || loginMode is AsyncData.Loading)
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface LoginFlow {
|
|
||||||
data object PasswordLogin : LoginFlow
|
|
||||||
data class OidcFlow(val oidcDetails: OidcDetails) : LoginFlow
|
|
||||||
data class AccountCreationFlow(val url: String) : LoginFlow
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ package io.element.android.features.login.impl.screens.confirmaccountprovider
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||||
import io.element.android.features.login.impl.accountprovider.anAccountProvider
|
import io.element.android.features.login.impl.accountprovider.anAccountProvider
|
||||||
|
import io.element.android.features.login.impl.login.LoginMode
|
||||||
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
|
||||||
|
|
@ -22,7 +23,7 @@ open class ConfirmAccountProviderStateProvider : PreviewParameterProvider<Confir
|
||||||
),
|
),
|
||||||
aConfirmAccountProviderState(
|
aConfirmAccountProviderState(
|
||||||
isAccountCreation = true,
|
isAccountCreation = true,
|
||||||
loginFlow = AsyncData.Failure(AccountCreationNotSupported())
|
loginMode = AsyncData.Failure(AccountCreationNotSupported())
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -30,11 +31,11 @@ open class ConfirmAccountProviderStateProvider : PreviewParameterProvider<Confir
|
||||||
private fun aConfirmAccountProviderState(
|
private fun aConfirmAccountProviderState(
|
||||||
accountProvider: AccountProvider = anAccountProvider(),
|
accountProvider: AccountProvider = anAccountProvider(),
|
||||||
isAccountCreation: Boolean = false,
|
isAccountCreation: Boolean = false,
|
||||||
loginFlow: AsyncData<LoginFlow> = AsyncData.Uninitialized,
|
loginMode: AsyncData<LoginMode> = AsyncData.Uninitialized,
|
||||||
eventSink: (ConfirmAccountProviderEvents) -> Unit = {},
|
eventSink: (ConfirmAccountProviderEvents) -> Unit = {},
|
||||||
) = ConfirmAccountProviderState(
|
) = ConfirmAccountProviderState(
|
||||||
accountProvider = accountProvider,
|
accountProvider = accountProvider,
|
||||||
isAccountCreation = isAccountCreation,
|
isAccountCreation = isAccountCreation,
|
||||||
loginFlow = loginFlow,
|
loginMode = loginMode,
|
||||||
eventSink = eventSink
|
eventSink = eventSink
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -19,15 +19,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.login.impl.R
|
import io.element.android.features.login.impl.R
|
||||||
import io.element.android.features.login.impl.dialogs.SlidingSyncNotSupportedDialog
|
import io.element.android.features.login.impl.login.LoginModeView
|
||||||
import io.element.android.features.login.impl.error.ChangeServerError
|
|
||||||
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||||
import io.element.android.libraries.designsystem.components.BigIcon
|
import io.element.android.libraries.designsystem.components.BigIcon
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
|
|
@ -47,9 +44,9 @@ fun ConfirmAccountProviderView(
|
||||||
onChange: () -> Unit,
|
onChange: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val isLoading by remember(state.loginFlow) {
|
val isLoading by remember(state.loginMode) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
state.loginFlow is AsyncData.Loading
|
state.loginMode is AsyncData.Loading
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val eventSink = state.eventSink
|
val eventSink = state.eventSink
|
||||||
|
|
@ -99,48 +96,16 @@ fun ConfirmAccountProviderView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
when (state.loginFlow) {
|
LoginModeView(
|
||||||
is AsyncData.Failure -> {
|
loginMode = state.loginMode,
|
||||||
when (val error = state.loginFlow.error) {
|
onClearError = {
|
||||||
is ChangeServerError.Error -> {
|
eventSink(ConfirmAccountProviderEvents.ClearError)
|
||||||
ErrorDialog(
|
},
|
||||||
content = error.message(),
|
onLearnMoreClick = onLearnMoreClick,
|
||||||
onSubmit = {
|
onOidcDetails = onOidcDetails,
|
||||||
eventSink.invoke(ConfirmAccountProviderEvents.ClearError)
|
onNeedLoginPassword = onNeedLoginPassword,
|
||||||
}
|
onCreateAccountContinue = onCreateAccountContinue,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
is ChangeServerError.SlidingSyncAlert -> {
|
|
||||||
SlidingSyncNotSupportedDialog(
|
|
||||||
onLearnMoreClick = {
|
|
||||||
onLearnMoreClick()
|
|
||||||
eventSink(ConfirmAccountProviderEvents.ClearError)
|
|
||||||
},
|
|
||||||
onDismiss = {
|
|
||||||
eventSink(ConfirmAccountProviderEvents.ClearError)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is AccountCreationNotSupported -> {
|
|
||||||
ErrorDialog(
|
|
||||||
content = stringResource(CommonStrings.error_account_creation_not_possible),
|
|
||||||
onSubmit = {
|
|
||||||
eventSink.invoke(ConfirmAccountProviderEvents.ClearError)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is AsyncData.Loading -> Unit // The Continue button shows the loading state
|
|
||||||
is AsyncData.Success -> {
|
|
||||||
when (val loginFlowState = state.loginFlow.data) {
|
|
||||||
is LoginFlow.OidcFlow -> onOidcDetails(loginFlowState.oidcDetails)
|
|
||||||
LoginFlow.PasswordLogin -> onNeedLoginPassword()
|
|
||||||
is LoginFlow.AccountCreationFlow -> onCreateAccountContinue(loginFlowState.url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AsyncData.Uninitialized -> Unit
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.login.impl.screens.onboarding
|
||||||
|
|
||||||
|
sealed interface OnBoardingEvents {
|
||||||
|
data class OnSignIn(
|
||||||
|
val defaultAccountProvider: String
|
||||||
|
) : OnBoardingEvents
|
||||||
|
|
||||||
|
data object ClearError : OnBoardingEvents
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.login.impl.screens.onboarding
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
|
import com.bumble.appyx.core.node.Node
|
||||||
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
|
import com.bumble.appyx.core.plugin.plugins
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
|
import io.element.android.features.login.impl.util.openLearnMorePage
|
||||||
|
import io.element.android.libraries.architecture.NodeInputs
|
||||||
|
import io.element.android.libraries.architecture.inputs
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
||||||
|
|
||||||
|
@ContributesNode(AppScope::class)
|
||||||
|
class OnBoardingNode @AssistedInject constructor(
|
||||||
|
@Assisted buildContext: BuildContext,
|
||||||
|
@Assisted plugins: List<Plugin>,
|
||||||
|
presenterFactory: OnBoardingPresenter.Factory,
|
||||||
|
) : Node(
|
||||||
|
buildContext = buildContext,
|
||||||
|
plugins = plugins
|
||||||
|
) {
|
||||||
|
interface Callback : Plugin {
|
||||||
|
fun onSignUp()
|
||||||
|
fun onSignIn()
|
||||||
|
fun onSignInWithQrCode()
|
||||||
|
fun onReportProblem()
|
||||||
|
fun onLoginPasswordNeeded()
|
||||||
|
fun onOidcDetails(oidcDetails: OidcDetails)
|
||||||
|
fun onCreateAccountContinue(url: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Params(
|
||||||
|
val accountProvider: String?,
|
||||||
|
val loginHint: String?,
|
||||||
|
) : NodeInputs
|
||||||
|
|
||||||
|
private val params = inputs<Params>()
|
||||||
|
|
||||||
|
private val presenter = presenterFactory.create(
|
||||||
|
params = params,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun onSignIn() {
|
||||||
|
plugins<Callback>().forEach { it.onSignIn() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSignUp() {
|
||||||
|
plugins<Callback>().forEach { it.onSignUp() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSignInWithQrCode() {
|
||||||
|
plugins<Callback>().forEach { it.onSignInWithQrCode() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onReportProblem() {
|
||||||
|
plugins<Callback>().forEach { it.onReportProblem() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onOidcDetails(data: OidcDetails) {
|
||||||
|
plugins<Callback>().forEach { it.onOidcDetails(data) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onLoginPasswordNeeded() {
|
||||||
|
plugins<Callback>().forEach { it.onLoginPasswordNeeded() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onCreateAccountContinue(url: String) {
|
||||||
|
plugins<Callback>().forEach { it.onCreateAccountContinue(url) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun View(modifier: Modifier) {
|
||||||
|
val state = presenter.present()
|
||||||
|
val context = LocalContext.current
|
||||||
|
OnBoardingView(
|
||||||
|
state = state,
|
||||||
|
modifier = modifier,
|
||||||
|
onSignIn = ::onSignIn,
|
||||||
|
onCreateAccount = ::onSignUp,
|
||||||
|
onSignInWithQrCode = ::onSignInWithQrCode,
|
||||||
|
onReportProblem = ::onReportProblem,
|
||||||
|
onOidcDetails = ::onOidcDetails,
|
||||||
|
onNeedLoginPassword = ::onLoginPasswordNeeded,
|
||||||
|
onLearnMoreClick = { openLearnMorePage(context) },
|
||||||
|
onCreateAccountContinue = ::onCreateAccountContinue,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.login.impl.screens.onboarding
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import io.element.android.appconfig.OnBoardingConfig
|
||||||
|
import io.element.android.features.login.impl.login.LoginHelper
|
||||||
|
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
|
||||||
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
|
|
||||||
|
class OnBoardingPresenter @AssistedInject constructor(
|
||||||
|
@Assisted private val params: OnBoardingNode.Params,
|
||||||
|
private val buildMeta: BuildMeta,
|
||||||
|
private val featureFlagService: FeatureFlagService,
|
||||||
|
private val rageshakeFeatureAvailability: RageshakeFeatureAvailability,
|
||||||
|
private val loginHelper: LoginHelper,
|
||||||
|
) : Presenter<OnBoardingState> {
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(
|
||||||
|
params: OnBoardingNode.Params,
|
||||||
|
): OnBoardingPresenter
|
||||||
|
}
|
||||||
|
|
||||||
|
private val defaultAccountProvider = params.accountProvider
|
||||||
|
private val loginHint = params.loginHint
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun present(): OnBoardingState {
|
||||||
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val canLoginWithQrCode by produceState(initialValue = false) {
|
||||||
|
value = defaultAccountProvider == null &&
|
||||||
|
featureFlagService.isFeatureEnabled(FeatureFlags.QrCodeLogin)
|
||||||
|
}
|
||||||
|
val canReportBug = remember { rageshakeFeatureAvailability.isAvailable() }
|
||||||
|
|
||||||
|
val loginMode by loginHelper.collectLoginMode()
|
||||||
|
|
||||||
|
fun handleEvent(event: OnBoardingEvents) {
|
||||||
|
when (event) {
|
||||||
|
is OnBoardingEvents.OnSignIn -> loginHelper.submit(
|
||||||
|
coroutineScope = localCoroutineScope,
|
||||||
|
isAccountCreation = false,
|
||||||
|
homeserverUrl = event.defaultAccountProvider,
|
||||||
|
loginHint = loginHint,
|
||||||
|
)
|
||||||
|
OnBoardingEvents.ClearError -> loginHelper.clearError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return OnBoardingState(
|
||||||
|
productionApplicationName = buildMeta.productionApplicationName,
|
||||||
|
defaultAccountProvider = defaultAccountProvider,
|
||||||
|
canLoginWithQrCode = canLoginWithQrCode,
|
||||||
|
canCreateAccount = defaultAccountProvider == null && OnBoardingConfig.CAN_CREATE_ACCOUNT,
|
||||||
|
canReportBug = canReportBug,
|
||||||
|
loginMode = loginMode,
|
||||||
|
eventSink = ::handleEvent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.login.impl.screens.onboarding
|
||||||
|
|
||||||
|
import io.element.android.features.login.impl.login.LoginMode
|
||||||
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
|
||||||
|
data class OnBoardingState(
|
||||||
|
val productionApplicationName: String,
|
||||||
|
val defaultAccountProvider: String?,
|
||||||
|
val canLoginWithQrCode: Boolean,
|
||||||
|
val canCreateAccount: Boolean,
|
||||||
|
val canReportBug: Boolean,
|
||||||
|
val loginMode: AsyncData<LoginMode>,
|
||||||
|
val eventSink: (OnBoardingEvents) -> Unit,
|
||||||
|
) {
|
||||||
|
val submitEnabled: Boolean
|
||||||
|
get() = defaultAccountProvider != null && (loginMode is AsyncData.Uninitialized || loginMode is AsyncData.Loading)
|
||||||
|
}
|
||||||
|
|
@ -5,9 +5,11 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.onboarding.impl
|
package io.element.android.features.login.impl.screens.onboarding
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import io.element.android.features.login.impl.login.LoginMode
|
||||||
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
|
||||||
open class OnBoardingStateProvider : PreviewParameterProvider<OnBoardingState> {
|
open class OnBoardingStateProvider : PreviewParameterProvider<OnBoardingState> {
|
||||||
override val values: Sequence<OnBoardingState>
|
override val values: Sequence<OnBoardingState>
|
||||||
|
|
@ -17,17 +19,24 @@ open class OnBoardingStateProvider : PreviewParameterProvider<OnBoardingState> {
|
||||||
anOnBoardingState(canCreateAccount = true),
|
anOnBoardingState(canCreateAccount = true),
|
||||||
anOnBoardingState(canLoginWithQrCode = true, canCreateAccount = true),
|
anOnBoardingState(canLoginWithQrCode = true, canCreateAccount = true),
|
||||||
anOnBoardingState(canLoginWithQrCode = true, canCreateAccount = true, canReportBug = true),
|
anOnBoardingState(canLoginWithQrCode = true, canCreateAccount = true, canReportBug = true),
|
||||||
|
anOnBoardingState(defaultAccountProvider = "element.io", canCreateAccount = false, canReportBug = true),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun anOnBoardingState(
|
fun anOnBoardingState(
|
||||||
productionApplicationName: String = "Element",
|
productionApplicationName: String = "Element",
|
||||||
|
defaultAccountProvider: String? = null,
|
||||||
canLoginWithQrCode: Boolean = false,
|
canLoginWithQrCode: Boolean = false,
|
||||||
canCreateAccount: Boolean = false,
|
canCreateAccount: Boolean = false,
|
||||||
canReportBug: Boolean = false,
|
canReportBug: Boolean = false,
|
||||||
|
loginMode: AsyncData<LoginMode> = AsyncData.Uninitialized,
|
||||||
|
eventSink: (OnBoardingEvents) -> Unit = {},
|
||||||
) = OnBoardingState(
|
) = OnBoardingState(
|
||||||
productionApplicationName = productionApplicationName,
|
productionApplicationName = productionApplicationName,
|
||||||
|
defaultAccountProvider = defaultAccountProvider,
|
||||||
canLoginWithQrCode = canLoginWithQrCode,
|
canLoginWithQrCode = canLoginWithQrCode,
|
||||||
canCreateAccount = canCreateAccount,
|
canCreateAccount = canCreateAccount,
|
||||||
canReportBug = canReportBug,
|
canReportBug = canReportBug,
|
||||||
|
loginMode = loginMode,
|
||||||
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.onboarding.impl
|
package io.element.android.features.login.impl.screens.onboarding
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
|
@ -16,6 +16,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
|
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
|
||||||
import androidx.compose.ui.BiasAlignment
|
import androidx.compose.ui.BiasAlignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -26,6 +29,9 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
|
import io.element.android.features.login.impl.R
|
||||||
|
import io.element.android.features.login.impl.login.LoginModeView
|
||||||
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.atomic.atoms.ElementLogoAtom
|
import io.element.android.libraries.designsystem.atomic.atoms.ElementLogoAtom
|
||||||
import io.element.android.libraries.designsystem.atomic.atoms.ElementLogoAtomSize
|
import io.element.android.libraries.designsystem.atomic.atoms.ElementLogoAtomSize
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||||
|
|
@ -36,6 +42,7 @@ import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||||
|
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
import io.element.android.libraries.testtags.testTag
|
import io.element.android.libraries.testtags.testTag
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
@ -51,6 +58,10 @@ fun OnBoardingView(
|
||||||
onSignInWithQrCode: () -> Unit,
|
onSignInWithQrCode: () -> Unit,
|
||||||
onSignIn: () -> Unit,
|
onSignIn: () -> Unit,
|
||||||
onCreateAccount: () -> Unit,
|
onCreateAccount: () -> Unit,
|
||||||
|
onOidcDetails: (OidcDetails) -> Unit,
|
||||||
|
onNeedLoginPassword: () -> Unit,
|
||||||
|
onLearnMoreClick: () -> Unit,
|
||||||
|
onCreateAccountContinue: (url: String) -> Unit,
|
||||||
onReportProblem: () -> Unit,
|
onReportProblem: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
@ -58,6 +69,16 @@ fun OnBoardingView(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
content = {
|
content = {
|
||||||
OnBoardingContent(state = state)
|
OnBoardingContent(state = state)
|
||||||
|
LoginModeView(
|
||||||
|
loginMode = state.loginMode,
|
||||||
|
onClearError = {
|
||||||
|
state.eventSink(OnBoardingEvents.ClearError)
|
||||||
|
},
|
||||||
|
onLearnMoreClick = onLearnMoreClick,
|
||||||
|
onOidcDetails = onOidcDetails,
|
||||||
|
onNeedLoginPassword = onNeedLoginPassword,
|
||||||
|
onCreateAccountContinue = onCreateAccountContinue,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
footer = {
|
footer = {
|
||||||
OnBoardingButtons(
|
OnBoardingButtons(
|
||||||
|
|
@ -126,6 +147,12 @@ private fun OnBoardingButtons(
|
||||||
onCreateAccount: () -> Unit,
|
onCreateAccount: () -> Unit,
|
||||||
onReportProblem: () -> Unit,
|
onReportProblem: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val isLoading by remember(state.loginMode) {
|
||||||
|
derivedStateOf {
|
||||||
|
state.loginMode is AsyncData.Loading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ButtonColumnMolecule {
|
ButtonColumnMolecule {
|
||||||
val signInButtonStringRes = if (state.canLoginWithQrCode || state.canCreateAccount) {
|
val signInButtonStringRes = if (state.canLoginWithQrCode || state.canCreateAccount) {
|
||||||
R.string.screen_onboarding_sign_in_manually
|
R.string.screen_onboarding_sign_in_manually
|
||||||
|
|
@ -140,13 +167,27 @@ private fun OnBoardingButtons(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Button(
|
val defaultAccountProvider = state.defaultAccountProvider
|
||||||
text = stringResource(id = signInButtonStringRes),
|
if (defaultAccountProvider == null) {
|
||||||
onClick = onSignIn,
|
Button(
|
||||||
modifier = Modifier
|
text = stringResource(id = signInButtonStringRes),
|
||||||
.fillMaxWidth()
|
onClick = onSignIn,
|
||||||
.testTag(TestTags.onBoardingSignIn)
|
modifier = Modifier
|
||||||
)
|
.fillMaxWidth()
|
||||||
|
.testTag(TestTags.onBoardingSignIn)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Button(
|
||||||
|
text = stringResource(id = R.string.screen_onboarding_sign_in_to, defaultAccountProvider),
|
||||||
|
showProgress = isLoading,
|
||||||
|
onClick = {
|
||||||
|
state.eventSink(OnBoardingEvents.OnSignIn(defaultAccountProvider))
|
||||||
|
},
|
||||||
|
enabled = state.submitEnabled || isLoading,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
if (state.canCreateAccount) {
|
if (state.canCreateAccount) {
|
||||||
TextButton(
|
TextButton(
|
||||||
text = stringResource(id = R.string.screen_onboarding_sign_up),
|
text = stringResource(id = R.string.screen_onboarding_sign_up),
|
||||||
|
|
@ -180,5 +221,9 @@ internal fun OnBoardingViewPreview(
|
||||||
onSignIn = {},
|
onSignIn = {},
|
||||||
onCreateAccount = {},
|
onCreateAccount = {},
|
||||||
onReportProblem = {},
|
onReportProblem = {},
|
||||||
|
onOidcDetails = {},
|
||||||
|
onNeedLoginPassword = {},
|
||||||
|
onLearnMoreClick = {},
|
||||||
|
onCreateAccountContinue = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -15,6 +15,8 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
|
import io.element.android.features.login.impl.changeserver.UnauthorizedAccountProviderException
|
||||||
import io.element.android.features.login.impl.qrcode.QrCodeLoginManager
|
import io.element.android.features.login.impl.qrcode.QrCodeLoginManager
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
|
@ -36,6 +38,7 @@ class QrCodeScanPresenter @Inject constructor(
|
||||||
private val qrCodeLoginDataFactory: MatrixQrCodeLoginDataFactory,
|
private val qrCodeLoginDataFactory: MatrixQrCodeLoginDataFactory,
|
||||||
private val qrCodeLoginManager: QrCodeLoginManager,
|
private val qrCodeLoginManager: QrCodeLoginManager,
|
||||||
private val coroutineDispatchers: CoroutineDispatchers,
|
private val coroutineDispatchers: CoroutineDispatchers,
|
||||||
|
private val enterpriseService: EnterpriseService,
|
||||||
) : Presenter<QrCodeScanState> {
|
) : Presenter<QrCodeScanState> {
|
||||||
private var isScanning by mutableStateOf(true)
|
private var isScanning by mutableStateOf(true)
|
||||||
|
|
||||||
|
|
@ -90,9 +93,17 @@ class QrCodeScanPresenter @Inject constructor(
|
||||||
|
|
||||||
launch(coroutineDispatchers.computation) {
|
launch(coroutineDispatchers.computation) {
|
||||||
suspend {
|
suspend {
|
||||||
qrCodeLoginDataFactory.parseQrCodeData(code).onFailure {
|
val data = qrCodeLoginDataFactory.parseQrCodeData(code).onFailure {
|
||||||
Timber.e(it, "Error parsing QR code data")
|
Timber.e(it, "Error parsing QR code data")
|
||||||
}.getOrThrow()
|
}.getOrThrow()
|
||||||
|
val serverName = data.serverName()
|
||||||
|
if (serverName != null && enterpriseService.isAllowedToConnectToHomeserver(serverName).not()) {
|
||||||
|
throw UnauthorizedAccountProviderException(
|
||||||
|
unauthorisedAccountProviderTitle = serverName,
|
||||||
|
authorisedAccountProviderTitles = enterpriseService.defaultHomeserverList(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
data
|
||||||
}.runCatchingUpdatingState(codeScannedAction)
|
}.runCatchingUpdatingState(codeScannedAction)
|
||||||
}.invokeOnCompletion {
|
}.invokeOnCompletion {
|
||||||
isProcessingCode.set(false)
|
isProcessingCode.set(false)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
package io.element.android.features.login.impl.screens.qrcode.scan
|
package io.element.android.features.login.impl.screens.qrcode.scan
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import io.element.android.features.login.impl.changeserver.UnauthorizedAccountProviderException
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData
|
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData
|
||||||
import io.element.android.libraries.matrix.api.auth.qrlogin.QrLoginException
|
import io.element.android.libraries.matrix.api.auth.qrlogin.QrLoginException
|
||||||
|
|
@ -19,6 +20,15 @@ open class QrCodeScanStateProvider : PreviewParameterProvider<QrCodeScanState> {
|
||||||
aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Loading),
|
aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Loading),
|
||||||
aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Failure(Exception("Error"))),
|
aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Failure(Exception("Error"))),
|
||||||
aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Failure(QrLoginException.OtherDeviceNotSignedIn)),
|
aQrCodeScanState(isScanning = false, authenticationAction = AsyncAction.Failure(QrLoginException.OtherDeviceNotSignedIn)),
|
||||||
|
aQrCodeScanState(
|
||||||
|
isScanning = false,
|
||||||
|
authenticationAction = AsyncAction.Failure(
|
||||||
|
UnauthorizedAccountProviderException(
|
||||||
|
unauthorisedAccountProviderTitle = "example.com",
|
||||||
|
authorisedAccountProviderTitles = listOf("element.io", "element.org"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
// Add other state here
|
// Add other state here
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.login.impl.R
|
import io.element.android.features.login.impl.R
|
||||||
|
import io.element.android.features.login.impl.changeserver.UnauthorizedAccountProviderException
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
||||||
import io.element.android.libraries.designsystem.components.BigIcon
|
import io.element.android.libraries.designsystem.components.BigIcon
|
||||||
|
|
@ -144,6 +145,12 @@ private fun ColumnScope.Buttons(
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
Text(
|
Text(
|
||||||
text = when (error) {
|
text = when (error) {
|
||||||
|
is UnauthorizedAccountProviderException -> {
|
||||||
|
stringResource(
|
||||||
|
id = R.string.screen_change_server_error_unauthorized_homeserver_title,
|
||||||
|
error.unauthorisedAccountProviderTitle,
|
||||||
|
)
|
||||||
|
}
|
||||||
is QrLoginException.OtherDeviceNotSignedIn -> {
|
is QrLoginException.OtherDeviceNotSignedIn -> {
|
||||||
stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_subtitle)
|
stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_subtitle)
|
||||||
}
|
}
|
||||||
|
|
@ -156,6 +163,12 @@ private fun ColumnScope.Buttons(
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = when (error) {
|
text = when (error) {
|
||||||
|
is UnauthorizedAccountProviderException -> {
|
||||||
|
stringResource(
|
||||||
|
id = R.string.screen_change_server_error_unauthorized_homeserver_content,
|
||||||
|
error.authorisedAccountProviderTitles.joinToString(),
|
||||||
|
)
|
||||||
|
}
|
||||||
is QrLoginException.OtherDeviceNotSignedIn -> {
|
is QrLoginException.OtherDeviceNotSignedIn -> {
|
||||||
stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_description)
|
stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_description)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@
|
||||||
<string name="screen_login_subtitle">"Matrix - гэта адкрытая сетка для бяспечнай, дэцэнтралізаванай сувязі."</string>
|
<string name="screen_login_subtitle">"Matrix - гэта адкрытая сетка для бяспечнай, дэцэнтралізаванай сувязі."</string>
|
||||||
<string name="screen_login_title">"Сардэчна запрашаем!"</string>
|
<string name="screen_login_title">"Сардэчна запрашаем!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Увайсці ў %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Увайсці ў %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Увайсці ўручную"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Увайсці з QR-кодам"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Стварыць уліковы запіс"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Сардэчна запрашаем у самы хуткі %1$s. Перавага ў хуткасці і прастаце."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Сардэчна запрашаем у %1$s. Зараджаны, для хуткасці і прастаты."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Будзьце ў сваім element"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Ўсталяванне бяспечнага злучэння"</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_description">"Не атрымалася ўсталяваць бяспечнае злучэнне з новай прыладай. Існуючыя прылады па-ранейшаму ў бяспецы, і вам не трэба турбавацца пра іх."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Што зараз?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Што зараз?"</string>
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,12 @@
|
||||||
<string name="screen_login_subtitle">"Matrix е отворена мрежа за сигурна, децентрализирана комуникация."</string>
|
<string name="screen_login_subtitle">"Matrix е отворена мрежа за сигурна, децентрализирана комуникация."</string>
|
||||||
<string name="screen_login_title">"Добре дошли отново!"</string>
|
<string name="screen_login_title">"Добре дошли отново!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Влизане в %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Влизане в %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Влизане ръчно"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Влизане с QR код"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Създаване на акаунт"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Добре дошли в най-бързия %1$s досега. Супер зареден за скорост и простота."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Добре дошли в %1$s. Супер зареден за скорост и простота."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Бъдете в стихията си"</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Повторен опит"</string>
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Повторен опит"</string>
|
||||||
<string name="screen_server_confirmation_change_server">"Промяна на доставчика на акаунт"</string>
|
<string name="screen_server_confirmation_change_server">"Промяна на доставчика на акаунт"</string>
|
||||||
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix е отворена мрежа за сигурна, децентрализирана комуникация."</string>
|
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix е отворена мрежа за сигурна, децентрализирана комуникация."</string>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@
|
||||||
<string name="screen_change_server_error_invalid_well_known">"Server není k dispozici kvůli problému se souborem well-known:
|
<string name="screen_change_server_error_invalid_well_known">"Server není k dispozici kvůli problému se souborem well-known:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"Vybraný poskytovatel účtu nepodporuje klouzavou synchronizaci. Pro použití %1$s je nutná aktualizace serveru."</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"Vybraný poskytovatel účtu nepodporuje klouzavou synchronizaci. Pro použití %1$s je nutná aktualizace serveru."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver">"Uživateli %1$s není dovoleno se připojit do %2$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver_content">"Tato aplikace byla nakonfigurována tak, aby umožňovala: %1$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver_title">"Poskytovatel účtu %1$s není povolen."</string>
|
||||||
<string name="screen_change_server_form_header">"Adresa URL domovského serveru"</string>
|
<string name="screen_change_server_form_header">"Adresa URL domovského serveru"</string>
|
||||||
<string name="screen_change_server_form_notice">"Zadejte adresu domény."</string>
|
<string name="screen_change_server_form_notice">"Zadejte adresu domény."</string>
|
||||||
<string name="screen_change_server_subtitle">"Jaká je adresa vašeho serveru?"</string>
|
<string name="screen_change_server_subtitle">"Jaká je adresa vašeho serveru?"</string>
|
||||||
|
|
@ -31,6 +34,13 @@
|
||||||
<string name="screen_login_subtitle">"Matrix je otevřená síť pro bezpečnou a decentralizovanou komunikaci."</string>
|
<string name="screen_login_subtitle">"Matrix je otevřená síť pro bezpečnou a decentralizovanou komunikaci."</string>
|
||||||
<string name="screen_login_title">"Vítejte zpět!"</string>
|
<string name="screen_login_title">"Vítejte zpět!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Přihlaste se k %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Přihlaste se k %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Ruční přihlášení"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_to">"Přihlásit se do %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Přihlásit se pomocí QR kódu"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Vytvořit účet"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Vítejte v dosud nejrychlejším %1$su. Vylepšený pro rychlost a jednoduchost."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Vítejte v %1$su. Vylepšený, pro rychlost a jednoduchost."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Buďte ve svém živlu"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Navazování zabezpečeného spojení"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"Navazování zabezpečeného spojení"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"K novému zařízení se nepodařilo navázat bezpečné připojení. Vaše stávající zařízení jsou stále v bezpečí a nemusíte se o ně obávat."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"K novému zařízení se nepodařilo navázat bezpečné připojení. Vaše stávající zařízení jsou stále v bezpečí a nemusíte se o ně obávat."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Co teď?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Co teď?"</string>
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,12 @@
|
||||||
<string name="screen_change_account_provider_subtitle">"Defnyddiwch ddarparwr cyfrif gwahanol, fel eich gweinydd preifat eich hun neu gyfrif gwaith."</string>
|
<string name="screen_change_account_provider_subtitle">"Defnyddiwch ddarparwr cyfrif gwahanol, fel eich gweinydd preifat eich hun neu gyfrif gwaith."</string>
|
||||||
<string name="screen_change_account_provider_title">"Newid darparwr cyfrif"</string>
|
<string name="screen_change_account_provider_title">"Newid darparwr cyfrif"</string>
|
||||||
<string name="screen_change_server_error_invalid_homeserver">"Doedd dim modd i ni gyrraedd y gweinydd cartref hwn. Gwiriwch eich bod wedi rhoi URL y gweinydd cartref yn gywir. Os yw\'r URL yn gywir, cysylltwch â gweinyddwr eich gweinydd cartref am ragor o help."</string>
|
<string name="screen_change_server_error_invalid_homeserver">"Doedd dim modd i ni gyrraedd y gweinydd cartref hwn. Gwiriwch eich bod wedi rhoi URL y gweinydd cartref yn gywir. Os yw\'r URL yn gywir, cysylltwch â gweinyddwr eich gweinydd cartref am ragor o help."</string>
|
||||||
<string name="screen_change_server_error_invalid_well_known">"Nid yw cydweddu llithrig ar gael oherwydd problem yn y ffeil adnabyddus:
|
<string name="screen_change_server_error_invalid_well_known">"Dyw cydweddu llithrig ddim ar gael oherwydd problem yn y ffeil .well-known:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"Dyw\'r darparwr cyfrif hwn ddim yn cefnogi cydweddu llithro. Mae angen uwchraddio\'r gweinydd i ddefnyddio %1$s."</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"Dyw\'r darparwr cyfrif hwn ddim yn cefnogi cydweddu llithro. Mae angen uwchraddio\'r gweinydd i ddefnyddio %1$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver">"Does dim caniatâd i %1$s gysylltu â %2$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver_content">"Mae\'r ap hwn wedi\'i ffurfweddu i ganiatáu: %1$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver_title">"Dyw darparwr cyfrif %1$s dddim yn cael ei ganiatáu."</string>
|
||||||
<string name="screen_change_server_form_header">"URL y Gweinydd Cartref"</string>
|
<string name="screen_change_server_form_header">"URL y Gweinydd Cartref"</string>
|
||||||
<string name="screen_change_server_form_notice">"Rhowch gyfeiriad parth."</string>
|
<string name="screen_change_server_form_notice">"Rhowch gyfeiriad parth."</string>
|
||||||
<string name="screen_change_server_subtitle">"Beth yw cyfeiriad eich gweinydd?"</string>
|
<string name="screen_change_server_subtitle">"Beth yw cyfeiriad eich gweinydd?"</string>
|
||||||
|
|
@ -31,6 +34,13 @@
|
||||||
<string name="screen_login_subtitle">"Mae Matrix yn rhwydwaith agored ar gyfer cyfathrebu diogel, datganoledig."</string>
|
<string name="screen_login_subtitle">"Mae Matrix yn rhwydwaith agored ar gyfer cyfathrebu diogel, datganoledig."</string>
|
||||||
<string name="screen_login_title">"Croeso nôl!"</string>
|
<string name="screen_login_title">"Croeso nôl!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Mewngofnodi i %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Mewngofnodi i %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Mewngofnodwch â llaw"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_to">"Mewngofnodi i %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Mewngofnodwch gyda chod QR"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Creu cyfrif"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Croeso i\'r %1$s cyflymaf erioed. Yn nodedig am gyflymder a symlrwydd."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Croeso i %1$s. Yn nodedig ar gyfer cyflymder a symlrwydd."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Byddwch yn eich elfen"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Yn creu cysylltiad diogel"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"Yn creu cysylltiad diogel"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Nid oedd modd gwneud cysylltiad diogel â\'r ddyfais newydd. Mae eich dyfeisiau presennol yn dal yn ddiogel a does dim angen i chi boeni amdanyn nhw."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Nid oedd modd gwneud cysylltiad diogel â\'r ddyfais newydd. Mae eich dyfeisiau presennol yn dal yn ddiogel a does dim angen i chi boeni amdanyn nhw."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Beth nawr?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Beth nawr?"</string>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
<string name="screen_change_server_error_invalid_well_known">"Der Server ist aufgrund eines Problems im \"well-known file\" nicht verfügbar:
|
<string name="screen_change_server_error_invalid_well_known">"Der Server ist aufgrund eines Problems im \"well-known file\" nicht verfügbar:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"Der gewählte Kontoanbieter unterstützt Sliding Sync nicht. Für die Verwendung von %1$s ist ein Upgrade des Servers erforderlich."</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"Der gewählte Kontoanbieter unterstützt Sliding Sync nicht. Für die Verwendung von %1$s ist ein Upgrade des Servers erforderlich."</string>
|
||||||
<string name="screen_change_server_error_unauthorized_homeserver">"%1$sdarf keine Verbindung herstellen zu%2$s."</string>
|
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s darf keine Verbindung herstellen zu %2$s."</string>
|
||||||
<string name="screen_change_server_form_header">"Homeserver-URL"</string>
|
<string name="screen_change_server_form_header">"Homeserver-URL"</string>
|
||||||
<string name="screen_change_server_form_notice">"Geben Sie eine Domainadresse ein."</string>
|
<string name="screen_change_server_form_notice">"Geben Sie eine Domainadresse ein."</string>
|
||||||
<string name="screen_change_server_subtitle">"Wie lautet die Adresse deines Servers?"</string>
|
<string name="screen_change_server_subtitle">"Wie lautet die Adresse deines Servers?"</string>
|
||||||
|
|
@ -32,6 +32,12 @@
|
||||||
<string name="screen_login_subtitle">"Matrix ist ein offenes Netzwerk für eine sichere, dezentrale Kommunikation."</string>
|
<string name="screen_login_subtitle">"Matrix ist ein offenes Netzwerk für eine sichere, dezentrale Kommunikation."</string>
|
||||||
<string name="screen_login_title">"Willkommen zurück!"</string>
|
<string name="screen_login_title">"Willkommen zurück!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Anmelden bei %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Anmelden bei %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Manuell anmelden"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Mit QR-Code anmelden"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Konto erstellen"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Willkommen beim schnellsten %1$s aller Zeiten. Optimiert für Geschwindigkeit und Einfachheit."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Willkommen zu %1$s. Aufgeladen, für Geschwindigkeit und Einfachheit."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Sei in Deinem Element"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Sichere Verbindung aufbauen"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"Sichere Verbindung aufbauen"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Es konnte keine sichere Verbindung zu dem neuen Gerät hergestellt werden."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Es konnte keine sichere Verbindung zu dem neuen Gerät hergestellt werden."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Und jetzt?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Und jetzt?"</string>
|
||||||
|
|
@ -54,7 +60,7 @@
|
||||||
Versuche, dich manuell anzumelden, oder scanne den QR-Code mit einem anderen Gerät."</string>
|
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_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_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_error_sliding_sync_not_supported_title">"%1$s wird nicht unterstützt"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_button_title">"Bereit zum Scannen"</string>
|
<string name="screen_qr_code_login_initial_state_button_title">"Bereit zum Scannen"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_1">"%1$s auf einem Desktop-Gerät öffnen"</string>
|
<string name="screen_qr_code_login_initial_state_item_1">"%1$s auf einem Desktop-Gerät öffnen"</string>
|
||||||
<string name="screen_qr_code_login_initial_state_item_2">"Klick auf deinen Avatar"</string>
|
<string name="screen_qr_code_login_initial_state_item_2">"Klick auf deinen Avatar"</string>
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@
|
||||||
<string name="screen_login_subtitle">"Το Matrix είναι ένα ανοιχτό δίκτυο για ασφαλή, αποκεντρωμένη επικοινωνία."</string>
|
<string name="screen_login_subtitle">"Το Matrix είναι ένα ανοιχτό δίκτυο για ασφαλή, αποκεντρωμένη επικοινωνία."</string>
|
||||||
<string name="screen_login_title">"Καλωσόρισες ξανά!"</string>
|
<string name="screen_login_title">"Καλωσόρισες ξανά!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Συνδέσου στο %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Συνδέσου στο %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Σύνδεση χειροκίνητα"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Συνδέσου με κωδικό QR"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Δημιουργία λογαριασμού"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Καλώς ήλθατε στο γρηγορότερο %1$s όλων των εποχών. Υπερτροφοδοτούμενο με ταχύτητα και απλότητα."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Καλώς ήρθες στο %1$s. Υπερφορτισμένο, για ταχύτητα και απλότητα."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Μείνε στο element σου"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Εγκαθίδρυση ασφαλούς σύνδεσης"</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_description">"Δεν ήταν δυνατή η πραγματοποίηση ασφαλούς σύνδεσης στη νέα συσκευή. Οι υπάρχουσες συσκευές σας εξακολουθούν να είναι ασφαλείς και δεν χρειάζεται να ανησυχείς για αυτές."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Τί είναι πάλι;"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Τί είναι πάλι;"</string>
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@
|
||||||
<string name="screen_login_subtitle">"Matrix es una red abierta para una comunicación segura y descentralizada."</string>
|
<string name="screen_login_subtitle">"Matrix es una red abierta para una comunicación segura y descentralizada."</string>
|
||||||
<string name="screen_login_title">"¡Hola de nuevo!"</string>
|
<string name="screen_login_title">"¡Hola de nuevo!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Iniciar sesión en %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Iniciar sesión en %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Iniciar sesión manualmente"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Iniciar sesión con un código QR"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Crear cuenta"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Bienvenido al %1$s más rápido de todos los tiempos. Diseñado para la velocidad y la simplicidad."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Bienvenido a %1$s. Vitaminado, para mayor rapidez y sencillez."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Siéntete en tu Elemento"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Estableciendo una conexión segura"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"Estableciendo una conexión segura"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"No se pudo establecer una conexión segura con el nuevo dispositivo. Tus dispositivos actuales siguen siendo seguros y no tienes que preocuparte por ellos."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"No se pudo establecer una conexión segura con el nuevo dispositivo. Tus dispositivos actuales siguen siendo seguros y no tienes que preocuparte por ellos."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"¿Y ahora qué?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"¿Y ahora qué?"</string>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"Valitud teenusepakkuja ei toeta „sliding sync“ režiimi. Rakenduse %1$s kasutamiseks on vaja serverit uuendada."</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"Valitud teenusepakkuja ei toeta „sliding sync“ režiimi. Rakenduse %1$s kasutamiseks on vaja serverit uuendada."</string>
|
||||||
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s ei saa kasutada %2$s koduserverit."</string>
|
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s ei saa kasutada %2$s koduserverit."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver_content">"See rakendus on seadistatud järgneva koduserveri kasutamiseks: %1$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver_title">"%1$s teenusepakkuja pole lubatud."</string>
|
||||||
<string name="screen_change_server_form_header">"Koduserveri url"</string>
|
<string name="screen_change_server_form_header">"Koduserveri url"</string>
|
||||||
<string name="screen_change_server_form_notice">"Sisesta domeeni aadress."</string>
|
<string name="screen_change_server_form_notice">"Sisesta domeeni aadress."</string>
|
||||||
<string name="screen_change_server_subtitle">"Mis on sinu koduserveri aadress?"</string>
|
<string name="screen_change_server_subtitle">"Mis on sinu koduserveri aadress?"</string>
|
||||||
|
|
@ -32,6 +34,13 @@
|
||||||
<string name="screen_login_subtitle">"Matrix on avatud võrk turvalise ja hajutatud suhtluse jaoks."</string>
|
<string name="screen_login_subtitle">"Matrix on avatud võrk turvalise ja hajutatud suhtluse jaoks."</string>
|
||||||
<string name="screen_login_title">"Tere tulemast tagasi!"</string>
|
<string name="screen_login_title">"Tere tulemast tagasi!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Logi sisse serverisse %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Logi sisse serverisse %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Logi sisse käsitsi"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_to">"Logi sisse teenusesse %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Logi sisse QR-koodi alusel"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Loo kasutajakonto"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Läbi aegade kiireim ja mugavaim %1$s."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Tere tulemast kasutama kiiret ja lihtsat suhtlusrakendust %1$s."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Ole oma elemendis"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Loome turvalist ühendust"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"Loome turvalist ühendust"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Turvalise ühenduse loomine uue seadmega ei õnnestunud. Sinu olemasolevad seadmed on jätkuvalt turvatud ja sa ei pea nende pärast muretsema."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Turvalise ühenduse loomine uue seadmega ei õnnestunud. Sinu olemasolevad seadmed on jätkuvalt turvatud ja sa ei pea nende pärast muretsema."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Mida järgmiseks teeme?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Mida järgmiseks teeme?"</string>
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,12 @@
|
||||||
<string name="screen_login_subtitle">"Matrix komunikazio seguru eta deszentralizaturako sare irekia da."</string>
|
<string name="screen_login_subtitle">"Matrix komunikazio seguru eta deszentralizaturako sare irekia da."</string>
|
||||||
<string name="screen_login_title">"Ongi etorri!"</string>
|
<string name="screen_login_title">"Ongi etorri!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Hasi saioa %1$s(e)n"</string>
|
<string name="screen_login_title_with_homeserver">"Hasi saioa %1$s(e)n"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Hasi saioa eskuz"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Hasi saioa QR kodearekin"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Sortu kontua"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Ongi etorri inoizko %1$s azkarrenera. Abiaduraz eta sinpletasunaz gainkargatua."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Ongi etorri %1$s-ra. Abiaduraz eta sinpletasunez gainezka."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Egon zure saltsan"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Konexio segurua ezartzen"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"Konexio segurua ezartzen"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Ezin izan da konexio segururik ezarri gailu berriarekin. Lehendik dauden gailuak seguru daude oraindik ere eta ez duzu haietaz kezkatu beharrik."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Ezin izan da konexio segururik ezarri gailu berriarekin. Lehendik dauden gailuak seguru daude oraindik ere eta ez duzu haietaz kezkatu beharrik."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Orain zer?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Orain zer?"</string>
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,12 @@
|
||||||
<string name="screen_login_subtitle">"ماتریکس شبکهای بار برای ارتباطات نامتمرکز و امن است."</string>
|
<string name="screen_login_subtitle">"ماتریکس شبکهای بار برای ارتباطات نامتمرکز و امن است."</string>
|
||||||
<string name="screen_login_title">"خوش برگشتید!"</string>
|
<string name="screen_login_title">"خوش برگشتید!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"ورود به %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"ورود به %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"ورود دستی"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"ورود با کد QR"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"ایجاد حساب"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"به سریعترین %1$s خوش آمدید. بازطرّاحی شده برای سرعت و سادگی."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"به %1$s خوش آمدید. بازطرّاحی شده برای سرعت و سادگی."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"در المنتتان باشید"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"برقرار کدن اتّصالی امن"</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_description">"نتوانست اتّصالی امن به افزارهٔ جدید بسازد. افزارههای موجودتان هنوز امنند و نیازی نیست نگرانشان باشید."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"اکنون چه؟"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"اکنون چه؟"</string>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@
|
||||||
<string name="screen_change_server_error_invalid_well_known">"Sliding sync ei ole saatavilla well-known tiedostossa olevan ongelman vuoksi:
|
<string name="screen_change_server_error_invalid_well_known">"Sliding sync ei ole saatavilla well-known tiedostossa olevan ongelman vuoksi:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"Valitsemasi palveluntarjoaja ei tue sliding syncia. Palvelimen päivitys tarvitaan %1$s -sovelluksen käyttämiseen."</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"Valitsemasi palveluntarjoaja ei tue sliding syncia. Palvelimen päivitys tarvitaan %1$s -sovelluksen käyttämiseen."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s ei saa yhdistää %2$s -palvelimeen."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver_content">"Tämä sovellus on määritetty sallimaan: %1$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver_title">"Palveluntarjoaja %1$s ei ole sallittu."</string>
|
||||||
<string name="screen_change_server_form_header">"Kotipalvelimen osoite"</string>
|
<string name="screen_change_server_form_header">"Kotipalvelimen osoite"</string>
|
||||||
<string name="screen_change_server_form_notice">"Anna verkkotunnuksen osoite."</string>
|
<string name="screen_change_server_form_notice">"Anna verkkotunnuksen osoite."</string>
|
||||||
<string name="screen_change_server_subtitle">"Mikä on palvelimesi osoite?"</string>
|
<string name="screen_change_server_subtitle">"Mikä on palvelimesi osoite?"</string>
|
||||||
|
|
@ -31,6 +34,13 @@
|
||||||
<string name="screen_login_subtitle">"Matrix on avoin verkko turvallista, hajautettua viestintää varten."</string>
|
<string name="screen_login_subtitle">"Matrix on avoin verkko turvallista, hajautettua viestintää varten."</string>
|
||||||
<string name="screen_login_title">"Tervetuloa takaisin!"</string>
|
<string name="screen_login_title">"Tervetuloa takaisin!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Kirjaudu sisään %1$s -palvelimelle"</string>
|
<string name="screen_login_title_with_homeserver">"Kirjaudu sisään %1$s -palvelimelle"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Kirjaudu sisään manuaalisesti"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_to">"Kirjaudu sisään %1$s -palvelimelle"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Kirjaudu sisään QR-koodilla"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Luo tili"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Tervetuloa kaikkien aikojen nopeimpaan %1$s -sovellukseen. Ahdettu nopeudella ja yksinkertaisuudella."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Tervetuloa %1$s -sovellukseen. Ahdettu nopeudella ja yksinkertaisuudella."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Ole elementissäsi"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Muodostetaan turvallista yhteyttä"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"Muodostetaan turvallista yhteyttä"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Turvallista yhteyttä uuteen laitteeseen ei voitu muodostaa. Olemassa olevat laitteesi ovat edelleen turvassa, eikä sinun tarvitse huolehtia niistä."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Turvallista yhteyttä uuteen laitteeseen ei voitu muodostaa. Olemassa olevat laitteesi ovat edelleen turvassa, eikä sinun tarvitse huolehtia niistä."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Mitä nyt?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Mitä nyt?"</string>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@
|
||||||
<string name="screen_change_server_error_invalid_well_known">"Ce fournisseur de compte n’est pas disponible en raison d’un problème dans le fichier .well-known:
|
<string name="screen_change_server_error_invalid_well_known">"Ce fournisseur de compte n’est pas disponible en raison d’un problème dans le fichier .well-known:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"Le fournisseur de compte sélectionné ne prend pas en charge le sliding sync. Une mise à jour du serveur est nécessaire pour pouvoir utiliser %1$s."</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"Le fournisseur de compte sélectionné ne prend pas en charge le sliding sync. Une mise à jour du serveur est nécessaire pour pouvoir utiliser %1$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s n’est pas autorisé à se connecter à %2$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver_content">"Cette application a été configurée pour autoriser: %1$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver_title">"Le fournisseur de compte %1$s n’est pas autorisé."</string>
|
||||||
<string name="screen_change_server_form_header">"URL du serveur d’accueil"</string>
|
<string name="screen_change_server_form_header">"URL du serveur d’accueil"</string>
|
||||||
<string name="screen_change_server_form_notice">"Saisissez une adresse de domaine."</string>
|
<string name="screen_change_server_form_notice">"Saisissez une adresse de domaine."</string>
|
||||||
<string name="screen_change_server_subtitle">"Quelle est l’adresse de votre serveur ?"</string>
|
<string name="screen_change_server_subtitle">"Quelle est l’adresse de votre serveur ?"</string>
|
||||||
|
|
@ -31,6 +34,13 @@
|
||||||
<string name="screen_login_subtitle">"Matrix est un réseau ouvert pour une communication sécurisée et décentralisée."</string>
|
<string name="screen_login_subtitle">"Matrix est un réseau ouvert pour une communication sécurisée et décentralisée."</string>
|
||||||
<string name="screen_login_title">"Content de vous revoir !"</string>
|
<string name="screen_login_title">"Content de vous revoir !"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Connectez-vous à %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Connectez-vous à %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Se connecter manuellement"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_to">"Se connecter à %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Se connecter avec un QR code"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Créer un compte"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Bienvenue dans l’application %1$s la plus rapide de tous les temps. Boosté pour plus de rapidité et de simplicité."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Bienvenue sur %1$s. Boosté, pour plus de rapidité et de simplicité."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Soyez dans votre Element"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Établissement d’une connexion sécurisée"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"Établissement d’une connexion sécurisée"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Aucune connexion sécurisée n’a pu être établie avec la nouvelle session. Vos sessions existantes sont toujours en sécurité et vous n’avez pas à vous en soucier."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Aucune connexion sécurisée n’a pu être établie avec la nouvelle session. Vos sessions existantes sont toujours en sécurité et vous n’avez pas à vous en soucier."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Et maintenant ?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Et maintenant ?"</string>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@
|
||||||
<string name="screen_change_server_error_invalid_well_known">"A kiszolgáló a well-known fájl problémája miatt nem érhető el:
|
<string name="screen_change_server_error_invalid_well_known">"A kiszolgáló a well-known fájl problémája miatt nem érhető el:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"A kiválasztott fiókszolgáltató nem támogatja a csúszóablakos szinkronizálást. Az %1$s használatához kiszolgálófrissítés szükséges."</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"A kiválasztott fiókszolgáltató nem támogatja a csúszóablakos szinkronizálást. Az %1$s használatához kiszolgálófrissítés szükséges."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s nem csatlakozhat ide: %2$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver_content">"Ezt az alkalmazást úgy konfigurálták, hogy engedélyezi ezt: %1$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver_title">"A(z) %1$s fiókszolgáltató nem engedélyezett."</string>
|
||||||
<string name="screen_change_server_form_header">"Matrix-kiszolgáló webcíme"</string>
|
<string name="screen_change_server_form_header">"Matrix-kiszolgáló webcíme"</string>
|
||||||
<string name="screen_change_server_form_notice">"Adjon meg egy domaincímet."</string>
|
<string name="screen_change_server_form_notice">"Adjon meg egy domaincímet."</string>
|
||||||
<string name="screen_change_server_subtitle">"Mi a kiszolgálója címe?"</string>
|
<string name="screen_change_server_subtitle">"Mi a kiszolgálója címe?"</string>
|
||||||
|
|
@ -31,6 +34,13 @@
|
||||||
<string name="screen_login_subtitle">"A Matrix egy nyitott hálózat a biztonságos, decentralizált kommunikációhoz."</string>
|
<string name="screen_login_subtitle">"A Matrix egy nyitott hálózat a biztonságos, decentralizált kommunikációhoz."</string>
|
||||||
<string name="screen_login_title">"Örülünk, hogy visszatért!"</string>
|
<string name="screen_login_title">"Örülünk, hogy visszatért!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Bejelentkezés ide: %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Bejelentkezés ide: %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Kézi bejelentkezés"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_to">"Bejelentkezés ide: %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Bejelentkezés QR-kóddal"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Fiók létrehozása"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Üdvözöljük a valaha volt leggyorsabb %1$sben. Felturbózva, a sebesség és az egyszerűség érdekében."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Üdvözli az %1$s. Felturbózva, a sebesség és az egyszerűség jegyében."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Legyen elemében"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Biztonságos kapcsolat létesítése"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"Biztonságos kapcsolat létesítése"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Nem sikerült biztonságos kapcsolatot létesíteni az új eszközzel. A meglévő eszközei továbbra is biztonságban vannak, és nem kell aggódnia miattuk."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Nem sikerült biztonságos kapcsolatot létesíteni az új eszközzel. A meglévő eszközei továbbra is biztonságban vannak, és nem kell aggódnia miattuk."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Most mi lesz?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Most mi lesz?"</string>
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@
|
||||||
<string name="screen_login_subtitle">"Matrix adalah jaringan terbuka untuk komunikasi yang aman dan terdesentralisasi."</string>
|
<string name="screen_login_subtitle">"Matrix adalah jaringan terbuka untuk komunikasi yang aman dan terdesentralisasi."</string>
|
||||||
<string name="screen_login_title">"Selamat datang kembali!"</string>
|
<string name="screen_login_title">"Selamat datang kembali!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Masuk ke %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Masuk ke %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Masuk secara manual"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Masuk dengan kode QR"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Buat akun"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Selamat datang di %1$s tercepat yang pernah ada. Berdaya besar untuk kecepatan dan kesederhanaan."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Selamat datang di %1$s. Berdaya penuh, untuk kecepatan dan kesederhanaan."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Berada di elemen Anda"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Membuat koneksi aman"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"Membuat koneksi aman"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Koneksi aman tidak dapat dibuat ke perangkat baru. Perangkat Anda yang ada masih aman dan Anda tidak perlu khawatir tentang mereka."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Koneksi aman tidak dapat dibuat ke perangkat baru. Perangkat Anda yang ada masih aman dan Anda tidak perlu khawatir tentang mereka."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Apa sekarang?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Apa sekarang?"</string>
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@
|
||||||
<string name="screen_login_subtitle">"Matrix è una rete aperta per comunicazioni sicure e decentralizzate."</string>
|
<string name="screen_login_subtitle">"Matrix è una rete aperta per comunicazioni sicure e decentralizzate."</string>
|
||||||
<string name="screen_login_title">"Bentornato!"</string>
|
<string name="screen_login_title">"Bentornato!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Accedi a %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Accedi a %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Accedi manualmente"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Accedi con codice QR"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Crea account"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Benvenuti nell\'%1$s più veloce di sempre. Potenziato per velocità e semplicità."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Benvenuto su %1$s. Potenziato in velocità e semplicità."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Sii nel tuo elemento"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Stabilendo la connessione"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"Stabilendo la connessione"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Non è stato possibile stabilire una connessione sicura con il nuovo dispositivo. I tuoi dispositivi esistenti sono ancora al sicuro e non devi preoccuparti di loro."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Non è stato possibile stabilire una connessione sicura con il nuovo dispositivo. I tuoi dispositivi esistenti sono ancora al sicuro e non devi preoccuparti di loro."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"E adesso?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"E adesso?"</string>
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,12 @@
|
||||||
<string name="screen_login_subtitle">"Matrix არის ღია ქსელი უსაფრთხო, დეცენტრალიზებული კომუნიკაციისთვის."</string>
|
<string name="screen_login_subtitle">"Matrix არის ღია ქსელი უსაფრთხო, დეცენტრალიზებული კომუნიკაციისთვის."</string>
|
||||||
<string name="screen_login_title">"კეთილი იყოს თქვენი მობრძანება!"</string>
|
<string name="screen_login_title">"კეთილი იყოს თქვენი მობრძანება!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"შესვლა %1$s-ში"</string>
|
<string name="screen_login_title_with_homeserver">"შესვლა %1$s-ში"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"ხელით შესვლა"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"შესვლა QR კოდით"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"ანგარიშის შექმნა"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"კეთილი იყოს თქვენი მობრძანება უსწრაფეს %1$s-ში. დამუხტულია სიჩქარისა და სიმარტივისათვის."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"კეთილი იყოს თქვენი მობრძანება %1$s-ში! დამუხტული სიჩქარისა და სიმარტივისთვის."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"იყავი შენს element-ში"</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"ხელახლა ცდა"</string>
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"ხელახლა ცდა"</string>
|
||||||
<string name="screen_server_confirmation_change_server">"შეცვალეთ ანგარიშის მომწოდებელი"</string>
|
<string name="screen_server_confirmation_change_server">"შეცვალეთ ანგარიშის მომწოდებელი"</string>
|
||||||
<string name="screen_server_confirmation_message_login_element_dot_io">"კერძო სერვერი Element-ის თანამშრომლებისთვის."</string>
|
<string name="screen_server_confirmation_message_login_element_dot_io">"კერძო სერვერი Element-ის თანამშრომლებისთვის."</string>
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,11 @@
|
||||||
<string name="screen_login_subtitle">"Matrix yra atviras tinklas, skirtas saugiam, decentralizuotam bendravimui."</string>
|
<string name="screen_login_subtitle">"Matrix yra atviras tinklas, skirtas saugiam, decentralizuotam bendravimui."</string>
|
||||||
<string name="screen_login_title">"Sveiki sugrįžę!"</string>
|
<string name="screen_login_title">"Sveiki sugrįžę!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Prisijungti prie %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Prisijungti prie %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Prisijunkite rankiniu būdu"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Prisijunkite naudodami QR kodą"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Sukurti paskyrą"</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Sveiki atvykę į %1$s. Įkrautas greitumui ir paprastumui."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Būkite savo elemente"</string>
|
||||||
<string name="screen_server_confirmation_change_server">"Keisti paskyros teikėją"</string>
|
<string name="screen_server_confirmation_change_server">"Keisti paskyros teikėją"</string>
|
||||||
<string name="screen_server_confirmation_message_login_element_dot_io">"Privatus serveris “Element” darbuotojams."</string>
|
<string name="screen_server_confirmation_message_login_element_dot_io">"Privatus serveris “Element” darbuotojams."</string>
|
||||||
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix yra atviras tinklas, skirtas saugiam, decentralizuotam bendravimui."</string>
|
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix yra atviras tinklas, skirtas saugiam, decentralizuotam bendravimui."</string>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@
|
||||||
<string name="screen_change_server_error_invalid_well_known">"Serveren er ikke tilgjengelig på grunn av et problem i den velkjente filen:
|
<string name="screen_change_server_error_invalid_well_known">"Serveren er ikke tilgjengelig på grunn av et problem i den velkjente filen:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"Den valgte kontoleverandøren støtter ikke sliding sync. En oppgradering av serveren er nødvendig for å bruke %1$s."</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"Den valgte kontoleverandøren støtter ikke sliding sync. En oppgradering av serveren er nødvendig for å bruke %1$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s har ikke lov til å koble seg til %2$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver_content">"Denne appen er konfigurert til å tillate: %1$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver_title">"Kontoleverandør %1$s er ikke tillatt."</string>
|
||||||
<string name="screen_change_server_form_header">"URL til hjemmeserver"</string>
|
<string name="screen_change_server_form_header">"URL til hjemmeserver"</string>
|
||||||
<string name="screen_change_server_form_notice">"Skriv inn en domeneadresse."</string>
|
<string name="screen_change_server_form_notice">"Skriv inn en domeneadresse."</string>
|
||||||
<string name="screen_change_server_subtitle">"Hva er adressen til serveren din?"</string>
|
<string name="screen_change_server_subtitle">"Hva er adressen til serveren din?"</string>
|
||||||
|
|
@ -31,6 +34,13 @@
|
||||||
<string name="screen_login_subtitle">"Matrix er et åpent nettverk for sikker, desentralisert kommunikasjon."</string>
|
<string name="screen_login_subtitle">"Matrix er et åpent nettverk for sikker, desentralisert kommunikasjon."</string>
|
||||||
<string name="screen_login_title">"Velkommen tilbake!"</string>
|
<string name="screen_login_title">"Velkommen tilbake!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Logg inn på %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Logg inn på %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Logg på manuelt"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_to">"Logg inn på %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Logg inn med QR-kode"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Opprett konto"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Velkommen til den raskeste %1$s noensinne. Superladet for hastighet og enkelhet."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Velkommen til %1$s. Supercharged, for hastighet og enkelhet."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Vær i ditt rette element"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Etablere en sikker forbindelse"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"Etablere en sikker forbindelse"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"En sikker tilkobling kunne ikke opprettes til den nye enheten. Dine eksisterende enheter er fortsatt trygge, og du trenger ikke å bekymre deg for dem."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"En sikker tilkobling kunne ikke opprettes til den nye enheten. Dine eksisterende enheter er fortsatt trygge, og du trenger ikke å bekymre deg for dem."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Hva nå?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Hva nå?"</string>
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@
|
||||||
<string name="screen_login_subtitle">"Matrix is een open netwerk voor veilige, gedecentraliseerde communicatie."</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">"Welkom terug!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Inloggen bij %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Inloggen bij %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Handmatig inloggen"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Inloggen met QR-code"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Account aanmaken"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Welkom bij de snelste %1$s ooit. Supercharged, voor snelheid en eenvoud."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Welkom bij %1$s. Supercharged, voor snelheid en eenvoud."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Wees in je element"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Een beveiligde verbinding tot stand brengen"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"Een beveiligde verbinding tot stand brengen"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Er kon geen beveiligde verbinding worden gemaakt met het nieuwe apparaat. Je bestaande apparaten zijn nog steeds veilig en je hoeft je daarover geen zorgen te maken."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Er kon geen beveiligde verbinding worden gemaakt met het nieuwe apparaat. Je bestaande apparaten zijn nog steeds veilig en je hoeft je daarover geen zorgen te maken."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Wat nu?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Wat nu?"</string>
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,13 @@
|
||||||
<string name="screen_login_subtitle">"Matrix to otwarta sieć do bezpiecznej i zdecentralizowanej komunikacji."</string>
|
<string name="screen_login_subtitle">"Matrix to otwarta sieć do bezpiecznej i zdecentralizowanej komunikacji."</string>
|
||||||
<string name="screen_login_title">"Witaj ponownie!"</string>
|
<string name="screen_login_title">"Witaj ponownie!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Zaloguj się do %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Zaloguj się do %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Zaloguj się ręcznie"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_to">"Zaloguj się do %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Zaloguj się za pomocą kodu QR"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Utwórz konto"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Witamy w %1$s. Szybszy i prostszy niż kiedykolwiek."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Witamy w %1$s. Doładowany, dla szybkości i prostoty."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Be in your element"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Nawiązanie bezpiecznego połączenia"</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_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_header">"Co teraz?"</string>
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,12 @@
|
||||||
<string name="screen_login_subtitle">"A Matrix é uma rede aberta para comunicação segura e descentralizada."</string>
|
<string name="screen_login_subtitle">"A Matrix é uma rede aberta para comunicação segura e descentralizada."</string>
|
||||||
<string name="screen_login_title">"Bem-vindo de volta!"</string>
|
<string name="screen_login_title">"Bem-vindo de volta!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Iniciar sessão em %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Iniciar sessão em %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Iniciar sessão manualmente"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Iniciar sessão com código QR"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Criar conta"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Bem-vindo ao mais rápido %1$s de todos os tempos. Turbinado para velocidade e simplicidade."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Bem-vindo ao %1$s. Turbinado, para velocidade e simplicidade"</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Esteja no seu elemento"</string>
|
||||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Tente novamente"</string>
|
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Tente novamente"</string>
|
||||||
<string name="screen_qr_code_login_no_camera_permission_state_description">"Você deve permitir ao %1$s usar a câmera do seu dispositivo para continuar."</string>
|
<string name="screen_qr_code_login_no_camera_permission_state_description">"Você deve permitir ao %1$s usar a câmera do seu dispositivo para continuar."</string>
|
||||||
<string name="screen_qr_code_login_verify_code_title">"Seu código de verificação"</string>
|
<string name="screen_qr_code_login_verify_code_title">"Seu código de verificação"</string>
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@
|
||||||
<string name="screen_login_subtitle">"A Matrix é uma rede aberta de comunicação descentralizada e segura."</string>
|
<string name="screen_login_subtitle">"A Matrix é uma rede aberta de comunicação descentralizada e segura."</string>
|
||||||
<string name="screen_login_title">"Bem-vindo(a) de volta!"</string>
|
<string name="screen_login_title">"Bem-vindo(a) de volta!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Iniciar sessão em %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Iniciar sessão em %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Iniciar sessão manualmente"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Iniciar sessão com código QR"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Criar conta"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Bem-vindo(a) à %1$s mais rápida de sempre. Super rápida e simples."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Bem-vindo(a) à %1$s. Revitalizado, rápido e simples."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"A liberdade do teu elemento"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"A estabelecer uma ligação segura"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"A estabelecer uma ligação segura"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Não foi possível estabelecer uma ligação segura com o novo dispositivo. Os teus outros dispositivos continuam seguros, não precisas de te preocupar com eles."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Não foi possível estabelecer uma ligação segura com o novo dispositivo. Os teus outros dispositivos continuam seguros, não precisas de te preocupar com eles."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"E agora?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"E agora?"</string>
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@
|
||||||
<string name="screen_login_subtitle">"Matrix este o rețea deschisă pentru o comunicare sigură și descentralizată."</string>
|
<string name="screen_login_subtitle">"Matrix este o rețea deschisă pentru o comunicare sigură și descentralizată."</string>
|
||||||
<string name="screen_login_title">"Bine ați revenit!"</string>
|
<string name="screen_login_title">"Bine ați revenit!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Conectați-vă la %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Conectați-vă la %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Conectați-vă manual"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Conectați-vă cu un cod QR"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Creați un cont"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Bine ați venit la cel mai rapid %1$s din toate timpurile. Supraalimentat pentru viteză și simplitate."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Bun venit în %1$s. Supraalimentat, pentru viteză și simplitate."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Fii în Elementul tău"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Se stabilește o conexiune securizată"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"Se stabilește o conexiune securizată"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Nu a putut fi făcută o conexiune sigură la noul dispozitiv. Dispozitivele existente sunt încă în siguranță și nu trebuie să vă faceți griji cu privire la ele."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Nu a putut fi făcută o conexiune sigură la noul dispozitiv. Dispozitivele existente sunt încă în siguranță și nu trebuie să vă faceți griji cu privire la ele."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Și acum?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Și acum?"</string>
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,12 @@
|
||||||
<string name="screen_login_subtitle">"Matrix — это открытая сеть для безопасной децентрализованной связи."</string>
|
<string name="screen_login_subtitle">"Matrix — это открытая сеть для безопасной децентрализованной связи."</string>
|
||||||
<string name="screen_login_title">"Рады видеть вас снова!"</string>
|
<string name="screen_login_title">"Рады видеть вас снова!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Войти в %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Войти в %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Войти вручную"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Войти QR-кодом"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Создать учетную запись"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Добро пожаловать в самый быстрый клиент %1$s. Ориентирован на скорость и простоту."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Добро пожаловать в %1$s. Ориентирован на скорость и простоту."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Чувствуйте себя как дома с Element"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Установление безопасного соединения"</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_description">"Не удалось установить безопасное соединение с новым устройством. Существующие устройства по-прежнему в безопасности, и вам не нужно беспокоиться о них."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Что теперь?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Что теперь?"</string>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"Vybraný poskytovateľ účtu nepodporuje kĺzavú synchronizáciu. Na používanie aplikácie %1$s je potrebná aktualizácia servera,"</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"Vybraný poskytovateľ účtu nepodporuje kĺzavú synchronizáciu. Na používanie aplikácie %1$s je potrebná aktualizácia servera,"</string>
|
||||||
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s nemá dovolené pripojiť sa k %2$s."</string>
|
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s nemá dovolené pripojiť sa k %2$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver_content">"Táto aplikácia bola nastavená tak, aby povoľovala: %1$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver_title">"Poskytovateľ účtu %1$s nie je povolený."</string>
|
||||||
<string name="screen_change_server_form_header">"Adresa URL domovského servera"</string>
|
<string name="screen_change_server_form_header">"Adresa URL domovského servera"</string>
|
||||||
<string name="screen_change_server_form_notice">"Zadajte adresu domény."</string>
|
<string name="screen_change_server_form_notice">"Zadajte adresu domény."</string>
|
||||||
<string name="screen_change_server_subtitle">"Aká je adresa vášho servera?"</string>
|
<string name="screen_change_server_subtitle">"Aká je adresa vášho servera?"</string>
|
||||||
|
|
@ -32,6 +34,13 @@
|
||||||
<string name="screen_login_subtitle">"Matrix je otvorená sieť pre bezpečnú a decentralizovanú komunikáciu."</string>
|
<string name="screen_login_subtitle">"Matrix je otvorená sieť pre bezpečnú a decentralizovanú komunikáciu."</string>
|
||||||
<string name="screen_login_title">"Vitajte späť!"</string>
|
<string name="screen_login_title">"Vitajte späť!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Prihlásiť sa do %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Prihlásiť sa do %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Prihlásiť sa manuálne"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_to">"Prihlásiť sa do %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Prihlásiť sa pomocou QR kódu"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Vytvoriť účet"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Vitajte v najrýchlejšom %1$s vôbec. Nadupaný pre rýchlosť a jednoduchosť."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Vitajte v %1$s. Nadupaný, pre rýchlosť a jednoduchosť."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Buďte vo svojom elemente"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Nadväzovanie bezpečného spojenia"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"Nadväzovanie bezpečného spojenia"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"K novému zariadeniu sa nepodarilo vytvoriť bezpečné pripojenie. Vaše existujúce zariadenia sú stále v bezpečí a nemusíte sa o ne obávať."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"K novému zariadeniu sa nepodarilo vytvoriť bezpečné pripojenie. Vaše existujúce zariadenia sú stále v bezpečí a nemusíte sa o ne obávať."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Čo teraz?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Čo teraz?"</string>
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@
|
||||||
<string name="screen_login_subtitle">"Matrix är ett öppet nätverk för säker, decentraliserad kommunikation."</string>
|
<string name="screen_login_subtitle">"Matrix är ett öppet nätverk för säker, decentraliserad kommunikation."</string>
|
||||||
<string name="screen_login_title">"Välkommen tillbaka!"</string>
|
<string name="screen_login_title">"Välkommen tillbaka!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Logga in på %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Logga in på %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Logga in manuellt"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Logga in med QR-kod"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Skapa konto"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Välkommen till den snabbaste %1$s någonsin. Superladdad för snabbhet och enkelhet."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Välkommen till %1$s. Superladdad, för snabbhet och enkelhet."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Var i ditt rätta element"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Upprättar en säker anslutning"</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_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_header">"Nu då?"</string>
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@
|
||||||
<string name="screen_login_subtitle">"Matrix, güvenli, merkezi olmayan iletişim için açık bir ağdır."</string>
|
<string name="screen_login_subtitle">"Matrix, güvenli, merkezi olmayan iletişim için açık bir ağdır."</string>
|
||||||
<string name="screen_login_title">"Tekrar hoş geldiniz!"</string>
|
<string name="screen_login_title">"Tekrar hoş geldiniz!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"%1$s adresinde oturum aç"</string>
|
<string name="screen_login_title_with_homeserver">"%1$s adresinde oturum aç"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Manuel olarak oturum aç"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"QR kodu ile giriş yap"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Hesap oluştur"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Şimdiye kadarki en hızlı %1$s hoş geldiniz. Hız ve basitlik için güçlendirildi."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"%1$s\'e hoş geldiniz. Hız ve basitlik için süper şarjlı."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Kendi elementinizde olun"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Güvenli bir bağlantı kuruluyor"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"Güvenli bir bağlantı kuruluyor"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Yeni cihaza güvenli bir bağlantı kurulamadı. Mevcut cihazlarınız hala güvende ve onlar için endişelenmenize gerek yok."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Yeni cihaza güvenli bir bağlantı kurulamadı. Mevcut cihazlarınız hala güvende ve onlar için endişelenmenize gerek yok."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Şimdi ne olacak?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Şimdi ne olacak?"</string>
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,10 @@
|
||||||
<string name="screen_change_account_provider_subtitle">"Використати іншого провайдера облікових записів, наприклад, власний приватний сервер або робочий обліковий запис."</string>
|
<string name="screen_change_account_provider_subtitle">"Використати іншого провайдера облікових записів, наприклад, власний приватний сервер або робочий обліковий запис."</string>
|
||||||
<string name="screen_change_account_provider_title">"Змінити провайдера облікового запису"</string>
|
<string name="screen_change_account_provider_title">"Змінити провайдера облікового запису"</string>
|
||||||
<string name="screen_change_server_error_invalid_homeserver">"Не вдалося під\'єднатися до цього домашнього сервера. Перевірте правильність введеної URL-адреси домашнього сервера. Якщо URL-адреса правильна, зверніться по додаткову допомогу до адміністратора домашнього сервера."</string>
|
<string name="screen_change_server_error_invalid_homeserver">"Не вдалося під\'єднатися до цього домашнього сервера. Перевірте правильність введеної URL-адреси домашнього сервера. Якщо URL-адреса правильна, зверніться по додаткову допомогу до адміністратора домашнього сервера."</string>
|
||||||
<string name="screen_change_server_error_invalid_well_known">"Sliding sync недоступний через проблему у файлі well-known:
|
<string name="screen_change_server_error_invalid_well_known">"Сервер недоступний через помилку у файлі well-known:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_form_header">"URL-адреса домашнього сервера"</string>
|
<string name="screen_change_server_form_header">"URL-адреса домашнього сервера"</string>
|
||||||
|
<string name="screen_change_server_form_notice">"Введіть адресу домену."</string>
|
||||||
<string name="screen_change_server_subtitle">"Яка адреса вашого сервера?"</string>
|
<string name="screen_change_server_subtitle">"Яка адреса вашого сервера?"</string>
|
||||||
<string name="screen_change_server_title">"Виберіть свій сервер"</string>
|
<string name="screen_change_server_title">"Виберіть свій сервер"</string>
|
||||||
<string name="screen_create_account_title">"Створити обліковий запис"</string>
|
<string name="screen_create_account_title">"Створити обліковий запис"</string>
|
||||||
|
|
@ -29,6 +30,13 @@
|
||||||
<string name="screen_login_subtitle">"Matrix — це відкрита мережа для безпечної, децентралізованої комунікації."</string>
|
<string name="screen_login_subtitle">"Matrix — це відкрита мережа для безпечної, децентралізованої комунікації."</string>
|
||||||
<string name="screen_login_title">"З поверненням!"</string>
|
<string name="screen_login_title">"З поверненням!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Увійти в %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Увійти в %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Увійти вручну"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_to">"Увійти в %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Увійти за допомогою QR-коду"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Створити обліковий запис"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Ласкаво просимо до найшвидшого %1$s. Заряджений для швидкості та простоти."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Ласкаво просимо до %1$s. Заряджений, для швидкості та простоти."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Будьте у своєму element"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Встановлення безпечного з\'єднання"</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_description">"Не вдалося встановити безпечне з\'єднання з новим пристроєм. Ваші наявні пристрої досі в безпеці, і вам не потрібно про них турбуватися."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Що тепер?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Що тепер?"</string>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,12 @@
|
||||||
<string name="screen_login_subtitle">"Matrix xavfsiz, markazlashmagan aloqa uchun ochiq tarmoqdir."</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">"Qaytib kelganingizdan xursandmiz!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Kirish%1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Kirish%1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Qo\'lda tizimga kiring"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"QR kod bilan tizimga kiring"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Hisob yaratish"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Eng tezkor %1$sga xush kelibsiz. Tezlik va oddylik uchun super zaryadlangan."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"%1$sga Xush kelibsiz. Tezlik va oddylik uchun o\'ta zaryadlangan."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Elementingizda bo\'ling"</string>
|
||||||
<string name="screen_server_confirmation_change_server">"Hisob provayderini o\'zgartiring"</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_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_login_matrix_dot_org">"Matrix xavfsiz, markazlashmagan aloqa uchun ochiq tarmoqdir."</string>
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@
|
||||||
<string name="screen_login_subtitle">"Matrix 是一個開放網路,為了安全且去中心化的通訊而生。"</string>
|
<string name="screen_login_subtitle">"Matrix 是一個開放網路,為了安全且去中心化的通訊而生。"</string>
|
||||||
<string name="screen_login_title">"歡迎回來!"</string>
|
<string name="screen_login_title">"歡迎回來!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"登入 %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"登入 %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"手動登入"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"使用 QR code 登入"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"建立帳號"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"歡迎使用有史以來最快的 %1$s。速度超快,操作簡便。"</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"歡迎使用 %1$s。速度超快且簡單。"</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Be in your element"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"建立安全連線"</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_description">"無法與新裝置建立安全連線。您現有的裝置仍然安全,您不必擔心它們。"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"現在怎麼辦?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"現在怎麼辦?"</string>
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@
|
||||||
<string name="screen_login_subtitle">"Matrix 是一个用于安全、去中心化通信的开放网络。"</string>
|
<string name="screen_login_subtitle">"Matrix 是一个用于安全、去中心化通信的开放网络。"</string>
|
||||||
<string name="screen_login_title">"欢迎回来!"</string>
|
<string name="screen_login_title">"欢迎回来!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"登录到 %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"登录到 %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"手动登录"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"使用二维码登录"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"创建账户"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"欢迎使用 %1$s,快而简约的消息应用。"</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"欢迎使用 %1$s,速度与简洁的极致。"</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"融入您的 Element"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"建立安全连接"</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_description">"无法与新设备建立安全连接。您现有的设备仍然安全,无需担心。"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"现在怎么办?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"现在怎么办?"</string>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"The selected account provider does not support sliding sync. An upgrade to the server is needed to use %1$s."</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"The selected account provider does not support sliding sync. An upgrade to the server is needed to use %1$s."</string>
|
||||||
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s is not allowed to connect to %2$s."</string>
|
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s is not allowed to connect to %2$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver_content">"This app has been configured to allow: %1$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver_title">"Account provider %1$s not allowed."</string>
|
||||||
<string name="screen_change_server_form_header">"Homeserver URL"</string>
|
<string name="screen_change_server_form_header">"Homeserver URL"</string>
|
||||||
<string name="screen_change_server_form_notice">"Enter a domain address."</string>
|
<string name="screen_change_server_form_notice">"Enter a domain address."</string>
|
||||||
<string name="screen_change_server_subtitle">"What is the address of your server?"</string>
|
<string name="screen_change_server_subtitle">"What is the address of your server?"</string>
|
||||||
|
|
@ -32,6 +34,13 @@
|
||||||
<string name="screen_login_subtitle">"Matrix is an open network for secure, decentralised communication."</string>
|
<string name="screen_login_subtitle">"Matrix is an open network for secure, decentralised communication."</string>
|
||||||
<string name="screen_login_title">"Welcome back!"</string>
|
<string name="screen_login_title">"Welcome back!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Sign in to %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Sign in to %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_manually">"Sign in manually"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_to">"Sign in to %1$s"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_with_qr_code">"Sign in with QR code"</string>
|
||||||
|
<string name="screen_onboarding_sign_up">"Create account"</string>
|
||||||
|
<string name="screen_onboarding_welcome_message">"Welcome to the fastest %1$s ever. Supercharged for speed and simplicity."</string>
|
||||||
|
<string name="screen_onboarding_welcome_subtitle">"Welcome to %1$s. Supercharged, for speed and simplicity."</string>
|
||||||
|
<string name="screen_onboarding_welcome_title">"Be in your element"</string>
|
||||||
<string name="screen_qr_code_login_connecting_subtitle">"Establishing a secure connection"</string>
|
<string name="screen_qr_code_login_connecting_subtitle">"Establishing a secure connection"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"A secure connection could not be made to the new device. Your existing devices are still safe and you don\'t need to worry about them."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"A secure connection could not be made to the new device. Your existing devices are still safe and you don\'t need to worry about them."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"What now?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"What now?"</string>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.login.impl
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.login.api.LoginParams
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class DefaultLoginIntentResolverTest {
|
||||||
|
@Test
|
||||||
|
fun `nominal case`() {
|
||||||
|
val sut = DefaultLoginIntentResolver()
|
||||||
|
val uriString = "https://mobile.element.io/element?account_provider=example.org&login_hint=mxid:@alice:example.org"
|
||||||
|
assertThat(sut.parse(uriString)).isEqualTo(
|
||||||
|
LoginParams(
|
||||||
|
accountProvider = "example.org",
|
||||||
|
loginHint = "mxid:@alice:example.org",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `extra unknown param`() {
|
||||||
|
val sut = DefaultLoginIntentResolver()
|
||||||
|
val uriString = "https://mobile.element.io/element?account_provider=example.org&login_hint=mxid:@alice:example.org&extra=uknown"
|
||||||
|
assertThat(sut.parse(uriString)).isEqualTo(
|
||||||
|
LoginParams(
|
||||||
|
accountProvider = "example.org",
|
||||||
|
loginHint = "mxid:@alice:example.org",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `no account provider`() {
|
||||||
|
val sut = DefaultLoginIntentResolver()
|
||||||
|
val uriString = "https://mobile.element.io/element?login_hint=mxid:@alice:example.org"
|
||||||
|
assertThat(sut.parse(uriString)).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `no path`() {
|
||||||
|
val sut = DefaultLoginIntentResolver()
|
||||||
|
val uriString = "https://mobile.element.io?account_provider=example.org&login_hint=mxid:@alice:example.org"
|
||||||
|
assertThat(sut.parse(uriString)).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `wrong path`() {
|
||||||
|
val sut = DefaultLoginIntentResolver()
|
||||||
|
val uriString = "https://mobile.element.io/wrong?account_provider=example.org&login_hint=mxid:@alice:example.org"
|
||||||
|
assertThat(sut.parse(uriString)).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `wrong host`() {
|
||||||
|
val sut = DefaultLoginIntentResolver()
|
||||||
|
val uriString = "https://wrong.element.io/element?account_provider=example.org&login_hint=mxid:@alice:example.org"
|
||||||
|
assertThat(sut.parse(uriString)).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `no login_hint param`() {
|
||||||
|
val sut = DefaultLoginIntentResolver()
|
||||||
|
val uriString = "https://mobile.element.io/element?account_provider=example.org"
|
||||||
|
assertThat(sut.parse(uriString)).isEqualTo(
|
||||||
|
LoginParams(
|
||||||
|
accountProvider = "example.org",
|
||||||
|
loginHint = null,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,11 +27,11 @@ class AccountProviderDataSourceTest {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState).isEqualTo(
|
assertThat(initialState).isEqualTo(
|
||||||
AccountProvider(
|
AccountProvider(
|
||||||
url = FakeEnterpriseService.A_FAKE_HOMESERVER,
|
url = AuthenticationConfig.MATRIX_ORG_URL,
|
||||||
title = FakeEnterpriseService.A_FAKE_HOMESERVER,
|
title = "matrix.org",
|
||||||
subtitle = null,
|
subtitle = null,
|
||||||
isPublic = false,
|
isPublic = true,
|
||||||
isMatrixOrg = false,
|
isMatrixOrg = true,
|
||||||
isValid = false,
|
isValid = false,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -40,9 +40,11 @@ class AccountProviderDataSourceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - initial state - matrix org`() = runTest {
|
fun `present - initial state - matrix org`() = runTest {
|
||||||
val sut = AccountProviderDataSource(FakeEnterpriseService(
|
val sut = AccountProviderDataSource(
|
||||||
defaultHomeserverResult = { AuthenticationConfig.MATRIX_ORG_URL }
|
FakeEnterpriseService(
|
||||||
))
|
defaultHomeserverListResult = { listOf(AuthenticationConfig.MATRIX_ORG_URL) }
|
||||||
|
)
|
||||||
|
)
|
||||||
sut.flow.test {
|
sut.flow.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState).isEqualTo(
|
assertThat(initialState).isEqualTo(
|
||||||
|
|
@ -63,7 +65,7 @@ class AccountProviderDataSourceTest {
|
||||||
val sut = AccountProviderDataSource(FakeEnterpriseService())
|
val sut = AccountProviderDataSource(FakeEnterpriseService())
|
||||||
sut.flow.test {
|
sut.flow.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.url).isEqualTo(FakeEnterpriseService.A_FAKE_HOMESERVER)
|
assertThat(initialState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
|
||||||
sut.userSelection(AccountProvider(url = "https://example.com"))
|
sut.userSelection(AccountProvider(url = "https://example.com"))
|
||||||
val changedState = awaitItem()
|
val changedState = awaitItem()
|
||||||
assertThat(changedState).isEqualTo(
|
assertThat(changedState).isEqualTo(
|
||||||
|
|
@ -78,7 +80,7 @@ class AccountProviderDataSourceTest {
|
||||||
)
|
)
|
||||||
sut.reset()
|
sut.reset()
|
||||||
val resetState = awaitItem()
|
val resetState = awaitItem()
|
||||||
assertThat(resetState.url).isEqualTo(FakeEnterpriseService.A_FAKE_HOMESERVER)
|
assertThat(resetState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ class ChangeServerPresenterTest {
|
||||||
createPresenter(
|
createPresenter(
|
||||||
enterpriseService = FakeEnterpriseService(
|
enterpriseService = FakeEnterpriseService(
|
||||||
isAllowedToConnectToHomeserverResult = isAllowedToConnectToHomeserverResult,
|
isAllowedToConnectToHomeserverResult = isAllowedToConnectToHomeserverResult,
|
||||||
|
defaultHomeserverListResult = { listOf("element.io") },
|
||||||
),
|
),
|
||||||
).test {
|
).test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
|
|
@ -94,8 +95,11 @@ class ChangeServerPresenterTest {
|
||||||
assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java)
|
||||||
val failureState = awaitItem()
|
val failureState = awaitItem()
|
||||||
assertThat(
|
assertThat(
|
||||||
(failureState.changeServerAction.errorOrNull() as ChangeServerError.UnauthorizedAccountProvider).accountProvider
|
(failureState.changeServerAction.errorOrNull() as ChangeServerError.UnauthorizedAccountProvider).unauthorisedAccountProviderTitle
|
||||||
).isEqualTo(anAccountProvider)
|
).isEqualTo(anAccountProvider.title)
|
||||||
|
assertThat(
|
||||||
|
(failureState.changeServerAction.errorOrNull() as ChangeServerError.UnauthorizedAccountProvider).authorisedAccountProviderTitles
|
||||||
|
).containsExactly("element.io")
|
||||||
isAllowedToConnectToHomeserverResult.assertions()
|
isAllowedToConnectToHomeserverResult.assertions()
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
.with(value(A_HOMESERVER_URL))
|
.with(value(A_HOMESERVER_URL))
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||||
import io.element.android.features.login.impl.changeserver.aChangeServerState
|
import io.element.android.features.login.impl.changeserver.aChangeServerState
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
|
|
@ -25,7 +26,8 @@ class ChangeAccountProviderPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - initial state`() = runTest {
|
fun `present - initial state`() = runTest {
|
||||||
val presenter = ChangeAccountProviderPresenter(
|
val presenter = ChangeAccountProviderPresenter(
|
||||||
changeServerPresenter = { aChangeServerState() }
|
changeServerPresenter = { aChangeServerState() },
|
||||||
|
enterpriseService = FakeEnterpriseService(),
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,13 @@ import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.appconfig.AuthenticationConfig
|
||||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||||
import io.element.android.features.login.impl.DefaultLoginUserStory
|
import io.element.android.features.login.impl.DefaultLoginUserStory
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||||
|
import io.element.android.features.login.impl.login.LoginMode
|
||||||
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
||||||
|
import io.element.android.features.login.impl.screens.onboarding.createLoginHelper
|
||||||
import io.element.android.features.login.impl.web.FakeWebClientUrlForAuthenticationRetriever
|
import io.element.android.features.login.impl.web.FakeWebClientUrlForAuthenticationRetriever
|
||||||
import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
|
import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
|
@ -24,6 +27,7 @@ import io.element.android.libraries.matrix.test.A_HOMESERVER_OIDC
|
||||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||||
import io.element.android.libraries.oidc.api.OidcAction
|
import io.element.android.libraries.oidc.api.OidcAction
|
||||||
|
import io.element.android.libraries.oidc.api.OidcActionFlow
|
||||||
import io.element.android.libraries.oidc.impl.customtab.DefaultOidcActionFlow
|
import io.element.android.libraries.oidc.impl.customtab.DefaultOidcActionFlow
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.waitForPredicate
|
import io.element.android.tests.testutils.waitForPredicate
|
||||||
|
|
@ -44,8 +48,8 @@ class ConfirmAccountProviderPresenterTest {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.isAccountCreation).isFalse()
|
assertThat(initialState.isAccountCreation).isFalse()
|
||||||
assertThat(initialState.submitEnabled).isTrue()
|
assertThat(initialState.submitEnabled).isTrue()
|
||||||
assertThat(initialState.accountProvider.url).isEqualTo(FakeEnterpriseService.A_FAKE_HOMESERVER)
|
assertThat(initialState.accountProvider.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
|
||||||
assertThat(initialState.loginFlow).isEqualTo(AsyncData.Uninitialized)
|
assertThat(initialState.loginMode).isEqualTo(AsyncData.Uninitialized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,11 +67,11 @@ class ConfirmAccountProviderPresenterTest {
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.submitEnabled).isTrue()
|
assertThat(loadingState.submitEnabled).isTrue()
|
||||||
assertThat(loadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(loadingState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
assertThat(successState.submitEnabled).isFalse()
|
assertThat(successState.submitEnabled).isFalse()
|
||||||
assertThat(successState.loginFlow).isInstanceOf(AsyncData.Success::class.java)
|
assertThat(successState.loginMode).isInstanceOf(AsyncData.Success::class.java)
|
||||||
assertThat(successState.loginFlow.dataOrNull()).isEqualTo(LoginFlow.PasswordLogin)
|
assertThat(successState.loginMode.dataOrNull()).isEqualTo(LoginMode.PasswordLogin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,11 +89,11 @@ class ConfirmAccountProviderPresenterTest {
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.submitEnabled).isTrue()
|
assertThat(loadingState.submitEnabled).isTrue()
|
||||||
assertThat(loadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(loadingState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
assertThat(successState.submitEnabled).isFalse()
|
assertThat(successState.submitEnabled).isFalse()
|
||||||
assertThat(successState.loginFlow).isInstanceOf(AsyncData.Success::class.java)
|
assertThat(successState.loginMode).isInstanceOf(AsyncData.Success::class.java)
|
||||||
assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java)
|
assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,15 +113,15 @@ class ConfirmAccountProviderPresenterTest {
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.submitEnabled).isTrue()
|
assertThat(loadingState.submitEnabled).isTrue()
|
||||||
assertThat(loadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(loadingState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
assertThat(successState.submitEnabled).isFalse()
|
assertThat(successState.submitEnabled).isFalse()
|
||||||
assertThat(successState.loginFlow).isInstanceOf(AsyncData.Success::class.java)
|
assertThat(successState.loginMode).isInstanceOf(AsyncData.Success::class.java)
|
||||||
assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java)
|
assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
|
||||||
authenticationService.givenOidcCancelError(A_THROWABLE)
|
authenticationService.givenOidcCancelError(A_THROWABLE)
|
||||||
defaultOidcActionFlow.post(OidcAction.GoBack)
|
defaultOidcActionFlow.post(OidcAction.GoBack)
|
||||||
val cancelFailureState = awaitItem()
|
val cancelFailureState = awaitItem()
|
||||||
assertThat(cancelFailureState.loginFlow).isInstanceOf(AsyncData.Failure::class.java)
|
assertThat(cancelFailureState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,14 +141,14 @@ class ConfirmAccountProviderPresenterTest {
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.submitEnabled).isTrue()
|
assertThat(loadingState.submitEnabled).isTrue()
|
||||||
assertThat(loadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(loadingState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
assertThat(successState.submitEnabled).isFalse()
|
assertThat(successState.submitEnabled).isFalse()
|
||||||
assertThat(successState.loginFlow).isInstanceOf(AsyncData.Success::class.java)
|
assertThat(successState.loginMode).isInstanceOf(AsyncData.Success::class.java)
|
||||||
assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java)
|
assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
|
||||||
defaultOidcActionFlow.post(OidcAction.GoBack)
|
defaultOidcActionFlow.post(OidcAction.GoBack)
|
||||||
val cancelFinalState = awaitItem()
|
val cancelFinalState = awaitItem()
|
||||||
assertThat(cancelFinalState.loginFlow).isInstanceOf(AsyncData.Uninitialized::class.java)
|
assertThat(cancelFinalState.loginMode).isInstanceOf(AsyncData.Uninitialized::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,17 +168,17 @@ class ConfirmAccountProviderPresenterTest {
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.submitEnabled).isTrue()
|
assertThat(loadingState.submitEnabled).isTrue()
|
||||||
assertThat(loadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(loadingState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
assertThat(successState.submitEnabled).isFalse()
|
assertThat(successState.submitEnabled).isFalse()
|
||||||
assertThat(successState.loginFlow).isInstanceOf(AsyncData.Success::class.java)
|
assertThat(successState.loginMode).isInstanceOf(AsyncData.Success::class.java)
|
||||||
assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java)
|
assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
|
||||||
authenticationService.givenLoginError(A_THROWABLE)
|
authenticationService.givenLoginError(A_THROWABLE)
|
||||||
defaultOidcActionFlow.post(OidcAction.Success("aUrl"))
|
defaultOidcActionFlow.post(OidcAction.Success("aUrl"))
|
||||||
val cancelLoadingState = awaitItem()
|
val cancelLoadingState = awaitItem()
|
||||||
assertThat(cancelLoadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(cancelLoadingState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
|
||||||
val cancelFailureState = awaitItem()
|
val cancelFailureState = awaitItem()
|
||||||
assertThat(cancelFailureState.loginFlow).isInstanceOf(AsyncData.Failure::class.java)
|
assertThat(cancelFailureState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,15 +202,15 @@ class ConfirmAccountProviderPresenterTest {
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.submitEnabled).isTrue()
|
assertThat(loadingState.submitEnabled).isTrue()
|
||||||
assertThat(loadingState.loginFlow).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(loadingState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
assertThat(successState.submitEnabled).isFalse()
|
assertThat(successState.submitEnabled).isFalse()
|
||||||
assertThat(successState.loginFlow).isInstanceOf(AsyncData.Success::class.java)
|
assertThat(successState.loginMode).isInstanceOf(AsyncData.Success::class.java)
|
||||||
assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java)
|
assertThat(successState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
|
||||||
assertThat(defaultLoginUserStory.loginFlowIsDone.value).isFalse()
|
assertThat(defaultLoginUserStory.loginFlowIsDone.value).isFalse()
|
||||||
defaultOidcActionFlow.post(OidcAction.Success("aUrl"))
|
defaultOidcActionFlow.post(OidcAction.Success("aUrl"))
|
||||||
val successSuccessState = awaitItem()
|
val successSuccessState = awaitItem()
|
||||||
assertThat(successSuccessState.loginFlow).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(successSuccessState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
|
||||||
waitForPredicate { defaultLoginUserStory.loginFlowIsDone.value }
|
waitForPredicate { defaultLoginUserStory.loginFlowIsDone.value }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -226,7 +230,7 @@ class ConfirmAccountProviderPresenterTest {
|
||||||
skipItems(1) // Loading
|
skipItems(1) // Loading
|
||||||
val failureState = awaitItem()
|
val failureState = awaitItem()
|
||||||
assertThat(failureState.submitEnabled).isFalse()
|
assertThat(failureState.submitEnabled).isFalse()
|
||||||
assertThat(failureState.loginFlow).isInstanceOf(AsyncData.Failure::class.java)
|
assertThat(failureState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -249,12 +253,12 @@ class ConfirmAccountProviderPresenterTest {
|
||||||
|
|
||||||
// Check an error was returned
|
// Check an error was returned
|
||||||
val submittedState = awaitItem()
|
val submittedState = awaitItem()
|
||||||
assertThat(submittedState.loginFlow).isInstanceOf(AsyncData.Failure::class.java)
|
assertThat(submittedState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
|
||||||
|
|
||||||
// Assert the error is then cleared
|
// Assert the error is then cleared
|
||||||
submittedState.eventSink(ConfirmAccountProviderEvents.ClearError)
|
submittedState.eventSink(ConfirmAccountProviderEvents.ClearError)
|
||||||
val clearedState = awaitItem()
|
val clearedState = awaitItem()
|
||||||
assertThat(clearedState.loginFlow).isEqualTo(AsyncData.Uninitialized)
|
assertThat(clearedState.loginMode).isEqualTo(AsyncData.Uninitialized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -277,11 +281,11 @@ class ConfirmAccountProviderPresenterTest {
|
||||||
skipItems(1) // Loading
|
skipItems(1) // Loading
|
||||||
// Check an error was returned
|
// Check an error was returned
|
||||||
val submittedState = awaitItem()
|
val submittedState = awaitItem()
|
||||||
assertThat(submittedState.loginFlow.errorOrNull()).isInstanceOf(AccountCreationNotSupported::class.java)
|
assertThat(submittedState.loginMode.errorOrNull()).isInstanceOf(AccountCreationNotSupported::class.java)
|
||||||
// Assert the error is then cleared
|
// Assert the error is then cleared
|
||||||
submittedState.eventSink(ConfirmAccountProviderEvents.ClearError)
|
submittedState.eventSink(ConfirmAccountProviderEvents.ClearError)
|
||||||
val clearedState = awaitItem()
|
val clearedState = awaitItem()
|
||||||
assertThat(clearedState.loginFlow).isEqualTo(AsyncData.Uninitialized)
|
assertThat(clearedState.loginMode).isEqualTo(AsyncData.Uninitialized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -300,8 +304,8 @@ class ConfirmAccountProviderPresenterTest {
|
||||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
||||||
skipItems(1) // Loading
|
skipItems(1) // Loading
|
||||||
val submittedState = awaitItem()
|
val submittedState = awaitItem()
|
||||||
assertThat(submittedState.loginFlow).isInstanceOf(AsyncData.Success::class.java)
|
assertThat(submittedState.loginMode).isInstanceOf(AsyncData.Success::class.java)
|
||||||
assertThat(submittedState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java)
|
assertThat(submittedState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -322,8 +326,8 @@ class ConfirmAccountProviderPresenterTest {
|
||||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
||||||
skipItems(1) // Loading
|
skipItems(1) // Loading
|
||||||
val submittedState = awaitItem()
|
val submittedState = awaitItem()
|
||||||
assertThat(submittedState.loginFlow).isInstanceOf(AsyncData.Success::class.java)
|
assertThat(submittedState.loginMode).isInstanceOf(AsyncData.Success::class.java)
|
||||||
assertThat(submittedState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java)
|
assertThat(submittedState.loginMode.dataOrNull()).isInstanceOf(LoginMode.Oidc::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -344,7 +348,7 @@ class ConfirmAccountProviderPresenterTest {
|
||||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
||||||
skipItems(1) // Loading
|
skipItems(1) // Loading
|
||||||
val submittedState = awaitItem()
|
val submittedState = awaitItem()
|
||||||
assertThat(submittedState.loginFlow.dataOrNull()).isEqualTo(LoginFlow.AccountCreationFlow(aUrl))
|
assertThat(submittedState.loginMode.dataOrNull()).isEqualTo(LoginMode.AccountCreation(aUrl))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -352,15 +356,17 @@ class ConfirmAccountProviderPresenterTest {
|
||||||
params: ConfirmAccountProviderPresenter.Params = ConfirmAccountProviderPresenter.Params(isAccountCreation = false),
|
params: ConfirmAccountProviderPresenter.Params = ConfirmAccountProviderPresenter.Params(isAccountCreation = false),
|
||||||
accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
||||||
matrixAuthenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
matrixAuthenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
||||||
defaultOidcActionFlow: DefaultOidcActionFlow = DefaultOidcActionFlow(),
|
defaultOidcActionFlow: OidcActionFlow = DefaultOidcActionFlow(),
|
||||||
defaultLoginUserStory: DefaultLoginUserStory = DefaultLoginUserStory(),
|
defaultLoginUserStory: DefaultLoginUserStory = DefaultLoginUserStory(),
|
||||||
webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever = FakeWebClientUrlForAuthenticationRetriever(),
|
webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever = FakeWebClientUrlForAuthenticationRetriever(),
|
||||||
) = ConfirmAccountProviderPresenter(
|
) = ConfirmAccountProviderPresenter(
|
||||||
params = params,
|
params = params,
|
||||||
accountProviderDataSource = accountProviderDataSource,
|
accountProviderDataSource = accountProviderDataSource,
|
||||||
authenticationService = matrixAuthenticationService,
|
loginHelper = createLoginHelper(
|
||||||
oidcActionFlow = defaultOidcActionFlow,
|
authenticationService = matrixAuthenticationService,
|
||||||
defaultLoginUserStory = defaultLoginUserStory,
|
oidcActionFlow = defaultOidcActionFlow,
|
||||||
webClientUrlForAuthenticationRetriever = webClientUrlForAuthenticationRetriever
|
defaultLoginUserStory = defaultLoginUserStory,
|
||||||
|
webClientUrlForAuthenticationRetriever = webClientUrlForAuthenticationRetriever,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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