Files
Hearth/app/src/main/assets/h5/js/pages/settings.js
T

283 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 设置页: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, '&quot;') + '">' +
'<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)));
}