mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-06 11:28:22 +00:00
Support for custom pin
This commit is contained in:
@@ -33,6 +33,8 @@ private const val DEBUG_PLAINTEXT_PREFERENCES = false
|
||||
private const val DEBUG_PREFERENCES_NAME = "debug_prefs"
|
||||
|
||||
private object PrefKeys {
|
||||
const val PIN = "pin"
|
||||
const val USE_PIN = "use_pin"
|
||||
const val CURRENT_ACCOUNT = "currently_logged_in_account"
|
||||
const val SAVED_ACCOUNTS = "all_saved_accounts"
|
||||
const val NOSTR_PRIVKEY = "nostr_privkey"
|
||||
@@ -115,6 +117,23 @@ object LocalPreferences {
|
||||
putBoolean(PrefKeys.USE_AUTH, settings.useAuth)
|
||||
putInt(PrefKeys.NOTIFICATION_TYPE, settings.notificationType.screenCode)
|
||||
putInt(PrefKeys.BIOMETRICS_TYPE, settings.biometricsTimeType.screenCode)
|
||||
putBoolean(PrefKeys.USE_PIN, settings.usePin)
|
||||
}.apply()
|
||||
}
|
||||
|
||||
fun loadPinFromEncryptedStorage(): String? {
|
||||
val context = NostrSigner.getInstance()
|
||||
return encryptedPreferences(context).getString(PrefKeys.PIN, null)
|
||||
}
|
||||
|
||||
fun savePinToEncryptedStorage(pin: String?) {
|
||||
val context = NostrSigner.getInstance()
|
||||
encryptedPreferences(context).edit().apply {
|
||||
if (pin == null) {
|
||||
remove(PrefKeys.PIN)
|
||||
} else {
|
||||
putString(PrefKeys.PIN, pin)
|
||||
}
|
||||
}.apply()
|
||||
}
|
||||
|
||||
@@ -138,6 +157,7 @@ object LocalPreferences {
|
||||
useAuth = getBoolean(PrefKeys.USE_AUTH, false),
|
||||
notificationType = parseNotificationType(getInt(PrefKeys.NOTIFICATION_TYPE, 1)),
|
||||
biometricsTimeType = parseBiometricsTimeType(getInt(PrefKeys.BIOMETRICS_TYPE, 0)),
|
||||
usePin = getBoolean(PrefKeys.USE_PIN, false),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -404,4 +424,12 @@ object LocalPreferences {
|
||||
return account
|
||||
}
|
||||
}
|
||||
|
||||
fun clearPin() {
|
||||
val context = NostrSigner.getInstance()
|
||||
encryptedPreferences(context).edit().apply {
|
||||
remove(PrefKeys.USE_PIN)
|
||||
}.apply()
|
||||
NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy(usePin = false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
@@ -35,6 +37,7 @@ import com.greenart7c3.nostrsigner.service.IntentUtils
|
||||
import com.greenart7c3.nostrsigner.ui.AccountScreen
|
||||
import com.greenart7c3.nostrsigner.ui.AccountStateViewModel
|
||||
import com.greenart7c3.nostrsigner.ui.BiometricsTimeType
|
||||
import com.greenart7c3.nostrsigner.ui.components.RandomPinInput
|
||||
import com.greenart7c3.nostrsigner.ui.theme.NostrSignerTheme
|
||||
import com.vitorpamplona.ammolite.service.HttpClientManager
|
||||
import java.time.Duration
|
||||
@@ -58,7 +61,6 @@ class MainActivity : AppCompatActivity() {
|
||||
mainViewModel = MainViewModel(applicationContext)
|
||||
|
||||
HttpClientManager.setDefaultUserAgent("Amber/${BuildConfig.VERSION_NAME}")
|
||||
|
||||
setContent {
|
||||
if (intent.isLaunchFromHistory()) {
|
||||
Log.d("isLaunchFromHistory", "Cleared intent history")
|
||||
@@ -78,6 +80,7 @@ class MainActivity : AppCompatActivity() {
|
||||
val navController = rememberNavController()
|
||||
mainViewModel.navController = navController
|
||||
var isAuthenticated by remember { mutableStateOf(false) }
|
||||
var showPinDialog by remember { mutableStateOf(false) }
|
||||
val keyguardLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
@@ -89,43 +92,48 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
launch(Dispatchers.IO) {
|
||||
val lastAuthTime = NostrSigner.getInstance().settings.lastBiometricsTime
|
||||
val whenToAsk = NostrSigner.getInstance().settings.biometricsTimeType
|
||||
val settings = NostrSigner.getInstance().settings
|
||||
val lastAuthTime = settings.lastBiometricsTime
|
||||
val whenToAsk = settings.biometricsTimeType
|
||||
val isTimeToAsk = when (whenToAsk) {
|
||||
BiometricsTimeType.EVERY_TIME -> true
|
||||
BiometricsTimeType.ONE_MINUTE -> minutesBetween(lastAuthTime, System.currentTimeMillis()) >= 1
|
||||
BiometricsTimeType.FIVE_MINUTES -> minutesBetween(lastAuthTime, System.currentTimeMillis()) >= 5
|
||||
BiometricsTimeType.TEN_MINUTES -> minutesBetween(lastAuthTime, System.currentTimeMillis()) >= 10
|
||||
}
|
||||
val shouldAuthenticate = NostrSigner.getInstance().settings.useAuth && isTimeToAsk
|
||||
val shouldAuthenticate = (settings.useAuth || settings.usePin) && isTimeToAsk
|
||||
if (!shouldAuthenticate) {
|
||||
isAuthenticated = true
|
||||
} else {
|
||||
if (!isAuthenticated) {
|
||||
launch(Dispatchers.Main) {
|
||||
Biometrics.authenticate(
|
||||
getString(R.string.authenticate),
|
||||
this@MainActivity,
|
||||
keyguardLauncher,
|
||||
{
|
||||
NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy(
|
||||
lastBiometricsTime = System.currentTimeMillis(),
|
||||
)
|
||||
if (settings.usePin) {
|
||||
showPinDialog = true
|
||||
} else {
|
||||
Biometrics.authenticate(
|
||||
getString(R.string.authenticate),
|
||||
this@MainActivity,
|
||||
keyguardLauncher,
|
||||
{
|
||||
NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy(
|
||||
lastBiometricsTime = System.currentTimeMillis(),
|
||||
)
|
||||
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||
isAuthenticated = true
|
||||
},
|
||||
{ _, message ->
|
||||
this@MainActivity.finish()
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
message,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
},
|
||||
)
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||
isAuthenticated = true
|
||||
},
|
||||
{ _, message ->
|
||||
this@MainActivity.finish()
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
message,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,6 +144,50 @@ class MainActivity : AppCompatActivity() {
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
) {
|
||||
if (showPinDialog) {
|
||||
Dialog(
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||
onDismissRequest = {
|
||||
showPinDialog = false
|
||||
this@MainActivity.finish()
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
getString(R.string.pin_does_not_match),
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
},
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
) {
|
||||
RandomPinInput(
|
||||
text = getString(R.string.enter_pin),
|
||||
onPinEntered = {
|
||||
val pin = LocalPreferences.loadPinFromEncryptedStorage()
|
||||
if (it == pin) {
|
||||
NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy(
|
||||
lastBiometricsTime = System.currentTimeMillis(),
|
||||
)
|
||||
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||
isAuthenticated = true
|
||||
showPinDialog = false
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
getString(R.string.pin_does_not_match),
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
||||
@@ -13,4 +13,5 @@ data class AmberSettings(
|
||||
val useAuth: Boolean = false,
|
||||
val notificationType: NotificationType = NotificationType.DIRECT,
|
||||
val biometricsTimeType: BiometricsTimeType = BiometricsTimeType.EVERY_TIME,
|
||||
val usePin: Boolean = false,
|
||||
)
|
||||
|
||||
@@ -903,6 +903,35 @@ fun MainScreen(
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
composable(
|
||||
Route.SetupPin.route,
|
||||
content = {
|
||||
SetupPinScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding),
|
||||
navController = navController,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
composable(
|
||||
Route.ConfirmPin.route,
|
||||
arguments = listOf(navArgument("pin") { type = NavType.StringType }),
|
||||
content = {
|
||||
it.arguments?.getString("pin")?.let { pin ->
|
||||
ConfirmPinScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding),
|
||||
accountStateViewModel = accountStateViewModel,
|
||||
pin = pin,
|
||||
navController = navController,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.greenart7c3.nostrsigner.NostrSigner
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.ui.components.AmberButton
|
||||
import com.greenart7c3.nostrsigner.ui.components.TitleExplainer
|
||||
import com.greenart7c3.nostrsigner.ui.navigation.Route
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -45,6 +46,7 @@ fun SecurityScreen(
|
||||
TitleExplainer(stringResource(BiometricsTimeType.TEN_MINUTES.resourceId)),
|
||||
)
|
||||
var enableBiometrics by remember { mutableStateOf(NostrSigner.getInstance().settings.useAuth) }
|
||||
var setupPin by remember { mutableStateOf(NostrSigner.getInstance().settings.usePin) }
|
||||
var biometricsIndex by remember {
|
||||
mutableIntStateOf(NostrSigner.getInstance().settings.biometricsTimeType.screenCode)
|
||||
}
|
||||
@@ -81,6 +83,38 @@ fun SecurityScreen(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp)
|
||||
.clickable {
|
||||
if (setupPin) {
|
||||
val pin = LocalPreferences.loadPinFromEncryptedStorage()
|
||||
navController.navigate("${Route.ConfirmPin.route.split("/")[0]}/$pin")
|
||||
} else {
|
||||
navController.navigate(Route.SetupPin.route)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringResource(R.string.setup_pin),
|
||||
)
|
||||
Switch(
|
||||
checked = setupPin,
|
||||
onCheckedChange = {
|
||||
if (setupPin) {
|
||||
val pin = LocalPreferences.loadPinFromEncryptedStorage()
|
||||
navController.navigate("${Route.ConfirmPin.route.split("/")[0]}/$pin")
|
||||
} else {
|
||||
navController.navigate(Route.SetupPin.route)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.padding(8.dp),
|
||||
|
||||
@@ -54,7 +54,6 @@ import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.NostrSigner
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
import com.greenart7c3.nostrsigner.service.Biometrics
|
||||
import com.greenart7c3.nostrsigner.service.NotificationDataSource
|
||||
import com.greenart7c3.nostrsigner.ui.actions.ConnectOrbotDialog
|
||||
import com.greenart7c3.nostrsigner.ui.actions.LogoutDialog
|
||||
@@ -101,20 +100,18 @@ fun SettingsScreen(
|
||||
modifier
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
if (Biometrics.isFingerprintAvailable(context)) {
|
||||
Box(
|
||||
Modifier
|
||||
.padding(8.dp),
|
||||
) {
|
||||
IconRow(
|
||||
title = stringResource(R.string.security),
|
||||
icon = Icons.Default.Security,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
onClick = {
|
||||
navController.navigate(Route.Security.route)
|
||||
},
|
||||
)
|
||||
}
|
||||
Box(
|
||||
Modifier
|
||||
.padding(8.dp),
|
||||
) {
|
||||
IconRow(
|
||||
title = stringResource(R.string.security),
|
||||
icon = Icons.Default.Security,
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
onClick = {
|
||||
navController.navigate(Route.Security.route)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.greenart7c3.nostrsigner.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.NostrSigner
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.ui.components.RandomPinInput
|
||||
import com.greenart7c3.nostrsigner.ui.navigation.Route
|
||||
|
||||
@Composable
|
||||
fun SetupPinScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
navController: NavController,
|
||||
) {
|
||||
Column(
|
||||
modifier
|
||||
.padding(16.dp),
|
||||
) {
|
||||
RandomPinInput { pin ->
|
||||
navController.navigate("${Route.ConfirmPin.route.split("/")[0]}/$pin")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ConfirmPinScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
accountStateViewModel: AccountStateViewModel,
|
||||
pin: String,
|
||||
navController: NavController,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Column(
|
||||
modifier
|
||||
.padding(16.dp),
|
||||
) {
|
||||
RandomPinInput(
|
||||
onPinEntered = { enteredPin ->
|
||||
if (enteredPin != pin) {
|
||||
accountStateViewModel.toast(context.getString(R.string.pin), context.getString(R.string.pin_does_not_match))
|
||||
} else {
|
||||
val usePin = NostrSigner.getInstance().settings.usePin
|
||||
if (usePin) {
|
||||
NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy(usePin = false)
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||
LocalPreferences.savePinToEncryptedStorage(null)
|
||||
navController.navigate(Route.Security.route) {
|
||||
popUpTo(Route.Security.route) {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NostrSigner.getInstance().settings = NostrSigner.getInstance().settings.copy(usePin = true)
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(NostrSigner.getInstance().settings)
|
||||
LocalPreferences.savePinToEncryptedStorage(pin)
|
||||
navController.navigate(Route.Security.route) {
|
||||
popUpTo(Route.Security.route) {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package com.greenart7c3.nostrsigner.ui.components
|
||||
|
||||
import android.view.WindowManager
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.itemsIndexed
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Backspace
|
||||
import androidx.compose.material.icons.filled.Backspace
|
||||
import androidx.compose.material.icons.filled.Done
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
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.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import com.greenart7c3.nostrsigner.service.getAppCompatActivity
|
||||
|
||||
@Composable
|
||||
fun RandomPinInput(
|
||||
text: String? = null,
|
||||
onPinEntered: (String) -> Unit,
|
||||
) {
|
||||
val randomNumbers = remember { (0..9).shuffled() }
|
||||
var selectedPin by remember { mutableStateOf("") }
|
||||
var obscureText by remember { mutableStateOf(true) }
|
||||
|
||||
val activity = LocalContext.current.getAppCompatActivity()
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
|
||||
DisposableEffect(key1 = lifecycleOwner) {
|
||||
val observer = LifecycleEventObserver { _, event ->
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_START -> {
|
||||
activity?.window?.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
)
|
||||
}
|
||||
|
||||
Lifecycle.Event.ON_PAUSE -> {
|
||||
activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
lifecycleOwner.lifecycle.addObserver(observer)
|
||||
|
||||
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(100.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
text?.let {
|
||||
Text(
|
||||
text = it,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
||||
Text(
|
||||
text = if (!obscureText) selectedPin else selectedPin.map { '*' }.joinToString(""),
|
||||
)
|
||||
|
||||
if (selectedPin.isNotEmpty()) {
|
||||
IconButton(
|
||||
onClick = { obscureText = !obscureText },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (obscureText) Icons.Default.Visibility else Icons.Default.VisibilityOff,
|
||||
contentDescription = "Show/Hide",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(3),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
itemsIndexed(randomNumbers.dropLast(1)) { _, number ->
|
||||
Button(
|
||||
onClick = {
|
||||
if (selectedPin.length < 6) {
|
||||
selectedPin += number.toString()
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(8.dp),
|
||||
) {
|
||||
Text(number.toString())
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Button(
|
||||
onClick = {
|
||||
if (selectedPin.isNotEmpty()) {
|
||||
selectedPin = selectedPin.dropLast(1) // Remove last digit
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1f).padding(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.Backspace,
|
||||
contentDescription = "Erase",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Button(
|
||||
onClick = {
|
||||
if (selectedPin.length < 6) {
|
||||
selectedPin += randomNumbers.last().toString()
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1f).padding(8.dp),
|
||||
) {
|
||||
Text(randomNumbers.last().toString())
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Button(
|
||||
enabled = selectedPin.length >= 4,
|
||||
onClick = {
|
||||
if (selectedPin.length >= 4) {
|
||||
onPinEntered(selectedPin) // Confirm the PIN
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1f).padding(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Done,
|
||||
contentDescription = "Done",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,6 +157,20 @@ sealed class Route(
|
||||
icon = Icons.Outlined.Settings,
|
||||
selectedIcon = Icons.Default.Settings,
|
||||
)
|
||||
|
||||
data object SetupPin : Route(
|
||||
title = NostrSigner.getInstance().getString(R.string.setup_pin),
|
||||
route = "SetupPin",
|
||||
icon = Icons.Outlined.Settings,
|
||||
selectedIcon = Icons.Default.Settings,
|
||||
)
|
||||
|
||||
data object ConfirmPin : Route(
|
||||
title = NostrSigner.getInstance().getString(R.string.confirm_pin),
|
||||
route = "ConfirmPin/{pin}",
|
||||
icon = Icons.Outlined.Settings,
|
||||
selectedIcon = Icons.Default.Settings,
|
||||
)
|
||||
}
|
||||
|
||||
val routes = listOf(
|
||||
@@ -179,4 +193,6 @@ val routes = listOf(
|
||||
Route.Activity,
|
||||
Route.RelayLogScreen,
|
||||
Route.EditConfiguration,
|
||||
Route.SetupPin,
|
||||
Route.ConfirmPin,
|
||||
)
|
||||
|
||||
@@ -409,4 +409,9 @@
|
||||
<string name="edit_configuration_description">To create a new nsecbunker you need to give it a name and select one or more relays, that’s all!</string>
|
||||
<string name="update">Update</string>
|
||||
<string name="add">Add</string>
|
||||
<string name="setup_pin">Setup PIN</string>
|
||||
<string name="pin">Pin</string>
|
||||
<string name="pin_does_not_match">Pin does not match</string>
|
||||
<string name="confirm_pin">Confirm Pin</string>
|
||||
<string name="enter_pin">Enter PIN</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user