689a906288b0
compose import screen's 'choose an import source' prompt into the remaining viewport height instead of trailing into dead space below the two option cards; scoped narrowly to the non-scrolling default landing state so the scrollable preview/error states are untouched
- time
- 2026-07-24 03:01 UTC · 3 months ago
- machine
- koh2
- agent
- claude-sonnet-5
- session
- gate1-gate2-verification-pass
- parent
- 0cf07b8d1c5c
↓ peach-android-689a9062.tar.gzatlas at this save
files changed1 file
app/src/main/java/com/peachpasswords/ui/vault/ImportScreen.kt+74 −45app/src/main/java/com/peachpasswords/ui/vault/ImportScreen.kt+74 −45
@@ -333,39 +333,48 @@333333 334334 Spacer(Modifier.size(PeachSpacing.lg))335335 336- Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState())) {337- when (val s = state) {338- is ImportUiState.Preview -> PreviewView(339- preview = s.preview,340- onConfirm = { mode -> vm.confirmImport(s.preview, mode) },341- onCancel = { vm.reset() },342- )343- is ImportUiState.Done -> DoneView(count = s.count, onDone = onBack)344- is ImportUiState.Failed -> {345- ErrorBanner(s.message)346- Spacer(Modifier.size(16.dp))347- when (route) {336+ if (state == ImportUiState.Idle && route == ImportRoute.CHOOSE) {337+ // The default landing state (two option cards + a prompt) never338+ // needs to scroll — give it the real remaining height so the339+ // prompt composes into it instead of trailing into dead space.340+ ChooseView(341+ onPeachScript = { route = ImportRoute.PEACHSCRIPT },342+ onFile = { route = ImportRoute.FILE },343+ modifier = Modifier.weight(1f).fillMaxWidth(),344+ composeRemainingSpace = true,345+ )346+ } else {347+ Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState())) {348+ when (val s = state) {349+ is ImportUiState.Preview -> PreviewView(350+ preview = s.preview,351+ onConfirm = { mode -> vm.confirmImport(s.preview, mode) },352+ onCancel = { vm.reset() },353+ )354+ is ImportUiState.Done -> DoneView(count = s.count, onDone = onBack)355+ is ImportUiState.Failed -> {356+ ErrorBanner(s.message)357+ Spacer(Modifier.size(16.dp))358+ when (route) {359+ ImportRoute.PEACHSCRIPT -> PeachScriptView(vm)360+ ImportRoute.FILE -> FileView(vm)361+ ImportRoute.CHOOSE -> ChooseView(362+ onPeachScript = { route = ImportRoute.PEACHSCRIPT },363+ onFile = { route = ImportRoute.FILE },364+ )365+ }366+ }367+ ImportUiState.Working -> WorkingView()368+ ImportUiState.Idle -> when (route) {369+ ImportRoute.CHOOSE -> Unit // handled above, outside the scroll container348370 ImportRoute.PEACHSCRIPT -> PeachScriptView(vm)349371 ImportRoute.FILE -> FileView(vm)350- ImportRoute.CHOOSE -> ChooseView(351- onPeachScript = { route = ImportRoute.PEACHSCRIPT },352- onFile = { route = ImportRoute.FILE },353- )354372 }373+ is ImportUiState.PeachScriptFile -> {374+ LaunchedEffect(s.text) { route = ImportRoute.PEACHSCRIPT }375+ PeachScriptView(vm, initialPaste = s.text)376+ }355377 }356- ImportUiState.Working -> WorkingView()357- ImportUiState.Idle -> when (route) {358- ImportRoute.CHOOSE -> ChooseView(359- onPeachScript = { route = ImportRoute.PEACHSCRIPT },360- onFile = { route = ImportRoute.FILE },361- )362- ImportRoute.PEACHSCRIPT -> PeachScriptView(vm)363- ImportRoute.FILE -> FileView(vm)364- }365- is ImportUiState.PeachScriptFile -> {366- LaunchedEffect(s.text) { route = ImportRoute.PEACHSCRIPT }367- PeachScriptView(vm, initialPaste = s.text)368- }369378 }370379 }371380 }@@ -375,22 +384,42 @@375384 // ---- Choose view ----------------------------------------------------------376385 377386 @Composable378-private fun ChooseView(onPeachScript: () -> Unit, onFile: () -> Unit) {379- OptionCard(380- title = "From PeachScript",381- subtitle = "Paste an encrypted Peach export",382- onClick = onPeachScript,383- )384- Spacer(Modifier.size(12.dp))385- OptionCard(386- title = "From file",387- subtitle = "Bitwarden, Proton Pass, 1Password, Chrome, Safari, Edge CSV",388- onClick = onFile,389- )390- PeachEmptyOrEndState(391- message = "Choose an import source.",392- detail = "Peach will preview everything before it changes your vault.",393- )387+private fun ChooseView(388+ onPeachScript: () -> Unit,389+ onFile: () -> Unit,390+ modifier: Modifier = Modifier,391+ // Only true from the non-scrolling call site, where `modifier` carries a392+ // Column-scope weight and the remaining height is real and boundable.393+ // The scrolling call site (inside a verticalScroll Column, unbounded394+ // height) must keep the plain flowing layout or `weight()` would crash.395+ composeRemainingSpace: Boolean = false,396+) {397+ Column(modifier) {398+ OptionCard(399+ title = "From PeachScript",400+ subtitle = "Paste an encrypted Peach export",401+ onClick = onPeachScript,402+ )403+ Spacer(Modifier.size(12.dp))404+ OptionCard(405+ title = "From file",406+ subtitle = "Bitwarden, Proton Pass, 1Password, Chrome, Safari, Edge CSV",407+ onClick = onFile,408+ )409+ if (composeRemainingSpace) {410+ Box(Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) {411+ PeachEmptyOrEndState(412+ message = "Choose an import source.",413+ detail = "Peach will preview everything before it changes your vault.",414+ )415+ }416+ } else {417+ PeachEmptyOrEndState(418+ message = "Choose an import source.",419+ detail = "Peach will preview everything before it changes your vault.",420+ )421+ }422+ }394423 }395424 396425 @Composable