UNPKG

one

Version:

One is a React framework for web, iOS, and Android apps.

224 lines (204 loc) • 5.83 kB
import { useEffect, useState } from 'react' import type { BatteryPowerState, BatteryState, BatterySubscription } from './types' import { assertLevelListener, assertLowPowerModeListener, assertStateListener, } from './validate' export type { BatteryLevelEvent, BatteryPowerModeEvent, BatteryPowerState, BatteryState, BatteryStateEvent, BatterySubscription, } from './types' // web entry. same signatures as the native entry: the published // declarations are built from this file and serve both platforms. the // battery status api supplies live level and charging state where the // browser has it; without it reads resolve expo's web values (full, // unknown, not low power) and listeners never fire. interface WebBatteryManager extends EventTarget { readonly level: number readonly charging: boolean } function getBatteryFn(): (() => Promise<WebBatteryManager>) | undefined { if (typeof navigator === 'undefined') return undefined const getBattery = (navigator as { getBattery?: unknown }).getBattery return typeof getBattery === 'function' ? (getBattery as () => Promise<WebBatteryManager>) : undefined } async function systemBattery(): Promise<WebBatteryManager | undefined> { const getBattery = getBatteryFn() if (!getBattery) return undefined try { return await getBattery() } catch { return undefined } } async function getLevel(): Promise<number> { const battery = await systemBattery() return battery ? battery.level : 1 } function stateOf(battery: WebBatteryManager | undefined): BatteryState { if (!battery) return 'unknown' return battery.charging ? 'charging' : 'unplugged' } async function getState(): Promise<BatteryState> { return stateOf(await systemBattery()) } async function getPowerState(): Promise<BatteryPowerState> { const battery = await systemBattery() return { batteryLevel: battery ? battery.level : 1, batteryState: stateOf(battery), lowPowerMode: false, } } async function isLowPowerModeEnabled(): Promise<boolean> { return false } function isAvailable(): boolean { return getBatteryFn() !== undefined } function listen( event: 'levelchange' | 'chargingchange', listener: () => void ): BatterySubscription { if (typeof window === 'undefined') return { remove: () => {}, } let removed = false void systemBattery().then((battery) => { if (removed) return battery?.addEventListener(event, listener) }) return { remove: () => { removed = true void systemBattery().then((battery) => { battery?.removeEventListener(event, listener) }) }, } } function addLevelListener( listener: (event: { batteryLevel: number }) => void ): BatterySubscription { assertLevelListener(listener) return listen('levelchange', () => { void getLevel().then((batteryLevel) => listener({ batteryLevel })) }) } function addStateListener( listener: (event: { batteryState: BatteryState }) => void ): BatterySubscription { assertStateListener(listener) return listen('chargingchange', () => { void getState().then((batteryState) => listener({ batteryState })) }) } function addLowPowerModeListener( listener: (event: { lowPowerMode: boolean }) => void ): BatterySubscription { assertLowPowerModeListener(listener) return { remove: () => {} } } function useBatteryLevel(): number { const [level, setLevel] = useState(1) useEffect(() => { let active = true let fromEvent = false void getLevel().then((next) => { if (active && !fromEvent) setLevel(next) }) const subscription = addLevelListener((event) => { if (active) { fromEvent = true setLevel(event.batteryLevel) } }) return () => { active = false subscription.remove() } }, []) return level } function useBatteryState(): BatteryState { const [state, setState] = useState<BatteryState>('unknown') useEffect(() => { let active = true let fromEvent = false void getState().then((next) => { if (active && !fromEvent) setState(next) }) const subscription = addStateListener((event) => { if (active) { fromEvent = true setState(event.batteryState) } }) return () => { active = false subscription.remove() } }, []) return state } function useLowPowerMode(): boolean { const [enabled] = useState(false) return enabled } function usePowerState(): BatteryPowerState { const [power, setPower] = useState<BatteryPowerState>({ batteryLevel: 1, batteryState: 'unknown', lowPowerMode: false, }) useEffect(() => { let active = true let levelFromEvent = false let stateFromEvent = false void getPowerState().then((next) => { if (!active) return setPower((current) => ({ batteryLevel: levelFromEvent ? current.batteryLevel : next.batteryLevel, batteryState: stateFromEvent ? current.batteryState : next.batteryState, lowPowerMode: next.lowPowerMode, })) }) const onLevel = addLevelListener((event) => { if (active) { levelFromEvent = true setPower((current) => ({ ...current, batteryLevel: event.batteryLevel })) } }) const onState = addStateListener((event) => { if (active) { stateFromEvent = true setPower((current) => ({ ...current, batteryState: event.batteryState })) } }) return () => { active = false onLevel.remove() onState.remove() } }, []) return power } export const Battery = Object.freeze({ getLevel, getState, getPowerState, isLowPowerModeEnabled, isAvailable, addLevelListener, addStateListener, addLowPowerModeListener, }) export { useBatteryLevel, useBatteryState, useLowPowerMode, usePowerState }