/** * Stale Data UX Snippets * * Platform-specific implementations for showing stale data banners * when polling hasn't succeeded for extended periods */ // Common configuration const STALE_DATA_CONFIG = { staleThresholdHours: 4, criticalThresholdHours: 24, bannerAutoDismissMs: 10000 }; // Common i18n keys const I18N_KEYS = { 'staleness.banner.title': 'Data may be outdated', 'staleness.banner.message': 'Last updated {hours} hours ago. Tap to refresh.', 'staleness.banner.critical': 'Data is very outdated. Please refresh.', 'staleness.banner.action_refresh': 'Refresh Now', 'staleness.banner.action_settings': 'Settings', 'staleness.banner.dismiss': 'Dismiss' }; /** * Android Implementation */ class AndroidStaleDataUX { private context: any; // Android Context private notificationManager: any; // NotificationManager constructor(context: any) { this.context = context; this.notificationManager = context.getSystemService('notification'); } showStalenessBanner(hoursSinceUpdate: number): void { const isCritical = hoursSinceUpdate >= STALE_DATA_CONFIG.criticalThresholdHours; const title = this.context.getString(I18N_KEYS['staleness.banner.title']); const message = isCritical ? this.context.getString(I18N_KEYS['staleness.banner.critical']) : this.context.getString(I18N_KEYS['staleness.banner.message'], hoursSinceUpdate); // Create notification const notification = { smallIcon: 'ic_warning', contentTitle: title, contentText: message, priority: isCritical ? 'high' : 'normal', autoCancel: true, actions: [ { title: this.context.getString(I18N_KEYS['staleness.banner.action_refresh']), intent: this.createRefreshIntent() }, { title: this.context.getString(I18N_KEYS['staleness.banner.action_settings']), intent: this.createSettingsIntent() } ] }; this.notificationManager.notify('stale_data_warning', notification); } private createRefreshIntent(): any { // Create PendingIntent for refresh action return { action: 'com.timesafari.dailynotification.REFRESH_DATA', flags: ['FLAG_UPDATE_CURRENT'] }; } private createSettingsIntent(): any { // Create PendingIntent for settings action return { action: 'com.timesafari.dailynotification.OPEN_SETTINGS', flags: ['FLAG_UPDATE_CURRENT'] }; } showInAppBanner(hoursSinceUpdate: number): void { // Show banner in app UI (Snackbar or similar) const message = this.context.getString( I18N_KEYS['staleness.banner.message'], hoursSinceUpdate ); // Create Snackbar const snackbar = { message, duration: 'LENGTH_INDEFINITE', action: { text: this.context.getString(I18N_KEYS['staleness.banner.action_refresh']), callback: () => this.refreshData() } }; // Show snackbar console.log('Showing Android in-app banner:', snackbar); } private refreshData(): void { // Trigger manual refresh console.log('Refreshing data on Android'); } } /** * iOS Implementation */ class iOSStaleDataUX { private viewController: any; // UIViewController constructor(viewController: any) { this.viewController = viewController; } showStalenessBanner(hoursSinceUpdate: number): void { const isCritical = hoursSinceUpdate >= STALE_DATA_CONFIG.criticalThresholdHours; const title = NSLocalizedString(I18N_KEYS['staleness.banner.title'], ''); const message = isCritical ? NSLocalizedString(I18N_KEYS['staleness.banner.critical'], '') : String(format: NSLocalizedString(I18N_KEYS['staleness.banner.message'], ''), hoursSinceUpdate); // Create alert controller const alert = { title, message, preferredStyle: 'alert', actions: [ { title: NSLocalizedString(I18N_KEYS['staleness.banner.action_refresh'], ''), style: 'default', handler: () => this.refreshData() }, { title: NSLocalizedString(I18N_KEYS['staleness.banner.action_settings'], ''), style: 'default', handler: () => this.openSettings() }, { title: NSLocalizedString(I18N_KEYS['staleness.banner.dismiss'], ''), style: 'cancel' } ] }; this.viewController.present(alert, animated: true); } showBannerView(hoursSinceUpdate: number): void { // Create banner view const banner = { title: NSLocalizedString(I18N_KEYS['staleness.banner.title'], ''), message: String(format: NSLocalizedString(I18N_KEYS['staleness.banner.message'], ''), hoursSinceUpdate), backgroundColor: 'systemYellow', textColor: 'label', actions: [ { title: NSLocalizedString(I18N_KEYS['staleness.banner.action_refresh'], ''), action: () => this.refreshData() } ] }; // Show banner console.log('Showing iOS banner view:', banner); } private refreshData(): void { console.log('Refreshing data on iOS'); } private openSettings(): void { console.log('Opening settings on iOS'); } } /** * Web Implementation */ class WebStaleDataUX { private container: HTMLElement; constructor(container: HTMLElement = document.body) { this.container = container; } showStalenessBanner(hoursSinceUpdate: number): void { const isCritical = hoursSinceUpdate >= STALE_DATA_CONFIG.criticalThresholdHours; const banner = document.createElement('div'); banner.className = `staleness-banner ${isCritical ? 'critical' : 'warning'}`; banner.innerHTML = `
`; // Add styles banner.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; background: ${isCritical ? '#ff6b6b' : '#ffd93d'}; color: ${isCritical ? 'white' : 'black'}; padding: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); z-index: 1000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; this.container.appendChild(banner); // Auto-dismiss after timeout setTimeout(() => { if (banner.parentElement) { banner.remove(); } }, STALE_DATA_CONFIG.bannerAutoDismissMs); } showToast(hoursSinceUpdate: number): void { const toast = document.createElement('div'); toast.className = 'staleness-toast'; toast.innerHTML = `