Files
donetick/src/utils/StatusBarManager.js

224 lines
6.2 KiB
JavaScript

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