/** * Notification Permission Manager * * Handles Android 13+ notification permissions with graceful fallbacks * Provides user-friendly permission request flows and education * * @author Matthew Raymer * @version 1.0.0 */ import { Capacitor } from '@capacitor/core'; /** * Permission status interface */ export interface PermissionStatus { notifications: 'granted' | 'denied' | 'prompt' | 'not_supported'; exactAlarms: 'granted' | 'denied' | 'not_supported'; batteryOptimization: 'granted' | 'denied' | 'not_supported'; overall: 'ready' | 'partial' | 'blocked'; } /** * Permission request result */ export interface PermissionRequestResult { success: boolean; permissions: PermissionStatus; message: string; nextSteps?: string[]; } /** * Permission education content */ export interface PermissionEducation { title: string; message: string; benefits: string[]; steps: string[]; fallbackOptions: string[]; } /** * Notification Permission Manager */ export class NotificationPermissionManager { private static instance: NotificationPermissionManager; private constructor() {} public static getInstance(): NotificationPermissionManager { if (!NotificationPermissionManager.instance) { NotificationPermissionManager.instance = new NotificationPermissionManager(); } return NotificationPermissionManager.instance; } /** * Check current permission status */ async checkPermissions(): Promise { try { const platform = Capacitor.getPlatform(); if (platform === 'web') { return { notifications: 'not_supported', exactAlarms: 'not_supported', batteryOptimization: 'not_supported', overall: 'blocked' }; } // Check notification permissions const notificationStatus = await this.checkNotificationPermissions(); // Check exact alarm permissions const exactAlarmStatus = await this.checkExactAlarmPermissions(); // Check battery optimization status const batteryStatus = await this.checkBatteryOptimizationStatus(); // Determine overall status const overall = this.determineOverallStatus(notificationStatus, exactAlarmStatus, batteryStatus); return { notifications: notificationStatus, exactAlarms: exactAlarmStatus, batteryOptimization: batteryStatus, overall }; } catch (error) { console.error('Error checking permissions:', error); return { notifications: 'denied', exactAlarms: 'denied', batteryOptimization: 'denied', overall: 'blocked' }; } } /** * Request all required permissions with education */ async requestPermissionsWithEducation(): Promise { try { const currentStatus = await this.checkPermissions(); if (currentStatus.overall === 'ready') { return { success: true, permissions: currentStatus, message: 'All permissions already granted' }; } const results: string[] = []; const nextSteps: string[] = []; // Request notification permissions if (currentStatus.notifications === 'prompt') { const notificationResult = await this.requestNotificationPermissions(); results.push(notificationResult.message); if (!notificationResult.success) { nextSteps.push('Enable notifications in device settings'); } } // Request exact alarm permissions if (currentStatus.exactAlarms === 'denied') { const exactAlarmResult = await this.requestExactAlarmPermissions(); results.push(exactAlarmResult.message); if (!exactAlarmResult.success) { nextSteps.push('Enable exact alarms in device settings'); } } // Request battery optimization exemption if (currentStatus.batteryOptimization === 'denied') { const batteryResult = await this.requestBatteryOptimizationExemption(); results.push(batteryResult.message); if (!batteryResult.success) { nextSteps.push('Disable battery optimization for this app'); } } const finalStatus = await this.checkPermissions(); const success = finalStatus.overall === 'ready' || finalStatus.overall === 'partial'; return { success, permissions: finalStatus, message: results.join('; '), nextSteps: nextSteps.length > 0 ? nextSteps : undefined }; } catch (error) { console.error('Error requesting permissions:', error); return { success: false, permissions: await this.checkPermissions(), message: 'Failed to request permissions: ' + (error instanceof Error ? error.message : String(error)) }; } } /** * Get permission education content */ getPermissionEducation(): PermissionEducation { return { title: 'Enable Notifications for Better Experience', message: 'To receive timely updates and reminders, please enable notifications and related permissions.', benefits: [ 'Receive daily updates at your preferred time', 'Get notified about important changes', 'Never miss important reminders', 'Enjoy reliable notification delivery' ], steps: [ 'Tap "Allow" when prompted for notification permissions', 'Enable exact alarms for precise timing', 'Disable battery optimization for this app', 'Test notifications to ensure everything works' ], fallbackOptions: [ 'Use in-app reminders as backup', 'Check the app regularly for updates', 'Enable email notifications if available' ] }; } /** * Show permission education dialog */ async showPermissionEducation(): Promise { try { const education = this.getPermissionEducation(); // Create and show education dialog const userChoice = await this.showEducationDialog(education); if (userChoice === 'continue') { return await this.requestPermissionsWithEducation().then(result => result.success); } return false; } catch (error) { console.error('Error showing permission education:', error); return false; } } /** * Handle permission denied gracefully */ async handlePermissionDenied(permissionType: 'notifications' | 'exactAlarms' | 'batteryOptimization'): Promise { try { const education = this.getPermissionEducation(); switch (permissionType) { case 'notifications': await this.showNotificationDeniedDialog(education); break; case 'exactAlarms': await this.showExactAlarmDeniedDialog(education); break; case 'batteryOptimization': await this.showBatteryOptimizationDeniedDialog(education); break; } } catch (error) { console.error('Error handling permission denied:', error); } } /** * Check if app can function with current permissions */ async canFunctionWithCurrentPermissions(): Promise { try { const status = await this.checkPermissions(); // App can function if notifications are granted, even without exact alarms return status.notifications === 'granted'; } catch (error) { console.error('Error checking if app can function:', error); return false; } } /** * Get fallback notification strategy */ getFallbackStrategy(): string[] { return [ 'Use in-app notifications instead of system notifications', 'Implement periodic background checks', 'Show notification badges in the app', 'Use email notifications as backup', 'Implement push notifications through a service' ]; } // Private helper methods private async checkNotificationPermissions(): Promise<'granted' | 'denied' | 'prompt' | 'not_supported'> { try { if (Capacitor.getPlatform() === 'web') { return 'not_supported'; } // Check if we can access the plugin if (typeof (window as any).Capacitor?.Plugins?.DailyNotification === 'undefined') { return 'not_supported'; } const status = await (window as any).Capacitor?.Plugins?.DailyNotification?.checkPermissions(); return status?.notifications || 'denied'; } catch (error) { console.error('Error checking notification permissions:', error); return 'denied'; } } private async checkExactAlarmPermissions(): Promise<'granted' | 'denied' | 'not_supported'> { try { if (Capacitor.getPlatform() === 'web') { return 'not_supported'; } if (typeof (window as any).Capacitor?.Plugins?.DailyNotification === 'undefined') { return 'denied'; } const status = await (window as any).Capacitor?.Plugins?.DailyNotification?.getExactAlarmStatus(); return status?.canSchedule ? 'granted' : 'denied'; } catch (error) { console.error('Error checking exact alarm permissions:', error); return 'denied'; } } private async checkBatteryOptimizationStatus(): Promise<'granted' | 'denied' | 'not_supported'> { try { if (Capacitor.getPlatform() === 'web') { return 'not_supported'; } if (typeof (window as any).Capacitor?.Plugins?.DailyNotification === 'undefined') { return 'denied'; } const status = await (window as any).Capacitor?.Plugins?.DailyNotification?.getBatteryStatus(); return status?.isOptimized ? 'denied' : 'granted'; } catch (error) { console.error('Error checking battery optimization status:', error); return 'denied'; } } private determineOverallStatus( notifications: string, exactAlarms: string, batteryOptimization: string ): 'ready' | 'partial' | 'blocked' { if (notifications === 'granted' && exactAlarms === 'granted' && batteryOptimization === 'granted') { return 'ready'; } else if (notifications === 'granted') { return 'partial'; } else { return 'blocked'; } } private async requestNotificationPermissions(): Promise<{ success: boolean; message: string }> { try { if (typeof (window as any).Capacitor?.Plugins?.DailyNotification === 'undefined') { return { success: false, message: 'Plugin not available' }; } const result = await (window as any).Capacitor?.Plugins?.DailyNotification?.requestPermissions(); return { success: result?.notifications === 'granted', message: result?.notifications === 'granted' ? 'Notification permissions granted' : 'Notification permissions denied' }; } catch (error) { return { success: false, message: 'Failed to request notification permissions' }; } } private async requestExactAlarmPermissions(): Promise<{ success: boolean; message: string }> { try { if (typeof (window as any).Capacitor?.Plugins?.DailyNotification === 'undefined') { return { success: false, message: 'Plugin not available' }; } await (window as any).Capacitor?.Plugins?.DailyNotification?.requestExactAlarmPermission(); // Check if permission was granted const status = await (window as any).Capacitor?.Plugins?.DailyNotification?.getExactAlarmStatus(); return { success: status?.canSchedule, message: status?.canSchedule ? 'Exact alarm permissions granted' : 'Exact alarm permissions denied' }; } catch (error) { return { success: false, message: 'Failed to request exact alarm permissions' }; } } private async requestBatteryOptimizationExemption(): Promise<{ success: boolean; message: string }> { try { if (typeof (window as any).Capacitor?.Plugins?.DailyNotification === 'undefined') { return { success: false, message: 'Plugin not available' }; } await (window as any).Capacitor?.Plugins?.DailyNotification?.requestBatteryOptimizationExemption(); // Check if exemption was granted const status = await (window as any).Capacitor?.Plugins?.DailyNotification?.getBatteryStatus(); return { success: !status?.isOptimized, message: !status?.isOptimized ? 'Battery optimization exemption granted' : 'Battery optimization exemption denied' }; } catch (error) { return { success: false, message: 'Failed to request battery optimization exemption' }; } } private async showEducationDialog(education: PermissionEducation): Promise<'continue' | 'cancel'> { // This would show a custom dialog with the education content // For now, we'll use a simple confirm dialog const message = `${education.title}\n\n${education.message}\n\nBenefits:\n${education.benefits.map(b => `• ${b}`).join('\n')}`; return new Promise((resolve) => { if (confirm(message)) { resolve('continue'); } else { resolve('cancel'); } }); } private async showNotificationDeniedDialog(education: PermissionEducation): Promise { const message = `Notifications are disabled. You can still use the app, but you won't receive timely updates.\n\nTo enable notifications:\n${education.steps.slice(0, 2).map(s => `• ${s}`).join('\n')}`; alert(message); } private async showExactAlarmDeniedDialog(_education: PermissionEducation): Promise { const message = `Exact alarms are disabled. Notifications may not arrive at the exact time you specified.\n\nTo enable exact alarms:\n• Go to device settings\n• Find this app\n• Enable "Alarms & reminders"`; alert(message); } private async showBatteryOptimizationDeniedDialog(_education: PermissionEducation): Promise { const message = `Battery optimization is enabled. This may prevent notifications from arriving on time.\n\nTo disable battery optimization:\n• Go to device settings\n• Find "Battery optimization"\n• Select this app\n• Choose "Don't optimize"`; alert(message); } } export default NotificationPermissionManager;