Merge branch 'master' into ios-2

This commit is contained in:
Matthew
2025-11-18 19:29:26 -08:00
6 changed files with 327 additions and 297 deletions

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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);

View File

@@ -34,7 +34,7 @@
<action android:name="com.timesafari.daily.NOTIFICATION" />
</intent-filter>
</receiver>
<!-- NotifyReceiver for AlarmManager-based notifications -->
<receiver
android:name="com.timesafari.dailynotification.NotifyReceiver"

View File

@@ -51,28 +51,24 @@
</head>
<body>
<div class="container">
<h1>🔔 DailyNotification Plugin Test</h1>
<p>Test the DailyNotification plugin functionality</p>
<p style="font-size: 12px; opacity: 0.8;">Build: 2025-10-14 05:00:00 UTC</p>
<div id="statusCard" class="status" style="margin-bottom: 20px; font-size: 14px;">
<strong>Plugin Status</strong><br>
<div style="margin-top: 10px;">
⚙️ Plugin Settings: <span id="configStatus">Not configured</span><br>
🔌 Native Fetcher: <span id="fetcherStatus">Not configured</span><br>
🔔 Notifications: <span id="notificationPermStatus">Checking...</span><br>
⏰ Exact Alarms: <span id="exactAlarmPermStatus">Checking...</span><br>
📢 Channel: <span id="channelStatus">Checking...</span><br>
<div id="pluginStatusContent" style="margin-top: 8px;">
Loading plugin status...
</div>
</div>
</div>
<button class="button" onclick="testPlugin()">Test Plugin</button>
<button class="button" onclick="configurePlugin()">Configure Plugin</button>
<button class="button" onclick="checkStatus()">Check Status</button>
<h2>🔔 Notification Tests</h2>
<button class="button" onclick="testNotification()">Test Notification</button>
<button class="button" onclick="scheduleNotification()">Schedule Notification</button>
<button class="button" onclick="showReminder()">Show Reminder</button>
<h2>🔐 Permission Management</h2>
<button class="button" onclick="checkPermissions()">Check Permissions</button>
<button class="button" onclick="requestPermissions()">Request Permissions</button>
<button class="button" onclick="openExactAlarmSettings()">Exact Alarm Settings</button>
<h2>📢 Channel Management</h2>
<button class="button" onclick="checkChannelStatus()">Check Channel Status</button>
<button class="button" onclick="openChannelSettings()">Open Channel Settings</button>
<button class="button" onclick="checkComprehensiveStatus()">Comprehensive Status</button>
<button class="button" onclick="testNotification()">Test Notification</button>
<button class="button" onclick="checkComprehensiveStatus()">Full System Status</button>
<div id="status" class="status">
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!<br>✅ Plugin settings<br>✅ 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:<br>
Enabled: ${result.isEnabled}<br>
Next Notification: ${nextTime}<br>
Pending: ${result.pending}<br>
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'}<br>
📅 Next Notification: ${nextTime}<br>
⏳ 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 + ')<br>' +
'🔔 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<br><br>' +
'Settings opened automatically.<br>' +
'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!<br>' +
'📥 Prefetch: ' + prefetchTimeReadable + ' (' + prefetchTimeString + ')<br>' +
'🔔 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:<br>
Notifications: ${result.notificationsEnabled ? '✅' : '❌'}<br>
Exact Alarm: ${result.exactAlarmEnabled ? '✅' : '❌'}<br>
Wake Lock: ${result.wakeLockEnabled ? '✅' : '❌'}<br>
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
});
</script>
</body>

View File

@@ -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') + ':' +