mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 16:11:20 +02:00
Add setting to protect a user profile with a PIN (#356)
Allows for setting a per-user profile PIN that must be entered to switch to the user. The PIN is a sequence of directional buttons, ie D-Pad left/right/up/down. This PIN is per device per user per server. The PIN is stored locally and not sent nor shared with the server. If you forget your PIN, can still access the app using server credentials which also removes the PIN. Additionally, there is another setting to disable automatically sign in. When enabled (default) the last user is automatically restored, even if there is a PIN. If disabled, a server & user must always be selected when opening the app. Closes #268 Closes #321
This commit is contained in:
parent
a294661520
commit
8ea84b3efe
20 changed files with 845 additions and 30 deletions
|
|
@ -19,6 +19,8 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||
|
|
@ -398,13 +400,21 @@ fun logTab(
|
|||
|
||||
suspend fun <T> onMain(block: suspend CoroutineScope.() -> T) = withContext(Dispatchers.Main, block)
|
||||
|
||||
fun Modifier.dimAndBlur(enabled: Boolean) =
|
||||
this.ifElse(
|
||||
enabled,
|
||||
Modifier
|
||||
.alpha(.5f)
|
||||
.blur(16.dp),
|
||||
)
|
||||
|
||||
fun Response<BaseItemDtoQueryResult>.toBaseItems(
|
||||
api: ApiClient,
|
||||
useSeriesForPrimary: Boolean,
|
||||
) = this.content.items.map { BaseItem.from(it, api, useSeriesForPrimary) }
|
||||
|
||||
@Composable
|
||||
fun rememberBackDropImage(item: BaseItem) {
|
||||
fun rememberBackDropImage(item: BaseItem): String? {
|
||||
val imageUrlService = LocalImageUrlService.current
|
||||
return remember(item) { imageUrlService.getItemImageUrl(item, ImageType.BACKDROP) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ import com.github.damontecres.wholphin.preferences.uiPreferences
|
|||
import com.github.damontecres.wholphin.preferences.updatePlaybackPreferences
|
||||
import com.github.damontecres.wholphin.services.UpdateChecker
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.playOnClickSound
|
||||
import com.github.damontecres.wholphin.ui.playSoundOnFocus
|
||||
|
|
@ -78,6 +79,8 @@ fun PreferencesContent(
|
|||
var focusedIndex by rememberSaveable { mutableStateOf(Pair(0, 0)) }
|
||||
val state = rememberLazyListState()
|
||||
var preferences by remember { mutableStateOf(initialPreferences) }
|
||||
val currentUser by viewModel.currentUser.observeAsState()
|
||||
var showPinFlow by remember { mutableStateOf(false) }
|
||||
|
||||
val navDrawerPins by viewModel.navDrawerPins.observeAsState(mapOf())
|
||||
var cacheUsage by remember { mutableStateOf(CacheUsage(0, 0, 0)) }
|
||||
|
|
@ -365,6 +368,19 @@ fun PreferencesContent(
|
|||
)
|
||||
}
|
||||
|
||||
AppPreference.RequireProfilePin -> {
|
||||
SwitchPreference(
|
||||
title = stringResource(pref.title),
|
||||
value = currentUser?.pin.isNotNullOrBlank(),
|
||||
onClick = {
|
||||
showPinFlow = true
|
||||
},
|
||||
summaryOn = stringResource(R.string.enabled),
|
||||
summaryOff = null,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val value = pref.getter.invoke(preferences)
|
||||
ComposablePreference(
|
||||
|
|
@ -408,6 +424,22 @@ fun PreferencesContent(
|
|||
}
|
||||
}
|
||||
}
|
||||
if (showPinFlow && currentUser != null) {
|
||||
currentUser?.let { user ->
|
||||
SetPinFlow(
|
||||
currentPin = user.pin,
|
||||
onAddPin = {
|
||||
viewModel.setPin(user, it)
|
||||
showPinFlow = false
|
||||
},
|
||||
onRemovePin = {
|
||||
viewModel.setPin(user, null)
|
||||
showPinFlow = false
|
||||
},
|
||||
onDismissRequest = { showPinFlow = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.github.damontecres.wholphin.data.NavDrawerItemRepository
|
|||
import com.github.damontecres.wholphin.data.ServerPreferencesDao
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.isPinned
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||
import com.github.damontecres.wholphin.data.model.NavDrawerPinnedItem
|
||||
import com.github.damontecres.wholphin.data.model.NavPinType
|
||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||
|
|
@ -47,6 +48,8 @@ class PreferencesViewModel
|
|||
private lateinit var allNavDrawerItems: List<NavDrawerItem>
|
||||
val navDrawerPins = MutableLiveData<Map<NavDrawerItem, Boolean>>(mapOf())
|
||||
|
||||
val currentUser get() = serverRepository.currentUser
|
||||
|
||||
init {
|
||||
viewModelScope.launchIO {
|
||||
serverRepository.currentUser.value?.let { user ->
|
||||
|
|
@ -100,6 +103,15 @@ class PreferencesViewModel
|
|||
}
|
||||
}
|
||||
|
||||
fun setPin(
|
||||
user: JellyfinUser,
|
||||
pin: String?,
|
||||
) {
|
||||
viewModelScope.launchIO(ExceptionHandler(autoToast = true)) {
|
||||
serverRepository.setUserPin(user, pin)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
suspend fun resetSubtitleSettings(appPreferences: DataStore<AppPreferences>) {
|
||||
appPreferences.updateData {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
package com.github.damontecres.wholphin.ui.preferences
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
||||
import com.github.damontecres.wholphin.ui.setup.PinEntryCreate
|
||||
|
||||
enum class PinFlowState {
|
||||
ENTER_PIN_TO_REMOVE,
|
||||
ENTER_PIN,
|
||||
CONFIRM_PIN,
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SetPinFlow(
|
||||
currentPin: String?,
|
||||
onAddPin: (String) -> Unit,
|
||||
onRemovePin: () -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var flowState by
|
||||
remember {
|
||||
mutableStateOf(if (currentPin.isNullOrBlank()) PinFlowState.ENTER_PIN else PinFlowState.ENTER_PIN_TO_REMOVE)
|
||||
}
|
||||
var pendingPin by remember { mutableStateOf("") }
|
||||
|
||||
AnimatedContent(flowState) { state ->
|
||||
BasicDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
when (state) {
|
||||
PinFlowState.ENTER_PIN_TO_REMOVE -> {
|
||||
PinEntryCreate(
|
||||
title = R.string.enter_pin,
|
||||
onTextChange = {
|
||||
if (it == currentPin) onRemovePin.invoke()
|
||||
},
|
||||
onConfirm = null,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
PinFlowState.ENTER_PIN -> {
|
||||
PinEntryCreate(
|
||||
title = R.string.enter_pin,
|
||||
onTextChange = {},
|
||||
onConfirm = {
|
||||
if (it.length >= 4) {
|
||||
pendingPin = it
|
||||
flowState = PinFlowState.CONFIRM_PIN
|
||||
} else {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.pin_too_short),
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
PinFlowState.CONFIRM_PIN -> {
|
||||
PinEntryCreate(
|
||||
title = R.string.confirm_pin,
|
||||
onTextChange = {},
|
||||
onConfirm = {
|
||||
if (it == pendingPin) {
|
||||
onAddPin.invoke(pendingPin)
|
||||
} else {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.incorrect),
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
flowState = PinFlowState.ENTER_PIN
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -30,8 +30,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.KeyEventType
|
||||
import androidx.compose.ui.input.key.key
|
||||
|
|
@ -58,8 +56,8 @@ import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
|||
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.dimAndBlur
|
||||
import com.github.damontecres.wholphin.ui.formatBytes
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
|
|
@ -195,14 +193,7 @@ fun InstallUpdatePage(
|
|||
onCancel = {
|
||||
viewModel.navigationManager.goBack()
|
||||
},
|
||||
modifier =
|
||||
modifier
|
||||
.ifElse(
|
||||
isDownloading,
|
||||
Modifier
|
||||
.blur(8.dp)
|
||||
.alpha(.33f),
|
||||
),
|
||||
modifier = modifier.dimAndBlur(isDownloading),
|
||||
)
|
||||
}
|
||||
if (isDownloading) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,227 @@
|
|||
package com.github.damontecres.wholphin.ui.setup
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.runtime.Composable
|
||||
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.draw.clip
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.KeyEventType
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.onKeyEvent
|
||||
import androidx.compose.ui.input.key.type
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.tv.material3.Button
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.ui.FontAwesome
|
||||
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
||||
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
||||
import com.github.damontecres.wholphin.ui.playback.isEnterKey
|
||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||
|
||||
@Composable
|
||||
fun PinEntry(
|
||||
onTextChange: (String) -> Unit,
|
||||
onClickServerAuth: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var input by remember { mutableStateOf("") }
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = modifier,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.enter_pin),
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
PinArrowRow(Modifier.align(Alignment.CenterHorizontally))
|
||||
PinEntryDots(input.length, Modifier.align(Alignment.CenterHorizontally))
|
||||
|
||||
Button(
|
||||
onClick = onClickServerAuth,
|
||||
modifier =
|
||||
Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.onKeyEvent {
|
||||
if (it.type == KeyEventType.KeyUp) {
|
||||
var str = input
|
||||
str +=
|
||||
when (it.key) {
|
||||
Key.DirectionUp -> "U"
|
||||
Key.DirectionRight -> "R"
|
||||
Key.DirectionDown -> "D"
|
||||
Key.DirectionLeft -> "L"
|
||||
else -> return@onKeyEvent false
|
||||
}
|
||||
onTextChange.invoke(str)
|
||||
input = str
|
||||
return@onKeyEvent true
|
||||
} else {
|
||||
return@onKeyEvent false
|
||||
}
|
||||
},
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.use_server_credentials),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.will_remove_pin),
|
||||
fontSize = 10.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PinEntryCreate(
|
||||
@StringRes title: Int,
|
||||
onTextChange: (String) -> Unit,
|
||||
onConfirm: ((String) -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var input by remember { mutableStateOf("") }
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier =
|
||||
modifier
|
||||
.onKeyEvent {
|
||||
if (isEnterKey(it)) {
|
||||
onConfirm?.invoke(input)
|
||||
return@onKeyEvent true
|
||||
}
|
||||
if (it.type == KeyEventType.KeyUp) {
|
||||
var str = input
|
||||
str +=
|
||||
when (it.key) {
|
||||
Key.DirectionUp -> "U"
|
||||
Key.DirectionRight -> "R"
|
||||
Key.DirectionDown -> "D"
|
||||
Key.DirectionLeft -> "L"
|
||||
else -> return@onKeyEvent false
|
||||
}
|
||||
onTextChange.invoke(str)
|
||||
input = str
|
||||
return@onKeyEvent true
|
||||
} else {
|
||||
return@onKeyEvent false
|
||||
}
|
||||
}.focusable(),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(title),
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
PinArrowRow(Modifier.align(Alignment.CenterHorizontally))
|
||||
PinEntryDots(input.length, Modifier.align(Alignment.CenterHorizontally))
|
||||
if (onConfirm != null) {
|
||||
Text(
|
||||
text = stringResource(R.string.press_enter_to_confirm),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PinArrowRow(modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = modifier,
|
||||
) {
|
||||
val arrows =
|
||||
listOf(R.string.fa_arrow_left_long, R.string.fa_arrow_up_long, R.string.fa_arrow_right_long, R.string.fa_arrow_down_long)
|
||||
arrows.forEach {
|
||||
Text(
|
||||
text = stringResource(it),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
fontFamily = FontAwesome,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PinEntryDots(
|
||||
count: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||
modifier =
|
||||
modifier
|
||||
.defaultMinSize(minWidth = 180.dp, minHeight = 40.dp)
|
||||
.background(
|
||||
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.8f),
|
||||
shape = CircleShape,
|
||||
).padding(vertical = 16.dp),
|
||||
) {
|
||||
repeat(count) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(horizontal = 8.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.onSurface.copy(alpha = 1f))
|
||||
.size(8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PinEntryDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onTextChange: (String) -> Unit,
|
||||
onClickServerAuth: () -> Unit,
|
||||
) {
|
||||
BasicDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
PinEntry(
|
||||
onTextChange = onTextChange,
|
||||
onClickServerAuth = onClickServerAuth,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewTvSpec
|
||||
@Composable
|
||||
private fun PinEntryPreview() {
|
||||
WholphinTheme {
|
||||
PinEntry(
|
||||
onTextChange = {},
|
||||
onClickServerAuth = {},
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@ import com.github.damontecres.wholphin.R
|
|||
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
||||
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
||||
import com.github.damontecres.wholphin.ui.components.EditTextBox
|
||||
import com.github.damontecres.wholphin.ui.dimAndBlur
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
|
|
@ -60,7 +61,7 @@ fun SwitchServerContent(
|
|||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier,
|
||||
modifier = modifier.dimAndBlur(showAddServer),
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
|
|
|
|||
|
|
@ -40,9 +40,11 @@ import androidx.tv.material3.Text
|
|||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinServer
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
||||
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
||||
import com.github.damontecres.wholphin.ui.components.EditTextBox
|
||||
import com.github.damontecres.wholphin.ui.dimAndBlur
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
|
|
@ -84,10 +86,11 @@ fun SwitchUserContent(
|
|||
}
|
||||
}
|
||||
}
|
||||
var switchUserWithPin by remember { mutableStateOf<JellyfinUser?>(null) }
|
||||
|
||||
currentServer?.let { server ->
|
||||
Box(
|
||||
modifier = modifier,
|
||||
modifier = modifier.dimAndBlur(showAddUser || switchUserWithPin != null),
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
|
|
@ -116,7 +119,11 @@ fun SwitchUserContent(
|
|||
users = users,
|
||||
currentUser = currentUser,
|
||||
onSwitchUser = { user ->
|
||||
viewModel.switchUser(user)
|
||||
if (user.pin.isNotNullOrBlank()) {
|
||||
switchUserWithPin = user
|
||||
} else {
|
||||
viewModel.switchUser(user)
|
||||
}
|
||||
},
|
||||
onAddUser = { showAddUser = true },
|
||||
onRemoveUser = { user ->
|
||||
|
|
@ -300,6 +307,18 @@ fun SwitchUserContent(
|
|||
}
|
||||
}
|
||||
}
|
||||
switchUserWithPin?.let { user ->
|
||||
PinEntryDialog(
|
||||
onDismissRequest = { switchUserWithPin = null },
|
||||
onClickServerAuth = {
|
||||
showAddUser = true
|
||||
switchUserWithPin = null
|
||||
},
|
||||
onTextChange = {
|
||||
if (it == user.pin) viewModel.switchUser(user)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue