Restructure Android project from nested module layout to standard Capacitor plugin structure following community conventions. Structure Changes: - Move plugin code from android/plugin/ to android/src/main/java/ - Move test app from android/app/ to test-apps/android-test-app/app/ - Remove nested android/plugin module structure - Remove nested android/app test app structure Build Infrastructure: - Add Gradle wrapper files (gradlew, gradlew.bat, gradle/wrapper/) - Transform android/build.gradle from root project to library module - Update android/settings.gradle for standalone plugin builds - Add android/gradle.properties with AndroidX configuration - Add android/consumer-rules.pro for ProGuard rules Configuration Updates: - Add prepare script to package.json for automatic builds on npm install - Update package.json version to 1.0.1 - Update android/build.gradle to properly resolve Capacitor dependencies - Update test-apps/android-test-app/settings.gradle with correct paths - Remove android/variables.gradle (hardcode values in build.gradle) Documentation: - Update BUILDING.md with new structure and build process - Update INTEGRATION_GUIDE.md to reflect standard structure - Update README.md to remove path fix warnings - Add test-apps/BUILD_PROCESS.md documenting test app build flows Test App Configuration: - Fix android-test-app to correctly reference plugin and Capacitor - Remove capacitor-cordova-android-plugins dependency (not needed) - Update capacitor.settings.gradle path verification in fix script BREAKING CHANGE: Plugin now uses standard Capacitor Android structure. Consuming apps must update their capacitor.settings.gradle to reference android/ instead of android/plugin/. This is automatically handled by Capacitor CLI for apps using standard plugin installation.
147 lines
6.2 KiB
Java
147 lines
6.2 KiB
Java
/**
|
|
* NativeNotificationContentFetcher.java
|
|
*
|
|
* Service Provider Interface (SPI) for native content fetchers.
|
|
*
|
|
* This interface is part of the Integration Point Refactor (PR1) that allows
|
|
* host apps to provide their own content fetching logic without hardcoding
|
|
* TimeSafari-specific code in the plugin.
|
|
*
|
|
* Host apps implement this interface in native code (Kotlin/Java) and register
|
|
* it with the plugin. The plugin calls this interface from background workers
|
|
* (WorkManager) to fetch notification content.
|
|
*
|
|
* @author Matthew Raymer
|
|
* @version 1.0.0
|
|
*/
|
|
|
|
package com.timesafari.dailynotification;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import java.util.List;
|
|
import java.util.concurrent.CompletableFuture;
|
|
|
|
/**
|
|
* Native content fetcher interface for host app implementations
|
|
*
|
|
* This interface enables the plugin to call host app's native code for
|
|
* fetching notification content. This is the ONLY path used by background
|
|
* workers, as JavaScript bridges are unreliable in background contexts.
|
|
*
|
|
* Implementation Requirements:
|
|
* - Must be thread-safe (may be called from WorkManager background threads)
|
|
* - Must complete within reasonable time (plugin enforces timeout)
|
|
* - Should return empty list on failure rather than throwing exceptions
|
|
* - Should handle errors gracefully and log for debugging
|
|
*
|
|
* Example Implementation:
|
|
* <pre>
|
|
* class TimeSafariNativeFetcher implements NativeNotificationContentFetcher {
|
|
* private final TimeSafariApi api;
|
|
* private final TokenProvider tokenProvider;
|
|
*
|
|
* @Override
|
|
* public CompletableFuture<List<NotificationContent>> fetchContent(
|
|
* FetchContext context) {
|
|
* return CompletableFuture.supplyAsync(() -> {
|
|
* try {
|
|
* String jwt = tokenProvider.freshToken();
|
|
* // Fetch from TimeSafari API
|
|
* // Convert to NotificationContent[]
|
|
* return notificationContents;
|
|
* } catch (Exception e) {
|
|
* Log.e("Fetcher", "Fetch failed", e);
|
|
* return Collections.emptyList();
|
|
* }
|
|
* });
|
|
* }
|
|
* }
|
|
* </pre>
|
|
*/
|
|
public interface NativeNotificationContentFetcher {
|
|
|
|
/**
|
|
* Fetch notification content from external source
|
|
*
|
|
* This method is called by the plugin when:
|
|
* - Background fetch work is triggered (WorkManager)
|
|
* - Prefetch is scheduled before notification time
|
|
* - Manual refresh is requested (if native fetcher enabled)
|
|
*
|
|
* The plugin will:
|
|
* - Enforce a timeout (default 30 seconds, configurable via SchedulingPolicy)
|
|
* - Handle empty lists gracefully (no notifications scheduled)
|
|
* - Log errors for debugging
|
|
* - Retry on failure based on SchedulingPolicy
|
|
*
|
|
* @param context Context about why fetch was triggered, including
|
|
* trigger type, scheduled time, and optional metadata
|
|
* @return CompletableFuture that resolves to list of NotificationContent.
|
|
* Empty list indicates no content available (not an error).
|
|
* The future should complete exceptionally only on unrecoverable errors.
|
|
*/
|
|
@NonNull
|
|
CompletableFuture<List<NotificationContent>> fetchContent(@NonNull FetchContext context);
|
|
|
|
/**
|
|
* Optional: Configure the native fetcher with API credentials and settings
|
|
*
|
|
* <p>This method is called by the plugin when {@code configureNativeFetcher} is invoked
|
|
* from TypeScript. It provides a cross-platform mechanism for passing configuration
|
|
* from the JavaScript layer to native code without using platform-specific storage
|
|
* mechanisms.</p>
|
|
*
|
|
* <p><b>When to implement:</b></p>
|
|
* <ul>
|
|
* <li>Your fetcher needs API credentials (URL, authentication tokens, etc.)</li>
|
|
* <li>Configuration should come from TypeScript/JavaScript code (e.g., from app config)</li>
|
|
* <li>You want to avoid hardcoding credentials in native code</li>
|
|
* </ul>
|
|
*
|
|
* <p><b>When to skip (use default no-op):</b></p>
|
|
* <ul>
|
|
* <li>Your fetcher gets credentials from platform-specific storage (SharedPreferences, Keychain, etc.)</li>
|
|
* <li>Your fetcher has hardcoded test credentials</li>
|
|
* <li>Configuration is handled internally and doesn't need external input</li>
|
|
* </ul>
|
|
*
|
|
* <p><b>Thread Safety:</b> This method may be called from any thread. Implementations
|
|
* must be thread-safe if storing configuration in instance variables.</p>
|
|
*
|
|
* <p><b>Implementation Pattern:</b></p>
|
|
* <pre>{@code
|
|
* private volatile String apiBaseUrl;
|
|
* private volatile String activeDid;
|
|
* private volatile String jwtSecret;
|
|
*
|
|
* @Override
|
|
* public void configure(String apiBaseUrl, String activeDid, String jwtSecret) {
|
|
* this.apiBaseUrl = apiBaseUrl;
|
|
* this.activeDid = activeDid;
|
|
* this.jwtSecret = jwtSecret;
|
|
* Log.i(TAG, "Fetcher configured with API: " + apiBaseUrl);
|
|
* }
|
|
* }</pre>
|
|
*
|
|
* @param apiBaseUrl Base URL for API server. Examples:
|
|
* - Android emulator: "http://10.0.2.2:3000" (maps to host localhost:3000)
|
|
* - iOS simulator: "http://localhost:3000"
|
|
* - Production: "https://api.timesafari.com"
|
|
* @param activeDid Active DID (Decentralized Identifier) for authentication.
|
|
* Used as the JWT issuer/subject. Format: "did:ethr:0x..."
|
|
* @param jwtToken Pre-generated JWT token (ES256K signed) from TypeScript.
|
|
* This token is generated in the host app using TimeSafari's
|
|
* {@code createEndorserJwtForKey()} function. The native fetcher
|
|
* should use this token directly in the Authorization header as
|
|
* "Bearer {jwtToken}". No JWT generation or signing is needed in Java.
|
|
*
|
|
* @see DailyNotificationPlugin#configureNativeFetcher(PluginCall)
|
|
*/
|
|
default void configure(String apiBaseUrl, String activeDid, String jwtToken) {
|
|
// Default no-op implementation - fetchers that need config can override
|
|
// This allows fetchers that don't need TypeScript-provided configuration
|
|
// to ignore this method without implementing an empty body.
|
|
}
|
|
}
|
|
|