[feat] add announcement inbox with local read tracking

This commit is contained in:
dijunkun
2026-09-29 13:04:54 +08:00
parent 597c1dd2c9
commit 2d49024bd6
12 changed files with 761 additions and 6 deletions
+116 -4
View File
@@ -1,4 +1,5 @@
import { LineEdit, ScrollView } from "std-widgets.slint";
import { AnnouncementItem, MessagePanel, MessageStrings } from "message_panel.slint";
import { FontAwesomeIcons, IconButton, CompactComboBox, ImGuiCheckBox, MiniToggleSwitch, CompactButton, PrimaryButton, SecondaryButton, ToggleSwitch, SectionTitle, DialogSurface, ImGuiLineStyle, ImGuiFontStyle } from "common.slint";
export struct RecentConnection {
@@ -273,6 +274,23 @@ export component MainWindow inherits Window {
in property <bool> signal-connected: false;
in property <bool> signal-tls-error: false;
in property <[RecentConnection]> recent-connections;
in property <[AnnouncementItem]> announcements;
in property <int> announcement-unread;
in property <int> announcement-total;
in property <int> announcement-offset;
in property <bool> announcement-loading;
in property <bool> announcement-failed;
in property <bool> announcement-read-save-failed;
in property <bool> announcement-loaded;
in-out property <bool> messages-open: false;
in property <string> announcement-selected-id;
in property <string> announcement-selected-title;
in property <string> announcement-selected-body;
in property <string> announcement-selected-date;
in property <bool> announcement-selected-read;
callback open-announcement(string, string);
callback refresh-announcements;
callback announcement-page(int);
in property <bool> update-available: false;
in property <UpdateCheckStatus> update-check-status: idle;
private property <string> update-status-text:
@@ -410,6 +428,10 @@ export component MainWindow inherits Window {
if root.wayland-notice-open { focus-sink.focus(); }
}
changed messages-open => {
if root.messages-open { focus-sink.focus(); }
}
reset-remote-id => {
root.remote-id-input = "";
remote-id-editor.focus();
@@ -623,6 +645,10 @@ export component MainWindow inherits Window {
}
return accept;
}
if root.messages-open {
if event.text == Key.Escape { root.messages-open = false; }
return accept;
}
return reject;
}
}
@@ -651,11 +677,12 @@ export component MainWindow inherits Window {
icon-size: 14px;
hover-background: #e1e5eb;
pressed-background: #cbd1d9;
tooltip: root.announcement-unread > 0 ? MessageStrings.unread + " " + root.announcement-unread : "";
z: 8;
clicked => { root.menu-open = !root.menu-open; }
}
if root.update-available && !root.menu-open && root.update-icon-blink-on: Text {
x: menu-button.x + 16px;
x: menu-button.x + (root.announcement-unread > 0 ? -18px : 16px);
y: menu-button.y + 1px;
width: 16px;
height: 16px;
@@ -668,6 +695,33 @@ export component MainWindow inherits Window {
vertical-alignment: center;
z: 9;
}
if root.announcement-unread > 0: Rectangle {
x: menu-button.x + menu-button.width - self.width + 4px;
y: menu-button.y - 1px;
width: max(16px, unread-badge-label.preferred-width + 8px);
height: 16px;
border-radius: 8px;
border-width: 1px;
border-color: white;
background: #d92d20;
z: 9;
accessible-role: button;
accessible-label: MessageStrings.messages + " · " + MessageStrings.unread + " " + root.announcement-unread;
accessible-action-default => { root.menu-open = !root.menu-open; }
unread-badge-label := Text {
text: root.announcement-unread > 99 ? "99+" : "" + root.announcement-unread;
color: white;
font-size: 10px;
font-weight: 600;
horizontal-alignment: center;
vertical-alignment: center;
accessible-role: none;
}
TouchArea {
mouse-cursor: pointer;
clicked => { root.menu-open = !root.menu-open; }
}
}
// Local and remote desktop panels.
Rectangle {
@@ -1146,9 +1200,10 @@ export component MainWindow inherits Window {
if root.menu-open: Rectangle {
x: menu-button.x + menu-button.width - self.width;
y: menu-button.y + menu-button.height + 2px;
width: min(118px, max(menu-settings-label.preferred-width,
menu-about-label.preferred-width + (root.update-available ? 25px : 0px)) + 28px);
height: 64px;
width: max(menu-settings-label.preferred-width,
max(menu-messages-label.preferred-width,
menu-about-label.preferred-width + (root.update-available ? 25px : 0px))) + 28px;
height: 93px;
background: white;
border-width: 1px;
border-color: ImGuiLineStyle.border;
@@ -1181,6 +1236,31 @@ export component MainWindow inherits Window {
clicked => { root.menu-open = false; root.settings-open = true; }
}
}
menu-messages := Rectangle {
height: 27px;
background: messages-touch.pressed ? #d8dadd
: messages-touch.has-hover ? #f0f1f2 : white;
border-radius: 3px;
accessible-role: button;
accessible-label: menu-messages-label.text;
accessible-action-default => { messages-touch.clicked(); }
menu-messages-label := Text {
x: 9px;
height: parent.height;
text: MessageStrings.messages + (root.announcement-unread > 0 ? " (" + root.announcement-unread + ")" : "");
color: #2d3138;
vertical-alignment: center;
accessible-role: none;
}
messages-touch := TouchArea {
mouse-cursor: pointer;
clicked => {
root.menu-open = false;
root.messages-open = true;
root.refresh-announcements();
}
}
}
menu-about := Rectangle {
height: 27px;
background: about-touch.pressed ? #d8dadd
@@ -1226,6 +1306,38 @@ export component MainWindow inherits Window {
background: ImGuiLineStyle.separator;
z: 1;
}
Rectangle {
x: 4px;
y: 60.5px;
width: parent.width - 8px;
height: 1px;
background: ImGuiLineStyle.separator;
z: 1;
}
}
if root.messages-open: Rectangle {
background: #00000042;
z: 40;
TouchArea { clicked => { focus-sink.focus(); } }
MessagePanel {
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2;
width: 520px;
height: 330px;
items: root.announcements;
unread-count: root.announcement-unread; total: root.announcement-total; offset: root.announcement-offset;
loading: root.announcement-loading; failed: root.announcement-failed; loaded: root.announcement-loaded;
read-save-failed: root.announcement-read-save-failed;
connected: root.signal-connected;
selected-id: root.announcement-selected-id; selected-title: root.announcement-selected-title;
selected-body: root.announcement-selected-body; selected-date: root.announcement-selected-date;
selected-read: root.announcement-selected-read;
open-message(id, revision) => { root.open-announcement(id, revision); }
refresh => { root.refresh-announcements(); }
change-page(offset) => { root.announcement-page(offset); }
close => { root.messages-open = false; }
}
}
copy-timer := Timer {