From c92b03d6c411fd59cffbaab3cd3fa71838df8358 Mon Sep 17 00:00:00 2001 From: ZhangLei Date: Tue, 4 Nov 2025 00:06:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=B8=81=E7=A7=8D=E5=9B=BE=E6=A0=87=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=99=A8=EF=BC=8C=E6=94=AF=E6=8C=81=E5=9F=BA=E4=BA=8E=E9=A6=96?= =?UTF-8?q?=E5=AD=97=E6=AF=8D=E7=94=9F=E6=88=90=E5=9B=BE=E6=A0=87=E5=B9=B6?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=9B=B8=E5=85=B3=E8=A7=86=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Bitcoin-Monitoring/Core/MenuBarManager.swift | 30 ++- .../Models/CustomCryptoSymbol.swift | 10 +- .../Utils/CryptoIconGenerator.swift | 185 ++++++++++++++++++ .../Views/PreferencesWindowView.swift | 14 +- 4 files changed, 226 insertions(+), 13 deletions(-) create mode 100644 Bitcoin-Monitoring/Utils/CryptoIconGenerator.swift diff --git a/Bitcoin-Monitoring/Core/MenuBarManager.swift b/Bitcoin-Monitoring/Core/MenuBarManager.swift index 3148d5a..be06b6a 100644 --- a/Bitcoin-Monitoring/Core/MenuBarManager.swift +++ b/Bitcoin-Monitoring/Core/MenuBarManager.swift @@ -146,9 +146,11 @@ class MenuBarManager: NSObject, ObservableObject { let symbolImage: NSImage? if self.appSettings.isUsingCustomSymbol() { + // 自定义币种:使用自定义图标 symbolImage = self.customSymbolImage() } else { - symbolImage = self.symbolImage(for: self.priceManager.selectedSymbol) + // 默认币种:直接从AppSettings获取当前选中的币种,避免依赖可能尚未更新的priceManager + symbolImage = self.symbolImage(for: self.appSettings.selectedSymbol) } symbolImage?.size = NSSize(width: 16, height: 16) @@ -178,11 +180,22 @@ class MenuBarManager: NSObject, ObservableObject { return NSImage(systemSymbolName: "bitcoinsign.circle.fill", accessibilityDescription: "Crypto") } - // 获取自定义币种的图标(统一使用BTC图标) + // 获取自定义币种的图标(基于首字母生成) private func customSymbolImage() -> NSImage? { + if appSettings.isUsingCustomSymbol(), + let index = appSettings.selectedCustomSymbolIndex, + index >= 0 && index < appSettings.customCryptoSymbols.count { + let customSymbol = appSettings.customCryptoSymbols[index] + return customSymbol.customIcon() + } return NSImage(systemSymbolName: "bitcoinsign.circle.fill", accessibilityDescription: "自定义币种") } + // 获取指定自定义币种的图标 + private func customSymbolImage(for customSymbol: CustomCryptoSymbol) -> NSImage? { + return customSymbol.customIcon() + } + // 格式化价格为千分位分隔形式 private func formatPriceWithCommas(_ price: Double) -> String { let formatter = NumberFormatter() @@ -251,7 +264,7 @@ class MenuBarManager: NSObject, ObservableObject { let placeholderTitle = isCurrent ? "✓ \(customSymbol.displayName) (自定义): 加载中..." : " \(customSymbol.displayName) (自定义): 加载中..." let item = NSMenuItem(title: placeholderTitle, action: #selector(self.selectOrCopySymbol(_:)), keyEquivalent: "") item.target = self - if let icon = customSymbolImage() { + if let icon = customSymbolImage(for: customSymbol) { icon.size = NSSize(width: 16, height: 16) item.image = icon } @@ -508,18 +521,19 @@ class MenuBarManager: NSObject, ObservableObject { print("✅ 已切换到自定义币种: \(customSymbol.displayName)") } - // 立即更新价格管理器 + // 立即更新价格管理器和UI self.priceManager.updateCryptoSymbolSettings() - self.updateMenuBarTitle(price: self.priceManager.currentPrice) + // 使用0.0价格强制更新显示状态,确保图标和文字都正确更新 + self.updateMenuBarTitle(price: 0.0) } else if let symbol = data["symbol"] as? CryptoSymbol { // 选择默认币种 appSettings.saveSelectedSymbol(symbol) - print("✅ 已切换到默认币种: \(symbol.displayName)") - // 立即更新价格管理器 + // 立即更新价格管理器和UI self.priceManager.updateCryptoSymbolSettings() - self.updateMenuBarTitle(price: self.priceManager.currentPrice) + // 使用0.0价格强制更新显示状态,确保图标和文字都正确更新 + self.updateMenuBarTitle(price: 0.0) } } } diff --git a/Bitcoin-Monitoring/Models/CustomCryptoSymbol.swift b/Bitcoin-Monitoring/Models/CustomCryptoSymbol.swift index 498c141..eb2ab41 100644 --- a/Bitcoin-Monitoring/Models/CustomCryptoSymbol.swift +++ b/Bitcoin-Monitoring/Models/CustomCryptoSymbol.swift @@ -6,6 +6,7 @@ // import Foundation +import AppKit /// 自定义加密货币数据模型 /// 支持用户定义的3-5字符币种符号,使用统一的BTC图标 @@ -121,11 +122,18 @@ extension CustomCryptoSymbol { return "\(symbol)/USDT" } - /// 对应的SF Symbols图标名称(统一使用BTC图标) + /// 对应的图标名称(基于首字母生成的自定义图标) var systemImageName: String { + // 保留系统图标名称作为后备方案 return "bitcoinsign.circle.fill" } + /// 获取基于首字母的自定义图标 + /// - Returns: 自定义生成的NSImage图标 + func customIcon() -> NSImage { + return CryptoIconGenerator.generateSystemIcon(for: symbol) + } + /// 菜单标题(带勾选标记和自定义标识) /// - Parameter isCurrent: 是否为当前选中币种 /// - Returns: 菜单展示文本 diff --git a/Bitcoin-Monitoring/Utils/CryptoIconGenerator.swift b/Bitcoin-Monitoring/Utils/CryptoIconGenerator.swift new file mode 100644 index 0000000..23104fa --- /dev/null +++ b/Bitcoin-Monitoring/Utils/CryptoIconGenerator.swift @@ -0,0 +1,185 @@ +// +// CryptoIconGenerator.swift +// Bitcoin Monitoring +// +// Created by Mark on 2025/11/03. +// + +import SwiftUI +import AppKit + +/** + * 加密货币图标生成器 + * 为自定义币种生成基于首字母的彩色图标 + */ +class CryptoIconGenerator { + + /// 图标缓存,避免重复生成相同币种的图标 + private static var iconCache: [String: NSImage] = [:] + + /// 预定义的颜色数组,用于生成不同币种的背景色 + private static let backgroundColors: [NSColor] = [ + NSColor(red: 0.2, green: 0.4, blue: 0.8, alpha: 1.0), // 蓝色 + NSColor(red: 0.8, green: 0.3, blue: 0.3, alpha: 1.0), // 红色 + NSColor(red: 0.3, green: 0.7, blue: 0.3, alpha: 1.0), // 绿色 + NSColor(red: 0.9, green: 0.6, blue: 0.2, alpha: 1.0), // 橙色 + NSColor(red: 0.7, green: 0.3, blue: 0.7, alpha: 1.0), // 紫色 + NSColor(red: 0.2, green: 0.7, blue: 0.8, alpha: 1.0), // 青色 + NSColor(red: 0.9, green: 0.4, blue: 0.6, alpha: 1.0), // 粉色 + NSColor(red: 0.6, green: 0.6, blue: 0.2, alpha: 1.0), // 黄色 + NSColor(red: 0.4, green: 0.8, blue: 0.6, alpha: 1.0), // 薄荷绿 + NSColor(red: 0.7, green: 0.5, blue: 0.3, alpha: 1.0), // 棕色 + ] + + /** + * 为自定义币种生成基于首字母的图标 + * - Parameter symbol: 币种符号(如 BTC、ETH、ADA) + * - Returns: 生成的NSImage图标 + */ + static func generateIcon(for symbol: String) -> NSImage { + // 检查缓存 + if let cachedIcon = iconCache[symbol] { + return cachedIcon + } + + // 生成新图标 + let icon = createLetterIcon(symbol: symbol) + + // 缓存图标 + iconCache[symbol] = icon + + return icon + } + + /** + * 创建基于首字母的图标 + * - Parameter symbol: 币种符号 + * - Returns: 生成的NSImage图标 + */ + private static func createLetterIcon(symbol: String) -> NSImage { + let size = NSSize(width: 64, height: 64) // 使用较大的尺寸生成,保证清晰度 + + let image = NSImage(size: size) + image.lockFocus() + + // 创建圆形背景 + let rect = NSRect(origin: .zero, size: size) + let circlePath = NSBezierPath(ovalIn: rect) + + // 选择背景色 + let backgroundColor = selectBackgroundColor(for: symbol) + backgroundColor.setFill() + circlePath.fill() + + // 绘制首字母 + let firstLetter = String(symbol.prefix(1)).uppercased() + let attributedString = NSAttributedString( + string: firstLetter, + attributes: [ + .font: NSFont.boldSystemFont(ofSize: 28), + .foregroundColor: NSColor.white + ] + ) + + // 计算文字位置,使其居中 + let textSize = attributedString.size() + let textRect = NSRect( + x: (size.width - textSize.width) / 2, + y: (size.height - textSize.height) / 2, + width: textSize.width, + height: textSize.height + ) + + attributedString.draw(in: textRect) + + image.unlockFocus() + + // 设置图像属性,确保正确显示 + image.isTemplate = false + + return image + } + + /** + * 根据币种符号选择背景色 + * 使用哈希算法确保相同币种总是得到相同的颜色 + * - Parameter symbol: 币种符号 + * - Returns: 背景色 + */ + private static func selectBackgroundColor(for symbol: String) -> NSColor { + // 使用简单的哈希算法 + var hash = 0 + for char in symbol { + hash = hash * 31 + Int(char.unicodeScalars.first!.value) + } + + // 取绝对值并使用颜色数组长度取模 + let colorIndex = abs(hash) % backgroundColors.count + return backgroundColors[colorIndex] + } + + /** + * 生成系统符号样式的图标(兼容SF Symbols) + * - Parameter symbol: 币种符号 + * - Returns: SF Symbols兼容的NSImage + */ + static func generateSystemIcon(for symbol: String) -> NSImage { + let icon = generateIcon(for: symbol) + + // 调整大小为标准SF Symbols尺寸 + let standardSize = NSSize(width: 16, height: 16) + let resizedIcon = resizeImage(icon, to: standardSize) + + return resizedIcon + } + + /** + * 调整图像尺寸 + * - Parameters: + * - image: 原始图像 + * - size: 目标尺寸 + * - Returns: 调整尺寸后的图像 + */ + private static func resizeImage(_ image: NSImage, to size: NSSize) -> NSImage { + let resizedImage = NSImage(size: size) + resizedImage.lockFocus() + + let drawingRect = NSRect(origin: .zero, size: size) + image.draw(in: drawingRect) + + resizedImage.unlockFocus() + resizedImage.isTemplate = false + + return resizedImage + } + + /** + * 清空图标缓存(用于测试或重置) + */ + static func clearCache() { + iconCache.removeAll() + } + + /** + * 获取缓存中的图标数量 + * - Returns: 缓存的图标数量 + */ + static func cacheCount() -> Int { + return iconCache.count + } +} + +// MARK: - SwiftUI 兼容扩展 + +extension CryptoIconGenerator { + + /** + * 为SwiftUI生成Image + * - Parameter symbol: 币种符号 + * - Returns: SwiftUI Image + */ + static func generateSwiftUIImage(for symbol: String) -> Image { + let nsImage = generateSystemIcon(for: symbol) + return Image(nsImage: nsImage) + } +} \ No newline at end of file diff --git a/Bitcoin-Monitoring/Views/PreferencesWindowView.swift b/Bitcoin-Monitoring/Views/PreferencesWindowView.swift index 2aecc22..5b7c8cb 100644 --- a/Bitcoin-Monitoring/Views/PreferencesWindowView.swift +++ b/Bitcoin-Monitoring/Views/PreferencesWindowView.swift @@ -449,10 +449,16 @@ struct PreferencesWindowView: View { .font(.system(size: 14)) .foregroundColor(isSelected ? .blue : .secondary) - // 币种图标 - Image(systemName: customSymbol.systemImageName) - .foregroundColor(.orange) - .font(.system(size: 16)) + // 币种图标(使用基于首字母的自定义图标) + Group { + let nsImage = customSymbol.customIcon() + Image(nsImage: nsImage) + .resizable() + .aspectRatio(contentMode: .fit) + } + .foregroundColor(.orange) + .font(.system(size: 16)) + .frame(width: 16, height: 16) // 币种信息 VStack(alignment: .leading, spacing: 2) {