mirror of
https://github.com/VGEAREN/Stockbar.git
synced 2026-10-06 14:33:11 +08:00
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:
co-authored by
Claude Opus 4.6
parent
88e705fece
commit
af7a45e027
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user