Browse Source

Pin auth : simple first iteration on ui

pull/1624/head
ganfra 11 months ago
parent
commit
784415f698
  1. 115
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/auth/PinAuthenticationView.kt
  2. 182
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/auth/numpad/PinKeypad.kt
  3. 26
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/auth/numpad/PinKeypadModel.kt

115
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/auth/PinAuthenticationView.kt

@ -16,22 +16,40 @@ @@ -16,22 +16,40 @@
package io.element.android.features.lockscreen.impl.auth
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
import io.element.android.features.lockscreen.impl.auth.numpad.PinKeypad
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
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.Icon
import io.element.android.libraries.designsystem.theme.components.Surface
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.theme.ElementTheme
@Composable
fun PinAuthenticationView(
@ -40,27 +58,23 @@ fun PinAuthenticationView( @@ -40,27 +58,23 @@ fun PinAuthenticationView(
) {
Surface(modifier) {
HeaderFooterPage(
modifier = Modifier
.systemBarsPadding()
.fillMaxSize(),
header = { PinAuthenticationHeader(modifier = Modifier.padding(top = 60.dp, bottom = 12.dp)) },
footer = { PinAuthenticationFooter(state) },
content = {
Box(
modifier = Modifier
.padding(top = 40.dp)
.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
PinKeypad(
onClick = {}
)
}
}
)
}
}
@Composable
private fun PinAuthenticationHeader(
modifier: Modifier = Modifier,
) {
IconTitleSubtitleMolecule(
modifier = modifier,
title = "Element X is locked",
subTitle = null,
iconImageVector = Icons.Default.Lock,
)
}
@Composable
private fun PinAuthenticationFooter(state: PinAuthenticationState) {
Button(
@ -72,6 +86,69 @@ private fun PinAuthenticationFooter(state: PinAuthenticationState) { @@ -72,6 +86,69 @@ private fun PinAuthenticationFooter(state: PinAuthenticationState) {
)
}
@Composable
private fun PinDotsRow(
modifier: Modifier = Modifier,
) {
Row(modifier, horizontalArrangement = spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
PinDot(isFilled = true)
PinDot(isFilled = true)
PinDot(isFilled = false)
PinDot(isFilled = false)
}
}
@Composable
private fun PinDot(
isFilled: Boolean,
modifier: Modifier = Modifier,
) {
val backgroundColor = if (isFilled) {
ElementTheme.colors.iconPrimary
} else {
ElementTheme.colors.bgSubtlePrimary
}
Box(
modifier = modifier
.size(14.dp)
.background(backgroundColor, CircleShape)
)
}
@Composable
private fun PinAuthenticationHeader(
modifier: Modifier = Modifier,
) {
Column(modifier, horizontalAlignment = Alignment.CenterHorizontally) {
Icon(
modifier = Modifier
.size(32.dp),
tint = ElementTheme.colors.iconPrimary,
imageVector = Icons.Filled.Lock,
contentDescription = "",
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Enter your PIN",
modifier = Modifier
.fillMaxWidth(),
textAlign = TextAlign.Center,
style = ElementTheme.typography.fontHeadingMdBold,
color = MaterialTheme.colorScheme.primary,
)
Spacer(Modifier.height(8.dp))
Text(
text = "You have 3 attempts to unlock",
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
style = ElementTheme.typography.fontBodyMdRegular,
color = MaterialTheme.colorScheme.secondary,
)
Spacer(Modifier.height(24.dp))
PinDotsRow()
}
}
@Composable
@PreviewsDayNight
internal fun PinAuthenticationViewPreview(@PreviewParameter(PinAuthenticationStateProvider::class) state: PinAuthenticationState) {

182
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/auth/numpad/PinKeypad.kt

@ -0,0 +1,182 @@ @@ -0,0 +1,182 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.lockscreen.impl.auth.numpad
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Backspace
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
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.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.toSp
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.theme.ElementTheme
@Composable
fun PinKeypad(
onClick: (PinKeypadModel) -> Unit,
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
verticalAlignment: Alignment.Vertical = Alignment.Top,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
) {
Column(
modifier = modifier,
verticalArrangement = verticalArrangement,
horizontalAlignment = horizontalAlignment,
) {
PinKeypadRow(
verticalAlignment = verticalAlignment,
horizontalArrangement = horizontalArrangement,
models = listOf(PinKeypadModel.Number("1"), PinKeypadModel.Number("2"), PinKeypadModel.Number("3")),
onClick = onClick,
)
PinKeypadRow(
verticalAlignment = verticalAlignment,
horizontalArrangement = horizontalArrangement,
models = listOf(PinKeypadModel.Number("4"), PinKeypadModel.Number("5"), PinKeypadModel.Number("6")),
onClick = onClick,
)
PinKeypadRow(
verticalAlignment = verticalAlignment,
horizontalArrangement = horizontalArrangement,
models = listOf(PinKeypadModel.Number("7"), PinKeypadModel.Number("8"), PinKeypadModel.Number("9")),
onClick = onClick,
)
PinKeypadRow(
verticalAlignment = verticalAlignment,
horizontalArrangement = horizontalArrangement,
models = listOf(PinKeypadModel.Empty, PinKeypadModel.Number("0"), PinKeypadModel.Back),
onClick = onClick,
)
}
}
@Composable
private fun PinKeypadRow(
models: List<PinKeypadModel>,
onClick: (PinKeypadModel) -> Unit,
modifier: Modifier = Modifier,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top,
) {
Row(
horizontalArrangement = horizontalArrangement,
verticalAlignment = verticalAlignment,
modifier = modifier,
) {
val commonModifier = Modifier.size(80.dp)
for (model in models) {
when (model) {
is PinKeypadModel.Empty -> {
Spacer(modifier = commonModifier)
}
is PinKeypadModel.Back -> {
PinKeypadBackButton(
modifier = commonModifier,
onClick = { onClick(model) },
)
}
is PinKeypadModel.Number -> {
PinKeyBadDigitButton(
size = 80.dp,
modifier = commonModifier,
digit = model.number,
onClick = { onClick(model) },
)
}
}
}
}
}
@Composable
private fun PinKeyBadDigitButton(
digit: String,
size: Dp,
onClick: (String) -> Unit,
modifier: Modifier = Modifier,
) {
Button(
colors = ButtonDefaults.buttonColors(
containerColor = ElementTheme.colors.bgSubtlePrimary,
contentColor = Color.Transparent,
),
shape = CircleShape,
contentPadding = PaddingValues(0.dp),
modifier = modifier
.clip(CircleShape),
onClick = { onClick(digit) }
) {
val fontSize = 80.dp.toSp() / 2
val originalFont = ElementTheme.typography.fontHeadingXlBold
val ratio = fontSize.value / originalFont.fontSize.value
val lineHeight = originalFont.lineHeight * ratio
Text(
text = digit,
color = ElementTheme.colors.textPrimary,
style = originalFont.copy(fontSize = fontSize, lineHeight = lineHeight, letterSpacing = 0.sp),
)
}
}
@Composable
private fun PinKeypadBackButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
IconButton(
modifier = modifier
.clip(CircleShape)
.background(color = Color.Transparent, shape = CircleShape),
onClick = onClick,
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.Backspace,
contentDescription = null,
)
}
}
@Composable
@PreviewsDayNight
fun PinKeypad() {
ElementPreview {
PinKeypad(onClick = {})
}
}

26
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/auth/numpad/PinKeypadModel.kt

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.lockscreen.impl.auth.numpad
import androidx.compose.runtime.Immutable
@Immutable
sealed interface PinKeypadModel {
data object Empty : PinKeypadModel
data object Back : PinKeypadModel
data class Number(val number: String) : PinKeypadModel
}
Loading…
Cancel
Save