refactor(android,ios): rename package com.timesafari to org.timesafari.dailynotification

- Android: move plugin source to org/timesafari/dailynotification, update
  namespace, manifest package, and all package/imports; change intent actions
  to org.timesafari.daily.NOTIFICATION and DISMISS
- iOS: update bundle IDs, BGTask identifiers, subsystem labels, and queue
  names in Plugin and Xcode projects
- Capacitor: update plugin class registration and appIds in configs
- Test apps (android-test-app, daily-notification-test, ios-test-app):
  applicationId/bundleId, manifests, ProGuard, scripts, and docs
- Docs: bulk update references; add CONSUMING_APP_MIGRATION_COM_TO_ORG.md
  for consuming app migration

BREAKING CHANGE: Consuming apps must update plugin class to
org.timesafari.dailynotification.DailyNotificationPlugin, manifest
receivers/actions, and iOS BGTask identifiers per migration doc.
This commit is contained in:
Jose Olarte III
2026-03-12 14:26:07 +08:00
parent b8d9b6247d
commit d8a0eaf413
171 changed files with 749 additions and 646 deletions

View File

@@ -1239,10 +1239,10 @@ dependencies {
-keep @androidx.room.Dao class *
# Plugin classes
-keep class com.timesafari.dailynotification.** { *; }
-keep class org.timesafari.dailynotification.** { *; }
# Capacitor plugin
-keep class com.timesafari.dailynotification.DailyNotificationPlugin { *; }
-keep class org.timesafari.dailynotification.DailyNotificationPlugin { *; }
# Encryption
-keep class javax.crypto.** { *; }

View File

@@ -653,7 +653,7 @@ public class MainActivity extends BridgeActivity {
{
"plugins": {
"DailyNotification": {
"class": "com.timesafari.dailynotification.DailyNotificationPlugin"
"class": "org.timesafari.dailynotification.DailyNotificationPlugin"
}
}
}
@@ -728,7 +728,7 @@ The Vue 3 test app uses a **project reference approach** for plugin integration:
{
"id": "DailyNotification",
"name": "DailyNotification",
"class": "com.timesafari.dailynotification.DailyNotificationPlugin"
"class": "org.timesafari.dailynotification.DailyNotificationPlugin"
}
]
```
@@ -1128,7 +1128,7 @@ npx cap sync android
#### AAR Duplicate Class Issues
```bash
# Problem: Duplicate class errors when integrating plugin AAR
# Error: "Duplicate class com.timesafari.dailynotification.BootReceiver found in modules"
# Error: "Duplicate class org.timesafari.dailynotification.BootReceiver found in modules"
# Root Cause: Plugin being included both as project reference and as AAR file
# Solution 1: Use Project Reference Approach (Recommended)

View File

@@ -564,12 +564,12 @@ await DailyNotification.updateDailyReminder('morning_checkin', {
<!-- NotifyReceiver for AlarmManager-based notifications -->
<!-- REQUIRED: Without this, alarms fire but notifications won't display -->
<receiver android:name="com.timesafari.dailynotification.NotifyReceiver"
<receiver android:name="org.timesafari.dailynotification.NotifyReceiver"
android:enabled="true"
android:exported="false">
</receiver>
<receiver android:name="com.timesafari.dailynotification.BootReceiver"
<receiver android:name="org.timesafari.dailynotification.BootReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
@@ -604,8 +604,8 @@ dependencies {
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.timesafari.dailynotification.content-fetch</string>
<string>com.timesafari.dailynotification.notification-delivery</string>
<string>org.timesafari.dailynotification.content-fetch</string>
<string>org.timesafari.dailynotification.notification-delivery</string>
</array>
```

View File

@@ -14,7 +14,7 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
android {
namespace "com.timesafari.dailynotification.plugin"
namespace "org.timesafari.dailynotification.plugin"
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
defaultConfig {

View File

@@ -2,7 +2,7 @@
# These rules are applied to consuming apps when they use this plugin
# Keep plugin classes
-keep class com.timesafari.dailynotification.** { *; }
-keep class org.timesafari.dailynotification.** { *; }
# Keep Capacitor plugin interface
-keep class com.getcapacitor.Plugin { *; }

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.timesafari.dailynotification.plugin">
package="org.timesafari.dailynotification.plugin">
<!-- Plugin receivers are declared in consuming app's manifest -->
<!-- This manifest is optional and mainly for library metadata -->

View File

@@ -2,7 +2,7 @@
{
"pkg": "@timesafari/daily-notification-plugin",
"name": "DailyNotification",
"classpath": "com.timesafari.dailynotification.DailyNotificationPlugin"
"classpath": "org.timesafari.dailynotification.DailyNotificationPlugin"
}
]

View File

@@ -1,4 +1,4 @@
package com.timesafari.dailynotification
package org.timesafari.dailynotification
import android.content.BroadcastReceiver
import android.content.Context

View File

@@ -1,4 +1,4 @@
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -43,7 +43,7 @@ public class ChannelManager {
Log.d(TAG, "Ensuring notification channel exists");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = notificationManager.getNotificationChannel(com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
NotificationChannel channel = notificationManager.getNotificationChannel(org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
if (channel == null) {
Log.d(TAG, "Creating notification channel");
@@ -72,7 +72,7 @@ public class ChannelManager {
public boolean isChannelEnabled() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = notificationManager.getNotificationChannel(com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
NotificationChannel channel = notificationManager.getNotificationChannel(org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
if (channel == null) {
Log.w(TAG, "Channel does not exist");
return false;
@@ -99,7 +99,7 @@ public class ChannelManager {
public int getChannelImportance() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = notificationManager.getNotificationChannel(com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
NotificationChannel channel = notificationManager.getNotificationChannel(org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
if (channel != null) {
return channel.getImportance();
}
@@ -117,7 +117,7 @@ public class ChannelManager {
* @return true if settings intent was launched, false otherwise
*/
public boolean openChannelSettings() {
return openChannelSettings(com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
return openChannelSettings(org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
}
/**
@@ -142,7 +142,7 @@ public class ChannelManager {
try {
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName())
.putExtra(Settings.EXTRA_CHANNEL_ID, channelId != null ? channelId : com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID)
.putExtra(Settings.EXTRA_CHANNEL_ID, channelId != null ? channelId : org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
@@ -180,11 +180,11 @@ public class ChannelManager {
private void createDefaultChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID,
com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_NAME,
org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID,
org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_NAME,
NotificationManager.IMPORTANCE_HIGH
);
channel.setDescription(com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_DESCRIPTION);
channel.setDescription(org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_DESCRIPTION);
channel.enableLights(true);
channel.enableVibration(true);
channel.setShowBadge(true);
@@ -200,7 +200,7 @@ public class ChannelManager {
* @return the default channel ID
*/
public String getDefaultChannelId() {
return com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID;
return org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID;
}
/**
@@ -209,7 +209,7 @@ public class ChannelManager {
public void logChannelStatus() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = notificationManager.getNotificationChannel(com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
NotificationChannel channel = notificationManager.getNotificationChannel(org.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
if (channel != null) {
Log.i(TAG, "Channel Status - ID: " + channel.getId() +
", Importance: " + channel.getImportance() +

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification
package org.timesafari.dailynotification
/**
* Centralized constants for Daily Notification Plugin
@@ -56,7 +56,7 @@ object DailyNotificationConstants {
* Action string for notification broadcast intents
* Used by AlarmManager PendingIntents
*/
const val ACTION_NOTIFICATION = "com.timesafari.daily.NOTIFICATION"
const val ACTION_NOTIFICATION = "org.timesafari.daily.NOTIFICATION"
// ============================================================
// Intent Extras Keys

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.util.Log;

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.util.Log;

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.app.AlarmManager;
import android.app.PendingIntent;

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.content.Context;
import android.util.Log;

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.content.Context;
import android.util.Log;
@@ -42,7 +42,7 @@ public class DailyNotificationFetcher {
private final Context context;
private final DailyNotificationStorage storage; // Deprecated path (kept for transitional read paths)
private final com.timesafari.dailynotification.storage.DailyNotificationStorageRoom roomStorage; // Preferred path
private final org.timesafari.dailynotification.storage.DailyNotificationStorageRoom roomStorage; // Preferred path
private final WorkManager workManager;
// ETag manager for efficient fetching
@@ -60,7 +60,7 @@ public class DailyNotificationFetcher {
public DailyNotificationFetcher(Context context,
DailyNotificationStorage storage,
com.timesafari.dailynotification.storage.DailyNotificationStorageRoom roomStorage) {
org.timesafari.dailynotification.storage.DailyNotificationStorageRoom roomStorage) {
this.context = context;
this.storage = storage;
this.roomStorage = roomStorage;
@@ -220,8 +220,8 @@ public class DailyNotificationFetcher {
return;
}
try {
com.timesafari.dailynotification.entities.NotificationContentEntity entity =
new com.timesafari.dailynotification.entities.NotificationContentEntity(
org.timesafari.dailynotification.entities.NotificationContentEntity entity =
new org.timesafari.dailynotification.entities.NotificationContentEntity(
content.getId() != null ? content.getId() : java.util.UUID.randomUUID().toString(),
"1.0.0",
null,

View File

@@ -9,7 +9,7 @@
* @created 2025-10-03 06:53:30 UTC
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.util.Log;
import android.content.Context;

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.content.Context;
import android.util.Log;

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.content.ContentValues;
import android.content.Context;

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.content.Context;
import android.os.Debug;

View File

@@ -1,4 +1,4 @@
package com.timesafari.dailynotification
package org.timesafari.dailynotification
import android.Manifest
import android.app.Activity
@@ -18,7 +18,7 @@ import androidx.work.WorkManager
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.Data
import java.util.concurrent.TimeUnit
import com.timesafari.dailynotification.DailyNotificationFetchWorker
import org.timesafari.dailynotification.DailyNotificationFetchWorker
import com.getcapacitor.JSObject
import com.getcapacitor.Plugin
import com.getcapacitor.PluginCall
@@ -585,7 +585,7 @@ open class DailyNotificationPlugin : Plugin() {
CoroutineScope(Dispatchers.IO).launch {
try {
val config = com.timesafari.dailynotification.entities.NotificationConfigEntity(
val config = org.timesafari.dailynotification.entities.NotificationConfigEntity(
configId, null, "native_fetcher", "config", configValue, "json"
)
getDatabase().notificationConfigDao().insertConfig(config)
@@ -2153,7 +2153,7 @@ open class DailyNotificationPlugin : Plugin() {
?: return@launch call.reject("Config value is required")
val configDataType = configJson.getString("configDataType", "string")
val entity = com.timesafari.dailynotification.entities.NotificationConfigEntity(
val entity = org.timesafari.dailynotification.entities.NotificationConfigEntity(
id, timesafariDid, configType, configKey, configValue, configDataType
)
@@ -2285,7 +2285,7 @@ open class DailyNotificationPlugin : Plugin() {
}
}
private fun configToJson(config: com.timesafari.dailynotification.entities.NotificationConfigEntity): JSObject {
private fun configToJson(config: org.timesafari.dailynotification.entities.NotificationConfigEntity): JSObject {
return JSObject().apply {
put("id", config.id)
put("timesafariDid", config.timesafariDid)
@@ -2474,7 +2474,7 @@ object TestDataHelper {
suspend fun injectInvalidNotificationData(database: DailyNotificationDatabase): Boolean {
return try {
val invalidNotification =
com.timesafari.dailynotification.entities.NotificationContentEntity()
org.timesafari.dailynotification.entities.NotificationContentEntity()
invalidNotification.id = "" // Empty ID - should be skipped by recovery
invalidNotification.title = "Test Invalid Notification"
invalidNotification.body = "This has an empty ID"
@@ -2683,7 +2683,7 @@ object ScheduleHelper {
// Persist title/body for this scheduleId so rollover and post-reboot resolve user content
// (see plugin-feedback-android-rollover-double-fire-and-user-content)
try {
val entity = com.timesafari.dailynotification.entities.NotificationContentEntity(
val entity = org.timesafari.dailynotification.entities.NotificationContentEntity(
scheduleId,
"1.3.1",
null,

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.content.BroadcastReceiver;
import android.content.Context;

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -59,7 +59,7 @@ public class DailyNotificationReceiver extends BroadcastReceiver {
return;
}
if ("com.timesafari.daily.NOTIFICATION".equals(action)) {
if ("org.timesafari.daily.NOTIFICATION".equals(action)) {
// Parse intent and enqueue work - keep receiver ultra-light
String notificationId = intent.getStringExtra(EXTRA_NOTIFICATION_ID);
if (notificationId == null) {
@@ -72,7 +72,7 @@ public class DailyNotificationReceiver extends BroadcastReceiver {
enqueueNotificationWork(context, notificationId, intent);
Log.d(TAG, "DN|RECEIVE_OK enqueued=" + notificationId);
} else if ("com.timesafari.daily.DISMISS".equals(action)) {
} else if ("org.timesafari.daily.DISMISS".equals(action)) {
// Handle dismissal - also lightweight
String notificationId = intent.getStringExtra(EXTRA_NOTIFICATION_ID);
if (notificationId != null) {
@@ -362,7 +362,7 @@ public class DailyNotificationReceiver extends BroadcastReceiver {
// Add dismiss action
Intent dismissIntent = new Intent(context, DailyNotificationReceiver.class);
dismissIntent.setAction("com.timesafari.daily.DISMISS");
dismissIntent.setAction("org.timesafari.daily.DISMISS");
dismissIntent.putExtra(EXTRA_NOTIFICATION_ID, content.getId());
PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(
@@ -432,8 +432,8 @@ public class DailyNotificationReceiver extends BroadcastReceiver {
}
// Create config for next notification
com.timesafari.dailynotification.UserNotificationConfig config =
new com.timesafari.dailynotification.UserNotificationConfig(
org.timesafari.dailynotification.UserNotificationConfig config =
new org.timesafari.dailynotification.UserNotificationConfig(
true, // enabled
cronExpression,
content.getTitle() != null ? content.getTitle() : "Daily Notification",
@@ -444,14 +444,14 @@ public class DailyNotificationReceiver extends BroadcastReceiver {
);
// Use centralized scheduling function with ROLLOVER_ON_FIRE source
com.timesafari.dailynotification.NotifyReceiver.scheduleExactNotification(
org.timesafari.dailynotification.NotifyReceiver.scheduleExactNotification(
context,
nextScheduledTime,
config,
false, // isStaticReminder
null, // reminderId
scheduleId,
com.timesafari.dailynotification.ScheduleSource.ROLLOVER_ON_FIRE,
org.timesafari.dailynotification.ScheduleSource.ROLLOVER_ON_FIRE,
false // skipPendingIntentIdempotence rollover path does not skip
);

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.content.Context;
import android.util.Log;

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -157,8 +157,8 @@ public class DailyNotificationScheduler {
// Create intent for the notification; setPackage ensures AlarmManager delivery on all OEMs
Intent intent = new Intent(context, DailyNotificationReceiver.class);
intent.setPackage(context.getPackageName());
intent.setAction(com.timesafari.dailynotification.DailyNotificationConstants.ACTION_NOTIFICATION);
intent.putExtra(com.timesafari.dailynotification.DailyNotificationConstants.EXTRA_NOTIFICATION_ID, content.getId());
intent.setAction(org.timesafari.dailynotification.DailyNotificationConstants.ACTION_NOTIFICATION);
intent.putExtra(org.timesafari.dailynotification.DailyNotificationConstants.EXTRA_NOTIFICATION_ID, content.getId());
// Check if this is a static reminder
if (content.getId().startsWith("reminder_") || content.getId().contains("_reminder")) {
@@ -481,7 +481,7 @@ public class DailyNotificationScheduler {
try {
Log.d(TAG, "Scheduling test alarm in " + secondsFromNow + " seconds");
// Delegate to NotifyReceiver.testAlarm()
com.timesafari.dailynotification.NotifyReceiver.Companion.testAlarm(context, secondsFromNow);
org.timesafari.dailynotification.NotifyReceiver.Companion.testAlarm(context, secondsFromNow);
Log.i(TAG, "Test alarm scheduled successfully");
} catch (Exception e) {
Log.e(TAG, "Error scheduling test alarm", e);
@@ -591,7 +591,7 @@ public class DailyNotificationScheduler {
// Note: NotifyReceiver.isAlarmScheduled is a Kotlin companion object function with default parameters
// From Java, we need to use Companion and provide explicit values (null is acceptable for optional params)
// Kotlin Long? maps to java.lang.Long in Java
return com.timesafari.dailynotification.NotifyReceiver.Companion.isAlarmScheduled(
return org.timesafari.dailynotification.NotifyReceiver.Companion.isAlarmScheduled(
context,
scheduleId,
triggerAtMillis
@@ -624,7 +624,7 @@ public class DailyNotificationScheduler {
// Delegate to NotifyReceiver which checks actual AlarmManager state
// Note: NotifyReceiver.getNextAlarmTime is a Kotlin companion object function
// Kotlin Long? maps to java.lang.Long in Java
return com.timesafari.dailynotification.NotifyReceiver.Companion.getNextAlarmTime(context);
return org.timesafari.dailynotification.NotifyReceiver.Companion.getNextAlarmTime(context);
} catch (Exception e) {
Log.e(TAG, "Error getting next alarm time", e);
return null;

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.content.Context;
import android.content.SharedPreferences;

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.content.Context;
import android.content.SharedPreferences;

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -30,9 +30,9 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.ConcurrentHashMap;
import com.timesafari.dailynotification.storage.DailyNotificationStorageRoom;
import com.timesafari.dailynotification.entities.NotificationContentEntity;
import com.timesafari.dailynotification.DailyNotificationFetcher;
import org.timesafari.dailynotification.storage.DailyNotificationStorageRoom;
import org.timesafari.dailynotification.entities.NotificationContentEntity;
import org.timesafari.dailynotification.DailyNotificationFetcher;
/**
* WorkManager worker for processing daily notifications
@@ -382,7 +382,7 @@ public class DailyNotificationWorker extends Worker {
// Create one-time work request
androidx.work.OneTimeWorkRequest softRefetchWork = new androidx.work.OneTimeWorkRequest.Builder(
com.timesafari.dailynotification.SoftRefetchWorker.class)
org.timesafari.dailynotification.SoftRefetchWorker.class)
.setConstraints(constraints)
.setInputData(inputData)
.setInitialDelay(softRefetchTime - System.currentTimeMillis(), java.util.concurrent.TimeUnit.MILLISECONDS)
@@ -469,7 +469,7 @@ public class DailyNotificationWorker extends Worker {
// Add action buttons
// 1. Dismiss action
Intent dismissIntent = new Intent(getApplicationContext(), DailyNotificationReceiver.class);
dismissIntent.setAction("com.timesafari.daily.DISMISS");
dismissIntent.setAction("org.timesafari.daily.DISMISS");
dismissIntent.putExtra("notification_id", content.getId());
PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(
@@ -550,14 +550,14 @@ public class DailyNotificationWorker extends Worker {
// When firing run used daily_rollover_* id, resolve canonical schedule so we still apply rolloverIntervalMinutes
String logicalScheduleIdForRollover = scheduleIdForRollover;
if (scheduleIdForRollover != null && scheduleIdForRollover.startsWith("daily_rollover_")) {
com.timesafari.dailynotification.Schedule canonical = com.timesafari.dailynotification.ScheduleHelper.getCanonicalRolloverScheduleBlocking(getApplicationContext());
org.timesafari.dailynotification.Schedule canonical = org.timesafari.dailynotification.ScheduleHelper.getCanonicalRolloverScheduleBlocking(getApplicationContext());
if (canonical != null) {
logicalScheduleIdForRollover = canonical.getId();
}
}
Integer rolloverMinutes = null;
if (logicalScheduleIdForRollover != null && !logicalScheduleIdForRollover.isEmpty()) {
com.timesafari.dailynotification.Schedule s = com.timesafari.dailynotification.ScheduleHelper.getScheduleBlocking(getApplicationContext(), logicalScheduleIdForRollover);
org.timesafari.dailynotification.Schedule s = org.timesafari.dailynotification.ScheduleHelper.getScheduleBlocking(getApplicationContext(), logicalScheduleIdForRollover);
if (s != null && s.getRolloverIntervalMinutes() != null && s.getRolloverIntervalMinutes() > 0) {
rolloverMinutes = s.getRolloverIntervalMinutes();
Log.d(TAG, "DN|ROLLOVER_INTERVAL scheduleId=" + logicalScheduleIdForRollover + " minutes=" + rolloverMinutes);
@@ -621,8 +621,8 @@ public class DailyNotificationWorker extends Worker {
}
// Create config for next notification
com.timesafari.dailynotification.UserNotificationConfig config =
new com.timesafari.dailynotification.UserNotificationConfig(
org.timesafari.dailynotification.UserNotificationConfig config =
new org.timesafari.dailynotification.UserNotificationConfig(
true, // enabled
cronExpression,
content.getTitle() != null ? content.getTitle() : "Daily Notification",
@@ -634,18 +634,18 @@ public class DailyNotificationWorker extends Worker {
// Use centralized scheduling function with ROLLOVER_ON_FIRE source
Log.d(TAG, "DN|ROLLOVER next=" + nextScheduledTime + " scheduleId=" + scheduleId + " static=" + preserveStaticReminder);
com.timesafari.dailynotification.NotifyReceiver.scheduleExactNotification(
org.timesafari.dailynotification.NotifyReceiver.scheduleExactNotification(
getApplicationContext(),
nextScheduledTime,
config,
preserveStaticReminder, // isStaticReminder preserve so next run keeps title/body
preserveStaticReminder ? scheduleId : null, // reminderId
scheduleId,
com.timesafari.dailynotification.ScheduleSource.ROLLOVER_ON_FIRE,
org.timesafari.dailynotification.ScheduleSource.ROLLOVER_ON_FIRE,
false // skipPendingIntentIdempotence rollover path does not skip
);
if (scheduleId != null && !scheduleId.startsWith("daily_rollover_")) {
com.timesafari.dailynotification.ScheduleHelper.updateScheduleNextRunTimeBlocking(
org.timesafari.dailynotification.ScheduleHelper.updateScheduleNextRunTimeBlocking(
getApplicationContext(), scheduleId, content.getScheduledTime(), nextScheduledTime);
}
// Log next scheduled time in readable format
@@ -693,8 +693,8 @@ public class DailyNotificationWorker extends Worker {
private NotificationContent getContentByScheduleId(String scheduleId) {
if (scheduleId == null || scheduleId.isEmpty()) return null;
try {
com.timesafari.dailynotification.DailyNotificationDatabase db =
com.timesafari.dailynotification.DailyNotificationDatabase.getInstance(getApplicationContext());
org.timesafari.dailynotification.DailyNotificationDatabase db =
org.timesafari.dailynotification.DailyNotificationDatabase.getInstance(getApplicationContext());
NotificationContentEntity entity = db.notificationContentDao().getNotificationById(scheduleId);
if (entity == null) {
entity = db.notificationContentDao().getNotificationById("daily_" + scheduleId);
@@ -716,8 +716,8 @@ public class DailyNotificationWorker extends Worker {
try {
DailyNotificationStorageRoom room = new DailyNotificationStorageRoom(getApplicationContext());
// Use unified database (Kotlin schema with Java entities)
com.timesafari.dailynotification.DailyNotificationDatabase db =
com.timesafari.dailynotification.DailyNotificationDatabase.getInstance(getApplicationContext());
org.timesafari.dailynotification.DailyNotificationDatabase db =
org.timesafari.dailynotification.DailyNotificationDatabase.getInstance(getApplicationContext());
NotificationContentEntity entity = db.notificationContentDao().getNotificationById(notificationId);
if (entity != null) {
return mapEntityToContent(entity);

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
/**
* Information about a scheduled daily reminder

View File

@@ -9,7 +9,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.content.Context;
import android.content.SharedPreferences;

View File

@@ -1,15 +1,15 @@
package com.timesafari.dailynotification
package org.timesafari.dailynotification
import android.content.Context
import androidx.room.*
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.timesafari.dailynotification.entities.NotificationContentEntity
import com.timesafari.dailynotification.entities.NotificationDeliveryEntity
import com.timesafari.dailynotification.entities.NotificationConfigEntity
import com.timesafari.dailynotification.dao.NotificationContentDao
import com.timesafari.dailynotification.dao.NotificationDeliveryDao
import com.timesafari.dailynotification.dao.NotificationConfigDao
import org.timesafari.dailynotification.entities.NotificationContentEntity
import org.timesafari.dailynotification.entities.NotificationDeliveryEntity
import org.timesafari.dailynotification.entities.NotificationConfigEntity
import org.timesafari.dailynotification.dao.NotificationContentDao
import org.timesafari.dailynotification.dao.NotificationDeliveryDao
import org.timesafari.dailynotification.dao.NotificationConfigDao
/**
* Unified SQLite schema for Daily Notification Plugin

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.app.AlarmManager;
import android.content.Context;

View File

@@ -9,7 +9,7 @@
* @created 2025-10-03 06:53:30 UTC
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.content.Context;
import android.util.Log;

View File

@@ -11,7 +11,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

View File

@@ -1,4 +1,4 @@
package com.timesafari.dailynotification
package org.timesafari.dailynotification
import android.content.Context
import android.os.SystemClock
@@ -203,7 +203,7 @@ class FetchWorker(
val notificationId = "notify_$notificationTime"
val (title, body) = parsePayload(payload)
val entity = com.timesafari.dailynotification.entities.NotificationContentEntity(
val entity = org.timesafari.dailynotification.entities.NotificationContentEntity(
notificationId,
"1.3.3", // Plugin version
null, // timesafariDid - can be set if available

View File

@@ -15,7 +15,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import androidx.annotation.NonNull;
import java.util.List;

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.util.Log;
import java.util.UUID;

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.app.NotificationManager;
import android.content.Context;
@@ -518,13 +518,13 @@ public class NotificationStatusChecker {
* @param database Database instance for querying schedules and history
* @return JSObject containing notification status (schedules, last notification time, etc.)
*/
public JSObject getNotificationStatus(com.timesafari.dailynotification.DailyNotificationDatabase database) {
public JSObject getNotificationStatus(org.timesafari.dailynotification.DailyNotificationDatabase database) {
try {
Log.d(TAG, "DN|NOTIFICATION_STATUS_START");
// Delegate to Kotlin helper function (uses runBlocking internally)
// This is safe because status checks are quick operations
return com.timesafari.dailynotification.NotificationStatusHelper.getNotificationStatusBlocking(database);
return org.timesafari.dailynotification.NotificationStatusHelper.getNotificationStatusBlocking(database);
} catch (Exception e) {
Log.e(TAG, "DN|NOTIFICATION_STATUS_ERR err=" + e.getMessage(), e);

View File

@@ -1,4 +1,4 @@
package com.timesafari.dailynotification
package org.timesafari.dailynotification
import android.app.AlarmManager
import android.app.AlarmManager.AlarmClockInfo
@@ -148,9 +148,9 @@ class NotifyReceiver : BroadcastReceiver() {
val notificationId = reminderId ?: "notify_${triggerAtMillis}"
val requestCode = getRequestCode(stableScheduleId)
val checkIntent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
val checkIntent = Intent(context, org.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
setPackage(context.packageName)
action = "com.timesafari.daily.NOTIFICATION"
action = "org.timesafari.daily.NOTIFICATION"
}
// IDEMPOTENCE CHECK: Verify no existing alarm for this trigger time before scheduling.
@@ -254,8 +254,8 @@ class NotifyReceiver : BroadcastReceiver() {
// Always create a notification content entity for recovery tracking
// Phase 1: Recovery needs NotificationContentEntity to detect missed notifications
val roomStorage = com.timesafari.dailynotification.storage.DailyNotificationStorageRoom(context)
val entity = com.timesafari.dailynotification.entities.NotificationContentEntity(
val roomStorage = org.timesafari.dailynotification.storage.DailyNotificationStorageRoom(context)
val entity = org.timesafari.dailynotification.entities.NotificationContentEntity(
notificationId,
"1.3.3", // Plugin version
null, // timesafariDid - can be set if available
@@ -288,9 +288,9 @@ class NotifyReceiver : BroadcastReceiver() {
// FIX: Use DailyNotificationReceiver (registered in manifest) instead of NotifyReceiver
// FIX: Set action to match manifest registration; setPackage() ensures AlarmManager
// delivery reaches this app on all OEMs (see daily-notification-plugin-android-receiver-issue)
val intent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
val intent = Intent(context, org.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
setPackage(context.packageName)
action = "com.timesafari.daily.NOTIFICATION" // Must match manifest intent-filter action
action = "org.timesafari.daily.NOTIFICATION" // Must match manifest intent-filter action
putExtra("notification_id", notificationId) // DailyNotificationReceiver expects this extra
putExtra("schedule_id", stableScheduleId) // Add stable scheduleId for tracking
// Also preserve original extras for backward compatibility if needed
@@ -484,9 +484,9 @@ class NotifyReceiver : BroadcastReceiver() {
fun cancelNotification(context: Context, scheduleId: String? = null, triggerAtMillis: Long? = null) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
// FIX: Use DailyNotificationReceiver to match what was scheduled
val intent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
val intent = Intent(context, org.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
setPackage(context.packageName)
action = "com.timesafari.daily.NOTIFICATION"
action = "org.timesafari.daily.NOTIFICATION"
}
val requestCode = when {
scheduleId != null -> getRequestCode(scheduleId)
@@ -540,9 +540,9 @@ class NotifyReceiver : BroadcastReceiver() {
*/
fun isAlarmScheduled(context: Context, scheduleId: String? = null, triggerAtMillis: Long? = null): Boolean {
// FIX: Use DailyNotificationReceiver to match what was scheduled
val intent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
val intent = Intent(context, org.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
setPackage(context.packageName)
action = "com.timesafari.daily.NOTIFICATION"
action = "org.timesafari.daily.NOTIFICATION"
}
val requestCode = when {
scheduleId != null -> getRequestCode(scheduleId)

View File

@@ -1,4 +1,4 @@
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.app.AlarmManager;
import android.app.PendingIntent;

View File

@@ -8,7 +8,7 @@
* @version 2.0.0 - Modular Architecture
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.Manifest;
import android.content.Context;
@@ -87,7 +87,7 @@ public class PermissionManager {
androidx.core.app.ActivityCompat.requestPermissions(
activity,
new String[]{android.Manifest.permission.POST_NOTIFICATIONS},
com.timesafari.dailynotification.DailyNotificationConstants.PERMISSION_REQUEST_CODE // Centralized constant
org.timesafari.dailynotification.DailyNotificationConstants.PERMISSION_REQUEST_CODE // Centralized constant
);
Log.d(TAG, "Permission dialog shown, waiting for user response");
@@ -125,7 +125,7 @@ public class PermissionManager {
*
* @return PermissionStatus with all permission states
*/
public com.timesafari.dailynotification.PermissionStatus getPermissionStatus() {
public org.timesafari.dailynotification.PermissionStatus getPermissionStatus() {
boolean postNotificationsGranted = false;
boolean exactAlarmsGranted = false;
boolean notificationsEnabledAtOsLevel = false;
@@ -168,7 +168,7 @@ public class PermissionManager {
batteryOptimizationsIgnored = true; // Pre-Android 6, no battery optimization restrictions
}
return new com.timesafari.dailynotification.PermissionStatus(
return new org.timesafari.dailynotification.PermissionStatus(
postNotificationsGranted,
exactAlarmsGranted,
batteryOptimizationsIgnored,
@@ -187,7 +187,7 @@ public class PermissionManager {
try {
Log.d(TAG, "Checking permission status");
com.timesafari.dailynotification.PermissionStatus status = getPermissionStatus();
org.timesafari.dailynotification.PermissionStatus status = getPermissionStatus();
JSObject result = status.toJSObject();
result.put("success", true);

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification
package org.timesafari.dailynotification
/**
* Comprehensive permission status model

View File

@@ -1,4 +1,4 @@
package com.timesafari.dailynotification
package org.timesafari.dailynotification
import android.app.PendingIntent
import android.content.Context
@@ -280,7 +280,7 @@ class ReactivationManager(private val context: Context) {
db.notificationContentDao().updateNotification(existing)
} else {
// Create new notification content entry for missed alarm
val notification = com.timesafari.dailynotification.entities.NotificationContentEntity(
val notification = org.timesafari.dailynotification.entities.NotificationContentEntity(
notificationId,
"1.3.3", // Plugin version
null, // timesafariDid
@@ -479,9 +479,9 @@ class ReactivationManager(private val context: Context) {
private fun alarmsExist(): Boolean {
return try {
// Check if any PendingIntent for our receiver exists (must match NotifyReceiver schedule path)
val intent = Intent(context, com.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
val intent = Intent(context, org.timesafari.dailynotification.DailyNotificationReceiver::class.java).apply {
setPackage(context.packageName)
action = "com.timesafari.daily.NOTIFICATION"
action = "org.timesafari.daily.NOTIFICATION"
}
val pendingIntent = PendingIntent.getBroadcast(
context,
@@ -1050,7 +1050,7 @@ class ReactivationManager(private val context: Context) {
db.notificationContentDao().updateNotification(existing)
} else {
// Create new notification content entry for missed alarm
val notification = com.timesafari.dailynotification.entities.NotificationContentEntity(
val notification = org.timesafari.dailynotification.entities.NotificationContentEntity(
notificationId,
"1.3.3", // Plugin version
null, // timesafariDid

View File

@@ -11,7 +11,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

View File

@@ -8,7 +8,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.content.Context;
import android.os.Trace;

View File

@@ -24,7 +24,7 @@
* @version 1.0.0
*/
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.content.Context;
import android.content.SharedPreferences;

View File

@@ -9,10 +9,10 @@
* @since 2025-10-20
*/
package com.timesafari.dailynotification.dao;
package org.timesafari.dailynotification.dao;
import androidx.room.*;
import com.timesafari.dailynotification.entities.NotificationConfigEntity;
import org.timesafari.dailynotification.entities.NotificationConfigEntity;
import java.util.List;

View File

@@ -9,10 +9,10 @@
* @since 2025-10-20
*/
package com.timesafari.dailynotification.dao;
package org.timesafari.dailynotification.dao;
import androidx.room.*;
import com.timesafari.dailynotification.entities.NotificationContentEntity;
import org.timesafari.dailynotification.entities.NotificationContentEntity;
import java.util.List;

View File

@@ -9,10 +9,10 @@
* @since 2025-10-20
*/
package com.timesafari.dailynotification.dao;
package org.timesafari.dailynotification.dao;
import androidx.room.*;
import com.timesafari.dailynotification.entities.NotificationDeliveryEntity;
import org.timesafari.dailynotification.entities.NotificationDeliveryEntity;
import java.util.List;

View File

@@ -9,7 +9,7 @@
* @since 2025-10-20
*/
package com.timesafari.dailynotification.entities;
package org.timesafari.dailynotification.entities;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;

View File

@@ -9,7 +9,7 @@
* @since 2025-10-20
*/
package com.timesafari.dailynotification.entities;
package org.timesafari.dailynotification.entities;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;

View File

@@ -9,7 +9,7 @@
* @since 2025-10-20
*/
package com.timesafari.dailynotification.entities;
package org.timesafari.dailynotification.entities;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;

View File

@@ -9,18 +9,18 @@
* @since 2025-10-20
*/
package com.timesafari.dailynotification.storage;
package org.timesafari.dailynotification.storage;
import android.content.Context;
import android.util.Log;
import com.timesafari.dailynotification.DailyNotificationDatabase;
import com.timesafari.dailynotification.dao.NotificationContentDao;
import com.timesafari.dailynotification.dao.NotificationDeliveryDao;
import com.timesafari.dailynotification.dao.NotificationConfigDao;
import com.timesafari.dailynotification.entities.NotificationContentEntity;
import com.timesafari.dailynotification.entities.NotificationDeliveryEntity;
import com.timesafari.dailynotification.entities.NotificationConfigEntity;
import org.timesafari.dailynotification.DailyNotificationDatabase;
import org.timesafari.dailynotification.dao.NotificationContentDao;
import org.timesafari.dailynotification.dao.NotificationDeliveryDao;
import org.timesafari.dailynotification.dao.NotificationConfigDao;
import org.timesafari.dailynotification.entities.NotificationContentEntity;
import org.timesafari.dailynotification.entities.NotificationDeliveryEntity;
import org.timesafari.dailynotification.entities.NotificationConfigEntity;
import java.util.List;
import java.util.concurrent.CompletableFuture;

View File

@@ -9,7 +9,7 @@
* @since 2025-12-22
*/
package com.timesafari.dailynotification
package org.timesafari.dailynotification
import android.content.Context
import androidx.test.core.app.ApplicationProvider

View File

@@ -12,7 +12,7 @@
* @since 2025-12-22
*/
package com.timesafari.dailynotification
package org.timesafari.dailynotification
import android.content.Context
import androidx.room.Room

View File

@@ -1,7 +1,7 @@
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.timesafari.dailynotification',
appId: 'org.timesafari.dailynotification',
appName: 'DailyNotification Test App',
webDir: 'www',
server: {

View File

@@ -1,6 +1,6 @@
[
{
"name": "DailyNotification",
"class": "com.timesafari.dailynotification.DailyNotificationPlugin"
"class": "org.timesafari.dailynotification.DailyNotificationPlugin"
}
]

View File

@@ -38,7 +38,7 @@ pnpm add @timesafari/daily-notification-plugin
```xml
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.timesafari.dailynotification.fetch</string>
<string>org.timesafari.dailynotification.fetch</string>
</array>
```
@@ -49,7 +49,7 @@ import BackgroundTasks
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.timesafari.dailynotification.fetch",
BGTaskScheduler.shared.register(forTaskWithIdentifier: "org.timesafari.dailynotification.fetch",
using: nil) { task in
// Handle background fetch task
}

View File

@@ -50,7 +50,7 @@ fi
xcrun simctl install "$SIMULATOR_ID" "$APP_PATH"
# Launch app
xcrun simctl launch "$SIMULATOR_ID" com.timesafari.dailynotification.test
xcrun simctl launch "$SIMULATOR_ID" org.timesafari.dailynotification.test
```
**Result:** ✅ Simulator now boots and app launches automatically

View File

@@ -94,7 +94,7 @@ po UNUserNotificationCenter.current().pendingNotificationRequests()
po await UNUserNotificationCenter.current().notificationSettings()
// Manually trigger BGTask (Simulator only)
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.timesafari.dailynotification.fetch"]
```
---

View File

@@ -196,7 +196,7 @@ DailyNotificationScheduler: Scheduling notification: [id]
**Solution:** Use simulator-only LLDB command:
```swift
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.timesafari.dailynotification.fetch"]
```
### Notifications Not Delivering

View File

@@ -169,7 +169,7 @@ The plugin class did NOT conform to `CAPBridgedPlugin` protocol, which is requir
**Solution Implemented (2025-11-13):**
1. **Added `CAPBridgedPlugin` conformance** via `@objc` extension:
- Implemented `identifier` property (returns `"com.timesafari.dailynotification"`)
- Implemented `identifier` property (returns `"org.timesafari.dailynotification"`)
- Implemented `jsName` property (returns `"DailyNotification"`)
- Implemented `pluginMethods` property (returns array of all `@objc` methods)
@@ -220,7 +220,7 @@ The plugin class did NOT conform to `CAPBridgedPlugin` protocol, which is requir
- Added diagnostic check to verify class is in `objc_getClassList()`
2. **Add CAPBridgedPlugin conformance** via `@objc` extension:
- Implemented `identifier` property (returns `"com.timesafari.dailynotification"`)
- Implemented `identifier` property (returns `"org.timesafari.dailynotification"`)
- Implemented `jsName` property (returns `"DailyNotification"`)
- Implemented `pluginMethods` property (returns array of all `@objc` methods)
@@ -750,7 +750,7 @@ A "successful run" is defined as: BGTask handler invoked, content fetch complete
3. **Serial Queue Pattern (Alternative):**
```swift
private let stateQueue = DispatchQueue(label: "com.timesafari.dailynotification.state", attributes: .serial)
private let stateQueue = DispatchQueue(label: "org.timesafari.dailynotification.state", attributes: .serial)
```
4. **Enforcement:**
@@ -1365,8 +1365,8 @@ scripts/
- **Lesson:** Verify actual state, not just command success
5. **Bundle Identifier Mismatch:**
- **Issue:** Script was using `com.timesafari.dailynotification.test` but actual bundle ID is `com.timesafari.dailynotification`
- **Fix:** Updated all launch commands to use correct bundle ID `com.timesafari.dailynotification`
- **Issue:** Script was using `org.timesafari.dailynotification.test` but actual bundle ID is `org.timesafari.dailynotification`
- **Fix:** Updated all launch commands to use correct bundle ID `org.timesafari.dailynotification`
- **Root Cause:** Project file has `.test` suffix but Info.plist resolves to base bundle ID
- **Files Affected:** `scripts/build-ios-test-app.sh`
- **Lesson:** Always verify actual bundle ID from installed app, not just project settings; bundle ID resolution can differ from project settings
@@ -1424,7 +1424,7 @@ scripts/
6. **Permission Reset for Testing:**
- **Issue:** Simulator permissions persist across app launches; need to reset for testing
- **Fix:** Use `xcrun simctl privacy booted reset all <bundle-id>` to reset permissions
- **Command:** `xcrun simctl privacy booted reset all com.timesafari.dailynotification`
- **Command:** `xcrun simctl privacy booted reset all org.timesafari.dailynotification`
- **Lesson:** Simulator permissions don't reset automatically; must manually reset for testing different permission states
7. **JavaScript Method Existence Check:**
@@ -1555,7 +1555,7 @@ scripts/
1. **BGTaskScheduler Not Running:**
- Check Info.plist has `BGTaskSchedulerPermittedIdentifiers`
- Verify task registered in AppDelegate before app finishes launching
- **Simulator-only debugging trick:** Use LLDB command to manually trigger: `e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]`
- **Simulator-only debugging trick:** Use LLDB command to manually trigger: `e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.timesafari.dailynotification.fetch"]`
- Note: This is for simulator testing only, not available in production
- **Testing Guide:** See `doc/test-app-ios/IOS_PREFETCH_TESTING.md` for comprehensive testing procedures

View File

@@ -88,7 +88,7 @@ npx cap sync ios
cat android/app/src/main/assets/capacitor.plugins.json | grep DailyNotification
# Expected output should include:
# "DailyNotification": { "class": "com.timesafari.dailynotification.DailyNotificationPlugin" }
# "DailyNotification": { "class": "org.timesafari.dailynotification.DailyNotificationPlugin" }
```
### Error Handling
@@ -151,14 +151,14 @@ cat android/app/src/main/assets/capacitor.plugins.json | grep DailyNotification
<!-- Daily Notification Plugin Receivers -->
<!-- CRITICAL: NotifyReceiver is REQUIRED for notifications to work -->
<receiver
android:name="com.timesafari.dailynotification.NotifyReceiver"
android:name="org.timesafari.dailynotification.NotifyReceiver"
android:enabled="true"
android:exported="false">
</receiver>
<!-- BootReceiver for reboot recovery (optional but recommended) -->
<receiver
android:name="com.timesafari.dailynotification.BootReceiver"
android:name="org.timesafari.dailynotification.BootReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
@@ -176,7 +176,7 @@ grep -A 3 "NotifyReceiver" android/app/src/main/AndroidManifest.xml
# Expected output:
# <receiver
# android:name="com.timesafari.dailynotification.NotifyReceiver"
# android:name="org.timesafari.dailynotification.NotifyReceiver"
# android:enabled="true"
```
@@ -223,8 +223,8 @@ grep -A 3 "NotifyReceiver" android/app/src/main/AndroidManifest.xml
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.timesafari.dailynotification.content-fetch</string>
<string>com.timesafari.dailynotification.notification-delivery</string>
<string>org.timesafari.dailynotification.content-fetch</string>
<string>org.timesafari.dailynotification.notification-delivery</string>
</array>
</dict>
```
@@ -491,7 +491,7 @@ files:
- type: "uses-permission"
name: "android.permission.POST_NOTIFICATIONS"
- type: "receiver"
name: "com.timesafari.dailynotification.NotifyReceiver"
name: "org.timesafari.dailynotification.NotifyReceiver"
attributes:
android:enabled: "true"
android:exported: "false"

View File

@@ -132,7 +132,7 @@ android/plugin/src/main/java/com/timesafari/dailynotification/
### **BootReceiver Registration**
```xml
<receiver
android:name="com.timesafari.dailynotification.BootReceiver"
android:name="org.timesafari.dailynotification.BootReceiver"
android:enabled="true"
android:exported="true"
android:directBootAware="true">

View File

@@ -469,7 +469,7 @@ public class DailyNotificationScheduler {
<!-- Boot Receiver -->
<receiver
android:name="com.timesafari.dailynotification.BootReceiver"
android:name="org.timesafari.dailynotification.BootReceiver"
android:enabled="true"
android:exported="true"
android:directBootAware="true">
@@ -482,7 +482,7 @@ public class DailyNotificationScheduler {
<!-- Notification Receiver -->
<receiver
android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
android:enabled="true"
android:exported="false">
</receiver>

View File

@@ -720,7 +720,7 @@ The plugin **MUST NOT** support or guarantee the following behaviors:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<receiver
android:name="com.timesafari.dailynotification.BootReceiver"
android:name="org.timesafari.dailynotification.BootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
@@ -730,11 +730,11 @@ The plugin **MUST NOT** support or guarantee the following behaviors:
</receiver>
<receiver
android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.timesafari.daily.NOTIFICATION" />
<action android:name="org.timesafari.daily.NOTIFICATION" />
</intent-filter>
</receiver>
```
@@ -789,15 +789,15 @@ The plugin **MUST NOT** support or guarantee the following behaviors:
#### 10.2.2 Background Tasks
**Required Background Task Identifiers**:
* `com.timesafari.dailynotification.fetch` - Background fetch
* `com.timesafari.dailynotification.notify` - Notification task (if used)
* `org.timesafari.dailynotification.fetch` - Background fetch
* `org.timesafari.dailynotification.notify` - Notification task (if used)
**Background Task Registration**:
* Register in `Info.plist`:
```xml
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.timesafari.dailynotification.fetch</string>
<string>org.timesafari.dailynotification.fetch</string>
</array>
```

View File

@@ -16,7 +16,7 @@ This guide provides step-by-step instructions for testing Phase 1 (Cold Start Re
**Environment**
- Device: Android Emulator Pixel 8 API 34
- App ID: `com.timesafari.dailynotification`
- App ID: `org.timesafari.dailynotification`
- Build: Debug APK from `test-apps/android-test-app`
- Script: `./test-phase1.sh`
- Date: 27 November 2025
@@ -109,7 +109,7 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk
# Verify installation
adb shell pm list packages | grep timesafari
# Should show: package:com.timesafari.dailynotification
# Should show: package:org.timesafari.dailynotification
```
### Option 2: Vue Test App (More Features)
@@ -162,7 +162,7 @@ adb logcat -s DNP-REACTIVATION > recovery_test.log
```bash
# Launch app to initialize database
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# Wait a few seconds for initialization
sleep 3
@@ -181,7 +181,7 @@ sleep 3
adb logcat -c
# 2. Launch app
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# 3. Schedule notification for 2 minutes in future
# (Use app UI or API - see "Scheduling Notifications" below)
@@ -195,7 +195,7 @@ adb shell dumpsys alarm | grep -i timesafari
# Should show scheduled alarm
# 6. Kill app process (simulates OS kill, NOT force stop)
adb shell am kill com.timesafari.dailynotification
adb shell am kill org.timesafari.dailynotification
# 7. Verify app is killed
adb shell ps | grep timesafari
@@ -206,7 +206,7 @@ adb shell ps | grep timesafari
# Or: Set system time forward (see "Time Manipulation" below)
# 9. Launch app (cold start)
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# 10. Check recovery logs immediately
adb logcat -d | grep DNP-REACTIVATION
@@ -226,11 +226,11 @@ DNP-REACTIVATION: App launch recovery completed: missed=1, rescheduled=0, verifi
```bash
# Check database (requires root or debug build)
adb shell run-as com.timesafari.dailynotification sqlite3 databases/daily_notification_plugin.db \
adb shell run-as org.timesafari.dailynotification sqlite3 databases/daily_notification_plugin.db \
"SELECT id, delivery_status, scheduled_time FROM notification_content WHERE delivery_status = 'missed';"
# Or check history table
adb shell run-as com.timesafari.dailynotification sqlite3 databases/daily_notification_plugin.db \
adb shell run-as org.timesafari.dailynotification sqlite3 databases/daily_notification_plugin.db \
"SELECT * FROM history WHERE kind = 'recovery' ORDER BY occurredAt DESC LIMIT 1;"
```
@@ -254,7 +254,7 @@ adb shell run-as com.timesafari.dailynotification sqlite3 databases/daily_notifi
adb logcat -c
# 2. Launch app
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# 3. Schedule notification for 10 minutes in future
# (Use app UI or API)
@@ -276,7 +276,7 @@ adb shell dumpsys alarm | grep -i timesafari
# Should show no alarms (or fewer alarms)
# 7. Launch app (triggers recovery)
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# 8. Check recovery logs
adb logcat -d | grep DNP-REACTIVATION
@@ -318,7 +318,7 @@ adb logcat -c
# See "Database Manipulation" section below
# 3. Launch app
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# 4. Check logs immediately
adb logcat -d | grep DNP-REACTIVATION
@@ -354,7 +354,7 @@ adb logcat -c
# See "Database Manipulation" section below
# 3. Launch app
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# 4. Check logs
adb logcat -d | grep DNP-REACTIVATION
@@ -426,10 +426,10 @@ adb shell date -s "2025-11-15 14:30:00"
```bash
# Check if app is debuggable
adb shell dumpsys package com.timesafari.dailynotification | grep debuggable
adb shell dumpsys package org.timesafari.dailynotification | grep debuggable
# Access database
adb shell run-as com.timesafari.dailynotification sqlite3 databases/daily_notification_plugin.db
adb shell run-as org.timesafari.dailynotification sqlite3 databases/daily_notification_plugin.db
# Example: Insert test notification
sqlite> INSERT INTO notification_content (
@@ -492,7 +492,7 @@ adb logcat -d > phase1_test_$(date +%Y%m%d_%H%M%S).log
#!/bin/bash
# Phase 1 Complete Test Sequence
PACKAGE="com.timesafari.dailynotification"
PACKAGE="org.timesafari.dailynotification"
ACTIVITY="${PACKAGE}/.MainActivity"
echo "=== Phase 1 Testing on Emulator ==="
@@ -601,7 +601,7 @@ adb devices
**Permission denied for database access**:
```bash
# Check if app is debuggable
adb shell dumpsys package com.timesafari.dailynotification | grep debuggable
adb shell dumpsys package org.timesafari.dailynotification | grep debuggable
# If not debuggable, rebuild with debug signing
cd test-apps/android-test-app
@@ -617,7 +617,7 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk
adb shell pm list packages | grep timesafari
# Uninstall and reinstall
adb uninstall com.timesafari.dailynotification
adb uninstall org.timesafari.dailynotification
adb install -r app/build/outputs/apk/debug/app-debug.apk
```
@@ -658,10 +658,10 @@ cd test-apps/android-test-app
adb install -r app/build/outputs/apk/debug/app-debug.apk
# Launch app
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# Kill app
adb shell am kill com.timesafari.dailynotification
adb shell am kill org.timesafari.dailynotification
# Monitor logs
adb logcat -s DNP-REACTIVATION

View File

@@ -90,19 +90,19 @@ Verify that when a force stop clears alarms, the plugin:
3. **Verify alarms are scheduled**
* Script runs:
```bash
adb shell dumpsys alarm | grep com.timesafari.dailynotification
adb shell dumpsys alarm | grep org.timesafari.dailynotification
```
* Confirm at least one `RTC_WAKEUP` alarm for `com.timesafari.dailynotification`.
* Confirm at least one `RTC_WAKEUP` alarm for `org.timesafari.dailynotification`.
4. **Force stop the app**
* Script executes:
```bash
adb shell am force-stop com.timesafari.dailynotification
adb shell am force-stop org.timesafari.dailynotification
```
5. **Confirm alarms after force stop**
* Script re-runs `dumpsys alarm`.
* Ideal test case: **0** alarms for `com.timesafari.dailynotification` (alarms cleared).
* Ideal test case: **0** alarms for `org.timesafari.dailynotification` (alarms cleared).
6. **Trigger recovery**
* Script clears logcat and launches the app.
@@ -165,12 +165,12 @@ Ensure we **do not run heavy force-stop recovery** when alarms are still intact.
* Click **Test Notification** again to create a second schedule.
3. **Verify alarms are scheduled**
* Confirm multiple alarms for `com.timesafari.dailynotification` via `dumpsys alarm`.
* Confirm multiple alarms for `org.timesafari.dailynotification` via `dumpsys alarm`.
4. **Simulate a "soft stop"**
* Script runs:
```bash
adb shell am kill com.timesafari.dailynotification
adb shell am kill org.timesafari.dailynotification
```
* Intent: stop the process but **not** clear alarms (actual behavior may vary by OS).
@@ -217,7 +217,7 @@ Ensure **force-stop recovery is not mis-triggered** when the app is freshly inst
1. **Clear state**
* Script uninstalls the app to clear DB/state:
```bash
adb uninstall com.timesafari.dailynotification
adb uninstall org.timesafari.dailynotification
```
2. **Reinstall APK**
@@ -273,7 +273,7 @@ Fill this in after your first successful emulator run.
**Environment**
- Device: Pixel 8 API 34 (Android 14)
- App ID: `com.timesafari.dailynotification`
- App ID: `org.timesafari.dailynotification`
- Build: Debug APK (`app-debug.apk`) from commit `<GIT_HASH>`
- Script: `./test-phase2.sh`
- Date: 2025-11-XX

View File

@@ -1,7 +1,7 @@
# Phase 2 Force Stop Recovery Verification
**Plugin:** Daily Notification Plugin
**Scope:** Force stop detection & recovery (App ID: `com.timesafari.dailynotification`)
**Scope:** Force stop detection & recovery (App ID: `org.timesafari.dailynotification`)
**Related Docs:**
- `android-implementation-directive-phase2.md`
@@ -142,7 +142,7 @@ or:
**Environment**
* Device: Pixel 8 API 34 (Android 14)
* App ID: `com.timesafari.dailynotification`
* App ID: `org.timesafari.dailynotification`
* Build: Debug `app-debug.apk` from commit `<GIT_HASH>`
* Script: `./test-phase2.sh`
* Date: 2025-11-XX

View File

@@ -91,7 +91,7 @@ Verify alarms are recreated on boot when schedules have **future run times**.
3. **Verify alarms are scheduled (pre-boot)**
* Script calls `show_alarms` and `count_alarms`.
* You should see at least one `RTC_WAKEUP` entry for `com.timesafari.dailynotification`.
* You should see at least one `RTC_WAKEUP` entry for `org.timesafari.dailynotification`.
4. **Reboot emulator**
* Script calls `reboot_emulator`:
@@ -193,7 +193,7 @@ Verify boot recovery handles an **empty DB / no schedules** safely and does **no
1. **Uninstall app to clear DB/state**
* Script calls:
```bash
adb uninstall com.timesafari.dailynotification
adb uninstall org.timesafari.dailynotification
```
2. **Reinstall APK**

View File

@@ -146,7 +146,7 @@ Script passes if:
**Environment**
* Device: Pixel 8 API 34 (Android 14)
* App ID: `com.timesafari.dailynotification`
* App ID: `org.timesafari.dailynotification`
* Build: Debug `app-debug.apk` from commit `<GIT_HASH>`
* Script: `./test-phase3.sh`
* Date: 2025-11-XX

View File

@@ -223,8 +223,8 @@ public class MyNativeFetcher implements NativeNotificationContentFetcher {
package com.example.app;
import android.app.Application;
import com.timesafari.dailynotification.DailyNotificationPlugin;
import com.timesafari.dailynotification.NativeNotificationContentFetcher;
import org.timesafari.dailynotification.DailyNotificationPlugin;
import org.timesafari.dailynotification.NativeNotificationContentFetcher;
public class MyApplication extends Application {
@Override
@@ -285,7 +285,7 @@ export async function setupDailyNotifications() {
package com.example.app;
import android.util.Log;
import com.timesafari.dailynotification.*;
import org.timesafari.dailynotification.*;
import java.util.*;
public class MyNativeFetcher implements NativeNotificationContentFetcher {

View File

@@ -18,8 +18,8 @@ This document provides comprehensive guidance for legal and store compliance req
const iosBackgroundTaskConfig = {
// Required: Register background task identifiers
backgroundTaskIdentifiers: [
'com.timesafari.dailynotification.fetch',
'com.timesafari.dailynotification.maintenance'
'org.timesafari.dailynotification.fetch',
'org.timesafari.dailynotification.maintenance'
],
// Required: Background modes in Info.plist

View File

@@ -794,7 +794,7 @@ class SecureJWTStorage(private val context: Context) {
**iOS (iOS Keychain)**:
```swift
class SecureJWTStorage {
private let keychain = Keychain(service: "com.timesafari.dailynotification")
private let keychain = Keychain(service: "org.timesafari.dailynotification")
func storeJWTSecret(_ secret: String) throws {
let data = secret.data(using: .utf8)!
@@ -1006,7 +1006,7 @@ fun scheduleImmediateCatchUp() {
**iOS (BGTaskScheduler)**:
```swift
// BGTaskScheduler Configuration
let taskIdentifier = "com.timesafari.dailynotification.starred-projects-polling"
let taskIdentifier = "org.timesafari.dailynotification.starred-projects-polling"
// Register background task
BGTaskScheduler.shared.register(
@@ -1025,7 +1025,7 @@ try BGTaskScheduler.shared.submit(request)
/*
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.timesafari.dailynotification.starred-projects-polling</string>
<string>org.timesafari.dailynotification.starred-projects-polling</string>
</array>
<key>UIBackgroundModes</key>
<array>
@@ -2472,7 +2472,7 @@ interface PollingScheduleConfig<TRequest, TResponse> {
**File**: `src/android/GenericPollingManager.java`
```java
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
import android.content.Context;
import android.util.Log;

View File

@@ -19,7 +19,7 @@ The initial approach using `BootReceiver` to restore notifications after device
```bash
# Boot receiver was registered but not triggered
adb shell "dumpsys package com.timesafari.dailynotification | grep -A5 -B5 BootReceiver"
adb shell "dumpsys package org.timesafari.dailynotification | grep -A5 -B5 BootReceiver"
# Output: BootReceiver registered but not in enabledComponents list
# After reboot, no recovery logs appeared
@@ -167,7 +167,7 @@ adb shell "dumpsys alarm | grep timesafari"
```bash
# 1. Schedule notification
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# Tap "Test Notification" (5 minutes from now)
# 2. Verify initial scheduling
@@ -179,7 +179,7 @@ adb reboot
# Wait 2-3 minutes for boot completion
# 4. Launch app (triggers recovery)
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# 5. Check recovery logs
adb logcat -d | grep -i "recovery" | tail -5

View File

@@ -621,7 +621,7 @@ Gaps uncovered:
adb shell dumpsys alarm | grep -i timesafari
# Force kill (not force-stop) - adjust package name based on test app
adb shell am kill com.timesafari.dailynotification
adb shell am kill org.timesafari.dailynotification
# Or for test apps:
# adb shell am kill com.timesafari.androidtestapp
# adb shell am kill <package-name-from-test-app-manifest>

View File

@@ -305,7 +305,7 @@ The plugin **must**:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<receiver
android:name="com.timesafari.dailynotification.BootReceiver"
android:name="org.timesafari.dailynotification.BootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
@@ -315,11 +315,11 @@ The plugin **must**:
</receiver>
<receiver
android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.timesafari.daily.NOTIFICATION" />
<action android:name="org.timesafari.daily.NOTIFICATION" />
</intent-filter>
</receiver>
```
@@ -372,15 +372,15 @@ The plugin **must**:
#### 5.2.2 Background Tasks
**Required Background Task Identifiers**:
* `com.timesafari.dailynotification.fetch` - Background fetch
* `com.timesafari.dailynotification.notify` - Notification task (if used)
* `org.timesafari.dailynotification.fetch` - Background fetch
* `org.timesafari.dailynotification.notify` - Notification task (if used)
**Background Task Registration**:
* Register in `Info.plist`:
```xml
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.timesafari.dailynotification.fetch</string>
<string>org.timesafari.dailynotification.fetch</string>
</array>
```

View File

@@ -39,7 +39,7 @@ Add to `Info.plist`:
```xml
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.timesafari.dailynotification.fetch</string>
<string>org.timesafari.dailynotification.fetch</string>
</array>
```

View File

@@ -0,0 +1,129 @@
# Consuming App Migration: com.timesafari → org.timesafari
Use this document in your **consuming app** repo (e.g. with Cursor or as a checklist) to migrate from `com.timesafari.dailynotification` to `org.timesafari.dailynotification` after the daily-notification-plugin has been updated.
## Summary of plugin changes
The plugins package/namespace and public identifiers were renamed:
- **Package/namespace**: `com.timesafari.dailynotification``org.timesafari.dailynotification`
- **Intent action (Android)**: `com.timesafari.daily.NOTIFICATION``org.timesafari.daily.NOTIFICATION`
- **Dismiss action (Android)**: `com.timesafari.daily.DISMISS``org.timesafari.daily.DISMISS`
- **iOS BGTask identifiers**: `com.timesafari.dailynotification.fetch` / `.notify` / `.prefetch``org.timesafari.dailynotification.*`
- **Capacitor plugin class**: `com.timesafari.dailynotification.DailyNotificationPlugin``org.timesafari.dailynotification.DailyNotificationPlugin`
Your app must align with these so the plugin loads and receivers/intents work.
---
## 1. Update plugin dependency
- Ensure the consuming app depends on a **version of the plugin that already uses `org.timesafari`** (e.g. after the plugin repos com→org migration is merged/released).
- Reinstall/sync: `npm install` (or equivalent) so the updated plugin is used.
---
## 2. Capacitor plugin registration (Android & iOS)
The plugin is registered by **class name**. Update every place that references the plugin class.
**Typical locations:**
- `android/app/src/main/assets/capacitor.plugins.json` (or `public/plugins` if you use that)
- Any script or config that writes the plugin class string
**Change:**
- From: `"class": "com.timesafari.dailynotification.DailyNotificationPlugin"` or `"classpath": "com.timesafari.dailynotification.DailyNotificationPlugin"`
- To: `"class": "org.timesafari.dailynotification.DailyNotificationPlugin"` or `"classpath": "org.timesafari.dailynotification.DailyNotificationPlugin"`
If you use a script (e.g. `fix-capacitor-plugins.js`) that injects this class name, update the script to use `org.timesafari.dailynotification.DailyNotificationPlugin`.
---
## 3. Android: `AndroidManifest.xml`
**Receiver class names** (must match the plugins new package):
- `com.timesafari.dailynotification.DailyNotificationReceiver``org.timesafari.dailynotification.DailyNotificationReceiver`
- `com.timesafari.dailynotification.NotifyReceiver``org.timesafari.dailynotification.NotifyReceiver`
- `com.timesafari.dailynotification.BootReceiver``org.timesafari.dailynotification.BootReceiver`
- Any other `com.timesafari.dailynotification.*` receiver → `org.timesafari.dailynotification.*`
**Intent action** (must match plugins `DailyNotificationConstants.ACTION_NOTIFICATION`):
- `<action android:name="com.timesafari.daily.NOTIFICATION" />``<action android:name="org.timesafari.daily.NOTIFICATION" />`
Search the manifest for `com.timesafari` and replace with `org.timesafari` for these plugin-related entries.
---
## 4. Android: ProGuard / R8 (if you keep plugin classes)
If you have custom keep rules that reference the plugin package:
- `com.timesafari.dailynotification``org.timesafari.dailynotification`
(e.g. `-keep class com.timesafari.dailynotification.**``-keep class org.timesafari.dailynotification.**`).
---
## 5. iOS: `Info.plist` (BGTask identifiers)
The plugin uses these background task identifiers. Your apps `Info.plist` must list the **same** identifiers.
**In `BGTaskSchedulerPermittedIdentifiers` (or equivalent):**
- `com.timesafari.dailynotification.fetch``org.timesafari.dailynotification.fetch`
- `com.timesafari.dailynotification.notify``org.timesafari.dailynotification.notify`
If you use the prefetch identifier:
- `com.timesafari.dailynotification.prefetch``org.timesafari.dailynotification.prefetch`
Update any other `com.timesafari.dailynotification.*` task IDs to `org.timesafari.dailynotification.*`.
---
## 6. App ID / Bundle ID (optional; breaking for installs)
- **Plugin package/namespace** change does **not** require you to change your apps **applicationId** (Android) or **Bundle ID** (iOS).
- If you **do** change your apps id from `com.timesafari.*` to `org.timesafari.*`, the store and OS will treat it as a **new app** (new install, no in-place update). Only change this if you intend that.
---
## 7. Custom intent actions / deep links
If your app or backend uses custom actions that included the old prefix (e.g. `com.timesafari.dailynotification.REFRESH_DATA` or `OPEN_SETTINGS`), update them to `org.timesafari.dailynotification.*` so they still match what the plugin or your code expects.
---
## 8. Scripts and docs in the consuming app
- Any script that uses the **plugin package** or **intent action** (e.g. `adb shell am start`, `dumpsys package`, or broadcast actions) should use `org.timesafari.dailynotification` and `org.timesafari.daily.NOTIFICATION` where applicable.
- Update internal docs or runbooks that reference the old package or action strings.
---
## 9. Verification
- **Android**: Build the app, install, and confirm notifications still schedule and fire. Check `adb shell dumpsys package <your.package>` for receivers and that alarms use `org.timesafari.daily.NOTIFICATION` if you inspect with `dumpsys alarm`.
- **iOS**: Build and run; confirm BGTask registration and notification behavior. Ensure `Info.plist` identifiers match the plugins Swift constants.
- **Capacitor**: Confirm the plugin is loaded (e.g. no “class not found” or missing plugin in the bridge).
---
## Quick find-and-replace (consuming app only)
Use with care; prefer updating specific files as above. Suggested patterns:
- **Plugin class**: `com.timesafari.dailynotification.DailyNotificationPlugin``org.timesafari.dailynotification.DailyNotificationPlugin`
- **Receiver/package references**: `com.timesafari.dailynotification.``org.timesafari.dailynotification.`
- **Notification intent action**: `com.timesafari.daily.NOTIFICATION``org.timesafari.daily.NOTIFICATION`
- **BGTask identifiers**: `com.timesafari.dailynotification.fetch` / `.notify` / `.prefetch``org.timesafari.dailynotification.fetch` / `.notify` / `.prefetch`
Do **not** blindly replace `com.timesafari` in your **apps own** applicationId/Bundle ID unless you intend to ship as a new app.
---
**Reference:** This migration is aligned with the changes in the `daily-notification-plugin` repo (package rename com → org). For plugin-side details, see that repos history and docs.

View File

@@ -508,10 +508,10 @@ Add required permissions to `android/app/src/main/AndroidManifest.xml`:
<!-- Existing application configuration -->
<!-- Daily Notification Plugin receivers and services -->
<receiver android:name="com.timesafari.dailynotification.NotifyReceiver"
<receiver android:name="org.timesafari.dailynotification.NotifyReceiver"
android:enabled="true"
android:exported="false" />
<receiver android:name="com.timesafari.dailynotification.BootReceiver"
<receiver android:name="org.timesafari.dailynotification.BootReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
@@ -599,8 +599,8 @@ Add required permissions to `ios/App/App/Info.plist`:
<!-- BGTaskScheduler identifiers -->
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.timesafari.dailynotification.content-fetch</string>
<string>com.timesafari.dailynotification.notification-delivery</string>
<string>org.timesafari.dailynotification.content-fetch</string>
<string>org.timesafari.dailynotification.notification-delivery</string>
</array>
<!-- Notification usage description -->

View File

@@ -59,14 +59,14 @@ Add to `android/app/src/main/AndroidManifest.xml`:
<!-- Daily Notification Plugin Receivers -->
<!-- REQUIRED: NotifyReceiver for AlarmManager-based notifications -->
<receiver
android:name="com.timesafari.dailynotification.NotifyReceiver"
android:name="org.timesafari.dailynotification.NotifyReceiver"
android:enabled="true"
android:exported="false">
</receiver>
<!-- BootReceiver for reboot recovery (optional but recommended) -->
<receiver
android:name="com.timesafari.dailynotification.BootReceiver"
android:name="org.timesafari.dailynotification.BootReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
@@ -108,8 +108,8 @@ Add to `ios/App/App/Info.plist`:
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.timesafari.dailynotification.content-fetch</string>
<string>com.timesafari.dailynotification.notification-delivery</string>
<string>org.timesafari.dailynotification.content-fetch</string>
<string>org.timesafari.dailynotification.notification-delivery</string>
</array>
```

View File

@@ -77,7 +77,7 @@ DailyNotificationPlugin.load()
**File to Create**: `NativeNotificationContentFetcher.java`
```java
package com.timesafari.dailynotification;
package org.timesafari.dailynotification;
public interface NativeNotificationContentFetcher {
java.util.concurrent.CompletableFuture<java.util.List<NotificationContent>>

View File

@@ -24,7 +24,7 @@ This document provides comprehensive troubleshooting guidance for integrating th
**Error Message:**
```
Duplicate class com.timesafari.dailynotification.BootReceiver found in modules:
Duplicate class org.timesafari.dailynotification.BootReceiver found in modules:
- plugin-debug.aar -> plugin-debug-runtime (:plugin-debug:)
- plugin-debug.aar -> plugin-debug-runtime (plugin-debug.aar)
```
@@ -82,7 +82,7 @@ implementation(name: 'plugin-debug', ext: 'aar')
{
"id": "DailyNotification",
"name": "DailyNotification",
"class": "com.timesafari.dailynotification.DailyNotificationPlugin"
"class": "org.timesafari.dailynotification.DailyNotificationPlugin"
}
```
@@ -166,7 +166,7 @@ dependencies {
{
"id": "DailyNotification",
"name": "DailyNotification",
"class": "com.timesafari.dailynotification.DailyNotificationPlugin"
"class": "org.timesafari.dailynotification.DailyNotificationPlugin"
}
]
```

View File

@@ -180,8 +180,8 @@ await DailyNotification.configure({
iosConfig: {
// Background task configuration
backgroundTasks: {
'com.timesafari.daily-notification-fetch': {
identifier: 'com.timesafari.daily-notification-fetch',
'org.timesafari.daily-notification-fetch': {
identifier: 'org.timesafari.daily-notification-fetch',
requiresNetworkConnectivity: true,
requiresExternalPower: false,
requiresDeviceIdle: false

View File

@@ -118,7 +118,7 @@ public class MainActivity extends BridgeActivity {
```xml
<!-- App Configuration -->
<application android:name="com.timesafari.dailynotification">
<application android:name="org.timesafari.dailynotification">
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -142,16 +142,16 @@ public class MainActivity extends BridgeActivity {
<!-- Plugin Components -->
<!-- Internal receiver: keep non-exported unless intentionally public -->
<receiver
android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.timesafari.daily.NOTIFICATION" />
<action android:name="org.timesafari.daily.NOTIFICATION" />
</intent-filter>
</receiver>
<receiver
android:name="com.timesafari.dailynotification.BootReceiver"
android:name="org.timesafari.dailynotification.BootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="1000">
@@ -164,7 +164,7 @@ public class MainActivity extends BridgeActivity {
**Minimal example (recommended):**
```xml
<receiver
android:name="com.timesafari.dailynotification.BootReceiver"
android:name="org.timesafari.dailynotification.BootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
@@ -203,7 +203,7 @@ public class MainActivity extends BridgeActivity {
```json
{
"appId": "com.timesafari.dailynotification",
"appId": "org.timesafari.dailynotification",
"appName": "DailyNotification Test App",
"webDir": "www",
"server": {
@@ -229,7 +229,7 @@ public class MainActivity extends BridgeActivity {
[
{
"name": "DailyNotification",
"classpath": "com.timesafari.dailynotification.DailyNotificationPlugin"
"classpath": "org.timesafari.dailynotification.DailyNotificationPlugin"
}
]
```
@@ -429,11 +429,11 @@ dependencies {
```gradle
android {
namespace "com.timesafari.dailynotification"
namespace "org.timesafari.dailynotification"
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "com.timesafari.dailynotification"
applicationId "org.timesafari.dailynotification"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1

View File

@@ -110,7 +110,7 @@ www/
#### Implementation Plan
```java
// New organization
com.timesafari.dailynotification/
org.timesafari.dailynotification/
plugin/
DailyNotificationPlugin.java (thin facade)
usecases/
@@ -587,16 +587,16 @@ public class SecureNetworkClient {
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<receiver
android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.timesafari.daily.NOTIFICATION" />
<action android:name="org.timesafari.daily.NOTIFICATION" />
</intent-filter>
</receiver>
<receiver
android:name="com.timesafari.dailynotification.BootReceiver"
android:name="org.timesafari.dailynotification.BootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="1000">
@@ -605,7 +605,7 @@ public class SecureNetworkClient {
</receiver>
<receiver
android:name="com.timesafari.dailynotification.TimeChangeReceiver"
android:name="org.timesafari.dailynotification.TimeChangeReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>

View File

@@ -96,7 +96,7 @@ This directive provides **descriptive overview and integration guidance** for An
**⚠️ Illustrative only** See Phase 1 for canonical implementation.
```kotlin
package com.timesafari.dailynotification
package org.timesafari.dailynotification
import android.app.AlarmManager
import android.content.Context
@@ -763,7 +763,7 @@ private suspend fun handleMissedAlarmOnBoot(
### 5.1 Test Setup
**Package Name**: `com.timesafari.dailynotification`
**Package Name**: `org.timesafari.dailynotification`
**Test App Location**: `test-apps/android-test-app/`
@@ -806,7 +806,7 @@ adb shell dumpsys jobscheduler | grep -i timesafari
#### Step 1: Schedule Alarm
**Via Test App UI**:
1. Launch test app: `adb shell am start -n com.timesafari.dailynotification/.MainActivity`
1. Launch test app: `adb shell am start -n org.timesafari.dailynotification/.MainActivity`
2. Click "Test Notification" button
3. Schedule alarm for 4 minutes in future (test app default)
4. Note the scheduled time
@@ -814,7 +814,7 @@ adb shell dumpsys jobscheduler | grep -i timesafari
**Via ADB (Alternative)**:
```bash
# Launch app
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# Wait for app to load, then use UI to schedule
# Or use monkey to click button (if button ID known)
@@ -835,7 +835,7 @@ adb logcat -d | grep "DN|SCHEDULE\|DN|ALARM"
```bash
# Kill app process (simulates OS kill)
adb shell am kill com.timesafari.dailynotification
adb shell am kill org.timesafari.dailynotification
# Verify app is killed
adb shell ps | grep timesafari
@@ -866,7 +866,7 @@ adb shell date
```bash
# Launch app (triggers cold start)
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# Monitor logs for recovery
adb logcat -c # Clear logs first
@@ -915,7 +915,7 @@ adb logcat -d | grep -E "DNP-REACTIVATION|COLD_START|missed"
#### Step 1: Schedule Multiple Alarms
**Via Test App UI**:
1. Launch app: `adb shell am start -n com.timesafari.dailynotification/.MainActivity`
1. Launch app: `adb shell am start -n org.timesafari.dailynotification/.MainActivity`
2. Schedule alarm #1 for 2 minutes in future
3. Schedule alarm #2 for 5 minutes in future
4. Schedule alarm #3 for 10 minutes in future
@@ -936,7 +936,7 @@ adb logcat -d | grep "DN|SCHEDULE"
```bash
# Force stop app (hard kill)
adb shell am force-stop com.timesafari.dailynotification
adb shell am force-stop org.timesafari.dailynotification
# Verify app is force-stopped
adb shell ps | grep timesafari
@@ -963,7 +963,7 @@ adb shell date
```bash
# Launch app (triggers force stop recovery)
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# Monitor logs immediately
adb logcat -c
@@ -1014,7 +1014,7 @@ adb shell dumpsys alarm | grep -A 10 timesafari
#### Step 1: Schedule Alarm Before Reboot
**Via Test App UI**:
1. Launch app: `adb shell am start -n com.timesafari.dailynotification/.MainActivity`
1. Launch app: `adb shell am start -n org.timesafari.dailynotification/.MainActivity`
2. Schedule alarm for 5 minutes in future
3. Note scheduled time
@@ -1075,7 +1075,7 @@ adb logcat -d | grep -E "DNP-BOOT|BOOT_COMPLETED|reschedule"
```bash
# Launch app to verify state
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# Check if missed alarms were handled
adb logcat -d | grep -E "missed|boot_recovery"
@@ -1103,7 +1103,7 @@ adb logcat -d | grep -E "missed|boot_recovery"
#### Step 1: Schedule Alarm
**Via Test App UI**:
1. Launch app: `adb shell am start -n com.timesafari.dailynotification/.MainActivity`
1. Launch app: `adb shell am start -n org.timesafari.dailynotification/.MainActivity`
2. Schedule alarm for 10 minutes in future
3. Note scheduled time
@@ -1142,7 +1142,7 @@ adb shell ps | grep timesafari
```bash
# Bring app to foreground
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# Monitor logs for warm start recovery
adb logcat -c
@@ -1191,7 +1191,7 @@ adb shell dumpsys alarm | grep -A 5 timesafari
```bash
# Launch app and schedule alarm for 4 minutes
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# Use UI to schedule alarm
```
@@ -1269,7 +1269,7 @@ adb logcat -d | grep -E "DN|RECEIVE_START|DN|WORK_START|DN|DISPLAY"
**Android 12+ (API 31+)**:
```bash
# Check current permission status
adb shell dumpsys package com.timesafari.dailynotification | grep -i "schedule_exact_alarm"
adb shell dumpsys package org.timesafari.dailynotification | grep -i "schedule_exact_alarm"
# Revoke permission (requires root or system app)
# Or use Settings UI:
@@ -1299,7 +1299,7 @@ adb logcat -d | grep -E "EXACT_ALARM|permission|SCHEDULE_EXACT"
adb shell am start -a android.settings.REQUEST_SCHEDULE_EXACT_ALARM
# Or navigate manually:
adb shell am start -a android.settings.APPLICATION_DETAILS_SETTINGS -d package:com.timesafari.dailynotification
adb shell am start -a android.settings.APPLICATION_DETAILS_SETTINGS -d package:org.timesafari.dailynotification
```
#### Step 4: Verify Alarm Scheduling
@@ -1412,7 +1412,7 @@ adb shell date +%s
```bash
#!/bin/bash
PACKAGE="com.timesafari.dailynotification"
PACKAGE="org.timesafari.dailynotification"
ACTIVITY="${PACKAGE}/.MainActivity"
echo "=== Test 1: Cold Start Recovery ==="

View File

@@ -84,7 +84,7 @@ Phase 1 implements **minimal viable app launch recovery** for cold start scenari
### 2.2 Class Structure
```kotlin
package com.timesafari.dailynotification
package org.timesafari.dailynotification
import android.content.Context
import android.util.Log
@@ -561,9 +561,9 @@ override fun load() {
**Steps**:
1. Schedule notification for 2 minutes in future
2. Kill app process: `adb shell am kill com.timesafari.dailynotification`
2. Kill app process: `adb shell am kill org.timesafari.dailynotification`
3. Wait 5 minutes (past scheduled time)
4. Launch app: `adb shell am start -n com.timesafari.dailynotification/.MainActivity`
4. Launch app: `adb shell am start -n org.timesafari.dailynotification/.MainActivity`
5. Check logs: `adb logcat -d | grep DNP-REACTIVATION`
**Expected**:

View File

@@ -163,7 +163,7 @@ private fun alarmsExist(): Boolean {
// Check if any PendingIntent for our receiver exists
// This is more reliable than nextAlarmClock
val intent = Intent(context, DailyNotificationReceiver::class.java).apply {
action = "com.timesafari.daily.NOTIFICATION"
action = "org.timesafari.daily.NOTIFICATION"
}
val pendingIntent = PendingIntent.getBroadcast(
context,
@@ -629,9 +629,9 @@ private fun calculateNextOccurrence(schedule: Schedule, fromTime: Long): Long {
**Steps**:
1. Schedule 3 notifications (2 minutes, 5 minutes, 10 minutes in future)
2. Verify alarms scheduled: `adb shell dumpsys alarm | grep timesafari`
3. Force stop app: `adb shell am force-stop com.timesafari.dailynotification`
3. Force stop app: `adb shell am force-stop org.timesafari.dailynotification`
4. Verify alarms cancelled: `adb shell dumpsys alarm | grep timesafari` (should be empty)
5. Launch app: `adb shell am start -n com.timesafari.dailynotification/.MainActivity`
5. Launch app: `adb shell am start -n org.timesafari.dailynotification/.MainActivity`
6. Check logs: `adb logcat -d | grep DNP-REACTIVATION`
**Expected**:

View File

@@ -29,7 +29,7 @@ public class TestApplication extends Application {
Context context = getApplicationContext();
NativeNotificationContentFetcher testFetcher =
new com.timesafari.dailynotification.test.TestNativeFetcher(context);
new org.timesafari.dailynotification.test.TestNativeFetcher(context);
DailyNotificationPlugin.setNativeFetcher(testFetcher);
}
}
@@ -175,7 +175,7 @@ implementation 'com.google.code.gson:gson:2.10.1'
**Test App (Working):**
```xml
<receiver
android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
android:enabled="true"
android:exported="false"> <!-- Note: false -->
```
@@ -183,7 +183,7 @@ implementation 'com.google.code.gson:gson:2.10.1'
**TimeSafari (Broken):**
```xml
<receiver
android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
android:enabled="true"
android:exported="true"> <!-- Note: true - potential security issue -->
```
@@ -240,8 +240,8 @@ 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;
import org.timesafari.dailynotification.DailyNotificationPlugin;
import org.timesafari.dailynotification.NativeNotificationContentFetcher;
public class TimeSafariApplication extends Application {
@@ -272,8 +272,8 @@ Create file: `android/app/src/main/java/app/timesafari/TimeSafariNativeFetcher.j
package app.timesafari;
import android.content.Context;
import com.timesafari.dailynotification.NativeNotificationContentFetcher;
import com.timesafari.dailynotification.NotificationContent;
import org.timesafari.dailynotification.NativeNotificationContentFetcher;
import org.timesafari.dailynotification.NotificationContent;
public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher {
@@ -318,11 +318,11 @@ public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher
<!-- Fix: Change exported to false -->
<receiver
android:name="com.timesafari.dailynotification.DailyNotificationReceiver"
android:name="org.timesafari.dailynotification.DailyNotificationReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.timesafari.daily.NOTIFICATION" />
<action android:name="org.timesafari.daily.NOTIFICATION" />
</intent-filter>
</receiver>
@@ -431,9 +431,9 @@ After implementing fixes, verify:
3. **Test receiver manually:**
```bash
adb shell am broadcast -a com.timesafari.daily.NOTIFICATION \
adb shell am broadcast -a org.timesafari.daily.NOTIFICATION \
--es id "test_notification" \
-n app.timesafari.app/com.timesafari.dailynotification.DailyNotificationReceiver
-n app.timesafari.app/org.timesafari.dailynotification.DailyNotificationReceiver
```
4. **Check notification permissions:**

View File

@@ -173,10 +173,10 @@ console.log('Performance:', status.performance);
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- Register new receivers -->
<receiver android:name="com.timesafari.dailynotification.NotifyReceiver"
<receiver android:name="org.timesafari.dailynotification.NotifyReceiver"
android:enabled="true"
android:exported="false" />
<receiver android:name="com.timesafari.dailynotification.BootReceiver"
<receiver android:name="org.timesafari.dailynotification.BootReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
@@ -209,8 +209,8 @@ dependencies {
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.timesafari.dailynotification.content-fetch</string>
<string>com.timesafari.dailynotification.notification-delivery</string>
<string>org.timesafari.dailynotification.content-fetch</string>
<string>org.timesafari.dailynotification.notification-delivery</string>
</array>
```

View File

@@ -88,7 +88,7 @@ This guide provides solutions to common iOS-specific issues when using the Daily
1. **Check BGTaskScheduler Registration:**
```swift
// Verify registration in AppDelegate
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.timesafari.dailynotification.fetch", using: nil) { task in
BGTaskScheduler.shared.register(forTaskWithIdentifier: "org.timesafari.dailynotification.fetch", using: nil) { task in
// Handler should be registered
}
```
@@ -97,7 +97,7 @@ This guide provides solutions to common iOS-specific issues when using the Daily
```xml
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.timesafari.dailynotification.fetch</string>
<string>org.timesafari.dailynotification.fetch</string>
</array>
```
@@ -364,7 +364,7 @@ print("Registered tasks: \(registered)")
**LLDB Command in Xcode:**
```lldb
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.timesafari.dailynotification.fetch"]
```
**Note:** This only works in simulator, not on physical devices.

View File

@@ -75,10 +75,10 @@
**How to Run:**
```bash
# Run all combined edge case tests
cd android && ./gradlew test --tests "com.timesafari.dailynotification.DailyNotificationRecoveryTests"
cd android && ./gradlew test --tests "org.timesafari.dailynotification.DailyNotificationRecoveryTests"
# Or run specific test
cd android && ./gradlew test --tests "com.timesafari.dailynotification.DailyNotificationRecoveryTests.test_combined_dst_boundary_duplicate_delivery_cold_start"
cd android && ./gradlew test --tests "org.timesafari.dailynotification.DailyNotificationRecoveryTests.test_combined_dst_boundary_duplicate_delivery_cold_start"
```
---

View File

@@ -97,7 +97,7 @@ Before starting, verify:
**Example placeholder test:**
```kotlin
package com.timesafari.dailynotification
package org.timesafari.dailynotification
import org.junit.Test
import org.junit.Assert.*
@@ -148,7 +148,7 @@ class DailyNotificationRecoveryTests {
**Example structure:**
```kotlin
package com.timesafari.dailynotification
package org.timesafari.dailynotification
import androidx.room.Room
import androidx.room.RoomDatabase

View File

@@ -793,7 +793,7 @@ Add to `Info.plist`:
\`\`\`xml
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.timesafari.dailynotification.fetch</string>
<string>org.timesafari.dailynotification.fetch</string>
</array>
\`\`\`

View File

@@ -13,7 +13,7 @@ This guide provides comprehensive testing procedures for the **fixed BootReceive
### **1. AndroidManifest.xml Updates**
```xml
<receiver
android:name="com.timesafari.dailynotification.BootReceiver"
android:name="org.timesafari.dailynotification.BootReceiver"
android:enabled="true"
android:exported="true"
android:directBootAware="true">
@@ -80,13 +80,13 @@ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
**Steps**:
```bash
# Check BootReceiver registration
adb shell "dumpsys package com.timesafari.dailynotification | grep -A10 -B5 BootReceiver"
adb shell "dumpsys package org.timesafari.dailynotification | grep -A10 -B5 BootReceiver"
```
**Expected Output**:
```
android.intent.action.LOCKED_BOOT_COMPLETED:
a440fcf com.timesafari.dailynotification/.BootReceiver filter 4e5fd5c
a440fcf org.timesafari.dailynotification/.BootReceiver filter 4e5fd5c
Action: "android.intent.action.LOCKED_BOOT_COMPLETED"
Action: "android.intent.action.BOOT_COMPLETED"
Action: "android.intent.action.MY_PACKAGE_REPLACED"
@@ -106,7 +106,7 @@ android.intent.action.LOCKED_BOOT_COMPLETED:
**Steps**:
```bash
# 1. Schedule notification
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# Tap "Test Notification" (5 minutes from now)
# 2. Verify initial scheduling
@@ -147,7 +147,7 @@ BootReceiver: Notification recovery completed: X/X recovered
**Steps**:
```bash
# 1. Schedule notification
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# Tap "Test Notification" (5 minutes from now)
# 2. Verify initial scheduling
@@ -182,7 +182,7 @@ BootReceiver: Notification recovery completed: X/X recovered
**Steps**:
```bash
# 1. Schedule notification
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# Tap "Test Notification" (5 minutes from now)
# 2. Reboot device
@@ -211,7 +211,7 @@ BootReceiver: Locked boot completed - ready for full recovery on unlock
**Steps**:
```bash
# 1. Launch app
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# 2. Tap "Exact Alarm Settings" button
# Should open exact alarm settings if needed
@@ -230,14 +230,14 @@ adb shell "dumpsys alarm | grep SCHEDULE_EXACT_ALARM"
### **Check BootReceiver Status**
```bash
# Verify registration
adb shell "dumpsys package com.timesafari.dailynotification | grep -A10 -B5 BootReceiver"
adb shell "dumpsys package org.timesafari.dailynotification | grep -A10 -B5 BootReceiver"
# Check if enabled
adb shell "pm list packages -d | grep timesafari"
# Should return nothing (app not disabled)
# Check permissions
adb shell "dumpsys package com.timesafari.dailynotification | grep -A5 -B5 permission"
adb shell "dumpsys package org.timesafari.dailynotification | grep -A5 -B5 permission"
```
### **Monitor Boot Events**
@@ -273,13 +273,13 @@ adb shell "dumpsys alarm | grep -A5 -B5 timesafari"
**Solutions**:
```bash
# Check if receiver is registered
adb shell "dumpsys package com.timesafari.dailynotification | grep BootReceiver"
adb shell "dumpsys package org.timesafari.dailynotification | grep BootReceiver"
# Check if app is disabled
adb shell "pm list packages -d | grep timesafari"
# Check if permissions are granted
adb shell "dumpsys package com.timesafari.dailynotification | grep RECEIVE_BOOT_COMPLETED"
adb shell "dumpsys package org.timesafari.dailynotification | grep RECEIVE_BOOT_COMPLETED"
```
### **Issue 2: Direct Boot Errors**
@@ -288,10 +288,10 @@ adb shell "dumpsys package com.timesafari.dailynotification | grep RECEIVE_BOOT_
**Solutions**:
```bash
# Check Direct Boot compatibility
adb shell "dumpsys package com.timesafari.dailynotification | grep directBootAware"
adb shell "dumpsys package org.timesafari.dailynotification | grep directBootAware"
# Check device protected storage
adb shell "ls -la /data/user_de/0/com.timesafari.dailynotification/"
adb shell "ls -la /data/user_de/0/org.timesafari.dailynotification/"
```
### **Issue 3: Exact Alarm Permission Denied**
@@ -303,7 +303,7 @@ adb shell "ls -la /data/user_de/0/com.timesafari.dailynotification/"
adb shell "dumpsys alarm | grep SCHEDULE_EXACT_ALARM"
# Open exact alarm settings
adb shell am start -a android.settings.REQUEST_SCHEDULE_EXACT_ALARM --es android.provider.extra.APP_PACKAGE com.timesafari.dailynotification
adb shell am start -a android.settings.REQUEST_SCHEDULE_EXACT_ALARM --es android.provider.extra.APP_PACKAGE org.timesafari.dailynotification
```
## 📊 **Success Metrics**

View File

@@ -31,7 +31,7 @@ This document provides comprehensive testing procedures for the DailyNotificatio
**Steps**:
```bash
# 1. Launch app and check channel status
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# 2. In app UI, tap "Check Channel Status"
# 3. Verify channel exists and is enabled
@@ -59,7 +59,7 @@ adb shell "dumpsys notification | grep -A5 'daily_default'"
**Steps**:
```bash
# 1. Block the notification channel manually
adb shell "am start -a android.settings.CHANNEL_NOTIFICATION_SETTINGS -e android.provider.extra.APP_PACKAGE com.timesafari.dailynotification -e android.provider.extra.CHANNEL_ID daily_default"
adb shell "am start -a android.settings.CHANNEL_NOTIFICATION_SETTINGS -e android.provider.extra.APP_PACKAGE org.timesafari.dailynotification -e android.provider.extra.CHANNEL_ID daily_default"
# 2. In system settings, disable the channel
# 3. Return to app and tap "Check Channel Status"
@@ -304,7 +304,7 @@ adb logcat -d | grep -i "recovery.*count"
**Steps**:
```bash
# 1. Schedule notification
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# Tap "Test Notification" in UI
# 2. Verify initial scheduling
@@ -383,7 +383,7 @@ adb logcat -d | grep -i "recovery.*performed.*recently.*skipping"
set -e
APP_PACKAGE="com.timesafari.dailynotification"
APP_PACKAGE="org.timesafari.dailynotification"
APP_ACTIVITY=".MainActivity"
TEST_TIMEOUT=300 # 5 minutes
@@ -645,7 +645,7 @@ import json
from typing import Dict, List, Optional, Tuple
class DailyNotificationTesterV2:
def __init__(self, package: str = "com.timesafari.dailynotification"):
def __init__(self, package: str = "org.timesafari.dailynotification"):
self.package = package
self.activity = f"{package}/.MainActivity"
self.test_results: Dict[str, bool] = {}
@@ -905,7 +905,7 @@ adb shell "svc data disable"
**Steps**:
```bash
# 1. Enable battery optimization for app
adb shell "dumpsys deviceidle whitelist -com.timesafari.dailynotification"
adb shell "dumpsys deviceidle whitelist -org.timesafari.dailynotification"
# 2. Schedule notification
# 3. Wait for notification
@@ -945,7 +945,7 @@ adb shell "dumpsys deviceidle whitelist -com.timesafari.dailynotification"
**Steps**:
```bash
# 1. Check initial memory usage
adb shell "dumpsys meminfo com.timesafari.dailynotification"
adb shell "dumpsys meminfo org.timesafari.dailynotification"
# 2. Schedule multiple notifications
# 3. Check memory usage again
@@ -1005,7 +1005,7 @@ adb logcat -d | grep -i "channelmanager"
adb shell "dumpsys alarm | grep SCHEDULE_EXACT_ALARM"
# Open exact alarm settings
adb shell "am start -a android.settings.REQUEST_SCHEDULE_EXACT_ALARM -d package:com.timesafari.dailynotification"
adb shell "am start -a android.settings.REQUEST_SCHEDULE_EXACT_ALARM -d package:org.timesafari.dailynotification"
```
#### Issue 3: JIT Refresh Not Working
@@ -1040,10 +1040,10 @@ adb logcat -d | grep -i "recovery.*performed.*recently"
# Comprehensive status check
adb shell "dumpsys notification | grep -A10 daily_default"
adb shell "dumpsys alarm | grep -A5 timesafari"
adb shell "dumpsys package com.timesafari.dailynotification | grep -A5 receiver"
adb shell "dumpsys package org.timesafari.dailynotification | grep -A5 receiver"
# Recovery state check
adb shell "run-as com.timesafari.dailynotification ls -la /data/data/com.timesafari.dailynotification/shared_prefs/"
adb shell "run-as org.timesafari.dailynotification ls -la /data/data/org.timesafari.dailynotification/shared_prefs/"
# Channel status check
adb shell "cmd notification list | grep daily_default"

View File

@@ -188,10 +188,10 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk
```bash
# Launch the app
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# Alternative: Launch with specific intent
adb shell am start -a android.intent.action.MAIN -n com.timesafari.dailynotification/.MainActivity
adb shell am start -a android.intent.action.MAIN -n org.timesafari.dailynotification/.MainActivity
```
### 8. Monitor App Logs
@@ -231,7 +231,7 @@ cd android && ./gradlew :app:assembleDebug
adb install app/build/outputs/apk/debug/app-debug.apk
# 6. Launch app
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# 7. Monitor logs
adb logcat -s "Capacitor" "DailyNotification" "Console"
@@ -259,7 +259,7 @@ npx cap run android
cd android
./gradlew :app:assembleDebug
adb install app/build/outputs/apk/debug/app-debug.apk
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
```
### Method 3: Using Monkey (Alternative Launch)
@@ -267,7 +267,7 @@ adb shell am start -n com.timesafari.dailynotification/.MainActivity
```bash
# Install and launch with Monkey
adb install app/build/outputs/apk/debug/app-debug.apk
adb shell monkey -p com.timesafari.dailynotification -c android.intent.category.LAUNCHER 1
adb shell monkey -p org.timesafari.dailynotification -c android.intent.category.LAUNCHER 1
```
## Troubleshooting
@@ -318,7 +318,7 @@ adb version
adb shell pm list packages | grep timesafari
# Uninstall existing app
adb uninstall com.timesafari.dailynotification
adb uninstall org.timesafari.dailynotification
# Install with force
adb install -r -t app/build/outputs/apk/debug/app-debug.apk
@@ -369,7 +369,7 @@ When the app launches successfully, you should see:
```bash
# ADB output
Starting: Intent { cmp=com.timesafari.dailynotification/.MainActivity }
Starting: Intent { cmp=org.timesafari.dailynotification/.MainActivity }
# Logcat output
D Capacitor: Starting BridgeActivity
@@ -415,7 +415,7 @@ The app should display:
./scripts/build-native.sh --platform android
cd android && ./gradlew :app:assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
```
### Automated Testing
@@ -426,7 +426,7 @@ adb wait-for-device
./scripts/build-native.sh --platform android
cd android && ./gradlew :app:assembleDebug
adb install app/build/outputs/apk/debug/app-debug.apk
adb shell am start -n com.timesafari.dailynotification/.MainActivity
adb shell am start -n org.timesafari.dailynotification/.MainActivity
# Run tests...
```

View File

@@ -54,7 +54,7 @@ po UNUserNotificationCenter.current().pendingNotificationRequests()
po await UNUserNotificationCenter.current().notificationSettings()
// Manually trigger BGTask (simulator only)
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.timesafari.dailynotification.fetch"]
```
---
@@ -78,7 +78,7 @@ e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWith
3. **Filter logs:**
- Click search box (top right)
- Type: `DNP-` or `com.timesafari.dailynotification`
- Type: `DNP-` or `org.timesafari.dailynotification`
- Press Enter
### Filter by Subsystem (Structured Logging):
@@ -86,14 +86,14 @@ e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWith
The plugin uses structured logging with subsystems:
```
com.timesafari.dailynotification.plugin # Plugin operations
com.timesafari.dailynotification.fetch # Fetch operations
com.timesafari.dailynotification.scheduler # Scheduling operations
com.timesafari.dailynotification.storage # Storage operations
org.timesafari.dailynotification.plugin # Plugin operations
org.timesafari.dailynotification.fetch # Fetch operations
org.timesafari.dailynotification.scheduler # Scheduling operations
org.timesafari.dailynotification.storage # Storage operations
```
**To filter by subsystem:**
- In Console.app search: `subsystem:com.timesafari.dailynotification`
- In Console.app search: `subsystem:org.timesafari.dailynotification`
---
@@ -108,7 +108,7 @@ com.timesafari.dailynotification.storage # Storage operations
xcrun simctl spawn booted log stream
# Stream only plugin logs (filtered)
xcrun simctl spawn booted log stream --predicate 'subsystem == "com.timesafari.dailynotification"'
xcrun simctl spawn booted log stream --predicate 'subsystem == "org.timesafari.dailynotification"'
# Stream with DNP- prefix filter
xcrun simctl spawn booted log stream | grep "DNP-"
@@ -121,7 +121,7 @@ xcrun simctl spawn booted log stream | grep "DNP-"
xcrun simctl spawn booted log stream > device.log 2>&1
# Save filtered logs
xcrun simctl spawn booted log stream --predicate 'subsystem == "com.timesafari.dailynotification"' > plugin.log 2>&1
xcrun simctl spawn booted log stream --predicate 'subsystem == "org.timesafari.dailynotification"' > plugin.log 2>&1
# Then analyze with grep
grep -E "\[DNP-(FETCH|SCHEDULER|PLUGIN)\]" device.log
@@ -144,7 +144,7 @@ xcrun simctl spawn booted log show --start "2025-11-15 10:00:00" --end "2025-11-
xcrun devicectl list devices
# Stream logs from physical device (requires device UDID)
xcrun devicectl device process launch --device <UDID> com.timesafari.dailynotification.test
xcrun devicectl device process launch --device <UDID> org.timesafari.dailynotification.test
# Or use Console.app for physical devices (easier)
```
@@ -162,7 +162,7 @@ xcrun devicectl device process launch --device <UDID> com.timesafari.dailynotifi
./scripts/validate-ios-logs.sh device.log
# From live stream
xcrun simctl spawn booted log stream --predicate 'subsystem == "com.timesafari.dailynotification"' | ./scripts/validate-ios-logs.sh
xcrun simctl spawn booted log stream --predicate 'subsystem == "org.timesafari.dailynotification"' | ./scripts/validate-ios-logs.sh
# From filtered grep
grep -E "\[DNP-(FETCH|SCHEDULER|PLUGIN)\]" device.log | ./scripts/validate-ios-logs.sh
@@ -207,7 +207,7 @@ grep -E "\[DNP-(FETCH|SCHEDULER|PLUGIN)\]" device.log | ./scripts/validate-ios-l
**Solutions:**
1. **Use specific filters:** `DNP-` instead of `DailyNotification`
2. **Filter by subsystem:** `subsystem:com.timesafari.dailynotification`
2. **Filter by subsystem:** `subsystem:org.timesafari.dailynotification`
3. **Use time range:** Only show logs from last 5 minutes
4. **Use validation script:** Automatically filters for important events
@@ -235,7 +235,7 @@ grep -E "\[DNP-(FETCH|SCHEDULER|PLUGIN)\]" device.log | ./scripts/validate-ios-l
```bash
# Stream plugin logs (simulator)
xcrun simctl spawn booted log stream --predicate 'subsystem == "com.timesafari.dailynotification"'
xcrun simctl spawn booted log stream --predicate 'subsystem == "org.timesafari.dailynotification"'
# Save logs to file
xcrun simctl spawn booted log stream > device.log 2>&1

View File

@@ -150,7 +150,7 @@ po UNUserNotificationCenter.current().pendingNotificationRequests()
# Check BGTask scheduling (simulator only)
# Use LLDB command in Xcode debugger:
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.timesafari.dailynotification.fetch"]
```
---
@@ -316,7 +316,7 @@ DNP-FETCH: BGTask rescheduled for [date]
**Manual Trigger (Simulator Only):**
```bash
# In Xcode debugger (LLDB)
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.timesafari.dailynotification.fetch"]
```
---
@@ -422,8 +422,8 @@ open DailyNotificationPlugin.xcodeproj
```xml
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.timesafari.dailynotification.fetch</string>
<string>com.timesafari.dailynotification.notify</string>
<string>org.timesafari.dailynotification.fetch</string>
<string>org.timesafari.dailynotification.notify</string>
</array>
```
@@ -464,7 +464,7 @@ po await UNUserNotificationCenter.current().notificationSettings()
**Check BGTask Status (Simulator Only):**
```swift
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.timesafari.dailynotification.fetch"]
```
**Check Storage:**

View File

@@ -137,7 +137,7 @@ Quick validation checklist (each step links to log verification):
- Background fetch
- Background processing (if using `BGProcessingTask`)
- Info.plist has:
- `BGTaskSchedulerPermittedIdentifiers` array with task identifier: `com.timesafari.dailynotification.fetch`
- `BGTaskSchedulerPermittedIdentifiers` array with task identifier: `org.timesafari.dailynotification.fetch`
- Plugin exposes:
- `scheduleDailyNotification()` method that schedules both prefetch and notification
@@ -186,7 +186,7 @@ Add structured logs at key points:
**On app startup:**
```
[DNP-FETCH] Registering BGTaskScheduler task (id=com.timesafari.dailynotification.fetch)
[DNP-FETCH] Registering BGTaskScheduler task (id=org.timesafari.dailynotification.fetch)
```
**When scheduling:**
@@ -197,7 +197,7 @@ Add structured logs at key points:
**When BGTask handler fires:**
```
[DNP-FETCH] BGTask handler invoked (task.identifier=com.timesafari.dailynotification.fetch)
[DNP-FETCH] BGTask handler invoked (task.identifier=org.timesafari.dailynotification.fetch)
```
**Inside prefetch logic:**
@@ -239,7 +239,7 @@ Add structured logs at key points:
2. **Open Xcode Debug Console** (View → Debug Area → Activate Console, or press Cmd+Shift+Y)
3. **In LLDB console, paste and execute:**
```bash
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.timesafari.dailynotification.fetch"]
```
4. Press Enter
@@ -270,7 +270,7 @@ Add structured logs at key points:
**Troubleshooting:**
- If LLDB command doesn't work, ensure the app is backgrounded first
- If you see "Simulate Background Fetch" in menu when app is running, it may be a different Xcode version - try the LLDB method anyway
- Verify BGTask identifier matches exactly: `com.timesafari.dailynotification.fetch`
- Verify BGTask identifier matches exactly: `org.timesafari.dailynotification.fetch`
### 5. Trigger or Wait for Notification
@@ -544,7 +544,7 @@ When everything is wired correctly, one full cycle should produce:
**Expected logs:**
```
[DNP-FETCH] Registering BGTaskScheduler task (id=com.timesafari.dailynotification.fetch)
[DNP-FETCH] Registering BGTaskScheduler task (id=org.timesafari.dailynotification.fetch)
[DNP-PLUGIN] Startup complete (hasPendingSchedules=true|false)
```
@@ -585,7 +585,7 @@ When everything is wired correctly, one full cycle should produce:
**Expected logs:**
```
[DNP-FETCH] BGTask handler invoked (id=com.timesafari.dailynotification.fetch)
[DNP-FETCH] BGTask handler invoked (id=org.timesafari.dailynotification.fetch)
[DNP-FETCH] Resolved next notification needing content (time=..., scheduleId=...)
[DNP-FETCH] Starting fetch from <URL> (notificationTime=..., jwtPresent=true)
[DNP-FETCH] Fetch success (status=200, bytes=1234, ttl=86400)

Some files were not shown because too many files have changed in this diff Show More