feat: implementar descarga de PDF de factura y soporte para guardar en Android

This commit is contained in:
2026-08-14 19:51:59 -03:00
parent b7bbef8035
commit 9e090c465a
2 changed files with 68 additions and 12 deletions
@@ -1,11 +1,17 @@
package com.hdrdevs.turnosxpress;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.os.Bundle;
import android.os.Build;
import android.os.Message;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.util.Log;
import android.view.KeyEvent;
@@ -33,6 +39,7 @@ import android.content.ContentResolver;
public class MainActivity extends AppCompatActivity {
private static final int LOGIN_REQUEST_CODE = 1;
private static final int STORAGE_PERMISSION_REQUEST_CODE = 2;
private WebView webView;
private ValueCallback<Uri[]> fileChooserCallback;
@@ -43,6 +50,7 @@ public class MainActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
tokenResult = "";
requestLegacyStoragePermissionIfNeeded();
// For Android Emulator, use 10.0.2.2 to access localhost on your development machine.
// If testing on a physical device, replace 10.0.2.2 with your development machine's actual local IP address.
@@ -220,6 +228,28 @@ public class MainActivity extends AppCompatActivity {
}
}
private void requestLegacyStoragePermissionIfNeeded() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P
&& ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_PERMISSION_REQUEST_CODE);
}
}
private String safePdfFilename(String filename) {
String safeName = filename == null ? "reporte.pdf" : filename.trim();
safeName = safeName.replaceAll("[\\\\/:*?\"<>|]", "_");
if (safeName.length() == 0) {
safeName = "reporte.pdf";
}
if (!safeName.toLowerCase().endsWith(".pdf")) {
safeName += ".pdf";
}
return safeName;
}
public class WebAppInterface {
@JavascriptInterface
public void startGoogleLogin() {
@@ -235,16 +265,20 @@ public class MainActivity extends AppCompatActivity {
@JavascriptInterface
public void savePdf(String base64Data, String filename) {
String safeFilename = safePdfFilename(filename);
try {
byte[] pdfAsBytes = Base64.decode(base64Data, 0);
String normalizedBase64 = base64Data == null ? "" : base64Data.replaceFirst("^data:application/pdf;base64,", "");
byte[] pdfAsBytes = Base64.decode(normalizedBase64, Base64.DEFAULT);
boolean isSaved = false;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContentResolver resolver = getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, filename);
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, safeFilename);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "application/pdf");
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
contentValues.put(MediaStore.MediaColumns.IS_PENDING, 1);
Uri uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues);
@@ -253,18 +287,31 @@ public class MainActivity extends AppCompatActivity {
outputStream.write(pdfAsBytes);
isSaved = true;
} catch (IOException e) {
e.printStackTrace();
Log.e("WEBVIEW", "Error writing PDF to MediaStore: " + safeFilename, e);
resolver.delete(uri, null, null);
}
if (isSaved) {
ContentValues completedValues = new ContentValues();
completedValues.put(MediaStore.MediaColumns.IS_PENDING, 0);
resolver.update(uri, completedValues, null, null);
}
}
} else {
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
File file = new File(path, filename);
if (!path.exists() && !path.mkdirs()) {
Log.e("WEBVIEW", "Could not create Downloads directory: " + path.getAbsolutePath());
} else {
File file = new File(path, safeFilename);
try (FileOutputStream os = new FileOutputStream(file)) {
os.write(pdfAsBytes);
isSaved = true;
MediaScannerConnection.scanFile(MainActivity.this, new String[]{file.getAbsolutePath()}, new String[]{"application/pdf"}, null);
} catch (IOException e) {
e.printStackTrace();
Log.e("WEBVIEW", "Error writing PDF to Downloads: " + safeFilename, e);
}
}
}
@@ -275,7 +322,7 @@ public class MainActivity extends AppCompatActivity {
}
} catch (Exception e) {
e.printStackTrace();
Log.e("WEBVIEW", "Error saving PDF: " + safeFilename, e);
runOnUiThread(() -> Toast.makeText(MainActivity.this, "Error: " + e.getMessage(), Toast.LENGTH_LONG).show());
}
}
@@ -844,7 +844,16 @@ export default function CashFlowMovementsList() {
},
};
(pdfMake as any).createPdf({ ...documentDefinition, content }).download(`factura-c-${invoice.pointOfSale || fiscalProfile.pointOfSale || 0}-${invoice.voucherNumber || 0}.pdf`);
const filename = `factura-c-${invoice.pointOfSale || fiscalProfile.pointOfSale || 0}-${invoice.voucherNumber || 0}.pdf`;
const pdf = (pdfMake as any).createPdf({ ...documentDefinition, content });
if ((window as unknown as { Android: any }).Android && (window as unknown as { Android: any }).Android.savePdf) {
pdf.getBase64((data: string) => {
(window as unknown as { Android: any }).Android?.savePdf(data, filename);
});
} else {
pdf.download(filename);
}
} catch (error) {
console.error("Error generating invoice PDF:", error);
alert.showError("No pudimos generar el PDF de la factura.");