Implement UNUserNotificationCenterDelegate in AppDelegate to display notifications when app is in foreground. Add visual feedback indicator in test app UI to confirm notification delivery. Changes: - AppDelegate: Conform to UNUserNotificationCenterDelegate protocol - AppDelegate: Implement willPresent and didReceive delegate methods - AppDelegate: Set delegate at multiple lifecycle points to ensure it's always active (immediate, after Capacitor init, on app active) - UI: Add notification received indicator in status card - UI: Add periodic check for notification delivery (every 5 seconds) - UI: Add instructions on where to look for notification banner - Docs: Add IOS_LOGGING_GUIDE.md for debugging iOS logs This fixes the issue where scheduled notifications were not visible when the app was in the foreground. The delegate method now properly presents notifications with banner, sound, and badge options. Verified working: Logs show delegate method called successfully when notification fires, with proper presentation options set.
476 lines
23 KiB
HTML
476 lines
23 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
|
<meta http-equiv="Pragma" content="no-cache">
|
|
<meta http-equiv="Expires" content="0">
|
|
<title>DailyNotification Plugin Test</title>
|
|
<style>
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
margin: 0;
|
|
padding: 20px;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
color: white;
|
|
}
|
|
.container {
|
|
max-width: 600px;
|
|
margin: 0 auto;
|
|
text-align: center;
|
|
}
|
|
h1 {
|
|
margin-bottom: 30px;
|
|
font-size: 2.5em;
|
|
}
|
|
.button {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
color: white;
|
|
padding: 15px 30px;
|
|
margin: 10px;
|
|
border-radius: 25px;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
.button:hover {
|
|
background: rgba(255, 255, 255, 0.3);
|
|
transform: translateY(-2px);
|
|
}
|
|
.status {
|
|
margin-top: 30px;
|
|
padding: 20px;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-radius: 10px;
|
|
font-family: monospace;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<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 id="notificationReceivedIndicator" style="margin-top: 8px; padding: 8px; background: rgba(0, 255, 0, 0.2); border-radius: 5px; display: none;">
|
|
<strong>🔔 Notification Received!</strong><br>
|
|
<span id="notificationReceivedTime"></span><br>
|
|
<small>Check the top of your screen for the notification banner</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button class="button" onclick="configurePlugin()">Configure Plugin</button>
|
|
<button class="button" onclick="requestPermissions()">Request Permissions</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...
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
console.log('Script loading...');
|
|
console.log('JavaScript is working!');
|
|
|
|
// Use real DailyNotification plugin
|
|
console.log('Using real DailyNotification plugin...');
|
|
window.DailyNotification = window.Capacitor.Plugins.DailyNotification;
|
|
|
|
// Define functions immediately and attach to window
|
|
|
|
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;
|
|
}
|
|
|
|
// Configure plugin settings
|
|
window.DailyNotification.configure({
|
|
storage: 'tiered',
|
|
ttlSeconds: 86400,
|
|
prefetchLeadMinutes: 60,
|
|
maxNotificationsPerDay: 3,
|
|
retentionDays: 7
|
|
})
|
|
.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.
|
|
return window.DailyNotification.configureNativeFetcher({
|
|
apiBaseUrl: 'http://10.0.2.2:3000', // Android emulator → host localhost
|
|
activeDid: 'did:ethr:0xDEMO1234567890', // Demo DID
|
|
jwtSecret: 'demo-jwt-secret-for-development-testing'
|
|
});
|
|
})
|
|
.then(() => {
|
|
// 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 loadPluginStatus() {
|
|
console.log('loadPluginStatus called');
|
|
const pluginStatusContent = document.getElementById('pluginStatusContent');
|
|
const statusCard = document.getElementById('statusCard');
|
|
|
|
try {
|
|
if (!window.DailyNotification) {
|
|
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';
|
|
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 => {
|
|
pluginStatusContent.innerHTML = `⚠️ Status check failed: ${error.message}`;
|
|
statusCard.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
|
});
|
|
} catch (error) {
|
|
pluginStatusContent.innerHTML = `⚠️ Status check failed: ${error.message}`;
|
|
statusCard.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
|
}
|
|
}
|
|
|
|
// Notification test functions
|
|
function testNotification() {
|
|
console.log('testNotification called');
|
|
|
|
// Quick sanity check - test plugin availability
|
|
if (window.Capacitor && window.Capacitor.isPluginAvailable) {
|
|
const isAvailable = window.Capacitor.isPluginAvailable('DailyNotification');
|
|
console.log('is plugin available?', isAvailable);
|
|
}
|
|
|
|
const status = document.getElementById('status');
|
|
status.innerHTML = 'Testing plugin connection...';
|
|
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;
|
|
}
|
|
|
|
// Test the notification method directly
|
|
console.log('Testing notification scheduling...');
|
|
const now = new Date();
|
|
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') + ':' +
|
|
prefetchTime.getMinutes().toString().padStart(2, '0');
|
|
|
|
window.DailyNotification.scheduleDailyNotification({
|
|
time: notificationTimeString,
|
|
title: 'Test Notification',
|
|
body: 'This is a test notification from the DailyNotification plugin!',
|
|
sound: true,
|
|
priority: 'high'
|
|
})
|
|
.then(() => {
|
|
const prefetchTimeReadable = prefetchTime.toLocaleTimeString();
|
|
const notificationTimeReadable = notificationTime.toLocaleTimeString();
|
|
status.innerHTML = '✅ Notification scheduled!<br>' +
|
|
'📥 Prefetch: ' + prefetchTimeReadable + ' (' + prefetchTimeString + ')<br>' +
|
|
'🔔 Notification: ' + notificationTimeReadable + ' (' + notificationTimeString + ')<br><br>' +
|
|
'<small>💡 When the notification fires, look for a banner at the <strong>top of your screen</strong>.</small>';
|
|
status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
|
|
// Refresh plugin status display
|
|
setTimeout(() => loadPluginStatus(), 500);
|
|
})
|
|
.catch(error => {
|
|
// 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}`;
|
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
|
}
|
|
}
|
|
|
|
|
|
// Permission management functions
|
|
function requestPermissions() {
|
|
console.log('requestPermissions called');
|
|
const status = document.getElementById('status');
|
|
status.innerHTML = 'Requesting 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.requestNotificationPermissions()
|
|
.then(() => {
|
|
status.innerHTML = 'Permission request completed! Check your device settings if needed.';
|
|
status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
|
|
|
|
// Refresh permission and channel status display after request
|
|
setTimeout(() => {
|
|
loadPermissionStatus();
|
|
loadChannelStatus();
|
|
}, 1000);
|
|
})
|
|
.catch(error => {
|
|
status.innerHTML = `Permission request failed: ${error.message}`;
|
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
|
});
|
|
} catch (error) {
|
|
status.innerHTML = `Permission request failed: ${error.message}`;
|
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
|
}
|
|
}
|
|
|
|
function loadChannelStatus() {
|
|
const channelStatus = document.getElementById('channelStatus');
|
|
|
|
try {
|
|
if (!window.DailyNotification) {
|
|
channelStatus.innerHTML = '❌ Plugin unavailable';
|
|
return;
|
|
}
|
|
|
|
window.DailyNotification.isChannelEnabled()
|
|
.then(result => {
|
|
const importanceText = getImportanceText(result.importance);
|
|
if (result.enabled) {
|
|
channelStatus.innerHTML = `✅ Enabled (${importanceText})`;
|
|
} else {
|
|
channelStatus.innerHTML = `❌ Disabled (${importanceText})`;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
channelStatus.innerHTML = '⚠️ Error';
|
|
});
|
|
} catch (error) {
|
|
channelStatus.innerHTML = '⚠️ Error';
|
|
}
|
|
}
|
|
|
|
function checkComprehensiveStatus() {
|
|
const status = document.getElementById('status');
|
|
status.innerHTML = 'Checking comprehensive 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
|
|
return;
|
|
}
|
|
|
|
window.DailyNotification.checkStatus()
|
|
.then(result => {
|
|
const canSchedule = result.canScheduleNow;
|
|
const issues = [];
|
|
|
|
if (!result.postNotificationsGranted) {
|
|
issues.push('POST_NOTIFICATIONS permission');
|
|
}
|
|
if (!result.channelEnabled) {
|
|
issues.push('notification channel disabled');
|
|
}
|
|
if (!result.exactAlarmsGranted) {
|
|
issues.push('exact alarm permission');
|
|
}
|
|
|
|
let statusText = `Status: ${canSchedule ? 'Ready to schedule' : 'Issues found'}`;
|
|
if (issues.length > 0) {
|
|
statusText += `\nIssues: ${issues.join(', ')}`;
|
|
}
|
|
|
|
statusText += `\nChannel: ${getImportanceText(result.channelImportance)}`;
|
|
statusText += `\nChannel ID: ${result.channelId}`;
|
|
|
|
status.innerHTML = statusText;
|
|
status.style.background = canSchedule ? 'rgba(0, 255, 0, 0.3)' : 'rgba(255, 0, 0, 0.3)';
|
|
})
|
|
.catch(error => {
|
|
status.innerHTML = `Status check failed: ${error.message}`;
|
|
status.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
|
|
}
|
|
}
|
|
|
|
function getImportanceText(importance) {
|
|
switch (importance) {
|
|
case 0: return 'None (blocked)';
|
|
case 1: return 'Min';
|
|
case 2: return 'Low';
|
|
case 3: return 'Default';
|
|
case 4: return 'High';
|
|
case 5: return 'Max';
|
|
default: return `Unknown (${importance})`;
|
|
}
|
|
}
|
|
|
|
// Attach to window object
|
|
window.configurePlugin = configurePlugin;
|
|
window.testNotification = testNotification;
|
|
window.requestPermissions = requestPermissions;
|
|
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';
|
|
}
|
|
}
|
|
|
|
// Check for notification delivery periodically
|
|
function checkNotificationDelivery() {
|
|
if (!window.DailyNotification) return;
|
|
|
|
window.DailyNotification.getNotificationStatus()
|
|
.then(result => {
|
|
if (result.lastNotificationTime) {
|
|
const lastTime = new Date(result.lastNotificationTime);
|
|
const now = new Date();
|
|
const timeDiff = now - lastTime;
|
|
|
|
// If notification was received in the last 2 minutes, show indicator
|
|
if (timeDiff > 0 && timeDiff < 120000) {
|
|
const indicator = document.getElementById('notificationReceivedIndicator');
|
|
const timeSpan = document.getElementById('notificationReceivedTime');
|
|
|
|
if (indicator && timeSpan) {
|
|
indicator.style.display = 'block';
|
|
timeSpan.textContent = `Received at ${lastTime.toLocaleTimeString()}`;
|
|
|
|
// Hide after 30 seconds
|
|
setTimeout(() => {
|
|
indicator.style.display = 'none';
|
|
}, 30000);
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.catch(error => {
|
|
// Silently fail - this is just for visual feedback
|
|
});
|
|
}
|
|
|
|
// 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();
|
|
|
|
// Check for notification delivery every 5 seconds
|
|
setInterval(checkNotificationDelivery, 5000);
|
|
}, 500);
|
|
});
|
|
|
|
|
|
|
|
console.log('Functions attached to window:', {
|
|
configurePlugin: typeof window.configurePlugin,
|
|
testNotification: typeof window.testNotification
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|