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";
/**