Lock settings : branch the flow

This commit is contained in:
ganfra 2023-10-23 18:11:02 +02:00
parent eb15ca3901
commit afe6d5a870
15 changed files with 100 additions and 15 deletions

View file

@ -48,11 +48,11 @@ import io.element.android.features.createroom.api.CreateRoomEntryPoint
import io.element.android.features.ftue.api.FtueEntryPoint import io.element.android.features.ftue.api.FtueEntryPoint
import io.element.android.features.ftue.api.state.FtueState import io.element.android.features.ftue.api.state.FtueState
import io.element.android.features.invitelist.api.InviteListEntryPoint import io.element.android.features.invitelist.api.InviteListEntryPoint
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.features.lockscreen.api.LockScreenEntryPoint import io.element.android.features.lockscreen.api.LockScreenEntryPoint
import io.element.android.features.lockscreen.api.LockScreenState import io.element.android.features.lockscreen.api.LockScreenState
import io.element.android.features.lockscreen.api.LockScreenStateService import io.element.android.features.lockscreen.api.LockScreenStateService
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.features.preferences.api.PreferencesEntryPoint import io.element.android.features.preferences.api.PreferencesEntryPoint
import io.element.android.features.roomlist.api.RoomListEntryPoint import io.element.android.features.roomlist.api.RoomListEntryPoint
import io.element.android.features.verifysession.api.VerifySessionEntryPoint import io.element.android.features.verifysession.api.VerifySessionEntryPoint
@ -218,7 +218,9 @@ class LoggedInFlowNode @AssistedInject constructor(
createNode<LoggedInNode>(buildContext) createNode<LoggedInNode>(buildContext)
} }
NavTarget.LockPermanent -> { NavTarget.LockPermanent -> {
lockScreenEntryPoint.createNode(this, buildContext) lockScreenEntryPoint.nodeBuilder(this, buildContext)
.target(LockScreenEntryPoint.Target.Unlock)
.build()
} }
NavTarget.RoomList -> { NavTarget.RoomList -> {
val callback = object : RoomListEntryPoint.Callback { val callback = object : RoomListEntryPoint.Callback {

View file

@ -16,6 +16,22 @@
package io.element.android.features.lockscreen.api package io.element.android.features.lockscreen.api
import io.element.android.libraries.architecture.SimpleFeatureEntryPoint import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import io.element.android.libraries.architecture.FeatureEntryPoint
interface LockScreenEntryPoint : SimpleFeatureEntryPoint interface LockScreenEntryPoint : FeatureEntryPoint {
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
interface NodeBuilder {
fun target(target: Target): NodeBuilder
fun build(): Node
}
enum class Target {
Settings,
Setup,
Unlock
}
}

View file

@ -27,7 +27,26 @@ import javax.inject.Inject
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
class DefaultLockScreenEntryPoint @Inject constructor() : LockScreenEntryPoint { class DefaultLockScreenEntryPoint @Inject constructor() : LockScreenEntryPoint {
override fun createNode(parentNode: Node, buildContext: BuildContext): Node { override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): LockScreenEntryPoint.NodeBuilder {
return parentNode.createNode<LockScreenFlowNode>(buildContext)
var innerTarget: LockScreenEntryPoint.Target = LockScreenEntryPoint.Target.Unlock
return object : LockScreenEntryPoint.NodeBuilder {
override fun target(target: LockScreenEntryPoint.Target): LockScreenEntryPoint.NodeBuilder {
innerTarget = target
return this
}
override fun build(): Node {
val inputs = LockScreenFlowNode.Inputs(
when (innerTarget) {
LockScreenEntryPoint.Target.Unlock -> LockScreenFlowNode.NavTarget.Unlock
LockScreenEntryPoint.Target.Setup -> LockScreenFlowNode.NavTarget.Setup
LockScreenEntryPoint.Target.Settings -> LockScreenFlowNode.NavTarget.Settings
}
)
return parentNode.createNode<LockScreenFlowNode>(buildContext, listOf(inputs))
}
}
} }
} }

View file

@ -27,9 +27,11 @@ import com.bumble.appyx.navmodel.backstack.BackStack
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.lockscreen.impl.settings.LockScreenSettingsNode
import io.element.android.features.lockscreen.impl.setup.SetupPinNode import io.element.android.features.lockscreen.impl.setup.SetupPinNode
import io.element.android.features.lockscreen.impl.unlock.PinUnlockNode import io.element.android.features.lockscreen.impl.unlock.PinUnlockNode
import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.BackstackNode
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope
@ -41,19 +43,26 @@ class LockScreenFlowNode @AssistedInject constructor(
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
) : BackstackNode<LockScreenFlowNode.NavTarget>( ) : BackstackNode<LockScreenFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
initialElement = NavTarget.Unlock, initialElement = plugins.filterIsInstance(Inputs::class.java).first().initialNavTarget,
savedStateMap = buildContext.savedStateMap, savedStateMap = buildContext.savedStateMap,
), ),
buildContext = buildContext, buildContext = buildContext,
plugins = plugins, plugins = plugins,
) { ) {
data class Inputs(
val initialNavTarget: NavTarget = NavTarget.Unlock,
) : NodeInputs
sealed interface NavTarget : Parcelable { sealed interface NavTarget : Parcelable {
@Parcelize @Parcelize
data object Unlock : NavTarget data object Unlock : NavTarget
@Parcelize @Parcelize
data object Setup : NavTarget data object Setup : NavTarget
@Parcelize
data object Settings : NavTarget
} }
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
@ -64,6 +73,9 @@ class LockScreenFlowNode @AssistedInject constructor(
NavTarget.Setup -> { NavTarget.Setup -> {
createNode<SetupPinNode>(buildContext) createNode<SetupPinNode>(buildContext)
} }
NavTarget.Settings -> {
createNode<LockScreenSettingsNode>(buildContext)
}
} }
} }

View file

@ -21,6 +21,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import io.element.android.appconfig.LockScreenConfig
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import javax.inject.Inject import javax.inject.Inject
@ -29,9 +30,6 @@ class LockScreenSettingsPresenter @Inject constructor() : Presenter<LockScreenSe
@Composable @Composable
override fun present(): LockScreenSettingsState { override fun present(): LockScreenSettingsState {
var isLockMandatory by remember {
mutableStateOf(false)
}
var isBiometricEnabled by remember { var isBiometricEnabled by remember {
mutableStateOf(false) mutableStateOf(false)
} }
@ -50,7 +48,7 @@ class LockScreenSettingsPresenter @Inject constructor() : Presenter<LockScreenSe
} }
return LockScreenSettingsState( return LockScreenSettingsState(
isLockMandatory = isLockMandatory, isPinMandatory = LockScreenConfig.IS_PIN_MANDATORY,
isBiometricEnabled = isBiometricEnabled, isBiometricEnabled = isBiometricEnabled,
showRemovePinConfirmation = showRemovePinConfirmation, showRemovePinConfirmation = showRemovePinConfirmation,
eventSink = ::handleEvents eventSink = ::handleEvents

View file

@ -17,7 +17,7 @@
package io.element.android.features.lockscreen.impl.settings package io.element.android.features.lockscreen.impl.settings
data class LockScreenSettingsState( data class LockScreenSettingsState(
val isLockMandatory: Boolean, val isPinMandatory: Boolean,
val isBiometricEnabled: Boolean, val isBiometricEnabled: Boolean,
val showRemovePinConfirmation: Boolean, val showRemovePinConfirmation: Boolean,
val eventSink: (LockScreenSettingsEvents) -> Unit val eventSink: (LockScreenSettingsEvents) -> Unit

View file

@ -32,7 +32,7 @@ fun aLockScreenSettingsState(
isBiometricEnabled: Boolean = false, isBiometricEnabled: Boolean = false,
showRemovePinConfirmation: Boolean = false, showRemovePinConfirmation: Boolean = false,
) = LockScreenSettingsState( ) = LockScreenSettingsState(
isLockMandatory = isLockMandatory, isPinMandatory = isLockMandatory,
isBiometricEnabled = isBiometricEnabled, isBiometricEnabled = isBiometricEnabled,
showRemovePinConfirmation = showRemovePinConfirmation, showRemovePinConfirmation = showRemovePinConfirmation,
eventSink = {} eventSink = {}

View file

@ -48,7 +48,7 @@ fun LockScreenSettingsView(
} }
) )
PreferenceDivider() PreferenceDivider()
if (!state.isLockMandatory) { if (!state.isPinMandatory) {
PreferenceText( PreferenceText(
title = stringResource(id = R.string.screen_app_lock_settings_remove_pin), title = stringResource(id = R.string.screen_app_lock_settings_remove_pin),
tintColor = ElementTheme.colors.textCriticalPrimary, tintColor = ElementTheme.colors.textCriticalPrimary,

View file

@ -50,6 +50,7 @@ dependencies {
implementation(projects.libraries.mediaupload.api) implementation(projects.libraries.mediaupload.api)
implementation(projects.libraries.permissions.api) implementation(projects.libraries.permissions.api)
implementation(projects.features.rageshake.api) implementation(projects.features.rageshake.api)
implementation(projects.features.lockscreen.api)
implementation(projects.features.analytics.api) implementation(projects.features.analytics.api)
implementation(projects.features.ftue.api) implementation(projects.features.ftue.api)
implementation(projects.features.logout.api) implementation(projects.features.logout.api)

View file

@ -29,6 +29,7 @@ import com.bumble.appyx.navmodel.backstack.operation.push
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
import io.element.android.features.preferences.api.PreferencesEntryPoint import io.element.android.features.preferences.api.PreferencesEntryPoint
import io.element.android.features.preferences.impl.about.AboutNode import io.element.android.features.preferences.impl.about.AboutNode
import io.element.android.features.preferences.impl.advanced.AdvancedSettingsNode import io.element.android.features.preferences.impl.advanced.AdvancedSettingsNode
@ -50,6 +51,7 @@ import kotlinx.parcelize.Parcelize
class PreferencesFlowNode @AssistedInject constructor( class PreferencesFlowNode @AssistedInject constructor(
@Assisted buildContext: BuildContext, @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
private val lockScreenEntryPoint: LockScreenEntryPoint,
) : BackstackNode<PreferencesFlowNode.NavTarget>( ) : BackstackNode<PreferencesFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
initialElement = NavTarget.Root, initialElement = NavTarget.Root,
@ -81,6 +83,9 @@ class PreferencesFlowNode @AssistedInject constructor(
@Parcelize @Parcelize
data object NotificationSettings : NavTarget data object NotificationSettings : NavTarget
@Parcelize
data object LockScreenSettings : NavTarget
@Parcelize @Parcelize
data class EditDefaultNotificationSetting(val isOneToOne: Boolean) : NavTarget data class EditDefaultNotificationSetting(val isOneToOne: Boolean) : NavTarget
@ -116,6 +121,10 @@ class PreferencesFlowNode @AssistedInject constructor(
backstack.push(NavTarget.NotificationSettings) backstack.push(NavTarget.NotificationSettings)
} }
override fun onOpenLockScreenSettings() {
backstack.push(NavTarget.LockScreenSettings)
}
override fun onOpenAdvancedSettings() { override fun onOpenAdvancedSettings() {
backstack.push(NavTarget.AdvancedSettings) backstack.push(NavTarget.AdvancedSettings)
} }
@ -162,6 +171,11 @@ class PreferencesFlowNode @AssistedInject constructor(
val inputs = EditUserProfileNode.Inputs(navTarget.matrixUser) val inputs = EditUserProfileNode.Inputs(navTarget.matrixUser)
createNode<EditUserProfileNode>(buildContext, listOf(inputs)) createNode<EditUserProfileNode>(buildContext, listOf(inputs))
} }
NavTarget.LockScreenSettings -> {
lockScreenEntryPoint.nodeBuilder(this, buildContext)
.target(LockScreenEntryPoint.Target.Settings)
.build()
}
} }
} }

View file

@ -47,6 +47,7 @@ class PreferencesRootNode @AssistedInject constructor(
fun onOpenAbout() fun onOpenAbout()
fun onOpenDeveloperSettings() fun onOpenDeveloperSettings()
fun onOpenNotificationSettings() fun onOpenNotificationSettings()
fun onOpenLockScreenSettings()
fun onOpenAdvancedSettings() fun onOpenAdvancedSettings()
fun onOpenUserProfile(matrixUser: MatrixUser) fun onOpenUserProfile(matrixUser: MatrixUser)
} }
@ -93,6 +94,10 @@ class PreferencesRootNode @AssistedInject constructor(
plugins<Callback>().forEach { it.onOpenNotificationSettings() } plugins<Callback>().forEach { it.onOpenNotificationSettings() }
} }
private fun onOpenLockScreenSettings() {
plugins<Callback>().forEach { it.onOpenLockScreenSettings() }
}
private fun onOpenUserProfile(matrixUser: MatrixUser) { private fun onOpenUserProfile(matrixUser: MatrixUser) {
plugins<Callback>().forEach { it.onOpenUserProfile(matrixUser) } plugins<Callback>().forEach { it.onOpenUserProfile(matrixUser) }
} }
@ -115,6 +120,7 @@ class PreferencesRootNode @AssistedInject constructor(
onSuccessLogout = { onSuccessLogout(activity, it) }, onSuccessLogout = { onSuccessLogout(activity, it) },
onManageAccountClicked = { onManageAccountClicked(activity, it, isDark) }, onManageAccountClicked = { onManageAccountClicked(activity, it, isDark) },
onOpenNotificationSettings = this::onOpenNotificationSettings, onOpenNotificationSettings = this::onOpenNotificationSettings,
onOpenLockScreenSettings = this::onOpenLockScreenSettings,
onOpenUserProfile = this::onOpenUserProfile, onOpenUserProfile = this::onOpenUserProfile,
) )
} }

View file

@ -68,6 +68,10 @@ class PreferencesRootPresenter @Inject constructor(
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
showNotificationSettings.value = featureFlagService.isFeatureEnabled(FeatureFlags.NotificationSettings) showNotificationSettings.value = featureFlagService.isFeatureEnabled(FeatureFlags.NotificationSettings)
} }
val showLockScreenSettings = remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
showLockScreenSettings.value = featureFlagService.isFeatureEnabled(FeatureFlags.PinUnlock)
}
// We should display the 'complete verification' option if the current session can be verified // We should display the 'complete verification' option if the current session can be verified
val showCompleteVerification by sessionVerificationService.canVerifySessionFlow.collectAsState(false) val showCompleteVerification by sessionVerificationService.canVerifySessionFlow.collectAsState(false)
@ -95,6 +99,7 @@ class PreferencesRootPresenter @Inject constructor(
showAnalyticsSettings = hasAnalyticsProviders, showAnalyticsSettings = hasAnalyticsProviders,
showDeveloperSettings = showDeveloperSettings, showDeveloperSettings = showDeveloperSettings,
showNotificationSettings = showNotificationSettings.value, showNotificationSettings = showNotificationSettings.value,
showLockScreenSettings = showLockScreenSettings.value,
snackbarMessage = snackbarMessage, snackbarMessage = snackbarMessage,
) )
} }

View file

@ -29,6 +29,7 @@ data class PreferencesRootState(
val devicesManagementUrl: String?, val devicesManagementUrl: String?,
val showAnalyticsSettings: Boolean, val showAnalyticsSettings: Boolean,
val showDeveloperSettings: Boolean, val showDeveloperSettings: Boolean,
val showLockScreenSettings: Boolean,
val showNotificationSettings: Boolean, val showNotificationSettings: Boolean,
val snackbarMessage: SnackbarMessage?, val snackbarMessage: SnackbarMessage?,
) )

View file

@ -30,5 +30,6 @@ fun aPreferencesRootState() = PreferencesRootState(
showAnalyticsSettings = true, showAnalyticsSettings = true,
showDeveloperSettings = true, showDeveloperSettings = true,
showNotificationSettings = true, showNotificationSettings = true,
showLockScreenSettings = true,
snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete), snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete),
) )

View file

@ -20,6 +20,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.outlined.InsertChart import androidx.compose.material.icons.outlined.InsertChart
import androidx.compose.material.icons.outlined.VerifiedUser import androidx.compose.material.icons.outlined.VerifiedUser
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -53,6 +54,7 @@ fun PreferencesRootView(
onManageAccountClicked: (url: String) -> Unit, onManageAccountClicked: (url: String) -> Unit,
onOpenAnalytics: () -> Unit, onOpenAnalytics: () -> Unit,
onOpenRageShake: () -> Unit, onOpenRageShake: () -> Unit,
onOpenLockScreenSettings: ()->Unit,
onOpenAbout: () -> Unit, onOpenAbout: () -> Unit,
onOpenDeveloperSettings: () -> Unit, onOpenDeveloperSettings: () -> Unit,
onOpenAdvancedSettings: () -> Unit, onOpenAdvancedSettings: () -> Unit,
@ -116,6 +118,13 @@ fun PreferencesRootView(
iconResourceId = CommonDrawables.ic_compound_info, iconResourceId = CommonDrawables.ic_compound_info,
onClick = onOpenAbout, onClick = onOpenAbout,
) )
if (state.showLockScreenSettings) {
PreferenceText(
title = stringResource(id = CommonStrings.common_screen_lock),
icon = Icons.Default.Lock,
onClick = onOpenLockScreenSettings,
)
}
HorizontalDivider() HorizontalDivider()
if (state.devicesManagementUrl != null) { if (state.devicesManagementUrl != null) {
PreferenceText( PreferenceText(
@ -183,6 +192,7 @@ private fun ContentToPreview(matrixUser: MatrixUser) {
onSuccessLogout = {}, onSuccessLogout = {},
onManageAccountClicked = {}, onManageAccountClicked = {},
onOpenNotificationSettings = {}, onOpenNotificationSettings = {},
onOpenLockScreenSettings = {},
onOpenUserProfile = {}, onOpenUserProfile = {},
) )
} }