Files
Stockbar/StockMonitor/Models/AppSettings.swift
T
vgearen 28eb6bd98c feat(korea): add Market.krStock + KRW currency + chart x-axis
Adding Market.krStock cascades into several exhaustive switches that must all
update together: ExchangeRates.convert, DisplayCurrency, StockChartView ranges
and labels. Bundles tasks 2/3/4/5/14 into one build-passing landing.

- Market enum: add krStock case + kr_ prefix routing in Market.from
- ExchangeRates: add usdToKrw (fallback 1380), krwToCny derived, KRW path in convert
- DisplayCurrency: add .krw (₩), display name 韩元
- CurrencyService.fetchRates: include fx_susdkrw in Sina FX batch URL
- StockChartView: KR fullDayRange 0...389 (KST 09:00-15:30, 390 min); KR axis labels
  09:00/10:30/12:00/13:30/15:30

Tests: +5 Stock cases for KR, +8 CurrencyKRWTests covering all KR↔CNY/USD/HKD/KRW
conversion paths and fallback constant.
2026-05-14 10:01:57 +08:00

98 lines
3.4 KiB
Swift

import Foundation
enum DisplayCurrency: String, Codable, CaseIterable {
case cny = "CNY"
case hkd = "HKD"
case usd = "USD"
case krw = "KRW"
var symbol: String {
switch self {
case .cny: return "¥"
case .hkd: return "HK$"
case .usd: return "$"
case .krw: return "₩"
}
}
var displayName: String {
switch self {
case .cny: return "人民币 ¥"
case .hkd: return "港币 HK$"
case .usd: return "美元 $"
case .krw: return "韩元 ₩"
}
}
}
enum SortRule: String, Codable, CaseIterable {
case changeDesc = "changeDesc"
case changeAsc = "changeAsc"
var displayName: String {
switch self {
case .changeDesc: return "涨跌幅降序"
case .changeAsc: return "涨跌幅升序"
}
}
}
enum USPriceMode: String, Codable, CaseIterable {
case sessionPrice = "session"
case regularPrice = "regular"
var displayName: String {
switch self {
case .sessionPrice: return "当前价(时段价格)"
case .regularPrice: return "盘中价"
}
}
}
enum ColorTheme: String, Codable, CaseIterable {
case chinese = "chinese" // 红涨绿跌
case western = "western" // 绿涨红跌
var displayName: String {
switch self {
case .chinese: return "红涨绿跌"
case .western: return "绿涨红跌"
}
}
}
struct AppSettings {
var statusBarStockId: String = ""
var refreshInterval: Int = 5
var colorScheme: ColorTheme = .chinese
var displayCurrency: DisplayCurrency = .cny
var sortRule: SortRule = .changeDesc
var usPriceMode: USPriceMode = .sessionPrice
var groupHoldings: Bool = false
var activeWatchlistId: String? = nil // nil = 真实持仓
static let validRefreshIntervals = [3, 5, 10, 30]
var upColorName: String { colorScheme == .chinese ? "upRed" : "upGreen" }
var downColorName: String { colorScheme == .chinese ? "downGreen" : "downRed" }
}
// MARK: - Codable(容错:缺失字段使用默认值)
extension AppSettings: Codable {
enum CodingKeys: String, CodingKey {
case statusBarStockId, refreshInterval, colorScheme, displayCurrency, sortRule, usPriceMode, groupHoldings, activeWatchlistId
}
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
statusBarStockId = (try? c.decodeIfPresent(String.self, forKey: .statusBarStockId)) ?? ""
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
sortRule = (try? c.decodeIfPresent(SortRule.self, forKey: .sortRule)) ?? .changeDesc
usPriceMode = (try? c.decodeIfPresent(USPriceMode.self, forKey: .usPriceMode)) ?? .sessionPrice
groupHoldings = (try? c.decodeIfPresent(Bool.self, forKey: .groupHoldings)) ?? false
activeWatchlistId = try? c.decodeIfPresent(String.self, forKey: .activeWatchlistId)
}
}