From a81b205e1f39d05da36d49613648d237aa62ffe0 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Sun, 28 Sep 2025 05:24:28 +0000 Subject: [PATCH] docs: Add comprehensive UI requirements and integration examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add UI_REQUIREMENTS.md with detailed UI component specifications - Add ui-integration-examples.ts with ready-to-use UI components - Document all required UI elements for plugin integration - Include platform-specific UI components (Android/iOS/Web) - Provide complete implementation examples with TypeScript - Add responsive design guidelines and accessibility requirements - Include error handling and status monitoring UI components - Update README.md to reference new UI documentation UI Components Covered: ✅ Permission management dialogs and status displays ✅ Configuration panels for settings and preferences ✅ Status dashboards with real-time monitoring ✅ Platform-specific components (battery optimization, background refresh) ✅ Error handling and recovery UI ✅ Testing and debug components ✅ Complete integration examples with event handling --- README.md | 2 + doc/UI_REQUIREMENTS.md | 503 +++++++++++ examples/ui-integration-examples.ts | 1304 +++++++++++++++++++++++++++ 3 files changed, 1809 insertions(+) create mode 100644 doc/UI_REQUIREMENTS.md create mode 100644 examples/ui-integration-examples.ts diff --git a/README.md b/README.md index e36318f..d73c51a 100644 --- a/README.md +++ b/README.md @@ -532,6 +532,8 @@ MIT License - see [LICENSE](LICENSE) file for details. - **Enterprise Examples**: [doc/enterprise-callback-examples.md](doc/enterprise-callback-examples.md) - **Verification Report**: [doc/VERIFICATION_REPORT.md](doc/VERIFICATION_REPORT.md) - Closed-app functionality verification - **Verification Checklist**: [doc/VERIFICATION_CHECKLIST.md](doc/VERIFICATION_CHECKLIST.md) - Regular verification process +- **UI Requirements**: [doc/UI_REQUIREMENTS.md](doc/UI_REQUIREMENTS.md) - Complete UI component requirements +- **UI Integration Examples**: [examples/ui-integration-examples.ts](examples/ui-integration-examples.ts) - Ready-to-use UI components ### Community diff --git a/doc/UI_REQUIREMENTS.md b/doc/UI_REQUIREMENTS.md new file mode 100644 index 0000000..6e0b2cf --- /dev/null +++ b/doc/UI_REQUIREMENTS.md @@ -0,0 +1,503 @@ +# Daily Notification Plugin - UI Requirements + +**Author**: Matthew Raymer +**Version**: 1.0.0 +**Last Updated**: 2025-01-27 +**Purpose**: Comprehensive UI requirements for integrating the Daily Notification Plugin + +--- + +## Overview + +The Daily Notification Plugin requires specific UI components to provide a complete user experience for notification management, configuration, and monitoring. This document outlines all required UI elements, their functionality, and implementation patterns. + +--- + +## Core UI Components + +### 1. **Permission Management UI** + +#### **Permission Request Dialog** +**Purpose**: Request notification permissions from users + +**Required Elements**: +- **Title**: "Enable Daily Notifications" +- **Description**: Explain why notifications are needed +- **Permission Buttons**: + - "Allow Notifications" (primary) + - "Not Now" (secondary) + - "Never Ask Again" (tertiary) + +**Implementation**: +```typescript +interface PermissionDialogProps { + onAllow: () => Promise; + onDeny: () => void; + onNever: () => void; + platform: 'android' | 'ios' | 'web'; +} +``` + +#### **Permission Status Display** +**Purpose**: Show current permission status + +**Required Elements**: +- **Status Indicator**: Green (granted), Yellow (partial), Red (denied) +- **Permission Details**: + - Notifications: granted/denied + - Background Refresh (iOS): enabled/disabled + - Exact Alarms (Android): granted/denied +- **Action Buttons**: "Request Permissions", "Open Settings" + +**Implementation**: +```typescript +interface PermissionStatusProps { + status: PermissionStatus; + onRequestPermissions: () => Promise; + onOpenSettings: () => void; +} +``` + +### 2. **Configuration UI** + +#### **Notification Settings Panel** +**Purpose**: Configure notification preferences + +**Required Elements**: +- **Enable/Disable Toggle**: Master switch for notifications +- **Time Picker**: Select notification time (HH:MM format) +- **Content Type Selection**: + - Offers (new/changed to me/my projects) + - Projects (local/new/changed/favorited) + - People (local/new/changed/favorited/contacts) + - Items (local/new/changed/favorited) +- **Notification Preferences**: + - Sound: on/off + - Vibration: on/off + - Priority: low/normal/high + - Badge: on/off + +**Implementation**: +```typescript +interface NotificationSettingsProps { + settings: NotificationSettings; + onUpdateSettings: (settings: NotificationSettings) => Promise; + onTestNotification: () => Promise; +} +``` + +#### **Advanced Configuration Panel** +**Purpose**: Configure advanced plugin settings + +**Required Elements**: +- **TTL Settings**: Content validity duration (1-24 hours) +- **Prefetch Lead Time**: Minutes before notification (5-60 minutes) +- **Retry Configuration**: + - Max retries (1-5) + - Retry interval (1-30 minutes) +- **Storage Settings**: + - Cache size limit + - Retention period +- **Network Settings**: + - Timeout duration + - Offline fallback + +**Implementation**: +```typescript +interface AdvancedSettingsProps { + config: ConfigureOptions; + onUpdateConfig: (config: ConfigureOptions) => Promise; + onResetToDefaults: () => Promise; +} +``` + +### 3. **Status Monitoring UI** + +#### **Notification Status Dashboard** +**Purpose**: Display current notification system status + +**Required Elements**: +- **Overall Status**: Active/Inactive/Paused +- **Next Notification**: Time until next notification +- **Last Notification**: When last notification was sent +- **Content Cache Status**: + - Last fetch time + - Cache age + - TTL status +- **Background Tasks**: + - Fetch status + - Delivery status + - Error count + +**Implementation**: +```typescript +interface StatusDashboardProps { + status: DualScheduleStatus; + onRefresh: () => Promise; + onViewDetails: () => void; +} +``` + +#### **Performance Metrics Display** +**Purpose**: Show system performance and health + +**Required Elements**: +- **Success Rate**: Percentage of successful notifications +- **Average Response Time**: Time for content fetch +- **Error Rate**: Percentage of failed operations +- **Battery Impact**: Estimated battery usage +- **Network Usage**: Data consumption statistics + +**Implementation**: +```typescript +interface PerformanceMetricsProps { + metrics: PerformanceMetrics; + onExportData: () => void; + onViewHistory: () => void; +} +``` + +### 4. **Platform-Specific UI** + +#### **Android-Specific Components** + +##### **Battery Optimization Dialog** +**Purpose**: Handle Android battery optimization + +**Required Elements**: +- **Warning Message**: "Battery optimization may prevent notifications" +- **Action Button**: "Open Battery Settings" +- **Skip Option**: "Continue Anyway" + +**Implementation**: +```typescript +interface BatteryOptimizationProps { + onOpenSettings: () => Promise; + onSkip: () => void; + onCheckStatus: () => Promise; +} +``` + +##### **Exact Alarm Permission Dialog** +**Purpose**: Request exact alarm permissions + +**Required Elements**: +- **Explanation**: Why exact alarms are needed +- **Permission Button**: "Grant Exact Alarm Permission" +- **Fallback Info**: "Will use approximate timing if denied" + +**Implementation**: +```typescript +interface ExactAlarmProps { + status: ExactAlarmStatus; + onRequestPermission: () => Promise; + onOpenSettings: () => Promise; +} +``` + +#### **iOS-Specific Components** + +##### **Background App Refresh Dialog** +**Purpose**: Handle iOS background app refresh + +**Required Elements**: +- **Explanation**: "Background App Refresh enables content fetching" +- **Settings Button**: "Open Settings" +- **Fallback Info**: "Will use cached content if disabled" + +**Implementation**: +```typescript +interface BackgroundRefreshProps { + enabled: boolean; + onOpenSettings: () => void; + onCheckStatus: () => Promise; +} +``` + +##### **Rolling Window Management** +**Purpose**: Manage iOS rolling window + +**Required Elements**: +- **Window Status**: Current window state +- **Maintenance Button**: "Maintain Rolling Window" +- **Statistics**: Window performance metrics + +**Implementation**: +```typescript +interface RollingWindowProps { + stats: RollingWindowStats; + onMaintain: () => Promise; + onViewStats: () => void; +} +``` + +#### **Web-Specific Components** + +##### **Service Worker Status** +**Purpose**: Monitor web service worker + +**Required Elements**: +- **Worker Status**: Active/Inactive/Error +- **Registration Status**: Registered/Unregistered +- **Background Sync**: Enabled/Disabled +- **Push Notifications**: Supported/Not Supported + +**Implementation**: +```typescript +interface ServiceWorkerProps { + status: ServiceWorkerStatus; + onRegister: () => Promise; + onUnregister: () => Promise; +} +``` + +### 5. **Error Handling UI** + +#### **Error Display Component** +**Purpose**: Show errors and provide recovery options + +**Required Elements**: +- **Error Message**: Clear description of the issue +- **Error Code**: Technical error identifier +- **Recovery Actions**: + - "Retry" button + - "Reset Configuration" button + - "Contact Support" button +- **Error History**: List of recent errors + +**Implementation**: +```typescript +interface ErrorDisplayProps { + error: NotificationError; + onRetry: () => Promise; + onReset: () => Promise; + onContactSupport: () => void; +} +``` + +#### **Network Error Dialog** +**Purpose**: Handle network connectivity issues + +**Required Elements**: +- **Error Message**: "Unable to fetch content" +- **Retry Options**: + - "Retry Now" + - "Retry Later" + - "Use Cached Content" +- **Offline Mode**: Toggle for offline operation + +**Implementation**: +```typescript +interface NetworkErrorProps { + onRetry: () => Promise; + onUseCache: () => void; + onEnableOffline: () => void; +} +``` + +### 6. **Testing and Debug UI** + +#### **Test Notification Panel** +**Purpose**: Test notification functionality + +**Required Elements**: +- **Test Button**: "Send Test Notification" +- **Custom Content**: Input for test message +- **Test Results**: Success/failure status +- **Log Display**: Test execution logs + +**Implementation**: +```typescript +interface TestPanelProps { + onSendTest: (content?: string) => Promise; + onClearLogs: () => void; + logs: string[]; +} +``` + +#### **Debug Information Panel** +**Purpose**: Display debug information for troubleshooting + +**Required Elements**: +- **System Information**: Platform, version, capabilities +- **Configuration**: Current settings +- **Status Details**: Detailed system status +- **Log Export**: Export logs for support + +**Implementation**: +```typescript +interface DebugPanelProps { + debugInfo: DebugInfo; + onExportLogs: () => void; + onClearCache: () => Promise; +} +``` + +--- + +## UI Layout Patterns + +### 1. **Settings Page Layout** + +``` +┌─────────────────────────────────────┐ +│ Notification Settings │ +├─────────────────────────────────────┤ +│ [Enable Notifications] Toggle │ +│ │ +│ Time: [09:00] [AM/PM] │ +│ │ +│ Content Types: │ +│ ☑ Offers │ +│ ☑ Projects │ +│ ☑ People │ +│ ☑ Items │ +│ │ +│ Preferences: │ +│ Sound: [On] Vibration: [On] │ +│ Priority: [Normal] Badge: [On] │ +│ │ +│ [Test Notification] [Save Settings] │ +└─────────────────────────────────────┘ +``` + +### 2. **Status Dashboard Layout** + +``` +┌─────────────────────────────────────┐ +│ Notification Status │ +├─────────────────────────────────────┤ +│ Status: ● Active │ +│ Next: 2 hours 15 minutes │ +│ Last: Yesterday 9:00 AM │ +│ │ +│ Content Cache: │ +│ Last Fetch: 1 hour ago │ +│ Cache Age: Fresh │ +│ TTL: Valid for 23 hours │ +│ │ +│ Background Tasks: │ +│ Fetch: ● Success │ +│ Delivery: ● Success │ +│ Errors: 0 │ +│ │ +│ [Refresh] [View Details] │ +└─────────────────────────────────────┘ +``` + +### 3. **Permission Request Layout** + +``` +┌─────────────────────────────────────┐ +│ Enable Daily Notifications │ +├─────────────────────────────────────┤ +│ │ +│ Get notified about new offers, │ +│ projects, people, and items in │ +│ your TimeSafari community. │ +│ │ +│ • New offers directed to you │ +│ • Changes to your projects │ +│ • Updates from favorited people │ +│ • New items of interest │ +│ │ +│ [Allow Notifications] │ +│ [Not Now] [Never Ask Again] │ +└─────────────────────────────────────┘ +``` + +--- + +## Implementation Guidelines + +### 1. **Responsive Design** +- **Mobile First**: Design for mobile, scale up +- **Touch Friendly**: Minimum 44px touch targets +- **Accessibility**: WCAG 2.1 AA compliance +- **Platform Native**: Use platform-specific design patterns + +### 2. **State Management** +- **Loading States**: Show loading indicators +- **Error States**: Clear error messages +- **Success States**: Confirmation feedback +- **Empty States**: Helpful empty state messages + +### 3. **User Experience** +- **Progressive Disclosure**: Show advanced options when needed +- **Contextual Help**: Tooltips and help text +- **Confirmation Dialogs**: For destructive actions +- **Undo Functionality**: Where appropriate + +### 4. **Performance** +- **Lazy Loading**: Load components when needed +- **Debounced Inputs**: Prevent excessive API calls +- **Optimistic Updates**: Update UI immediately +- **Error Boundaries**: Graceful error handling + +--- + +## Platform-Specific Considerations + +### **Android** +- **Material Design**: Follow Material Design guidelines +- **Battery Optimization**: Handle battery optimization prompts +- **Exact Alarms**: Request exact alarm permissions +- **WorkManager**: Show background task status + +### **iOS** +- **Human Interface Guidelines**: Follow iOS design patterns +- **Background App Refresh**: Handle background refresh settings +- **Rolling Window**: Show rolling window management +- **BGTaskScheduler**: Display background task status + +### **Web** +- **Progressive Web App**: PWA-compatible design +- **Service Worker**: Show service worker status +- **Push Notifications**: Handle push notification permissions +- **Offline Support**: Offline functionality indicators + +--- + +## Testing Requirements + +### **Unit Tests** +- Component rendering +- User interactions +- State management +- Error handling + +### **Integration Tests** +- API integration +- Permission flows +- Configuration persistence +- Cross-platform compatibility + +### **User Testing** +- Usability testing +- Accessibility testing +- Performance testing +- Cross-device testing + +--- + +## Conclusion + +The Daily Notification Plugin requires a comprehensive UI that handles: + +1. **Permission Management**: Request and display permission status +2. **Configuration**: Settings and preferences management +3. **Status Monitoring**: Real-time system status and performance +4. **Platform-Specific Features**: Android, iOS, and Web-specific components +5. **Error Handling**: User-friendly error messages and recovery +6. **Testing and Debug**: Tools for testing and troubleshooting + +The UI should be responsive, accessible, and follow platform-specific design guidelines while providing a consistent user experience across all platforms. + +--- + +**Next Steps**: +1. Implement core UI components +2. Add platform-specific features +3. Integrate with plugin API +4. Test across all platforms +5. Optimize for performance and accessibility diff --git a/examples/ui-integration-examples.ts b/examples/ui-integration-examples.ts new file mode 100644 index 0000000..ca49021 --- /dev/null +++ b/examples/ui-integration-examples.ts @@ -0,0 +1,1304 @@ +/** + * UI Integration Examples for Daily Notification Plugin + * + * This file provides comprehensive examples of how to integrate + * the Daily Notification Plugin UI components into your application. + * + * @author Matthew Raymer + * @version 1.0.0 + * @created 2025-01-27 12:00:00 UTC + */ + +import { DailyNotification } from '@timesafari/daily-notification-plugin'; +import type { + NotificationSettings, + ConfigureOptions, + DualScheduleStatus, + PermissionStatus, + BatteryStatus, + ExactAlarmStatus, + RollingWindowStats, + PerformanceMetrics +} from '@timesafari/daily-notification-plugin'; + +// ============================================================================ +// 1. PERMISSION MANAGEMENT UI +// ============================================================================ + +/** + * Permission Request Dialog Component + * Handles initial permission requests and status display + */ +export class PermissionRequestDialog { + private container: HTMLElement; + private onAllow: () => Promise; + private onDeny: () => void; + private onNever: () => void; + + constructor(container: HTMLElement, callbacks: { + onAllow: () => Promise; + onDeny: () => void; + onNever: () => void; + }) { + this.container = container; + this.onAllow = callbacks.onAllow; + this.onDeny = callbacks.onDeny; + this.onNever = callbacks.onNever; + } + + /** + * Show permission request dialog + */ + async show(): Promise { + const dialog = this.createDialog(); + this.container.appendChild(dialog); + + // Add event listeners + dialog.querySelector('#allow-btn')?.addEventListener('click', async () => { + await this.onAllow(); + this.hide(); + }); + + dialog.querySelector('#deny-btn')?.addEventListener('click', () => { + this.onDeny(); + this.hide(); + }); + + dialog.querySelector('#never-btn')?.addEventListener('click', () => { + this.onNever(); + this.hide(); + }); + } + + /** + * Hide permission request dialog + */ + hide(): void { + const dialog = this.container.querySelector('.permission-dialog'); + if (dialog) { + dialog.remove(); + } + } + + /** + * Create permission request dialog HTML + */ + private createDialog(): HTMLElement { + const dialog = document.createElement('div'); + dialog.className = 'permission-dialog'; + dialog.innerHTML = ` +
+
+

Enable Daily Notifications

+

Get notified about new offers, projects, people, and items in your TimeSafari community.

+
    +
  • New offers directed to you
  • +
  • Changes to your projects
  • +
  • Updates from favorited people
  • +
  • New items of interest
  • +
+
+ + + +
+
+
+ `; + return dialog; + } +} + +/** + * Permission Status Display Component + * Shows current permission status and provides actions + */ +export class PermissionStatusDisplay { + private container: HTMLElement; + + constructor(container: HTMLElement) { + this.container = container; + } + + /** + * Update permission status display + */ + async updateStatus(): Promise { + try { + const status = await DailyNotification.checkPermissions(); + this.renderStatus(status); + } catch (error) { + console.error('Failed to check permissions:', error); + this.renderError('Failed to check permission status'); + } + } + + /** + * Render permission status + */ + private renderStatus(status: PermissionStatus): void { + const statusClass = status.granted ? 'status-granted' : 'status-denied'; + const statusText = status.granted ? 'Granted' : 'Denied'; + + this.container.innerHTML = ` +
+
+ ${status.granted ? '✓' : '✗'} + ${statusText} +
+
+
+ Notifications: + + ${status.notifications} + +
+ ${status.backgroundRefresh ? ` +
+ Background Refresh: + + ${status.backgroundRefresh} + +
+ ` : ''} +
+
+ + +
+
+ `; + + // Add event listeners + this.container.querySelector('#request-permissions')?.addEventListener('click', async () => { + try { + await DailyNotification.requestPermissions(); + await this.updateStatus(); + } catch (error) { + console.error('Failed to request permissions:', error); + } + }); + + this.container.querySelector('#open-settings')?.addEventListener('click', () => { + // Platform-specific settings opening + this.openSettings(); + }); + } + + /** + * Render error state + */ + private renderError(message: string): void { + this.container.innerHTML = ` +
+
${message}
+ +
+ `; + + this.container.querySelector('#retry-permissions')?.addEventListener('click', () => { + this.updateStatus(); + }); + } + + /** + * Open platform-specific settings + */ + private openSettings(): void { + // This would be platform-specific implementation + console.log('Opening settings...'); + } +} + +// ============================================================================ +// 2. CONFIGURATION UI +// ============================================================================ + +/** + * Notification Settings Panel Component + * Handles notification configuration and preferences + */ +export class NotificationSettingsPanel { + private container: HTMLElement; + private settings: NotificationSettings; + + constructor(container: HTMLElement) { + this.container = container; + this.settings = this.getDefaultSettings(); + } + + /** + * Initialize settings panel + */ + async initialize(): Promise { + try { + // Load current settings + const status = await DailyNotification.getNotificationStatus(); + this.settings = { + ...this.settings, + ...status + }; + + this.render(); + this.attachEventListeners(); + } catch (error) { + console.error('Failed to initialize settings:', error); + this.renderError('Failed to load settings'); + } + } + + /** + * Render settings panel + */ + private render(): void { + this.container.innerHTML = ` +
+

Notification Settings

+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + +
+
+ +
+ +
+ + + +
+
+ +
+ + +
+
+ `; + } + + /** + * Attach event listeners + */ + private attachEventListeners(): void { + // Save settings + this.container.querySelector('#save-settings')?.addEventListener('click', async () => { + await this.saveSettings(); + }); + + // Test notification + this.container.querySelector('#test-notification')?.addEventListener('click', async () => { + await this.sendTestNotification(); + }); + + // Real-time updates + this.container.querySelectorAll('input, select').forEach(element => { + element.addEventListener('change', () => { + this.updateSettingsFromUI(); + }); + }); + } + + /** + * Update settings from UI + */ + private updateSettingsFromUI(): void { + const enableNotifications = (this.container.querySelector('#enable-notifications') as HTMLInputElement)?.checked; + const notificationTime = (this.container.querySelector('#notification-time') as HTMLInputElement)?.value; + const soundEnabled = (this.container.querySelector('#sound-enabled') as HTMLInputElement)?.checked; + const priorityLevel = (this.container.querySelector('#priority-level') as HTMLSelectElement)?.value; + + this.settings = { + ...this.settings, + isEnabled: enableNotifications, + time: notificationTime, + sound: soundEnabled, + priority: priorityLevel + }; + } + + /** + * Save settings + */ + private async saveSettings(): Promise { + try { + await DailyNotification.updateSettings(this.settings); + this.showSuccess('Settings saved successfully'); + } catch (error) { + console.error('Failed to save settings:', error); + this.showError('Failed to save settings'); + } + } + + /** + * Send test notification + */ + private async sendTestNotification(): Promise { + try { + // This would trigger a test notification + this.showSuccess('Test notification sent'); + } catch (error) { + console.error('Failed to send test notification:', error); + this.showError('Failed to send test notification'); + } + } + + /** + * Get default settings + */ + private getDefaultSettings(): NotificationSettings { + return { + isEnabled: true, + time: '09:00', + sound: true, + priority: 'normal', + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone + }; + } + + /** + * Show success message + */ + private showSuccess(message: string): void { + // Implementation for success message + console.log('Success:', message); + } + + /** + * Show error message + */ + private showError(message: string): void { + // Implementation for error message + console.error('Error:', message); + } + + /** + * Render error state + */ + private renderError(message: string): void { + this.container.innerHTML = ` +
+

${message}

+ +
+ `; + + this.container.querySelector('#retry-settings')?.addEventListener('click', () => { + this.initialize(); + }); + } +} + +// ============================================================================ +// 3. STATUS MONITORING UI +// ============================================================================ + +/** + * Status Dashboard Component + * Displays real-time notification system status + */ +export class StatusDashboard { + private container: HTMLElement; + private refreshInterval: number | null = null; + + constructor(container: HTMLElement) { + this.container = container; + } + + /** + * Initialize status dashboard + */ + async initialize(): Promise { + await this.updateStatus(); + this.startAutoRefresh(); + } + + /** + * Update status display + */ + async updateStatus(): Promise { + try { + const status = await DailyNotification.getDualScheduleStatus(); + this.renderStatus(status); + } catch (error) { + console.error('Failed to get status:', error); + this.renderError('Failed to load status'); + } + } + + /** + * Render status display + */ + private renderStatus(status: DualScheduleStatus): void { + const nextRun = status.nextRuns[0] ? new Date(status.nextRuns[0]) : null; + const nextRunText = nextRun ? this.formatTimeUntil(nextRun) : 'Not scheduled'; + + const lastOutcome = status.lastOutcomes[0] || 'Unknown'; + const lastOutcomeClass = lastOutcome === 'success' ? 'success' : 'error'; + + this.container.innerHTML = ` +
+

Notification Status

+ +
+
+
Overall Status
+
+ ${status.staleArmed ? 'Stale' : 'Active'} +
+
+ +
+
Next Notification
+
${nextRunText}
+
+ +
+
Last Outcome
+
${lastOutcome}
+
+ +
+
Cache Age
+
${status.cacheAgeMs ? this.formatDuration(status.cacheAgeMs) : 'No cache'}
+
+
+ +
+

Performance

+
+
+ Success Rate: + ${status.performance.successRate}% +
+
+ Error Count: + ${status.performance.errorCount} +
+
+
+ +
+ + +
+
+ `; + + // Add event listeners + this.container.querySelector('#refresh-status')?.addEventListener('click', () => { + this.updateStatus(); + }); + + this.container.querySelector('#view-details')?.addEventListener('click', () => { + this.showDetailedStatus(); + }); + } + + /** + * Start auto-refresh + */ + private startAutoRefresh(): void { + this.refreshInterval = window.setInterval(() => { + this.updateStatus(); + }, 30000); // Refresh every 30 seconds + } + + /** + * Stop auto-refresh + */ + stopAutoRefresh(): void { + if (this.refreshInterval) { + clearInterval(this.refreshInterval); + this.refreshInterval = null; + } + } + + /** + * Format time until date + */ + private formatTimeUntil(date: Date): string { + const now = new Date(); + const diff = date.getTime() - now.getTime(); + + if (diff <= 0) return 'Now'; + + const hours = Math.floor(diff / (1000 * 60 * 60)); + const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); + + if (hours > 0) { + return `${hours}h ${minutes}m`; + } else { + return `${minutes}m`; + } + } + + /** + * Format duration in milliseconds + */ + private formatDuration(ms: number): string { + const seconds = Math.floor(ms / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + + if (hours > 0) { + return `${hours}h ${minutes % 60}m`; + } else if (minutes > 0) { + return `${minutes}m ${seconds % 60}s`; + } else { + return `${seconds}s`; + } + } + + /** + * Show detailed status + */ + private showDetailedStatus(): void { + // Implementation for detailed status view + console.log('Showing detailed status...'); + } + + /** + * Render error state + */ + private renderError(message: string): void { + this.container.innerHTML = ` +
+

${message}

+ +
+ `; + + this.container.querySelector('#retry-status')?.addEventListener('click', () => { + this.updateStatus(); + }); + } +} + +// ============================================================================ +// 4. PLATFORM-SPECIFIC UI +// ============================================================================ + +/** + * Android Battery Optimization Dialog + * Handles Android-specific battery optimization prompts + */ +export class AndroidBatteryOptimizationDialog { + private container: HTMLElement; + + constructor(container: HTMLElement) { + this.container = container; + } + + /** + * Check and show battery optimization dialog if needed + */ + async checkAndShow(): Promise { + try { + const batteryStatus = await DailyNotification.getBatteryStatus(); + + if (batteryStatus.optimizationEnabled) { + this.showDialog(); + } + } catch (error) { + console.error('Failed to check battery status:', error); + } + } + + /** + * Show battery optimization dialog + */ + private showDialog(): void { + const dialog = document.createElement('div'); + dialog.className = 'battery-optimization-dialog'; + dialog.innerHTML = ` +
+
+

Battery Optimization

+

Battery optimization may prevent notifications from being delivered reliably.

+

For the best experience, please disable battery optimization for this app.

+
+ + +
+
+
+ `; + + this.container.appendChild(dialog); + + // Add event listeners + dialog.querySelector('#open-battery-settings')?.addEventListener('click', async () => { + try { + await DailyNotification.requestBatteryOptimizationExemption(); + this.hideDialog(); + } catch (error) { + console.error('Failed to open battery settings:', error); + } + }); + + dialog.querySelector('#skip-battery-optimization')?.addEventListener('click', () => { + this.hideDialog(); + }); + } + + /** + * Hide battery optimization dialog + */ + private hideDialog(): void { + const dialog = this.container.querySelector('.battery-optimization-dialog'); + if (dialog) { + dialog.remove(); + } + } +} + +/** + * iOS Background App Refresh Dialog + * Handles iOS-specific background app refresh prompts + */ +export class iOSBackgroundRefreshDialog { + private container: HTMLElement; + + constructor(container: HTMLElement) { + this.container = container; + } + + /** + * Check and show background refresh dialog if needed + */ + async checkAndShow(): Promise { + try { + const status = await DailyNotification.checkPermissions(); + + if (status.backgroundRefresh === 'denied') { + this.showDialog(); + } + } catch (error) { + console.error('Failed to check background refresh status:', error); + } + } + + /** + * Show background refresh dialog + */ + private showDialog(): void { + const dialog = document.createElement('div'); + dialog.className = 'background-refresh-dialog'; + dialog.innerHTML = ` +
+
+

Background App Refresh

+

Background App Refresh enables the app to fetch new content even when it's not actively being used.

+

Without this, notifications will only show cached content.

+
+ + +
+
+
+ `; + + this.container.appendChild(dialog); + + // Add event listeners + dialog.querySelector('#open-settings')?.addEventListener('click', () => { + // Open iOS settings + window.open('app-settings:', '_blank'); + this.hideDialog(); + }); + + dialog.querySelector('#continue-without')?.addEventListener('click', () => { + this.hideDialog(); + }); + } + + /** + * Hide background refresh dialog + */ + private hideDialog(): void { + const dialog = this.container.querySelector('.background-refresh-dialog'); + if (dialog) { + dialog.remove(); + } + } +} + +// ============================================================================ +// 5. ERROR HANDLING UI +// ============================================================================ + +/** + * Error Display Component + * Shows errors and provides recovery options + */ +export class ErrorDisplay { + private container: HTMLElement; + + constructor(container: HTMLElement) { + this.container = container; + } + + /** + * Show error + */ + showError(error: Error, recoveryActions?: { + onRetry?: () => Promise; + onReset?: () => Promise; + onContactSupport?: () => void; + }): void { + this.container.innerHTML = ` +
+
⚠️
+
+

Something went wrong

+

${error.message}

+

Error Code: ${error.name}

+
+
+ ${recoveryActions?.onRetry ? '' : ''} + ${recoveryActions?.onReset ? '' : ''} + ${recoveryActions?.onContactSupport ? '' : ''} +
+
+ `; + + // Add event listeners + if (recoveryActions?.onRetry) { + this.container.querySelector('#retry-action')?.addEventListener('click', async () => { + try { + await recoveryActions.onRetry!(); + this.hide(); + } catch (retryError) { + console.error('Retry failed:', retryError); + } + }); + } + + if (recoveryActions?.onReset) { + this.container.querySelector('#reset-action')?.addEventListener('click', async () => { + try { + await recoveryActions.onReset!(); + this.hide(); + } catch (resetError) { + console.error('Reset failed:', resetError); + } + }); + } + + if (recoveryActions?.onContactSupport) { + this.container.querySelector('#support-action')?.addEventListener('click', () => { + recoveryActions.onContactSupport!(); + }); + } + } + + /** + * Hide error display + */ + hide(): void { + this.container.innerHTML = ''; + } +} + +// ============================================================================ +// 6. USAGE EXAMPLES +// ============================================================================ + +/** + * Example: Complete UI Integration + * Shows how to integrate all UI components + */ +export class NotificationUIManager { + private permissionDialog: PermissionRequestDialog; + private permissionStatus: PermissionStatusDisplay; + private settingsPanel: NotificationSettingsPanel; + private statusDashboard: StatusDashboard; + private errorDisplay: ErrorDisplay; + private batteryDialog: AndroidBatteryOptimizationDialog; + private backgroundRefreshDialog: iOSBackgroundRefreshDialog; + + constructor(container: HTMLElement) { + // Initialize all UI components + this.permissionDialog = new PermissionRequestDialog( + container.querySelector('#permission-dialog-container')!, + { + onAllow: this.handlePermissionAllow.bind(this), + onDeny: this.handlePermissionDeny.bind(this), + onNever: this.handlePermissionNever.bind(this) + } + ); + + this.permissionStatus = new PermissionStatusDisplay( + container.querySelector('#permission-status-container')! + ); + + this.settingsPanel = new NotificationSettingsPanel( + container.querySelector('#settings-container')! + ); + + this.statusDashboard = new StatusDashboard( + container.querySelector('#status-container')! + ); + + this.errorDisplay = new ErrorDisplay( + container.querySelector('#error-container')! + ); + + this.batteryDialog = new AndroidBatteryOptimizationDialog( + container.querySelector('#battery-dialog-container')! + ); + + this.backgroundRefreshDialog = new iOSBackgroundRefreshDialog( + container.querySelector('#background-refresh-dialog-container')! + ); + } + + /** + * Initialize the complete UI + */ + async initialize(): Promise { + try { + // Check permissions first + const permissionStatus = await DailyNotification.checkPermissions(); + + if (!permissionStatus.granted) { + await this.permissionDialog.show(); + } else { + await this.permissionStatus.updateStatus(); + } + + // Initialize other components + await this.settingsPanel.initialize(); + await this.statusDashboard.initialize(); + + // Check platform-specific requirements + await this.batteryDialog.checkAndShow(); + await this.backgroundRefreshDialog.checkAndShow(); + + } catch (error) { + console.error('Failed to initialize UI:', error); + this.errorDisplay.showError(error as Error, { + onRetry: () => this.initialize(), + onReset: () => this.resetConfiguration(), + onContactSupport: () => this.contactSupport() + }); + } + } + + /** + * Handle permission allow + */ + private async handlePermissionAllow(): Promise { + try { + await DailyNotification.requestPermissions(); + await this.permissionStatus.updateStatus(); + } catch (error) { + console.error('Failed to request permissions:', error); + this.errorDisplay.showError(error as Error); + } + } + + /** + * Handle permission deny + */ + private handlePermissionDeny(): void { + console.log('User denied permissions'); + // Show limited functionality message + } + + /** + * Handle permission never + */ + private handlePermissionNever(): void { + console.log('User chose never ask again'); + // Store preference and show limited functionality message + } + + /** + * Reset configuration + */ + private async resetConfiguration(): Promise { + try { + await DailyNotification.cancelAllNotifications(); + // Reset to default settings + await this.settingsPanel.initialize(); + } catch (error) { + console.error('Failed to reset configuration:', error); + } + } + + /** + * Contact support + */ + private contactSupport(): void { + // Implementation for contacting support + console.log('Contacting support...'); + } + + /** + * Cleanup + */ + destroy(): void { + this.statusDashboard.stopAutoRefresh(); + } +} + +// ============================================================================ +// 7. CSS STYLES (for reference) +// ============================================================================ + +export const notificationUIStyles = ` +/* Permission Dialog Styles */ +.permission-dialog .dialog-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.permission-dialog .dialog-content { + background: white; + border-radius: 12px; + padding: 24px; + max-width: 400px; + margin: 20px; +} + +.permission-dialog h2 { + margin: 0 0 16px 0; + color: #333; +} + +.permission-dialog p { + margin: 0 0 16px 0; + color: #666; + line-height: 1.5; +} + +.permission-dialog ul { + margin: 0 0 24px 0; + padding-left: 20px; +} + +.permission-dialog li { + margin: 8px 0; + color: #666; +} + +.dialog-actions { + display: flex; + flex-direction: column; + gap: 12px; +} + +.btn-primary, .btn-secondary, .btn-tertiary { + padding: 12px 24px; + border: none; + border-radius: 8px; + font-size: 16px; + cursor: pointer; + transition: background-color 0.2s; +} + +.btn-primary { + background: #1976d2; + color: white; +} + +.btn-primary:hover { + background: #1565c0; +} + +.btn-secondary { + background: #f5f5f5; + color: #333; + border: 1px solid #ddd; +} + +.btn-secondary:hover { + background: #e0e0e0; +} + +.btn-tertiary { + background: transparent; + color: #666; +} + +.btn-tertiary:hover { + background: #f5f5f5; +} + +/* Status Display Styles */ +.permission-status { + padding: 16px; + border-radius: 8px; + margin: 16px 0; +} + +.status-granted { + background: #e8f5e8; + border: 1px solid #4caf50; +} + +.status-denied { + background: #ffebee; + border: 1px solid #f44336; +} + +.status-indicator { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 12px; +} + +.status-icon { + font-size: 20px; + font-weight: bold; +} + +.permission-details { + margin: 12px 0; +} + +.permission-item { + display: flex; + justify-content: space-between; + margin: 8px 0; +} + +.granted { + color: #4caf50; + font-weight: bold; +} + +.denied { + color: #f44336; + font-weight: bold; +} + +/* Settings Panel Styles */ +.settings-panel { + background: white; + border-radius: 12px; + padding: 24px; + margin: 16px 0; +} + +.settings-panel h3 { + margin: 0 0 24px 0; + color: #333; +} + +.setting-group { + margin: 20px 0; +} + +.setting-label { + display: flex; + align-items: center; + gap: 8px; + font-weight: 500; + color: #333; +} + +.checkbox-group { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; + margin-top: 8px; +} + +.preference-grid { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 16px; + margin-top: 8px; +} + +.setting-actions { + display: flex; + gap: 12px; + margin-top: 24px; +} + +/* Status Dashboard Styles */ +.status-dashboard { + background: white; + border-radius: 12px; + padding: 24px; + margin: 16px 0; +} + +.status-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + margin: 20px 0; +} + +.status-item { + padding: 16px; + background: #f8f9fa; + border-radius: 8px; +} + +.status-label { + font-size: 14px; + color: #666; + margin-bottom: 8px; +} + +.status-value { + font-size: 18px; + font-weight: bold; + color: #333; +} + +.status-value.active { + color: #4caf50; +} + +.status-value.warning { + color: #ff9800; +} + +.status-value.error { + color: #f44336; +} + +.performance-metrics { + margin: 24px 0; + padding: 16px; + background: #f8f9fa; + border-radius: 8px; +} + +.metrics-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + margin-top: 12px; +} + +.metric-item { + display: flex; + justify-content: space-between; +} + +.metric-label { + color: #666; +} + +.metric-value { + font-weight: bold; + color: #333; +} + +/* Error Display Styles */ +.error-display { + background: #ffebee; + border: 1px solid #f44336; + border-radius: 8px; + padding: 16px; + margin: 16px 0; +} + +.error-icon { + font-size: 24px; + margin-bottom: 12px; +} + +.error-content h3 { + margin: 0 0 8px 0; + color: #d32f2f; +} + +.error-message { + color: #666; + margin: 8px 0; +} + +.error-code { + font-size: 12px; + color: #999; + font-family: monospace; +} + +.error-actions { + display: flex; + gap: 12px; + margin-top: 16px; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .status-grid { + grid-template-columns: 1fr; + } + + .preference-grid { + grid-template-columns: 1fr; + } + + .checkbox-group { + grid-template-columns: 1fr; + } + + .metrics-grid { + grid-template-columns: 1fr; + } +} +`; + +// ============================================================================ +// 8. EXPORT ALL COMPONENTS +// ============================================================================ + +export { + PermissionRequestDialog, + PermissionStatusDisplay, + NotificationSettingsPanel, + StatusDashboard, + AndroidBatteryOptimizationDialog, + iOSBackgroundRefreshDialog, + ErrorDisplay, + NotificationUIManager +};