My Resumes history, modern M3 redesign, authenticated exports #2
@ -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>
|
||||
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
6
app/src/main/res/xml/file_paths.xml
Normal file
6
app/src/main/res/xml/file_paths.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<cache-path
|
||||
name="exports"
|
||||
path="exports/" />
|
||||
</paths>
|
||||
Loading…
x
Reference in New Issue
Block a user