v1.1.2: Status bar PnL display options and login item fix

- Add setting to choose status bar PnL: none / daily / total / both
- Fix launch-at-login: remove unconditional register() on every launch
- Add error logging for SMAppService register/unregister

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
vgearen
2026-03-19 09:27:08 +08:00
co-authored by Claude Opus 4.6
parent 88e705fece
commit af7a45e027
5 changed files with 73 additions and 20 deletions
+22 -4
View File
@@ -22,6 +22,22 @@ enum DisplayCurrency: String, Codable, CaseIterable {
}
}
enum StatusBarPnL: String, Codable, CaseIterable {
case none = "none"
case daily = "daily"
case total = "total"
case both = "both"
var displayName: String {
switch self {
case .none: return "不显示"
case .daily: return "日盈亏"
case .total: return "总盈亏"
case .both: return "日盈亏 + 总盈亏"
}
}
}
enum ColorTheme: String, Codable, CaseIterable {
case chinese = "chinese" // 红涨绿跌
case western = "western" // 绿涨红跌
@@ -35,10 +51,11 @@ enum ColorTheme: String, Codable, CaseIterable {
}
struct AppSettings {
var statusBarStockId: String = ""
var refreshInterval: Int = 5
var colorScheme: ColorTheme = .chinese
var statusBarStockId: String = ""
var refreshInterval: Int = 5
var colorScheme: ColorTheme = .chinese
var displayCurrency: DisplayCurrency = .cny
var statusBarPnL: StatusBarPnL = .total
static let validRefreshIntervals = [3, 5, 10, 30]
@@ -49,7 +66,7 @@ struct AppSettings {
// MARK: - Codable(容错:缺失字段使用默认值)
extension AppSettings: Codable {
enum CodingKeys: String, CodingKey {
case statusBarStockId, refreshInterval, colorScheme, displayCurrency
case statusBarStockId, refreshInterval, colorScheme, displayCurrency, statusBarPnL
}
init(from decoder: Decoder) throws {
@@ -58,5 +75,6 @@ extension AppSettings: Codable {
refreshInterval = (try? c.decodeIfPresent(Int.self, forKey: .refreshInterval)) ?? 5
colorScheme = (try? c.decodeIfPresent(ColorTheme.self, forKey: .colorScheme)) ?? .chinese
displayCurrency = (try? c.decodeIfPresent(DisplayCurrency.self, forKey: .displayCurrency)) ?? .cny
statusBarPnL = (try? c.decodeIfPresent(StatusBarPnL.self, forKey: .statusBarPnL)) ?? .total
}
}
+2 -1
View File
@@ -7,7 +7,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
appLogger.info("applicationDidFinishLaunching")
logToFile("applicationDidFinishLaunching — pid=\(ProcessInfo.processInfo.processIdentifier)")
try? SMAppService.mainApp.register()
let status = SMAppService.mainApp.status
logToFile("SMAppService.mainApp.status = \(status.rawValue)")
}
func applicationWillTerminate(_ notification: Notification) {
+23 -7
View File
@@ -18,16 +18,11 @@ struct MenuBarLabel: View {
Text(quote.formattedPercent)
.font(.system(size: 11))
.foregroundColor(appState.quoteColor(for: quote))
if appState.hasPnLData {
if appState.hasPnLData, appState.config.statusBarPnL != .none {
Text("|")
.font(.system(size: 11))
.foregroundColor(.secondary)
let pnl = appState.totalPnL
Text(pnl >= 0
? "+\(String(format: "%.0f", pnl))"
: "\(String(format: "%.0f", pnl))")
.font(.system(size: 11))
.foregroundColor(appState.pnlColor(pnl))
pnlLabels
}
if appState.hasError {
Text("⚠").font(.system(size: 11)).foregroundColor(.yellow)
@@ -35,4 +30,25 @@ struct MenuBarLabel: View {
}
}
}
@ViewBuilder
private var pnlLabels: some View {
let mode = appState.config.statusBarPnL
if mode == .daily || mode == .both {
let daily = appState.totalDailyPnL
Text(daily >= 0
? "日+\(String(format: "%.0f", daily))"
: "日\(String(format: "%.0f", daily))")
.font(.system(size: 11))
.foregroundColor(appState.pnlColor(daily))
}
if mode == .total || mode == .both {
let pnl = appState.totalPnL
Text(pnl >= 0
? "浮+\(String(format: "%.0f", pnl))"
: "浮\(String(format: "%.0f", pnl))")
.font(.system(size: 11))
.foregroundColor(appState.pnlColor(pnl))
}
}
}
+22 -4
View File
@@ -51,6 +51,18 @@ struct SettingsView: View {
}.pickerStyle(.menu)
}
// 状态栏盈亏显示
section("状态栏盈亏") {
Picker("", selection: Binding(
get: { appState.config.statusBarPnL },
set: { appState.config.statusBarPnL = $0 }
)) {
ForEach(StatusBarPnL.allCases, id: \.rawValue) { opt in
Text(opt.displayName).tag(opt)
}
}.pickerStyle(.menu)
}
// 刷新间隔
section("刷新间隔") {
Picker("", selection: Binding(
@@ -88,11 +100,17 @@ struct SettingsView: View {
section("开机启动") {
Toggle("登录后自动启动", isOn: $launchAtLogin)
.onChange(of: launchAtLogin) { enabled in
if enabled {
try? SMAppService.mainApp.register()
} else {
try? SMAppService.mainApp.unregister()
do {
if enabled {
try SMAppService.mainApp.register()
} else {
try SMAppService.mainApp.unregister()
}
} catch {
logToFile("SMAppService \(enabled ? "register" : "unregister") failed: \(error)")
}
// 刷新为实际状态,防止 UI 与系统不一致
launchAtLogin = (SMAppService.mainApp.status == .enabled)
}
}