diff --git a/mobile/android/app/build.gradle b/mobile/android/app/build.gradle
index 18f1a93..0387929 100644
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -9,8 +9,8 @@ android {
targetSdkVersion rootProject.ext.targetSdkVersion
// Version values below are overwritten by scripts/release.sh from
// the root package.json. versionCode auto-increments per release.
- versionCode 714001
- versionName "7.14.1"
+ versionCode 714002
+ versionName "7.14.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
diff --git a/mobile/android/app/src/main/java/com/pedshub/scribe/MainActivity.java b/mobile/android/app/src/main/java/com/pedshub/scribe/MainActivity.java
index 19932c0..3fabeab 100644
--- a/mobile/android/app/src/main/java/com/pedshub/scribe/MainActivity.java
+++ b/mobile/android/app/src/main/java/com/pedshub/scribe/MainActivity.java
@@ -1,11 +1,17 @@
package com.pedshub.scribe;
import android.Manifest;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
+import android.print.PrintAttributes;
+import android.print.PrintDocumentAdapter;
+import android.print.PrintManager;
+import android.util.Base64;
import android.webkit.PermissionRequest;
import android.webkit.WebChromeClient;
+import android.webkit.WebViewClient;
import android.webkit.WebView;
import androidx.annotation.NonNull;
@@ -18,6 +24,7 @@ public class MainActivity extends BridgeActivity {
private static final int MIC_PERMISSION_CODE = 1001;
private PermissionRequest pendingPermissionRequest;
+ private WebView printWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -35,6 +42,9 @@ public class MainActivity extends BridgeActivity {
// Register JS interface for foreground service control
setupRecordingBridge();
+
+ // Register JS interface for Android's print / Save as PDF flow.
+ setupPrintBridge();
}
// ── WebView Microphone Permission ──────────────────────────
@@ -80,6 +90,11 @@ public class MainActivity extends BridgeActivity {
webView.addJavascriptInterface(new RecordingBridge(this), "NativeRecording");
}
+ private void setupPrintBridge() {
+ WebView webView = this.bridge.getWebView();
+ webView.addJavascriptInterface(new PrintBridge(this), "NativePrint");
+ }
+
public static class RecordingBridge {
private final MainActivity activity;
@@ -100,4 +115,36 @@ public class MainActivity extends BridgeActivity {
activity.startService(intent);
}
}
+
+ public static class PrintBridge {
+ private final MainActivity activity;
+
+ PrintBridge(MainActivity activity) {
+ this.activity = activity;
+ }
+
+ @android.webkit.JavascriptInterface
+ public void printHtml(String title, String base64Html) {
+ activity.runOnUiThread(() -> activity.printHtmlFromBase64(title, base64Html));
+ }
+ }
+
+ private void printHtmlFromBase64(String title, String base64Html) {
+ try {
+ byte[] decoded = Base64.decode(base64Html, Base64.DEFAULT);
+ String html = new String(decoded, java.nio.charset.StandardCharsets.UTF_8);
+ printWebView = new WebView(this);
+ printWebView.setWebViewClient(new WebViewClient() {
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
+ PrintDocumentAdapter adapter = view.createPrintDocumentAdapter(title != null && !title.isEmpty() ? title : "Clinical Assistant Export");
+ printManager.print(title != null && !title.isEmpty() ? title : "Clinical Assistant Export", adapter, new PrintAttributes.Builder().build());
+ }
+ });
+ printWebView.loadDataWithBaseURL(null, html, "text/html", "UTF-8", null);
+ } catch (Exception e) {
+ android.util.Log.e("PedScribe", "Native print failed", e);
+ }
+ }
}
diff --git a/mobile/android/app/src/main/res/xml/file_paths.xml b/mobile/android/app/src/main/res/xml/file_paths.xml
index bd0c4d8..c7bcaad 100644
--- a/mobile/android/app/src/main/res/xml/file_paths.xml
+++ b/mobile/android/app/src/main/res/xml/file_paths.xml
@@ -2,4 +2,6 @@
-
\ No newline at end of file
+
+
+
diff --git a/mobile/package-lock.json b/mobile/package-lock.json
index 27a14b6..045905d 100644
--- a/mobile/package-lock.json
+++ b/mobile/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "pedscribe-mobile",
- "version": "7.14.1",
+ "version": "7.14.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pedscribe-mobile",
- "version": "7.14.1",
+ "version": "7.14.2",
"dependencies": {
"@aparajita/capacitor-biometric-auth": "^8.0.0",
"@capacitor/android": "^6.0.0",
diff --git a/mobile/package.json b/mobile/package.json
index 1b652de..b7be2ec 100644
--- a/mobile/package.json
+++ b/mobile/package.json
@@ -1,6 +1,6 @@
{
"name": "pedscribe-mobile",
- "version": "7.14.1",
+ "version": "7.14.2",
"description": "PedScribe native mobile app — Capacitor wrapper for Pediatric AI Scribe",
"private": true,
"scripts": {
diff --git a/package-lock.json b/package-lock.json
index 51d7896..f7e4c79 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "pediatric-ai-scribe",
- "version": "7.14.1",
+ "version": "7.14.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pediatric-ai-scribe",
- "version": "7.14.1",
+ "version": "7.14.2",
"dependencies": {
"@marp-team/marp-cli": "^4.3.1",
"@marp-team/marp-core": "^4.3.0",
diff --git a/package.json b/package.json
index db4cad6..2a15d52 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "pediatric-ai-scribe",
- "version": "7.14.1",
+ "version": "7.14.2",
"description": "AI-powered pediatric clinical documentation platform",
"main": "server.js",
"scripts": {
diff --git a/public/js/assistant/export.js b/public/js/assistant/export.js
index fc8d194..2944b2a 100644
--- a/public/js/assistant/export.js
+++ b/public/js/assistant/export.js
@@ -78,7 +78,9 @@ export function createAssistantExporter(options) {
var originalText = printBtn.textContent;
printBtn.textContent = 'Preparing export...';
try {
- if (!(await saveInlineExport(buildPrintableChatHtml(items, imageSrc, false)))) window.print();
+ var html = buildPrintableChatHtml(items, imageSrc, false);
+ if (await printWithNativeBridge(html)) return;
+ if (!(await saveInlineExport(html))) window.print();
} finally {
printBtn.disabled = false;
printBtn.textContent = originalText;
@@ -150,7 +152,7 @@ async function saveInlineExport(html) {
if (!plugins || !plugins.Filesystem) return false;
try {
var name = 'clinical-assistant-export-' + new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19) + '.html';
- var saved = await plugins.Filesystem.writeFile({ path: name, data: utf8ToBase64(html), directory: 'DOCUMENTS' });
+ var saved = await plugins.Filesystem.writeFile({ path: name, data: utf8ToBase64(html), directory: 'CACHE' });
if (plugins.Share && saved && saved.uri) {
try {
await plugins.Share.share({ title: 'Clinical Assistant Export', text: 'Open this export and use Print to save as PDF.', url: saved.uri, dialogTitle: 'Save or share export' });
@@ -158,9 +160,21 @@ async function saveInlineExport(html) {
if (!isShareCancel(e)) throw e;
}
}
- if (typeof window.showToast === 'function') window.showToast('Export saved to Documents', 'success');
+ if (typeof window.showToast === 'function') window.showToast('Export prepared', 'success');
return true;
} catch (e) {
+ if (typeof window.showToast === 'function') window.showToast(e.message || 'Export failed', 'error');
+ return false;
+ }
+}
+
+async function printWithNativeBridge(html) {
+ if (!window.NativePrint || typeof window.NativePrint.printHtml !== 'function') return false;
+ try {
+ window.NativePrint.printHtml('Clinical Assistant Export', utf8ToBase64(html));
+ return true;
+ } catch (e) {
+ if (typeof window.showToast === 'function') window.showToast(e.message || 'Native print failed', 'error');
return false;
}
}
diff --git a/public/js/assistant/images.js b/public/js/assistant/images.js
index be04d32..3af6648 100644
--- a/public/js/assistant/images.js
+++ b/public/js/assistant/images.js
@@ -76,8 +76,16 @@ export function createAssistantImageStore() {
}
async function saveWithNativeShare(src) {
+ if (isNativeApp()) {
+ return await saveWithCapacitorShare(src);
+ }
var webShare = await shareWithWebFile(src);
if (webShare !== 'unavailable') return true;
+
+ return await saveWithCapacitorShare(src);
+}
+
+async function saveWithCapacitorShare(src) {
var plugins = window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins : null;
if (!plugins) return false;
if (!plugins.Filesystem && plugins.Share && !/^data:image\//i.test(src)) {
@@ -90,7 +98,7 @@ async function saveWithNativeShare(src) {
try {
var base64 = await imageSourceToBase64(src);
var name = 'clinical-visual-' + new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19) + '.png';
- var saved = await plugins.Filesystem.writeFile({ path: name, data: base64, directory: 'DOCUMENTS' });
+ var saved = await plugins.Filesystem.writeFile({ path: name, data: base64, directory: 'CACHE' });
if (plugins.Share && saved && saved.uri) {
try {
await plugins.Share.share({ title: 'Clinical visual', text: 'Clinical Assistant generated visual', url: saved.uri, dialogTitle: 'Save or share clinical visual' });
@@ -98,7 +106,7 @@ async function saveWithNativeShare(src) {
if (!isShareCancel(e)) throw e;
}
}
- if (typeof window.showToast === 'function') window.showToast('Image saved to Documents', 'success');
+ if (typeof window.showToast === 'function') window.showToast('Image prepared', 'success');
return true;
} catch (e) {
if (typeof window.showToast === 'function') window.showToast('Could not save with the app. Opening browser download instead.', 'error');