import { Capacitor } from '@capacitor/core' import { StatusBar, Style } from '@capacitor/status-bar' import { SafeArea } from 'capacitor-plugin-safe-area' import { THEME_BACKGROUND } from '@/constants/theme' /** * StatusBarManager - A utility class to handle status bar configuration * following Capacitor best practices and theme-aware styling */ class StatusBarManager { constructor() { this.isNativePlatform = Capacitor.isNativePlatform() this.platform = Capacitor.getPlatform() this.listeners = [] this.currentTheme = 'light' this.safeAreaApplied = false } /** * Initialize the status bar with proper configuration * @param {string} initialTheme - The initial theme ('light' | 'dark' | 'system') */ async initialize(initialTheme = 'light') { if (!this.isNativePlatform) { console.log('StatusBarManager: Not running on native platform') return } try { // Configure basic status bar settings await StatusBar.setOverlaysWebView({ overlay: false }) await StatusBar.show() // Set initial theme await this.setTheme(initialTheme) // Apply safe area insets await this.applySafeAreaInsets() console.log('StatusBarManager: Initialized successfully') } catch (error) { console.error('StatusBarManager: Failed to initialize:', error) } } /** * Set status bar style based on theme * @param {string} theme - The theme ('light' | 'dark' | 'system') */ async setTheme(theme) { if (!this.isNativePlatform) return this.currentTheme = theme try { const resolvedTheme = theme === 'system' ? window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' : theme const style = resolvedTheme === 'dark' ? Style.Dark : Style.Light await StatusBar.setStyle({ style }) await StatusBar.setBackgroundColor({ color: THEME_BACKGROUND[resolvedTheme], }) console.log(`StatusBarManager: Theme set to ${theme}, style: ${style}`) } catch (error) { console.error('StatusBarManager: Failed to set theme:', error) } } /** * Apply safe area insets using CSS custom properties * Components should use env() variables or CSS custom properties for proper safe area handling */ async applySafeAreaInsets() { if (!this.isNativePlatform || this.safeAreaApplied) return try { // Get safe area insets const { insets } = await SafeArea.getSafeAreaInsets() // Apply CSS custom properties for safe area this.applySafeAreaCSS(insets) this.safeAreaApplied = true console.log('StatusBarManager: Safe area insets applied:', insets) } catch (error) { console.error( 'StatusBarManager: Failed to apply safe area insets:', error, ) } } /** * Apply safe area insets using CSS custom properties * @param {Object} insets - The safe area insets */ applySafeAreaCSS(insets) { const root = document.documentElement // Set CSS custom properties that can be used throughout the app root.style.setProperty('--safe-area-inset-top', `${insets.top}px`) root.style.setProperty('--safe-area-inset-right', `${insets.right}px`) root.style.setProperty('--safe-area-inset-bottom', `${insets.bottom}px`) root.style.setProperty('--safe-area-inset-left', `${insets.left}px`) // Note: We no longer apply padding directly to the body to avoid double // application with component-level safe area handling. Components should // use the CSS custom properties or the utility classes from safe-area.css // Let's apply it directly to body for now: if (Capacitor.getPlatform() === 'android') { // removing the top padding on android as it is handled by the navbar // and adding it causes double padding // document.body.style.paddingTop = `${insets.top}px` document.body.style.paddingRight = `${insets.right}px` document.body.style.paddingBottom = `${insets.bottom}px` document.body.style.paddingLeft = `${insets.left}px` } } /** * Add a listener for theme changes * @param {Function} callback - Function to call when theme changes * @returns {Function} - Cleanup function to remove the listener */ addThemeChangeListener(callback) { this.listeners.push(callback) // Return cleanup function return () => { const index = this.listeners.indexOf(callback) if (index > -1) { this.listeners.splice(index, 1) } } } /** * Notify all listeners of theme change * @param {string} newTheme - The new theme */ notifyThemeChange(newTheme) { this.listeners.forEach(callback => { try { callback(newTheme) } catch (error) { console.error( 'StatusBarManager: Error in theme change listener:', error, ) } }) } /** * Update status bar based on resolved theme (after system detection) * @param {string} resolvedTheme - The actual theme being used ('light' | 'dark') */ async updateResolvedTheme(resolvedTheme) { if (!this.isNativePlatform) return await this.setTheme(resolvedTheme) } /** * Hide the status bar */ async hide() { if (!this.isNativePlatform) return try { await StatusBar.hide() } catch (error) { console.error('StatusBarManager: Failed to hide status bar:', error) } } /** * Show the status bar */ async show() { if (!this.isNativePlatform) return try { await StatusBar.show() } catch (error) { console.error('StatusBarManager: Failed to show status bar:', error) } } /** * Get current status bar info */ async getInfo() { if (!this.isNativePlatform) return null try { return await StatusBar.getInfo() } catch (error) { console.error('StatusBarManager: Failed to get status bar info:', error) return null } } /** * Clean up all listeners and reset state */ cleanup() { this.listeners = [] this.safeAreaApplied = false console.log('StatusBarManager: Cleaned up') } } // Create and export a singleton instance const statusBarManager = new StatusBarManager() export default statusBarManager