forked from jsnbuchanan/crowd-funder-for-time-pwa
Fix Android image share workflow and add local plugin persistence
- Add SafeArea and SharedImage plugins to capacitor.plugins.json - Create restore-local-plugins.js to persist plugins after cap sync - Integrate plugin restoration into build scripts - Improve Android share intent timing with retry logic - Clean up debug logging while keeping essential error handling - Add documentation for local plugin management Fixes issue where SharedImage plugin wasn't recognized, causing "UNIMPLEMENTED" errors. Image sharing now works correctly on Android.
This commit is contained in:
@@ -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"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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",
|
||||
|
||||
60
scripts/README-restore-local-plugins.md
Normal file
60
scripts/README-restore-local-plugins.md
Normal file
@@ -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)
|
||||
@@ -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
|
||||
|
||||
|
||||
78
scripts/restore-local-plugins.js
Executable file
78
scripts/restore-local-plugins.js
Executable file
@@ -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();
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user