My Resumes history, modern M3 redesign, authenticated exports #2

Merged
armistace merged 10 commits from feature/my-resumes-modern into master 2026-08-04 23:58:13 +10:00
4 changed files with 82 additions and 7 deletions
Showing only changes of commit e90af27e66 - Show all commits

View File

@ -34,6 +34,16 @@
android:name=".ui.AdminActivity" android:name=".ui.AdminActivity"
android:exported="false" /> android:exported="false" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application> </application>
</manifest> </manifest>

View File

@ -313,4 +313,21 @@ object ApiClient {
fun getExportPdfUrl(jobId: String): String = "$API_BASE/export/$jobId/pdf" fun getExportPdfUrl(jobId: String): String = "$API_BASE/export/$jobId/pdf"
fun getExportDocxUrl(jobId: String): String = "$API_BASE/export/$jobId/docx" fun getExportDocxUrl(jobId: String): String = "$API_BASE/export/$jobId/docx"
@Throws(IOException::class)
fun downloadExport(url: String): ByteArray {
val req = Request.Builder()
.url(url)
.build()
client.newCall(req).execute().use { res ->
if (!res.isSuccessful) {
val errBody = res.body?.string()
val detail = try {
gson.fromJson(errBody, Map::class.java)["detail"] as? String
} catch (_: Exception) { null }
throw IOException(detail ?: "Export failed")
}
return res.body?.bytes() ?: throw IOException("Empty response")
}
}
} }

View File

@ -13,11 +13,14 @@ import android.webkit.WebView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.example.resbuilder.R import com.example.resbuilder.R
import com.example.resbuilder.data.model.Job import com.example.resbuilder.data.model.Job
import com.example.resbuilder.data.remote.ApiClient import com.example.resbuilder.data.remote.ApiClient
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import java.io.File
import java.io.FileOutputStream
class ResultFragment : Fragment() { class ResultFragment : Fragment() {
@ -86,14 +89,58 @@ class ResultFragment : Fragment() {
newButton.setOnClickListener { listener?.onNewJob() } newButton.setOnClickListener { listener?.onNewJob() }
jobId?.let { id -> jobId?.let { id ->
pdfButton.setOnClickListener { openUrl(ApiClient.getExportPdfUrl(id)) } pdfButton.setOnClickListener { downloadAndOpen(id, "pdf") }
docxButton.setOnClickListener { openUrl(ApiClient.getExportDocxUrl(id)) } docxButton.setOnClickListener { downloadAndOpen(id, "docx") }
} ?: run { } ?: run {
pdfButton.isEnabled = false pdfButton.isEnabled = false
docxButton.isEnabled = false docxButton.isEnabled = false
} }
} }
private fun downloadAndOpen(jobId: String, format: String) {
val url = if (format == "pdf") ApiClient.getExportPdfUrl(jobId) else ApiClient.getExportDocxUrl(jobId)
val dir = File(requireContext().cacheDir, "exports")
if (!dir.exists()) dir.mkdirs()
val safeId = jobId.replace("/", "_").replace("..", "_").take(8)
val file = File(dir, "$safeId.$format")
pdfButton.isEnabled = false
docxButton.isEnabled = false
Toast.makeText(context, R.string.exporting, Toast.LENGTH_SHORT).show()
Thread {
try {
val bytes = ApiClient.downloadExport(url)
FileOutputStream(file).use { it.write(bytes) }
requireActivity().runOnUiThread {
pdfButton.isEnabled = true
docxButton.isEnabled = true
openFile(file)
}
} catch (e: Exception) {
requireActivity().runOnUiThread {
pdfButton.isEnabled = true
docxButton.isEnabled = true
Toast.makeText(context, e.message ?: getString(R.string.export_failed), Toast.LENGTH_LONG).show()
}
}
}.start()
}
private fun openFile(file: File) {
val uri = FileProvider.getUriForFile(requireContext(), "${requireContext().packageName}.fileprovider", file)
val mime = if (file.extension == "pdf") "application/pdf" else "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, mime)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
try {
startActivity(intent)
} catch (e: Exception) {
Toast.makeText(context, R.string.export_no_viewer, Toast.LENGTH_LONG).show()
}
}
private fun copyText() { private fun copyText() {
val raw = rawContent val raw = rawContent
if (raw.isNullOrEmpty()) { if (raw.isNullOrEmpty()) {
@ -105,11 +152,6 @@ class ResultFragment : Fragment() {
Toast.makeText(context, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
} }
private fun openUrl(url: String) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intent)
}
fun setOnNewListener(l: OnNewListener) { fun setOnNewListener(l: OnNewListener) {
listener = l listener = l
} }

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path
name="exports"
path="exports/" />
</paths>