estim_control/src/context/EstimContext.tsx
2025-02-28 13:50:53 +08:00

175 lines
5.1 KiB
TypeScript

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<BluetoothState>(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<PeripheralItem | null>(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<Channels, PrimitiveAtom<ChannelState>> = {
a: atomWithRefresh<ChannelState>(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<ChannelState>(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<UnlistenFn | null>(null);
const unlistenDevice = useRef<UnlistenFn | null>(null);
const unlistenPreipherals = useRef<UnlistenFn | null>(null);
const unlistenChannelA = useRef<UnlistenFn | null>(null);
const unlistenChannelB = useRef<UnlistenFn | null>(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;