Merge branch 'release/25.05.4' into main

This commit is contained in:
Benoit Marty
2025-05-21 17:45:01 +02:00
260 changed files with 2798 additions and 2459 deletions

32
.github/renovate.json vendored
View File

@@ -1,29 +1,29 @@
{
"$schema" : "https://docs.renovatebot.com/renovate-schema.json",
"extends" : [
"config:base"
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
],
"labels" : [
"labels": [
"PR-Dependencies"
],
"ignoreDeps" : [
"ignoreDeps": [
"string:app_name",
"gradle"
],
"packageRules" : [
"packageRules": [
{
"matchPackagePatterns" : [
"^org.jetbrains.kotlin",
"^com.google.devtools.ksp",
"^androidx.compose.compiler"
],
"groupName" : "kotlin"
"groupName": "kotlin",
"matchPackageNames": [
"/^org.jetbrains.kotlin/",
"/^com.google.devtools.ksp/",
"/^androidx.compose.compiler/"
]
},
{
"matchPackagePatterns" : [
"^org.maplibre"
],
"versioning" : "semver"
"versioning": "semver",
"matchPackageNames": [
"/^org.maplibre/"
]
}
]
}

View File

@@ -56,7 +56,7 @@ jobs:
maestro-cloud:
name: Maestro test suite
runs-on: ubuntu-latest
needs: [build-apk]
needs: [ build-apk ]
# Allow one per PR.
concurrency:
group: ${{ format('maestro-{0}', github.ref) }}
@@ -80,6 +80,7 @@ jobs:
- name: Install maestro
run: curl -fsSL "https://get.maestro.mobile.dev" | bash
- name: Run Maestro tests in emulator
id: maestro_test
uses: reactivecircus/android-emulator-runner@v2
continue-on-error: true
env:
@@ -109,3 +110,8 @@ jobs:
retention-days: 5
overwrite: true
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
View 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
});

View File

@@ -82,7 +82,7 @@ jobs:
# https://github.com/codecov/codecov-action
- name: ☂️ Upload coverage reports to codecov
uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
with:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}

2
.idea/kotlinc.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.1.20" />
<option name="version" value="2.1.21" />
</component>
</project>

View File

@@ -7,22 +7,24 @@ appId: ${MAESTRO_APP_ID}
- runFlow: ../assertions/assertLoginDisplayed.yaml
- tapOn:
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"
## Working when running Maestro locally, but not on the CI yet.
- tapOn:
id: "login-email_username"
id: "form-1"
- inputText: ${MAESTRO_USERNAME}
- pressKey: Enter
- tapOn:
id: "login-password"
- inputText: "wrong-password"
- pressKey: Enter
- tapOn: "Continue"
- tapOn: "OK"
- tapOn:
id: "login-password"
- eraseText: 20
id: "form-3"
- inputText: ${MAESTRO_PASSWORD}
- pressKey: Enter
- tapOn: "Continue"
## Back to native world
- runFlow: ../assertions/assertSessionVerificationDisplayed.yaml
- runFlow: ./verifySession.yaml
- runFlow: ../assertions/assertAnalyticsDisplayed.yaml

View File

@@ -8,6 +8,6 @@ appId: ${MAESTRO_APP_ID}
- hideKeyboard
- tapOn: "Continue"
- extendedWaitUntil:
visible: "Device verified"
visible: "Verification complete"
timeout: 30000
- tapOn: "Continue"

View File

@@ -7,8 +7,9 @@ appId: ${MAESTRO_APP_ID}
- tapOn:
text: ${MAESTRO_INVITEE1_MXID}
index: 1
- tapOn: "Send invite"
- takeScreenshot: build/maestro/330-createAndDeleteDM
- tapOn: "maestroelement2"
- scroll
- tapOn: "Leave conversation"
- tapOn: "Leave room"
- tapOn: "Leave"

View 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

View File

@@ -6,5 +6,6 @@ appId: ${MAESTRO_APP_ID}
- runFlow: messages/text.yaml
- runFlow: messages/location.yaml
- runFlow: messages/poll.yaml
- runFlow: call/call.yaml
- back
- runFlow: ../../assertions/assertHomeDisplayed.yaml

View File

@@ -1,3 +1,27 @@
Changes in Element X v25.05.3
=============================
Version 25.05.2 was skipped.
## What's Changed
### 🐛 Bugfixes
* Disable Continue button when the login field is cleared. by @bmarty in https://github.com/element-hq/element-x-android/pull/4699
* Revert "fix(deps): update dependency io.element.android:element-call-embedded to v0.10.0", which caused an issue with to-device events in the latest version by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4706
### 🗣 Translations
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/4703
### 🧱 Build
* Update Gradle Wrapper from 8.13 to 8.14 by @ElementBot in https://github.com/element-hq/element-x-android/pull/4645
### Dependency upgrades
* fix(deps): update datastore to v1.1.6 by @renovate in https://github.com/element-hq/element-x-android/pull/4630
* fix(deps): update lifecycle to v2.9.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4693
* fix(deps): update dependency androidx.sqlite:sqlite-ktx to v2.5.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4692
### Others
* Update "Learn more" link by @bmarty in https://github.com/element-hq/element-x-android/pull/4686
* Keep call notification ringing while a call is present in the room by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4634
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.05.1...v25.05.3
Changes in Element X v25.05.1
=============================

View File

@@ -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).
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

View File

@@ -10,7 +10,4 @@ package io.element.android.appconfig
object MatrixConfiguration {
const val MATRIX_TO_PERMALINK_BASE_URL: String = "https://matrix.to/#/"
val clientPermalinkBaseUrl: String? = null
// TODO remove this when report is fixed
const val CAN_REPORT_ROOM = false
}

View File

@@ -20,13 +20,10 @@ import com.bumble.appyx.core.node.Node
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.operation.push
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.login.api.LoginEntryPoint
import io.element.android.features.login.api.LoginFlowType
import io.element.android.features.onboarding.api.OnBoardingEntryPoint
import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.designsystem.utils.ForceOrientationInMobileDevices
@@ -39,12 +36,11 @@ import kotlinx.parcelize.Parcelize
class NotLoggedInFlowNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val onBoardingEntryPoint: OnBoardingEntryPoint,
private val loginEntryPoint: LoginEntryPoint,
private val notLoggedInImageLoaderFactory: NotLoggedInImageLoaderFactory,
) : BaseFlowNode<NotLoggedInFlowNode.NavTarget>(
backstack = BackStack(
initialElement = NavTarget.OnBoarding,
initialElement = NavTarget.Root,
savedStateMap = buildContext.savedStateMap
),
buildContext = buildContext,
@@ -65,42 +61,22 @@ class NotLoggedInFlowNode @AssistedInject constructor(
sealed interface NavTarget : Parcelable {
@Parcelize
data object OnBoarding : NavTarget
@Parcelize
data class LoginFlow(val type: LoginFlowType) : NavTarget
data object Root : NavTarget
}
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) {
NavTarget.OnBoarding -> {
val callback = object : OnBoardingEntryPoint.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))
}
NavTarget.Root -> {
val callback = object : LoginEntryPoint.Callback {
override fun onReportProblem() {
plugins<Callback>().forEach { it.onOpenBugReport() }
}
}
onBoardingEntryPoint
loginEntryPoint
.nodeBuilder(this, buildContext)
.callback(callback)
.build()
}
is NavTarget.LoginFlow -> {
loginEntryPoint.nodeBuilder(this, buildContext)
.params(LoginEntryPoint.Params(flowType = navTarget.type))
.build()
}
}
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<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_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>
</resources>

View 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

View File

@@ -67,6 +67,7 @@ dependencies {
implementation(projects.features.enterprise.api)
implementation(projects.libraries.architecture)
implementation(projects.libraries.androidutils)
implementation(projects.libraries.audio.api)
implementation(projects.libraries.core)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.featureflag.api)

View File

@@ -10,9 +10,6 @@ package io.element.android.features.call.impl.ui
import android.Manifest
import android.app.PictureInPictureParams
import android.content.Intent
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.os.Build
import android.os.Bundle
import android.util.Rational
@@ -46,6 +43,8 @@ import io.element.android.features.call.impl.utils.CallIntentDataParser
import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.audio.api.AudioFocus
import io.element.android.libraries.audio.api.AudioFocusRequester
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.designsystem.theme.ElementThemeApp
@@ -65,16 +64,12 @@ class ElementCallActivity :
@Inject lateinit var enterpriseService: EnterpriseService
@Inject lateinit var pictureInPicturePresenter: PictureInPicturePresenter
@Inject lateinit var buildMeta: BuildMeta
@Inject lateinit var audioFocus: AudioFocus
private lateinit var presenter: Presenter<CallScreenState>
private lateinit var audioManager: AudioManager
private var requestPermissionCallback: RequestPermissionCallback? = null
private var audiofocusRequest: AudioFocusRequest? = null
private var audioFocusChangeListener: AudioManager.OnAudioFocusChangeListener? = null
private val requestPermissionsLauncher = registerPermissionResultLauncher()
private val webViewTarget = mutableStateOf<CallType?>(null)
@@ -102,8 +97,6 @@ class ElementCallActivity :
pictureInPicturePresenter.setPipView(this)
audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
setContent {
val pipState = pictureInPicturePresenter.present()
ListenToAndroidEvents(pipState)
@@ -133,7 +126,13 @@ class ElementCallActivity :
}
private fun setCallIsActive() {
requestAudioFocus()
audioFocus.requestAudioFocus(
requester = AudioFocusRequester.ElementCall,
onFocusLost = {
// If the audio focus is lost, we do not stop the call.
Timber.tag(loggerTag.value).w("Audio focus lost")
}
)
CallForegroundService.start(this)
}
@@ -175,7 +174,7 @@ class ElementCallActivity :
override fun onDestroy() {
super.onDestroy()
releaseAudioFocus()
audioFocus.releaseAudioFocus()
CallForegroundService.stop(this)
pictureInPicturePresenter.setPipView(null)
}
@@ -241,37 +240,6 @@ class ElementCallActivity :
}
}
@Suppress("DEPRECATION")
private fun requestAudioFocus() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.build()
val request = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(audioAttributes)
.build()
audioManager.requestAudioFocus(request)
audiofocusRequest = request
} else {
val listener = AudioManager.OnAudioFocusChangeListener { }
audioManager.requestAudioFocus(
listener,
AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE,
)
audioFocusChangeListener = listener
}
}
@Suppress("DEPRECATION")
private fun releaseAudioFocus() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
audiofocusRequest?.let { audioManager.abandonAudioFocusRequest(it) }
} else {
audioFocusChangeListener?.let { audioManager.abandonAudioFocus(it) }
}
}
@RequiresApi(Build.VERSION_CODES.O)
override fun setPipParams() {
setPictureInPictureParams(getPictureInPictureParams())

View File

@@ -13,7 +13,7 @@ import io.element.android.libraries.matrix.api.core.SessionId
interface EnterpriseService {
val isEnterpriseBuild: Boolean
suspend fun isEnterpriseUser(sessionId: SessionId): Boolean
fun defaultHomeserver(): String?
fun defaultHomeserverList(): List<String>
suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String): Boolean
fun semanticColorsLight(): SemanticColors

View File

@@ -22,7 +22,7 @@ class DefaultEnterpriseService @Inject constructor() : EnterpriseService {
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 fun semanticColorsLight(): SemanticColors = compoundColorsLight

View File

@@ -21,9 +21,9 @@ class DefaultEnterpriseServiceTest {
}
@Test
fun `defaultHomeserver should return null`() {
fun `defaultHomeserverList should return empty list`() {
val defaultEnterpriseService = DefaultEnterpriseService()
assertThat<String?>(defaultEnterpriseService.defaultHomeserver()).isNull()
assertThat(defaultEnterpriseService.defaultHomeserverList()).isEmpty()
}
@Test

View File

@@ -16,7 +16,7 @@ import io.element.android.tests.testutils.simulateLongTask
class FakeEnterpriseService(
override val isEnterpriseBuild: Boolean = false,
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 semanticColorsLightResult: () -> SemanticColors = { lambdaError() },
private val semanticColorsDarkResult: () -> SemanticColors = { lambdaError() },
@@ -27,8 +27,8 @@ class FakeEnterpriseService(
isEnterpriseUserResult(sessionId)
}
override fun defaultHomeserver(): String? {
return defaultHomeserverResult()
override fun defaultHomeserverList(): List<String> {
return defaultHomeserverListResult()
}
override suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String): Boolean = simulateLongTask {

View File

@@ -23,7 +23,6 @@ import androidx.compose.runtime.setValue
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
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.acceptdecline.AcceptDeclineInviteEvents
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
@@ -100,6 +99,8 @@ class JoinRoomPresenter @AssistedInject constructor(
val hideInviteAvatars by remember {
appPreferencesStore.getHideInviteAvatarsFlow()
}.collectAsState(initial = false)
val canReportRoom by produceState(false) { value = matrixClient.canReportRoom() }
val contentState by produceState<ContentState>(
initialValue = ContentState.Loading,
key1 = roomInfo,
@@ -212,7 +213,7 @@ class JoinRoomPresenter @AssistedInject constructor(
applicationName = buildMeta.applicationName,
knockMessage = knockMessage,
hideInviteAvatars = hideInviteAvatars,
canReportRoom = MatrixConfiguration.CAN_REPORT_ROOM,
canReportRoom = canReportRoom,
eventSink = ::handleEvents
)
}

View File

@@ -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_initial_loading_title">"Yn llwytho 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_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>

View File

@@ -101,7 +101,7 @@ class SendLocationPresenter @Inject constructor(
when (mode) {
SendLocationState.Mode.PinLocation -> {
val geoUri = event.cameraPosition.toGeoUri()
room.sendLocation(
room.liveTimeline.sendLocation(
body = generateBody(geoUri),
geoUri = geoUri,
description = null,
@@ -119,7 +119,7 @@ class SendLocationPresenter @Inject constructor(
}
SendLocationState.Mode.SenderLocation -> {
val geoUri = event.toGeoUri()
room.sendLocation(
room.liveTimeline.sendLocation(
body = generateBody(geoUri),
geoUri = geoUri,
description = null,

View File

@@ -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.core.aBuildMeta
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.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
@@ -266,7 +267,9 @@ class SendLocationPresenterTest {
Result.success(Unit)
}
val joinedRoom = FakeJoinedRoom(
sendLocationResult = sendLocationResult,
liveTimeline = FakeTimeline().apply {
sendLocationLambda = sendLocationResult
},
)
val sendLocationPresenter = createSendLocationPresenter(joinedRoom)
fakePermissionsPresenter.givenState(
@@ -327,7 +330,9 @@ class SendLocationPresenterTest {
Result.success(Unit)
}
val joinedRoom = FakeJoinedRoom(
sendLocationResult = sendLocationResult,
liveTimeline = FakeTimeline().apply {
sendLocationLambda = sendLocationResult
},
)
val sendLocationPresenter = createSendLocationPresenter(joinedRoom)
fakePermissionsPresenter.givenState(
@@ -388,7 +393,9 @@ class SendLocationPresenterTest {
Result.success(Unit)
}
val joinedRoom = FakeJoinedRoom(
sendLocationResult = sendLocationResult,
liveTimeline = FakeTimeline().apply {
sendLocationLambda = sendLocationResult
},
)
val sendLocationPresenter = createSendLocationPresenter(joinedRoom)
fakePermissionsPresenter.givenState(

View File

@@ -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_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>
<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_pin_android">"Defnyddio PIN"</string>
<string name="screen_signout_in_progress_dialog_content">"Yn allgofnodi…"</string>

View File

@@ -7,28 +7,20 @@
package io.element.android.features.login.api
import android.os.Parcelable
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import io.element.android.libraries.architecture.FeatureEntryPoint
import kotlinx.parcelize.Parcelize
interface LoginEntryPoint : FeatureEntryPoint {
data class Params(
val flowType: LoginFlowType
)
interface Callback : Plugin {
fun onReportProblem()
}
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
interface NodeBuilder {
fun params(params: Params): NodeBuilder
fun callback(callback: Callback): NodeBuilder
fun build(): Node
}
}
@Parcelize
enum class LoginFlowType : Parcelable {
SIGN_IN_MANUAL,
SIGN_IN_QR_CODE,
SIGN_UP
}

View File

@@ -29,9 +29,11 @@ setupAnvil(componentMergingStrategy = ComponentMergingStrategy.KSP)
dependencies {
implementation(projects.appconfig)
implementation(projects.features.enterprise.api)
implementation(projects.features.rageshake.api)
implementation(projects.libraries.core)
implementation(projects.libraries.androidutils)
implementation(projects.libraries.architecture)
implementation(projects.libraries.featureflag.api)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.network)
@@ -57,6 +59,7 @@ dependencies {
testImplementation(libs.test.truth)
testImplementation(libs.test.turbine)
testImplementation(projects.features.enterprise.test)
testImplementation(projects.libraries.featureflag.test)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.oidc.impl)
testImplementation(projects.libraries.permissions.test)

View File

@@ -22,8 +22,8 @@ class DefaultLoginEntryPoint @Inject constructor() : LoginEntryPoint {
val plugins = ArrayList<Plugin>()
return object : LoginEntryPoint.NodeBuilder {
override fun params(params: LoginEntryPoint.Params): LoginEntryPoint.NodeBuilder {
plugins += LoginFlowNode.Inputs(flowType = params.flowType)
override fun callback(callback: LoginEntryPoint.Callback): LoginEntryPoint.NodeBuilder {
plugins += callback
return this
}

View File

@@ -18,6 +18,7 @@ import com.bumble.appyx.core.lifecycle.subscribe
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 com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.push
import com.bumble.appyx.navmodel.backstack.operation.singleTop
@@ -25,8 +26,9 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.login.api.LoginFlowType
import io.element.android.features.login.api.LoginEntryPoint
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
import io.element.android.features.login.impl.onboarding.OnBoardingNode
import io.element.android.features.login.impl.qrcode.QrCodeLoginFlowNode
import io.element.android.features.login.impl.screens.changeaccountprovider.ChangeAccountProviderNode
import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderNode
@@ -35,9 +37,7 @@ import io.element.android.features.login.impl.screens.loginpassword.LoginPasswor
import io.element.android.features.login.impl.screens.searchaccountprovider.SearchAccountProviderNode
import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.auth.OidcDetails
import io.element.android.libraries.oidc.api.OidcAction
@@ -57,7 +57,7 @@ class LoginFlowNode @AssistedInject constructor(
private val oidcEntryPoint: OidcEntryPoint,
) : BaseFlowNode<LoginFlowNode.NavTarget>(
backstack = BackStack(
initialElement = NavTarget.Root,
initialElement = NavTarget.OnBoarding,
savedStateMap = buildContext.savedStateMap,
),
buildContext = buildContext,
@@ -66,12 +66,6 @@ class LoginFlowNode @AssistedInject constructor(
private var activity: Activity? = null
private var darkTheme: Boolean = false
data class Inputs(
val flowType: LoginFlowType,
) : NodeInputs
private val inputs: Inputs = inputs()
private var customChromeTabStarted = false
override fun onBuilt() {
@@ -96,10 +90,15 @@ class LoginFlowNode @AssistedInject constructor(
sealed interface NavTarget : Parcelable {
@Parcelize
data object Root : NavTarget
data object OnBoarding : NavTarget
@Parcelize
data object ConfirmAccountProvider : NavTarget
data object QrCode : NavTarget
@Parcelize
data class ConfirmAccountProvider(
val isAccountCreation: Boolean,
) : NavTarget
@Parcelize
data object ChangeAccountProvider : NavTarget
@@ -119,16 +118,36 @@ class LoginFlowNode @AssistedInject constructor(
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) {
NavTarget.Root -> {
if (inputs.flowType == LoginFlowType.SIGN_IN_QR_CODE) {
createNode<QrCodeLoginFlowNode>(buildContext)
} else {
resolve(NavTarget.ConfirmAccountProvider, buildContext)
NavTarget.OnBoarding -> {
val callback = object : OnBoardingNode.Callback {
override fun onSignUp() {
backstack.push(
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() }
}
}
createNode<OnBoardingNode>(buildContext, listOf(callback))
}
NavTarget.ConfirmAccountProvider -> {
NavTarget.QrCode -> {
createNode<QrCodeLoginFlowNode>(buildContext)
}
is NavTarget.ConfirmAccountProvider -> {
val inputs = ConfirmAccountProviderNode.Inputs(
isAccountCreation = inputs.flowType == LoginFlowType.SIGN_UP,
isAccountCreation = navTarget.isAccountCreation,
)
val callback = object : ConfirmAccountProviderNode.Callback {
override fun onOidcDetails(oidcDetails: OidcDetails) {
@@ -162,7 +181,10 @@ class LoginFlowNode @AssistedInject constructor(
val callback = object : ChangeAccountProviderNode.Callback {
override fun onDone() {
// 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() {
@@ -176,7 +198,10 @@ class LoginFlowNode @AssistedInject constructor(
val callback = object : SearchAccountProviderNode.Callback {
override fun onDone() {
// 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)
}
}

View File

@@ -20,14 +20,15 @@ import javax.inject.Inject
class AccountProviderDataSource @Inject constructor(
enterpriseService: EnterpriseService,
) {
private val defaultAccountProvider = (enterpriseService.defaultHomeserver() ?: AuthenticationConfig.MATRIX_ORG_URL).let { url ->
AccountProvider(
url = url,
subtitle = null,
isPublic = url == AuthenticationConfig.MATRIX_ORG_URL,
isMatrixOrg = url == AuthenticationConfig.MATRIX_ORG_URL,
)
}
private val defaultAccountProvider = (enterpriseService.defaultHomeserverList().firstOrNull() ?: AuthenticationConfig.MATRIX_ORG_URL)
.let { url ->
AccountProvider(
url = url,
subtitle = null,
isPublic = url == AuthenticationConfig.MATRIX_ORG_URL,
isMatrixOrg = url == AuthenticationConfig.MATRIX_ORG_URL,
)
}
private val accountProvider: MutableStateFlow<AccountProvider> = MutableStateFlow(
defaultAccountProvider

View File

@@ -21,8 +21,10 @@ open class AccountProviderProvider : PreviewParameterProvider<AccountProvider> {
)
}
fun anAccountProvider() = AccountProvider(
url = AuthenticationConfig.MATRIX_ORG_URL,
fun anAccountProvider(
url: String = AuthenticationConfig.MATRIX_ORG_URL,
) = AccountProvider(
url = url,
subtitle = "Matrix.org is an open network for secure, decentralized communication.",
isPublic = true,
isMatrixOrg = true,

View File

@@ -56,7 +56,10 @@ class ChangeServerPresenter @Inject constructor(
) = launch {
suspend {
if (enterpriseService.isAllowedToConnectToHomeserver(data.url).not()) {
throw UnauthorizedAccountProviderException(data)
throw UnauthorizedAccountProviderException(
unauthorisedAccountProviderTitle = data.title,
authorisedAccountProviderTitles = enterpriseService.defaultHomeserverList(),
)
}
authenticationService.setHomeserver(data.url).map {
authenticationService.getHomeserverDetails().value!!

View File

@@ -8,7 +8,6 @@
package io.element.android.features.login.impl.changeserver
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.libraries.architecture.AsyncData
import io.element.android.libraries.ui.strings.CommonStrings
@@ -19,7 +18,14 @@ open class ChangeServerStateProvider : PreviewParameterProvider<ChangeServerStat
aChangeServerState(),
aChangeServerState(changeServerAction = AsyncData.Failure(ChangeServerError.Error(CommonStrings.error_unknown))),
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"),
)
)
),
)
}

View File

@@ -62,7 +62,7 @@ fun ChangeServerView(
content = stringResource(
id = R.string.screen_change_server_error_unauthorized_homeserver,
LocalBuildMeta.current.applicationName,
error.accountProvider.title,
error.unauthorisedAccountProviderTitle,
),
onSubmit = {
eventSink.invoke(ChangeServerEvents.ClearError)

View File

@@ -7,8 +7,7 @@
package io.element.android.features.login.impl.changeserver
import io.element.android.features.login.impl.accountprovider.AccountProvider
class UnauthorizedAccountProviderException(
val accountProvider: AccountProvider,
val unauthorisedAccountProviderTitle: String,
val authorisedAccountProviderTitles: List<String>,
) : Exception()

View File

@@ -11,7 +11,6 @@ import androidx.annotation.StringRes
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.accountprovider.AccountProvider
import io.element.android.features.login.impl.changeserver.UnauthorizedAccountProviderException
import io.element.android.libraries.matrix.api.auth.AuthenticationException
import io.element.android.libraries.ui.strings.CommonStrings
@@ -26,7 +25,8 @@ sealed class ChangeServerError : Throwable() {
}
data class UnauthorizedAccountProvider(
val accountProvider: AccountProvider,
val unauthorisedAccountProviderTitle: String,
val authorisedAccountProviderTitles: List<String>,
) : ChangeServerError()
data object SlidingSyncAlert : ChangeServerError()
@@ -35,7 +35,10 @@ sealed class ChangeServerError : Throwable() {
fun from(error: Throwable): ChangeServerError = when (error) {
is AuthenticationException.SlidingSyncVersion -> SlidingSyncAlert
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)
}
}

View File

@@ -5,7 +5,7 @@
* 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.onboarding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -16,7 +16,6 @@ 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.onboarding.api.OnBoardingEntryPoint
import io.element.android.libraries.di.AppScope
@ContributesNode(AppScope::class)
@@ -28,20 +27,27 @@ class OnBoardingNode @AssistedInject constructor(
buildContext = buildContext,
plugins = plugins
) {
interface Callback : Plugin {
fun onSignUp()
fun onSignIn()
fun onSignInWithQrCode()
fun onReportProblem()
}
private fun onSignIn() {
plugins<OnBoardingEntryPoint.Callback>().forEach { it.onSignIn() }
plugins<Callback>().forEach { it.onSignIn() }
}
private fun onSignUp() {
plugins<OnBoardingEntryPoint.Callback>().forEach { it.onSignUp() }
plugins<Callback>().forEach { it.onSignUp() }
}
private fun onSignInWithQrCode() {
plugins<OnBoardingEntryPoint.Callback>().forEach { it.onSignInWithQrCode() }
plugins<Callback>().forEach { it.onSignInWithQrCode() }
}
private fun onReportProblem() {
plugins<OnBoardingEntryPoint.Callback>().forEach { it.onReportProblem() }
plugins<Callback>().forEach { it.onReportProblem() }
}
@Composable

View File

@@ -5,7 +5,7 @@
* 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.onboarding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue

View File

@@ -5,7 +5,7 @@
* 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.onboarding
data class OnBoardingState(
val productionApplicationName: String,

View File

@@ -5,7 +5,7 @@
* 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.onboarding
import androidx.compose.ui.tooling.preview.PreviewParameterProvider

View File

@@ -5,7 +5,7 @@
* 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.onboarding
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
@@ -26,6 +26,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.login.impl.R
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.molecules.ButtonColumnMolecule

View File

@@ -8,29 +8,39 @@
package io.element.android.features.login.impl.screens.changeaccountprovider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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.changeserver.ChangeServerState
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.uri.ensureProtocol
import javax.inject.Inject
class ChangeAccountProviderPresenter @Inject constructor(
private val changeServerPresenter: Presenter<ChangeServerState>,
private val enterpriseService: EnterpriseService,
) : Presenter<ChangeAccountProviderState> {
@Composable
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()
return ChangeAccountProviderState(
// Just matrix.org by default for now
accountProviders = listOf(
AccountProvider(
url = AuthenticationConfig.MATRIX_ORG_URL,
subtitle = null,
isPublic = true,
isMatrixOrg = true,
isValid = true,
)
),
accountProviders = staticAccountProviderList,
changeServerState = changeServerState,
)
}

View File

@@ -11,7 +11,7 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider
import io.element.android.features.login.impl.changeserver.ChangeServerState
// 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 changeServerState: ChangeServerState,
)

View File

@@ -15,6 +15,8 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import io.element.android.features.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.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter
@@ -36,6 +38,7 @@ class QrCodeScanPresenter @Inject constructor(
private val qrCodeLoginDataFactory: MatrixQrCodeLoginDataFactory,
private val qrCodeLoginManager: QrCodeLoginManager,
private val coroutineDispatchers: CoroutineDispatchers,
private val enterpriseService: EnterpriseService,
) : Presenter<QrCodeScanState> {
private var isScanning by mutableStateOf(true)
@@ -90,9 +93,17 @@ class QrCodeScanPresenter @Inject constructor(
launch(coroutineDispatchers.computation) {
suspend {
qrCodeLoginDataFactory.parseQrCodeData(code).onFailure {
val data = qrCodeLoginDataFactory.parseQrCodeData(code).onFailure {
Timber.e(it, "Error parsing QR code data")
}.getOrThrow()
val serverName = data.serverName()
if (serverName != null && enterpriseService.isAllowedToConnectToHomeserver(serverName).not()) {
throw UnauthorizedAccountProviderException(
unauthorisedAccountProviderTitle = serverName,
authorisedAccountProviderTitles = enterpriseService.defaultHomeserverList(),
)
}
data
}.runCatchingUpdatingState(codeScannedAction)
}.invokeOnCompletion {
isProcessingCode.set(false)

View File

@@ -8,6 +8,7 @@
package io.element.android.features.login.impl.screens.qrcode.scan
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.matrix.api.auth.qrlogin.MatrixQrCodeLoginData
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.Failure(Exception("Error"))),
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
)
}

View File

@@ -35,6 +35,7 @@ import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
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.designsystem.atomic.pages.FlowStepPage
import io.element.android.libraries.designsystem.components.BigIcon
@@ -144,6 +145,12 @@ private fun ColumnScope.Buttons(
Spacer(modifier = Modifier.width(4.dp))
Text(
text = when (error) {
is UnauthorizedAccountProviderException -> {
stringResource(
id = R.string.screen_change_server_error_unauthorized_homeserver_title,
error.unauthorisedAccountProviderTitle,
)
}
is QrLoginException.OtherDeviceNotSignedIn -> {
stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_subtitle)
}
@@ -156,6 +163,12 @@ private fun ColumnScope.Buttons(
}
Text(
text = when (error) {
is UnauthorizedAccountProviderException -> {
stringResource(
id = R.string.screen_change_server_error_unauthorized_homeserver_content,
error.authorisedAccountProviderTitles.joinToString(),
)
}
is QrLoginException.OtherDeviceNotSignedIn -> {
stringResource(R.string.screen_qr_code_login_device_not_signed_in_scan_state_description)
}

View File

@@ -29,6 +29,12 @@
<string name="screen_login_subtitle">"Matrix - гэта адкрытая сетка для бяспечнай, дэцэнтралізаванай сувязі."</string>
<string name="screen_login_title">"Сардэчна запрашаем!"</string>
<string name="screen_login_title_with_homeserver">"Увайсці ў %1$s"</string>
<string name="screen_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_connection_note_secure_state_description">"Не атрымалася ўсталяваць бяспечнае злучэнне з новай прыладай. Існуючыя прылады па-ранейшаму ў бяспецы, і вам не трэба турбавацца пра іх."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Што зараз?"</string>

View File

@@ -19,6 +19,12 @@
<string name="screen_login_subtitle">"Matrix е отворена мрежа за сигурна, децентрализирана комуникация."</string>
<string name="screen_login_title">"Добре дошли отново!"</string>
<string name="screen_login_title_with_homeserver">"Влизане в %1$s"</string>
<string name="screen_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_server_confirmation_change_server">"Промяна на доставчика на акаунт"</string>
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix е отворена мрежа за сигурна, децентрализирана комуникация."</string>

View File

@@ -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:
%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_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_notice">"Zadejte adresu domény."</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_title">"Vítejte zpět!"</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_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>

View File

@@ -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_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_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>
<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_notice">"Rhowch gyfeiriad parth."</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_title">"Croeso nôl!"</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_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>

View File

@@ -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:
%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_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_notice">"Geben Sie eine Domainadresse ein."</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_title">"Willkommen zurück!"</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_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>
@@ -54,7 +60,7 @@
Versuche, dich manuell anzumelden, oder scanne den QR-Code mit einem anderen Gerät."</string>
<string name="screen_qr_code_login_error_linking_not_suported_title">"QR-Code wird nicht unterstützt"</string>
<string name="screen_qr_code_login_error_sliding_sync_not_supported_subtitle">"Ihr Kontoanbieter unterstützt %1$s nicht."</string>
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$swird nicht unterstützt"</string>
<string name="screen_qr_code_login_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_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>

View File

@@ -29,6 +29,12 @@
<string name="screen_login_subtitle">"Το Matrix είναι ένα ανοιχτό δίκτυο για ασφαλή, αποκεντρωμένη επικοινωνία."</string>
<string name="screen_login_title">"Καλωσόρισες ξανά!"</string>
<string name="screen_login_title_with_homeserver">"Συνδέσου στο %1$s"</string>
<string name="screen_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_connection_note_secure_state_description">"Δεν ήταν δυνατή η πραγματοποίηση ασφαλούς σύνδεσης στη νέα συσκευή. Οι υπάρχουσες συσκευές σας εξακολουθούν να είναι ασφαλείς και δεν χρειάζεται να ανησυχείς για αυτές."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Τί είναι πάλι;"</string>

View File

@@ -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_title">"¡Hola de nuevo!"</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_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>

View File

@@ -18,6 +18,8 @@
%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_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_notice">"Sisesta domeeni 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_title">"Tere tulemast tagasi!"</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_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>

View File

@@ -20,6 +20,12 @@
<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_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_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>

View File

@@ -25,6 +25,12 @@
<string name="screen_login_subtitle">"ماتریکس شبکه‌ای بار برای ارتباطات نامتمرکز و امن است."</string>
<string name="screen_login_title">"خوش برگشتید!"</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_connection_note_secure_state_description">"نتوانست اتّصالی امن به افزارهٔ جدید بسازد. افزاره‌های موجودتان هنوز امنند و نیازی نیست نگرانشان باشید."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"اکنون چه؟"</string>

View File

@@ -17,6 +17,9 @@
<string name="screen_change_server_error_invalid_well_known">"Sliding sync ei ole saatavilla well-known tiedostossa olevan ongelman vuoksi:
%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_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_notice">"Anna verkkotunnuksen 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_title">"Tervetuloa takaisin!"</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_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>

View File

@@ -17,6 +17,9 @@
<string name="screen_change_server_error_invalid_well_known">"Ce fournisseur de compte nest pas disponible en raison dun problème dans le fichier .well-known:
%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 nest 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 nest pas autorisé."</string>
<string name="screen_change_server_form_header">"URL du serveur daccueil"</string>
<string name="screen_change_server_form_notice">"Saisissez une adresse de domaine."</string>
<string name="screen_change_server_subtitle">"Quelle est ladresse 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_title">"Content de vous revoir !"</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 lapplication %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 dune connexion sécurisée"</string>
<string name="screen_qr_code_login_connection_note_secure_state_description">"Aucune connexion sécurisée na pu être établie avec la nouvelle session. Vos sessions existantes sont toujours en sécurité et vous navez pas à vous en soucier."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Et maintenant ?"</string>

View File

@@ -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:
%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_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_notice">"Adjon meg egy domaincímet."</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_title">"Örülünk, hogy visszatért!"</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_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>

View File

@@ -29,6 +29,12 @@
<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_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_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>

View File

@@ -29,6 +29,12 @@
<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_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_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>

View File

@@ -28,6 +28,12 @@
<string name="screen_login_subtitle">"Matrix არის ღია ქსელი უსაფრთხო, დეცენტრალიზებული კომუნიკაციისთვის."</string>
<string name="screen_login_title">"კეთილი იყოს თქვენი მობრძანება!"</string>
<string name="screen_login_title_with_homeserver">"შესვლა %1$s-ში"</string>
<string name="screen_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_server_confirmation_change_server">"შეცვალეთ ანგარიშის მომწოდებელი"</string>
<string name="screen_server_confirmation_message_login_element_dot_io">"კერძო სერვერი Element-ის თანამშრომლებისთვის."</string>

View File

@@ -22,6 +22,11 @@
<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_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_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>

View File

@@ -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:
%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_notice">"Skriv inn en domeneadresse."</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_title">"Velkommen tilbake!"</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_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>

View File

@@ -29,6 +29,12 @@
<string name="screen_login_subtitle">"Matrix is een open netwerk voor veilige, gedecentraliseerde communicatie."</string>
<string name="screen_login_title">"Welkom terug!"</string>
<string name="screen_login_title_with_homeserver">"Inloggen bij %1$s"</string>
<string name="screen_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_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>

View File

@@ -32,6 +32,13 @@
<string name="screen_login_subtitle">"Matrix to otwarta sieć do bezpiecznej i zdecentralizowanej komunikacji."</string>
<string name="screen_login_title">"Witaj ponownie!"</string>
<string name="screen_login_title_with_homeserver">"Zaloguj się do %1$s"</string>
<string name="screen_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_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>

View File

@@ -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_title">"Bem-vindo de volta!"</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_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>

View File

@@ -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_title">"Bem-vindo(a) de volta!"</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_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>

View File

@@ -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_title">"Bine ați revenit!"</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_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>

View File

@@ -30,6 +30,12 @@
<string name="screen_login_subtitle">"Matrix — это открытая сеть для безопасной децентрализованной связи."</string>
<string name="screen_login_title">"Рады видеть вас снова!"</string>
<string name="screen_login_title_with_homeserver">"Войти в %1$s"</string>
<string name="screen_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_connection_note_secure_state_description">"Не удалось установить безопасное соединение с новым устройством. Существующие устройства по-прежнему в безопасности, и вам не нужно беспокоиться о них."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Что теперь?"</string>

View File

@@ -18,6 +18,8 @@
%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_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_notice">"Zadajte adresu domény."</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_title">"Vitajte späť!"</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_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>

View File

@@ -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_title">"Välkommen tillbaka!"</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_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>

View File

@@ -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_title">"Tekrar hoş geldiniz!"</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_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>

View File

@@ -14,9 +14,10 @@
<string name="screen_change_account_provider_subtitle">"Використати іншого провайдера облікових записів, наприклад, власний приватний сервер або робочий обліковий запис."</string>
<string name="screen_change_account_provider_title">"Змінити провайдера облікового запису"</string>
<string name="screen_change_server_error_invalid_homeserver">"Не вдалося під\'єднатися до цього домашнього сервера. Перевірте правильність введеної URL-адреси домашнього сервера. Якщо 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>
<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_title">"Виберіть свій сервер"</string>
<string name="screen_create_account_title">"Створити обліковий запис"</string>
@@ -29,6 +30,13 @@
<string name="screen_login_subtitle">"Matrix — це відкрита мережа для безпечної, децентралізованої комунікації."</string>
<string name="screen_login_title">"З поверненням!"</string>
<string name="screen_login_title_with_homeserver">"Увійти в %1$s"</string>
<string name="screen_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_connection_note_secure_state_description">"Не вдалося встановити безпечне з\'єднання з новим пристроєм. Ваші наявні пристрої досі в безпеці, і вам не потрібно про них турбуватися."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Що тепер?"</string>

View File

@@ -26,6 +26,12 @@
<string name="screen_login_subtitle">"Matrix xavfsiz, markazlashmagan aloqa uchun ochiq tarmoqdir."</string>
<string name="screen_login_title">"Qaytib kelganingizdan xursandmiz!"</string>
<string name="screen_login_title_with_homeserver">"Kirish%1$s"</string>
<string name="screen_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_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>

View File

@@ -29,6 +29,12 @@
<string name="screen_login_subtitle">"Matrix 是一個開放網路,為了安全且去中心化的通訊而生。"</string>
<string name="screen_login_title">"歡迎回來!"</string>
<string name="screen_login_title_with_homeserver">"登入 %1$s"</string>
<string name="screen_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_connection_note_secure_state_description">"無法與新裝置建立安全連線。您現有的裝置仍然安全,您不必擔心它們。"</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"現在怎麼辦?"</string>

View File

@@ -29,6 +29,12 @@
<string name="screen_login_subtitle">"Matrix 是一个用于安全、去中心化通信的开放网络。"</string>
<string name="screen_login_title">"欢迎回来!"</string>
<string name="screen_login_title_with_homeserver">"登录到 %1$s"</string>
<string name="screen_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_connection_note_secure_state_description">"无法与新设备建立安全连接。您现有的设备仍然安全,无需担心。"</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"现在怎么办?"</string>

View File

@@ -18,6 +18,8 @@
%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_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_notice">"Enter a domain address."</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_title">"Welcome back!"</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_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>

View File

@@ -27,11 +27,11 @@ class AccountProviderDataSourceTest {
val initialState = awaitItem()
assertThat(initialState).isEqualTo(
AccountProvider(
url = FakeEnterpriseService.A_FAKE_HOMESERVER,
title = FakeEnterpriseService.A_FAKE_HOMESERVER,
url = AuthenticationConfig.MATRIX_ORG_URL,
title = "matrix.org",
subtitle = null,
isPublic = false,
isMatrixOrg = false,
isPublic = true,
isMatrixOrg = true,
isValid = false,
)
)
@@ -40,9 +40,11 @@ class AccountProviderDataSourceTest {
@Test
fun `present - initial state - matrix org`() = runTest {
val sut = AccountProviderDataSource(FakeEnterpriseService(
defaultHomeserverResult = { AuthenticationConfig.MATRIX_ORG_URL }
))
val sut = AccountProviderDataSource(
FakeEnterpriseService(
defaultHomeserverListResult = { listOf(AuthenticationConfig.MATRIX_ORG_URL) }
)
)
sut.flow.test {
val initialState = awaitItem()
assertThat(initialState).isEqualTo(
@@ -63,7 +65,7 @@ class AccountProviderDataSourceTest {
val sut = AccountProviderDataSource(FakeEnterpriseService())
sut.flow.test {
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"))
val changedState = awaitItem()
assertThat(changedState).isEqualTo(
@@ -78,7 +80,7 @@ class AccountProviderDataSourceTest {
)
sut.reset()
val resetState = awaitItem()
assertThat(resetState.url).isEqualTo(FakeEnterpriseService.A_FAKE_HOMESERVER)
assertThat(resetState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
}
}
}

View File

@@ -84,6 +84,7 @@ class ChangeServerPresenterTest {
createPresenter(
enterpriseService = FakeEnterpriseService(
isAllowedToConnectToHomeserverResult = isAllowedToConnectToHomeserverResult,
defaultHomeserverListResult = { listOf("element.io") },
),
).test {
val initialState = awaitItem()
@@ -94,8 +95,11 @@ class ChangeServerPresenterTest {
assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java)
val failureState = awaitItem()
assertThat(
(failureState.changeServerAction.errorOrNull() as ChangeServerError.UnauthorizedAccountProvider).accountProvider
).isEqualTo(anAccountProvider)
(failureState.changeServerAction.errorOrNull() as ChangeServerError.UnauthorizedAccountProvider).unauthorisedAccountProviderTitle
).isEqualTo(anAccountProvider.title)
assertThat(
(failureState.changeServerAction.errorOrNull() as ChangeServerError.UnauthorizedAccountProvider).authorisedAccountProviderTitles
).containsExactly("element.io")
isAllowedToConnectToHomeserverResult.assertions()
.isCalledOnce()
.with(value(A_HOMESERVER_URL))

View File

@@ -5,7 +5,7 @@
* 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.onboarding
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow

View File

@@ -5,13 +5,14 @@
* 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.onboarding
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.login.impl.R
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.clickOn

View File

@@ -11,6 +11,7 @@ import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.enterprise.test.FakeEnterpriseService
import io.element.android.features.login.impl.accountprovider.AccountProvider
import io.element.android.features.login.impl.changeserver.aChangeServerState
import io.element.android.tests.testutils.WarmUpRule
@@ -25,7 +26,8 @@ class ChangeAccountProviderPresenterTest {
@Test
fun `present - initial state`() = runTest {
val presenter = ChangeAccountProviderPresenter(
changeServerPresenter = { aChangeServerState() }
changeServerPresenter = { aChangeServerState() },
enterpriseService = FakeEnterpriseService(),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()

View File

@@ -11,6 +11,7 @@ import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.appconfig.AuthenticationConfig
import io.element.android.features.enterprise.test.FakeEnterpriseService
import io.element.android.features.login.impl.DefaultLoginUserStory
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
@@ -44,7 +45,7 @@ class ConfirmAccountProviderPresenterTest {
val initialState = awaitItem()
assertThat(initialState.isAccountCreation).isFalse()
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)
}
}

View File

@@ -8,6 +8,7 @@
package io.element.android.features.login.impl.screens.createaccount
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.login.impl.accountprovider.AccountProviderDataSource
import io.element.android.libraries.matrix.api.auth.external.ExternalSession
@@ -60,7 +61,7 @@ class DefaultMessageParserTest {
// missing homeServer
assertThat(sut.parse(validMessage.replace(""""home_server": "home_server",""", ""))).isEqualTo(
anExternalSession(
homeserverUrl = FakeEnterpriseService.A_FAKE_HOMESERVER,
homeserverUrl = AuthenticationConfig.MATRIX_ORG_URL,
)
)
}

View File

@@ -8,6 +8,7 @@
package io.element.android.features.login.impl.screens.loginpassword
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.login.impl.DefaultLoginUserStory
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
@@ -33,7 +34,7 @@ class LoginPasswordPresenterTest {
fun `present - initial state`() = runTest {
createLoginPasswordPresenter().test {
val initialState = awaitItem()
assertThat(initialState.accountProvider.url).isEqualTo(FakeEnterpriseService.A_FAKE_HOMESERVER)
assertThat(initialState.accountProvider.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
assertThat(initialState.formState).isEqualTo(LoginFormState.Default)
assertThat(initialState.loginAction).isEqualTo(AsyncData.Uninitialized)
assertThat(initialState.submitEnabled).isFalse()

View File

@@ -11,12 +11,17 @@ import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.features.enterprise.test.FakeEnterpriseService
import io.element.android.features.login.impl.changeserver.UnauthorizedAccountProviderException
import io.element.android.features.login.impl.qrcode.FakeQrCodeLoginManager
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
import io.element.android.libraries.matrix.api.auth.qrlogin.QrLoginException
import io.element.android.libraries.matrix.test.auth.qrlogin.FakeMatrixQrCodeLoginData
import io.element.android.libraries.matrix.test.auth.qrlogin.FakeMatrixQrCodeLoginDataFactory
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.test
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@@ -38,10 +43,22 @@ class QrCodeScanPresenterTest {
@Test
fun `present - scanned QR code successfully`() = runTest {
val presenter = createQrCodeScanPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val qrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory(
parseQrCodeLoginDataResult = {
Result.success(
FakeMatrixQrCodeLoginData(
serverNameResult = { "example.com" }
)
)
}
)
val presenter = createQrCodeScanPresenter(
qrCodeLoginDataFactory = qrCodeLoginDataFactory,
enterpriseService = FakeEnterpriseService(
isAllowedToConnectToHomeserverResult = { true },
)
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink(QrCodeScanEvents.QrCodeScanned(byteArrayOf()))
assertThat(awaitItem().isScanning).isFalse()
@@ -50,6 +67,38 @@ class QrCodeScanPresenterTest {
}
}
@Test
fun `present - scanned QR code successfully, but homeserver not allowed`() = runTest {
val qrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory(
parseQrCodeLoginDataResult = {
Result.success(
FakeMatrixQrCodeLoginData(
serverNameResult = { "example.com" }
)
)
}
)
val presenter = createQrCodeScanPresenter(
qrCodeLoginDataFactory = qrCodeLoginDataFactory,
enterpriseService = FakeEnterpriseService(
isAllowedToConnectToHomeserverResult = { false },
defaultHomeserverListResult = { listOf("element.io") },
)
)
presenter.test {
val initialState = awaitItem()
initialState.eventSink(QrCodeScanEvents.QrCodeScanned(byteArrayOf()))
assertThat(awaitItem().isScanning).isFalse()
assertThat(awaitItem().authenticationAction.isLoading()).isTrue()
awaitItem().also { state ->
assertThat((state.authenticationAction.errorOrNull() as UnauthorizedAccountProviderException).unauthorisedAccountProviderTitle)
.isEqualTo("example.com")
assertThat((state.authenticationAction.errorOrNull() as UnauthorizedAccountProviderException).authorisedAccountProviderTitles)
.containsExactly("element.io")
}
}
}
@Test
fun `present - scanned QR code failed and can be retried`() = runTest {
val qrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory(
@@ -103,9 +152,11 @@ class QrCodeScanPresenterTest {
qrCodeLoginDataFactory: FakeMatrixQrCodeLoginDataFactory = FakeMatrixQrCodeLoginDataFactory(),
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
qrCodeLoginManager: FakeQrCodeLoginManager = FakeQrCodeLoginManager(),
enterpriseService: EnterpriseService = FakeEnterpriseService(),
) = QrCodeScanPresenter(
qrCodeLoginDataFactory = qrCodeLoginDataFactory,
qrCodeLoginManager = qrCodeLoginManager,
coroutineDispatchers = coroutineDispatchers,
enterpriseService = enterpriseService,
)
}

View File

@@ -424,7 +424,7 @@ class MessageComposerPresenter @AssistedInject constructor(
resetComposer(markdownTextEditorState, richTextEditorState, fromEdit = capturedMode is MessageComposerMode.Edit)
when (capturedMode) {
is MessageComposerMode.Attachment,
is MessageComposerMode.Normal -> room.sendMessage(
is MessageComposerMode.Normal -> room.liveTimeline.sendMessage(
body = message.markdown,
htmlBody = message.html,
intentionalMentions = message.intentionalMentions

View File

@@ -38,29 +38,5 @@
<string name="screen_room_timeline_reactions_show_less">"Dangos llai"</string>
<string name="screen_room_timeline_reactions_show_more">"Dangos rhagor"</string>
<string name="screen_room_timeline_read_marker_title">"Newydd"</string>
<plurals name="screen_room_timeline_state_changes">
<item quantity="zero">"%1$d newid ystafelloedd"</item>
<item quantity="one">"%1$d newid ystafell"</item>
<item quantity="two">"%1$d newid ystafell"</item>
<item quantity="few">"%1$d newid ystafell"</item>
<item quantity="many">"%1$d newid ystafell"</item>
<item quantity="other">"%1$d newid ystafell"</item>
</plurals>
<plurals name="screen_room_typing_many_members">
<item quantity="zero">"%1$s, %2$s a %3$d arall"</item>
<item quantity="one">"%1$s, %2$s a %3$d arall"</item>
<item quantity="two">"%1$s, %2$s a %3$d arall"</item>
<item quantity="few">"%1$s, %2$s a %3$d arall"</item>
<item quantity="many">"%1$s, %2$s a %3$d arall"</item>
<item quantity="other">"%1$s, %2$s a %3$d arall"</item>
</plurals>
<plurals name="screen_room_typing_notification">
<item quantity="zero">"Mae %1$s yn teipio"</item>
<item quantity="one">"Mae %1$s yn teipio"</item>
<item quantity="two">"Mae %1$s yn teipio"</item>
<item quantity="few">"Mae %1$s yn teipio"</item>
<item quantity="many">"Mae %1$s yn teipio"</item>
<item quantity="other">"Mae %1$s yn teipio"</item>
</plurals>
<string name="screen_room_typing_two_members">"%1$s a %2$s"</string>
</resources>

View File

@@ -5,6 +5,8 @@
* Please see LICENSE files in the repository root for full details.
*/
@file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.features.messages.impl
import androidx.lifecycle.Lifecycle
@@ -96,6 +98,7 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Rule
@@ -173,12 +176,14 @@ class MessagesPresenterTest {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId()))
advanceUntilIdle()
assert(toggleReactionSuccess)
.isCalledOnce()
.with(value("👍"), value(AN_EVENT_ID.toEventOrTransactionId()))
// No crashes when sending a reaction failed
timeline.apply { toggleReactionLambda = toggleReactionFailure }
timeline.toggleReactionLambda = toggleReactionFailure
initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId()))
advanceUntilIdle()
assert(toggleReactionFailure)
.isCalledOnce()
.with(value("👍"), value(AN_EVENT_ID.toEventOrTransactionId()))
@@ -209,6 +214,7 @@ class MessagesPresenterTest {
val initialState = awaitItem()
initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId()))
initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId()))
advanceUntilIdle()
assert(toggleReactionSuccess)
.isCalledExactly(2)
.withSequence(

View File

@@ -35,6 +35,7 @@ import io.element.android.libraries.matrix.test.A_CAPTION
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.mediaupload.api.MediaSender
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
@@ -108,15 +109,18 @@ class AttachmentsPreviewPresenterTest {
fun `present - send media success scenario`() = runTest {
val sendFileResult =
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
Result.success(FakeMediaUploadHandler())
}
Result.success(FakeMediaUploadHandler())
}
val room = FakeJoinedRoom(
progressCallbackValues = listOf(
Pair(0, 10),
Pair(5, 10),
Pair(10, 10)
),
sendFileResult = sendFileResult,
liveTimeline = FakeTimeline(
progressCallbackValues = listOf(
Pair(0, 10),
Pair(5, 10),
Pair(10, 10)
),
).apply {
sendFileLambda = sendFileResult
},
)
val onDoneListener = lambdaRecorder<Unit> { }
val presenter = createAttachmentsPreviewPresenter(
@@ -146,10 +150,12 @@ class AttachmentsPreviewPresenterTest {
fun `present - send media after pre-processing success scenario`() = runTest {
val sendFileResult =
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
Result.success(FakeMediaUploadHandler())
}
Result.success(FakeMediaUploadHandler())
}
val room = FakeJoinedRoom(
sendFileResult = sendFileResult,
liveTimeline = FakeTimeline().apply {
sendFileLambda = sendFileResult
},
)
val onDoneListener = lambdaRecorder<Unit> { }
val processLatch = CompletableDeferred<Unit>()
@@ -182,10 +188,12 @@ class AttachmentsPreviewPresenterTest {
fun `present - send media before pre-processing success scenario`() = runTest {
val sendFileResult =
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
Result.success(FakeMediaUploadHandler())
}
Result.success(FakeMediaUploadHandler())
}
val room = FakeJoinedRoom(
sendFileResult = sendFileResult,
liveTimeline = FakeTimeline().apply {
sendFileLambda = sendFileResult
},
)
val onDoneListener = lambdaRecorder<Unit> { }
val processLatch = CompletableDeferred<Unit>()
@@ -298,7 +306,9 @@ class AttachmentsPreviewPresenterTest {
givenImageResult()
}
val room = FakeJoinedRoom(
sendImageResult = sendImageResult,
liveTimeline = FakeTimeline().apply {
sendImageLambda = sendImageResult
},
)
val onDoneListener = lambdaRecorder<Unit> { }
val presenter = createAttachmentsPreviewPresenter(
@@ -340,7 +350,9 @@ class AttachmentsPreviewPresenterTest {
givenVideoResult()
}
val room = FakeJoinedRoom(
sendVideoResult = sendVideoResult,
liveTimeline = FakeTimeline().apply {
sendVideoLambda = sendVideoResult
},
)
val onDoneListener = lambdaRecorder<Unit> { }
val presenter = createAttachmentsPreviewPresenter(
@@ -382,7 +394,9 @@ class AttachmentsPreviewPresenterTest {
givenAudioResult()
}
val room = FakeJoinedRoom(
sendAudioResult = sendAudioResult,
liveTimeline = FakeTimeline().apply {
sendAudioLambda = sendAudioResult
},
)
val onDoneListener = lambdaRecorder<Unit> { }
val presenter = createAttachmentsPreviewPresenter(
@@ -416,10 +430,12 @@ class AttachmentsPreviewPresenterTest {
val failure = MediaPreProcessor.Failure(null)
val sendFileResult =
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
Result.failure(failure)
}
Result.failure(failure)
}
val room = FakeJoinedRoom(
sendFileResult = sendFileResult,
liveTimeline = FakeTimeline().apply {
sendFileLambda = sendFileResult
},
)
val presenter = createAttachmentsPreviewPresenter(room = room, mediaUploadOnSendQueueEnabled = false)
moleculeFlow(RecompositionMode.Immediate) {
@@ -445,11 +461,13 @@ class AttachmentsPreviewPresenterTest {
val failure = MediaPreProcessor.Failure(null)
val sendFileResult =
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
Result.failure(failure)
}
Result.failure(failure)
}
val onDoneListenerResult = lambdaRecorder<Unit> {}
val room = FakeJoinedRoom(
sendFileResult = sendFileResult,
liveTimeline = FakeTimeline().apply {
sendFileLambda = sendFileResult
},
)
val presenter = createAttachmentsPreviewPresenter(room = room, mediaUploadOnSendQueueEnabled = true, onDoneListener = onDoneListenerResult)
moleculeFlow(RecompositionMode.Immediate) {

View File

@@ -384,7 +384,9 @@ class MessageComposerPresenterTest {
val presenter = createPresenter(
coroutineScope = this,
room = FakeJoinedRoom(
sendMessageResult = { _, _, _ -> Result.success(Unit) },
liveTimeline = FakeTimeline().apply {
sendMessageLambda = { _, _, _ -> Result.success(Unit) }
},
typingNoticeResult = { Result.success(Unit) }
),
)
@@ -418,7 +420,9 @@ class MessageComposerPresenterTest {
coroutineScope = this,
isRichTextEditorEnabled = false,
room = FakeJoinedRoom(
sendMessageResult = { _, _, _ -> Result.success(Unit) },
liveTimeline = FakeTimeline().apply {
sendMessageLambda = { _, _, _ -> Result.success(Unit) }
},
typingNoticeResult = { Result.success(Unit) }
),
)
@@ -1118,16 +1122,16 @@ class MessageComposerPresenterTest {
val editMessageLambda = lambdaRecorder { _: EventOrTransactionId, _: String, _: String?, _: List<IntentionalMention> ->
Result.success(Unit)
}
val timeline = FakeTimeline().apply {
this.replyMessageLambda = replyMessageLambda
this.editMessageLambda = editMessageLambda
}
val sendMessageResult = lambdaRecorder { _: String, _: String?, _: List<IntentionalMention> ->
Result.success(Unit)
}
val timeline = FakeTimeline().apply {
this.replyMessageLambda = replyMessageLambda
this.editMessageLambda = editMessageLambda
sendMessageLambda = sendMessageResult
}
val room = FakeJoinedRoom(
liveTimeline = timeline,
sendMessageResult = sendMessageResult,
typingNoticeResult = { Result.success(Unit) }
)
val presenter = createPresenter(room = room, coroutineScope = this)

View File

@@ -24,6 +24,7 @@ import io.element.android.libraries.matrix.api.media.AudioInfo
import io.element.android.libraries.matrix.api.room.message.ReplyParameters
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer
import io.element.android.libraries.mediaupload.api.MediaSender
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
@@ -45,6 +46,7 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
@@ -65,7 +67,9 @@ class VoiceMessageComposerPresenterTest {
Result.success(FakeMediaUploadHandler())
}
private val joinedRoom = FakeJoinedRoom(
sendVoiceMessageResult = sendVoiceMessageResult
liveTimeline = FakeTimeline().apply {
sendVoiceMessageLambda = sendVoiceMessageResult
},
)
private val mediaPreProcessor = FakeMediaPreProcessor().apply { givenAudioResult() }
private val mediaSender = MediaSender(mediaPreProcessor, joinedRoom, InMemorySessionPreferencesStore())
@@ -295,7 +299,6 @@ class VoiceMessageComposerPresenterTest {
awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
assertThat(awaitItem().voiceMessageState).isEqualTo(aPreviewState().toSendingState())
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
sendVoiceMessageResult.assertions().isCalledOnce()
@@ -317,7 +320,7 @@ class VoiceMessageComposerPresenterTest {
awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
skipItems(1) // Sending state
advanceUntilIdle()
// Now reply with a voice message
messageComposerContext.composerMode = aReplyMode()
awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
@@ -653,7 +656,7 @@ class VoiceMessageComposerPresenterTest {
permissionsPresenter: PermissionsPresenter = createFakePermissionsPresenter(),
): VoiceMessageComposerPresenter {
return VoiceMessageComposerPresenter(
this,
backgroundScope,
voiceRecorder,
analyticsService,
mediaSender,

View File

@@ -1,29 +0,0 @@
/*
* 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.onboarding.api
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import io.element.android.libraries.architecture.FeatureEntryPoint
interface OnBoardingEntryPoint : FeatureEntryPoint {
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
interface NodeBuilder {
fun callback(callback: Callback): NodeBuilder
fun build(): Node
}
interface Callback : Plugin {
fun onSignUp()
fun onSignIn()
fun onSignInWithQrCode()
fun onReportProblem()
}
}

View File

@@ -1,52 +0,0 @@
import extension.setupAnvil
/*
* Copyright 2022-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.
*/
plugins {
id("io.element.android-compose-library")
id("kotlin-parcelize")
}
android {
namespace = "io.element.android.features.onboarding.impl"
testOptions {
unitTests {
isIncludeAndroidResources = true
}
}
}
setupAnvil()
dependencies {
implementation(projects.appconfig)
implementation(projects.features.rageshake.api)
implementation(projects.libraries.core)
implementation(projects.libraries.androidutils)
implementation(projects.libraries.architecture)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.featureflag.api)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.testtags)
implementation(projects.libraries.uiStrings)
api(projects.features.onboarding.api)
testImplementation(libs.test.junit)
testImplementation(libs.androidx.compose.ui.test.junit)
testImplementation(libs.androidx.test.ext.junit)
testImplementation(libs.coroutines.test)
testImplementation(libs.molecule.runtime)
testImplementation(libs.test.robolectric)
testImplementation(libs.test.truth)
testImplementation(libs.test.turbine)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.featureflag.test)
testImplementation(projects.tests.testutils)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
}

View File

@@ -1,35 +0,0 @@
/*
* 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.onboarding.impl
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.onboarding.api.OnBoardingEntryPoint
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.AppScope
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultOnBoardingEntryPoint @Inject constructor() : OnBoardingEntryPoint {
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): OnBoardingEntryPoint.NodeBuilder {
return object : OnBoardingEntryPoint.NodeBuilder {
val plugins = ArrayList<Plugin>()
override fun callback(callback: OnBoardingEntryPoint.Callback): OnBoardingEntryPoint.NodeBuilder {
plugins += callback
return this
}
override fun build(): Node {
return parentNode.createNode<OnBoardingNode>(buildContext, plugins)
}
}
}
}

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<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>
</resources>

Some files were not shown because too many files have changed in this diff Show More