Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions app/src/main/java/to/bitkit/ui/ContentView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ import to.bitkit.ui.settings.SecuritySettingsScreen
import to.bitkit.ui.settings.SettingsScreen
import to.bitkit.ui.settings.backups.BackupWalletScreen
import to.bitkit.ui.settings.backups.RestoreWalletScreen
import to.bitkit.ui.settings.pin.ChangePinConfirmScreen
import to.bitkit.ui.settings.pin.ChangePinScreen
import to.bitkit.ui.settings.pin.DisablePinScreen
import to.bitkit.ui.settings.pin.ChangePinNewScreen
import to.bitkit.ui.settings.pin.ChangePinResultScreen
import to.bitkit.ui.utils.screenScaleIn
import to.bitkit.ui.utils.screenScaleOut
import to.bitkit.ui.utils.screenSlideIn
Expand Down Expand Up @@ -234,6 +238,10 @@ fun ContentView(
generalSettings(navController)
securitySettings(navController)
disablePin(navController)
changePin(navController)
changePinNew(navController)
changePinConfirm(navController)
changePinResult(navController)
defaultUnitSettings(currencyViewModel, navController)
localCurrencySettings(currencyViewModel, navController)
backupSettings(navController)
Expand Down Expand Up @@ -491,6 +499,34 @@ private fun NavGraphBuilder.disablePin(navController: NavHostController) {
}
}

private fun NavGraphBuilder.changePin(navController: NavHostController) {
composableWithDefaultTransitions<Routes.ChangePin> {
ChangePinScreen(navController)
}
}

private fun NavGraphBuilder.changePinNew(navController: NavHostController) {
composableWithDefaultTransitions<Routes.ChangePinNew> {
ChangePinNewScreen(navController)
}
}

private fun NavGraphBuilder.changePinConfirm(navController: NavHostController) {
composableWithDefaultTransitions<Routes.ChangePinConfirm> { navBackEntry ->
val route = navBackEntry.toRoute<Routes.ChangePinConfirm>()
ChangePinConfirmScreen(
newPin = route.newPin,
navController = navController,
)
}
}

private fun NavGraphBuilder.changePinResult(navController: NavHostController) {
composableWithDefaultTransitions<Routes.ChangePinResult> {
ChangePinResultScreen(navController)
}
}

private fun NavGraphBuilder.defaultUnitSettings(
currencyViewModel: CurrencyViewModel,
navController: NavHostController,
Expand Down Expand Up @@ -700,6 +736,22 @@ fun NavController.navigateToDisablePin() = navigate(
route = Routes.DisablePin,
)

fun NavController.navigateToChangePin() = navigate(
route = Routes.ChangePin,
)

fun NavController.navigateToChangePinNew() = navigate(
route = Routes.ChangePinNew,
)

fun NavController.navigateToChangePinConfirm(newPin: String) = navigate(
route = Routes.ChangePinConfirm(newPin),
)

fun NavController.navigateToChangePinResult() = navigate(
route = Routes.ChangePinResult,
)

fun NavController.navigateToAuthCheck(
showLogoOnPin: Boolean = false,
requirePin: Boolean = false,
Expand Down Expand Up @@ -812,6 +864,18 @@ object Routes {
@Serializable
data object DisablePin

@Serializable
data object ChangePin

@Serializable
data object ChangePinNew

@Serializable
data class ChangePinConfirm(val newPin: String)

@Serializable
data object ChangePinResult

@Serializable
data class AuthCheck(
val showLogoOnPin: Boolean = false,
Expand Down
11 changes: 10 additions & 1 deletion app/src/main/java/to/bitkit/ui/components/AuthCheckView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import to.bitkit.R
import to.bitkit.env.Env
import to.bitkit.ui.scaffold.AppTopBar
import to.bitkit.ui.shared.util.clickableAlpha
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors
import to.bitkit.ui.utils.rememberBiometricAuthSupported
Expand Down Expand Up @@ -57,6 +58,7 @@ fun AuthCheckView(
validatePin = appViewModel::validatePin,
onSuccess = onSuccess,
onBack = onBack,
onClickForgotPin = { appViewModel.toast(Exception("TODO: Forgot PIN")) },
)
}

Expand All @@ -71,6 +73,7 @@ private fun AuthCheckViewContent(
validatePin: (String) -> Boolean,
onSuccess: (() -> Unit)? = null,
onBack: (() -> Unit)?,
onClickForgotPin: () -> Unit,
) {
var showBio by rememberSaveable { mutableStateOf(isBiometricsEnabled) }

Expand Down Expand Up @@ -98,6 +101,7 @@ private fun AuthCheckViewContent(
onShowBiometrics = { showBio = true },
onSuccess = onSuccess,
onBack = onBack,
onClickForgotPin = onClickForgotPin,
)
}
}
Expand All @@ -112,6 +116,7 @@ private fun PinPad(
onShowBiometrics: () -> Unit,
onSuccess: (() -> Unit)?,
onBack: (() -> Unit)? = null,
onClickForgotPin: () -> Unit = {},
) {
var pin by remember { mutableStateOf("") }
val isLastAttempt = attemptsRemaining == 1
Expand Down Expand Up @@ -153,11 +158,11 @@ private fun PinPad(
modifier = Modifier.padding(horizontal = 16.dp)
)
} else {
// TODO: onClick: show forgotPin sheet
BodyS(
text = stringResource(R.string.security__pin_attempts).replace("{attemptsRemaining}", "$attemptsRemaining"),
color = Colors.Brand,
textAlign = TextAlign.Center,
modifier = Modifier.clickableAlpha { onClickForgotPin() }
)
}
Spacer(modifier = Modifier.height(16.dp))
Expand Down Expand Up @@ -213,6 +218,7 @@ private fun PreviewBio() {
showLogoOnPin = true,
validatePin = { true },
attemptsRemaining = 8,
onClickForgotPin = {},
)
}
}
Expand All @@ -229,6 +235,7 @@ private fun PreviewPin() {
showLogoOnPin = true,
validatePin = { true },
attemptsRemaining = 8,
onClickForgotPin = {},
)
}
}
Expand All @@ -245,6 +252,7 @@ private fun PreviewPinAttempts() {
showLogoOnPin = false,
validatePin = { true },
attemptsRemaining = 6,
onClickForgotPin = {},
)
}
}
Expand All @@ -261,6 +269,7 @@ private fun PreviewPinAttemptLast() {
showLogoOnPin = true,
validatePin = { true },
attemptsRemaining = 1,
onClickForgotPin = {},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import to.bitkit.ui.components.BodyS
import to.bitkit.ui.components.settings.SettingsButtonRow
import to.bitkit.ui.components.settings.SettingsSwitchRow
import to.bitkit.ui.navigateToAuthCheck
import to.bitkit.ui.navigateToChangePin
import to.bitkit.ui.navigateToDisablePin
import to.bitkit.ui.navigateToHome
import to.bitkit.ui.scaffold.AppTopBar
Expand Down Expand Up @@ -60,6 +61,9 @@ fun SecuritySettingsScreen(
navController.navigateToDisablePin()
}
},
onChangePinClick = {
navController.navigateToChangePin()
},
onPinOnLaunchClick = {
navController.navigateToAuthCheck(
onSuccessActionId = AuthCheckAction.TOGGLE_PIN_ON_LAUNCH,
Expand All @@ -84,6 +88,7 @@ private fun SecuritySettingsContent(
isBiometricEnabled: Boolean,
isBiometrySupported: Boolean,
onPinClick: () -> Unit = {},
onChangePinClick: () -> Unit = {},
onPinOnLaunchClick: () -> Unit = {},
onUseBiometricsClick: () -> Unit = {},
onBackClick: () -> Unit = {},
Expand All @@ -108,6 +113,10 @@ private fun SecuritySettingsContent(
onClick = onPinClick,
)
if (isPinEnabled) {
SettingsButtonRow(
title = stringResource(R.string.settings__security__pin_change),
onClick = onChangePinClick,
)
SettingsSwitchRow(
title = stringResource(R.string.settings__security__pin_launch),
isChecked = isPinOnLaunchEnabled,
Expand Down
154 changes: 154 additions & 0 deletions app/src/main/java/to/bitkit/ui/settings/pin/ChangePinConfirmScreen.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package to.bitkit.ui.settings.pin

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import kotlinx.coroutines.delay
import to.bitkit.R
import to.bitkit.env.Env
import to.bitkit.ui.appViewModel
import to.bitkit.ui.components.BodyM
import to.bitkit.ui.components.BodyS
import to.bitkit.ui.components.KEY_DELETE
import to.bitkit.ui.components.PinDots
import to.bitkit.ui.components.PinNumberPad
import to.bitkit.ui.navigateToHome
import to.bitkit.ui.navigateToChangePinResult
import to.bitkit.ui.scaffold.AppTopBar
import to.bitkit.ui.scaffold.CloseNavIcon
import to.bitkit.ui.scaffold.ScreenColumn
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors

@Composable
fun ChangePinConfirmScreen(
newPin: String,
navController: NavController,
) {
val app = appViewModel ?: return
var pin by remember { mutableStateOf("") }
var showError by remember { mutableStateOf(false) }

LaunchedEffect(pin) {
if (pin.length == Env.PIN_LENGTH) {
if (pin == newPin) {
app.editPin(newPin)
navController.navigateToChangePinResult()
} else {
showError = true
delay(500)
pin = ""
}
}
}

ChangePinConfirmContent(
pin = pin,
showError = showError,
onKeyPress = { key ->
if (key == KEY_DELETE) {
if (pin.isNotEmpty()) {
pin = pin.dropLast(1)
}
} else if (pin.length < Env.PIN_LENGTH) {
pin += key
}
},
onBackClick = { navController.popBackStack() },
onCloseClick = { navController.navigateToHome() },
)
}

@Composable
private fun ChangePinConfirmContent(
pin: String,
showError: Boolean,
onKeyPress: (String) -> Unit,
onBackClick: () -> Unit,
onCloseClick: () -> Unit,
) {
ScreenColumn {
AppTopBar(
titleText = stringResource(R.string.security__cp_retype_title),
onBackClick = onBackClick,
actions = { CloseNavIcon(onClick = onCloseClick) },
)

Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(horizontal = 16.dp)
) {
BodyM(
text = stringResource(R.string.security__cp_retype_text),
color = Colors.White64,
)

Spacer(modifier = Modifier.height(32.dp))

AnimatedVisibility(visible = showError) {
BodyS(
text = stringResource(R.string.security__pin_not_match),
textAlign = TextAlign.Center,
color = Colors.Brand,
modifier = Modifier.fillMaxWidth()
)
}

PinDots(
pin = pin,
modifier = Modifier.padding(vertical = 16.dp),
)

Spacer(modifier = Modifier.weight(1f))

PinNumberPad(
modifier = Modifier.height(350.dp),
onPress = onKeyPress,
)
}
}
}

@Preview(showBackground = true)
@Composable
private fun Preview() {
AppThemeSurface {
ChangePinConfirmContent(
pin = "12",
showError = false,
onKeyPress = {},
onBackClick = {},
onCloseClick = {},
)
}
}

@Preview(showBackground = true)
@Composable
private fun PreviewRetry() {
AppThemeSurface {
ChangePinConfirmContent(
pin = "",
showError = true,
onKeyPress = {},
onBackClick = {},
onCloseClick = {},
)
}
}
Loading