Enhance audio page functionality: Added vuSensor state management and updated balance and vu sensitivity calculations. Adjusted slider ranges and improved real-time updates for audio settings. Localized system page labels for better user experience.
This commit is contained in:
@@ -62,6 +62,7 @@ export interface DeviceState {
|
|||||||
audioFormat: string;
|
audioFormat: string;
|
||||||
pcm: number;
|
pcm: number;
|
||||||
vu: number;
|
vu: number;
|
||||||
|
vuSensor: number;
|
||||||
vu_count: number;
|
vu_count: number;
|
||||||
screenLight: number;
|
screenLight: number;
|
||||||
knob_breathlight: number;
|
knob_breathlight: number;
|
||||||
@@ -227,6 +228,7 @@ export const MOCK_DEVICE_STATE: DeviceState = {
|
|||||||
audioFormat: "PCM 44.1 KHz",
|
audioFormat: "PCM 44.1 KHz",
|
||||||
pcm: 1,
|
pcm: 1,
|
||||||
vu: 0,
|
vu: 0,
|
||||||
|
vuSensor: 0,
|
||||||
vu_count: 5,
|
vu_count: 5,
|
||||||
screenLight: 0,
|
screenLight: 0,
|
||||||
knob_breathlight: 1,
|
knob_breathlight: 1,
|
||||||
|
|||||||
@@ -58,14 +58,20 @@ export default function AudioPage() {
|
|||||||
const [, setLocation] = useLocation();
|
const [, setLocation] = useLocation();
|
||||||
const { deviceState, updateSetting } = useDevice();
|
const { deviceState, updateSetting } = useDevice();
|
||||||
const ds = deviceState;
|
const ds = deviceState;
|
||||||
|
const vuSensor = (ds as ({ vuSensor?: number } | null))?.vuSensor;
|
||||||
|
|
||||||
const balanceVal = ds?.balance ?? 0;
|
// balance 从接口获取的是乘以 10 后的值,需要除以 10 显示(例如:-130 → -13.0)
|
||||||
|
const balanceVal = ds?.balance !== undefined ? ds.balance / 10 : 0;
|
||||||
|
// vuSensor 从接口获取的是放大 2 倍的值,需要除以 2 显示(例如:20 → +10dB)
|
||||||
|
const vuSensVal = vuSensor !== undefined ? vuSensor / 2 : 0;
|
||||||
const [localBalance, setLocalBalance] = useState(balanceVal);
|
const [localBalance, setLocalBalance] = useState(balanceVal);
|
||||||
const [localVuSens, setLocalVuSens] = useState(12);
|
const [localVuSens, setLocalVuSens] = useState(0);
|
||||||
const [filterIdx] = useState(0);
|
const [filterIdx] = useState(0);
|
||||||
const isDragging = useRef(false);
|
const isBalanceDragging = useRef(false);
|
||||||
|
const isVuDragging = useRef(false);
|
||||||
|
|
||||||
useEffect(() => { if (!isDragging.current) setLocalBalance(balanceVal); }, [balanceVal]);
|
useEffect(() => { if (!isBalanceDragging.current) setLocalBalance(balanceVal); }, [balanceVal]);
|
||||||
|
useEffect(() => { if (!isVuDragging.current) setLocalVuSens(vuSensVal); }, [vuSensVal]);
|
||||||
|
|
||||||
const gainIdx = ds?.analogGain ?? 0;
|
const gainIdx = ds?.analogGain ?? 0;
|
||||||
const stepIdx = ds?.soundStep ?? 1;
|
const stepIdx = ds?.soundStep ?? 1;
|
||||||
@@ -99,12 +105,13 @@ export default function AudioPage() {
|
|||||||
{localBalance >= 0 ? `+${localBalance.toFixed(1)}` : localBalance.toFixed(1)} dB
|
{localBalance >= 0 ? `+${localBalance.toFixed(1)}` : localBalance.toFixed(1)} dB
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<CyanSlider value={localBalance} min={-10} max={10} step={0.1}
|
<CyanSlider value={localBalance} min={-15} max={15} step={0.1}
|
||||||
onChange={(v) => {
|
onChange={(v) => {
|
||||||
isDragging.current = true;
|
isBalanceDragging.current = true;
|
||||||
setLocalBalance(v);
|
setLocalBalance(v);
|
||||||
updateSetting({ balance: v });
|
// 实时调用接口(例如:-13.0 → -130)
|
||||||
setTimeout(() => { isDragging.current = false; }, 500);
|
updateSetting({ balance: Math.round(v * 10) });
|
||||||
|
setTimeout(() => { isBalanceDragging.current = false; }, 500);
|
||||||
}} />
|
}} />
|
||||||
</div>
|
</div>
|
||||||
<div className="border-t border-white/[0.06]" />
|
<div className="border-t border-white/[0.06]" />
|
||||||
@@ -112,14 +119,16 @@ export default function AudioPage() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-[16px] text-white">VU 表灵敏度</span>
|
<span className="text-[16px] text-white">VU 表灵敏度</span>
|
||||||
<span className="text-[16px] font-semibold" style={{ color: "#00FFF6" }}>
|
<span className="text-[16px] font-semibold" style={{ color: "#00FFF6" }}>
|
||||||
{localVuSens} dB
|
{localVuSens >= 0 ? `+${localVuSens}` : localVuSens} dB
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<CyanSlider value={localVuSens} min={0} max={24}
|
<CyanSlider value={localVuSens} min={-20} max={20}
|
||||||
onChange={(v) => {
|
onChange={(v) => {
|
||||||
isDragging.current = true;
|
isVuDragging.current = true;
|
||||||
setLocalVuSens(v);
|
setLocalVuSens(v);
|
||||||
setTimeout(() => { isDragging.current = false; }, 500);
|
// 接口参数需要将显示值乘以 2(例如:10dB -> vuSensor=20)
|
||||||
|
updateSetting({ vuSensor: Math.round(v * 2) });
|
||||||
|
setTimeout(() => { isVuDragging.current = false; }, 500);
|
||||||
}} />
|
}} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -341,9 +341,7 @@ export default function Home() {
|
|||||||
<ListRow
|
<ListRow
|
||||||
icon={<Settings size={16} />}
|
icon={<Settings size={16} />}
|
||||||
label="音频设置"
|
label="音频设置"
|
||||||
toggle
|
onClick={() => setLocation("/audio")}
|
||||||
checked={audioOn}
|
|
||||||
onToggle={(v) => updateSetting({ audio_enable: v ? 1 : 0 })}
|
|
||||||
/>
|
/>
|
||||||
<ListRow
|
<ListRow
|
||||||
icon={<Gauge size={16} />}
|
icon={<Gauge size={16} />}
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ import { useLocation } from "wouter";
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import BottomNav from "@/components/BottomNav";
|
import BottomNav from "@/components/BottomNav";
|
||||||
import { navigateToSelect } from "./SelectPage";
|
import { navigateToSelect } from "./SelectPage";
|
||||||
|
import localeZh from "@/locales/data-zh.json";
|
||||||
|
import localeZhHK from "@/locales/data-zh-HK.json";
|
||||||
|
import localeEn from "@/locales/data-en.json";
|
||||||
|
|
||||||
function IOSToggle({ checked, onChange }: { checked: boolean; onChange: (v: boolean) => void }) {
|
function IOSToggle({ checked, onChange }: { checked: boolean; onChange: (v: boolean) => void }) {
|
||||||
return (
|
return (
|
||||||
@@ -61,12 +64,31 @@ function ListRow({ label, description, value, onClick, toggle, checked, onToggle
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const SCREEN_LIGHT_OPTIONS = ["亮", "中等", "暗"];
|
type LocaleSettings = {
|
||||||
const KNOB_LIGHT_OPTIONS = ["关闭", "亮", "中等", "暗"];
|
settings: {
|
||||||
const SCREEN_OFF_OPTIONS = ["常亮", "30秒", "1分钟", "3分钟", "5分钟"];
|
vu: string;
|
||||||
const SLEEP_OPTIONS = ["关闭", "1分钟", "5分钟", "10分钟"];
|
screenBrightness: { label: string; options: Array<{ index: number; label: string }> };
|
||||||
const AUTO_HOME_OPTIONS = ["关闭", "20秒", "40秒", "60秒"];
|
turnOffScreen: { label: string; options: Array<{ index: number; label: string }> };
|
||||||
const LANG_OPTIONS = ["English", "繁體中文", "简体中文"];
|
knobBrightness: { label: string; options: Array<{ index: number; label: string }> };
|
||||||
|
buttonLight: { label: string; options: Array<{ index: number; label: string }> };
|
||||||
|
language: { label: string; options: Array<{ index: number; label: string }> };
|
||||||
|
Automatic: { label: string; options: Array<{ index: number; label: string }> };
|
||||||
|
sleepTime?: { label: string; options: Array<{ index: number; label: string }> };
|
||||||
|
sleep?: { label: string; options: Array<{ index: number; label: string }> };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const LOCALE_BY_LANG: Record<number, LocaleSettings> = {
|
||||||
|
0: localeEn as LocaleSettings,
|
||||||
|
1: localeZhHK as LocaleSettings,
|
||||||
|
2: localeZh as LocaleSettings,
|
||||||
|
};
|
||||||
|
|
||||||
|
function optionLabels(items: Array<{ index: number; label: string }> | undefined, fallback: string[]) {
|
||||||
|
if (!items || items.length === 0) return fallback;
|
||||||
|
const sorted = [...items].sort((a, b) => a.index - b.index);
|
||||||
|
return sorted.map((item) => item.label);
|
||||||
|
}
|
||||||
|
|
||||||
export default function SystemPage() {
|
export default function SystemPage() {
|
||||||
const [, setLocation] = useLocation();
|
const [, setLocation] = useLocation();
|
||||||
@@ -87,6 +109,14 @@ export default function SystemPage() {
|
|||||||
const autoHomeIdx = ds?.autoHome ?? 1;
|
const autoHomeIdx = ds?.autoHome ?? 1;
|
||||||
const langIdx = ds?.language ?? 2;
|
const langIdx = ds?.language ?? 2;
|
||||||
const knobBreathLightOn = (ds?.knob_breathlight ?? 1) === 0;
|
const knobBreathLightOn = (ds?.knob_breathlight ?? 1) === 0;
|
||||||
|
const localeData = LOCALE_BY_LANG[langIdx] ?? localeZh;
|
||||||
|
const settingsText = localeData.settings;
|
||||||
|
const SCREEN_LIGHT_OPTIONS = optionLabels(settingsText.screenBrightness?.options, ["亮", "中等", "暗"]);
|
||||||
|
const KNOB_LIGHT_OPTIONS = optionLabels(settingsText.knobBrightness?.options, ["关闭", "亮", "中等", "暗"]);
|
||||||
|
const SCREEN_OFF_OPTIONS = optionLabels(settingsText.turnOffScreen?.options, ["常亮", "30秒", "1分钟", "3分钟", "5分钟"]);
|
||||||
|
const SLEEP_OPTIONS = optionLabels(settingsText.sleepTime?.options ?? settingsText.sleep?.options, ["关闭", "1分钟", "5分钟", "10分钟"]);
|
||||||
|
const AUTO_HOME_OPTIONS = optionLabels(settingsText.Automatic?.options, ["关闭", "20秒", "40秒", "60秒"]);
|
||||||
|
const LANG_OPTIONS = optionLabels(settingsText.language?.options, ["English", "繁體中文", "简体中文"]);
|
||||||
|
|
||||||
const goSelect = (title: string, options: string[], selected: number, key: string) => {
|
const goSelect = (title: string, options: string[], selected: number, key: string) => {
|
||||||
navigateToSelect(title, options, selected, "/system", key);
|
navigateToSelect(title, options, selected, "/system", key);
|
||||||
@@ -99,7 +129,7 @@ export default function SystemPage() {
|
|||||||
<button onClick={() => setLocation("/")} className="mr-4 text-white/60 active:text-white transition-colors">
|
<button onClick={() => setLocation("/")} className="mr-4 text-white/60 active:text-white transition-colors">
|
||||||
<ChevronLeft size={24} />
|
<ChevronLeft size={24} />
|
||||||
</button>
|
</button>
|
||||||
<h1 className="flex-1 text-center text-[17px] font-semibold text-white">设置</h1>
|
<h1 className="flex-1 text-center text-[17px] font-semibold text-white">{settingsText.vu}</h1>
|
||||||
<div className="w-8" />
|
<div className="w-8" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -107,16 +137,16 @@ export default function SystemPage() {
|
|||||||
{/* ── 显示 ── */}
|
{/* ── 显示 ── */}
|
||||||
<SectionHeader title="显示" />
|
<SectionHeader title="显示" />
|
||||||
<div className="ios-list-group">
|
<div className="ios-list-group">
|
||||||
<ListRow label="屏幕亮度" description="调节屏幕的整体亮度"
|
<ListRow label={settingsText.screenBrightness.label} description="调节屏幕的整体亮度"
|
||||||
value={SCREEN_LIGHT_OPTIONS[screenLightIdx]}
|
value={SCREEN_LIGHT_OPTIONS[screenLightIdx]}
|
||||||
onClick={() => goSelect("屏幕亮度", SCREEN_LIGHT_OPTIONS, screenLightIdx, "screenLight")} />
|
onClick={() => goSelect(settingsText.screenBrightness.label, SCREEN_LIGHT_OPTIONS, screenLightIdx, "screenLight")} />
|
||||||
<ListRow label="自动锁屏" description="设备无操作后自动关闭屏幕的时间"
|
<ListRow label={settingsText.turnOffScreen.label} description="设备无操作后自动关闭屏幕的时间"
|
||||||
value={SCREEN_OFF_OPTIONS[screenOffIdx]}
|
value={SCREEN_OFF_OPTIONS[screenOffIdx]}
|
||||||
onClick={() => goSelect("自动锁屏", SCREEN_OFF_OPTIONS, screenOffIdx, "screenOff")} />
|
onClick={() => goSelect(settingsText.turnOffScreen.label, SCREEN_OFF_OPTIONS, screenOffIdx, "screenOff")} />
|
||||||
<ListRow label="旋钮亮度" description="调节旋钮指示灯的亮度"
|
<ListRow label={settingsText.knobBrightness.label} description="调节旋钮指示灯的亮度"
|
||||||
value={KNOB_LIGHT_OPTIONS[buttonLightIdx]}
|
value={KNOB_LIGHT_OPTIONS[buttonLightIdx]}
|
||||||
onClick={() => goSelect("旋钮亮度", KNOB_LIGHT_OPTIONS, buttonLightIdx, "buttonLight")} />
|
onClick={() => goSelect(settingsText.knobBrightness.label, KNOB_LIGHT_OPTIONS, buttonLightIdx, "buttonLight")} />
|
||||||
<ListRow label="旋钮熄屏呼吸灯" description="锁屏后旋钮是否显示呼吸灯效果"
|
<ListRow label={settingsText.buttonLight.label} description="锁屏后旋钮是否显示呼吸灯效果"
|
||||||
toggle checked={knobBreathLightOn}
|
toggle checked={knobBreathLightOn}
|
||||||
onToggle={(v) => updateSetting({ knob_breathlight: v ? 0 : 1 })} />
|
onToggle={(v) => updateSetting({ knob_breathlight: v ? 0 : 1 })} />
|
||||||
</div>
|
</div>
|
||||||
@@ -124,15 +154,15 @@ export default function SystemPage() {
|
|||||||
{/* ── 通用 ── */}
|
{/* ── 通用 ── */}
|
||||||
<SectionHeader title="通用" />
|
<SectionHeader title="通用" />
|
||||||
<div className="ios-list-group">
|
<div className="ios-list-group">
|
||||||
<ListRow label="语言" description="设置系统显示语言"
|
<ListRow label={settingsText.language.label} description="设置系统显示语言"
|
||||||
value={LANG_OPTIONS[langIdx]}
|
value={LANG_OPTIONS[langIdx]}
|
||||||
onClick={() => goSelect("选择语言", LANG_OPTIONS, langIdx, "language")} />
|
onClick={() => goSelect(settingsText.language.label, LANG_OPTIONS, langIdx, "language")} />
|
||||||
<ListRow label="自动返回首页" description="闲置一段时间后自动回到主界面"
|
<ListRow label={settingsText.Automatic.label} description="闲置一段时间后自动回到主界面"
|
||||||
value={AUTO_HOME_OPTIONS[autoHomeIdx]}
|
value={AUTO_HOME_OPTIONS[autoHomeIdx]}
|
||||||
onClick={() => goSelect("自动返回首页", AUTO_HOME_OPTIONS, autoHomeIdx, "autoHome")} />
|
onClick={() => goSelect(settingsText.Automatic.label, AUTO_HOME_OPTIONS, autoHomeIdx, "autoHome")} />
|
||||||
<ListRow label="休眠" description="设置设备进入低功耗模式的时间"
|
<ListRow label={settingsText.sleepTime?.label ?? settingsText.sleep?.label ?? "休眠"} description="设置设备进入低功耗模式的时间"
|
||||||
value={SLEEP_OPTIONS[sleepIdx]}
|
value={SLEEP_OPTIONS[sleepIdx]}
|
||||||
onClick={() => goSelect("休眠时间", SLEEP_OPTIONS, sleepIdx, "sleep")} />
|
onClick={() => goSelect(settingsText.sleepTime?.label ?? settingsText.sleep?.label ?? "休眠", SLEEP_OPTIONS, sleepIdx, "sleep")} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ── 关于 ── */}
|
{/* ── 关于 ── */}
|
||||||
|
|||||||
Reference in New Issue
Block a user