diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 1d8ad70d11..46e22c040a 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -27,6 +27,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
imageUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
+ if (imageUris != null && !imageUris.isEmpty()) {
+ processSharedImage(imageUris.get(0), null);
+ }
+ }
+ }
+
+ /**
+ * Process a shared image: read it, convert to base64, and write to temp file
+ */
+ private void processSharedImage(Uri imageUri, String fileName) {
+ try {
+ // Read image data from URI
+ InputStream inputStream = getContentResolver().openInputStream(imageUri);
+ if (inputStream == null) {
+ Log.e(TAG, "Failed to open input stream for shared image");
+ return;
+ }
+
+ // Read image bytes
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ byte[] data = new byte[8192];
+ int nRead;
+ while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
+ buffer.write(data, 0, nRead);
+ }
+ buffer.flush();
+ byte[] imageBytes = buffer.toByteArray();
+ inputStream.close();
+
+ // Convert to base64
+ String base64String = Base64.encodeToString(imageBytes, Base64.NO_WRAP);
+
+ // Extract filename from URI or use default
+ String actualFileName = fileName;
+ if (actualFileName == null || actualFileName.isEmpty()) {
+ String path = imageUri.getPath();
+ if (path != null) {
+ int lastSlash = path.lastIndexOf('/');
+ if (lastSlash >= 0 && lastSlash < path.length() - 1) {
+ actualFileName = path.substring(lastSlash + 1);
+ }
+ }
+ if (actualFileName == null || actualFileName.isEmpty()) {
+ actualFileName = "shared-image.jpg";
+ }
+ }
+
+ // Write to temp file in app's internal files directory
+ // JavaScript will read this file using Capacitor's Filesystem plugin
+ writeSharedImageToTempFile(base64String, actualFileName);
+
+ Log.d(TAG, "Successfully processed shared image: " + actualFileName);
+ } catch (IOException e) {
+ Log.e(TAG, "Error processing shared image", e);
+ } catch (Exception e) {
+ Log.e(TAG, "Unexpected error processing shared image", e);
+ }
+ }
+
+ /**
+ * Write shared image data to temp JSON file for JavaScript to read
+ * File is written to app's internal files directory (accessible via Capacitor Filesystem plugin)
+ */
+ private void writeSharedImageToTempFile(String base64, String fileName) {
+ try {
+ // Get app's internal files directory
+ File filesDir = getFilesDir();
+ File tempFile = new File(filesDir, TEMP_FILE_NAME);
+
+ // Create JSON object
+ JSONObject jsonData = new JSONObject();
+ jsonData.put("base64", base64);
+ jsonData.put("fileName", fileName);
+
+ // Write to file
+ FileWriter writer = new FileWriter(tempFile);
+ writer.write(jsonData.toString());
+ writer.close();
+
+ Log.d(TAG, "Wrote shared image data to temp file: " + tempFile.getAbsolutePath());
+ } catch (Exception e) {
+ Log.e(TAG, "Error writing shared image to temp file", e);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main.capacitor.ts b/src/main.capacitor.ts
index 402c7fbbf4..1bc7f08ef7 100644
--- a/src/main.capacitor.ts
+++ b/src/main.capacitor.ts
@@ -63,12 +63,14 @@ let isProcessingSharedImage = false;
* More reliable than hardcoded timeout - checks if file actually exists
*
* @param filePath - Path to the file to check
+ * @param directory - Directory to check (default: Directory.Documents)
* @param maxRetries - Maximum number of retry attempts (default: 5)
* @param initialDelay - Initial delay in milliseconds (default: 100)
* @returns Promise - true if file exists, false if max retries reached
*/
async function pollForFileExistence(
filePath: string,
+ directory: Directory = Directory.Documents,
maxRetries: number = 5,
initialDelay: number = 100,
): Promise {
@@ -76,7 +78,7 @@ async function pollForFileExistence(
try {
await Filesystem.stat({
path: filePath,
- directory: Directory.Documents,
+ directory: directory,
});
// File exists
return true;
@@ -176,12 +178,19 @@ async function checkAndStoreNativeSharedImage(): Promise<{
isProcessingSharedImage = true;
try {
- if (!Capacitor.isNativePlatform() || Capacitor.getPlatform() !== "ios") {
+ if (
+ !Capacitor.isNativePlatform() ||
+ (Capacitor.getPlatform() !== "ios" &&
+ Capacitor.getPlatform() !== "android")
+ ) {
isProcessingSharedImage = false;
return { success: false };
}
- logger.debug("[Main] Checking for iOS shared image from App Group");
+ const platform = Capacitor.getPlatform();
+ logger.debug(
+ `[Main] Checking for ${platform} shared image from native layer`,
+ );
// Use Capacitor's native bridge to call the ShareImagePlugin
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -194,16 +203,22 @@ async function checkAndStoreNativeSharedImage(): Promise<{
}
// WORKAROUND: Since the plugin isn't being auto-discovered, use a temp file bridge
- // AppDelegate writes the shared image data to a temp file, and we read it here
+ // Native layer (AppDelegate on iOS, MainActivity on Android) writes the shared image data to a temp file, and we read it here
const tempFilePath = "timesafari_shared_photo.json";
+ // Use platform-specific directory:
+ // - iOS: Directory.Documents (AppDelegate writes to Documents directory)
+ // - Android: Directory.Data (MainActivity writes to getFilesDir() which maps to Data)
+ const fileDirectory =
+ platform === "android" ? Directory.Data : Directory.Documents;
+
// Check if file exists first (more reliable than hardcoded timeout)
- const fileExists = await pollForFileExistence(tempFilePath);
+ const fileExists = await pollForFileExistence(tempFilePath, fileDirectory);
if (fileExists) {
try {
const fileContent = await Filesystem.readFile({
path: tempFilePath,
- directory: Directory.Documents,
+ directory: fileDirectory,
encoding: Encoding.UTF8,
});
@@ -223,7 +238,7 @@ async function checkAndStoreNativeSharedImage(): Promise<{
try {
await Filesystem.deleteFile({
path: tempFilePath,
- directory: Directory.Documents,
+ directory: fileDirectory,
});
logger.debug("[Main] Deleted temp file after reading");
} catch (deleteError) {
@@ -492,7 +507,10 @@ const registerDeepLinkListener = async () => {
* This is called when app becomes active (from share extension or app launch)
*/
async function checkForSharedImageAndNavigate() {
- if (!Capacitor.isNativePlatform() || Capacitor.getPlatform() !== "ios") {
+ if (
+ !Capacitor.isNativePlatform() ||
+ (Capacitor.getPlatform() !== "ios" && Capacitor.getPlatform() !== "android")
+ ) {
return;
}
@@ -540,15 +558,21 @@ logger.log("[Capacitor] 🚀 Mounting app");
app.mount("#app");
logger.info(`[Main] ✅ App mounted successfully`);
-// Check for shared image on initial load (in case app was launched from share extension)
-if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === "ios") {
+// Check for shared image on initial load (in case app was launched from share sheet)
+if (
+ Capacitor.isNativePlatform() &&
+ (Capacitor.getPlatform() === "ios" || Capacitor.getPlatform() === "android")
+) {
setTimeout(async () => {
await checkForSharedImageAndNavigate();
}, 1000); // Small delay to ensure router is ready
}
// Listen for app state changes to detect when app becomes active
-if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === "ios") {
+if (
+ Capacitor.isNativePlatform() &&
+ (Capacitor.getPlatform() === "ios" || Capacitor.getPlatform() === "android")
+) {
CapacitorApp.addListener("appStateChange", async ({ isActive }) => {
if (isActive) {
logger.debug("[Main] 📱 App became active, checking for shared image");