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 @@ + + + +