diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9d1ea27..8b60d13 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -34,6 +34,16 @@
android:name=".ui.AdminActivity"
android:exported="false" />
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/example/resbuilder/data/remote/ApiClient.kt b/app/src/main/java/com/example/resbuilder/data/remote/ApiClient.kt
index 889fc6e..fd4b162 100644
--- a/app/src/main/java/com/example/resbuilder/data/remote/ApiClient.kt
+++ b/app/src/main/java/com/example/resbuilder/data/remote/ApiClient.kt
@@ -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")
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/resbuilder/ui/ResultFragment.kt b/app/src/main/java/com/example/resbuilder/ui/ResultFragment.kt
index 5c39cec..595082d 100644
--- a/app/src/main/java/com/example/resbuilder/ui/ResultFragment.kt
+++ b/app/src/main/java/com/example/resbuilder/ui/ResultFragment.kt
@@ -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
}
diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 0000000..ccaa566
--- /dev/null
+++ b/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,6 @@
+
+
+
+