用户报告设置页很多按钮无反应 + 关闭后托盘看不到图标。两处问题:\n\n1. ServerCard 占位按钮无回调:\n- server-card.slint:去掉了 toggled/edit-clicked/remove-clicked 三个回调以及 enabled 切换、Edit / Remove 按钮;变成纯展示卡(状态点 + name + url + 当前标记)\n- settings-page.slint:去掉硬编码的 Home / NAS 占位,改用 active-server-name / active-server-url / active-server-enabled 三个属性(MainWindow → AppConfig 实时填充);空时显示 '(未配置服务器)' 空态提示\n- app.slint:MainWindow 加三个属性 + 转发给 SettingsPage\n- lib.rs:startup 阶段把活跃服务器信息塞进 UI;测试连接成功/失败时更新 enabled 状态\n\n2. 托盘图标看不见:\n- tray.rs:原本 hIcon 写 null,Win10+ 上 Shell_NotifyIconW(NIM_ADD, NULL hIcon) 实际渲染为空白或很弱的小方块。改用 LoadIconW(NULL, IDI_APPLICATION) 加载 Windows 自带的应用程序图标(无需嵌入 .ico 资源,主题感知)。show_toast / Drop 的 hIcon 维持 null(让 NIM_MODIFY/NIM_DELETE 不动原有图标)。AGENTS.md §53 标记真 .ico 资源为 V2 项。
417 lines
17 KiB
Plaintext
417 lines
17 KiB
Plaintext
// Main window + sidebar (AGENTS.md §6, §7).
|
||
//
|
||
// Phase 7 C4:
|
||
// - UI 字符串全部翻译为简体中文(AGENTS.md §55:默认中文)。
|
||
// - 优先级过滤从 Slint 侧 visible 谓词改为 Rust 侧重新构建
|
||
// filtered VecModel(参见 message-list.slint 的注释)。
|
||
// - 新增 `priority-filter-changed(int)` 回调,Sidebar 切换过滤
|
||
// 时通知 Rust 重建 filtered model。
|
||
|
||
import { AppearanceMode } from "./theme.slint";
|
||
import { NavItem, SectionLabel } from "./components.slint";
|
||
import { MessageItem, Priority } from "./message-card.slint";
|
||
import { MessageList } from "./message-list.slint";
|
||
import { EmptyState } from "./empty-state.slint";
|
||
import { SettingsPage } from "./settings-page.slint";
|
||
import { AboutDialog } from "./dialogs.slint";
|
||
|
||
export enum SidebarSelection {
|
||
All,
|
||
Settings,
|
||
About,
|
||
}
|
||
|
||
export component MainWindow inherits Window {
|
||
title: "Gotify";
|
||
preferred-width: 960px;
|
||
preferred-height: 680px;
|
||
min-width: 760px;
|
||
min-height: 520px;
|
||
|
||
in-out property <AppearanceMode> theme: AppearanceMode.Dark;
|
||
out property <bool> is-dark: theme != AppearanceMode.Light;
|
||
|
||
out property <color> bg: is-dark ? #1f2024 : #f5f6f8;
|
||
out property <color> surface: is-dark ? #28292d : #ffffff;
|
||
out property <color> surface-elev: is-dark ? #2f3035 : #fafbfc;
|
||
out property <color> border: is-dark ? #3a3b40 : #e4e6ea;
|
||
out property <color> text-primary: is-dark ? #e6e6e6 : #1a1a1a;
|
||
out property <color> text-second: is-dark ? #a0a0a0 : #5c5c5c;
|
||
out property <color> text-muted: is-dark ? #6a6a6a : #9a9a9a;
|
||
out property <color> accent: is-dark ? #4ea1ff : #0a66c2;
|
||
out property <color> success: #4caf50;
|
||
out property <color> warning: #ff9800;
|
||
out property <color> danger: #f44336;
|
||
out property <color> priority-low: #6a6a6a;
|
||
out property <color> priority-normal: is-dark ? #4ea1ff : #0a66c2;
|
||
out property <color> priority-high: #ff9800;
|
||
out property <color> priority-max: #f44336;
|
||
|
||
background: bg;
|
||
|
||
in-out property <SidebarSelection> selection: SidebarSelection.All;
|
||
|
||
// ── Phase 7 C4 fix: AboutDialog overlay (see dialogs.slint header) ──
|
||
in-out property <bool> show-about: false;
|
||
callback close-about();
|
||
|
||
// ── 消息 + 选择 ──
|
||
in-out property <[MessageItem]> messages;
|
||
in-out property <int> selected-message-index: -1;
|
||
callback test-notification-clicked();
|
||
callback message-card-clicked(int);
|
||
|
||
// ── Phase 7 C4: 优先级过滤。sidebar 的高亮状态由此属性驱动,但
|
||
// 真正的列表过滤已搬到 Rust 侧(每次切换重建 filtered VecModel,
|
||
// 避免 Slint `visible: false` 在 for 循环里不折叠空间导致的布局
|
||
// bug)。0=全部, 1=低, 2=普通, 3=高, 4=最高 ──
|
||
in-out property <int> priority-filter: 0;
|
||
callback priority-filter-changed(int);
|
||
|
||
// ── 设置 / About / 窗口状态 ──
|
||
in-out property <int> appearance-index: 2;
|
||
callback about-clicked();
|
||
callback appearance-changed-persist(int);
|
||
|
||
in-out property <bool> desktop-notify: true;
|
||
in-out property <bool> sound: true;
|
||
in-out property <bool> notify-high: true;
|
||
in-out property <bool> notify-max: true;
|
||
in-out property <bool> start-with-windows: false;
|
||
in-out property <bool> start-minimized: false;
|
||
in-out property <bool> close-to-tray: true;
|
||
callback setting-toggled(string, bool);
|
||
|
||
in-out property <bool> is-connected: false;
|
||
in-out property <string> status-text: "未连接";
|
||
callback test-connection-requested();
|
||
|
||
// Phase 7 C5: active-server summary forwarded into SettingsPage
|
||
// so the Connection section reflects the real AppConfig instead
|
||
// of the old hardcoded "Home / NAS" placeholders.
|
||
in property <string> active-server-name: "(未配置服务器)";
|
||
in property <string> active-server-url: "";
|
||
in property <bool> active-server-enabled: false;
|
||
|
||
HorizontalLayout {
|
||
// §7 Sidebar width: 220-240px.
|
||
Rectangle {
|
||
width: 232px;
|
||
background: surface;
|
||
|
||
VerticalLayout {
|
||
padding: 20px;
|
||
spacing: 6px;
|
||
|
||
Text {
|
||
text: "Gotify";
|
||
color: text-primary;
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
}
|
||
Text {
|
||
text: "Windows 客户端 · Phase 7";
|
||
color: text-muted;
|
||
font-size: 10px;
|
||
}
|
||
|
||
Rectangle { height: 12px; }
|
||
|
||
// Live status row (Phase 3 C2): dot + text.
|
||
HorizontalLayout {
|
||
spacing: 6px;
|
||
alignment: center;
|
||
Rectangle {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 4px;
|
||
background: is-connected ? success : text-muted;
|
||
}
|
||
Text {
|
||
text: is-connected ? "已连接" : "未连接";
|
||
color: is-connected ? success : text-muted;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
Rectangle { height: 12px; }
|
||
|
||
SectionLabel { label: "消息"; text-color: text-muted; }
|
||
NavItem {
|
||
label: "全部";
|
||
selected: root.selection == SidebarSelection.All
|
||
&& root.priority-filter == 0;
|
||
clicked => {
|
||
root.selection = SidebarSelection.All;
|
||
root.priority-filter = 0;
|
||
root.priority-filter-changed(0);
|
||
}
|
||
}
|
||
|
||
Rectangle { height: 12px; }
|
||
|
||
SectionLabel { label: "服务器"; text-color: text-muted; }
|
||
Text {
|
||
text: "(未配置服务器)";
|
||
color: text-muted;
|
||
font-size: 11px;
|
||
}
|
||
|
||
Rectangle { height: 12px; }
|
||
|
||
SectionLabel { label: "过滤"; text-color: text-muted; }
|
||
NavItem {
|
||
label: "最高";
|
||
selected: root.selection == SidebarSelection.All
|
||
&& root.priority-filter == 4;
|
||
has-accent: true;
|
||
accent-color: priority-max;
|
||
clicked => {
|
||
root.selection = SidebarSelection.All;
|
||
root.priority-filter = 4;
|
||
root.priority-filter-changed(4);
|
||
}
|
||
}
|
||
NavItem {
|
||
label: "高";
|
||
selected: root.selection == SidebarSelection.All
|
||
&& root.priority-filter == 3;
|
||
has-accent: true;
|
||
accent-color: priority-high;
|
||
clicked => {
|
||
root.selection = SidebarSelection.All;
|
||
root.priority-filter = 3;
|
||
root.priority-filter-changed(3);
|
||
}
|
||
}
|
||
NavItem {
|
||
label: "普通";
|
||
selected: root.selection == SidebarSelection.All
|
||
&& root.priority-filter == 2;
|
||
has-accent: true;
|
||
accent-color: priority-normal;
|
||
clicked => {
|
||
root.selection = SidebarSelection.All;
|
||
root.priority-filter = 2;
|
||
root.priority-filter-changed(2);
|
||
}
|
||
}
|
||
NavItem {
|
||
label: "低";
|
||
selected: root.selection == SidebarSelection.All
|
||
&& root.priority-filter == 1;
|
||
has-accent: true;
|
||
accent-color: priority-low;
|
||
clicked => {
|
||
root.selection = SidebarSelection.All;
|
||
root.priority-filter = 1;
|
||
root.priority-filter-changed(1);
|
||
}
|
||
}
|
||
|
||
Rectangle { height: 12px; }
|
||
|
||
SectionLabel { label: "应用"; text-color: text-muted; }
|
||
NavItem {
|
||
label: "设置";
|
||
selected: root.selection == SidebarSelection.Settings;
|
||
clicked => { root.selection = SidebarSelection.Settings; }
|
||
}
|
||
NavItem {
|
||
label: "关于";
|
||
selected: root.selection == SidebarSelection.About;
|
||
clicked => {
|
||
root.selection = SidebarSelection.About;
|
||
root.about-clicked();
|
||
root.show-about = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Right pane — driven by `selection`.
|
||
Rectangle {
|
||
background: bg;
|
||
|
||
HorizontalLayout {
|
||
padding: 0;
|
||
spacing: 0;
|
||
|
||
// 消息列(All 视图时可见)。
|
||
Rectangle {
|
||
background: bg;
|
||
horizontal-stretch: 1;
|
||
visible: root.selection == SidebarSelection.All;
|
||
|
||
VerticalLayout {
|
||
padding: 24px;
|
||
spacing: 4px;
|
||
|
||
Text {
|
||
text: messages.length == 0 ? "消息" : "消息 · " + messages.length;
|
||
color: text-primary;
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
}
|
||
Text {
|
||
text: status-text;
|
||
color: text-muted;
|
||
font-size: 11px;
|
||
}
|
||
|
||
Rectangle { height: 12px; }
|
||
|
||
if messages.length > 0: MessageList {
|
||
messages: messages;
|
||
selected-index: root.selected-message-index;
|
||
vertical-stretch: 1;
|
||
message-clicked(idx) => {
|
||
root.selected-message-index = idx;
|
||
root.message-card-clicked(idx);
|
||
}
|
||
}
|
||
|
||
if messages.length == 0: EmptyState {
|
||
vertical-stretch: 1;
|
||
test-notification => {
|
||
root.test-notification-clicked();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 详情面板:选中消息且在 All 视图时显示。
|
||
if root.selection == SidebarSelection.All
|
||
&& root.selected-message-index >= 0
|
||
&& root.selected-message-index < root.messages.length: Rectangle {
|
||
width: 360px;
|
||
background: surface-elev;
|
||
border-color: border;
|
||
border-width: 1px;
|
||
|
||
VerticalLayout {
|
||
padding: 20px;
|
||
spacing: 8px;
|
||
|
||
HorizontalLayout {
|
||
spacing: 8px;
|
||
alignment: center;
|
||
Rectangle {
|
||
width: 10px;
|
||
height: 10px;
|
||
border-radius: 5px;
|
||
background: root.messages[root.selected-message-index].priority == Priority.Low ? priority-low :
|
||
root.messages[root.selected-message-index].priority == Priority.Normal ? priority-normal :
|
||
root.messages[root.selected-message-index].priority == Priority.High ? priority-high :
|
||
priority-max;
|
||
}
|
||
Text {
|
||
text: root.messages[root.selected-message-index].app_name;
|
||
color: text-second;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
horizontal-stretch: 1;
|
||
}
|
||
Text {
|
||
text: root.messages[root.selected-message-index].timestamp_unix == 0
|
||
? ""
|
||
: "id #" + root.messages[root.selected-message-index].id;
|
||
color: text-muted;
|
||
font-size: 11px;
|
||
}
|
||
}
|
||
|
||
Text {
|
||
text: root.messages[root.selected-message-index].title != ""
|
||
? root.messages[root.selected-message-index].title
|
||
: root.messages[root.selected-message-index].body;
|
||
color: text-primary;
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
wrap: word-wrap;
|
||
}
|
||
|
||
Text {
|
||
text: root.messages[root.selected-message-index].relative_time;
|
||
color: text-muted;
|
||
font-size: 11px;
|
||
}
|
||
|
||
Rectangle {
|
||
height: 1px;
|
||
background: border;
|
||
}
|
||
|
||
Text {
|
||
text: root.messages[root.selected-message-index].body;
|
||
color: text-primary;
|
||
font-size: 13px;
|
||
wrap: word-wrap;
|
||
vertical-stretch: 1;
|
||
}
|
||
|
||
if root.messages[root.selected-message-index].has_click_url: Rectangle {
|
||
height: 32px;
|
||
background: accent;
|
||
border-radius: 6px;
|
||
TouchArea {
|
||
clicked => {
|
||
root.message-card-clicked(root.selected-message-index);
|
||
}
|
||
}
|
||
Text {
|
||
x: 0;
|
||
y: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
text: "打开链接 ↗";
|
||
color: white;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
horizontal-alignment: center;
|
||
vertical-alignment: center;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if root.selection == SidebarSelection.Settings: SettingsPage {
|
||
appearance-index: root.appearance-index;
|
||
desktop-notify: root.desktop-notify;
|
||
sound: root.sound;
|
||
notify-high: root.notify-high;
|
||
notify-max: root.notify-max;
|
||
start-with-windows: root.start-with-windows;
|
||
start-minimized: root.start-minimized;
|
||
close-to-tray: root.close-to-tray;
|
||
// Phase 7 C5: forward active server config so the
|
||
// Connection section shows the real entry instead
|
||
// of the hardcoded "Home / NAS" placeholders.
|
||
active-server-name: root.active-server-name;
|
||
active-server-url: root.active-server-url;
|
||
active-server-enabled: root.active-server-enabled;
|
||
appearance-changed(idx) => {
|
||
root.appearance-index = idx;
|
||
root.theme = idx == 0 ? AppearanceMode.System :
|
||
idx == 1 ? AppearanceMode.Light :
|
||
AppearanceMode.Dark;
|
||
root.appearance-changed-persist(idx);
|
||
}
|
||
test-connection-requested => {
|
||
root.test-connection-requested();
|
||
}
|
||
toggle-persist(label, value) => {
|
||
root.setting-toggled(label, value);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if root.show-about: AboutDialog {
|
||
close-dialog => {
|
||
root.show-about = false;
|
||
}
|
||
}
|
||
} |