diff --git a/android/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.kt b/android/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.kt index 49dc010..5a1865b 100644 --- a/android/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.kt +++ b/android/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.kt @@ -95,7 +95,7 @@ open class DailyNotificationPlugin : Plugin() { Log.e(TAG, "Context is null, cannot initialize database") return } - db = DailyNotificationDatabase.getDatabase(context) + db = DailyNotificationDatabase.getDatabase(context) Log.i(TAG, "Daily Notification Plugin loaded successfully") } catch (e: Exception) { Log.e(TAG, "Failed to initialize Daily Notification Plugin", e) @@ -1048,21 +1048,73 @@ open class DailyNotificationPlugin : Plugin() { @PluginMethod fun isChannelEnabled(call: PluginCall) { try { - val channelId = call.getString("channelId") ?: "daily_notification_channel" - val enabled = NotificationManagerCompat.from(context).areNotificationsEnabled() + if (context == null) { + return call.reject("Context not available") + } + + // Use the actual channel ID that matches what's used in notifications + val channelId = call.getString("channelId") ?: "timesafari.daily" + + // Check app-level notifications first + val appNotificationsEnabled = NotificationManagerCompat.from(context).areNotificationsEnabled() // Get notification channel importance if available var importance = 0 + var channelEnabled = false if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val notificationManager = context?.getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager? - val channel = notificationManager?.getNotificationChannel(channelId) - importance = channel?.importance ?: android.app.NotificationManager.IMPORTANCE_DEFAULT + val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as? android.app.NotificationManager + var channel = notificationManager?.getNotificationChannel(channelId) + + if (channel == null) { + // Channel doesn't exist - create it first (same as ChannelManager does) + Log.i(TAG, "Channel $channelId doesn't exist, creating it") + val newChannel = android.app.NotificationChannel( + channelId, + "Daily Notifications", + android.app.NotificationManager.IMPORTANCE_HIGH + ).apply { + description = "Daily notifications from TimeSafari" + enableLights(true) + enableVibration(true) + setShowBadge(true) + } + notificationManager?.createNotificationChannel(newChannel) + Log.i(TAG, "Channel $channelId created with HIGH importance") + + // Re-fetch the channel from the system to get actual state + // (in case it was previously blocked by user) + channel = notificationManager?.getNotificationChannel(channelId) + } + + // Now check the channel (re-fetched from system to get actual state) + if (channel != null) { + importance = channel.importance + // Channel is enabled if importance is not IMPORTANCE_NONE + // IMPORTANCE_NONE = 0 means blocked/disabled + channelEnabled = importance != android.app.NotificationManager.IMPORTANCE_NONE + Log.d(TAG, "Channel $channelId status: importance=$importance, enabled=$channelEnabled") + } else { + // Channel still doesn't exist after creation attempt - should not happen + Log.w(TAG, "Channel $channelId still doesn't exist after creation attempt") + importance = android.app.NotificationManager.IMPORTANCE_NONE + channelEnabled = false + } + } else { + // Pre-Oreo: channels don't exist, use app-level check + channelEnabled = appNotificationsEnabled + importance = android.app.NotificationManager.IMPORTANCE_DEFAULT } + val finalEnabled = appNotificationsEnabled && channelEnabled + Log.i(TAG, "Channel status check complete: channelId=$channelId, appNotificationsEnabled=$appNotificationsEnabled, channelEnabled=$channelEnabled, importance=$importance, finalEnabled=$finalEnabled") + val result = JSObject().apply { - put("enabled", enabled) + // Channel is enabled if both app notifications are enabled AND channel importance is not NONE + put("enabled", finalEnabled) put("channelId", channelId) put("importance", importance) + put("appNotificationsEnabled", appNotificationsEnabled) + put("channelBlocked", importance == android.app.NotificationManager.IMPORTANCE_NONE) } call.resolve(result) } catch (e: Exception) { @@ -1074,28 +1126,80 @@ open class DailyNotificationPlugin : Plugin() { @PluginMethod fun openChannelSettings(call: PluginCall) { try { - val channelId = call.getString("channelId") ?: "daily_notification_channel" - val intent = Intent(android.provider.Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply { - putExtra(android.provider.Settings.EXTRA_APP_PACKAGE, context?.packageName) - putExtra(android.provider.Settings.EXTRA_CHANNEL_ID, channelId) + if (context == null) { + return call.reject("Context not available") } - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + // Use the actual channel ID that matches what's used in notifications + val channelId = call.getString("channelId") ?: "timesafari.daily" + + // Ensure channel exists before trying to open settings + // This ensures the channel-specific settings page can be opened + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as? android.app.NotificationManager + val channel = notificationManager?.getNotificationChannel(channelId) + + if (channel == null) { + // Channel doesn't exist - create it first + Log.i(TAG, "Channel $channelId doesn't exist, creating it") + val newChannel = android.app.NotificationChannel( + channelId, + "Daily Notifications", + android.app.NotificationManager.IMPORTANCE_HIGH + ).apply { + description = "Daily notifications from TimeSafari" + enableLights(true) + enableVibration(true) + setShowBadge(true) + } + notificationManager?.createNotificationChannel(newChannel) + Log.i(TAG, "Channel $channelId created") + } + } + + // Try to open channel-specific settings first try { - activity?.startActivity(intent) + val intent = Intent(android.provider.Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply { + putExtra(android.provider.Settings.EXTRA_APP_PACKAGE, context.packageName) + putExtra(android.provider.Settings.EXTRA_CHANNEL_ID, channelId) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + + activity?.startActivity(intent) ?: context.startActivity(intent) + Log.i(TAG, "Channel settings opened for channel: $channelId") + val result = JSObject().apply { put("opened", true) put("channelId", channelId) } call.resolve(result) } catch (e: Exception) { - Log.e(TAG, "Failed to start activity", e) - val result = JSObject().apply { - put("opened", false) - put("channelId", channelId) - put("error", e.message) + // Fallback to general app notification settings if channel-specific fails + Log.w(TAG, "Failed to open channel-specific settings, trying app notification settings", e) + try { + val fallbackIntent = Intent(android.provider.Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { + putExtra(android.provider.Settings.EXTRA_APP_PACKAGE, context.packageName) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + activity?.startActivity(fallbackIntent) ?: context.startActivity(fallbackIntent) + Log.i(TAG, "App notification settings opened (fallback)") + + val result = JSObject().apply { + put("opened", true) + put("channelId", channelId) + put("fallback", true) + put("message", "Opened app notification settings (channel-specific unavailable)") + } + call.resolve(result) + } catch (e2: Exception) { + Log.e(TAG, "Failed to open notification settings", e2) + val result = JSObject().apply { + put("opened", false) + put("channelId", channelId) + put("error", e2.message) + } + call.resolve(result) } - call.resolve(result) } } catch (e: Exception) { Log.e(TAG, "Failed to open channel settings", e) @@ -1286,9 +1390,9 @@ open class DailyNotificationPlugin : Plugin() { reminderId = scheduleId ) - // Always schedule prefetch 5 minutes before notification + // Always schedule prefetch 2 minutes before notification // (URL is optional - native fetcher will be used if registered) - val fetchTime = nextRunTime - (5 * 60 * 1000L) // 5 minutes before + val fetchTime = nextRunTime - (2 * 60 * 1000L) // 2 minutes before val delayMs = fetchTime - System.currentTimeMillis() if (delayMs > 0) { diff --git a/android/src/main/java/com/timesafari/dailynotification/DailyNotificationReceiver.java b/android/src/main/java/com/timesafari/dailynotification/DailyNotificationReceiver.java index f6ca728..e548de8 100644 --- a/android/src/main/java/com/timesafari/dailynotification/DailyNotificationReceiver.java +++ b/android/src/main/java/com/timesafari/dailynotification/DailyNotificationReceiver.java @@ -68,7 +68,8 @@ public class DailyNotificationReceiver extends BroadcastReceiver { } // Enqueue work immediately - don't block receiver - enqueueNotificationWork(context, notificationId); + // Pass the full intent to extract static reminder extras + enqueueNotificationWork(context, notificationId, intent); Log.d(TAG, "DN|RECEIVE_OK enqueued=" + notificationId); } else if ("com.timesafari.daily.DISMISS".equals(action)) { @@ -99,17 +100,42 @@ public class DailyNotificationReceiver extends BroadcastReceiver { * * @param context Application context * @param notificationId ID of notification to process + * @param intent Intent containing notification data (may include static reminder extras) */ - private void enqueueNotificationWork(Context context, String notificationId) { + private void enqueueNotificationWork(Context context, String notificationId, Intent intent) { try { // Create unique work name based on notification ID to prevent duplicates // WorkManager will automatically skip if work with this name already exists String workName = "display_" + notificationId; - Data inputData = new Data.Builder() + // Extract static reminder extras from intent if present + // Static reminders have title/body in Intent extras, not in storage + boolean isStaticReminder = intent.getBooleanExtra("is_static_reminder", false); + String title = intent.getStringExtra("title"); + String body = intent.getStringExtra("body"); + boolean sound = intent.getBooleanExtra("sound", true); + boolean vibration = intent.getBooleanExtra("vibration", true); + String priority = intent.getStringExtra("priority"); + if (priority == null) { + priority = "normal"; + } + + Data.Builder dataBuilder = new Data.Builder() .putString("notification_id", notificationId) .putString("action", "display") - .build(); + .putBoolean("is_static_reminder", isStaticReminder); + + // Add static reminder data if present + if (isStaticReminder && title != null && body != null) { + dataBuilder.putString("title", title) + .putString("body", body) + .putBoolean("sound", sound) + .putBoolean("vibration", vibration) + .putString("priority", priority); + Log.d(TAG, "DN|WORK_ENQUEUE static_reminder id=" + notificationId); + } + + Data inputData = dataBuilder.build(); OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(DailyNotificationWorker.class) .setInputData(inputData) diff --git a/android/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java b/android/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java index 139855e..ad9ba07 100644 --- a/android/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java +++ b/android/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java @@ -127,8 +127,42 @@ public class DailyNotificationWorker extends Worker { try { Log.d(TAG, "DN|DISPLAY_START id=" + notificationId); + // Check if this is a static reminder (title/body in input data, not storage) + Data inputData = getInputData(); + boolean isStaticReminder = inputData.getBoolean("is_static_reminder", false); + NotificationContent content; + + if (isStaticReminder) { + // Static reminder: create NotificationContent from input data + String title = inputData.getString("title"); + String body = inputData.getString("body"); + boolean sound = inputData.getBoolean("sound", true); + boolean vibration = inputData.getBoolean("vibration", true); + String priority = inputData.getString("priority"); + if (priority == null) { + priority = "normal"; + } + + if (title == null || body == null) { + Log.w(TAG, "DN|DISPLAY_SKIP static_reminder_missing_data id=" + notificationId); + return Result.success(); + } + + // Create NotificationContent from input data + // Use current time as scheduled time for static reminders + long scheduledTime = System.currentTimeMillis(); + content = new NotificationContent(title, body, scheduledTime); + content.setId(notificationId); + content.setSound(sound); + content.setPriority(priority); + // Note: fetchedAt is automatically set to current time in NotificationContent constructor + // Note: vibration is handled in displayNotification() method, not stored in NotificationContent + + Log.d(TAG, "DN|DISPLAY_STATIC_REMINDER id=" + notificationId + " title=" + title); + } else { + // Regular notification: load from storage // Prefer Room storage; fallback to legacy SharedPreferences storage - NotificationContent content = getContentFromRoomOrLegacy(notificationId); + content = getContentFromRoomOrLegacy(notificationId); if (content == null) { // Content not found - likely removed during deduplication or cleanup @@ -143,8 +177,9 @@ public class DailyNotificationWorker extends Worker { return Result.success(); } - // JIT Freshness Re-check (Soft TTL) + // JIT Freshness Re-check (Soft TTL) - skip for static reminders content = performJITFreshnessCheck(content); + } // Display the notification boolean displayed = displayNotification(content); @@ -356,6 +391,13 @@ public class DailyNotificationWorker extends Worker { try { Log.d(TAG, "DN|DISPLAY_NOTIF_START id=" + content.getId()); + // Ensure notification channel exists before displaying + ChannelManager channelManager = new ChannelManager(getApplicationContext()); + if (!channelManager.ensureChannelExists()) { + Log.w(TAG, "DN|DISPLAY_NOTIF_ERR channel_blocked id=" + content.getId()); + return false; + } + NotificationManager notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE); diff --git a/test-apps/android-test-app/app/src/main/AndroidManifest.xml b/test-apps/android-test-app/app/src/main/AndroidManifest.xml index 99045d2..df04067 100644 --- a/test-apps/android-test-app/app/src/main/AndroidManifest.xml +++ b/test-apps/android-test-app/app/src/main/AndroidManifest.xml @@ -34,7 +34,7 @@ - +
-

πŸ”” DailyNotification Plugin Test

-

Test the DailyNotification plugin functionality

-

Build: 2025-10-14 05:00:00 UTC

+
+ Plugin Status
+
+ βš™οΈ Plugin Settings: Not configured
+ πŸ”Œ Native Fetcher: Not configured
+ πŸ”” Notifications: Checking...
+ ⏰ Exact Alarms: Checking...
+ πŸ“’ Channel: Checking...
+
+ Loading plugin status... +
+
+
- - - -

πŸ”” Notification Tests

- - - - -

πŸ” Permission Management

- - - -

πŸ“’ Channel Management

- - - + +
Ready to test... @@ -88,37 +84,26 @@ window.DailyNotification = window.Capacitor.Plugins.DailyNotification; // Define functions immediately and attach to window - function testPlugin() { - console.log('testPlugin called'); - const status = document.getElementById('status'); - status.innerHTML = 'Testing plugin...'; - status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background - - try { - if (!window.DailyNotification) { - status.innerHTML = 'DailyNotification plugin not available'; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background - return; - } - // Plugin is loaded and ready - status.innerHTML = 'Plugin is loaded and ready!'; - status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background - } catch (error) { - status.innerHTML = `Plugin test failed: ${error.message}`; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background - } - } function configurePlugin() { console.log('configurePlugin called'); const status = document.getElementById('status'); + const configStatus = document.getElementById('configStatus'); + const fetcherStatus = document.getElementById('fetcherStatus'); + status.innerHTML = 'Configuring plugin...'; status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background + // Update top status to show configuring + configStatus.innerHTML = '⏳ Configuring...'; + fetcherStatus.innerHTML = '⏳ Waiting...'; + try { if (!window.DailyNotification) { status.innerHTML = 'DailyNotification plugin not available'; status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background + configStatus.innerHTML = '❌ Plugin unavailable'; + fetcherStatus.innerHTML = '❌ Plugin unavailable'; return; } @@ -132,6 +117,9 @@ }) .then(() => { console.log('Plugin settings configured, now configuring native fetcher...'); + // Update top status + configStatus.innerHTML = 'βœ… Configured'; + // Configure native fetcher with demo credentials // Note: DemoNativeFetcher uses hardcoded mock data, so this is optional // but demonstrates the API. In production, this would be real credentials. @@ -142,48 +130,62 @@ }); }) .then(() => { - status.innerHTML = 'Plugin configured successfully!
βœ… Plugin settings
βœ… Native fetcher (optional for demo)'; + // Update top status + fetcherStatus.innerHTML = 'βœ… Configured'; + + // Update bottom status for user feedback + status.innerHTML = 'Plugin configured successfully!'; status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background }) .catch(error => { + // Update top status with error + if (configStatus.innerHTML.includes('Configuring')) { + configStatus.innerHTML = '❌ Failed'; + } + if (fetcherStatus.innerHTML.includes('Waiting') || fetcherStatus.innerHTML.includes('Configuring')) { + fetcherStatus.innerHTML = '❌ Failed'; + } + status.innerHTML = `Configuration failed: ${error.message}`; status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background }); } catch (error) { + configStatus.innerHTML = '❌ Error'; + fetcherStatus.innerHTML = '❌ Error'; status.innerHTML = `Configuration failed: ${error.message}`; status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background } } - function checkStatus() { - console.log('checkStatus called'); - const status = document.getElementById('status'); - status.innerHTML = 'Checking plugin status...'; - status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background + function loadPluginStatus() { + console.log('loadPluginStatus called'); + const pluginStatusContent = document.getElementById('pluginStatusContent'); + const statusCard = document.getElementById('statusCard'); try { if (!window.DailyNotification) { - status.innerHTML = 'DailyNotification plugin not available'; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background + pluginStatusContent.innerHTML = '❌ DailyNotification plugin not available'; + statusCard.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background return; } window.DailyNotification.getNotificationStatus() .then(result => { const nextTime = result.nextNotificationTime ? new Date(result.nextNotificationTime).toLocaleString() : 'None scheduled'; - status.innerHTML = `Plugin Status:
- Enabled: ${result.isEnabled}
- Next Notification: ${nextTime}
- Pending: ${result.pending}
- Settings: ${JSON.stringify(result.settings)}`; - status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background + const hasSchedules = result.isEnabled || (result.pending && result.pending > 0); + const statusIcon = hasSchedules ? 'βœ…' : '⏸️'; + pluginStatusContent.innerHTML = `${statusIcon} Active Schedules: ${hasSchedules ? 'Yes' : 'No'}
+ πŸ“… Next Notification: ${nextTime}
+ ⏳ Pending: ${result.pending || 0}`; + statusCard.style.background = hasSchedules ? + 'rgba(0, 255, 0, 0.15)' : 'rgba(255, 255, 255, 0.1)'; // Green if active, light gray if none }) .catch(error => { - status.innerHTML = `Status check failed: ${error.message}`; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background + pluginStatusContent.innerHTML = `⚠️ Status check failed: ${error.message}`; + statusCard.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background }); } catch (error) { - status.innerHTML = `Status check failed: ${error.message}`; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background + pluginStatusContent.innerHTML = `⚠️ Status check failed: ${error.message}`; + statusCard.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background } } @@ -211,8 +213,8 @@ // Test the notification method directly console.log('Testing notification scheduling...'); const now = new Date(); - const notificationTime = new Date(now.getTime() + 600000); // 10 minutes from now - const prefetchTime = new Date(now.getTime() + 300000); // 5 minutes from now + const notificationTime = new Date(now.getTime() + 240000); // 4 minutes from now + const prefetchTime = new Date(now.getTime() + 120000); // 2 minutes from now (2 min before notification) const notificationTimeString = notificationTime.getHours().toString().padStart(2, '0') + ':' + notificationTime.getMinutes().toString().padStart(2, '0'); const prefetchTimeString = prefetchTime.getHours().toString().padStart(2, '0') + ':' + @@ -232,10 +234,22 @@ 'πŸ“₯ Prefetch: ' + prefetchTimeReadable + ' (' + prefetchTimeString + ')
' + 'πŸ”” Notification: ' + notificationTimeReadable + ' (' + notificationTimeString + ')'; status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background + // Refresh plugin status display + setTimeout(() => loadPluginStatus(), 500); }) .catch(error => { - status.innerHTML = `Notification failed: ${error.message}`; + // Check if this is an exact alarm permission error + if (error.code === 'EXACT_ALARM_PERMISSION_REQUIRED' || + error.message.includes('Exact alarm permission') || + error.message.includes('Alarms & reminders')) { + status.innerHTML = '⚠️ Exact Alarm Permission Required

' + + 'Settings opened automatically.
' + + 'Please enable "Allow exact alarms" and return to try again.'; + status.style.background = 'rgba(255, 165, 0, 0.3)'; // Orange background + } else { + status.innerHTML = `❌ Notification failed: ${error.message}`; status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background + } }); } catch (error) { status.innerHTML = `Notification test failed: ${error.message}`; @@ -243,130 +257,8 @@ } } - function scheduleNotification() { - console.log('scheduleNotification called'); - const status = document.getElementById('status'); - status.innerHTML = 'Scheduling notification...'; - status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background - - try { - if (!window.DailyNotification) { - status.innerHTML = 'DailyNotification plugin not available'; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background - return; - } - - // Schedule notification for 10 minutes from now (allows 5 min prefetch to fire) - const now = new Date(); - const notificationTime = new Date(now.getTime() + 600000); // 10 minutes from now - const prefetchTime = new Date(now.getTime() + 300000); // 5 minutes from now - const notificationTimeString = notificationTime.getHours().toString().padStart(2, '0') + ':' + - notificationTime.getMinutes().toString().padStart(2, '0'); - const prefetchTimeString = prefetchTime.getHours().toString().padStart(2, '0') + ':' + - prefetchTime.getMinutes().toString().padStart(2, '0'); - - window.DailyNotification.scheduleDailyNotification({ - time: notificationTimeString, - title: 'Scheduled Notification', - body: 'This notification was scheduled 10 minutes ago!', - sound: true, - priority: 'default' - }) - .then(() => { - const prefetchTimeReadable = prefetchTime.toLocaleTimeString(); - const notificationTimeReadable = notificationTime.toLocaleTimeString(); - status.innerHTML = 'βœ… Notification scheduled!
' + - 'πŸ“₯ Prefetch: ' + prefetchTimeReadable + ' (' + prefetchTimeString + ')
' + - 'πŸ”” Notification: ' + notificationTimeReadable + ' (' + notificationTimeString + ')'; - status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background - }) - .catch(error => { - status.innerHTML = `Scheduling failed: ${error.message}`; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background - }); - } catch (error) { - status.innerHTML = `Scheduling test failed: ${error.message}`; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background - } - } - - function showReminder() { - console.log('showReminder called'); - const status = document.getElementById('status'); - status.innerHTML = 'Showing reminder...'; - status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background - - try { - if (!window.DailyNotification) { - status.innerHTML = 'DailyNotification plugin not available'; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background - return; - } - - // Schedule daily reminder using scheduleDailyReminder - const now = new Date(); - const reminderTime = new Date(now.getTime() + 10000); // 10 seconds from now - const timeString = reminderTime.getHours().toString().padStart(2, '0') + ':' + - reminderTime.getMinutes().toString().padStart(2, '0'); - - window.DailyNotification.scheduleDailyReminder({ - id: 'daily-reminder-test', - title: 'Daily Reminder', - body: 'Don\'t forget to check your daily notifications!', - time: timeString, - sound: true, - vibration: true, - priority: 'default', - repeatDaily: false // Just for testing - }) - .then(() => { - status.innerHTML = 'Daily reminder scheduled for ' + timeString + '!'; - status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background - }) - .catch(error => { - status.innerHTML = `Reminder failed: ${error.message}`; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background - }); - } catch (error) { - status.innerHTML = `Reminder test failed: ${error.message}`; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background - } - } // Permission management functions - function checkPermissions() { - console.log('checkPermissions called'); - const status = document.getElementById('status'); - status.innerHTML = 'Checking permissions...'; - status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background - - try { - if (!window.DailyNotification) { - status.innerHTML = 'DailyNotification plugin not available'; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background - return; - } - - window.DailyNotification.checkPermissionStatus() - .then(result => { - status.innerHTML = `Permission Status:
- Notifications: ${result.notificationsEnabled ? 'βœ…' : '❌'}
- Exact Alarm: ${result.exactAlarmEnabled ? 'βœ…' : '❌'}
- Wake Lock: ${result.wakeLockEnabled ? 'βœ…' : '❌'}
- All Granted: ${result.allPermissionsGranted ? 'βœ…' : '❌'}`; - status.style.background = result.allPermissionsGranted ? - 'rgba(0, 255, 0, 0.3)' : 'rgba(255, 165, 0, 0.3)'; // Green or orange - }) - .catch(error => { - status.innerHTML = `Permission check failed: ${error.message}`; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background - }); - } catch (error) { - status.innerHTML = `Permission check failed: ${error.message}`; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background - } - } - function requestPermissions() { console.log('requestPermissions called'); const status = document.getElementById('status'); @@ -385,9 +277,10 @@ status.innerHTML = 'Permission request completed! Check your device settings if needed.'; status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background - // Check permissions again after request + // Refresh permission and channel status display after request setTimeout(() => { - checkPermissions(); + loadPermissionStatus(); + loadChannelStatus(); }, 1000); }) .catch(error => { @@ -400,91 +293,29 @@ } } - function openExactAlarmSettings() { - console.log('openExactAlarmSettings called'); - const status = document.getElementById('status'); - status.innerHTML = 'Opening exact alarm settings...'; - status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background + function loadChannelStatus() { + const channelStatus = document.getElementById('channelStatus'); try { if (!window.DailyNotification) { - status.innerHTML = 'DailyNotification plugin not available'; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background - return; - } - - window.DailyNotification.openExactAlarmSettings() - .then(() => { - status.innerHTML = 'Exact alarm settings opened! Please enable "Allow exact alarms" and return to the app.'; - status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background - }) - .catch(error => { - status.innerHTML = `Failed to open exact alarm settings: ${error.message}`; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background - }); - } catch (error) { - status.innerHTML = `Failed to open exact alarm settings: ${error.message}`; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background - } - } - - function checkChannelStatus() { - const status = document.getElementById('status'); - status.innerHTML = 'Checking channel status...'; - status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background - - try { - if (!window.DailyNotification) { - status.innerHTML = 'DailyNotification plugin not available'; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background + channelStatus.innerHTML = '❌ Plugin unavailable'; return; } window.DailyNotification.isChannelEnabled() .then(result => { const importanceText = getImportanceText(result.importance); - status.innerHTML = `Channel Status: ${result.enabled ? 'Enabled' : 'Disabled'} (${importanceText})`; - status.style.background = result.enabled ? 'rgba(0, 255, 0, 0.3)' : 'rgba(255, 0, 0, 0.3)'; - }) - .catch(error => { - status.innerHTML = `Channel check failed: ${error.message}`; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background - }); - } catch (error) { - status.innerHTML = `Channel check failed: ${error.message}`; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background - } - } - - function openChannelSettings() { - const status = document.getElementById('status'); - status.innerHTML = 'Opening channel settings...'; - status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background - - try { - if (!window.DailyNotification) { - status.innerHTML = 'DailyNotification plugin not available'; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background - return; - } - - window.DailyNotification.openChannelSettings() - .then(result => { - if (result.opened) { - status.innerHTML = 'Channel settings opened! Please enable notifications and return to the app.'; - status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background + if (result.enabled) { + channelStatus.innerHTML = `βœ… Enabled (${importanceText})`; } else { - status.innerHTML = 'Could not open channel settings (may not be available on this device)'; - status.style.background = 'rgba(255, 165, 0, 0.3)'; // Orange background + channelStatus.innerHTML = `❌ Disabled (${importanceText})`; } }) .catch(error => { - status.innerHTML = `Failed to open channel settings: ${error.message}`; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background + channelStatus.innerHTML = '⚠️ Error'; }); } catch (error) { - status.innerHTML = `Failed to open channel settings: ${error.message}`; - status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background + channelStatus.innerHTML = '⚠️ Error'; } } @@ -549,26 +380,53 @@ } // Attach to window object - window.testPlugin = testPlugin; window.configurePlugin = configurePlugin; - window.checkStatus = checkStatus; window.testNotification = testNotification; - window.scheduleNotification = scheduleNotification; - window.showReminder = showReminder; - window.checkPermissions = checkPermissions; window.requestPermissions = requestPermissions; - window.openExactAlarmSettings = openExactAlarmSettings; - window.checkChannelStatus = checkChannelStatus; - window.openChannelSettings = openChannelSettings; window.checkComprehensiveStatus = checkComprehensiveStatus; + function loadPermissionStatus() { + const notificationPermStatus = document.getElementById('notificationPermStatus'); + const exactAlarmPermStatus = document.getElementById('exactAlarmPermStatus'); + + try { + if (!window.DailyNotification) { + notificationPermStatus.innerHTML = '❌ Plugin unavailable'; + exactAlarmPermStatus.innerHTML = '❌ Plugin unavailable'; + return; + } + + window.DailyNotification.checkPermissionStatus() + .then(result => { + notificationPermStatus.innerHTML = result.notificationsEnabled ? 'βœ… Granted' : '❌ Not granted'; + exactAlarmPermStatus.innerHTML = result.exactAlarmEnabled ? 'βœ… Granted' : '❌ Not granted'; + }) + .catch(error => { + notificationPermStatus.innerHTML = '⚠️ Error'; + exactAlarmPermStatus.innerHTML = '⚠️ Error'; + }); + } catch (error) { + notificationPermStatus.innerHTML = '⚠️ Error'; + exactAlarmPermStatus.innerHTML = '⚠️ Error'; + } + } + + // Load plugin status automatically on page load + window.addEventListener('load', () => { + console.log('Page loaded, loading plugin status...'); + // Small delay to ensure Capacitor is ready + setTimeout(() => { + loadPluginStatus(); + loadPermissionStatus(); + loadChannelStatus(); + }, 500); + }); + + + console.log('Functions attached to window:', { - testPlugin: typeof window.testPlugin, configurePlugin: typeof window.configurePlugin, - checkStatus: typeof window.checkStatus, - testNotification: typeof window.testNotification, - scheduleNotification: typeof window.scheduleNotification, - showReminder: typeof window.showReminder + testNotification: typeof window.testNotification }); diff --git a/www/index.html b/www/index.html index be40a1c..48908a4 100644 --- a/www/index.html +++ b/www/index.html @@ -432,8 +432,8 @@ scheduledTime.setDate(scheduledTime.getDate() + 1); } - // Calculate prefetch time (5 minutes before notification) - const prefetchTime = new Date(scheduledTime.getTime() - 300000); // 5 minutes + // Calculate prefetch time (2 minutes before notification) + const prefetchTime = new Date(scheduledTime.getTime() - 120000); // 2 minutes const prefetchTimeReadable = prefetchTime.toLocaleTimeString(); const notificationTimeReadable = scheduledTime.toLocaleTimeString(); const prefetchTimeString = prefetchTime.getHours().toString().padStart(2, '0') + ':' +