954dad0e1f77

feat: Add Codex PDF sharing (v2.3.37)

- Add a native share-sheet action for generated Codex PDFs - Expose cached Codex PDFs through a scoped non-exported FileProvider - Update browser parity notes for PDF save/share completion - Verified with focused CodexGenerateScreenTest and full testDebugUnitTest/assembleDebug/lintDebug gate

time
2026-06-26 11:55 UTC · 4 months ago
machine
koh2
parent
dfcad5a107c1
5 files app/build.gradle+2 −2app/src/main/AndroidManifest.xml+10 −0app/src/main/java/com/peachpasswords/ui/codex/CodexGenerateScreen.kt+13 −0app/src/main/java/com/peachpasswords/ui/codex/CodexViewModel.kt+12 −0app/src/main/res/xml/file_paths.xml+6 −0
app/build.gradle+2 −2
@@ -16,8 +16,8 @@1616         applicationId "com.peachpasswords"1717         minSdk 261818         targetSdk 3519-        versionCode 23360020-        versionName "2.3.36"19+        versionCode 23370020+        versionName "2.3.37"2121 2222         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"2323         vectorDrawables {

view file

app/src/main/AndroidManifest.xml+10 −0
@@ -58,6 +58,16 @@5858             android:excludeFromRecents="true"5959             android:theme="@style/Theme.Peach.Transparent" />6060 61+        <provider62+            android:name="androidx.core.content.FileProvider"63+            android:authorities="${applicationId}.fileprovider"64+            android:exported="false"65+            android:grantUriPermissions="true">66+            <meta-data67+                android:name="android.support.FILE_PROVIDER_PATHS"68+                android:resource="@xml/file_paths" />69+        </provider>70+6171         <service6272             android:name=".autofill.PeachAutofillService"6373             android:permission="android.permission.BIND_AUTOFILL_SERVICE"

view file

app/src/main/java/com/peachpasswords/ui/codex/CodexGenerateScreen.kt+13 −0
@@ -1,5 +1,6 @@11 package com.peachpasswords.ui.codex22 3+import android.content.Intent34 import androidx.activity.compose.rememberLauncherForActivityResult45 import androidx.activity.result.contract.ActivityResultContracts56 import androidx.compose.foundation.background@@ -29,6 +30,7 @@2930 import androidx.compose.ui.draw.clip3031 import androidx.compose.ui.graphics.Color3132 import androidx.compose.ui.graphics.SolidColor33+import androidx.compose.ui.platform.LocalContext3234 import androidx.compose.ui.text.font.FontStyle3335 import androidx.compose.ui.unit.dp3436 import androidx.compose.ui.unit.sp@@ -51,6 +53,7 @@5153 ) {5254     val state by vm.generate.collectAsStateWithLifecycle()5355     val accent = LocalPeachAccent.current56+    val context = LocalContext.current5457     val saveLauncher = rememberLauncherForActivityResult(5558         ActivityResultContracts.CreateDocument("application/pdf"),5659     ) { uri -> uri?.let { vm.writePdf(it) } }@@ -134,6 +137,16 @@134137                     SummaryRow("pages", s.manifest.pageCount.toString(), accent)135138                     Spacer(Modifier.height(24.dp))136139                     CodexButton("Save PDF…", accent, filled = true) { saveLauncher.launch(codexPdfFileName()) }140+                    Spacer(Modifier.height(10.dp))141+                    CodexButton("Share PDF…", peachColors.T2, filled = false) {142+                        val uri = vm.sharePdfUri() ?: return@CodexButton143+                        val intent = Intent(Intent.ACTION_SEND).apply {144+                            type = "application/pdf"145+                            putExtra(Intent.EXTRA_STREAM, uri)146+                            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)147+                        }148+                        context.startActivity(Intent.createChooser(intent, "Share Codex PDF"))149+                    }137150                     Spacer(Modifier.height(10.dp))138151                     CodexButton("Generate again", peachColors.T2, filled = false) { vm.startGenerate(System.currentTimeMillis()) }139152                 }

view file

app/src/main/java/com/peachpasswords/ui/codex/CodexViewModel.kt+12 −0
@@ -2,6 +2,7 @@22 33 import android.content.Context44 import android.net.Uri5+import androidx.core.content.FileProvider56 import androidx.lifecycle.ViewModel67 import androidx.lifecycle.viewModelScope78 import androidx.camera.core.ImageProxy@@ -26,6 +27,7 @@2627 import kotlinx.coroutines.flow.asStateFlow2728 import kotlinx.coroutines.launch2829 import kotlinx.coroutines.withContext30+import java.io.File2931 import javax.inject.Inject3032 3133 sealed interface GenerateState {@@ -118,6 +120,16 @@118120         context.contentResolver.openOutputStream(uri)?.use { it.write(pdf) } != null119121     } catch (_: Exception) {120122         false123+    }124+125+    fun sharePdfUri(fileName: String = codexPdfFileName()): Uri? = try {126+        val pdf = (_generate.value as? GenerateState.Done)?.pdf ?: return null127+        val dir = File(context.cacheDir, "codex").apply { mkdirs() }128+        val file = File(dir, fileName)129+        file.outputStream().use { it.write(pdf) }130+        FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)131+    } catch (_: Exception) {132+        null121133     }122134 123135     fun scan(uri: Uri) {

view file

app/src/main/res/xml/file_paths.xml+6 −0
@@ -0,0 +1,6 @@1+<?xml version="1.0" encoding="utf-8"?>2+<paths xmlns:android="http://schemas.android.com/apk/res/android">3+    <cache-path4+        name="codex"5+        path="codex/" />6+</paths>

view file