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: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>
</manifest>

View File

@ -313,4 +313,21 @@ object ApiClient {
fun getExportPdfUrl(jobId: String): String = "$API_BASE/export/$jobId/pdf"
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.Toast
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
import com.example.resbuilder.R
import com.example.resbuilder.data.model.Job
import com.example.resbuilder.data.remote.ApiClient
import com.google.android.material.button.MaterialButton
import java.io.File
import java.io.FileOutputStream
class ResultFragment : Fragment() {
@ -86,14 +89,58 @@ class ResultFragment : Fragment() {
newButton.setOnClickListener { listener?.onNewJob() }
jobId?.let { id ->
pdfButton.setOnClickListener { openUrl(ApiClient.getExportPdfUrl(id)) }
docxButton.setOnClickListener { openUrl(ApiClient.getExportDocxUrl(id)) }
pdfButton.setOnClickListener { downloadAndOpen(id, "pdf") }
docxButton.setOnClickListener { downloadAndOpen(id, "docx") }
} ?: run {
pdfButton.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() {
val raw = rawContent
if (raw.isNullOrEmpty()) {
@ -105,11 +152,6 @@ class ResultFragment : Fragment() {
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) {
listener = l
}

View File

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