Files
OpenStats/Packages/OpenStatsKit/Sources/OpenStatsUI/Panel/KeepAwakePage.swift
T
gentpan 4741d40e1b OpenStats:macOS 菜单栏系统监控初始版本
- 菜单栏:CPU/GPU/内存多样式、网速两行、温度、风扇
- 面板:概览仪表盘、进程、散热、防休眠,液态玻璃与浅色/深色主题
- 采集层:CPU/内存/网络/GPU/磁盘/电池/进程/SMC 温度与风扇,按界面分级采样
- root 辅助工具:风扇调速、合盖不睡眠,断开时自动恢复
- XcodeGen 工程、单元测试、截图模式
2026-09-13 02:44:05 +05:00

145 lines
5.5 KiB
Swift

import Metrics
import SwiftUI
struct KeepAwakePage: View {
@Environment(AppModel.self) private var model
var body: some View {
PageScroll {
HeroCard()
HStack(alignment: .top, spacing: DS.Space.s3) {
VStack(spacing: DS.Space.s3) {
ModeCard()
DurationCard()
}
.frame(maxWidth: .infinity)
LidCard().frame(maxWidth: .infinity)
}
.fixedSize(horizontal: false, vertical: true)
}
}
}
private struct HeroCard: View {
@Environment(AppModel.self) private var model
var body: some View {
let keepAwake = model.keepAwake
Card {
HStack(spacing: DS.Space.s3) {
RoundedRectangle(cornerRadius: DS.Radius.md)
.fill(keepAwake.isActive ? DS.Palette.primary : DS.Palette.track)
.frame(width: DS.Space.s12 - DS.Space.s2, height: DS.Space.s12 - DS.Space.s2)
.overlay {
Image(systemName: keepAwake.isActive ? "cup.and.saucer.fill" : "cup.and.saucer")
.font(.system(size: DS.TextSize.lg.rawValue, weight: .medium))
.foregroundStyle(keepAwake.isActive ? DS.Palette.onPrimary : DS.Palette.textSecondary)
}
VStack(alignment: .leading, spacing: DS.Space.s1 / 2) {
Text("防休眠")
.dsFont(.lg, weight: .semibold)
.foregroundStyle(DS.Palette.textPrimary)
StatusLine()
}
Spacer()
DSToggle(isOn: Binding(get: { keepAwake.isActive },
set: { value in Task { await keepAwake.setActive(value) } }),
label: "防休眠")
}
if let notice = keepAwake.notice {
InfoBanner(icon: keepAwake.noticeIsError ? "exclamationmark.triangle.fill" : "info.circle.fill",
text: notice,
tone: keepAwake.noticeIsError ? .error : .primary)
}
}
}
}
private struct StatusLine: View {
@Environment(AppModel.self) private var model
var body: some View {
let keepAwake = model.keepAwake
TimelineView(.periodic(from: .now, by: 30)) { context in
Text(text(now: context.date))
.dsFont(.sm)
.foregroundStyle(keepAwake.isActive ? DS.Palette.primary : DS.Palette.textSecondary)
.monospacedDigit()
}
}
private func text(now: Date) -> String {
let keepAwake = model.keepAwake
guard keepAwake.isActive else { return "未开启,Mac 按系统设置休眠" }
var parts = [keepAwake.mode.title]
if keepAwake.lidClosedActive { parts.append("合盖运行") }
if let end = keepAwake.endDate {
let minutes = max(1, Int(ceil(end.timeIntervalSince(now) / 60)))
parts.append("剩余 \(Format.duration(minutes: minutes))")
} else {
parts.append("不限时")
}
return parts.joined(separator: " · ")
}
}
private struct ModeCard: View {
@Environment(AppModel.self) private var model
var body: some View {
Card {
Text("模式").dsFont(.sm, weight: .semibold).foregroundStyle(DS.Palette.textPrimary)
ForEach(Array(KeepAwakeController.Mode.allCases.enumerated()), id: \.element) { index, mode in
if index > 0 { HairlineDivider() }
RadioRow(title: mode.title, subtitle: mode.subtitle, isSelected: model.keepAwake.mode == mode) {
model.keepAwake.setMode(mode)
}
}
}
}
}
private struct LidCard: View {
@Environment(AppModel.self) private var model
var body: some View {
let keepAwake = model.keepAwake
Card {
SettingRow(title: "合盖后继续运行",
subtitle: "合上屏幕时 Mac 不进入睡眠,下载、渲染、远程连接不中断。") {
DSToggle(isOn: Binding(get: { keepAwake.lidClosedRequested },
set: { value in Task { await keepAwake.setLidClosed(value) } }),
label: "合盖后继续运行")
.disabled(!model.helper.isReady)
}
if !model.helper.isReady {
HelperRequiredBanner(text: "合盖运行需要修改系统睡眠设置,需安装辅助工具(管理员授权一次)。")
}
InfoBanner(icon: "exclamationmark.triangle",
text: "合盖运行时散热变差,请勿放入包中。使用电池且电量低于 \(model.settings.lidModeBatteryFloor)% 时会自动关闭。",
tone: .warning)
}
}
}
private struct DurationCard: View {
@Environment(AppModel.self) private var model
var body: some View {
Card {
Text("持续时间").dsFont(.sm, weight: .semibold).foregroundStyle(DS.Palette.textPrimary)
SegmentedControl(selection: Binding(get: { model.keepAwake.duration },
set: { model.keepAwake.setDuration($0) }),
options: KeepAwakeController.Duration.allCases.map { ($0, $0.title) })
Text("到时后自动恢复系统默认的睡眠行为。")
.dsFont(.xs)
.foregroundStyle(DS.Palette.textTertiary)
}
}
}