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
4 changes: 4 additions & 0 deletions app/src/main/java/to/bitkit/data/SettingsStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ class SettingsStore @Inject constructor(
val isPinOnIdleEnabled: Flow<Boolean> = store.data.map { it[IS_PIN_ON_IDLE_ENABLED] == true }
suspend fun setIsPinOnIdleEnabled(value: Boolean) { store.edit { it[IS_PIN_ON_IDLE_ENABLED] = value } }

val isPinForPaymentsEnabled: Flow<Boolean> = store.data.map { it[IS_PIN_FOR_PAYMENTS_ENABLED] == true }
suspend fun setIsPinForPaymentsEnabled(value: Boolean) { store.edit { it[IS_PIN_FOR_PAYMENTS_ENABLED] = value } }

suspend fun wipe() {
store.edit { it.clear() }
Logger.info("Deleted all user settings data.")
Expand All @@ -97,5 +100,6 @@ class SettingsStore @Inject constructor(
private val IS_PIN_ON_LAUNCH_ENABLED = booleanPreferencesKey("is_pin_on_launch_enabled")
private val IS_BIOMETRIC_ENABLED = booleanPreferencesKey("is_biometric_enabled")
private val IS_PIN_ON_IDLE_ENABLED = booleanPreferencesKey("is_pin_on_idle_enabled")
private val IS_PIN_FOR_PAYMENTS_ENABLED = booleanPreferencesKey("is_pin_for_payments_enabled")
}
}
6 changes: 6 additions & 0 deletions app/src/main/java/to/bitkit/ui/components/AuthCheckScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ fun AuthCheckScreen(
val isPinOnLaunchEnabled by app.isPinOnLaunchEnabled.collectAsStateWithLifecycle()
val isBiometricEnabled by app.isBiometricEnabled.collectAsStateWithLifecycle()
val isPinOnIdleEnabled by app.isPinOnIdleEnabled.collectAsStateWithLifecycle()
val isPinForPaymentsEnabled by app.isPinForPaymentsEnabled.collectAsStateWithLifecycle()

AuthCheckView(
showLogoOnPin = route.showLogoOnPin,
Expand All @@ -37,6 +38,10 @@ fun AuthCheckScreen(
app.setIsPinOnIdleEnabled(!isPinOnIdleEnabled)
}

AuthCheckAction.TOGGLE_PIN_FOR_PAYMENTS -> {
app.setIsPinForPaymentsEnabled(!isPinForPaymentsEnabled)
}

AuthCheckAction.DISABLE_PIN -> {
app.removePin()
}
Expand All @@ -52,5 +57,6 @@ object AuthCheckAction {
const val TOGGLE_PIN_ON_LAUNCH = "TOGGLE_PIN_ON_LAUNCH"
const val TOGGLE_BIOMETRICS = "TOGGLE_BIOMETRICS"
const val TOGGLE_PIN_ON_IDLE = "TOGGLE_PIN_ON_IDLE"
const val TOGGLE_PIN_FOR_PAYMENTS = "TOGGLE_PIN_FOR_PAYMENTS"
const val DISABLE_PIN = "DISABLE_PIN"
}
6 changes: 4 additions & 2 deletions app/src/main/java/to/bitkit/ui/components/AuthCheckView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ private fun PinPad(
)
} else {
BodyS(
text = stringResource(R.string.security__pin_attempts).replace("{attemptsRemaining}", "$attemptsRemaining"),
text = stringResource(R.string.security__pin_attempts)
.replace("{attemptsRemaining}", "$attemptsRemaining"),
color = Colors.Brand,
textAlign = TextAlign.Center,
modifier = Modifier.clickableAlpha { onClickForgotPin() }
Expand All @@ -175,7 +176,8 @@ private fun PinPad(
if (allowBiometrics) {
val biometricsName = stringResource(R.string.security__bio)
PrimaryButton(
text = stringResource(R.string.security__pin_use_biometrics).replace("{biometricsName}", biometricsName),
text = stringResource(R.string.security__pin_use_biometrics)
.replace("{biometricsName}", biometricsName),
onClick = onShowBiometrics,
fullWidth = false,
size = ButtonSize.Small,
Expand Down
187 changes: 187 additions & 0 deletions app/src/main/java/to/bitkit/ui/screens/wallets/send/PinCheckScreen.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package to.bitkit.ui.screens.wallets.send

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
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.navigationBarsPadding
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.lifecycle.compose.collectAsStateWithLifecycle
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.scaffold.SheetTopBar
import to.bitkit.ui.shared.util.clickableAlpha
import to.bitkit.ui.shared.util.gradientBackground
import to.bitkit.ui.theme.AppThemeSurface
import to.bitkit.ui.theme.Colors

const val PIN_CHECK_RESULT_KEY = "PIN_CHECK_RESULT_KEY"

@Composable
fun PinCheckScreen(
onBack: () -> Unit,
onSuccess: () -> Unit,
) {
val app = appViewModel ?: return
val attemptsRemaining by app.pinAttemptsRemaining.collectAsStateWithLifecycle()
var pin by remember { mutableStateOf("") }

LaunchedEffect(pin) {
if (pin.length == Env.PIN_LENGTH) {
if (app.validatePin(pin)) {
onSuccess()
}
pin = ""
}
}

PinCheckContent(
pin = pin,
attemptsRemaining = attemptsRemaining,
onKeyPress = { key ->
if (key == KEY_DELETE) {
if (pin.isNotEmpty()) {
pin = pin.dropLast(1)
}
} else if (pin.length < Env.PIN_LENGTH) {
pin += key
}
},
onBack = onBack,
onClickForgotPin = { app.setShowForgotPin(true) },
)
}

@Composable
private fun PinCheckContent(
pin: String,
attemptsRemaining: Int,
onKeyPress: (String) -> Unit,
onBack: () -> Unit,
onClickForgotPin: () -> Unit,
) {
val isLastAttempt = attemptsRemaining == 1

Column(
modifier = Modifier
.fillMaxWidth()
.gradientBackground()
.navigationBarsPadding()
) {
SheetTopBar(
titleText = stringResource(R.string.security__pin_send_title),
onBack = onBack,
)
Spacer(Modifier.height(32.dp))

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

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

AnimatedVisibility(visible = attemptsRemaining < Env.PIN_ATTEMPTS) {
if (isLastAttempt) {
BodyS(
text = stringResource(R.string.security__pin_last_attempt),
color = Colors.Brand,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 32.dp)
)
} else {
BodyS(
text = stringResource(R.string.security__pin_attempts)
.replace("{attemptsRemaining}", "$attemptsRemaining"),
color = Colors.Brand,
textAlign = TextAlign.Center,
modifier = Modifier
.padding(horizontal = 32.dp)
.clickableAlpha { onClickForgotPin() }
)
}
Spacer(modifier = Modifier.height(16.dp))
}

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

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

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

@Preview
@Composable
private fun Preview() {
AppThemeSurface {
PinCheckContent(
pin = "123",
attemptsRemaining = 8,
onKeyPress = {},
onBack = {},
onClickForgotPin = {},
)
}
}

@Preview
@Composable
private fun PreviewAttempts() {
AppThemeSurface {
PinCheckContent(
pin = "123",
attemptsRemaining = 3,
onKeyPress = {},
onBack = {},
onClickForgotPin = {},
)
}
}

@Preview
@Composable
private fun PreviewAttemptsLast() {
AppThemeSurface {
PinCheckContent(
pin = "123",
attemptsRemaining = 1,
onKeyPress = {},
onBack = {},
onClickForgotPin = {},
)
}
}
Loading