diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 788c62c..f05521d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -189,8 +189,9 @@ Selected-day almanac is always enabled; legacy `showAlmanac` preferences are ign Details still calculate on demand, while widgets reuse the bounded eight-day summary. The snapshot keeps `showsAlmanac = true` to invalidate old disabled-almanac caches. Calendar enablement, optional features and week start persist through `AppSettings` and the -optional fields in `SettingsDocument`. Week start initially uses `Calendar.current.firstWeekday`, -then persists independently of later system changes; all seven weekdays are supported by the +optional fields in `SettingsDocument`. Fresh installs take region defaults and `Calendar.current.firstWeekday`; existing installs +(an existing `calendarEnabled` key) without saved calendar values keep the legacy defaults and +Monday. Both then persist independently of later system changes; all seven weekdays are supported by the settings picker, backups, month grids and widgets. Menu-bar lunar modes remain available in every app language and use localized lunar dates when lunar display is enabled. Disabling lunar display hides its menu-bar presets and text-style controls, renders the saved lunar diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index f1d7857..960f01f 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -55,7 +55,7 @@ GitHub Actions 只在分支 push 且 `server/**` 发生变化时构建并发布 有 Developer ID Application 证书时,Taskfile 会自动使用钥匙串中第一个证书的唯一指纹;否则由构建脚本逐层补齐 ad-hoc 签名与 entitlement。临时签名没有团队身份,不能加载启用团队验证的 Sparkle 动态库,因此仅此本地/CI 路径关闭 Hardened Runtime;正式 Developer ID 构建仍强制启用,且临时签名构建不能在线安装或操作特权辅助工具。 桌面小组件与主应用通过 `group.work.12306.xstats` App Group 共享只读展示摘要。 菜单栏日历在所有应用语言下均提供“日期与星期”“日期与农历”“农历”“自定义”显示方式;日期与农历按应用语言呈现,切换语言保留用户选择。关闭农历时,字号与对比度子选项以及菜单栏中的两种农历方式隐藏;已保存的农历方式暂以“日期与星期”呈现,字号和对比度样式不生效,重新开启后恢复原选择。子选项位于农历开关下方。自定义可自由组合 y/M/d 字段与分隔符,年份可省略,输入时显示预览并提示无效格式,不影响月历与单日详情的星期显示;支持农历字号与对比度、可选悬停展开,按分钟刷新,休眠或关闭功能后停止。日程和到期提醒默认关闭,通过明确的授权按钮请求 EventKit 权限,只在日历面板可见时查询当前 42 格日期范围;只读展示,不修改日程,也不向 Widget、设置备份或服务端传递内容。列表 ID 仅保存在本机,显示偏好参与设置备份。节假日覆盖年份来自内置 Tyme 数据并随应用更新;“假期倒计时与请假建议”仅在开启“中国大陆节假日与调休”后显示在该开关下方;关闭节假日时隐藏子选项并保留原选择。假期倒计时与最多 3 天的请假建议只基于已公布的中国调休与双休安排,跨到未知年份时停止推算。切换到未收录法定安排的年份时,不查询该年的法定节假日,不计算或沿用当前年份的假期概览与请假建议,仅提示缺少该年数据;返回有数据的年份后恢复展示。 -日历首次初始化按 macOS 地区设置决定默认值,不按设备销售地或应用语言判断:中国大陆(CN)默认启用农历、大陆节假日与调休、传统与公历节日、节气与时令及假期概览;其他地区(含台湾、香港、澳门)默认不启用这些地区相关的可选内容。各地区的月历和单日详情均固定显示星期,不提供独立开关;旧配置与备份中的星期开关被忽略,其他选择保留。藏历与回历各地区均默认关闭。“节气与时令”统一控制二十四节气、三伏、梅雨与数九;月历只显示节气当天、时令阶段起点及出梅,单日详情与单日小组件保留具体天数。旧节气或时令开关任一开启即恢复为开启,全部关闭才恢复为关闭;使用新的设置键使 Widget 旧摘要失效并重新生成。月历不再显示干支,也不提供对应开关;年月日干支只在日期详情中显示,旧偏好与备份中的干支选项被忽略,其他选项保留。用户可自行启用或关闭,首次默认值保存后不随语言、地区变化重置;已有本机偏好及旧备份保持原选择。每周起始日首次采用 macOS 系统日历设置并保存,支持星期日到星期六,手动选择及备份恢复优先;后续系统设置变化不覆盖已保存的值,主应用月历与小组件使用同一周起始日。大陆节假日数据不能用于推断其他地区的法定安排。单日及整月小组件遵循日历选项,未启用大陆节假日时“明天上班吗”提示“仅支持中国大陆节假日”;黄历在单日详情中固定显示,旧偏好与备份中的黄历开关被忽略;宜忌详情仍只为选中日期按需计算,小组件复用主应用预计算的八天摘要缓存。 +日历首次初始化按 macOS 地区设置决定默认值,不按设备销售地或应用语言判断:中国大陆(CN)默认启用农历、大陆节假日与调休、传统与公历节日、节气与时令及假期概览;其他地区(含台湾、香港、澳门)默认不启用这些地区相关的可选内容。各地区的月历和单日详情均固定显示星期,不提供独立开关;旧配置与备份中的星期开关被忽略,其他选择保留。藏历与回历各地区均默认关闭。“节气与时令”统一控制二十四节气、三伏、梅雨与数九;月历只显示节气当天、时令阶段起点及出梅,单日详情与单日小组件保留具体天数。旧节气或时令开关任一开启即恢复为开启,全部关闭才恢复为关闭;使用新的设置键使 Widget 旧摘要失效并重新生成。月历不再显示干支,也不提供对应开关;年月日干支只在日期详情中显示,旧偏好与备份中的干支选项被忽略,其他选项保留。用户可自行启用或关闭,首次默认值保存后不随语言、地区变化重置;已有本机偏好及旧备份保持原选择;曾开关过日历功能的已有安装若未保存日历选项,沿用旧版默认(与中国大陆默认相同,每周从星期一开始),不按地区重新初始化。每周起始日首次采用 macOS 系统日历设置并保存,支持星期日到星期六,手动选择及备份恢复优先;后续系统设置变化不覆盖已保存的值,主应用月历与小组件使用同一周起始日。大陆节假日数据不能用于推断其他地区的法定安排。单日及整月小组件遵循日历选项,未启用大陆节假日时“明天上班吗”提示“仅支持中国大陆节假日”,已启用但次日所在年份安排尚未公布时提示“暂无法判断”;黄历在单日详情中固定显示,旧偏好与备份中的黄历开关被忽略;宜忌详情仍只为选中日期按需计算,小组件复用主应用预计算的八天摘要缓存。 月历使用固定六行布局,日期 ID 在生成日期时计算一次。面板打开时测量初始高度,后续翻月、详情与日程内容变化在内部滚动,不从内容几何回调反向调整窗口,避免窗口高度与懒布局估算形成反馈循环。 月历计算仅缓存最近一个月份(包含周起始日和时区),供实际面板、高度测量和重复打开共用;翻月替换缓存,避免浏览历史导致内存累积。日程日期标记在工作 actor 内随查询结果生成,界面不再逐格扫描日程列表;连续 EventKit 通知按 300 毫秒窗口合并,关闭面板时取消待执行刷新。 用于正式签名的 Apple Developer 团队须为主应用和 Widget 扩展启用同一个 App Group; diff --git a/Packages/XStatsKit/Sources/Localization/CalendarTranslations.swift b/Packages/XStatsKit/Sources/Localization/CalendarTranslations.swift index 7845d1a..1e10c87 100644 --- a/Packages/XStatsKit/Sources/Localization/CalendarTranslations.swift +++ b/Packages/XStatsKit/Sources/Localization/CalendarTranslations.swift @@ -51,6 +51,7 @@ extension Translations { 不上班 Day off 暂无安排 No schedule data 仅支持中国大陆节假日 Only Mainland China holidays are supported +暂无法判断 Cannot determine yet 工作日 Weekday 周末 Weekend 节假日 Public holiday diff --git a/Packages/XStatsKit/Sources/Localization/Resources/ar.tsv b/Packages/XStatsKit/Sources/Localization/Resources/ar.tsv index 9814a69..f9cf4c8 100644 --- a/Packages/XStatsKit/Sources/Localization/Resources/ar.tsv +++ b/Packages/XStatsKit/Sources/Localization/Resources/ar.tsv @@ -1435,6 +1435,7 @@ Sub2API 账号平台与所选来源不匹配 منصة حساب Sub2API لا تط 不上班 يوم عطلة 暂无安排 لا توجد بيانات 仅支持中国大陆节假日 يدعم العطلات الرسمية في البر الرئيسي للصين فقط +暂无法判断 لا يمكن التحديد حاليًا 工作日 يوم عمل 周末 عطلة نهاية الأسبوع 节假日 عطلة رسمية diff --git a/Packages/XStatsKit/Sources/Localization/Resources/de.tsv b/Packages/XStatsKit/Sources/Localization/Resources/de.tsv index 84ff99e..57e83f9 100644 --- a/Packages/XStatsKit/Sources/Localization/Resources/de.tsv +++ b/Packages/XStatsKit/Sources/Localization/Resources/de.tsv @@ -1435,6 +1435,7 @@ Sub2API 账号平台与所选来源不匹配 Die Plattform des Sub2API-Kontos st 不上班 Frei 暂无安排 Keine Daten 仅支持中国大陆节假日 Nur Feiertage in Festlandchina werden unterstützt +暂无法判断 Derzeit nicht bestimmbar 工作日 Werktag 周末 Wochenende 节假日 Feiertag diff --git a/Packages/XStatsKit/Sources/Localization/Resources/es.tsv b/Packages/XStatsKit/Sources/Localization/Resources/es.tsv index ae41930..71afc65 100644 --- a/Packages/XStatsKit/Sources/Localization/Resources/es.tsv +++ b/Packages/XStatsKit/Sources/Localization/Resources/es.tsv @@ -1435,6 +1435,7 @@ Sub2API 账号平台与所选来源不匹配 La plataforma de la cuenta de Sub2A 不上班 Día libre 暂无安排 Sin datos 仅支持中国大陆节假日 Solo se admiten los festivos de China continental +暂无法判断 Aún no se puede determinar 工作日 Día laborable 周末 Fin de semana 节假日 Festivo diff --git a/Packages/XStatsKit/Sources/Localization/Resources/fr.tsv b/Packages/XStatsKit/Sources/Localization/Resources/fr.tsv index 6baa1be..ab33fdb 100644 --- a/Packages/XStatsKit/Sources/Localization/Resources/fr.tsv +++ b/Packages/XStatsKit/Sources/Localization/Resources/fr.tsv @@ -1435,6 +1435,7 @@ Sub2API 账号平台与所选来源不匹配 La plateforme du compte Sub2API ne 不上班 Repos 暂无安排 Aucune donnée 仅支持中国大陆节假日 Seuls les jours fériés de Chine continentale sont pris en charge +暂无法判断 Impossible à déterminer pour le moment 工作日 Jour ouvré 周末 Week-end 节假日 Jour férié diff --git a/Packages/XStatsKit/Sources/Localization/Resources/ja.tsv b/Packages/XStatsKit/Sources/Localization/Resources/ja.tsv index be48580..d409f9f 100644 --- a/Packages/XStatsKit/Sources/Localization/Resources/ja.tsv +++ b/Packages/XStatsKit/Sources/Localization/Resources/ja.tsv @@ -1435,6 +1435,7 @@ Sub2API 账号平台与所选来源不匹配 Sub2API アカウントのプラッ 不上班 休み 暂无安排 予定データなし 仅支持中国大陆节假日 中国本土の祝日のみ対応 +暂无法判断 判定できません 工作日 平日 周末 週末 节假日 祝日 diff --git a/Packages/XStatsKit/Sources/Localization/Resources/ko.tsv b/Packages/XStatsKit/Sources/Localization/Resources/ko.tsv index ec00c26..c26d25e 100644 --- a/Packages/XStatsKit/Sources/Localization/Resources/ko.tsv +++ b/Packages/XStatsKit/Sources/Localization/Resources/ko.tsv @@ -1435,6 +1435,7 @@ Sub2API 账号平台与所选来源不匹配 Sub2API 계정 플랫폼이 선택 不上班 쉬는 날 暂无安排 일정 데이터 없음 仅支持中国大陆节假日 중국 본토 공휴일만 지원 +暂无法判断 아직 판단할 수 없음 工作日 평일 周末 주말 节假日 공휴일 diff --git a/Packages/XStatsKit/Sources/Localization/Resources/zh-Hant.tsv b/Packages/XStatsKit/Sources/Localization/Resources/zh-Hant.tsv index 8bbf942..409b9ee 100644 --- a/Packages/XStatsKit/Sources/Localization/Resources/zh-Hant.tsv +++ b/Packages/XStatsKit/Sources/Localization/Resources/zh-Hant.tsv @@ -1436,6 +1436,7 @@ Sub2API 账号平台与所选来源不匹配 Sub2API 帳號平台與所選來源 不上班 不上班 暂无安排 暫無安排 仅支持中国大陆节假日 僅支援中國大陸節假日 +暂无法判断 暫時無法判斷 工作日 工作日 周末 週末 节假日 節假日 diff --git a/Packages/XStatsKit/Sources/WidgetData/WidgetSnapshot.swift b/Packages/XStatsKit/Sources/WidgetData/WidgetSnapshot.swift index 3f59368..41841a7 100644 --- a/Packages/XStatsKit/Sources/WidgetData/WidgetSnapshot.swift +++ b/Packages/XStatsKit/Sources/WidgetData/WidgetSnapshot.swift @@ -22,13 +22,15 @@ public struct WidgetSnapshot: Codable, Equatable, Sendable { case work, weekend, holiday, makeupWork, makeupDayOff, unknown /// 有放假数据但未细分法定假日与调休的年份。 case dayOff + /// 用户未启用中国大陆节假日;与数据未覆盖的 unknown 分开提示。 + case holidaysDisabled /// 数据未覆盖的年份不能按普通工作日推断,避免给出错误的二元答案。 public var needsWork: Bool? { switch self { case .work, .makeupWork: true case .weekend, .holiday, .makeupDayOff, .dayOff: false - case .unknown: nil + case .unknown, .holidaysDisabled: nil } } } diff --git a/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarAlmanac.swift b/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarAlmanac.swift index 6820781..de87c5c 100644 --- a/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarAlmanac.swift +++ b/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarAlmanac.swift @@ -48,7 +48,9 @@ extension CalendarEngine { static func widgetSummary(for day: CalendarDay, features: Set = Set(CalendarFeature.allCases)) -> WidgetSnapshot.CalendarSummary { let schedule: WidgetSnapshot.CalendarSummary.Schedule - if !features.contains(.holidays) || !hasHolidayData(year: day.year) { + if !features.contains(.holidays) { + schedule = .holidaysDisabled + } else if !hasHolidayData(year: day.year) { schedule = .unknown } else if let holiday = day.holiday { if holiday.isWork { diff --git a/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarEngine.swift b/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarEngine.swift index 6e6ee91..b7e276a 100644 --- a/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarEngine.swift +++ b/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarEngine.swift @@ -11,11 +11,12 @@ import WidgetData public enum CalendarFeature: String, CaseIterable, Identifiable, Sendable { case lunar, holidays, festivals, seasonalInfo, tibetan, hijri public var id: String { rawValue } - public static var defaults: Set { defaults(for: .current) } + /// 中国大陆默认值,也是按地区区分之前的旧版默认;已有安装缺少保存值时沿用,避免升级后日历内容消失。 + static let mainlandChinaDefaults: Set = [.lunar, .holidays, .festivals, .seasonalInfo] /// 默认值依据使用地区,不依据应用语言或设备销售地;其他地区可自行启用。 public static func defaults(for locale: Locale) -> Set { - locale.region?.identifier == "CN" ? [.lunar, .holidays, .festivals, .seasonalInfo] : [] + locale.region?.identifier == "CN" ? mainlandChinaDefaults : [] } var title: String { diff --git a/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarPopover.swift b/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarPopover.swift index 0400d60..a407688 100644 --- a/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarPopover.swift +++ b/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarPopover.swift @@ -47,7 +47,7 @@ struct CalendarPopover: View { private let sizing: CalendarPopoverSizing init(referenceDate: Date? = nil, showsDayDetails: Bool = false, - sizing: CalendarPopoverSizing = .standard, firstWeekday: Int = 2) { + sizing: CalendarPopoverSizing = .standard, firstWeekday: Int) { self.referenceDate = referenceDate self.sizing = sizing let today = CalendarEngine.today(at: referenceDate ?? Date()) diff --git a/Packages/XStatsKit/Sources/XStatsUI/Snapshot/SnapshotRenderer.swift b/Packages/XStatsKit/Sources/XStatsUI/Snapshot/SnapshotRenderer.swift index 5c664bd..c2b20f5 100644 --- a/Packages/XStatsKit/Sources/XStatsUI/Snapshot/SnapshotRenderer.swift +++ b/Packages/XStatsKit/Sources/XStatsUI/Snapshot/SnapshotRenderer.swift @@ -128,7 +128,10 @@ enum SnapshotRenderer { let name = "XStats.calendarSnapshot.\(UUID().uuidString)" guard let defaults = UserDefaults(suiteName: name) else { return } defer { defaults.removePersistentDomain(forName: name) } - let settings = AppSettings(defaults: defaults) + // 固定地区与周起始日,截图不随运行机器的系统设置变化。 + var weekCalendar = Calendar(identifier: .gregorian) + weekCalendar.firstWeekday = 2 + let settings = AppSettings(defaults: defaults, calendarLocale: Locale(identifier: "zh_CN"), calendar: weekCalendar) settings.calendarEnabled = true settings.language = L10n.language let model = AppModel(settings: settings, historyURL: nil, aiUsageProviders: []) @@ -137,14 +140,14 @@ enum SnapshotRenderer { for (name, suffix) in [(NSAppearance.Name.aqua, "light"), (.darkAqua, "dark")] { guard let appearance = NSAppearance(named: name) else { continue } NSApp.appearance = appearance - settings.calendarFeatures = CalendarFeature.defaults - write(CalendarPopover(referenceDate: september), model: model, appearance: appearance, + settings.calendarFeatures = CalendarFeature.mainlandChinaDefaults + write(CalendarPopover(referenceDate: september, firstWeekday: settings.calendarFirstWeekday), model: model, appearance: appearance, to: outputDirectory.appendingPathComponent("calendar-\(suffix).png")) settings.calendarPreferences.largeLunarText = true settings.calendarPreferences.strongerLunarText = true settings.calendarPreferences.showEvents = true settings.calendarPreferences.showReminders = true - write(CalendarPopover(referenceDate: september), model: model, appearance: appearance, + write(CalendarPopover(referenceDate: september, firstWeekday: settings.calendarFirstWeekday), model: model, appearance: appearance, to: outputDirectory.appendingPathComponent("calendar-agenda-\(suffix).png")) write(CalendarOptionsPopover(), model: model, appearance: appearance, to: outputDirectory.appendingPathComponent("calendar-options-\(suffix).png")) @@ -155,12 +158,12 @@ enum SnapshotRenderer { settings.calendarPreferences.display = .dateLunar write(CalendarOptionsPopover(), model: model, appearance: appearance, to: outputDirectory.appendingPathComponent("calendar-lunar-\(suffix).png")) - // 重置呈现偏好;黄历在日期详情中固定显示,其他选项仍按本机地区初始化。 + // 重置呈现偏好;黄历在日期详情中固定显示,其他选项沿用固定的中国大陆默认值。 settings.calendarPreferences = CalendarPreferences(locale: Locale(identifier: "zh_CN")) - write(CalendarPopover(referenceDate: september, showsDayDetails: true), model: model, appearance: appearance, + write(CalendarPopover(referenceDate: september, showsDayDetails: true, firstWeekday: settings.calendarFirstWeekday), model: model, appearance: appearance, to: outputDirectory.appendingPathComponent("calendar-almanac-\(suffix).png")) settings.calendarFeatures = Set(CalendarFeature.allCases) - write(CalendarPopover(referenceDate: summer, showsDayDetails: true), model: model, appearance: appearance, + write(CalendarPopover(referenceDate: summer, showsDayDetails: true, firstWeekday: settings.calendarFirstWeekday), model: model, appearance: appearance, to: outputDirectory.appendingPathComponent("calendar-details-\(suffix).png")) } } diff --git a/Packages/XStatsKit/Sources/XStatsUI/State/AppSettings.swift b/Packages/XStatsKit/Sources/XStatsUI/State/AppSettings.swift index ce4d6c9..186f6b5 100644 --- a/Packages/XStatsKit/Sources/XStatsUI/State/AppSettings.swift +++ b/Packages/XStatsKit/Sources/XStatsUI/State/AppSettings.swift @@ -749,18 +749,22 @@ public final class AppSettings { ?? (defaults.bool(forKey: Keys.restCycleEnabled) ? .cycle : .single) restSound = defaults.string(forKey: Keys.restSound).flatMap(RestSound.init(rawValue:)) ?? .off restHUDStyle = defaults.string(forKey: Keys.restHUDStyle).flatMap(RestHUDStyle.init(rawValue:)) ?? .countdown + // 旧版只在用户开关日历后写入该键;已有安装缺少保存值时沿用旧版默认,不按地区重新初始化。 + let isExistingCalendarInstall = defaults.object(forKey: Keys.calendarEnabled) != nil calendarEnabled = defaults.bool(forKey: Keys.calendarEnabled) let savedCalendarPreferences = defaults.data(forKey: Keys.calendarPreferences) calendarPreferences = savedCalendarPreferences - .flatMap { try? JSONDecoder().decode(CalendarPreferences.self, from: $0) } ?? CalendarPreferences(locale: calendarLocale) + .flatMap { try? JSONDecoder().decode(CalendarPreferences.self, from: $0) } + ?? CalendarPreferences(locale: isExistingCalendarInstall ? Locale(identifier: "zh_CN") : calendarLocale) calendarEventSourceIDs = defaults.stringArray(forKey: "calendarEventSourceIDs").map { Set($0) } calendarReminderSourceIDs = defaults.stringArray(forKey: "calendarReminderSourceIDs").map { Set($0) } let savedCalendarFeatures = defaults.stringArray(forKey: Keys.calendarFeatures) calendarFeatures = savedCalendarFeatures - .map(CalendarFeature.restored(from:)) ?? CalendarFeature.defaults(for: calendarLocale) + .map(CalendarFeature.restored(from:)) + ?? (isExistingCalendarInstall ? CalendarFeature.mainlandChinaDefaults : CalendarFeature.defaults(for: calendarLocale)) let savedCalendarFirstWeekday = (defaults.object(forKey: Keys.calendarFirstWeekday) as? Int) .flatMap { (1...7).contains($0) ? $0 : nil } - calendarFirstWeekday = savedCalendarFirstWeekday ?? calendar.firstWeekday + calendarFirstWeekday = savedCalendarFirstWeekday ?? (isExistingCalendarInstall ? 2 : calendar.firstWeekday) let items = defaults.stringArray(forKey: Keys.menuBarItems)?.compactMap(MenuBarItem.init(rawValue:)) menuBarItems = Set(items ?? [.cpu, .memory, .network]) menuBarStyle = defaults.string(forKey: Keys.menuBarStyle).flatMap(MenuBarStyle.init(rawValue:)) ?? .stacked diff --git a/Packages/XStatsKit/Tests/XStatsUITests/CalendarAlmanacTests.swift b/Packages/XStatsKit/Tests/XStatsUITests/CalendarAlmanacTests.swift index ac7ea25..c090f49 100644 --- a/Packages/XStatsKit/Tests/XStatsUITests/CalendarAlmanacTests.swift +++ b/Packages/XStatsKit/Tests/XStatsUITests/CalendarAlmanacTests.swift @@ -12,7 +12,7 @@ struct CalendarAlmanacTests { let day = try #require(CalendarEngine.day(year: date.0, month: date.1, day: date.2)) let summary = CalendarEngine.widgetSummary(for: day, features: []) #expect(summary.festivals.isEmpty && summary.solarTerm == nil) - #expect(summary.holidayName == nil && summary.schedule.needsWork == nil) + #expect(summary.holidayName == nil && summary.schedule == .holidaysDisabled) #expect(summary.seasonalDescriptions == nil) #expect(summary.twelveStar != nil) } diff --git a/Packages/XStatsKit/Tests/XStatsUITests/CalendarEnhancementTests.swift b/Packages/XStatsKit/Tests/XStatsUITests/CalendarEnhancementTests.swift index ea09826..23f5a78 100644 --- a/Packages/XStatsKit/Tests/XStatsUITests/CalendarEnhancementTests.swift +++ b/Packages/XStatsKit/Tests/XStatsUITests/CalendarEnhancementTests.swift @@ -26,7 +26,7 @@ struct CalendarEnhancementTests { window.isReleasedWhenClosed = false defer { window.close() } for details in [false, true] { - let hosting = NSHostingView(rootView: CalendarPopover(referenceDate: date, showsDayDetails: details) + let hosting = NSHostingView(rootView: CalendarPopover(referenceDate: date, showsDayDetails: details, firstWeekday: model.settings.calendarFirstWeekday) .environment(model) .environment(\.reportPopoverHeight, reporter)) window.contentView = hosting @@ -193,6 +193,27 @@ struct CalendarEnhancementTests { #expect(!disabled.calendarPreferences.showHolidayOverview) } + @Test(arguments: [true, false]) + func existingInstallKeepsLegacyCalendarDefaultsInEveryRegion(calendarEnabled: Bool) throws { + let name = "CalendarUpgradeTests.\(UUID())" + let defaults = try #require(UserDefaults(suiteName: name)) + defer { defaults.removePersistentDomain(forName: name) } + // 旧版只写入过日历开关,未保存任何日历选项。 + defaults.set(calendarEnabled, forKey: "calendarEnabled") + var calendar = Calendar(identifier: .gregorian) + calendar.firstWeekday = 1 + let settings = AppSettings(defaults: defaults, calendarLocale: Locale(identifier: "en_US"), calendar: calendar) + #expect(settings.calendarEnabled == calendarEnabled) + #expect(settings.calendarFeatures == [.lunar, .holidays, .festivals, .seasonalInfo]) + #expect(settings.calendarPreferences.showHolidayOverview) + #expect(settings.calendarFirstWeekday == 2) + // 沿用的默认值在首次启动时保存,之后地区或系统周起始日变化不再影响。 + let reloaded = AppSettings(defaults: defaults, calendarLocale: Locale(identifier: "ja_JP"), calendar: calendar) + #expect(reloaded.calendarFeatures == settings.calendarFeatures) + #expect(reloaded.calendarPreferences == settings.calendarPreferences) + #expect(reloaded.calendarFirstWeekday == 2) + } + @Test(arguments: Array(1...7)) func weekStartUsesSystemDefaultOnceAndPreservesManualChoice(firstWeekday: Int) throws { let name = "CalendarWeekStartTests.\(UUID())" diff --git a/Packages/XStatsKit/Tests/XStatsUITests/CalendarTests.swift b/Packages/XStatsKit/Tests/XStatsUITests/CalendarTests.swift index 1ae8176..c6eded4 100644 --- a/Packages/XStatsKit/Tests/XStatsUITests/CalendarTests.swift +++ b/Packages/XStatsKit/Tests/XStatsUITests/CalendarTests.swift @@ -219,7 +219,7 @@ struct CalendarTests { defer { defaults.removePersistentDomain(forName: name) } let settings = AppSettings(defaults: defaults) #expect(settings.calendarEnabled == false) - #expect(settings.calendarFeatures == CalendarFeature.defaults) + #expect(settings.calendarFeatures == CalendarFeature.defaults(for: .current)) settings.calendarEnabled = true settings.calendarFeatures = [.hijri, .lunar] settings.calendarFirstWeekday = 1 diff --git a/Widget/CalendarWidget.swift b/Widget/CalendarWidget.swift index 657cffc..04f0d2d 100644 --- a/Widget/CalendarWidget.swift +++ b/Widget/CalendarWidget.swift @@ -296,7 +296,7 @@ private struct TomorrowWorkWidgetView: View { case .makeupWork: tr("调休补班") case .makeupDayOff: tr("调休放假") case .dayOff: tr("放假") - case .unknown: nil + case .unknown, .holidaysDisabled: nil } } @@ -323,7 +323,7 @@ private struct TomorrowWorkWidgetView: View { .minimumScaleFactor(0.75) } } else { - Text(tr("仅支持中国大陆节假日")) + Text(tr(entry.tomorrow?.schedule == .holidaysDisabled ? "仅支持中国大陆节假日" : "暂无法判断")) .font(.caption.weight(.medium)) .foregroundStyle(.secondary) }