Allow download on native mobile

This commit is contained in:
MartinBraquet
2026-02-13 17:59:13 +01:00
parent 7437a2fb45
commit a0c1cf964b
4 changed files with 64 additions and 17 deletions

View File

@@ -1,6 +1,7 @@
package com.compassconnections.app;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
@@ -13,8 +14,10 @@ import android.webkit.WebView;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import com.capacitorjs.plugins.pushnotifications.PushNotificationsPlugin;
import com.compassconnections.app.MainActivity.WebAppInterface;
import com.getcapacitor.BridgeActivity;
import com.getcapacitor.BridgeWebViewClient;
import com.getcapacitor.Plugin;
@@ -29,6 +32,10 @@ import com.google.android.play.core.install.model.UpdateAvailability;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import ee.forgr.capacitor.social.login.GoogleProvider;
import ee.forgr.capacitor.social.login.ModifiedMainActivityForSocialLoginPlugin;
import ee.forgr.capacitor.social.login.SocialLoginPlugin;
@@ -57,13 +64,42 @@ public class MainActivity extends BridgeActivity implements ModifiedMainActivity
}
}
public static class NativeBridge {
public static class WebAppInterface {
private final Context context;
public WebAppInterface(Context context) {
this.context = context;
}
@JavascriptInterface
public boolean isNativeApp() {
return true;
public void downloadFile(String filename, String content) {
try {
// Create file in app-specific external storage
File file = new File(context.getExternalFilesDir(null), filename);
// Write content to file
FileOutputStream fos = new FileOutputStream(file);
fos.write(content.getBytes());
fos.close();
// Get URI via FileProvider
String authority = context.getPackageName() + ".provider";
android.net.Uri uri = FileProvider.getUriForFile(context, authority, file);
// Launch intent to view/share file
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, "application/json");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
} catch (IOException e) {
Log.i("CompassApp", "Failed to download file", e);
}
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
@@ -100,7 +136,7 @@ public class MainActivity extends BridgeActivity implements ModifiedMainActivity
settings.setUserAgentString(settings.getUserAgentString() + " CompassAppWebView");
settings.setJavaScriptEnabled(true);
webView.addJavascriptInterface(new NativeBridge(), "AndroidBridge");
webView.addJavascriptInterface(new WebAppInterface(this), "AndroidBridge");
registerPlugin(PushNotificationsPlugin.class);
// Initialize the Bridge with Push Notifications plugin

View File

@@ -4,7 +4,6 @@ import {IS_WEBVIEW} from "common/hosting/constants";
export function isAndroidApp() {
try {
// Detect if Android bridge exists
// return typeof (window as any).AndroidBridge?.isNativeApp === 'function';
return Capacitor.isNativePlatform() || IS_WEBVIEW
} catch {
return false;

View File

@@ -4,6 +4,14 @@ import Script from 'next/script'
import clsx from "clsx";
import {IS_DEPLOYED} from "common/hosting/constants";
declare global {
interface Window {
AndroidBridge?: {
downloadFile: (filename: string, content: string) => void
}
}
}
export default function Document() {
return (
<Html lang="en">

View File

@@ -25,6 +25,7 @@ import HiddenProfilesModal from 'web/components/settings/hidden-profiles-modal'
import {EmailVerificationButton} from "web/components/email-verification-button";
import {api} from 'web/lib/api'
import {useUser} from "web/hooks/use-user";
import {isNativeMobile} from "web/lib/util/webview";
export default function NotificationsPage() {
const t = useT()
@@ -209,17 +210,21 @@ const DataPrivacySettings = () => {
try {
setIsDownloading(true)
const data = await api('me/data', {})
const blob = new Blob([JSON.stringify(data, null, 2)], {
type: 'application/json',
})
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `compass-data-export${user?.username ? `-${user.username}` : ''}.json`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
const jsonString = JSON.stringify(data, null, 2)
const filename = `compass-data-export${user?.username ? `-${user.username}` : ''}.json`;
if (isNativeMobile() && window.AndroidBridge && window.AndroidBridge.downloadFile) {
window.AndroidBridge.downloadFile(filename, jsonString)
} else {
const blob = new Blob([jsonString], {type: 'application/json'})
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
}
toast.success(
t(
'settings.data_privacy.download.success',
@@ -256,4 +261,3 @@ const DataPrivacySettings = () => {
</div>
)
}