Vault screen: remove top bar, inline History/Settings buttons, fix keyboard gap
This commit is contained in:
parent
2cc18c7011
commit
bd3cbbb537
@ -1,7 +1,10 @@
|
||||
package cz.bugsy.passwordzebra.ui.navigation
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.ime
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Lock
|
||||
import androidx.compose.material3.Icon
|
||||
@ -13,6 +16,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavDestination.Companion.hierarchy
|
||||
@ -92,7 +96,10 @@ fun AppNavigation() {
|
||||
}
|
||||
},
|
||||
) { innerPadding ->
|
||||
Box(modifier = Modifier.padding(bottom = innerPadding.calculateBottomPadding())) {
|
||||
val density = LocalDensity.current
|
||||
val imeVisible = WindowInsets.ime.getBottom(density) > 0
|
||||
val bottomPadding = if (imeVisible) 0.dp else innerPadding.calculateBottomPadding()
|
||||
Box(modifier = Modifier.padding(bottom = bottomPadding)) {
|
||||
NavHost(navController = navController, startDestination = ROUTE_WORD_GENERATOR) {
|
||||
composable(ROUTE_WORD_GENERATOR) {
|
||||
WordGeneratorScreen()
|
||||
|
||||
@ -10,17 +10,21 @@ 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.heightIn
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ContentCopy
|
||||
import androidx.compose.material.icons.filled.History
|
||||
import androidx.compose.material.icons.filled.Password
|
||||
import androidx.compose.material.icons.filled.LockReset
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
@ -32,14 +36,13 @@ import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.FilledIconButton
|
||||
import androidx.compose.material3.FilledTonalIconButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
@ -48,10 +51,14 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
@ -75,6 +82,8 @@ fun DeterministicScreen(
|
||||
}
|
||||
|
||||
var showRotateConfirmDialog by remember { mutableStateOf(false) }
|
||||
val masterPasswordFocus = remember { FocusRequester() }
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
|
||||
if (state.showRotateWarning) {
|
||||
AlertDialog(
|
||||
@ -146,44 +155,39 @@ fun DeterministicScreen(
|
||||
)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.det_screen_title)) },
|
||||
actions = {
|
||||
IconButton(onClick = onNavigateToHistory) {
|
||||
Icon(Icons.Default.History, contentDescription = stringResource(R.string.det_history))
|
||||
}
|
||||
IconButton(onClick = onNavigateToSettings) {
|
||||
Icon(Icons.Default.Settings, contentDescription = stringResource(R.string.det_settings))
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.statusBarsPadding()
|
||||
.imePadding(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = 88.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
// Generated password box — shown at top once available
|
||||
if (state.generatedPassword.isNotEmpty()) {
|
||||
// Generated password box — always visible, even when empty
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 80.dp)
|
||||
.border(
|
||||
width = 2.dp,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
)
|
||||
.clickable { viewModel.copyAndScheduleClear(context) }
|
||||
.clickable(enabled = state.generatedPassword.isNotEmpty()) {
|
||||
viewModel.copyAndScheduleClear(context)
|
||||
}
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
if (state.generatedPassword.isNotEmpty()) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
text = state.generatedPassword,
|
||||
@ -214,8 +218,10 @@ fun DeterministicScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inline rotate button — right-aligned, error color, confirmation dialog
|
||||
// Inline rotate button — only shown when password is generated
|
||||
if (state.generatedPassword.isNotEmpty()) {
|
||||
TextButton(
|
||||
onClick = { showRotateConfirmDialog = true },
|
||||
modifier = Modifier.align(Alignment.End),
|
||||
@ -247,6 +253,13 @@ fun DeterministicScreen(
|
||||
.fillMaxWidth()
|
||||
.menuAnchor(),
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||
keyboardActions = KeyboardActions(
|
||||
onNext = {
|
||||
expanded = false
|
||||
masterPasswordFocus.requestFocus()
|
||||
},
|
||||
),
|
||||
)
|
||||
ExposedDropdownMenu(
|
||||
expanded = expanded && filteredHistory.isNotEmpty(),
|
||||
@ -268,11 +281,22 @@ fun DeterministicScreen(
|
||||
value = String(state.masterPassword),
|
||||
onValueChange = { viewModel.updateMasterPassword(it.toCharArray()) },
|
||||
label = { Text(stringResource(R.string.det_master_password)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(masterPasswordFocus),
|
||||
singleLine = true,
|
||||
visualTransformation = if (state.masterPasswordVisible) VisualTransformation.None
|
||||
else PasswordVisualTransformation(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Done,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = {
|
||||
keyboardController?.hide()
|
||||
viewModel.generate()
|
||||
},
|
||||
),
|
||||
trailingIcon = {
|
||||
IconButton(onClick = { viewModel.toggleMasterPasswordVisible() }) {
|
||||
Icon(
|
||||
@ -284,21 +308,48 @@ fun DeterministicScreen(
|
||||
},
|
||||
)
|
||||
|
||||
// Square icon generate button
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
} // end Column
|
||||
|
||||
// Button row: History | Generate | Settings — floats just above keyboard
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(horizontal = 16.dp, vertical = 14.dp),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
FilledTonalIconButton(
|
||||
onClick = onNavigateToHistory,
|
||||
modifier = Modifier.size(48.dp),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
) {
|
||||
Icon(Icons.Default.History, contentDescription = stringResource(R.string.det_history))
|
||||
}
|
||||
|
||||
Spacer(Modifier.width(20.dp))
|
||||
|
||||
FilledIconButton(
|
||||
onClick = { viewModel.generate() },
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.size(60.dp),
|
||||
modifier = Modifier.size(60.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Password,
|
||||
imageVector = Icons.Filled.LockReset,
|
||||
contentDescription = stringResource(R.string.det_generate),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(Modifier.width(20.dp))
|
||||
|
||||
FilledTonalIconButton(
|
||||
onClick = onNavigateToSettings,
|
||||
modifier = Modifier.size(48.dp),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
) {
|
||||
Icon(Icons.Default.Settings, contentDescription = stringResource(R.string.det_settings))
|
||||
}
|
||||
}
|
||||
} // end outer Box
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user