version>=29 使用了新的重命名接口、EQ 排序功能

This commit is contained in:
eafonyang
2026-07-07 17:14:52 +08:00
parent 61fc54cd5a
commit b7fdfe0101
12 changed files with 468 additions and 77 deletions
+63 -21
View File
@@ -21,12 +21,14 @@ import { toast } from "sonner";
import {
fetchLuxsinAudioCurve,
normalizePeqFiltersForSubmit,
parseFirmwareVersion,
type PeqFilter,
type PeqApplyPayload,
type PeqChangePayload,
type PeqState,
buildPeqPresetBody,
} from "@/lib/luxsinApi";
import { supportsPeqPresetRenameAndSort } from "@/config/firmwareFeatures";
import { getFilterType, getFilterShortName } from "@/lib/peqAudio";
import localeZh from "@/locales/data-zh.json";
import localeEn from "@/locales/data-en.json";
@@ -101,6 +103,12 @@ export default function EQPage() {
return (peq.eqUi ?? (localeEn.peq as typeof localeEn.peq).eqUi) as PeqEqUi;
}, [deviceState?.language]);
const firmwareVersion = useMemo(
() => parseFirmwareVersion(deviceState?.version),
[deviceState?.version],
);
const peqReorderEnabled = supportsPeqPresetRenameAndSort(firmwareVersion);
const peqCardLabels = useMemo(() => {
const pack = resolveLocalePack(deviceState?.language);
return pack.peq ?? localeEn.peq;
@@ -526,15 +534,18 @@ export default function EQPage() {
const applyPeqStateToUI = (
remote: { peq?: Array<{ name: string; filters?: any[] | string }>; peqSelect?: number; filters?: PeqFilter[] },
) => {
const resolvedPeqSelect =
remote.peqSelect ?? deviceState?.peqSelect ?? peqState?.peqSelect ?? 0;
if (remote.peq !== undefined) {
syncPeqCatalog({
filters: remote.filters ?? peqState?.filters ?? [],
peq: remote.peq,
peqSelect: remote.peqSelect ?? deviceState?.peqSelect ?? peqState?.peqSelect,
peqSelect: resolvedPeqSelect,
});
}
lastPeqCatalogSyncKeyRef.current = buildPeqCatalogSyncKey(remote, deviceState?.peqSelect);
syncPeqCatalogFromState(remote, deviceState?.peqSelect);
lastPeqCatalogSyncKeyRef.current = buildPeqCatalogSyncKey(remote, resolvedPeqSelect);
syncPeqCatalogFromState(remote, resolvedPeqSelect);
const items = mergeRemotePeqCatalog((remote.peq ?? []) as PeqCatalogItem[]);
if (items.length === 0) {
@@ -544,7 +555,7 @@ export default function EQPage() {
}
const nextIdx = Math.min(
Math.max(deviceState?.peqSelect ?? remote.peqSelect ?? 0, 0),
Math.max(resolvedPeqSelect, 0),
items.length - 1,
);
const nextBands = normalizeFiltersFromPeq(items[nextIdx] as { filters?: any[] | string });
@@ -866,10 +877,11 @@ export default function EQPage() {
// msgCount 轮询更新 peqState 后,同步耳机列表与当前选中项
useEffect(() => {
if (!peqState?.peq) return;
const key = buildPeqCatalogSyncKey(peqState, deviceState?.peqSelect);
const resolvedPeqSelect = peqState.peqSelect ?? deviceState?.peqSelect;
const key = buildPeqCatalogSyncKey(peqState, resolvedPeqSelect);
if (!key || key === lastPeqCatalogSyncKeyRef.current) return;
lastPeqCatalogSyncKeyRef.current = key;
syncPeqCatalogFromState(peqState, deviceState?.peqSelect);
syncPeqCatalogFromState(peqState, resolvedPeqSelect);
}, [peqState, deviceState?.peqSelect, syncPeqCatalogFromState]);
// 切换耳机时默认回到 A 组对比
@@ -1872,6 +1884,7 @@ export default function EQPage() {
open={isPresetManageDialogOpen}
items={peqItems}
selectedIdx={headphoneIdx}
reorderEnabled={peqReorderEnabled}
onClose={() => setIsPresetManageDialogOpen(false)}
eqUi={eqUi}
onDeletePresets={async (names) => {
@@ -1896,6 +1909,26 @@ export default function EQPage() {
}}
onRenamePreset={async (oldName, newName, item) => {
try {
if (isDemoMode || !api) {
const nextItems = peqItems.map(it =>
it.name === oldName ? { ...it, name: newName } : it
);
delete peqPresetCacheRef.current[oldName];
applyPeqStateToUI({ peq: nextItems, peqSelect: headphoneIdx });
toast.success(eqUi.toastRenamed);
return true;
}
const firmwareVersion = parseFirmwareVersion(deviceState?.version);
if (supportsPeqPresetRenameAndSort(firmwareVersion)) {
await api.renamePeq(oldName, newName);
delete peqPresetCacheRef.current[oldName];
const latest = await fetchEqSyncPeq(api, "renamePreset");
applyPeqStateToUI(latest);
toast.success(eqUi.toastRenamed);
return true;
}
const rawFilters = normalizePeqFiltersForSubmit(item.filters);
const filters = rawFilters.map(f => ({
...f,
@@ -1915,26 +1948,12 @@ export default function EQPage() {
},
};
if (isDemoMode || !api) {
const nextItems = peqItems.map(it =>
it.name === oldName ? { ...it, name: newName } : it
);
delete peqPresetCacheRef.current[oldName];
applyPeqStateToUI({ peq: nextItems, peqSelect: headphoneIdx });
toast.success(eqUi.toastRenamed);
return true;
}
// Step 1: peqChange — create new preset with new name + same EQ data
// Use api.upgradePeqChange directly (NOT the DeviceContext wrapper)
// to avoid applyPeqFiltersToState side-effect that overwrites current UI bands.
// Legacy (< 29): peqChange then peqRemove
await api.upgradePeqChange(payload);
await new Promise(r => setTimeout(r, 300));
// Step 2: peqRemove — delete the old preset
delete peqPresetCacheRef.current[oldName];
await api.removePeq([oldName]);
await new Promise(r => setTimeout(r, 300));
// Step 3: sync latest state from device
const latest = await fetchEqSyncPeq(api, "renamePreset");
applyPeqStateToUI(latest);
toast.success(eqUi.toastRenamed);
@@ -1944,6 +1963,29 @@ export default function EQPage() {
return false;
}
}}
onConfirmOrder={async (names, peqSelect) => {
try {
if (isDemoMode || !api) {
const byName = new Map(peqItems.map(it => [it.name, it]));
const nextItems = names
.map(name => byName.get(name))
.filter((it): it is NonNullable<typeof it> => !!it);
applyPeqStateToUI({ peq: nextItems, peqSelect });
toast.success(eqUi.toastOrderSaved);
return true;
}
await api.sortPeq(names, peqSelect);
const latest = await fetchEqSyncPeq(api, "sortPresets");
const nextSelect = latest.peqSelect ?? peqSelect;
applyPeqStateToUI({ ...latest, peqSelect: nextSelect });
updateSetting({ peqSelect: nextSelect });
toast.success(eqUi.toastOrderSaved);
return true;
} catch {
toast.error(eqUi.toastOrderFail);
return false;
}
}}
/>
<ShareDialog