refactor(shared-image): replace temp file approach with native Capacitor plugins

Replace the buggy temp file polling mechanism with direct native-to-JS
communication via custom Capacitor plugins for iOS and Android. This
eliminates race conditions, file management complexity, and polling overhead.

BREAKING CHANGE: Removes backward compatibility with temp file approach.
Android minSdkVersion upgraded from 22 to 23.

Changes:
- iOS: Created SharedImagePlugin (CAPBridgedPlugin) and SharedImageUtility
  - Uses App Group UserDefaults for data sharing between extension and app
  - Implements getSharedImage() and hasSharedImage() methods
  - Removes temp file writing/reading logic from AppDelegate
- Android: Created SharedImagePlugin with @CapacitorPlugin annotation
  - Uses SharedPreferences for data storage instead of temp files
  - Implements same interface as iOS plugin
  - Removes temp file handling from MainActivity
- TypeScript: Added plugin definitions and registration
  - Created SharedImagePlugin interface and web fallback
  - Updated main.capacitor.ts to use plugin instead of Filesystem API
  - Removed pollForFileExistence() and related file I/O code
- Android: Upgraded minSdkVersion from 22 to 23
  - Required for SharedPreferences improvements and better API support

Benefits:
- Eliminates race conditions from file polling
- Removes file cleanup complexity
- Direct native-to-JS communication (no file I/O)
- Better performance (no polling overhead)
- More reliable data transfer between share extension and app
- Cleaner architecture with proper plugin abstraction
This commit is contained in:
Jose Olarte III
2025-12-04 18:03:47 +08:00
parent eeac7fdb66
commit 84983ee10b
14 changed files with 1498 additions and 218 deletions

View File

@@ -15,18 +15,20 @@ import android.webkit.WebSettings;
import android.webkit.WebViewClient;
import com.getcapacitor.BridgeActivity;
import app.timesafari.safearea.SafeAreaPlugin;
import app.timesafari.sharedimage.SharedImagePlugin;
//import com.getcapacitor.community.sqlite.SQLite;
import android.content.SharedPreferences;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import org.json.JSONObject;
public class MainActivity extends BridgeActivity {
private static final String TAG = "MainActivity";
private static final String TEMP_FILE_NAME = "timesafari_shared_photo.json";
private static final String SHARED_PREFS_NAME = "shared_image";
private static final String KEY_BASE64 = "shared_image_base64";
private static final String KEY_FILE_NAME = "shared_image_file_name";
private static final String KEY_READY = "shared_image_ready";
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -62,6 +64,9 @@ public class MainActivity extends BridgeActivity {
// Register SafeArea plugin
registerPlugin(SafeAreaPlugin.class);
// Register SharedImage plugin
registerPlugin(SharedImagePlugin.class);
// Initialize SQLite
//registerPlugin(SQLite.class);
@@ -78,7 +83,7 @@ public class MainActivity extends BridgeActivity {
/**
* Handle share intents (ACTION_SEND or ACTION_SEND_MULTIPLE)
* Processes shared images and writes them to a temp file for JavaScript to read
* Processes shared images and stores them in SharedPreferences for plugin to read
*/
private void handleShareIntent(Intent intent) {
if (intent == null) {
@@ -164,9 +169,8 @@ public class MainActivity extends BridgeActivity {
}
}
// Write to temp file in app's internal files directory
// JavaScript will read this file using Capacitor's Filesystem plugin
writeSharedImageToTempFile(base64String, actualFileName);
// Store in SharedPreferences for plugin to read
storeSharedImageInPreferences(base64String, actualFileName);
Log.d(TAG, "Successfully processed shared image: " + actualFileName);
} catch (IOException e) {
@@ -177,28 +181,21 @@ public class MainActivity extends BridgeActivity {
}
/**
* 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)
* Store shared image data in SharedPreferences for plugin to read
* Plugin will read and clear the data when called
*/
private void writeSharedImageToTempFile(String base64, String fileName) {
private void storeSharedImageInPreferences(String base64, String fileName) {
try {
// Get app's internal files directory
File filesDir = getFilesDir();
File tempFile = new File(filesDir, TEMP_FILE_NAME);
SharedPreferences prefs = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(KEY_BASE64, base64);
editor.putString(KEY_FILE_NAME, fileName);
editor.putBoolean(KEY_READY, true);
editor.apply();
// 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());
Log.d(TAG, "Stored shared image data in SharedPreferences");
} catch (Exception e) {
Log.e(TAG, "Error writing shared image to temp file", e);
Log.e(TAG, "Error storing shared image in SharedPreferences", e);
}
}

View File

@@ -0,0 +1,79 @@
package app.timesafari.sharedimage;
import android.content.Context;
import android.content.SharedPreferences;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
@CapacitorPlugin(name = "SharedImage")
public class SharedImagePlugin extends Plugin {
private static final String SHARED_PREFS_NAME = "shared_image";
private static final String KEY_BASE64 = "shared_image_base64";
private static final String KEY_FILE_NAME = "shared_image_file_name";
private static final String KEY_READY = "shared_image_ready";
/**
* Get shared image data from SharedPreferences
* Returns base64 string and fileName, or null if no image exists
* Clears the data after reading to prevent re-reading
*/
@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)
JSObject result = new JSObject();
result.put("base64", null);
result.put("fileName", 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", base64);
result.put("fileName", fileName);
call.resolve(result);
}
/**
* Check if shared image exists without reading it
* Useful for quick checks before calling getSharedImage()
*/
@PluginMethod
public void hasSharedImage(PluginCall call) {
SharedPreferences prefs = getSharedPreferences();
boolean hasImage = prefs.contains(KEY_BASE64) && prefs.contains(KEY_FILE_NAME);
JSObject result = new JSObject();
result.put("hasImage", hasImage);
call.resolve(result);
}
/**
* Get SharedPreferences instance for shared image data
*/
private SharedPreferences getSharedPreferences() {
Context context = getContext();
if (context == null) {
throw new IllegalStateException("Plugin context is null");
}
return context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
}
}