81 lines
3.5 KiB
Swift
81 lines
3.5 KiB
Swift
import Foundation
|
|
|
|
struct AppConfig: Codable, Equatable {
|
|
var appToken: String = ""
|
|
var tableId: String = ""
|
|
var assigneeName: String = ""
|
|
var fieldMappings: FieldMappings = FieldMappings()
|
|
var pollIntervalSeconds: Int = 300
|
|
/// Daily refresh times in "HH:mm" format, e.g. ["08:00", "14:00"].
|
|
/// Only used when pollIntervalSeconds == -1.
|
|
var dailyRefreshTimes: [String] = []
|
|
var showFloatingWidget: Bool = false
|
|
var launchAtLogin: Bool = true
|
|
var feishuBaseDomain: String = "xorbitlab.feishu.cn"
|
|
/// When false (the default), Bugger waits for the next scheduled poll
|
|
/// instead of fetching immediately on launch. Avoids a crash-on-start
|
|
/// loop if the table fetch is failing.
|
|
var refreshOnStart: Bool = false
|
|
|
|
struct FieldMappings: Codable, Equatable {
|
|
var titleField: String = "Title"
|
|
var priorityField: String = "Priority"
|
|
var statusField: String = "Status"
|
|
var assigneeField: String = "Assignee"
|
|
var reporterField: String = "Reporter"
|
|
var customerField: String = "CustomerName"
|
|
var createdAtField: String = "Created At"
|
|
var updatedAtField: String = "Updated At"
|
|
|
|
/// Map Feishu display values to standard BugStatus cases.
|
|
/// Keys are the Feishu select-option text; values are the standard status.
|
|
/// Unknown values default to .unknown (shown as active).
|
|
var statusMappings: [String: BugStatus] = [
|
|
"Bug Triage": .open,
|
|
"Open": .open,
|
|
"New": .open,
|
|
"待处理": .open,
|
|
"开发进行中": .inProgress,
|
|
"In Progress": .inProgress,
|
|
"验收中": .inReview,
|
|
"In Review": .inReview,
|
|
"验收完毕": .resolved,
|
|
"Resolved": .resolved,
|
|
"Fixed": .resolved,
|
|
"取消": .closed,
|
|
"Closed": .closed,
|
|
"Won't fix": .closed,
|
|
"转需求": .closed,
|
|
"验收不通过": .closed,
|
|
]
|
|
}
|
|
|
|
var isConfigured: Bool {
|
|
!appToken.isEmpty && !tableId.isEmpty
|
|
}
|
|
|
|
// Explicit keys + tolerant decoding so a config persisted before a new
|
|
// field was added (missing key) doesn't fail to decode and wipe settings.
|
|
enum CodingKeys: String, CodingKey {
|
|
case appToken, tableId, assigneeName, fieldMappings
|
|
case pollIntervalSeconds, dailyRefreshTimes
|
|
case showFloatingWidget, launchAtLogin, feishuBaseDomain, refreshOnStart
|
|
}
|
|
|
|
init() {}
|
|
|
|
init(from decoder: Decoder) throws {
|
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
|
appToken = try c.decodeIfPresent(String.self, forKey: .appToken) ?? ""
|
|
tableId = try c.decodeIfPresent(String.self, forKey: .tableId) ?? ""
|
|
assigneeName = try c.decodeIfPresent(String.self, forKey: .assigneeName) ?? ""
|
|
fieldMappings = try c.decodeIfPresent(FieldMappings.self, forKey: .fieldMappings) ?? FieldMappings()
|
|
pollIntervalSeconds = try c.decodeIfPresent(Int.self, forKey: .pollIntervalSeconds) ?? 300
|
|
dailyRefreshTimes = try c.decodeIfPresent([String].self, forKey: .dailyRefreshTimes) ?? []
|
|
showFloatingWidget = try c.decodeIfPresent(Bool.self, forKey: .showFloatingWidget) ?? false
|
|
launchAtLogin = try c.decodeIfPresent(Bool.self, forKey: .launchAtLogin) ?? true
|
|
feishuBaseDomain = try c.decodeIfPresent(String.self, forKey: .feishuBaseDomain) ?? "xorbitlab.feishu.cn"
|
|
refreshOnStart = try c.decodeIfPresent(Bool.self, forKey: .refreshOnStart) ?? false
|
|
}
|
|
}
|