f6b8ff169519

fix: Route peach backup files into restore flow (v2.3.29)

- Detect .peach filenames and PeachScript content in the generic file picker - Prefill the PeachScript restore form for picked backup files - Keep wrong-password retries on the PeachScript restore surface Verified: ./gradlew compileDebugKotlin testDebugUnitTest --tests com.peachpasswords.ui.vault.ImportParsersTest Verified: ./gradlew testDebugUnitTest :app:assembleDebug lintDebug

time
2026-06-26 09:06 UTC · 4 months ago
machine
koh2
parent
eee8f3be34b5
3 files app/build.gradle+2 −2app/src/main/java/com/peachpasswords/ui/vault/ImportScreen.kt+22 −3app/src/test/java/com/peachpasswords/ui/vault/ImportParsersTest.kt+16 −0
app/build.gradle+2 −2
@@ -16,8 +16,8 @@1616         applicationId "com.peachpasswords"1717         minSdk 261818         targetSdk 3519-        versionCode 23280020-        versionName "2.3.28"19+        versionCode 23290020+        versionName "2.3.29"2121 2222         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"2323         vectorDrawables {

view file

app/src/main/java/com/peachpasswords/ui/vault/ImportScreen.kt+22 −3
@@ -28,6 +28,7 @@2828 import androidx.compose.material3.Icon2929 import androidx.compose.material3.Text3030 import androidx.compose.runtime.Composable31+import androidx.compose.runtime.LaunchedEffect3132 import androidx.compose.runtime.collectAsState3233 import androidx.compose.runtime.getValue3334 import androidx.compose.runtime.mutableStateOf@@ -84,6 +85,7 @@8485 sealed interface ImportUiState {8586     data object Idle : ImportUiState8687     data object Working : ImportUiState88+    data class PeachScriptFile(val text: String) : ImportUiState8789     data class Preview(val preview: ImportPreview) : ImportUiState8890     data class Done(val count: Int) : ImportUiState8991     data class Failed(val message: String) : ImportUiState@@ -98,6 +100,11 @@98100     const val UNRECOGNISED_FILE = "Unrecognised file format."99101 }100102 103+internal fun shouldRouteFileToPeachScript(path: String?, content: String): Boolean {104+    val path = path?.lowercase().orEmpty()105+    return path.endsWith(".peach") || PeachScriptImport.looksLikePeachScript(content.trim())106+}107+101108 @HiltViewModel102109 class ImportViewModel @Inject constructor(103110     private val vault: VaultRepository,@@ -119,7 +126,11 @@119126     fun back(onBack: () -> Unit) {120127         // Preview/error back returns to the input view; otherwise leave the screen.121128         when (_state.value) {122-            is ImportUiState.Preview, is ImportUiState.Failed, is ImportUiState.Done -> reset()129+            is ImportUiState.Preview,130+            is ImportUiState.Failed,131+            is ImportUiState.Done,132+            is ImportUiState.PeachScriptFile,133+            -> reset()123134             else -> onBack()124135         }125136     }@@ -185,6 +196,10 @@185196                 _state.value = ImportUiState.Failed(ImportErrors.UNREADABLE_FILE)186197                 return@launch187198             }199+            if (shouldRouteFileToPeachScript(uri.lastPathSegment, content)) {200+                _state.value = ImportUiState.PeachScriptFile(content)201+                return@launch202+            }188203             val result = withContext(Dispatchers.Default) { CsvImport.detectAndParse(content) }189204             when (result) {190205                 is ImportParseResult.Success -> {@@ -331,6 +346,10 @@331346                         ImportRoute.PEACHSCRIPT -> PeachScriptView(vm)332347                         ImportRoute.FILE -> FileView(vm)333348                     }349+                    is ImportUiState.PeachScriptFile -> {350+                        LaunchedEffect(s.text) { route = ImportRoute.PEACHSCRIPT }351+                        PeachScriptView(vm, initialPaste = s.text)352+                    }334353                 }335354             }336355         }@@ -378,8 +397,8 @@378397 // ---- PeachScript sub-view -------------------------------------------------379398 380399 @Composable381-private fun PeachScriptView(vm: ImportViewModel) {382-    var paste by remember { mutableStateOf("") }400+private fun PeachScriptView(vm: ImportViewModel, initialPaste: String = "") {401+    var paste by remember(initialPaste) { mutableStateOf(initialPaste) }383402     var backupPassword by remember { mutableStateOf("") }384403     val needsPassword = remember(paste) { vm.peachScriptNeedsPassword(paste) }385404 

view file

app/src/test/java/com/peachpasswords/ui/vault/ImportParsersTest.kt+16 −0
@@ -63,4 +63,20 @@6363         // The old JSON marker is not PeachScript anymore (PUA-encoded binary only).6464         assertFalse(PeachScriptImport.looksLikePeachScript("""{"peachscript":1,"encrypted":true}"""))6565     }66+67+    @Test fun file_picker_routes_peachscript_files_to_peachscript_restore() {68+        val out = PeachScriptExport.buildEncrypted(69+            listOf(70+                VaultEntry(71+                    id = "11111111-2222-3333-4444-555555555555", type = "login",72+                    title = "GH", username = "u", password = "p", createdAt = 0, modifiedAt = 0,73+                ),74+            ),75+            "backup-pass-123",76+        )77+78+        assertTrue(shouldRouteFileToPeachScript("backup.txt", out))79+        assertTrue(shouldRouteFileToPeachScript("backup.peach", "not yet decoded"))80+        assertFalse(shouldRouteFileToPeachScript("export.csv", "name,url,username,password\nA,https://a.com,u,p\n"))81+    }6682 }

view file