Merge pull request #1714 from vector-im/feature/fga/unlock_settings_2

Pin unlock : implement design for in-app unlock
This commit is contained in:
ganfra 2023-10-31 21:39:25 +01:00 committed by GitHub
commit 46869b8df8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 228 additions and 104 deletions

View file

@ -94,7 +94,8 @@ class LockScreenFlowNode @AssistedInject constructor(
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) {
NavTarget.Unlock -> {
createNode<PinUnlockNode>(buildContext)
val inputs = PinUnlockNode.Inputs(isInAppUnlock = false)
createNode<PinUnlockNode>(buildContext, plugins = listOf(inputs))
}
NavTarget.Setup -> {
createNode<LockScreenSetupFlowNode>(buildContext)

View file

@ -113,7 +113,8 @@ class LockScreenSettingsFlowNode @AssistedInject constructor(
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) {
NavTarget.Unlock -> {
createNode<PinUnlockNode>(buildContext)
val inputs = PinUnlockNode.Inputs(isInAppUnlock = true)
createNode<PinUnlockNode>(buildContext, plugins = listOf(inputs))
}
NavTarget.Setup -> {
val callback = object : LockScreenSetupFlowNode.Callback {

View file

@ -20,6 +20,7 @@ import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
sealed interface PinUnlockEvents {
data class OnPinKeypadPressed(val pinKeypadModel: PinKeypadModel) : PinUnlockEvents
data class OnPinEntryChanged(val entryAsText: String) : PinUnlockEvents
data object OnForgetPin : PinUnlockEvents
data object ClearSignOutPrompt : PinUnlockEvents
data object SignOut : PinUnlockEvents

View file

@ -24,6 +24,8 @@ import com.bumble.appyx.core.plugin.Plugin
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.SessionScope
@ContributesNode(SessionScope::class)
@ -33,11 +35,18 @@ class PinUnlockNode @AssistedInject constructor(
private val presenter: PinUnlockPresenter,
) : Node(buildContext, plugins = plugins) {
data class Inputs(
val isInAppUnlock: Boolean
) : NodeInputs
private val inputs: Inputs = inputs()
@Composable
override fun View(modifier: Modifier) {
val state = presenter.present()
PinUnlockView(
state = state,
isInAppUnlock = inputs.isInAppUnlock,
modifier = modifier
)
}

View file

@ -116,6 +116,9 @@ class PinUnlockPresenter @Inject constructor(
PinUnlockEvents.ClearBiometricError -> {
biometricUnlockResult = null
}
is PinUnlockEvents.OnPinEntryChanged -> {
pinEntryState.value = pinEntry.process(event.entryAsText)
}
}
}
return PinUnlockState(
@ -159,6 +162,16 @@ class PinUnlockPresenter @Inject constructor(
}
}
private fun Async<PinEntry>.process(pinEntryAsText: String): Async<PinEntry> {
return when (this) {
is Async.Success -> {
val pinEntry = data.fillWith(pinEntryAsText)
Async.Success(pinEntry)
}
else -> this
}
}
private fun CoroutineScope.signOut(signOutAction: MutableState<Async<String?>>) = launch {
suspend {
matrixClient.logout(ignoreSdkError = true)

View file

@ -14,6 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.lockscreen.impl.unlock
import androidx.compose.foundation.background
@ -29,6 +30,7 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBarsPadding
@ -37,8 +39,12 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
@ -46,10 +52,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import io.element.android.features.lockscreen.impl.R
import io.element.android.features.lockscreen.impl.components.PinEntryTextField
import io.element.android.features.lockscreen.impl.pin.model.PinDigit
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypad
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom
import io.element.android.libraries.designsystem.components.ProgressDialog
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
@ -66,6 +74,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun PinUnlockView(
state: PinUnlockState,
isInAppUnlock: Boolean,
modifier: Modifier = Modifier,
) {
OnLifecycleEvent { _, event ->
@ -75,56 +84,7 @@ fun PinUnlockView(
}
}
Surface(modifier) {
BoxWithConstraints {
val commonModifier = Modifier
.fillMaxSize()
.systemBarsPadding()
.padding(all = 20.dp)
val header = @Composable {
PinUnlockHeader(
state = state,
modifier = Modifier.padding(top = 60.dp, bottom = 12.dp)
)
}
val footer = @Composable {
PinUnlockFooter(
modifier = Modifier.padding(top = 24.dp),
showBiometricUnlock = state.showBiometricUnlock,
onUseBiometric = {
state.eventSink(PinUnlockEvents.OnUseBiometric)
},
onForgotPin = {
state.eventSink(PinUnlockEvents.OnForgetPin)
},
)
}
val content = @Composable { constraints: BoxWithConstraintsScope ->
PinKeypad(
onClick = {
state.eventSink(PinUnlockEvents.OnPinKeypadPressed(it))
},
maxWidth = constraints.maxWidth,
maxHeight = constraints.maxHeight,
horizontalAlignment = Alignment.CenterHorizontally,
)
}
if (maxHeight < 600.dp) {
PinUnlockCompactView(
header = header,
footer = footer,
content = content,
modifier = commonModifier,
)
} else {
PinUnlockExpandedView(
header = header,
footer = footer,
content = content,
modifier = commonModifier,
)
}
}
PinUnlockPage(state = state, isInAppUnlock = isInAppUnlock)
if (state.showSignOutPrompt) {
SignOutPrompt(
isCancellable = state.isSignOutPromptCancellable,
@ -144,6 +104,86 @@ fun PinUnlockView(
}
}
@Composable
private fun PinUnlockPage(
state: PinUnlockState,
isInAppUnlock: Boolean,
modifier: Modifier = Modifier
) {
BoxWithConstraints {
val commonModifier = modifier
.fillMaxSize()
.systemBarsPadding()
.imePadding()
.padding(all = 20.dp)
val header = @Composable {
PinUnlockHeader(
state = state,
isInAppUnlock = isInAppUnlock,
modifier = Modifier.padding(top = 60.dp)
)
}
val footer = @Composable {
PinUnlockFooter(
modifier = Modifier.padding(top = 24.dp),
showBiometricUnlock = state.showBiometricUnlock,
onUseBiometric = {
state.eventSink(PinUnlockEvents.OnUseBiometric)
},
onForgotPin = {
state.eventSink(PinUnlockEvents.OnForgetPin)
},
)
}
val content = @Composable { constraints: BoxWithConstraintsScope ->
if (isInAppUnlock) {
val pinEntry = state.pinEntry.dataOrNull()
if (pinEntry != null) {
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
PinEntryTextField(
pinEntry = pinEntry,
isSecured = true,
onValueChange = {
state.eventSink(PinUnlockEvents.OnPinEntryChanged(it))
},
modifier = Modifier
.focusRequester(focusRequester)
.fillMaxWidth()
)
}
} else {
PinKeypad(
onClick = {
state.eventSink(PinUnlockEvents.OnPinKeypadPressed(it))
},
maxWidth = constraints.maxWidth,
maxHeight = constraints.maxHeight,
horizontalAlignment = Alignment.CenterHorizontally,
)
}
}
if (maxHeight < 600.dp) {
PinUnlockCompactView(
header = header,
footer = footer,
content = content,
modifier = commonModifier,
)
} else {
PinUnlockExpandedView(
header = header,
footer = footer,
content = content,
modifier = commonModifier,
)
}
}
}
@Composable
private fun SignOutPrompt(
isCancellable: Boolean,
@ -248,16 +288,21 @@ private fun PinDot(
@Composable
private fun PinUnlockHeader(
state: PinUnlockState,
isInAppUnlock: Boolean,
modifier: Modifier = Modifier,
) {
Column(modifier, horizontalAlignment = Alignment.CenterHorizontally) {
Icon(
modifier = Modifier
.size(32.dp),
tint = ElementTheme.colors.iconPrimary,
imageVector = Icons.Filled.Lock,
contentDescription = "",
)
if (isInAppUnlock) {
RoundedIconAtom(imageVector = Icons.Filled.Lock)
} else {
Icon(
modifier = Modifier
.size(32.dp),
tint = ElementTheme.colors.iconPrimary,
imageVector = Icons.Filled.Lock,
contentDescription = "",
)
}
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(id = CommonStrings.common_enter_your_pin),
@ -290,8 +335,8 @@ private fun PinUnlockHeader(
style = ElementTheme.typography.fontBodyMdRegular,
color = subtitleColor,
)
Spacer(Modifier.height(24.dp))
if (state.pinEntry is Async.Success) {
if (!isInAppUnlock && state.pinEntry is Async.Success) {
Spacer(Modifier.height(24.dp))
PinDotsRow(state.pinEntry.data)
}
}
@ -314,10 +359,22 @@ private fun PinUnlockFooter(
@Composable
@PreviewsDayNight
internal fun PinUnlockViewPreview(@PreviewParameter(PinUnlockStateProvider::class) state: PinUnlockState) {
internal fun PinUnlockInAppViewPreview(@PreviewParameter(PinUnlockStateProvider::class) state: PinUnlockState) {
ElementPreview {
PinUnlockView(
state = state,
isInAppUnlock = true,
)
}
}
@Composable
@PreviewsDayNight
internal fun PinUnlockDefaultViewPreview(@PreviewParameter(PinUnlockStateProvider::class) state: PinUnlockState) {
ElementPreview {
PinUnlockView(
state = state,
isInAppUnlock = false,
)
}
}

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1ece6fcfbf1c064287f6e0ead16c3abc471ea3f7db1aa4dbb7b8cdd2657e115a
size 40369

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8526a23e31e2c87ca2f43b127f00f05067a3525eff3319fe2965530096caab87
size 40836

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:88a97cfbbb601a889cc1f2a81ac6fcb9a58288232358968d536f4216ae8882f0
size 41692

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c5313634888c4d26b2a6c92485de0b2b4d7a6479ba5a872794c9fa8d8d30639b
size 46796

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:82d010d152311c8a19e33fa80ea7d70c061a0ed3bdc654f0cb7276207398d4ae
size 37930

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ee09e25c131831cb08ebd85fb2fcbe078f075da74734f34c9811fac6b6e30437
size 44372

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:38dfb87d845af20021622440ff3fee5d81f32d1a8a574f4ab9fece71ce36f5ce
size 35193

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2d538426d487708ba2139b322b31ff58675279f19c9dec53d636f68b0d5fa6a0
size 37231

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9651646be4e8b732d9d8e1395191ac2b3e1ca2dbf7280090741ffcac290402d4
size 37679

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:079e0f74c91d988d03af37ff66a94ad85df5e6312d7ebd531fde1cbf1105ec8e
size 38219

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:724b77f4eb0e3d721b05eddfc3d9c2e5b585213d1a4e4eca5d598175536abe47
size 41018

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6236756ef44cf19160e7977a9390dee51950f5324446355179e16285c5584270
size 35185

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bd70746d6c331e0c2004bc756b8ef207f755b357b3a6eff57ad085ff75e08a0f
size 38676

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4faa194d51d356d8ad659c225a92b124d9d93f16972acff1aa1b45aabfd101b6
size 31475

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b58f216453eb1d131ece67219021f074c1f5b1bc7593b76a2efedfb2c9f09887
size 23690

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:65226593d4548b87ded518f143b9a2030908befc5f423846a03e7938fe681390
size 23410

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:41e9a94fd494477437bbb625df08870875ef8021f7b02bc6e1953fac0cd3a8d3
size 24948

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e9c4bc23a4a15ebc6d434da33381336f01d7c1566fb9ef9dae94bf03138df334
size 39615

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e7081eb4ef25d5220e638e83ad4a3aa4248db8ddf22e371cb7698b6ce0fd91f3
size 21328

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1f7aa0b84dd7b059443afb2d0f281ab684266883010e9146b557954bf17c7807
size 37078

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:96299aa2059245aa3dd2c374d122c8cbda50040bf80930e62e74d634407bc913
size 26731

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:900a9ce415198cb4736d99f91e1f997021363f45ab5dded5e3870631e91c864f
size 22476

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bdb6f9f7e684d0bf2f84670cc7848b038fd6c8f7fde119de53fcc576aa522af5
size 22420

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9428610c1ffa0b93e53d1470a06e3cc8f3568a2dd86fd3fa99ef76d935220a5d
size 23467

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:05f512aa7541a7b5f92f14c3f9bcc2c8d69141b92fd0be4fcc2ead162ccd7015
size 34474

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:02876aee2a4e9574d4503bb2cb71ff25a588e5917356cff3f3d0ac517a16eee3
size 20399

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ae7c91ff420b979f5b818e5f750e3c740c9c03a0301660f90bb7471997c88b6b
size 32104

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4bca2311722ab2f3603da2cdb6181411c83b929c38b87b63a8923d0e563f915a
size 23809

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c94f10441359bdb901ee1f8be9c307312586d6ca54495e88ec231c1e931eb36b
size 38994

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:38456a511fbc72e3d544c8f88138be4f24b6adb3a10629989c037141213a9e19
size 39441

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:083c3972e5d019ce1d8565a655bd6f6fb6689e17792ddf7ed282d5eee529a7b6
size 40292

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bda49a721fbd19b4cbe0f4dc1b4013154a55e17f78e099f411d1caaef82cfa6c
size 46711

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1facbdcba634b37a8162876e6af02a3565eaee2ac4afe32816b1f8e9602b2a65
size 36565

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b0d360c0697733a9e1067589be5178f631da673743b97daeb50ebdc51fd89502
size 44289

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dc653f37c55ce7f9ef001d8f8ab57de23008baae2f644c6db49a7f4bccc831d5
size 34938

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ab8c04689414b5183fa6ab5a479063b478b3d02dfb6bf2df72b1b606c77392ad
size 36278

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fda961a2240c1458ad561c8f4e7fa265bbe6c70cf9b0fbfcd1ae0879036a3343
size 36714

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0cb889338b21dd8e1a913348ff02cd217f48840c4d54b99ed4d89b67dff771bb
size 37254

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:836f26b162a031b14151511405ec0b9dd43fe1c37127a5ec05e277ab0e2ab667
size 41054

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a4b3e856f102b2d53fd5776204a8576ed515e9e4bfd9899e4138eb1515a11b12
size 34241

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2c53928f35405266f99bbc4976f9c12915f47765289481b2cd65086925ea1b50
size 38710

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fd06f078cff08947537a58ddc2df9a79f4aa3d55372b6b8eafa962f2266ca02f
size 31443