diff --git a/android/app/src/main/assets/capacitor.plugins.json b/android/app/src/main/assets/capacitor.plugins.json index 72f18d8c..438497bf 100644 --- a/android/app/src/main/assets/capacitor.plugins.json +++ b/android/app/src/main/assets/capacitor.plugins.json @@ -34,5 +34,13 @@ { "pkg": "@capawesome/capacitor-file-picker", "classpath": "io.capawesome.capacitorjs.plugins.filepicker.FilePickerPlugin" + }, + { + "pkg": "SafeArea", + "classpath": "app.timesafari.safearea.SafeAreaPlugin" + }, + { + "pkg": "SharedImage", + "classpath": "app.timesafari.sharedimage.SharedImagePlugin" } ] diff --git a/android/app/src/main/java/app/timesafari/sharedimage/SharedImagePlugin.java b/android/app/src/main/java/app/timesafari/sharedimage/SharedImagePlugin.java index 947b4c83..862f7ccb 100644 --- a/android/app/src/main/java/app/timesafari/sharedimage/SharedImagePlugin.java +++ b/android/app/src/main/java/app/timesafari/sharedimage/SharedImagePlugin.java @@ -23,32 +23,37 @@ public class SharedImagePlugin extends Plugin { */ @PluginMethod public void getSharedImage(PluginCall call) { - SharedPreferences prefs = getSharedPreferences(); - - String base64 = prefs.getString(KEY_BASE64, null); - String fileName = prefs.getString(KEY_FILE_NAME, null); - - if (base64 == null || fileName == null) { - // No shared image exists - return null (not an error) + try { + SharedPreferences prefs = getSharedPreferences(); + + String base64 = prefs.getString(KEY_BASE64, null); + String fileName = prefs.getString(KEY_FILE_NAME, null); + + if (base64 == null || fileName == null) { + // No shared image exists - return null values (not an error) + JSObject result = new JSObject(); + result.put("base64", (String) null); + result.put("fileName", (String) null); + call.resolve(result); + return; + } + + // Clear the shared data after reading + SharedPreferences.Editor editor = prefs.edit(); + editor.remove(KEY_BASE64); + editor.remove(KEY_FILE_NAME); + editor.remove(KEY_READY); + editor.apply(); + + // Return the shared image data JSObject result = new JSObject(); - result.put("base64", null); - result.put("fileName", null); + result.put("base64", base64); + result.put("fileName", fileName); call.resolve(result); - return; + } catch (Exception e) { + android.util.Log.e("SharedImagePlugin", "Error in getSharedImage()", e); + call.reject("Error getting shared image: " + e.getMessage()); } - - // Clear the shared data after reading - SharedPreferences.Editor editor = prefs.edit(); - editor.remove(KEY_BASE64); - editor.remove(KEY_FILE_NAME); - editor.remove(KEY_READY); - editor.apply(); - - // Return the shared image data - JSObject result = new JSObject(); - result.put("base64", base64); - result.put("fileName", fileName); - call.resolve(result); } /** diff --git a/package.json b/package.json index 78360a4b..ff8051ac 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,8 @@ "auto-run:android": "./scripts/auto-run.sh --platform=android", "auto-run:electron": "./scripts/auto-run.sh --platform=electron", "build:capacitor": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --mode capacitor --config vite.config.capacitor.mts", - "build:capacitor:sync": "npm run build:capacitor && npx cap sync", - "build:native": "vite build && npx cap sync && npx capacitor-assets generate", + "build:capacitor:sync": "npm run build:capacitor && npx cap sync && node scripts/restore-local-plugins.js", + "build:native": "vite build && npx cap sync && node scripts/restore-local-plugins.js && npx capacitor-assets generate", "assets:config": "npx tsx scripts/assets-config.ts", "assets:validate": "npx tsx scripts/assets-validator.ts", "assets:validate:android": "./scripts/build-android.sh --assets-only", diff --git a/scripts/README-restore-local-plugins.md b/scripts/README-restore-local-plugins.md new file mode 100644 index 00000000..395a745a --- /dev/null +++ b/scripts/README-restore-local-plugins.md @@ -0,0 +1,60 @@ +# Restore Local Capacitor Plugins + +## Overview + +The `restore-local-plugins.js` script ensures that local custom Capacitor plugins (`SafeArea` and `SharedImage`) are automatically restored to `android/app/src/main/assets/capacitor.plugins.json` after running `npx cap sync android`. + +## Why This Is Needed + +The `capacitor.plugins.json` file is auto-generated by Capacitor during `npx cap sync` and gets overwritten, removing any manually added local plugins. This script automatically restores them. + +## Usage + +### Automatic (Recommended) + +The script is automatically run by: +- `./scripts/build-android.sh` (after `cap sync`) +- `npm run build:capacitor:sync` +- `npm run build:native` + +### Manual + +If you run `npx cap sync android` directly, you can restore plugins manually: + +```bash +node scripts/restore-local-plugins.js +``` + +## What It Does + +1. Reads `android/app/src/main/assets/capacitor.plugins.json` +2. Checks if local plugins (`SafeArea` and `SharedImage`) are present +3. Adds any missing local plugins +4. Preserves the existing JSON format + +## Local Plugins + +The following local plugins are automatically restored: + +- **SafeArea**: `app.timesafari.safearea.SafeAreaPlugin` +- **SharedImage**: `app.timesafari.sharedimage.SharedImagePlugin` + +## Adding New Local Plugins + +To add a new local plugin, edit `scripts/restore-local-plugins.js` and add it to the `LOCAL_PLUGINS` array: + +```javascript +const LOCAL_PLUGINS = [ + // ... existing plugins ... + { + pkg: 'YourPluginName', + classpath: 'app.timesafari.yourpackage.YourPluginClass' + } +]; +``` + +## Notes + +- The script is idempotent - running it multiple times won't create duplicates +- The script preserves the existing JSON formatting (tabs, etc.) +- If the plugins file doesn't exist, the script will exit with an error (run `npx cap sync android` first) diff --git a/scripts/build-android.sh b/scripts/build-android.sh index 39383064..8a29bf9b 100755 --- a/scripts/build-android.sh +++ b/scripts/build-android.sh @@ -385,6 +385,7 @@ fi if [ "$SYNC_ONLY" = true ]; then log_info "Sync-only mode: syncing with Capacitor" safe_execute "Syncing with Capacitor" "npx cap sync android" || exit 6 + safe_execute "Restoring local plugins" "node scripts/restore-local-plugins.js" || exit 7 log_success "Sync completed successfully!" exit 0 fi @@ -472,6 +473,9 @@ fi # Step 8: Sync with Capacitor safe_execute "Syncing with Capacitor" "npx cap sync android" || exit 6 +# Step 8.5: Restore local plugins (capacitor.plugins.json gets overwritten by cap sync) +safe_execute "Restoring local plugins" "node scripts/restore-local-plugins.js" || exit 7 + # Step 9: Generate assets safe_execute "Generating assets" "npx capacitor-assets generate --android" || exit 7 diff --git a/scripts/restore-local-plugins.js b/scripts/restore-local-plugins.js new file mode 100755 index 00000000..3b947d6a --- /dev/null +++ b/scripts/restore-local-plugins.js @@ -0,0 +1,78 @@ +#!/usr/bin/env node +/** + * Restore Local Capacitor Plugins + * + * This script ensures that local custom plugins (SafeArea and SharedImage) + * are present in capacitor.plugins.json after `npx cap sync` runs. + * + * The capacitor.plugins.json file is auto-generated by Capacitor and gets + * overwritten during sync, so we need to restore our local plugins. + * + * Usage: + * node scripts/restore-local-plugins.js + * + * This should be run after `npx cap sync android` or `npx cap sync ios` + */ + +const fs = require('fs'); +const path = require('path'); + +const PLUGINS_FILE = path.join(__dirname, '../android/app/src/main/assets/capacitor.plugins.json'); + +// Local plugins that need to be added +const LOCAL_PLUGINS = [ + { + pkg: 'SafeArea', + classpath: 'app.timesafari.safearea.SafeAreaPlugin' + }, + { + pkg: 'SharedImage', + classpath: 'app.timesafari.sharedimage.SharedImagePlugin' + } +]; + +function restoreLocalPlugins() { + try { + // Read the current plugins file + if (!fs.existsSync(PLUGINS_FILE)) { + console.error(`❌ Plugins file not found: ${PLUGINS_FILE}`); + console.error(' Run "npx cap sync android" first to generate the file.'); + process.exit(1); + } + + const content = fs.readFileSync(PLUGINS_FILE, 'utf8'); + let plugins = JSON.parse(content); + + if (!Array.isArray(plugins)) { + console.error(`❌ Invalid plugins file format: expected array, got ${typeof plugins}`); + process.exit(1); + } + + // Check which local plugins are missing + const existingPackages = new Set(plugins.map(p => p.pkg)); + const missingPlugins = LOCAL_PLUGINS.filter(p => !existingPackages.has(p.pkg)); + + if (missingPlugins.length === 0) { + console.log('✅ All local plugins are already present in capacitor.plugins.json'); + return; + } + + // Add missing plugins + plugins.push(...missingPlugins); + + // Write back to file with proper formatting (matching existing style) + const formatted = JSON.stringify(plugins, null, '\t'); + fs.writeFileSync(PLUGINS_FILE, formatted + '\n', 'utf8'); + + console.log('✅ Restored local plugins to capacitor.plugins.json:'); + missingPlugins.forEach(p => { + console.log(` - ${p.pkg} (${p.classpath})`); + }); + } catch (error) { + console.error('❌ Error restoring local plugins:', error.message); + process.exit(1); + } +} + +// Run the script +restoreLocalPlugins(); diff --git a/src/main.capacitor.ts b/src/main.capacitor.ts index 698492a8..0fe2c0ac 100644 --- a/src/main.capacitor.ts +++ b/src/main.capacitor.ts @@ -149,16 +149,22 @@ async function checkAndStoreNativeSharedImage(): Promise<{ return { success: false }; } - const platform = Capacitor.getPlatform(); - logger.debug( - `[Main] Checking for ${platform} shared image via SharedImage plugin`, - ); - // Use SharedImage plugin to get shared image data directly from native layer // No file I/O or polling needed - direct native-to-JS communication - const result = await SharedImage.getSharedImage(); + let result; + try { + result = await SharedImage.getSharedImage(); + } catch (error) { + logger.error("[Main] Error calling SharedImage.getSharedImage():", { + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined, + }); + isProcessingSharedImage = false; + return { success: false }; + } - if (result && result.base64) { + // Check if we have valid image data (base64 must be non-null and non-empty) + if (result && result.base64 && result.base64.trim().length > 0) { const fileName = result.fileName || "shared-image.jpg"; // Store in temp database using extracted method @@ -394,13 +400,24 @@ app.mount("#app"); logger.info(`[Main] ✅ App mounted successfully`); // Check for shared image on initial load (in case app was launched from share sheet) +// On Android, share intents are processed in MainActivity.onCreate, so we need to check +// after a delay to ensure the native code has finished processing if ( Capacitor.isNativePlatform() && (Capacitor.getPlatform() === "ios" || Capacitor.getPlatform() === "android") ) { - setTimeout(async () => { - await checkForSharedImageAndNavigate(); - }, 1000); // Small delay to ensure router is ready + // Use multiple checks with increasing delays to handle timing issues + // Android share intent processing happens in onCreate, which may complete after JS loads + const checkDelays = + Capacitor.getPlatform() === "android" + ? [500, 1500, 3000] // Android needs more time for share intent processing + : [1000]; // iOS is faster + + checkDelays.forEach((delay) => { + setTimeout(async () => { + await checkForSharedImageAndNavigate(); + }, delay); + }); } // Listen for app state changes to detect when app becomes active