diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/auth/PinAuthenticationView.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/auth/PinAuthenticationView.kt index 2b62e46800..740d85adec 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/auth/PinAuthenticationView.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/auth/PinAuthenticationView.kt @@ -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( ) { 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) { ) } +@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) { diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/auth/numpad/PinKeypad.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/auth/numpad/PinKeypad.kt new file mode 100644 index 0000000000..43abc24002 --- /dev/null +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/auth/numpad/PinKeypad.kt @@ -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, + 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 = {}) + } +} + + diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/auth/numpad/PinKeypadModel.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/auth/numpad/PinKeypadModel.kt new file mode 100644 index 0000000000..108486e400 --- /dev/null +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/auth/numpad/PinKeypadModel.kt @@ -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 +}