修正下一个口令时间窗口错误及高度挤压问题

This commit is contained in:
2026-06-17 00:17:06 +08:00
parent 2d45b717a4
commit fce997c3d6
2 changed files with 61 additions and 23 deletions
@@ -40,6 +40,8 @@ fun TokenDetailSheet(
var nextCode by remember { mutableStateOf("") } var nextCode by remember { mutableStateOf("") }
var progress by remember { mutableStateOf(1f) } var progress by remember { mutableStateOf(1f) }
var remainingSeconds by remember { mutableStateOf(0) } var remainingSeconds by remember { mutableStateOf(0) }
val showNextPreview = remainingSeconds in 0..5 &&
(token.tokenType == TokenType.TOTP || token.tokenType == TokenType.STEAM)
val secretBytes = remember(token.secret.value) { val secretBytes = remember(token.secret.value) {
try { try {
@@ -64,7 +66,7 @@ fun TokenDetailSheet(
secretBytes, time, token.period, token.digits, token.algorithm.ordinal secretBytes, time, token.period, token.digits, token.algorithm.ordinal
) )
nextCode = NativeLib.generateTOTP( nextCode = NativeLib.generateTOTP(
secretBytes, time + 1, token.period, token.digits, token.algorithm.ordinal secretBytes, time + token.period, token.period, token.digits, token.algorithm.ordinal
) )
val remaining = periodMs - (now % periodMs) val remaining = periodMs - (now % periodMs)
progress = remaining.toFloat() / periodMs.toFloat() progress = remaining.toFloat() / periodMs.toFloat()
@@ -73,7 +75,7 @@ fun TokenDetailSheet(
TokenType.STEAM -> { TokenType.STEAM -> {
val time = now / 1000L val time = now / 1000L
currentCode = NativeLib.generateSteam(secretBytes, time) currentCode = NativeLib.generateSteam(secretBytes, time)
nextCode = NativeLib.generateSteam(secretBytes, time + 1) nextCode = NativeLib.generateSteam(secretBytes, time + 30)
val periodMs = 30000L val periodMs = 30000L
val remaining = periodMs - (now % periodMs) val remaining = periodMs - (now % periodMs)
progress = remaining.toFloat() / periodMs.toFloat() progress = remaining.toFloat() / periodMs.toFloat()
@@ -133,12 +135,17 @@ fun TokenDetailSheet(
color = MaterialTheme.colorScheme.primary, color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
if (remainingSeconds <= 5 && (token.tokenType == TokenType.TOTP || token.tokenType == TokenType.STEAM)) { Box(
Text( modifier = Modifier.height(20.dp),
text = formatCode(nextCode), contentAlignment = Alignment.Center
style = MaterialTheme.typography.bodySmall, ) {
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f) if (showNextPreview) {
) Text(
text = formatCode(nextCode),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
)
}
} }
} }
@@ -150,7 +157,7 @@ fun TokenDetailSheet(
CircularProgressIndicator( CircularProgressIndicator(
progress = { progress }, progress = { progress },
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
color = if (remainingSeconds <= 5) warningColor else MaterialTheme.colorScheme.primary, color = if (showNextPreview) warningColor else MaterialTheme.colorScheme.primary,
trackColor = MaterialTheme.colorScheme.surfaceVariant, trackColor = MaterialTheme.colorScheme.surfaceVariant,
strokeWidth = 3.dp, strokeWidth = 3.dp,
) )
@@ -158,7 +165,7 @@ fun TokenDetailSheet(
text = "$remainingSeconds", text = "$remainingSeconds",
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = if (remainingSeconds <= 5) warningColor else MaterialTheme.colorScheme.onSurface color = if (showNextPreview) warningColor else MaterialTheme.colorScheme.onSurface
) )
} }
} }
@@ -25,7 +25,11 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@@ -48,6 +52,15 @@ fun TokensScreen(
val uiState by viewModel.uiState.collectAsStateWithLifecycle() val uiState by viewModel.uiState.collectAsStateWithLifecycle()
var showAddBottomSheet by remember { mutableStateOf(false) } var showAddBottomSheet by remember { mutableStateOf(false) }
var isSearchExpanded by remember { mutableStateOf(false) } var isSearchExpanded by remember { mutableStateOf(false) }
var searchFieldHasFocus by remember { mutableStateOf(false) }
val searchFocusRequester = remember { FocusRequester() }
val focusManager = LocalFocusManager.current
LaunchedEffect(isSearchExpanded) {
if (isSearchExpanded) {
searchFocusRequester.requestFocus()
}
}
Scaffold( Scaffold(
topBar = { topBar = {
@@ -62,7 +75,9 @@ fun TokensScreen(
} }
AnimatedVisibility( AnimatedVisibility(
visible = isSearchExpanded, visible = isSearchExpanded,
modifier = Modifier.weight(1f) modifier = Modifier
.weight(1f)
.padding(end = 16.dp)
) { ) {
Surface( Surface(
shape = MaterialTheme.shapes.large, shape = MaterialTheme.shapes.large,
@@ -72,12 +87,21 @@ fun TokensScreen(
TextField( TextField(
value = uiState.searchQuery, value = uiState.searchQuery,
onValueChange = { viewModel.updateSearchQuery(it) }, onValueChange = { viewModel.updateSearchQuery(it) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier
.fillMaxWidth()
.focusRequester(searchFocusRequester)
.onFocusChanged { focusState ->
if (searchFieldHasFocus && !focusState.isFocused) {
isSearchExpanded = false
}
searchFieldHasFocus = focusState.isFocused
},
placeholder = { Text(stringResource(R.string.search_tokens)) }, placeholder = { Text(stringResource(R.string.search_tokens)) },
leadingIcon = { Icon(Icons.Default.Search, contentDescription = stringResource(R.string.search)) }, leadingIcon = { Icon(Icons.Default.Search, contentDescription = stringResource(R.string.search)) },
trailingIcon = { trailingIcon = {
IconButton(onClick = { IconButton(onClick = {
viewModel.updateSearchQuery("") viewModel.updateSearchQuery("")
focusManager.clearFocus(force = true)
isSearchExpanded = false isSearchExpanded = false
}) { }) {
Icon(Icons.Default.Close, contentDescription = "Clear search") Icon(Icons.Default.Close, contentDescription = "Clear search")
@@ -211,6 +235,8 @@ fun TokenCard(
var remainingSeconds by remember { mutableStateOf(0) } var remainingSeconds by remember { mutableStateOf(0) }
var showDetailSheet by remember { mutableStateOf(false) } var showDetailSheet by remember { mutableStateOf(false) }
val context = LocalContext.current val context = LocalContext.current
val showNextPreview = remainingSeconds in 0..5 &&
(token.tokenType == TokenType.TOTP || token.tokenType == TokenType.STEAM)
val secretBytes = remember(token.secret.value) { val secretBytes = remember(token.secret.value) {
try { try {
@@ -240,7 +266,7 @@ fun TokenCard(
) )
nextCode = NativeLib.generateTOTP( nextCode = NativeLib.generateTOTP(
secretBytes, secretBytes,
time + 1, time + token.period,
token.period, token.period,
token.digits, token.digits,
token.algorithm.ordinal token.algorithm.ordinal
@@ -257,7 +283,7 @@ fun TokenCard(
) )
nextCode = NativeLib.generateSteam( nextCode = NativeLib.generateSteam(
secretBytes, secretBytes,
time + 1 time + 30
) )
val periodMs = 30000L val periodMs = 30000L
val remaining = periodMs - (now % periodMs) val remaining = periodMs - (now % periodMs)
@@ -318,12 +344,17 @@ fun TokenCard(
color = MaterialTheme.colorScheme.primary, color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
if (remainingSeconds <= 5 && (token.tokenType == TokenType.TOTP || token.tokenType == TokenType.STEAM)) { Box(
Text( modifier = Modifier.height(20.dp),
text = formatCode(nextCode), contentAlignment = Alignment.CenterStart
style = MaterialTheme.typography.bodySmall, ) {
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f) if (showNextPreview) {
) Text(
text = formatCode(nextCode),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
)
}
} }
} }
@@ -339,7 +370,7 @@ fun TokenCard(
CircularProgressIndicator( CircularProgressIndicator(
progress = { progress }, progress = { progress },
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
color = if (remainingSeconds <= 5) warningColor else MaterialTheme.colorScheme.primary, color = if (showNextPreview) warningColor else MaterialTheme.colorScheme.primary,
trackColor = MaterialTheme.colorScheme.surfaceVariant, trackColor = MaterialTheme.colorScheme.surfaceVariant,
strokeWidth = 3.dp, strokeWidth = 3.dp,
) )
@@ -347,7 +378,7 @@ fun TokenCard(
text = "$remainingSeconds", text = "$remainingSeconds",
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = if (remainingSeconds <= 5) warningColor else MaterialTheme.colorScheme.onSurface color = if (showNextPreview) warningColor else MaterialTheme.colorScheme.onSurface
) )
} }
} }