FileSaver fails in Capacitor WebView, so save via MediaStore; clear tray notifications when sync reports rooms as read on another device, and improve collapsed notification avatars with conversation shortcuts.
85 lines
2.9 KiB
TypeScript
85 lines
2.9 KiB
TypeScript
import { Capacitor, registerPlugin, type PluginListenerHandle } from '@capacitor/core';
|
|
|
|
/** Metadata for a file received from an Android share intent. */
|
|
export type AndroidSharedFile = {
|
|
path: string;
|
|
name: string;
|
|
mimeType: string;
|
|
size: number;
|
|
};
|
|
|
|
/** Payload persisted by the native Android share handler. */
|
|
export type AndroidSharePayload = {
|
|
text?: string;
|
|
subject?: string;
|
|
files: AndroidSharedFile[];
|
|
receivedAt: number;
|
|
};
|
|
|
|
interface AndroidShareHandlerPlugin {
|
|
/** Returns the last pending share payload, if one exists. */
|
|
getPendingShare(): Promise<{ share: AndroidSharePayload | null }>;
|
|
/** Clears the last pending share payload after it has been consumed. */
|
|
clearPendingShare(): Promise<void>;
|
|
/** Save a base64 file to gallery / Downloads (MediaStore), or share-sheet fallback. */
|
|
saveFile(options: {
|
|
filename: string;
|
|
mimeType?: string;
|
|
base64: string;
|
|
}): Promise<{ saved: boolean; shared?: boolean; uri?: string }>;
|
|
/** Open the system share sheet for a base64 file. */
|
|
shareFile(options: {
|
|
filename: string;
|
|
mimeType?: string;
|
|
base64: string;
|
|
}): Promise<{ shared: boolean }>;
|
|
addListener(
|
|
eventName: 'shareReceived',
|
|
listenerFunc: (payload: AndroidSharePayload) => void
|
|
): Promise<PluginListenerHandle>;
|
|
}
|
|
|
|
const AndroidShareHandler = registerPlugin<AndroidShareHandlerPlugin>('AndroidShareHandler');
|
|
|
|
/** Returns true when the Android share bridge is available. */
|
|
export const isAndroidShareSupported = (): boolean =>
|
|
Capacitor.isNativePlatform() && Capacitor.getPlatform() === 'android';
|
|
|
|
/** Fetches the current pending native share payload. */
|
|
export const getPendingAndroidShare = async (): Promise<AndroidSharePayload | null> => {
|
|
if (!isAndroidShareSupported()) return null;
|
|
const result = await AndroidShareHandler.getPendingShare();
|
|
return result.share;
|
|
};
|
|
|
|
/** Clears the native pending share payload. */
|
|
export const clearPendingAndroidShare = async (): Promise<void> => {
|
|
if (!isAndroidShareSupported()) return;
|
|
await AndroidShareHandler.clearPendingShare();
|
|
};
|
|
|
|
/** Subscribes to new incoming native share payloads. */
|
|
export const listenForAndroidShares = async (
|
|
listener: (payload: AndroidSharePayload) => void
|
|
): Promise<PluginListenerHandle | undefined> => {
|
|
if (!isAndroidShareSupported()) return undefined;
|
|
return AndroidShareHandler.addListener('shareReceived', listener);
|
|
};
|
|
|
|
/** Converts a cached native shared file into a browser File for upload. */
|
|
export const materializeSharedFile = async (
|
|
sharedFile: AndroidSharedFile,
|
|
receivedAt: number
|
|
): Promise<File> => {
|
|
const fileUrl = Capacitor.convertFileSrc(sharedFile.path);
|
|
const response = await fetch(fileUrl);
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to read shared file: ${sharedFile.name}`);
|
|
}
|
|
|
|
const blob = await response.blob();
|
|
return new File([blob], sharedFile.name, {
|
|
type: sharedFile.mimeType || blob.type,
|
|
lastModified: receivedAt,
|
|
});
|
|
}; |