Files
code-utils/tray_status.go
2026-08-14 17:54:06 +08:00

140 lines
3.4 KiB
Go
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.
package main
import (
"bytes"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"strings"
)
// trayStatus 汇总托盘展示所需的运行态。
type trayStatus struct {
loggedIn bool
online bool
syncing bool
pending int
unread int
todayDue int
lastErr string
username string
}
func (a *App) collectTrayStatus() trayStatus {
st := trayStatus{}
if a.store == nil {
return st
}
st.loggedIn = a.syncUserID() > 0
st.online = a.syncOnline.Load()
st.syncing = a.syncing.Load()
st.username = a.store.Meta("sync_username")
a.syncMu.Lock()
st.lastErr = a.syncLastErr
a.syncMu.Unlock()
_ = a.store.db.QueryRow(`SELECT (SELECT COUNT(*) FROM todos WHERE dirty=1)
+(SELECT COUNT(*) FROM tickets WHERE dirty=1)
+(SELECT COUNT(*) FROM notes WHERE dirty=1)`).Scan(&st.pending)
_ = a.store.db.QueryRow(`SELECT COUNT(*) FROM messages WHERE is_read=0`).Scan(&st.unread)
if badges, e := a.GetNavBadges(); e == nil {
st.todayDue = badges.TodayDue
}
return st
}
func (s trayStatus) tooltip(locale string) string {
base := "年糕崽崽项目管理PMS"
if locale == "en" {
base = "Niangaozai PMS"
}
parts := []string{base}
switch {
case s.syncing:
parts = append(parts, pick(locale, "同步中…", "Syncing…"))
case !s.loggedIn:
parts = append(parts, pick(locale, "未登录", "Signed out"))
case s.lastErr != "":
parts = append(parts, pick(locale, "同步异常", "Sync error"))
case s.online:
parts = append(parts, pick(locale, "在线", "Online"))
default:
parts = append(parts, pick(locale, "离线", "Offline"))
}
if s.pending > 0 {
parts = append(parts, fmt.Sprintf(pick(locale, "待推送 %d", "Pending %d"), s.pending))
}
if s.unread > 0 {
parts = append(parts, fmt.Sprintf(pick(locale, "未读 %d", "Unread %d"), s.unread))
}
if s.todayDue > 0 {
parts = append(parts, fmt.Sprintf(pick(locale, "今日到期 %d", "Due today %d"), s.todayDue))
}
return strings.Join(parts, " · ")
}
func (s trayStatus) badgeColor() color.RGBA {
switch {
case s.syncing:
return color.RGBA{R: 79, G: 157, B: 245, A: 255} // blue
case s.lastErr != "":
return color.RGBA{R: 240, G: 94, B: 104, A: 255} // red
case !s.loggedIn:
return color.RGBA{R: 146, G: 155, B: 172, A: 255} // muted
case !s.online:
return color.RGBA{R: 231, G: 189, B: 53, A: 255} // yellow
case s.pending > 0 || s.unread > 0:
return color.RGBA{R: 231, G: 189, B: 53, A: 255} // yellow attention
default:
return color.RGBA{R: 67, G: 201, B: 150, A: 255} // green
}
}
func pick(locale, zh, en string) string {
if locale == "en" {
return en
}
return zh
}
// trayIconWithBadge 在应用图标右下角叠一个状态色点,供托盘 SetIcon 使用。
func trayIconWithBadge(src []byte, badge color.RGBA) []byte {
img, e := png.Decode(bytes.NewReader(src))
if e != nil {
return src
}
b := img.Bounds()
out := image.NewRGBA(b)
draw.Draw(out, b, img, b.Min, draw.Src)
size := b.Dx()
if size < 8 {
return src
}
r := size / 5
if r < 3 {
r = 3
}
cx := b.Max.X - r - size/12
cy := b.Max.Y - r - size/12
for y := cy - r; y <= cy+r; y++ {
for x := cx - r; x <= cx+r; x++ {
dx, dy := x-cx, y-cy
if dx*dx+dy*dy > r*r {
continue
}
// 外圈深色描边提升对比度。
if dx*dx+dy*dy > (r-1)*(r-1) {
out.Set(x, y, color.RGBA{0, 0, 0, 180})
} else {
out.Set(x, y, badge)
}
}
}
var buf bytes.Buffer
if e := png.Encode(&buf, out); e != nil {
return src
}
return buf.Bytes()
}