You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1304 lines
33 KiB

/**
* 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<void>;
private onDeny: () => void;
private onNever: () => void;
constructor(container: HTMLElement, callbacks: {
onAllow: () => Promise<void>;
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<void> {
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 = `
<div class="dialog-overlay">
<div class="dialog-content">
<h2>Enable Daily Notifications</h2>
<p>Get notified about new offers, projects, people, and items in your TimeSafari community.</p>
<ul>
<li>New offers directed to you</li>
<li>Changes to your projects</li>
<li>Updates from favorited people</li>
<li>New items of interest</li>
</ul>
<div class="dialog-actions">
<button id="allow-btn" class="btn-primary">Allow Notifications</button>
<button id="deny-btn" class="btn-secondary">Not Now</button>
<button id="never-btn" class="btn-tertiary">Never Ask Again</button>
</div>
</div>
</div>
`;
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<void> {
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 = `
<div class="permission-status ${statusClass}">
<div class="status-indicator">
<span class="status-icon">${status.granted ? '✓' : '✗'}</span>
<span class="status-text">${statusText}</span>
</div>
<div class="permission-details">
<div class="permission-item">
<span>Notifications:</span>
<span class="${status.notifications === 'granted' ? 'granted' : 'denied'}">
${status.notifications}
</span>
</div>
${status.backgroundRefresh ? `
<div class="permission-item">
<span>Background Refresh:</span>
<span class="${status.backgroundRefresh === 'granted' ? 'granted' : 'denied'}">
${status.backgroundRefresh}
</span>
</div>
` : ''}
</div>
<div class="permission-actions">
<button id="request-permissions" class="btn-primary">Request Permissions</button>
<button id="open-settings" class="btn-secondary">Open Settings</button>
</div>
</div>
`;
// 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 = `
<div class="permission-status status-error">
<div class="error-message">${message}</div>
<button id="retry-permissions" class="btn-primary">Retry</button>
</div>
`;
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<void> {
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 = `
<div class="settings-panel">
<h3>Notification Settings</h3>
<div class="setting-group">
<label class="setting-label">
<input type="checkbox" id="enable-notifications" ${this.settings.isEnabled ? 'checked' : ''}>
Enable Daily Notifications
</label>
</div>
<div class="setting-group">
<label for="notification-time">Notification Time:</label>
<input type="time" id="notification-time" value="${this.settings.time || '09:00'}">
</div>
<div class="setting-group">
<label>Content Types:</label>
<div class="checkbox-group">
<label><input type="checkbox" id="content-offers" checked> Offers</label>
<label><input type="checkbox" id="content-projects" checked> Projects</label>
<label><input type="checkbox" id="content-people" checked> People</label>
<label><input type="checkbox" id="content-items" checked> Items</label>
</div>
</div>
<div class="setting-group">
<label>Notification Preferences:</label>
<div class="preference-grid">
<label>
<input type="checkbox" id="sound-enabled" ${this.settings.sound ? 'checked' : ''}>
Sound
</label>
<label>
<input type="checkbox" id="vibration-enabled" checked>
Vibration
</label>
<label>
<select id="priority-level">
<option value="low" ${this.settings.priority === 'low' ? 'selected' : ''}>Low</option>
<option value="normal" ${this.settings.priority === 'normal' ? 'selected' : ''}>Normal</option>
<option value="high" ${this.settings.priority === 'high' ? 'selected' : ''}>High</option>
</select>
Priority
</label>
</div>
</div>
<div class="setting-actions">
<button id="test-notification" class="btn-secondary">Test Notification</button>
<button id="save-settings" class="btn-primary">Save Settings</button>
</div>
</div>
`;
}
/**
* 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<void> {
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<void> {
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 = `
<div class="settings-error">
<p>${message}</p>
<button id="retry-settings" class="btn-primary">Retry</button>
</div>
`;
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<void> {
await this.updateStatus();
this.startAutoRefresh();
}
/**
* Update status display
*/
async updateStatus(): Promise<void> {
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 = `
<div class="status-dashboard">
<h3>Notification Status</h3>
<div class="status-grid">
<div class="status-item">
<div class="status-label">Overall Status</div>
<div class="status-value ${status.staleArmed ? 'warning' : 'active'}">
${status.staleArmed ? 'Stale' : 'Active'}
</div>
</div>
<div class="status-item">
<div class="status-label">Next Notification</div>
<div class="status-value">${nextRunText}</div>
</div>
<div class="status-item">
<div class="status-label">Last Outcome</div>
<div class="status-value ${lastOutcomeClass}">${lastOutcome}</div>
</div>
<div class="status-item">
<div class="status-label">Cache Age</div>
<div class="status-value">${status.cacheAgeMs ? this.formatDuration(status.cacheAgeMs) : 'No cache'}</div>
</div>
</div>
<div class="performance-metrics">
<h4>Performance</h4>
<div class="metrics-grid">
<div class="metric-item">
<span class="metric-label">Success Rate:</span>
<span class="metric-value">${status.performance.successRate}%</span>
</div>
<div class="metric-item">
<span class="metric-label">Error Count:</span>
<span class="metric-value">${status.performance.errorCount}</span>
</div>
</div>
</div>
<div class="status-actions">
<button id="refresh-status" class="btn-secondary">Refresh</button>
<button id="view-details" class="btn-primary">View Details</button>
</div>
</div>
`;
// 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 = `
<div class="status-error">
<p>${message}</p>
<button id="retry-status" class="btn-primary">Retry</button>
</div>
`;
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<void> {
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 = `
<div class="dialog-overlay">
<div class="dialog-content">
<h2>Battery Optimization</h2>
<p>Battery optimization may prevent notifications from being delivered reliably.</p>
<p>For the best experience, please disable battery optimization for this app.</p>
<div class="dialog-actions">
<button id="open-battery-settings" class="btn-primary">Open Battery Settings</button>
<button id="skip-battery-optimization" class="btn-secondary">Continue Anyway</button>
</div>
</div>
</div>
`;
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<void> {
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 = `
<div class="dialog-overlay">
<div class="dialog-content">
<h2>Background App Refresh</h2>
<p>Background App Refresh enables the app to fetch new content even when it's not actively being used.</p>
<p>Without this, notifications will only show cached content.</p>
<div class="dialog-actions">
<button id="open-settings" class="btn-primary">Open Settings</button>
<button id="continue-without" class="btn-secondary">Continue Without</button>
</div>
</div>
</div>
`;
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<void>;
onReset?: () => Promise<void>;
onContactSupport?: () => void;
}): void {
this.container.innerHTML = `
<div class="error-display">
<div class="error-icon">⚠️</div>
<div class="error-content">
<h3>Something went wrong</h3>
<p class="error-message">${error.message}</p>
<p class="error-code">Error Code: ${error.name}</p>
</div>
<div class="error-actions">
${recoveryActions?.onRetry ? '<button id="retry-action" class="btn-primary">Retry</button>' : ''}
${recoveryActions?.onReset ? '<button id="reset-action" class="btn-secondary">Reset Configuration</button>' : ''}
${recoveryActions?.onContactSupport ? '<button id="support-action" class="btn-tertiary">Contact Support</button>' : ''}
</div>
</div>
`;
// 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<void> {
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<void> {
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<void> {
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
};