ya-webadb/apps/demo/components/demo-mode.tsx

353 lines
11 KiB
TypeScript

import { Dropdown, IDropdownOption, Position, Separator, SpinButton, Toggle } from '@fluentui/react';
import { AdbDemoModeMobileDataType, AdbDemoModeMobileDataTypes, AdbDemoModeSignalStrength, AdbDemoModeStatusBarMode, AdbDemoModeStatusBarModes } from '@yume-chan/adb';
import { autorun, makeAutoObservable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react-lite";
import { CSSProperties, useCallback } from 'react';
import { global } from "../state";
const SignalStrengthOptions =
Object.values(AdbDemoModeSignalStrength)
.map((key) => ({
key,
text: {
[AdbDemoModeSignalStrength.Hidden]: 'Hidden',
[AdbDemoModeSignalStrength.Level0]: 'Level 0',
[AdbDemoModeSignalStrength.Level1]: 'Level 1',
[AdbDemoModeSignalStrength.Level2]: 'Level 2',
[AdbDemoModeSignalStrength.Level3]: 'Level 3',
[AdbDemoModeSignalStrength.Level4]: 'Level 4',
}[key],
}));
const MobileDataTypeOptions =
AdbDemoModeMobileDataTypes
.map((key) => ({
key,
text: {
'1x': '1X',
'3g': '3G',
'4g': '4G',
'4g+': '4G+',
'5g': '5G',
'5ge': '5GE',
'5g+': '5G+',
'e': 'EDGE',
'g': 'GPRS',
'h': 'HSPA',
'h+': 'HSPA+',
'lte': 'LTE',
'lte+': 'LTE+',
'dis': 'Disabled',
'not': 'Not default SIM',
'null': 'Unknown',
}[key],
}));
const StatusBarModeOptions =
AdbDemoModeStatusBarModes
.map((key) => ({
key,
text: {
'opaque': 'Opaque',
'translucent': 'Translucent',
'semi-transparent': 'Semi-transparent',
'transparent': 'Transparent',
'warning': 'Warning',
}[key],
}));
class DemoModeState {
allowed = false;
enabled = false;
features: Map<string, unknown> = new Map();
constructor() {
makeAutoObservable(this);
reaction(
() => global.device,
async (device) => {
if (device) {
const allowed = await device.demoMode.getAllowed();
runInAction(() => this.allowed = allowed);
if (allowed) {
const enabled = await device.demoMode.getEnabled();
runInAction(() => this.enabled = enabled);
}
} else {
this.allowed = false;
this.enabled = false;
this.features.clear();
}
},
{ fireImmediately: true }
);
// Apply all features when enable
autorun(() => {
if (this.enabled) {
for (const group of FEATURES) {
for (const feature of group) {
feature.onChange(this.features.get(feature.key) ?? feature.initial);
}
}
}
});
}
}
const state = new DemoModeState();
interface FeatureDefinition {
key: string;
label: string;
type: string;
min?: number;
max?: number;
step?: number;
options?: { key: string, text: string; }[];
initial: unknown;
onChange: (value: unknown) => void;
}
const FEATURES: FeatureDefinition[][] = [
[
{
key: 'batteryLevel',
label: 'Battery Level',
type: 'number',
min: 0,
max: 100,
step: 1,
initial: 100,
onChange: (value) => global.device!.demoMode.setBatteryLevel(value as number),
},
{
key: 'batteryCharging',
label: 'Battery Charging',
type: 'boolean',
initial: false,
onChange: (value) => global.device!.demoMode.setBatteryCharging(value as boolean),
},
{
key: 'powerSaveMode',
label: 'Power Save Mode',
type: 'boolean',
initial: false,
onChange: (value) => global.device!.demoMode.setPowerSaveMode(value as boolean),
},
],
[
{
key: 'wifiSignalStrength',
label: 'Wifi Signal Strength',
type: 'select',
options: SignalStrengthOptions,
initial: AdbDemoModeSignalStrength.Level4,
onChange: (value) => global.device!.demoMode.setWifiSignalStrength(value as AdbDemoModeSignalStrength),
},
{
key: 'airplaneMode',
label: 'Airplane Mode',
type: 'boolean',
initial: false,
onChange: (value) => global.device!.demoMode.setAirplaneMode(value as boolean),
},
{
key: 'mobileDataType',
label: 'Mobile Data Type',
type: 'select',
options: MobileDataTypeOptions,
initial: 'lte',
onChange: (value) => global.device!.demoMode.setMobileDataType(value as AdbDemoModeMobileDataType),
},
{
key: 'mobileSignalStrength',
label: 'Mobile Signal Strength',
type: 'select',
options: SignalStrengthOptions,
initial: AdbDemoModeSignalStrength.Level4,
onChange: (value) => global.device!.demoMode.setMobileSignalStrength(value as AdbDemoModeSignalStrength),
},
],
[
{
key: 'statusBarMode',
label: 'Status Bar Mode',
type: 'select',
options: StatusBarModeOptions,
initial: 'transparent',
onChange: (value) => global.device!.demoMode.setStatusBarMode(value as AdbDemoModeStatusBarMode),
},
{
key: 'vibrateMode',
label: 'Vibrate Mode Indicator',
type: 'boolean',
initial: false,
onChange: (value) => global.device!.demoMode.setVibrateModeEnabled(value as boolean),
},
{
key: 'bluetoothConnected',
label: 'Bluetooth Indicator',
type: 'boolean',
initial: false,
onChange: (value) => global.device!.demoMode.setBluetoothConnected(value as boolean),
},
{
key: 'locatingIcon',
label: 'Locating Icon',
type: 'boolean',
initial: false,
onChange: (value) => global.device!.demoMode.setLocatingIcon(value as boolean),
},
{
key: 'alarmIcon',
label: 'Alarm Icon',
type: 'boolean',
initial: false,
onChange: (value) => global.device!.demoMode.setAlarmIcon(value as boolean),
},
{
key: 'notificationsVisibility',
label: 'Notifications Visibility',
type: 'boolean',
initial: true,
onChange: (value) => global.device!.demoMode.setNotificationsVisibility(value as boolean),
},
{
key: 'hour',
label: 'Clock Hour',
type: 'number',
min: 0,
max: 23,
step: 1,
initial: 12,
onChange: (value) => global.device!.demoMode.setTime(value as number, state.features.get('minute') as number | undefined ?? 34)
},
{
key: 'minute',
label: 'Clock Minute',
type: 'number',
min: 0,
max: 59,
step: 1,
initial: 34,
onChange: (value) => global.device!.demoMode.setTime(state.features.get('hour') as number | undefined ?? 34, value as number)
},
],
];
const FeatureBase = ({ feature }: { feature: FeatureDefinition; }) => {
const handleChange = useCallback((e, value: unknown) => {
switch (feature.type) {
case 'select':
value = (value as IDropdownOption).key;
break;
case 'number':
value = parseFloat(value as string);
default:
break;
}
feature.onChange(value);
runInAction(() => {
state.features.set(feature.key, value);
state.enabled = true;
});
}, [feature]);
const value = state.features.get(feature.key) ?? feature.initial;
switch (feature.type) {
case 'boolean':
return (
<Toggle
label={feature.label}
disabled={!state.allowed}
checked={value as boolean}
onChange={handleChange}
/>
);
case 'number':
return (
<SpinButton
label={feature.label}
labelPosition={Position.top}
disabled={!state.allowed}
min={feature.min}
max={feature.max}
step={feature.step}
value={value as string}
onChange={handleChange}
/>
);
case 'select':
return (
<Dropdown
label={feature.label}
disabled={!state.allowed}
options={feature.options!}
selectedKey={value as string}
onChange={handleChange}
/>
);
default:
return null;
};
};
const Feature = observer(FeatureBase);
export interface DemoModeProps {
style?: CSSProperties;
}
const DemoModeBase = ({
style,
}: DemoModeProps) => {
const handleAllowedChange = useCallback(async (e, value?: boolean) => {
await global.device!.demoMode.setAllowed(value!);
runInAction(() => {
state.allowed = value!;
state.enabled = false;
});
}, []);
const handleEnabledChange = useCallback(async (e, value?: boolean) => {
await global.device!.demoMode.setEnabled(value!);
runInAction(() => state.enabled = value!);
}, []);
return (
<div style={{ padding: 12, overflow: 'hidden auto', ...style }}>
<Toggle
label="Allowed"
disabled={!global.device}
checked={state.allowed}
onChange={handleAllowedChange}
/>
<Toggle
label="Enabled"
disabled={!state.allowed}
checked={state.enabled}
onChange={handleEnabledChange}
/>
<div><strong>Note:</strong></div>
<div>Device may not support all options.</div>
{FEATURES.map((group, index) => (
<div key={index}>
<Separator />
{group.map(feature => (
<Feature key={feature.key} feature={feature} />
))}
</div>
))}
</div>
);
};
export const DemoMode = observer(DemoModeBase);