First Commit
This commit is contained in:
19
features/ftue/api/build.gradle.kts
Normal file
19
features/ftue/api/build.gradle.kts
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.ftue.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.architecture)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.api
|
||||
|
||||
import io.element.android.libraries.architecture.SimpleFeatureEntryPoint
|
||||
|
||||
interface FtueEntryPoint : SimpleFeatureEntryPoint
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.api.state
|
||||
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
* Service to manage the First Time User Experience state (aka Onboarding).
|
||||
*/
|
||||
interface FtueService {
|
||||
/** The current state of the FTUE. */
|
||||
val state: StateFlow<FtueState>
|
||||
}
|
||||
|
||||
/** The state of the FTUE. */
|
||||
sealed interface FtueState {
|
||||
/** The FTUE state is unknown, nothing to do for now. */
|
||||
data object Unknown : FtueState
|
||||
|
||||
/** The FTUE state is incomplete. The FTUE flow should be displayed. */
|
||||
data object Incomplete : FtueState
|
||||
|
||||
/** The FTUE state is complete. The FTUE flow should not be displayed anymore. */
|
||||
data object Complete : FtueState
|
||||
}
|
||||
60
features/ftue/impl/build.gradle.kts
Normal file
60
features/ftue/impl/build.gradle.kts
Normal file
@@ -0,0 +1,60 @@
|
||||
import extension.setupDependencyInjection
|
||||
import extension.testCommonDependencies
|
||||
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.ftue.impl"
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupDependencyInjection()
|
||||
|
||||
dependencies {
|
||||
api(projects.features.ftue.api)
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.libraries.uiCommon)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.libraries.testtags)
|
||||
implementation(projects.features.analytics.api)
|
||||
implementation(projects.features.logout.api)
|
||||
implementation(projects.features.securebackup.api)
|
||||
implementation(projects.features.verifysession.api)
|
||||
implementation(projects.services.analytics.api)
|
||||
implementation(projects.features.lockscreen.api)
|
||||
implementation(projects.libraries.permissions.api)
|
||||
implementation(projects.libraries.permissions.noop)
|
||||
implementation(projects.services.toolbox.api)
|
||||
implementation(projects.appconfig)
|
||||
|
||||
testCommonDependencies(libs, true)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.services.analytics.test)
|
||||
testImplementation(projects.services.analytics.noop)
|
||||
testImplementation(projects.libraries.permissions.test)
|
||||
testImplementation(projects.libraries.preferences.test)
|
||||
testImplementation(projects.features.lockscreen.test)
|
||||
testImplementation(projects.services.toolbox.test)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.features.ftue.api.FtueEntryPoint
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultFtueEntryPoint : FtueEntryPoint {
|
||||
override fun createNode(parentNode: Node, buildContext: BuildContext): Node {
|
||||
return parentNode.createNode<FtueFlowNode>(buildContext)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||
import com.bumble.appyx.navmodel.backstack.operation.newRoot
|
||||
import com.bumble.appyx.navmodel.backstack.operation.replace
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.features.analytics.api.AnalyticsEntryPoint
|
||||
import io.element.android.features.ftue.impl.notifications.NotificationsOptInNode
|
||||
import io.element.android.features.ftue.impl.sessionverification.FtueSessionVerificationFlowNode
|
||||
import io.element.android.features.ftue.impl.state.DefaultFtueService
|
||||
import io.element.android.features.ftue.impl.state.FtueStep
|
||||
import io.element.android.features.ftue.impl.state.InternalFtueState
|
||||
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
|
||||
import io.element.android.libraries.architecture.BackstackView
|
||||
import io.element.android.libraries.architecture.BaseFlowNode
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.ui.common.nodes.emptyNode
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
@AssistedInject
|
||||
class FtueFlowNode(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val defaultFtueService: DefaultFtueService,
|
||||
private val analyticsEntryPoint: AnalyticsEntryPoint,
|
||||
private val lockScreenEntryPoint: LockScreenEntryPoint,
|
||||
) : BaseFlowNode<FtueFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.Placeholder,
|
||||
savedStateMap = buildContext.savedStateMap,
|
||||
),
|
||||
buildContext = buildContext,
|
||||
plugins = plugins,
|
||||
) {
|
||||
sealed interface NavTarget : Parcelable {
|
||||
@Parcelize
|
||||
data object Placeholder : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object SessionVerification : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object NotificationsOptIn : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object AnalyticsOptIn : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object LockScreenSetup : NavTarget
|
||||
}
|
||||
|
||||
override fun onBuilt() {
|
||||
super.onBuilt()
|
||||
defaultFtueService.ftueStepStateFlow
|
||||
.filterIsInstance(InternalFtueState.Incomplete::class)
|
||||
.onEach {
|
||||
showStep(it.nextStep)
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
NavTarget.Placeholder -> {
|
||||
emptyNode(buildContext)
|
||||
}
|
||||
is NavTarget.SessionVerification -> {
|
||||
val callback = object : FtueSessionVerificationFlowNode.Callback {
|
||||
override fun onDone() {
|
||||
defaultFtueService.onUserCompletedSessionVerification()
|
||||
}
|
||||
}
|
||||
createNode<FtueSessionVerificationFlowNode>(buildContext, listOf(callback))
|
||||
}
|
||||
NavTarget.NotificationsOptIn -> {
|
||||
val callback = object : NotificationsOptInNode.Callback {
|
||||
override fun onNotificationsOptInFinished() {
|
||||
defaultFtueService.updateFtueStep()
|
||||
}
|
||||
}
|
||||
createNode<NotificationsOptInNode>(buildContext, listOf(callback))
|
||||
}
|
||||
NavTarget.AnalyticsOptIn -> {
|
||||
analyticsEntryPoint.createNode(this, buildContext)
|
||||
}
|
||||
NavTarget.LockScreenSetup -> {
|
||||
val callback = object : LockScreenEntryPoint.Callback {
|
||||
override fun onSetupDone() {
|
||||
defaultFtueService.updateFtueStep()
|
||||
}
|
||||
}
|
||||
lockScreenEntryPoint.createNode(
|
||||
parentNode = this,
|
||||
buildContext = buildContext,
|
||||
navTarget = LockScreenEntryPoint.Target.Setup,
|
||||
callback = callback,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showStep(ftueStep: FtueStep) {
|
||||
when (ftueStep) {
|
||||
FtueStep.WaitingForInitialState -> {
|
||||
backstack.newRoot(NavTarget.Placeholder)
|
||||
}
|
||||
FtueStep.SessionVerification -> {
|
||||
backstack.newRoot(NavTarget.SessionVerification)
|
||||
}
|
||||
FtueStep.NotificationsOptIn -> {
|
||||
backstack.newRoot(NavTarget.NotificationsOptIn)
|
||||
}
|
||||
FtueStep.AnalyticsOptIn -> {
|
||||
backstack.replace(NavTarget.AnalyticsOptIn)
|
||||
}
|
||||
FtueStep.LockscreenSetup -> {
|
||||
backstack.newRoot(NavTarget.LockScreenSetup)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
BackstackView()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.di
|
||||
|
||||
import dev.zacsweers.metro.BindingContainer
|
||||
import dev.zacsweers.metro.Binds
|
||||
import dev.zacsweers.metro.ContributesTo
|
||||
import io.element.android.features.ftue.impl.sessionverification.choosemode.ChooseSelfVerificationModePresenter
|
||||
import io.element.android.features.ftue.impl.sessionverification.choosemode.ChooseSelfVerificationModeState
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
||||
@ContributesTo(SessionScope::class)
|
||||
@BindingContainer
|
||||
interface FtueModule {
|
||||
@Binds
|
||||
fun bindChooseSelfVerificationMethodPresenter(presenter: ChooseSelfVerificationModePresenter): Presenter<ChooseSelfVerificationModeState>
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.notifications
|
||||
|
||||
sealed interface NotificationsOptInEvents {
|
||||
data object ContinueClicked : NotificationsOptInEvents
|
||||
data object NotNowClicked : NotificationsOptInEvents
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.notifications
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
|
||||
@ContributesNode(AppScope::class)
|
||||
@AssistedInject
|
||||
class NotificationsOptInNode(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
presenterFactory: NotificationsOptInPresenter.Factory,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
interface Callback : NodeInputs {
|
||||
fun onNotificationsOptInFinished()
|
||||
}
|
||||
|
||||
private val callback = inputs<Callback>()
|
||||
|
||||
private val presenter: NotificationsOptInPresenter = presenterFactory.create(callback)
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
NotificationsOptInView(
|
||||
state = state,
|
||||
onBack = { callback.onNotificationsOptInFinished() },
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.notifications
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedFactory
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||
import io.element.android.libraries.permissions.api.PermissionStateProvider
|
||||
import io.element.android.libraries.permissions.api.PermissionsEvents
|
||||
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
||||
import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter
|
||||
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AssistedInject
|
||||
class NotificationsOptInPresenter(
|
||||
permissionsPresenterFactory: PermissionsPresenter.Factory,
|
||||
@Assisted private val callback: NotificationsOptInNode.Callback,
|
||||
@AppCoroutineScope
|
||||
private val appCoroutineScope: CoroutineScope,
|
||||
private val permissionStateProvider: PermissionStateProvider,
|
||||
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
|
||||
) : Presenter<NotificationsOptInState> {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(callback: NotificationsOptInNode.Callback): NotificationsOptInPresenter
|
||||
}
|
||||
|
||||
private val postNotificationPermissionsPresenter: PermissionsPresenter =
|
||||
// Ask for POST_NOTIFICATION PERMISSION on Android 13+
|
||||
if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
|
||||
permissionsPresenterFactory.create(Manifest.permission.POST_NOTIFICATIONS)
|
||||
} else {
|
||||
NoopPermissionsPresenter()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): NotificationsOptInState {
|
||||
val notificationsPermissionsState = postNotificationPermissionsPresenter.present()
|
||||
|
||||
fun handleEvent(event: NotificationsOptInEvents) {
|
||||
when (event) {
|
||||
NotificationsOptInEvents.ContinueClicked -> {
|
||||
if (notificationsPermissionsState.permissionGranted) {
|
||||
callback.onNotificationsOptInFinished()
|
||||
} else {
|
||||
notificationsPermissionsState.eventSink(PermissionsEvents.RequestPermissions)
|
||||
}
|
||||
}
|
||||
NotificationsOptInEvents.NotNowClicked -> {
|
||||
if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
|
||||
appCoroutineScope.setPermissionDenied()
|
||||
}
|
||||
callback.onNotificationsOptInFinished()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(notificationsPermissionsState) {
|
||||
if (notificationsPermissionsState.permissionGranted ||
|
||||
notificationsPermissionsState.permissionAlreadyDenied) {
|
||||
callback.onNotificationsOptInFinished()
|
||||
}
|
||||
}
|
||||
|
||||
return NotificationsOptInState(
|
||||
notificationsPermissionState = notificationsPermissionsState,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
private fun CoroutineScope.setPermissionDenied() = launch {
|
||||
permissionStateProvider.setPermissionDenied(Manifest.permission.POST_NOTIFICATIONS, true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.notifications
|
||||
|
||||
import io.element.android.libraries.permissions.api.PermissionsState
|
||||
|
||||
data class NotificationsOptInState(
|
||||
val notificationsPermissionState: PermissionsState,
|
||||
val eventSink: (NotificationsOptInEvents) -> Unit
|
||||
)
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.notifications
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.permissions.api.aPermissionsState
|
||||
|
||||
open class NotificationsOptInStateProvider : PreviewParameterProvider<NotificationsOptInState> {
|
||||
override val values: Sequence<NotificationsOptInState>
|
||||
get() = sequenceOf(
|
||||
aNotificationsOptInState(),
|
||||
// Add other states here
|
||||
)
|
||||
}
|
||||
|
||||
fun aNotificationsOptInState() = NotificationsOptInState(
|
||||
notificationsPermissionState = aPermissionsState(showDialog = false),
|
||||
eventSink = {}
|
||||
)
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.notifications
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
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.ftue.impl.R
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.background.OnboardingBackground
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.Surface
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Composable
|
||||
fun NotificationsOptInView(
|
||||
state: NotificationsOptInState,
|
||||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BackHandler(onBack = onBack)
|
||||
|
||||
HeaderFooterPage(
|
||||
modifier = modifier
|
||||
.statusBarsPadding()
|
||||
.fillMaxSize(),
|
||||
background = { OnboardingBackground() },
|
||||
header = { NotificationsOptInHeader(modifier = Modifier.padding(top = 60.dp, bottom = 28.dp)) },
|
||||
footer = { NotificationsOptInFooter(state) },
|
||||
) {
|
||||
NotificationsOptInContent()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NotificationsOptInHeader(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
IconTitleSubtitleMolecule(
|
||||
modifier = modifier,
|
||||
title = stringResource(R.string.screen_notification_optin_title),
|
||||
subTitle = stringResource(R.string.screen_notification_optin_subtitle),
|
||||
iconStyle = BigIcon.Style.Default(CompoundIcons.NotificationsSolid()),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NotificationsOptInFooter(state: NotificationsOptInState) {
|
||||
ButtonColumnMolecule {
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(CommonStrings.action_ok),
|
||||
onClick = {
|
||||
state.eventSink(NotificationsOptInEvents.ContinueClicked)
|
||||
}
|
||||
)
|
||||
TextButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(CommonStrings.action_not_now),
|
||||
onClick = {
|
||||
state.eventSink(NotificationsOptInEvents.NotNowClicked)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NotificationsOptInContent() {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(
|
||||
16.dp,
|
||||
alignment = Alignment.CenterVertically
|
||||
)
|
||||
) {
|
||||
NotificationRow(
|
||||
avatarLetter = "M",
|
||||
avatarColorsId = "5",
|
||||
firstRowPercent = 1f,
|
||||
secondRowPercent = 0.4f
|
||||
)
|
||||
|
||||
NotificationRow(
|
||||
avatarLetter = "A",
|
||||
avatarColorsId = "1",
|
||||
firstRowPercent = 1f,
|
||||
secondRowPercent = 1f
|
||||
)
|
||||
|
||||
NotificationRow(
|
||||
avatarLetter = "T",
|
||||
avatarColorsId = "4",
|
||||
firstRowPercent = 0.65f,
|
||||
secondRowPercent = 0f
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NotificationRow(
|
||||
avatarLetter: String,
|
||||
avatarColorsId: String,
|
||||
firstRowPercent: Float,
|
||||
secondRowPercent: Float,
|
||||
) {
|
||||
Surface(
|
||||
color = ElementTheme.colors.bgCanvasDisabled,
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
shadowElevation = 2.dp,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Avatar(
|
||||
avatarData = AvatarData(id = avatarColorsId, name = avatarLetter, size = AvatarSize.NotificationsOptIn),
|
||||
avatarType = AvatarType.User,
|
||||
)
|
||||
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.fillMaxWidth(firstRowPercent)
|
||||
.height(10.dp)
|
||||
.background(ElementTheme.colors.borderInteractiveSecondary)
|
||||
)
|
||||
if (secondRowPercent > 0f) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.fillMaxWidth(secondRowPercent)
|
||||
.height(10.dp)
|
||||
.background(ElementTheme.colors.borderInteractiveSecondary)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun NotificationsOptInViewPreview(
|
||||
@PreviewParameter(NotificationsOptInStateProvider::class) state: NotificationsOptInState
|
||||
) {
|
||||
ElementPreview {
|
||||
NotificationsOptInView(
|
||||
onBack = {},
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.sessionverification
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
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.navmodel.backstack.BackStack
|
||||
import com.bumble.appyx.navmodel.backstack.operation.newRoot
|
||||
import com.bumble.appyx.navmodel.backstack.operation.pop
|
||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.appconfig.LearnMoreConfig
|
||||
import io.element.android.features.ftue.impl.sessionverification.choosemode.ChooseSelfVerificationModeNode
|
||||
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
|
||||
import io.element.android.features.verifysession.api.OutgoingVerificationEntryPoint
|
||||
import io.element.android.libraries.architecture.BackstackView
|
||||
import io.element.android.libraries.architecture.BaseFlowNode
|
||||
import io.element.android.libraries.architecture.callback
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.designsystem.utils.OpenUrlInTabView
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
@AssistedInject
|
||||
class FtueSessionVerificationFlowNode(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val outgoingVerificationEntryPoint: OutgoingVerificationEntryPoint,
|
||||
private val secureBackupEntryPoint: SecureBackupEntryPoint,
|
||||
) : BaseFlowNode<FtueSessionVerificationFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.Root,
|
||||
savedStateMap = buildContext.savedStateMap,
|
||||
),
|
||||
buildContext = buildContext,
|
||||
plugins = plugins,
|
||||
) {
|
||||
sealed interface NavTarget : Parcelable {
|
||||
@Parcelize
|
||||
data object Root : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object UseAnotherDevice : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object EnterRecoveryKey : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object ResetIdentity : NavTarget
|
||||
}
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun onDone()
|
||||
}
|
||||
|
||||
private val callback: Callback = callback()
|
||||
|
||||
private val secureBackupEntryPointCallback = object : SecureBackupEntryPoint.Callback {
|
||||
override fun onDone() {
|
||||
lifecycleScope.launch {
|
||||
// Move to the completed state view in the verification flow
|
||||
backstack.newRoot(NavTarget.UseAnotherDevice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
is NavTarget.Root -> {
|
||||
val callback = object : ChooseSelfVerificationModeNode.Callback {
|
||||
override fun navigateToUseAnotherDevice() {
|
||||
backstack.push(NavTarget.UseAnotherDevice)
|
||||
}
|
||||
|
||||
override fun navigateToUseRecoveryKey() {
|
||||
backstack.push(NavTarget.EnterRecoveryKey)
|
||||
}
|
||||
|
||||
override fun navigateToResetKey() {
|
||||
backstack.push(NavTarget.ResetIdentity)
|
||||
}
|
||||
|
||||
override fun navigateToLearnMoreAboutEncryption() {
|
||||
learnMoreUrl.value = LearnMoreConfig.DEVICE_VERIFICATION_URL
|
||||
}
|
||||
}
|
||||
createNode<ChooseSelfVerificationModeNode>(buildContext, plugins = listOf(callback))
|
||||
}
|
||||
is NavTarget.UseAnotherDevice -> {
|
||||
outgoingVerificationEntryPoint.createNode(
|
||||
parentNode = this,
|
||||
buildContext = buildContext,
|
||||
params = OutgoingVerificationEntryPoint.Params(
|
||||
showDeviceVerifiedScreen = true,
|
||||
verificationRequest = VerificationRequest.Outgoing.CurrentSession,
|
||||
),
|
||||
callback = object : OutgoingVerificationEntryPoint.Callback {
|
||||
override fun onDone() {
|
||||
callback.onDone()
|
||||
}
|
||||
|
||||
override fun onBack() {
|
||||
backstack.pop()
|
||||
}
|
||||
|
||||
override fun navigateToLearnMoreAboutEncryption() {
|
||||
// Note that this callback is never called. The "Learn more" link is not displayed
|
||||
// for the self session interactive verification.
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
is NavTarget.EnterRecoveryKey -> {
|
||||
secureBackupEntryPoint.createNode(
|
||||
parentNode = this,
|
||||
buildContext = buildContext,
|
||||
params = SecureBackupEntryPoint.Params(SecureBackupEntryPoint.InitialTarget.EnterRecoveryKey),
|
||||
callback = secureBackupEntryPointCallback
|
||||
)
|
||||
}
|
||||
is NavTarget.ResetIdentity -> {
|
||||
secureBackupEntryPoint.createNode(
|
||||
parentNode = this,
|
||||
buildContext = buildContext,
|
||||
params = SecureBackupEntryPoint.Params(SecureBackupEntryPoint.InitialTarget.ResetIdentity),
|
||||
callback = object : SecureBackupEntryPoint.Callback {
|
||||
override fun onDone() {
|
||||
callback.onDone()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val learnMoreUrl = mutableStateOf<String?>(null)
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
BackstackView()
|
||||
|
||||
OpenUrlInTabView(learnMoreUrl)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.sessionverification.choosemode
|
||||
|
||||
sealed interface ChooseSelfVerificationModeEvent {
|
||||
data object SignOut : ChooseSelfVerificationModeEvent
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.sessionverification.choosemode
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutView
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.callback
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
@AssistedInject
|
||||
class ChooseSelfVerificationModeNode(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: Presenter<ChooseSelfVerificationModeState>,
|
||||
private val directLogoutView: DirectLogoutView,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
interface Callback : Plugin {
|
||||
fun navigateToUseAnotherDevice()
|
||||
fun navigateToUseRecoveryKey()
|
||||
fun navigateToResetKey()
|
||||
fun navigateToLearnMoreAboutEncryption()
|
||||
}
|
||||
|
||||
private val callback: Callback = callback()
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
|
||||
ChooseSelfVerificationModeView(
|
||||
state = state,
|
||||
onUseAnotherDevice = callback::navigateToUseAnotherDevice,
|
||||
onUseRecoveryKey = callback::navigateToUseRecoveryKey,
|
||||
onResetKey = callback::navigateToResetKey,
|
||||
onLearnMore = callback::navigateToLearnMoreAboutEncryption,
|
||||
modifier = modifier,
|
||||
)
|
||||
|
||||
directLogoutView.Render(state = state.directLogoutState)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.sessionverification.choosemode
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutEvents
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.mapState
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||
|
||||
@Inject
|
||||
class ChooseSelfVerificationModePresenter(
|
||||
private val encryptionService: EncryptionService,
|
||||
private val directLogoutPresenter: Presenter<DirectLogoutState>,
|
||||
) : Presenter<ChooseSelfVerificationModeState> {
|
||||
@Composable
|
||||
override fun present(): ChooseSelfVerificationModeState {
|
||||
val hasDevicesToVerifyAgainst by encryptionService.hasDevicesToVerifyAgainst.collectAsState()
|
||||
val canEnterRecoveryKey by encryptionService.recoveryStateStateFlow
|
||||
.mapState { recoveryState ->
|
||||
when (recoveryState) {
|
||||
RecoveryState.WAITING_FOR_SYNC,
|
||||
RecoveryState.UNKNOWN -> AsyncData.Loading()
|
||||
RecoveryState.INCOMPLETE -> AsyncData.Success(true)
|
||||
RecoveryState.ENABLED,
|
||||
RecoveryState.DISABLED -> AsyncData.Success(false)
|
||||
}
|
||||
}
|
||||
.collectAsState()
|
||||
val buttonsState by remember {
|
||||
derivedStateOf {
|
||||
val canUseAnotherDevice = hasDevicesToVerifyAgainst.dataOrNull()
|
||||
val canEnterRecoveryKey = canEnterRecoveryKey.dataOrNull()
|
||||
if (canUseAnotherDevice == null || canEnterRecoveryKey == null) {
|
||||
AsyncData.Loading()
|
||||
} else {
|
||||
AsyncData.Success(
|
||||
ChooseSelfVerificationModeState.ButtonsState(
|
||||
canUseAnotherDevice = canUseAnotherDevice,
|
||||
canEnterRecoveryKey = canEnterRecoveryKey,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val directLogoutState = directLogoutPresenter.present()
|
||||
|
||||
fun handleEvent(event: ChooseSelfVerificationModeEvent) {
|
||||
when (event) {
|
||||
ChooseSelfVerificationModeEvent.SignOut -> directLogoutState.eventSink(DirectLogoutEvents.Logout(ignoreSdkError = false))
|
||||
}
|
||||
}
|
||||
|
||||
return ChooseSelfVerificationModeState(
|
||||
buttonsState = buttonsState,
|
||||
directLogoutState = directLogoutState,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.sessionverification.choosemode
|
||||
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
|
||||
data class ChooseSelfVerificationModeState(
|
||||
val buttonsState: AsyncData<ButtonsState>,
|
||||
val directLogoutState: DirectLogoutState,
|
||||
val eventSink: (ChooseSelfVerificationModeEvent) -> Unit,
|
||||
) {
|
||||
data class ButtonsState(
|
||||
val canUseAnotherDevice: Boolean,
|
||||
val canEnterRecoveryKey: Boolean,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.sessionverification.choosemode
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.logout.api.direct.aDirectLogoutState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
|
||||
class ChooseSelfVerificationModeStateProvider :
|
||||
PreviewParameterProvider<ChooseSelfVerificationModeState> {
|
||||
override val values = sequenceOf(
|
||||
aChooseSelfVerificationModeState(
|
||||
buttonsState = AsyncData.Success(
|
||||
aButtonsState(canUseAnotherDevice = false, canEnterRecoveryKey = true),
|
||||
),
|
||||
),
|
||||
aChooseSelfVerificationModeState(
|
||||
buttonsState = AsyncData.Success(
|
||||
aButtonsState(canUseAnotherDevice = false, canEnterRecoveryKey = false),
|
||||
),
|
||||
),
|
||||
aChooseSelfVerificationModeState(
|
||||
buttonsState = AsyncData.Success(
|
||||
aButtonsState(canUseAnotherDevice = true, canEnterRecoveryKey = true),
|
||||
),
|
||||
),
|
||||
aChooseSelfVerificationModeState(
|
||||
buttonsState = AsyncData.Success(
|
||||
aButtonsState(canUseAnotherDevice = true, canEnterRecoveryKey = false),
|
||||
),
|
||||
),
|
||||
aChooseSelfVerificationModeState(
|
||||
buttonsState = AsyncData.Loading(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun aChooseSelfVerificationModeState(
|
||||
buttonsState: AsyncData<ChooseSelfVerificationModeState.ButtonsState> = AsyncData.Success(aButtonsState()),
|
||||
) = ChooseSelfVerificationModeState(
|
||||
buttonsState = buttonsState,
|
||||
directLogoutState = aDirectLogoutState(),
|
||||
eventSink = {},
|
||||
)
|
||||
|
||||
fun aButtonsState(
|
||||
canUseAnotherDevice: Boolean = true,
|
||||
canEnterRecoveryKey: Boolean = true,
|
||||
) = ChooseSelfVerificationModeState.ButtonsState(
|
||||
canUseAnotherDevice = canUseAnotherDevice,
|
||||
canEnterRecoveryKey = canEnterRecoveryKey,
|
||||
)
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.sessionverification.choosemode
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
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.ftue.impl.R
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ChooseSelfVerificationModeView(
|
||||
state: ChooseSelfVerificationModeState,
|
||||
onUseAnotherDevice: () -> Unit,
|
||||
onUseRecoveryKey: () -> Unit,
|
||||
onResetKey: () -> Unit,
|
||||
onLearnMore: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val activity = LocalActivity.current
|
||||
BackHandler {
|
||||
activity?.finish()
|
||||
}
|
||||
HeaderFooterPage(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {},
|
||||
actions = {
|
||||
TextButton(
|
||||
text = stringResource(CommonStrings.action_signout),
|
||||
onClick = { state.eventSink(ChooseSelfVerificationModeEvent.SignOut) }
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
header = {
|
||||
IconTitleSubtitleMolecule(
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
iconStyle = BigIcon.Style.Default(CompoundIcons.LockSolid()),
|
||||
title = stringResource(id = R.string.screen_identity_confirmation_title),
|
||||
subTitle = stringResource(id = R.string.screen_identity_confirmation_subtitle)
|
||||
)
|
||||
},
|
||||
footer = {
|
||||
ChooseSelfVerificationModeButtons(
|
||||
state = state,
|
||||
onUseAnotherDevice = onUseAnotherDevice,
|
||||
onUseRecoveryKey = onUseRecoveryKey,
|
||||
onResetKey = onResetKey,
|
||||
)
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.clickable(onClick = onLearnMore)
|
||||
.padding(vertical = 4.dp, horizontal = 16.dp),
|
||||
text = stringResource(CommonStrings.action_learn_more),
|
||||
style = ElementTheme.typography.fontBodyLgMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ChooseSelfVerificationModeButtons(
|
||||
state: ChooseSelfVerificationModeState,
|
||||
onUseAnotherDevice: () -> Unit,
|
||||
onUseRecoveryKey: () -> Unit,
|
||||
onResetKey: () -> Unit,
|
||||
) {
|
||||
ButtonColumnMolecule(
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
) {
|
||||
when (state.buttonsState) {
|
||||
AsyncData.Uninitialized,
|
||||
is AsyncData.Failure,
|
||||
is AsyncData.Loading -> {
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = false,
|
||||
showProgress = true,
|
||||
text = stringResource(CommonStrings.common_loading),
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
is AsyncData.Success -> {
|
||||
if (state.buttonsState.data.canUseAnotherDevice) {
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(R.string.screen_identity_use_another_device),
|
||||
onClick = onUseAnotherDevice,
|
||||
)
|
||||
}
|
||||
if (state.buttonsState.data.canEnterRecoveryKey) {
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(R.string.screen_session_verification_enter_recovery_key),
|
||||
onClick = onUseRecoveryKey,
|
||||
)
|
||||
}
|
||||
OutlinedButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(R.string.screen_identity_confirmation_cannot_confirm),
|
||||
onClick = onResetKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun ChooseSelfVerificationModeViewPreview(
|
||||
@PreviewParameter(ChooseSelfVerificationModeStateProvider::class) state: ChooseSelfVerificationModeState
|
||||
) = ElementPreview {
|
||||
ChooseSelfVerificationModeView(
|
||||
state = state,
|
||||
onUseAnotherDevice = {},
|
||||
onUseRecoveryKey = {},
|
||||
onResetKey = {},
|
||||
onLearnMore = {},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.state
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import io.element.android.features.ftue.api.state.FtueService
|
||||
import io.element.android.features.ftue.api.state.FtueState
|
||||
import io.element.android.features.lockscreen.api.LockScreenService
|
||||
import io.element.android.libraries.core.coroutine.mapState
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||
import io.element.android.libraries.permissions.api.PermissionStateProvider
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
@SingleIn(SessionScope::class)
|
||||
class DefaultFtueService(
|
||||
private val sdkVersionProvider: BuildVersionSdkIntProvider,
|
||||
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val permissionStateProvider: PermissionStateProvider,
|
||||
private val lockScreenService: LockScreenService,
|
||||
private val sessionVerificationService: SessionVerificationService,
|
||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||
) : FtueService {
|
||||
private val userNeedsToConfirmSessionVerificationSuccess = MutableStateFlow(false)
|
||||
|
||||
val ftueStepStateFlow = MutableStateFlow<InternalFtueState>(InternalFtueState.Unknown)
|
||||
|
||||
override val state = ftueStepStateFlow
|
||||
.mapState {
|
||||
when (it) {
|
||||
is InternalFtueState.Unknown -> FtueState.Unknown
|
||||
is InternalFtueState.Incomplete -> FtueState.Incomplete
|
||||
is InternalFtueState.Complete -> FtueState.Complete
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
combine(
|
||||
sessionVerificationService.sessionVerifiedStatus.onEach { sessionVerifiedStatus ->
|
||||
if (sessionVerifiedStatus == SessionVerifiedStatus.NotVerified) {
|
||||
// Ensure we wait for the user to confirm the session verified screen before going further
|
||||
userNeedsToConfirmSessionVerificationSuccess.value = true
|
||||
}
|
||||
},
|
||||
userNeedsToConfirmSessionVerificationSuccess,
|
||||
analyticsService.didAskUserConsentFlow.distinctUntilChanged(),
|
||||
) {
|
||||
updateFtueStep()
|
||||
}
|
||||
.launchIn(sessionCoroutineScope)
|
||||
}
|
||||
|
||||
fun updateFtueStep() = sessionCoroutineScope.launch {
|
||||
val step = getNextStep(null)
|
||||
ftueStepStateFlow.value = when (step) {
|
||||
null -> InternalFtueState.Complete
|
||||
else -> InternalFtueState.Incomplete(step)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getNextStep(completedStep: FtueStep? = null): FtueStep? =
|
||||
when (completedStep) {
|
||||
null -> if (!isSessionVerificationStateReady()) {
|
||||
FtueStep.WaitingForInitialState
|
||||
} else {
|
||||
getNextStep(FtueStep.WaitingForInitialState)
|
||||
}
|
||||
FtueStep.WaitingForInitialState -> if (isSessionNotVerified() || userNeedsToConfirmSessionVerificationSuccess.value) {
|
||||
FtueStep.SessionVerification
|
||||
} else {
|
||||
getNextStep(FtueStep.SessionVerification)
|
||||
}
|
||||
FtueStep.SessionVerification -> if (shouldAskNotificationPermissions()) {
|
||||
FtueStep.NotificationsOptIn
|
||||
} else {
|
||||
getNextStep(FtueStep.NotificationsOptIn)
|
||||
}
|
||||
FtueStep.NotificationsOptIn -> if (shouldDisplayLockscreenSetup()) {
|
||||
FtueStep.LockscreenSetup
|
||||
} else {
|
||||
getNextStep(FtueStep.LockscreenSetup)
|
||||
}
|
||||
FtueStep.LockscreenSetup -> if (needsAnalyticsOptIn()) {
|
||||
FtueStep.AnalyticsOptIn
|
||||
} else {
|
||||
getNextStep(FtueStep.AnalyticsOptIn)
|
||||
}
|
||||
FtueStep.AnalyticsOptIn -> null
|
||||
}
|
||||
|
||||
private fun isSessionVerificationStateReady(): Boolean {
|
||||
return sessionVerificationService.sessionVerifiedStatus.value != SessionVerifiedStatus.Unknown
|
||||
}
|
||||
|
||||
private suspend fun isSessionNotVerified(): Boolean {
|
||||
return sessionVerificationService.sessionVerifiedStatus.value == SessionVerifiedStatus.NotVerified && !canSkipVerification()
|
||||
}
|
||||
|
||||
private suspend fun canSkipVerification(): Boolean {
|
||||
return sessionPreferencesStore.isSessionVerificationSkipped().first()
|
||||
}
|
||||
|
||||
private suspend fun needsAnalyticsOptIn(): Boolean {
|
||||
return analyticsService.didAskUserConsentFlow.first().not()
|
||||
}
|
||||
|
||||
private suspend fun shouldAskNotificationPermissions(): Boolean {
|
||||
return if (sdkVersionProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
|
||||
val permission = Manifest.permission.POST_NOTIFICATIONS
|
||||
val isPermissionDenied = permissionStateProvider.isPermissionDenied(permission).first()
|
||||
val isPermissionGranted = permissionStateProvider.isPermissionGranted(permission)
|
||||
!isPermissionGranted && !isPermissionDenied
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun shouldDisplayLockscreenSetup(): Boolean {
|
||||
return lockScreenService.isSetupRequired().first()
|
||||
}
|
||||
|
||||
fun onUserCompletedSessionVerification() {
|
||||
userNeedsToConfirmSessionVerificationSuccess.value = false
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface FtueStep {
|
||||
data object WaitingForInitialState : FtueStep
|
||||
data object SessionVerification : FtueStep
|
||||
data object NotificationsOptIn : FtueStep
|
||||
data object AnalyticsOptIn : FtueStep
|
||||
data object LockscreenSetup : FtueStep
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.state
|
||||
|
||||
sealed interface InternalFtueState {
|
||||
data object Unknown : InternalFtueState
|
||||
|
||||
data class Incomplete(
|
||||
val nextStep: FtueStep,
|
||||
) : InternalFtueState
|
||||
|
||||
data object Complete : InternalFtueState
|
||||
}
|
||||
16
features/ftue/impl/src/main/res/values-be/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-be/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Не можаце пацвердзіць?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Стварыць новы ключ аднаўлення"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Пацвердзіце гэтую прыладу, каб наладзіць бяспечны абмен паведамленнямі."</string>
|
||||
<string name="screen_identity_confirmation_title">"Пацвердзіце, што гэта вы"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Выкарыстоўвайце іншую прыладу"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Выкарыстоўваць ключ аднаўлення"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Цяпер вы можаце бяспечна чытаць і адпраўляць паведамленні, і ўсе, з кім вы маеце зносіны ў чаце, таксама могуць давяраць гэтай прыладзе."</string>
|
||||
<string name="screen_identity_confirmed_title">"Прылада праверана"</string>
|
||||
<string name="screen_identity_use_another_device">"Выкарыстоўвайце іншую прыладу"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Чаканне на іншай прыладзе…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Вы можаце змяніць налады пазней."</string>
|
||||
<string name="screen_notification_optin_title">"Дазвольце апавяшчэнні і ніколі не прапускайце іх"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Увядзіце ключ аднаўлення"</string>
|
||||
</resources>
|
||||
13
features/ftue/impl/src/main/res/values-bg/translations.xml
Normal file
13
features/ftue/impl/src/main/res/values-bg/translations.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Не можете да потвърдите?"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Потвърдете това устройство, за да настроите защитени съобщения."</string>
|
||||
<string name="screen_identity_confirmation_title">"Потвърдете самоличността си"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Използване на друго устройство"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Използване на ключ за възстановяване"</string>
|
||||
<string name="screen_identity_confirmed_title">"Устройството е потвърдено"</string>
|
||||
<string name="screen_identity_use_another_device">"Използване на друго устройство"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Можете да промените настройките си по-късно."</string>
|
||||
<string name="screen_notification_optin_title">"Разрешете известията и никога не пропускайте съобщение"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Въвеждане на ключ за възстановяване"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-cs/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-cs/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Nemůžete potvrdit?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Vytvoření nového klíče pro obnovení"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Ověřte toto zařízení a nastavte zabezpečené zasílání zpráv."</string>
|
||||
<string name="screen_identity_confirmation_title">"Potvrďte, že jste to vy"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Použít jiné zařízení"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Použít klíč pro obnovení"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Nyní můžete bezpečně číst nebo odesílat zprávy, a kdokoli, s kým chatujete, může tomuto zařízení důvěřovat."</string>
|
||||
<string name="screen_identity_confirmed_title">"Zařízení ověřeno"</string>
|
||||
<string name="screen_identity_use_another_device">"Použít jiné zařízení"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Čekání na jiném zařízení…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Nastavení můžete později změnit."</string>
|
||||
<string name="screen_notification_optin_title">"Povolte oznámení a nezmeškejte žádnou zprávu"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Zadejte klíč pro obnovení"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-cy/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-cy/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Methu cadarnhau?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Crëwch allwedd adfer newydd"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Dilyswch y ddyfais hon er mwyn gosod negeseuon diogel."</string>
|
||||
<string name="screen_identity_confirmation_title">"Cadarnhewch eich hunaniaeth"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Defnyddiwch ddyfais arall"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Defnyddiwch allwedd adfer"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Nawr gallwch chi ddarllen neu anfon negeseuon yn ddiogel, a gall unrhyw un rydych chi\'n sgwrsio â nhw ymddiried yn y ddyfais hon hefyd."</string>
|
||||
<string name="screen_identity_confirmed_title">"Dyfais wedi\'i dilysu"</string>
|
||||
<string name="screen_identity_use_another_device">"Defnyddiwch ddyfais arall"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Yn aros ar ddyfais arall…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Gallwch newid eich gosodiadau yn nes ymlaen."</string>
|
||||
<string name="screen_notification_optin_title">"Caniatáu hysbysiadau a pheidio byth â cholli neges"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Rhowch eich allwedd adfer"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-da/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-da/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Kan ikke bekræfte?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Opret en ny gendannelsesnøgle"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Verificér denne enhed for at konfigurere sikre meddelelser."</string>
|
||||
<string name="screen_identity_confirmation_title">"Bekræft din identitet"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Brug en anden enhed"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Brug gendannelsesnøgle"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Nu kan du læse eller sende beskeder sikkert, og enhver du samtaler med kan også stole på denne enhed."</string>
|
||||
<string name="screen_identity_confirmed_title">"Enhed verificeret"</string>
|
||||
<string name="screen_identity_use_another_device">"Brug en anden enhed"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Venter på en anden enhed…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Du kan ændre dine indstillinger senere."</string>
|
||||
<string name="screen_notification_optin_title">"Tillad notifikationer, og gå aldrig glip af en besked"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Indtast gendannelsesnøgle"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-de/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-de/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Bestätigung unmöglich?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Erstelle einen neuen Wiederherstellungsschlüssel"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Verifiziere dieses Gerät, um sichere Chats einzurichten."</string>
|
||||
<string name="screen_identity_confirmation_title">"Bestätige deine Identität"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Ein anderes Gerät verwenden"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Wiederherstellungsschlüssel verwenden"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Du kannst jetzt verschlüsselte Nachrichten lesen und versenden. Dein Chatpartner vertraut nun diesem Gerät."</string>
|
||||
<string name="screen_identity_confirmed_title">"Gerät verifiziert"</string>
|
||||
<string name="screen_identity_use_another_device">"Ein anderes Gerät verwenden"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Bitte warten bis das andere Gerät bereit ist."</string>
|
||||
<string name="screen_notification_optin_subtitle">"Du kannst deine Einstellungen später ändern."</string>
|
||||
<string name="screen_notification_optin_title">"Erlaube Benachrichtigungen und verpasse keine Nachricht"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Wiederherstellungsschlüssel eingeben"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-el/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-el/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Δεν μπορείς να επιβεβαιώσεις;"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Δημιουργία νέου κλειδιού ανάκτησης"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Επαλήθευσε αυτήν τη συσκευή για να ρυθμίσεις την ασφαλή επικοινωνία."</string>
|
||||
<string name="screen_identity_confirmation_title">"Επιβεβαίωσε ότι είσαι εσύ"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Χρήση άλλης συσκευής"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Χρήση κλειδιού ανάκτησης"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Τώρα μπορείς να διαβάζεις ή να στέλνεις μηνύματα με ασφάλεια και επίσης μπορεί να εμπιστευτεί αυτήν τη συσκευή οποιοσδήποτε με τον οποίο συνομιλείς."</string>
|
||||
<string name="screen_identity_confirmed_title">"Επαληθευμένη συσκευή"</string>
|
||||
<string name="screen_identity_use_another_device">"Χρήση άλλης συσκευής"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Αναμονή σε άλλη συσκευή…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Μπορείς να αλλάξεις τις ρυθμίσεις σου αργότερα."</string>
|
||||
<string name="screen_notification_optin_title">"Επέτρεψε τις ειδοποιήσεις και μην χάσεις ούτε ένα μήνυμα"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Εισαγωγή κλειδιού ανάκτησης"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-es/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-es/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"¿No puedes confirmar?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Crear una nueva clave de recuperación"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Verifica este dispositivo para configurar la mensajería segura."</string>
|
||||
<string name="screen_identity_confirmation_title">"Confirma que eres tú"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Usar otro dispositivo"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Usar clave de recuperación"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Ahora puedes leer o enviar mensajes de forma segura y cualquier persona con la que chatees también puede confiar en este dispositivo."</string>
|
||||
<string name="screen_identity_confirmed_title">"Dispositivo verificado"</string>
|
||||
<string name="screen_identity_use_another_device">"Usar otro dispositivo"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Esperando en otro dispositivo…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Puedes cambiar la configuración más tarde."</string>
|
||||
<string name="screen_notification_optin_title">"Activa las notificaciones y nunca te pierdas un mensaje"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Introduce la clave de recuperación"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-et/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-et/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Kas kinnitamine pole võimalik?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Loo uus taastevõti"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Krüptitud sõnumivahetuse tagamiseks verifitseeri see seade."</string>
|
||||
<string name="screen_identity_confirmation_title">"Kinnita, et see oled sina"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Kasuta teist seadet"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Kasuta taastevõtit"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Nüüd saad saata või lugeda sõnumeid turvaliselt ning kõik sinu vestluspartnerid võivad usaldada seda seadet."</string>
|
||||
<string name="screen_identity_confirmed_title">"Seade on verifitseeritud"</string>
|
||||
<string name="screen_identity_use_another_device">"Kasuta teist seadet"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Ootame teise seadme järgi…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Sa võid seadistusi hiljem alati muuta."</string>
|
||||
<string name="screen_notification_optin_title">"Luba teavitused ja kunagi ei jää sul sõnumid märkamata"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Sisesta taastevõti"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-eu/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-eu/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Ezin duzu baieztatu?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Sortu berreskuratze-gako berria"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Egiaztatu gailua mezularitza segurua konfiguratzeko."</string>
|
||||
<string name="screen_identity_confirmation_title">"Berretsi zure identitatea"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Erabili beste gailu bat"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Erabili berreskuratze-gakoa"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Orain mezuak modu seguruan irakurri edo bidal ditzakezu, eta txateatzen duzun edonor ere fida daiteke gailu honetaz."</string>
|
||||
<string name="screen_identity_confirmed_title">"Gailua egiaztatu da"</string>
|
||||
<string name="screen_identity_use_another_device">"Erabili beste gailu bat"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Beste gailuaren zain…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Geroago alda ditzakezu ezarpenak."</string>
|
||||
<string name="screen_notification_optin_title">"Baimendu jakinarazpenak eta ez galdu inoiz mezurik"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Sartu berreskuratze-gakoa"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-fa/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-fa/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"نمیتوانید تأیید کنید؟"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"ایجاد کلید بازیابی جدید"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"تأیید این افزاره برای برپایی پیامرسانی امن."</string>
|
||||
<string name="screen_identity_confirmation_title">"تأیید هویتتان"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"استفاده از افزارهای دیگر"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"استفاده از کلید بازیابی"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"اکنون میتوانید پیامها را به صورت امن فرستاده و بگیرید و هرکسی که با او گپ میزنید نیز میتواند به این افزاره اعتماد کند."</string>
|
||||
<string name="screen_identity_confirmed_title">"افزاره تأیید شده"</string>
|
||||
<string name="screen_identity_use_another_device">"استفاده از افزارهای دیگر"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"منتظر افزارهٔ دیگر…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"میتوانید بعداً تنظیماتتان را تغییر دهید."</string>
|
||||
<string name="screen_notification_optin_title">"اجازه به آگاهیها و از دست ندادن پیامها"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"ورود کلید بازیابی"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-fi/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-fi/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Etkö voi vahvistaa?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Luo uusi palautusavain"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Vahvista tämä laite suojattua viestintää varten."</string>
|
||||
<string name="screen_identity_confirmation_title">"Vahvista identiteettisi"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Käytä toista laitetta"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Käytä palautusavainta"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Nyt voit lukea ja lähettää viestejä turvallisesti, ja kaikki, joiden kanssa keskustelet, voivat myös luottaa tähän laitteeseen."</string>
|
||||
<string name="screen_identity_confirmed_title">"Laite vahvistettu"</string>
|
||||
<string name="screen_identity_use_another_device">"Käytä toista laitetta"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Odotetaan toista laitetta…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Voit muuttaa asetuksia myöhemmin."</string>
|
||||
<string name="screen_notification_optin_title">"Salli ilmoitukset ja älä koskaan missaa viestejä"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Syötä palautusavain"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-fr/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-fr/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Confirmation impossible ?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Créer une nouvelle clé de récupération"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Vérifier cette session pour configurer votre messagerie sécurisée."</string>
|
||||
<string name="screen_identity_confirmation_title">"Confirmez votre identité"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Utiliser une autre session"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Utiliser la clé de récupération"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Vous pouvez désormais lire ou envoyer des messages en toute sécurité, et toute personne avec qui vous discutez peut également faire confiance à cette session."</string>
|
||||
<string name="screen_identity_confirmed_title">"Session vérifiée"</string>
|
||||
<string name="screen_identity_use_another_device">"Utiliser une autre session"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"En attente d’une autre session…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Vous pourrez modifier vos paramètres ultérieurement."</string>
|
||||
<string name="screen_notification_optin_title">"Autorisez les notifications et ne manquez aucun message"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Utiliser la clé de récupération"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-hu/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-hu/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Nem tudja megerősíteni?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Új helyreállítási kulcs létrehozása"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"A biztonságos üzenetkezelés beállításához ellenőrizze ezt az eszközt."</string>
|
||||
<string name="screen_identity_confirmation_title">"Erősítse meg, hogy Ön az"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Másik eszköz használata"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Helyreállítási kulcs használata"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Mostantól biztonságosan olvashat vagy küldhet üzeneteket, és bármelyik csevegőpartnere megbízhat ebben az eszközben."</string>
|
||||
<string name="screen_identity_confirmed_title">"Eszköz ellenőrizve"</string>
|
||||
<string name="screen_identity_use_another_device">"Másik eszköz használata"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Várakozás a másik eszközre…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"A beállításokat később is módosíthatja."</string>
|
||||
<string name="screen_notification_optin_title">"Értesítések engedélyezése, hogy soha ne maradjon le egyetlen üzenetről sem"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Adja meg a helyreállítási kulcsot"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-in/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-in/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Tidak dapat mengonfirmasi?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Buat kunci pemulihan baru"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Verifikasi perangkat ini untuk menyiapkan perpesanan aman."</string>
|
||||
<string name="screen_identity_confirmation_title">"Konfirmasi bahwa ini Anda"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Gunakan perangkat lain"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Gunakan kunci pemulihan"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Sekarang Anda dapat membaca atau mengirim pesan dengan aman, dan siapa pun yang mengobrol dengan Anda juga dapat mempercayai perangkat ini."</string>
|
||||
<string name="screen_identity_confirmed_title">"Perangkat terverifikasi"</string>
|
||||
<string name="screen_identity_use_another_device">"Gunakan perangkat lain"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Menunggu di perangkat lain…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Anda dapat mengubah pengaturan Anda nanti."</string>
|
||||
<string name="screen_notification_optin_title">"Izinkan pemberitahuan dan jangan pernah melewatkan pesan"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Masukkan kunci pemulihan"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-it/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-it/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Non puoi confermare?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Crea una nuova chiave di recupero"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Verifica questo dispositivo per segnare i tuoi messaggi come sicuri."</string>
|
||||
<string name="screen_identity_confirmation_title">"Conferma la tua identità"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Usa un altro dispositivo"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Usa la chiave di recupero"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Ora puoi leggere o inviare messaggi in tutta sicurezza e anche chi chatta con te può fidarsi di questo dispositivo."</string>
|
||||
<string name="screen_identity_confirmed_title">"Dispositivo verificato"</string>
|
||||
<string name="screen_identity_use_another_device">"Usa un altro dispositivo"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"In attesa sull\'altro dispositivo…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Potrai modificare le tue impostazioni in seguito."</string>
|
||||
<string name="screen_notification_optin_title">"Consenti le notifiche e non perdere mai un messaggio"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Inserisci la chiave di recupero"</string>
|
||||
</resources>
|
||||
12
features/ftue/impl/src/main/res/values-ka/translations.xml
Normal file
12
features/ftue/impl/src/main/res/values-ka/translations.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"ახალი აღდგენის გასაღების შექმნა"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"დაადასტურეთ ეს მოწყობილობა უსაფრთხო მიმოწერისათვის."</string>
|
||||
<string name="screen_identity_confirmation_title">"დაამტკიცეთ თქვენი პიროვნება"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"ახლა თქვენ შეძლებთ შეტყობინებების წაკითხვას ან გაგზავნას უსაფრთხოდ, სხვა მომხმარებლებსაც შეუძლიათ ამ მოწყობილობას ენდონ."</string>
|
||||
<string name="screen_identity_confirmed_title">"მოწყობილობა დადასტურებულია"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"ველოდებით სხვა მოწყობილობას…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"თქვენ შეგიძლიათ შეცვალოთ თქვენი პარამეტრები მოგვიანებით."</string>
|
||||
<string name="screen_notification_optin_title">"ყველა შეტყობინებაზე შეტყობინებების მიღება"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"შეიყვანეთ აღდგენის გასაღები"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-ko/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-ko/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"확인할 수 없나요?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"새로운 복구 키 만들기"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"보안 메시징을 설정하려면 이 장치를 확인하세요."</string>
|
||||
<string name="screen_identity_confirmation_title">"본인 확인"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"다른 기기 사용"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"복구 키 사용"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"이제 메시지를 안전하게 읽거나 보낼 수 있으며, 채팅 상대도 이 기기를 신뢰할 수 있습니다."</string>
|
||||
<string name="screen_identity_confirmed_title">"기기 검증됨"</string>
|
||||
<string name="screen_identity_use_another_device">"다른 기기 사용"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"다른 기기에서 대기 중…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"나중에 설정을 변경할 수 있습니다."</string>
|
||||
<string name="screen_notification_optin_title">"알림을 허용하고 메시지를 놓치지 마세요."</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"복구 키를 입력하세요"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-nb/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-nb/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Kan du ikke bekrefte?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Opprett en ny gjenopprettingsnøkkel"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Verifiser denne enheten for å sette opp sikker meldingsutveksling."</string>
|
||||
<string name="screen_identity_confirmation_title">"Bekreft identiteten din"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Bruk en annen enhet"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Bruk gjenopprettingsnøkkel"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Nå kan du lese eller sende meldinger på en sikker måte, og alle du chatter med kan også stole på denne enheten."</string>
|
||||
<string name="screen_identity_confirmed_title">"Enhet verifisert"</string>
|
||||
<string name="screen_identity_use_another_device">"Bruk en annen enhet"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Venter på en annen enhet…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Du kan endre innstillingene dine senere."</string>
|
||||
<string name="screen_notification_optin_title">"Tillat varslinger og gå aldri glipp av en melding"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Skriv inn gjenopprettingsnøkkel"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-nl/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-nl/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Kan ik dit niet bevestigen?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Maak een nieuwe herstelsleutel"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Verifieer dit apparaat om beveiligde berichten in te stellen."</string>
|
||||
<string name="screen_identity_confirmation_title">"Bevestig dat jij het bent"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Gebruik een ander apparaat"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Gebruik de herstelsleutel"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Nu kun je veilig berichten lezen of verzenden, en iedereen met wie je chat kan dit apparaat ook vertrouwen."</string>
|
||||
<string name="screen_identity_confirmed_title">"Apparaat geverifieerd"</string>
|
||||
<string name="screen_identity_use_another_device">"Gebruik een ander apparaat"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Wachten op ander apparaat…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Je kunt je instellingen later wijzigen."</string>
|
||||
<string name="screen_notification_optin_title">"Sta meldingen toe en mis nooit meer een bericht"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Voer herstelsleutel in"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-pl/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-pl/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Nie możesz potwierdzić?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Utwórz nowy klucz przywracania"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Zweryfikuj to urządzenie, aby skonfigurować bezpieczne przesyłanie wiadomości."</string>
|
||||
<string name="screen_identity_confirmation_title">"Potwierdź, że to Ty"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Użyj innego urządzenia"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Użyj klucza przywracania"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Teraz możesz bezpiecznie czytać i wysyłać wiadomości, każdy z kim czatujesz również może ufać temu urządzeniu."</string>
|
||||
<string name="screen_identity_confirmed_title">"Urządzenie zweryfikowane"</string>
|
||||
<string name="screen_identity_use_another_device">"Użyj innego urządzenia"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Oczekiwanie na inne urządzenie…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Możesz zmienić ustawienia później."</string>
|
||||
<string name="screen_notification_optin_title">"Zezwól na powiadomienia i nie przegap żadnej wiadomości"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Wprowadź klucz przywracania"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Não consegue confirmar?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Criar uma nova chave de recuperação"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Verifique este dispositivo para configurar as mensagens seguras."</string>
|
||||
<string name="screen_identity_confirmation_title">"Confirme sua identidade"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Usar outro dispositivo"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Usar chave de recuperação"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Agora você pode ler ou enviar mensagens com segurança, e qualquer pessoa com quem você conversa também pode confiar neste dispositivo."</string>
|
||||
<string name="screen_identity_confirmed_title">"Dispositivo verificado"</string>
|
||||
<string name="screen_identity_use_another_device">"Usar outro dispositivo"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Aguardando o outro dispositivo…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Você pode alterar suas configurações mais tarde."</string>
|
||||
<string name="screen_notification_optin_title">"Permita as notificações e nunca perca uma mensagem"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Digitar chave de recuperação"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-pt/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-pt/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Não é possível confirmar?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Criar uma nova chave de recuperação"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Verifica este dispositivo para configurar o envio seguro de mensagens."</string>
|
||||
<string name="screen_identity_confirmation_title">"Confirma que és tu"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Utilizar outro dispositivo"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Utilizar chave de recuperação"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Agora podes ler ou enviar mensagens de forma segura, e qualquer pessoa com quem converses também pode confiar neste dispositivo."</string>
|
||||
<string name="screen_identity_confirmed_title">"Dispositivo verificado"</string>
|
||||
<string name="screen_identity_use_another_device">"Utilizar outro dispositivo"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"A aguardar por outros dispositivos…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Podes alterar as tuas definições mais tarde."</string>
|
||||
<string name="screen_notification_optin_title">"Permite as notificações e nunca percas uma mensagem"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Insere a chave de recuperação"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-ro/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-ro/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Nu puteți confirma?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Creați o nouă cheie de recuperare"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Verificați acest dispozitiv pentru a configura mesagerie securizată."</string>
|
||||
<string name="screen_identity_confirmation_title">"Confirmați că sunteți dumneavoastră"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Utilizați un alt dispozitiv"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Utilizați cheia de recuperare"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Acum puteți citi sau trimite mesaje în siguranță, iar oricine cu care conversați poate avea încredere în acest dispozitiv."</string>
|
||||
<string name="screen_identity_confirmed_title">"Dispozitiv verificat"</string>
|
||||
<string name="screen_identity_use_another_device">"Utilizați un alt dispozitiv"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Se așteaptă celălalt dispozitiv…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Puteți modifica setările mai târziu."</string>
|
||||
<string name="screen_notification_optin_title">"Permiteți notificările și nu pierdeți niciodată un mesaj"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Introduceți cheia de recuperare"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-ru/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-ru/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Не можете подтвердить?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Создайте новый ключ восстановления"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Подтвердите это устройство, чтобы настроить безопасный обмен сообщениями."</string>
|
||||
<string name="screen_identity_confirmation_title">"Подтвердите, что это вы"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Использовать другое устройство"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Используйте recovery key"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Теперь вы можете безопасно читать и отправлять сообщения, и все, с кем вы общаетесь в чате, также могут доверять этому устройству."</string>
|
||||
<string name="screen_identity_confirmed_title">"Устройство проверено"</string>
|
||||
<string name="screen_identity_use_another_device">"Использовать другое устройство"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Ожидание на другом устройстве…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Вы можете изменить настройки позже."</string>
|
||||
<string name="screen_notification_optin_title">"Разрешите отправку уведомлений и ни одно сообщение не будет пропущено"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Введите ключ восстановления"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-sk/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-sk/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Nemôžete potvrdiť?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Vytvoriť nový kľúč na obnovenie"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Ak chcete nastaviť zabezpečené správy, overte toto zariadenie."</string>
|
||||
<string name="screen_identity_confirmation_title">"Potvrďte, že ste to vy"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Použite iné zariadenie"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Použiť kľúč na obnovenie"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Teraz môžete bezpečne čítať alebo odosielať správy a tomuto zariadeniu môže dôverovať aj ktokoľvek, s kým konverzujete."</string>
|
||||
<string name="screen_identity_confirmed_title">"Zariadenie overené"</string>
|
||||
<string name="screen_identity_use_another_device">"Použite iné zariadenie"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Čaká sa na druhom zariadení…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Svoje nastavenia môžete neskôr zmeniť."</string>
|
||||
<string name="screen_notification_optin_title">"Povoľte oznámenia a nikdy nezmeškajte žiadnu správu"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Zadajte kľúč na obnovenie"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-sv/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-sv/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Kan du inte bekräfta?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Skapa en ny återställningsnyckel"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Verifiera den här enheten för att konfigurera säkra meddelanden."</string>
|
||||
<string name="screen_identity_confirmation_title">"Bekräfta att det är du"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Använd en annan enhet"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Använd återställningsnyckel"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Nu kan du läsa eller skicka meddelanden säkert, och alla du chattar med kan också lita på den här enheten."</string>
|
||||
<string name="screen_identity_confirmed_title">"Enhet verifierad"</string>
|
||||
<string name="screen_identity_use_another_device">"Använd en annan enhet"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Väntar på annan enhet …"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Du kan ändra dina inställningar senare."</string>
|
||||
<string name="screen_notification_optin_title">"Tillåt aviseringar och missa aldrig ett meddelande"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Ange återställningsnyckel"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-tr/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-tr/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Onaylayamıyor musunuz?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Yeni bir kurtarma anahtarı oluştur"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Güvenli mesajlaşmayı ayarlamak için bu cihazı doğrulayın."</string>
|
||||
<string name="screen_identity_confirmation_title">"Kimliğinizi doğrulayın"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Başka bir cihaz kullan"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Kurtarma anahtarı kullan"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Artık mesajları güvenli bir şekilde okuyabilir veya gönderebilirsiniz ve sohbet ettiğiniz herkes de bu cihaza güvenebilir."</string>
|
||||
<string name="screen_identity_confirmed_title">"Cihaz doğrulandı"</string>
|
||||
<string name="screen_identity_use_another_device">"Başka bir cihaz kullan"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Diğer cihazda bekleniyor…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Ayarlarınızı daha sonra değiştirebilirsiniz."</string>
|
||||
<string name="screen_notification_optin_title">"Bildirimlere izin verin ve hiçbir mesajı kaçırmayın"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Kurtarma anahtarını girin"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-uk/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-uk/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Не можете підтвердити?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Створити новий ключ відновлення"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Верифікуйте цей пристрій, щоб налаштувати безпечний обмін повідомленнями."</string>
|
||||
<string name="screen_identity_confirmation_title">"Підтвердьте, що це ви"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Використовуйте інший пристрій"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Використовуйте ключ відновлення"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Тепер ви можете безпечно читати або надсилати повідомлення, і кожен, з ким ви спілкуєтесь, також може довіряти цьому пристрою."</string>
|
||||
<string name="screen_identity_confirmed_title">"Пристрій перевірено"</string>
|
||||
<string name="screen_identity_use_another_device">"Використовуйте інший пристрій"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Чекає на інше пристрій…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Ви можете змінити свої налаштування пізніше."</string>
|
||||
<string name="screen_notification_optin_title">"Дозволити сповіщення і ніколи не пропускати повідомлення"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Введіть ключ відновлення"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-ur/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-ur/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"تصدیق نہیں کر سکتے؟"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"ایک نئی بازیابی کلید تخلیق کریں"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"محفوظ پیغام رسانی ترتیب دینے کیلئے اس آلے کی توثیق کریں۔"</string>
|
||||
<string name="screen_identity_confirmation_title">"اپنی شناخت کی تصدیق کریں"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"دوسرا آلہ استعمال کریں"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"بازیابی کلید استعمال کریں"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"اب آپ محفوظ طریقے سے پیغامات پڑھ یا بھیج سکتے ہیں، اور جسکے ساتھ آپ گفتگو کرتے ہیں وہ بھی اس آلہ پر بھروسہ کر سکتا ہے۔"</string>
|
||||
<string name="screen_identity_confirmed_title">"آلہ توثیق شدہ"</string>
|
||||
<string name="screen_identity_use_another_device">"دوسرا آلہ استعمال کریں"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"دوسرے آلہ پر منتظر…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"آپ بعد میں اپنی ترتیبات تبدیل کر سکتے ہیں۔"</string>
|
||||
<string name="screen_notification_optin_title">"اطلاعات کی اجازت دیں اور کبھی بھی کسی پیغام سے محروم نہ ہوں۔"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"بازیابی کلید درج کریں"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-uz/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-uz/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Tasdiqlay olmayapsizmi?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Yangi tiklash kalitini yarating"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Xavfsiz xabarlashuvni sozlash uchun ushbu qurilmani tasdiqlang."</string>
|
||||
<string name="screen_identity_confirmation_title">"Shaxsingizni tasdiqlang"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Boshqa qurilmadan foydalanish"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Qayta tiklash kalitidan foydalaning"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Endi xabarlarni xavfsiz tarzda o‘qish yoki yuborish imkoniyatiga egasiz, shuningdek, siz bilan muloqot qilayotgan har qanday kishi ham bu qurilmaga ishonch bildirishi mumkin."</string>
|
||||
<string name="screen_identity_confirmed_title">"Qurilma tasdiqlandi"</string>
|
||||
<string name="screen_identity_use_another_device">"Boshqa qurilmadan foydalanish"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Boshqa qurilmada kutilmoqda…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Sozlamalaringizni keyinroq o\'zgartirishingiz mumkin."</string>
|
||||
<string name="screen_notification_optin_title">"Bildirishnomalarga ruxsat bering va hech qachon xabarni o\'tkazib yubormang"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Tiklash kalitini kiriting"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"無法確認?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"建立新的復原金鑰"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"驗證這部裝置以設定安全通訊。"</string>
|
||||
<string name="screen_identity_confirmation_title">"確認這是你本人"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"使用另一部裝置"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"使用復原金鑰"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"您可以安全地讀取和發送訊息了,與您聊天的人也可以信任這部裝置。"</string>
|
||||
<string name="screen_identity_confirmed_title">"裝置已驗證"</string>
|
||||
<string name="screen_identity_use_another_device">"使用另一部裝置"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"正在等待其他裝置…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"您稍後仍可變更設定。"</string>
|
||||
<string name="screen_notification_optin_title">"允許通知,永遠不會錯誤任何訊息"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"輸入復原金鑰"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values-zh/translations.xml
Normal file
16
features/ftue/impl/src/main/res/values-zh/translations.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"无法确认?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"创建新的恢复密钥"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"验证此设备以开始安全地收发消息。"</string>
|
||||
<string name="screen_identity_confirmation_title">"确认这是你"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"使用其他设备"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"使用恢复密钥"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"现在,您可以安全地阅读或发送消息,与您聊天的人也会信任此设备。"</string>
|
||||
<string name="screen_identity_confirmed_title">"设备已验证"</string>
|
||||
<string name="screen_identity_use_another_device">"使用其他设备"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"正在等待其他设备……"</string>
|
||||
<string name="screen_notification_optin_subtitle">"您可以稍后更改设置。"</string>
|
||||
<string name="screen_notification_optin_title">"允许通知,绝不错过任何消息"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"输入恢复密钥"</string>
|
||||
</resources>
|
||||
16
features/ftue/impl/src/main/res/values/localazy.xml
Normal file
16
features/ftue/impl/src/main/res/values/localazy.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Can\'t confirm?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Create a new recovery key"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Verify this device to set up secure messaging."</string>
|
||||
<string name="screen_identity_confirmation_title">"Confirm your identity"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Use another device"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Use recovery key"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Now you can read or send messages securely, and anyone you chat with can also trust this device."</string>
|
||||
<string name="screen_identity_confirmed_title">"Device verified"</string>
|
||||
<string name="screen_identity_use_another_device">"Use another device"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Waiting on other device…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"You can change your settings later."</string>
|
||||
<string name="screen_notification_optin_title">"Allow notifications and never miss a message"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Enter recovery key"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl
|
||||
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.testing.junit4.util.MainDispatcherRule
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.lockscreen.test.FakeLockScreenEntryPoint
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.node.TestParentNode
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultFtueEntryPointTest {
|
||||
@get:Rule
|
||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
@get:Rule
|
||||
val mainDispatcherRule = MainDispatcherRule()
|
||||
|
||||
@Test
|
||||
fun `test node builder`() = runTest {
|
||||
val entryPoint = DefaultFtueEntryPoint()
|
||||
val parentNode = TestParentNode.create { buildContext, plugins ->
|
||||
FtueFlowNode(
|
||||
buildContext = buildContext,
|
||||
plugins = plugins,
|
||||
analyticsEntryPoint = { _, _ -> lambdaError() },
|
||||
defaultFtueService = createDefaultFtueService(),
|
||||
lockScreenEntryPoint = FakeLockScreenEntryPoint(),
|
||||
)
|
||||
}
|
||||
val result = entryPoint.createNode(parentNode, BuildContext.root(null))
|
||||
assertThat(result).isInstanceOf(FtueFlowNode::class.java)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl
|
||||
|
||||
import android.os.Build
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.ftue.api.state.FtueState
|
||||
import io.element.android.features.ftue.impl.state.DefaultFtueService
|
||||
import io.element.android.features.ftue.impl.state.FtueStep
|
||||
import io.element.android.features.ftue.impl.state.InternalFtueState
|
||||
import io.element.android.features.lockscreen.api.LockScreenService
|
||||
import io.element.android.features.lockscreen.test.FakeLockScreenService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||
import io.element.android.libraries.permissions.api.PermissionStateProvider
|
||||
import io.element.android.libraries.permissions.test.FakePermissionStateProvider
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.noop.NoopAnalyticsService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultFtueServiceTest {
|
||||
@Test
|
||||
fun `given any check being false and session verification state being loaded, FtueState is Incomplete`() = runTest {
|
||||
val sessionVerificationService = FakeSessionVerificationService().apply {
|
||||
emitVerifiedStatus(SessionVerifiedStatus.Unknown)
|
||||
}
|
||||
val service = createDefaultFtueService(
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
)
|
||||
|
||||
service.state.test {
|
||||
// Verification state is unknown, we don't display the flow yet
|
||||
assertThat(awaitItem()).isEqualTo(FtueState.Unknown)
|
||||
|
||||
// Verification state is known, we should display the flow if any check is false
|
||||
sessionVerificationService.emitVerifiedStatus(SessionVerifiedStatus.NotVerified)
|
||||
assertThat(awaitItem()).isEqualTo(FtueState.Incomplete)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given all checks being true, FtueState is Complete`() = runTest {
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val sessionVerificationService = FakeSessionVerificationService()
|
||||
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = true)
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
val service = createDefaultFtueService(
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
analyticsService = analyticsService,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
lockScreenService = lockScreenService,
|
||||
)
|
||||
|
||||
sessionVerificationService.emitVerifiedStatus(SessionVerifiedStatus.Verified)
|
||||
analyticsService.setDidAskUserConsent()
|
||||
permissionStateProvider.setPermissionGranted()
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
service.updateFtueStep()
|
||||
service.state.test {
|
||||
assertThat(awaitItem()).isEqualTo(FtueState.Unknown)
|
||||
assertThat(awaitItem()).isEqualTo(FtueState.Complete)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given all checks being true with no analytics, FtueState is Complete`() = runTest {
|
||||
val analyticsService = NoopAnalyticsService()
|
||||
val sessionVerificationService = FakeSessionVerificationService()
|
||||
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = true)
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
val service = createDefaultFtueService(
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
analyticsService = analyticsService,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
lockScreenService = lockScreenService,
|
||||
)
|
||||
|
||||
sessionVerificationService.emitVerifiedStatus(SessionVerifiedStatus.Verified)
|
||||
permissionStateProvider.setPermissionGranted()
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
service.updateFtueStep()
|
||||
service.state.test {
|
||||
assertThat(awaitItem()).isEqualTo(FtueState.Unknown)
|
||||
assertThat(awaitItem()).isEqualTo(FtueState.Complete)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `traverse flow`() = runTest {
|
||||
val sessionVerificationService = FakeSessionVerificationService().apply {
|
||||
emitVerifiedStatus(SessionVerifiedStatus.NotVerified)
|
||||
}
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false)
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
val service = createDefaultFtueService(
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
analyticsService = analyticsService,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
lockScreenService = lockScreenService,
|
||||
)
|
||||
|
||||
service.ftueStepStateFlow.test {
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Unknown)
|
||||
// Session verification
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Incomplete(FtueStep.SessionVerification))
|
||||
sessionVerificationService.emitVerifiedStatus(SessionVerifiedStatus.Verified)
|
||||
// User completes verification
|
||||
service.onUserCompletedSessionVerification()
|
||||
// Notifications opt in
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Incomplete(FtueStep.NotificationsOptIn))
|
||||
permissionStateProvider.setPermissionGranted()
|
||||
// Simulate event from NotificationsOptInNode.Callback.onNotificationsOptInFinished
|
||||
service.updateFtueStep()
|
||||
// Entering PIN code
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Incomplete(FtueStep.LockscreenSetup))
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
// Simulate event from LockScreenEntryPoint.Callback.onSetupDone()
|
||||
service.updateFtueStep()
|
||||
// Analytics opt in
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Incomplete(FtueStep.AnalyticsOptIn))
|
||||
analyticsService.setDidAskUserConsent()
|
||||
// Final step
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Complete)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `if a check for a step is true, start from the next one`() = runTest {
|
||||
val sessionVerificationService = FakeSessionVerificationService()
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false)
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
val service = createDefaultFtueService(
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
analyticsService = analyticsService,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
lockScreenService = lockScreenService,
|
||||
)
|
||||
|
||||
// Skip first 3 steps
|
||||
sessionVerificationService.emitVerifiedStatus(SessionVerifiedStatus.Verified)
|
||||
permissionStateProvider.setPermissionGranted()
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
|
||||
service.ftueStepStateFlow.test {
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Unknown)
|
||||
// Analytics opt in
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Incomplete(FtueStep.AnalyticsOptIn))
|
||||
analyticsService.setDidAskUserConsent()
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Complete)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `if version is older than 13 we don't display the notification opt in screen`() = runTest {
|
||||
val sessionVerificationService = FakeSessionVerificationService()
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
|
||||
val service = createDefaultFtueService(
|
||||
sdkIntVersion = Build.VERSION_CODES.M,
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
analyticsService = analyticsService,
|
||||
lockScreenService = lockScreenService,
|
||||
)
|
||||
|
||||
sessionVerificationService.emitVerifiedStatus(SessionVerifiedStatus.Verified)
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
|
||||
service.ftueStepStateFlow.test {
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Unknown)
|
||||
// Analytics opt in
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Incomplete(FtueStep.AnalyticsOptIn))
|
||||
analyticsService.setDidAskUserConsent()
|
||||
assertThat(awaitItem()).isEqualTo(InternalFtueState.Complete)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun TestScope.createDefaultFtueService(
|
||||
sessionVerificationService: SessionVerificationService = FakeSessionVerificationService(),
|
||||
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
||||
permissionStateProvider: PermissionStateProvider = FakePermissionStateProvider(permissionGranted = false),
|
||||
lockScreenService: LockScreenService = FakeLockScreenService(),
|
||||
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||
// First version where notification permission is required
|
||||
sdkIntVersion: Int = Build.VERSION_CODES.TIRAMISU,
|
||||
) = DefaultFtueService(
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkIntVersion),
|
||||
analyticsService = analyticsService,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
lockScreenService = lockScreenService,
|
||||
sessionPreferencesStore = sessionPreferencesStore,
|
||||
)
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.notifications
|
||||
|
||||
import android.os.Build
|
||||
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.libraries.permissions.api.PermissionStateProvider
|
||||
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
||||
import io.element.android.libraries.permissions.test.FakePermissionStateProvider
|
||||
import io.element.android.libraries.permissions.test.FakePermissionsPresenter
|
||||
import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory
|
||||
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class NotificationsOptInPresenterTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private var isFinished = false
|
||||
|
||||
@Test
|
||||
fun `initial state`() = runTest {
|
||||
val presenter = createPresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.notificationsPermissionState.showDialog).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `show dialog on continue clicked`() = runTest {
|
||||
val permissionPresenter = FakePermissionsPresenter()
|
||||
val presenter = createPresenter(permissionPresenter)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(NotificationsOptInEvents.ContinueClicked)
|
||||
assertThat(awaitItem().notificationsPermissionState.showDialog).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `finish flow on continue clicked with permission already granted`() = runTest {
|
||||
val permissionPresenter = FakePermissionsPresenter().apply {
|
||||
setPermissionGranted()
|
||||
}
|
||||
val presenter = createPresenter(permissionPresenter)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(NotificationsOptInEvents.ContinueClicked)
|
||||
assertThat(isFinished).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `finish flow on not now clicked`() = runTest {
|
||||
val permissionPresenter = FakePermissionsPresenter()
|
||||
val presenter = createPresenter(
|
||||
permissionsPresenter = permissionPresenter,
|
||||
sdkIntVersion = Build.VERSION_CODES.M
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(NotificationsOptInEvents.NotNowClicked)
|
||||
assertThat(isFinished).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `set permission denied on not now clicked in API 33`() = runTest(StandardTestDispatcher()) {
|
||||
val permissionPresenter = FakePermissionsPresenter()
|
||||
val permissionStateProvider = FakePermissionStateProvider()
|
||||
val presenter = createPresenter(
|
||||
permissionsPresenter = permissionPresenter,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
sdkIntVersion = Build.VERSION_CODES.TIRAMISU
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(NotificationsOptInEvents.NotNowClicked)
|
||||
|
||||
// Allow background coroutines to run
|
||||
runCurrent()
|
||||
|
||||
val isPermissionDenied = runBlocking {
|
||||
permissionStateProvider.isPermissionDenied("notifications").first()
|
||||
}
|
||||
assertThat(isPermissionDenied).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createPresenter(
|
||||
permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(),
|
||||
permissionStateProvider: PermissionStateProvider = FakePermissionStateProvider(),
|
||||
sdkIntVersion: Int = Build.VERSION_CODES.TIRAMISU,
|
||||
) = NotificationsOptInPresenter(
|
||||
permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter),
|
||||
callback = object : NotificationsOptInNode.Callback {
|
||||
override fun onNotificationsOptInFinished() {
|
||||
isFinished = true
|
||||
}
|
||||
},
|
||||
appCoroutineScope = this,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
buildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(sdkIntVersion),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.sessionverification.choosemode
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutEvents
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.features.logout.api.direct.aDirectLogoutState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class ChooseSessionVerificationModePresenterTest {
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = createPresenter()
|
||||
presenter.test {
|
||||
awaitItem().run {
|
||||
assertThat(buttonsState.isLoading()).isTrue()
|
||||
assertThat(directLogoutState.logoutAction.isUninitialized()).isTrue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - state is relayed from EncryptionService, order 1`() = runTest {
|
||||
val encryptionService = FakeEncryptionService()
|
||||
val presenter = createPresenter(encryptionService = encryptionService)
|
||||
presenter.test {
|
||||
assertThat(awaitItem().buttonsState.isLoading()).isTrue()
|
||||
// Has device to verify against
|
||||
encryptionService.emitHasDevicesToVerifyAgainst(AsyncData.Success(false))
|
||||
// Can enter recovery key
|
||||
encryptionService.emitRecoveryState(RecoveryState.DISABLED)
|
||||
assertThat(awaitItem().buttonsState.dataOrNull()).isEqualTo(
|
||||
ChooseSelfVerificationModeState.ButtonsState(
|
||||
canUseAnotherDevice = false,
|
||||
canEnterRecoveryKey = false,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - state is relayed from EncryptionService, order 2`() = runTest {
|
||||
val encryptionService = FakeEncryptionService()
|
||||
val presenter = createPresenter(encryptionService = encryptionService)
|
||||
presenter.test {
|
||||
assertThat(awaitItem().buttonsState.isLoading()).isTrue()
|
||||
// Can enter recovery key
|
||||
encryptionService.emitRecoveryState(RecoveryState.DISABLED)
|
||||
// Has device to verify against
|
||||
encryptionService.emitHasDevicesToVerifyAgainst(AsyncData.Success(false))
|
||||
assertThat(awaitItem().buttonsState.dataOrNull()).isEqualTo(
|
||||
ChooseSelfVerificationModeState.ButtonsState(
|
||||
canUseAnotherDevice = false,
|
||||
canEnterRecoveryKey = false,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - can use another device`() = runTest {
|
||||
val encryptionService = FakeEncryptionService()
|
||||
val presenter = createPresenter(encryptionService = encryptionService)
|
||||
presenter.test {
|
||||
assertThat(awaitItem().buttonsState.isLoading()).isTrue()
|
||||
// Can enter recovery key
|
||||
encryptionService.emitRecoveryState(RecoveryState.DISABLED)
|
||||
// Has device to verify against
|
||||
encryptionService.emitHasDevicesToVerifyAgainst(AsyncData.Success(true))
|
||||
assertThat(awaitItem().buttonsState.dataOrNull()).isEqualTo(
|
||||
ChooseSelfVerificationModeState.ButtonsState(
|
||||
canUseAnotherDevice = true,
|
||||
canEnterRecoveryKey = false,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - can enter recovery key`() = runTest {
|
||||
val encryptionService = FakeEncryptionService()
|
||||
val presenter = createPresenter(encryptionService = encryptionService)
|
||||
presenter.test {
|
||||
assertThat(awaitItem().buttonsState.isLoading()).isTrue()
|
||||
// Can enter recovery key
|
||||
encryptionService.emitRecoveryState(RecoveryState.INCOMPLETE)
|
||||
// Has device to verify against
|
||||
encryptionService.emitHasDevicesToVerifyAgainst(AsyncData.Success(false))
|
||||
assertThat(awaitItem().buttonsState.dataOrNull()).isEqualTo(
|
||||
ChooseSelfVerificationModeState.ButtonsState(
|
||||
canUseAnotherDevice = false,
|
||||
canEnterRecoveryKey = true,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sing out action triggers a direct logout`() = runTest {
|
||||
val logoutEventRecorder = lambdaRecorder<DirectLogoutEvents, Unit> {}
|
||||
val logoutPresenter = Presenter<DirectLogoutState> {
|
||||
aDirectLogoutState(eventSink = logoutEventRecorder)
|
||||
}
|
||||
val presenter = createPresenter(directLogoutPresenter = logoutPresenter)
|
||||
presenter.test {
|
||||
val initial = awaitItem()
|
||||
initial.eventSink(ChooseSelfVerificationModeEvent.SignOut)
|
||||
logoutEventRecorder.assertions().isCalledOnce()
|
||||
.with(value(DirectLogoutEvents.Logout(ignoreSdkError = false)))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPresenter(
|
||||
encryptionService: FakeEncryptionService = FakeEncryptionService(),
|
||||
directLogoutPresenter: Presenter<DirectLogoutState> = Presenter<DirectLogoutState> { aDirectLogoutState() }
|
||||
) = ChooseSelfVerificationModePresenter(
|
||||
encryptionService = encryptionService,
|
||||
directLogoutPresenter = directLogoutPresenter,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.sessionverification.choosemode
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.features.ftue.impl.R
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
import io.element.android.tests.testutils.ensureCalledOnce
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ChooseSessionVerificationModeViewTest {
|
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Config(qualifiers = "h1024dp")
|
||||
@Test
|
||||
fun `clicking on learn more invokes the expected callback`() {
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setChooseSelfVerificationModeView(
|
||||
aChooseSelfVerificationModeState(),
|
||||
onLearnMoreClick = callback,
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_learn_more)
|
||||
}
|
||||
}
|
||||
|
||||
@Config(qualifiers = "h1024dp")
|
||||
@Test
|
||||
fun `clicking on use another device calls the callback`() {
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setChooseSelfVerificationModeView(
|
||||
aChooseSelfVerificationModeState(AsyncData.Success(aButtonsState(canUseAnotherDevice = true))),
|
||||
onUseAnotherDevice = callback,
|
||||
)
|
||||
rule.clickOn(R.string.screen_identity_use_another_device)
|
||||
}
|
||||
}
|
||||
|
||||
@Config(qualifiers = "h1024dp")
|
||||
@Test
|
||||
fun `clicking on enter recovery key calls the callback`() {
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setChooseSelfVerificationModeView(
|
||||
aChooseSelfVerificationModeState(AsyncData.Success(aButtonsState(canEnterRecoveryKey = true))),
|
||||
onEnterRecoveryKey = callback,
|
||||
)
|
||||
rule.clickOn(R.string.screen_session_verification_enter_recovery_key)
|
||||
}
|
||||
}
|
||||
|
||||
@Config(qualifiers = "h1024dp")
|
||||
@Test
|
||||
fun `clicking on cannot confirm calls the reset keys callback`() {
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setChooseSelfVerificationModeView(
|
||||
aChooseSelfVerificationModeState(),
|
||||
onResetKey = callback,
|
||||
)
|
||||
rule.clickOn(R.string.screen_identity_confirmation_cannot_confirm)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setChooseSelfVerificationModeView(
|
||||
state: ChooseSelfVerificationModeState,
|
||||
onLearnMoreClick: () -> Unit = EnsureNeverCalled(),
|
||||
onUseAnotherDevice: () -> Unit = EnsureNeverCalled(),
|
||||
onResetKey: () -> Unit = EnsureNeverCalled(),
|
||||
onEnterRecoveryKey: () -> Unit = EnsureNeverCalled(),
|
||||
) {
|
||||
setContent {
|
||||
ChooseSelfVerificationModeView(
|
||||
state = state,
|
||||
onLearnMore = onLearnMoreClick,
|
||||
onUseAnotherDevice = onUseAnotherDevice,
|
||||
onResetKey = onResetKey,
|
||||
onUseRecoveryKey = onEnterRecoveryKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
21
features/ftue/test/build.gradle.kts
Normal file
21
features/ftue/test/build.gradle.kts
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.ftue.test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.features.ftue.api)
|
||||
implementation(projects.tests.testutils)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.test
|
||||
|
||||
import io.element.android.features.ftue.api.state.FtueService
|
||||
import io.element.android.features.ftue.api.state.FtueState
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
class FakeFtueService : FtueService {
|
||||
override val state: MutableStateFlow<FtueState> = MutableStateFlow(FtueState.Unknown)
|
||||
|
||||
suspend fun emitState(newState: FtueState) {
|
||||
state.emit(newState)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user