283 lines
10 KiB
JavaScript
283 lines
10 KiB
JavaScript
// 设置页:Miuix Preference 分组 + 玻璃条目,含主题/遮罩/亮度/权限/网络交互
|
||
// Settings page: Miuix Preference groups + glass items with interactions for
|
||
// theme / mask / brightness / permission / network
|
||
window.loadSettings = function () {
|
||
const page = document.querySelector('[data-page="settings"]');
|
||
if (page.dataset.loaded) return; page.dataset.loaded = '1';
|
||
page.innerHTML = `<div class="st-wrap">
|
||
<div class="st-group">显示</div>
|
||
<div class="st-item" id="theme-item">
|
||
<span class="lbl">主题</span>
|
||
<span class="arrow" id="theme-state">跟随系统</span>
|
||
</div>
|
||
<div class="st-item" id="glass-item">
|
||
<span class="lbl">玻璃效果</span>
|
||
<span class="sw" id="glass-sw"><i></i></span>
|
||
</div>
|
||
<div class="st-item" id="brightness-item">
|
||
<span class="lbl">屏幕亮度</span>
|
||
<span class="arrow" id="brightness-state">›</span>
|
||
</div>
|
||
<div class="st-group" id="mask-group" style="display:none">背景遮罩</div>
|
||
<div class="st-item" id="mask-item" style="display:none">
|
||
<span class="lbl">启用背景遮罩</span>
|
||
<span class="sw" id="mask-sw"><i></i></span>
|
||
</div>
|
||
<div class="st-item" id="mask-alpha-item" style="display:none">
|
||
<span class="lbl">遮罩明暗度</span>
|
||
<span class="slider" id="mask-slider"><i></i></span>
|
||
</div>
|
||
<div class="st-group">权限</div>
|
||
<div class="st-item" id="notif-access">
|
||
<span class="lbl">通知使用权(媒体卡)</span>
|
||
<span class="arrow" id="notif-access-state">›</span>
|
||
</div>
|
||
<div class="st-group">网络</div>
|
||
<div class="st-item" id="server-url-item">
|
||
<span class="lbl">webAPP 服务器地址</span>
|
||
<span class="arrow" id="server-url-state">›</span>
|
||
</div>
|
||
<div class="st-item" id="check-update-item">
|
||
<span class="lbl">检查更新</span>
|
||
<span class="arrow" id="check-update-state">›</span>
|
||
</div>
|
||
<div class="st-group">关于</div>
|
||
<div class="st-item" id="export-log-item">
|
||
<span class="lbl">导出日志</span>
|
||
<span class="arrow" id="export-log-state">›</span>
|
||
</div>
|
||
<div class="st-item"><span class="lbl">版本 0.1.0</span><span class="arrow">›</span></div>
|
||
</div>`;
|
||
setupTheme(page);
|
||
setupGlass(page);
|
||
setupMask(page);
|
||
setupNotificationAccess(page);
|
||
setupBrightness(page);
|
||
setupServerUrl(page);
|
||
setupCheckUpdate(page);
|
||
setupExportLog(page);
|
||
};
|
||
|
||
// 判断当前是否深色主题(与 mask.js 逻辑一致)
|
||
// Whether the current theme is dark (same logic as mask.js)
|
||
function isDarkTheme() {
|
||
const t = theme.get();
|
||
if (t === 'dark') return true;
|
||
if (t === 'light') return false;
|
||
return matchMedia('(prefers-color-scheme: dark)').matches;
|
||
}
|
||
|
||
// 主题三态循环:跟随系统 → 浅色 → 深色
|
||
// Theme tri-state cycle: system → light → dark
|
||
function setupTheme(page) {
|
||
const state = page.querySelector('#theme-state');
|
||
const refresh = () => { state.textContent = theme.label(theme.get()); };
|
||
refresh();
|
||
page.querySelector('#theme-item').onclick = () => {
|
||
theme.cycle();
|
||
refresh();
|
||
window.mask.apply();
|
||
setupMask(page); // 主题切换后重算遮罩项的显示与状态
|
||
};
|
||
}
|
||
|
||
// 玻璃效果开关:普通半透明 ↔ 液态玻璃(backdrop-filter 磨砂 + 边缘高光)
|
||
// Glass effect toggle: plain translucent vs liquid glass
|
||
function setupGlass(page) {
|
||
const sw = page.querySelector('#glass-sw');
|
||
const refresh = () => sw.classList.toggle('on', glass.enabled());
|
||
refresh();
|
||
page.querySelector('#glass-item').onclick = () => {
|
||
glass.setEnabled(!glass.enabled());
|
||
refresh();
|
||
};
|
||
}
|
||
|
||
// 背景遮罩:开关 + 明暗度(仅夜间主题显示)
|
||
// Wallpaper dim: toggle + opacity (shown only under dark theme)
|
||
function setupMask(page) {
|
||
const group = page.querySelector('#mask-group');
|
||
const maskItem = page.querySelector('#mask-item');
|
||
const alphaItem = page.querySelector('#mask-alpha-item');
|
||
const sw = page.querySelector('#mask-sw');
|
||
const slider = page.querySelector('#mask-slider');
|
||
const thumb = slider.querySelector('i');
|
||
|
||
const dark = isDarkTheme();
|
||
const show = dark;
|
||
group.style.display = show ? '' : 'none';
|
||
maskItem.style.display = show ? '' : 'none';
|
||
alphaItem.style.display = show ? '' : 'none';
|
||
|
||
const refresh = () => {
|
||
sw.classList.toggle('on', window.mask.enabled());
|
||
const a = window.mask.alpha();
|
||
thumb.style.left = (a * 100) + '%';
|
||
slider.style.setProperty('--fill', (a * 100) + '%');
|
||
};
|
||
refresh();
|
||
|
||
sw.onclick = () => {
|
||
window.mask.setEnabled(!window.mask.enabled());
|
||
refresh();
|
||
};
|
||
makeSliderDraggable(slider, (a) => {
|
||
window.mask.setAlpha(Math.round(a * 100) / 100);
|
||
refresh();
|
||
});
|
||
}
|
||
|
||
// 通知使用权授权项:异步查状态,未授权点击跳转系统授权页
|
||
// Notification-access item: async state check, tap to open the system grant page
|
||
function setupNotificationAccess(page) {
|
||
const item = page.querySelector('#notif-access');
|
||
const state = page.querySelector('#notif-access-state');
|
||
bridge.call('getNotificationAccess').then(granted => {
|
||
state.textContent = granted ? '已开启 ✓' : '去开启 ›';
|
||
if (granted) state.style.color = 'var(--accent)';
|
||
}).catch(() => {});
|
||
item.onclick = () => {
|
||
bridge.call('requestNotificationAccess').catch(() => {});
|
||
};
|
||
}
|
||
|
||
// 屏幕亮度:未授权「修改系统设置」时跳授权页,已授权点击滑块调整系统亮度
|
||
// Screen brightness: jump to grant page when lacking WRITE_SETTINGS; adjust via
|
||
// slider click once granted
|
||
function setupBrightness(page) {
|
||
const item = page.querySelector('#brightness-item');
|
||
const state = page.querySelector('#brightness-state');
|
||
let sliderEl = null;
|
||
|
||
const renderSlider = (value) => {
|
||
if (sliderEl) sliderEl.remove();
|
||
sliderEl = document.createElement('span');
|
||
sliderEl.className = 'slider brightness-slider';
|
||
sliderEl.innerHTML = '<i></i>';
|
||
sliderEl.style.width = '160px';
|
||
const pct = value / 255 * 100;
|
||
sliderEl.style.setProperty('--fill', pct + '%');
|
||
sliderEl.querySelector('i').style.left = pct + '%';
|
||
makeSliderDraggable(sliderEl, (ratio) => {
|
||
const v = Math.round(ratio * 255);
|
||
bridge.call('setSystemBrightness', v).then(() => renderSlider(v)).catch(() => {});
|
||
});
|
||
item.appendChild(sliderEl);
|
||
};
|
||
|
||
const refresh = () => {
|
||
bridge.call('canWriteSettings').then(canWrite => {
|
||
if (!canWrite) {
|
||
state.textContent = '去授权 ›';
|
||
if (sliderEl) { sliderEl.remove(); sliderEl = null; }
|
||
} else {
|
||
bridge.call('getSystemBrightness').then(v => {
|
||
state.textContent = '';
|
||
renderSlider(v);
|
||
}).catch(() => {});
|
||
}
|
||
}).catch(() => {});
|
||
};
|
||
refresh();
|
||
|
||
item.onclick = () => {
|
||
bridge.call('canWriteSettings').then(canWrite => {
|
||
if (!canWrite) bridge.call('requestWriteSettings');
|
||
}).catch(() => {});
|
||
};
|
||
}
|
||
|
||
// webAPP 服务器地址:点击展开输入框,保存后重新拉取清单
|
||
// webAPP server URL: expand an input on tap, re-fetch manifest after saving
|
||
function setupServerUrl(page) {
|
||
const item = page.querySelector('#server-url-item');
|
||
const state = page.querySelector('#server-url-state');
|
||
let editing = false;
|
||
|
||
const startEdit = (current) => {
|
||
editing = true;
|
||
item.innerHTML = '<input class="url-input" id="server-url-input" value="' + (current || '').replace(/"/g, '"') + '">' +
|
||
'<button class="url-save" id="server-url-save">确认</button>';
|
||
const input = item.querySelector('#server-url-input');
|
||
const saveBtn = item.querySelector('#server-url-save');
|
||
input.focus();
|
||
const save = () => {
|
||
const v = input.value.trim();
|
||
if (!v) return;
|
||
bridge.call('setServerUrl', v).then(() => {
|
||
editing = false;
|
||
refresh();
|
||
}).catch(() => {});
|
||
};
|
||
saveBtn.onclick = save;
|
||
input.onkeydown = (e) => { if (e.key === 'Enter') save(); };
|
||
};
|
||
|
||
const refresh = () => {
|
||
bridge.call('getServerUrl').then(url => {
|
||
state.textContent = (url || '未设置') + ' ›';
|
||
}).catch(() => {});
|
||
};
|
||
refresh();
|
||
|
||
item.onclick = () => {
|
||
if (editing) return;
|
||
bridge.call('getServerUrl').then(startEdit).catch(() => startEdit(''));
|
||
};
|
||
}
|
||
|
||
// 检查更新:触发重新拉取 webAPP 清单 + 卡片目录,完成后提示
|
||
// Check update: re-fetch the webAPP manifest + card catalog, then show a hint
|
||
function setupCheckUpdate(page) {
|
||
const item = page.querySelector('#check-update-item');
|
||
const state = page.querySelector('#check-update-state');
|
||
item.onclick = () => {
|
||
state.textContent = '检查中…';
|
||
bridge.call('refreshManifest').then(result => {
|
||
let r = { added: 0, removed: 0, changed: 0 };
|
||
try { r = JSON.parse(result); } catch (e) {}
|
||
const parts = [];
|
||
if (r.added > 0) parts.push('新增 ' + r.added);
|
||
if (r.removed > 0) parts.push('移除 ' + r.removed);
|
||
if (r.changed > 0) parts.push('更新 ' + r.changed);
|
||
state.textContent = parts.length ? parts.join(',') + ' ›' : '无更新 ›';
|
||
}).catch(() => {
|
||
state.textContent = '失败 ›';
|
||
});
|
||
};
|
||
}
|
||
|
||
// 导出日志:点击导出到 Download,显示结果
|
||
// Export log: tap to export to Download, show the result
|
||
function setupExportLog(page) {
|
||
const item = page.querySelector('#export-log-item');
|
||
const state = page.querySelector('#export-log-state');
|
||
item.onclick = () => {
|
||
state.textContent = '导出中…';
|
||
bridge.call('exportLog').then(result => {
|
||
state.textContent = result + ' ›';
|
||
}).catch(() => {
|
||
state.textContent = '失败 ›';
|
||
});
|
||
};
|
||
}
|
||
|
||
// 通用滑块拖动:touch 拖动实时回调(保留 click 兼容桌面调试)
|
||
// Generic draggable slider: touch-drag with live callback (click kept for desktop)
|
||
function makeSliderDraggable(slider, onRatio) {
|
||
let dragging = false;
|
||
const ratioAt = (clientX) => {
|
||
const rect = slider.getBoundingClientRect();
|
||
return Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));
|
||
};
|
||
slider.addEventListener('touchstart', (e) => {
|
||
dragging = true;
|
||
onRatio(ratioAt(e.touches[0].clientX));
|
||
}, { passive: true });
|
||
slider.addEventListener('touchmove', (e) => {
|
||
if (dragging) onRatio(ratioAt(e.touches[0].clientX));
|
||
}, { passive: true });
|
||
slider.addEventListener('touchend', () => { dragging = false; }, { passive: true });
|
||
slider.addEventListener('click', (e) => onRatio(ratioAt(e.clientX)));
|
||
}
|