a781101cdf46
fix vault list accent scatter (new-entry link now neutral, not competing with SMART/ALL pills) and compose end-of-list footer to center in remaining viewport height instead of trailing into dead space
- time
- 2026-07-24 02:16 UTC · 3 months ago
- machine
- koh2
- agent
- claude-sonnet-5
- session
- gate1-gate2-verification-pass
- parent
- a9fab5504cb3
↓ peach-android-a781101c.tar.gzatlas at this save
files changed1 file
app/src/main/java/com/peachpasswords/ui/vault/VaultListScreen.kt+59 −37app/src/main/java/com/peachpasswords/ui/vault/VaultListScreen.kt+59 −37
@@ -17,12 +17,14 @@1717 import androidx.compose.foundation.interaction.collectIsPressedAsState1818 import androidx.compose.foundation.layout.Arrangement1919 import androidx.compose.foundation.layout.Box20+import androidx.compose.foundation.layout.BoxWithConstraints2021 import androidx.compose.foundation.layout.Column2122 import androidx.compose.foundation.layout.Row2223 import androidx.compose.foundation.layout.Spacer2324 import androidx.compose.foundation.layout.fillMaxSize2425 import androidx.compose.foundation.layout.fillMaxWidth2526 import androidx.compose.foundation.layout.height27+import androidx.compose.foundation.layout.heightIn2628 import androidx.compose.foundation.layout.padding2729 import androidx.compose.foundation.layout.size2830 import androidx.compose.foundation.layout.statusBarsPadding@@ -196,42 +198,56 @@196198 onHealth = { onNavigate(PeachNav.Health) },197199 onTrash = { onNavigate(PeachNav.Trash) },198200 )199- PeachPullToRefresh(200- refreshing = syncStatus is SyncStatus.Syncing,201- onRefresh = { syncVm.syncNow() },202- modifier = Modifier.fillMaxWidth().weight(1f),203- ) {204- LazyColumn(Modifier.fillMaxSize()) {205- if (entries.isEmpty()) {206- item { EmptyState(onSeed = vm::seedDemo) }207- }208- items(entries, key = { it.id }) { entry ->209- EntryRow(210- entry = entry,211- flashing = flashId == entry.id,212- canSwipeCopy = vm.canSwipeCopy(entry) && !isMultiSelect,213- multiSelect = isMultiSelect,214- selected = entry.id in selectedIds,215- onTap = {216- if (isMultiSelect) vm.toggleSelected(entry.id)217- else onNavigate(entryTapDestination(entry))218- },219- onLongPress = {220- if (isMultiSelect) vm.toggleSelected(entry.id)221- else menuEntry = entry222- },223- onSwipeCopy = { vm.copyPassword(entry) },224- )225- }226- item {227- NewEntryRow(onClick = { onNavigate(PeachNav.EntryEdit(null)) })228- if (entries.isNotEmpty()) {229- PeachEmptyOrEndState(230- message = "all caught up",231- modifier = Modifier.padding(top = PeachSpacing.sm),201+ BoxWithConstraints(Modifier.fillMaxWidth().weight(1f)) {202+ // Short lists (a handful of entries) would otherwise leave the203+ // "all caught up" footer stranded near the top with dead space204+ // trailing beneath it — center the footer in whatever viewport205+ // height the entries don't already occupy (principle: composed206+ // space, never leftover). Long lists get a remainder of ~0 and207+ // are unaffected.208+ val footerMinHeight = (maxHeight - ENTRY_ROW_HEIGHT * entries.size).coerceAtLeast(0.dp)209+ PeachPullToRefresh(210+ refreshing = syncStatus is SyncStatus.Syncing,211+ onRefresh = { syncVm.syncNow() },212+ modifier = Modifier.fillMaxSize(),213+ ) {214+ LazyColumn(Modifier.fillMaxSize()) {215+ if (entries.isEmpty()) {216+ item { EmptyState(onSeed = vm::seedDemo) }217+ }218+ items(entries, key = { it.id }) { entry ->219+ EntryRow(220+ entry = entry,221+ flashing = flashId == entry.id,222+ canSwipeCopy = vm.canSwipeCopy(entry) && !isMultiSelect,223+ multiSelect = isMultiSelect,224+ selected = entry.id in selectedIds,225+ onTap = {226+ if (isMultiSelect) vm.toggleSelected(entry.id)227+ else onNavigate(entryTapDestination(entry))228+ },229+ onLongPress = {230+ if (isMultiSelect) vm.toggleSelected(entry.id)231+ else menuEntry = entry232+ },233+ onSwipeCopy = { vm.copyPassword(entry) },232234 )233235 }234- Spacer(Modifier.navigationBarsPadding())236+ item {237+ Column(238+ modifier = Modifier.fillMaxWidth().heightIn(min = footerMinHeight),239+ verticalArrangement = Arrangement.Center,240+ ) {241+ NewEntryRow(onClick = { onNavigate(PeachNav.EntryEdit(null)) })242+ if (entries.isNotEmpty()) {243+ PeachEmptyOrEndState(244+ message = "all caught up",245+ modifier = Modifier.padding(top = PeachSpacing.sm),246+ )247+ }248+ }249+ Spacer(Modifier.navigationBarsPadding())250+ }235251 }236252 }237253 }@@ -664,9 +680,15 @@664680 }665681 }666682 683+/** Approximate on-screen height of one [EntryRow], used to size the trailing footer. */684+private val ENTRY_ROW_HEIGHT = 76.dp685+667686 @Composable668687 private fun NewEntryRow(onClick: () -> Unit) {669- val accent = LocalPeachAccent.current688+ // Neutral, not accent: the persistent "+" in PeachTopBar already owns the689+ // add-entry accent moment. A second accent-colored add affordance here690+ // would compete with the SMART/ALL selection pills for the screen's one691+ // hero accent use.670692 Row(671693 modifier = Modifier672694 .fillMaxWidth()@@ -675,8 +697,8 @@675697 verticalAlignment = Alignment.CenterVertically,676698 horizontalArrangement = Arrangement.spacedBy(10.dp),677699 ) {678- Icon(Icons.Rounded.Add, contentDescription = null, tint = accent, modifier = Modifier.size(16.dp))679- Text("new entry", color = accent, style = PeachType.labelMono)700+ Icon(Icons.Rounded.Add, contentDescription = null, tint = peachColors.T1, modifier = Modifier.size(16.dp))701+ Text("new entry", color = peachColors.T1, style = PeachType.labelMono)680702 }681703 }682704