import { invoke } from '@tauri-apps/api/core'; import { listen, UnlistenFn } from '@tauri-apps/api/event'; import { message } from '@tauri-apps/plugin-dialog'; import { atom, PrimitiveAtom, useSetAtom } from 'jotai'; import { atomFamily, atomWithRefresh, RESET } from 'jotai/utils'; import { FC, ReactNode, useEffect, useRef } from 'react'; export type Channels = 'a' | 'b'; type ChannelState = { playing: boolean; playMode: 'shuffle' | 'repeat' | 'repeat-one'; strength: number; maxStrength: number; boosting: boolean; boostLevel: number; maxBoostLevel: number; }; type PeripheralItem = { id: string; address: string; represent: string | null; isUnknown: boolean; isConnected: boolean; rssi: number | null; battery: number | null; }; type BluetoothState = { ready: boolean | null; searching: boolean | null; connected: string | null; }; export const BleState = atomWithRefresh(async (get) => { try { const state = await invoke('central_device_state'); return { ready: state.isReady, searching: state.isScanning, connected: state.connected, }; } catch (e) { console.error('[refresh central]', e); } return { ready: null, searching: null, connected: null, }; }); export const DeviceState = atomWithRefresh(async (get) => { try { const state = await invoke('connected_peripheral_state'); return state; } catch (e) { console.error('[refresh connected]', e); } return null; }); export const FoundPeripherals = atom( [] as PeripheralItem[], (get, set, item: PeripheralItem | typeof RESET) => { if (item === RESET) { void set(FoundPeripherals, []); } else { void set(FoundPeripherals, (prev) => { const foundIndex = prev.findIndex((i) => i.id === item.id); if (foundIndex !== -1) { prev[foundIndex] = item; } else { prev.push(item); } return prev; }); } }, ); const Channels: Record> = { a: atomWithRefresh(async (get) => { try { const state = await invoke('channel_a_state'); return { playing: state.isPlaying, playMode: state.playMode, strength: state.strength, maxStrength: state.maxStrength, boosting: state.isBoosting, boostLevel: state.boostLevel, maxBoostLevel: state.maxBoostLevel, }; } catch (e) { console.error('[refresh channel a]', e); } return { playing: false, playMode: 'repeat-one', strength: 0, maxStrength: 100, boosting: false, boostLevel: 0, maxBoostLevel: 100, }; }), b: atom(async (get) => { try { const state = await invoke('channel_b_state'); return { playing: state.isPlaying, playMode: state.playMode, strength: state.strength, maxStrength: state.maxStrength, boosting: state.isBoosting, boostLevel: state.boostLevel, maxBoostLevel: state.maxBoostLevel, }; } catch (e) { console.error('[refresh channel b]', e); } return { playing: false, playMode: 'repeat-one', strength: 0, maxStrength: 100, boosting: false, boostLevel: 0, maxBoostLevel: 100, }; }), }; export const ChannelState = atomFamily((channel: Channels) => Channels[channel]); const EstimWatchProvider: FC<{ children?: ReactNode }> = ({ children }) => { const unlistenBle = useRef(null); const unlistenDevice = useRef(null); const unlistenPreipherals = useRef(null); const unlistenChannelA = useRef(null); const unlistenChannelB = useRef(null); const refreshBle = useSetAtom(BleState); const refreshDevice = useSetAtom(DeviceState); const refreshPeripherals = useSetAtom(FoundPeripherals); const refreshChannelA = useSetAtom(ChannelState('a')); const refreshChannelB = useSetAtom(ChannelState('b')); useEffect(() => { (async function () { try { unlistenBle.current = await listen('central_state_updated', () => refreshBle()); unlistenDevice.current = await listen('peripheral_connected', () => refreshDevice()); unlistenPreipherals.current = await listen('peripheral_found', (event) => { refreshPeripherals(event.payload); }); unlistenChannelA.current = await listen('channel_a_updated', () => refreshChannelA()); unlistenChannelB.current = await listen('channel_b_updated', () => refreshChannelB()); await invoke('activate_central_adapter'); } catch (e) { console.error('[Activate Adapter]', e); await message('Fail to activate Bluetooth adapter.', { title: 'Bluetooth Error', kind: 'error', }); } })(); return () => { unlistenBle.current?.(); unlistenDevice.current?.(); unlistenPreipherals.current?.(); unlistenChannelA.current?.(); unlistenChannelB.current?.(); }; }, []); return <>{children}; }; export default EstimWatchProvider;