diff --git a/android/app/build.gradle b/android/app/build.gradle index b1babb49..82e7711d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -27,6 +27,12 @@ if (!project.ext.MY_KEYSTORE_FILE) { android { namespace 'app.timesafari' compileSdk rootProject.ext.compileSdkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + defaultConfig { applicationId "app.timesafari.app" minSdkVersion rootProject.ext.minSdkVersion @@ -101,13 +107,20 @@ dependencies { implementation project(':capacitor-android') implementation project(':capacitor-community-sqlite') implementation "androidx.biometric:biometric:1.2.0-alpha05" - + // Daily Notification Plugin dependencies implementation "androidx.room:room-runtime:2.6.1" implementation "androidx.work:work-runtime-ktx:2.9.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" annotationProcessor "androidx.room:room-compiler:2.6.1" - + + // Capacitor annotation processor for automatic plugin discovery + annotationProcessor project(':capacitor-android') + + // Additional dependencies for notification plugin + implementation 'androidx.lifecycle:lifecycle-service:2.7.0' + implementation 'com.google.code.gson:gson:2.10.1' + testImplementation "junit:junit:$junitVersion" androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 45869c63..6348d220 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,10 +1,12 @@ + android:exported="false"> @@ -99,4 +101,8 @@ + + + + diff --git a/android/app/src/main/assets/capacitor.config.json b/android/app/src/main/assets/capacitor.config.json index 04ab8d0c..e2b66a6d 100644 --- a/android/app/src/main/assets/capacitor.config.json +++ b/android/app/src/main/assets/capacitor.config.json @@ -42,6 +42,31 @@ "biometricTitle": "Biometric login for TimeSafari" }, "electronIsEncryption": false + }, + "DailyNotification": { + "debugMode": true, + "enableNotifications": true, + "timesafariConfig": { + "activeDid": "", + "endpoints": { + "projectsLastUpdated": "https://api.endorser.ch/api/v2/report/plansLastUpdatedBetween" + }, + "starredProjectsConfig": { + "enabled": true, + "starredPlanHandleIds": [], + "fetchInterval": "0 8 * * *" + } + }, + "networkConfig": { + "timeout": 30000, + "retryAttempts": 3, + "retryDelay": 1000 + }, + "contentFetch": { + "enabled": true, + "schedule": "0 8 * * *", + "fetchLeadTimeMinutes": 5 + } } }, "ios": { diff --git a/android/app/src/main/assets/capacitor.plugins.json b/android/app/src/main/assets/capacitor.plugins.json index 7e0d8ea0..721bea0d 100644 --- a/android/app/src/main/assets/capacitor.plugins.json +++ b/android/app/src/main/assets/capacitor.plugins.json @@ -38,13 +38,5 @@ { "pkg": "@timesafari/daily-notification-plugin", "classpath": "com.timesafari.dailynotification.DailyNotificationPlugin" - }, - { - "pkg": "SafeArea", - "classpath": "app.timesafari.safearea.SafeAreaPlugin" - }, - { - "pkg": "SharedImage", - "classpath": "app.timesafari.sharedimage.SharedImagePlugin" } ] diff --git a/android/app/src/main/java/app/timesafari/TimeSafariApplication.java b/android/app/src/main/java/app/timesafari/TimeSafariApplication.java new file mode 100644 index 00000000..c06846c8 --- /dev/null +++ b/android/app/src/main/java/app/timesafari/TimeSafariApplication.java @@ -0,0 +1,27 @@ +package app.timesafari; + +import android.app.Application; +import android.content.Context; +import android.util.Log; +import com.timesafari.dailynotification.DailyNotificationPlugin; +import com.timesafari.dailynotification.NativeNotificationContentFetcher; + +public class TimeSafariApplication extends Application { + + private static final String TAG = "TimeSafariApplication"; + + @Override + public void onCreate() { + super.onCreate(); + + Log.i(TAG, "Initializing TimeSafari notifications"); + + // Register native fetcher with application context + Context context = getApplicationContext(); + NativeNotificationContentFetcher fetcher = + new TimeSafariNativeFetcher(context); + DailyNotificationPlugin.setNativeFetcher(fetcher); + + Log.i(TAG, "Native fetcher registered"); + } +} diff --git a/android/app/src/main/java/app/timesafari/TimeSafariNativeFetcher.java b/android/app/src/main/java/app/timesafari/TimeSafariNativeFetcher.java new file mode 100644 index 00000000..6ebc688e --- /dev/null +++ b/android/app/src/main/java/app/timesafari/TimeSafariNativeFetcher.java @@ -0,0 +1,73 @@ +package app.timesafari; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.timesafari.dailynotification.FetchContext; +import com.timesafari.dailynotification.NativeNotificationContentFetcher; +import com.timesafari.dailynotification.NotificationContent; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher { + + private static final String TAG = "TimeSafariNativeFetcher"; + private final Context context; + + // Configuration from TypeScript (set via configure()) + private volatile String apiBaseUrl; + private volatile String activeDid; + private volatile String jwtToken; + + public TimeSafariNativeFetcher(Context context) { + this.context = context; + } + + @Override + public void configure(String apiBaseUrl, String activeDid, String jwtToken) { + this.apiBaseUrl = apiBaseUrl; + this.activeDid = activeDid; + this.jwtToken = jwtToken; + Log.i(TAG, "Fetcher configured with API: " + apiBaseUrl + ", DID: " + activeDid); + } + + @NonNull + @Override + public CompletableFuture> fetchContent(@NonNull FetchContext fetchContext) { + Log.d(TAG, "Fetching notification content, trigger: " + fetchContext.trigger); + + return CompletableFuture.supplyAsync(() -> { + try { + // TODO: Implement actual content fetching for TimeSafari + // This should query the TimeSafari API for notification content + // using the configured apiBaseUrl, activeDid, and jwtToken + + // For now, return a placeholder notification + long scheduledTime = fetchContext.scheduledTime != null + ? fetchContext.scheduledTime + : System.currentTimeMillis() + 60000; // 1 minute from now + + NotificationContent content = new NotificationContent( + "TimeSafari Update", + "Check your starred projects for updates!", + scheduledTime + ); + + List results = new ArrayList<>(); + results.add(content); + + Log.d(TAG, "Returning " + results.size() + " notification(s)"); + return results; + + } catch (Exception e) { + Log.e(TAG, "Fetch failed", e); + return Collections.emptyList(); + } + }); + } +} diff --git a/android/app/src/main/res/xml/network_security_config.xml b/android/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 00000000..485472dd --- /dev/null +++ b/android/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,12 @@ + + + + + + + + + localhost + 10.0.2.2 + + diff --git a/capacitor.config.ts b/capacitor.config.ts index 24ef38c6..d73665a8 100644 --- a/capacitor.config.ts +++ b/capacitor.config.ts @@ -44,6 +44,31 @@ const config: CapacitorConfig = { biometricTitle: 'Biometric login for TimeSafari' }, electronIsEncryption: false + }, + DailyNotification: { + debugMode: true, + enableNotifications: true, + timesafariConfig: { + activeDid: '', // Will be set dynamically from user's DID + endpoints: { + projectsLastUpdated: 'https://api.endorser.ch/api/v2/report/plansLastUpdatedBetween' + }, + starredProjectsConfig: { + enabled: true, + starredPlanHandleIds: [], + fetchInterval: '0 8 * * *' + } + }, + networkConfig: { + timeout: 30000, + retryAttempts: 3, + retryDelay: 1000 + }, + contentFetch: { + enabled: true, + schedule: '0 8 * * *', + fetchLeadTimeMinutes: 5 + } } }, ios: { diff --git a/src/services/notifications/NativeNotificationService.ts b/src/services/notifications/NativeNotificationService.ts index 9b14707e..0a80aff1 100644 --- a/src/services/notifications/NativeNotificationService.ts +++ b/src/services/notifications/NativeNotificationService.ts @@ -40,7 +40,10 @@ import { logger } from "@/utils/logger"; * - Native OS notification UI */ export class NativeNotificationService implements NotificationServiceInterface { - private readonly reminderId = "timesafari_daily_reminder"; + // IMPORTANT: ID must start with "daily_" for proper schedule rollover handling + // The plugin's scheduleNextNotification() preserves IDs starting with "daily_" + // but replaces others with random "daily_rollover_xxx" IDs, causing conflicts + private readonly reminderId = "daily_timesafari_reminder"; private readonly platformName = "native"; /**