diff --git a/App/Shared/Resources/de.lproj/Localizable.strings b/App/Shared/Resources/de.lproj/Localizable.strings index b37acca..58fe471 100644 --- a/App/Shared/Resources/de.lproj/Localizable.strings +++ b/App/Shared/Resources/de.lproj/Localizable.strings @@ -65,11 +65,6 @@ "Open Settings" = "Einstellungen öffnen"; "Log your weight" = "Gewicht eintragen"; "Open LogWeight to record today's weight." = "Öffne LogWeight, um dein heutiges Gewicht zu erfassen."; -"Display" = "Anzeige"; -"Show trend arrow" = "Trendpfeil anzeigen"; -"Trending up" = "Steigender Trend"; -"Trending down" = "Fallender Trend"; -"Weight stable" = "Gewicht stabil"; "Cannot Delete Entry" = "Eintrag kann nicht gelöscht werden"; "OK" = "OK"; "Delete" = "Löschen"; diff --git a/App/Shared/Resources/en.lproj/Localizable.strings b/App/Shared/Resources/en.lproj/Localizable.strings index 7ff21fc..fdf7619 100644 --- a/App/Shared/Resources/en.lproj/Localizable.strings +++ b/App/Shared/Resources/en.lproj/Localizable.strings @@ -65,11 +65,6 @@ "Open Settings" = "Open Settings"; "Log your weight" = "Log your weight"; "Open LogWeight to record today's weight." = "Open LogWeight to record today's weight."; -"Display" = "Display"; -"Show trend arrow" = "Show trend arrow"; -"Trending up" = "Trending up"; -"Trending down" = "Trending down"; -"Weight stable" = "Weight stable"; "Cannot Delete Entry" = "Cannot Delete Entry"; "OK" = "OK"; "Delete" = "Delete"; diff --git a/App/Shared/Resources/es.lproj/Localizable.strings b/App/Shared/Resources/es.lproj/Localizable.strings index 3dc39f2..7144d2e 100644 --- a/App/Shared/Resources/es.lproj/Localizable.strings +++ b/App/Shared/Resources/es.lproj/Localizable.strings @@ -65,11 +65,6 @@ "Open Settings" = "Abrir Ajustes"; "Log your weight" = "Registra tu peso"; "Open LogWeight to record today's weight." = "Abre LogWeight para registrar el peso de hoy."; -"Display" = "Visualización"; -"Show trend arrow" = "Mostrar flecha de tendencia"; -"Trending up" = "Tendencia al alza"; -"Trending down" = "Tendencia a la baja"; -"Weight stable" = "Peso estable"; "Cannot Delete Entry" = "No se puede eliminar el registro"; "OK" = "OK"; "Delete" = "Eliminar"; diff --git a/App/Shared/Resources/fr.lproj/Localizable.strings b/App/Shared/Resources/fr.lproj/Localizable.strings index 156a861..3dab994 100644 --- a/App/Shared/Resources/fr.lproj/Localizable.strings +++ b/App/Shared/Resources/fr.lproj/Localizable.strings @@ -65,11 +65,6 @@ "Open Settings" = "Ouvrir Réglages"; "Log your weight" = "Enregistrez votre poids"; "Open LogWeight to record today's weight." = "Ouvrez LogWeight pour enregistrer le poids du jour."; -"Display" = "Affichage"; -"Show trend arrow" = "Afficher la flèche de tendance"; -"Trending up" = "Tendance à la hausse"; -"Trending down" = "Tendance à la baisse"; -"Weight stable" = "Poids stable"; "Cannot Delete Entry" = "Impossible de supprimer l’entrée"; "OK" = "OK"; "Delete" = "Supprimer"; diff --git a/App/Shared/Resources/it.lproj/Localizable.strings b/App/Shared/Resources/it.lproj/Localizable.strings index efd48bd..c5ff374 100644 --- a/App/Shared/Resources/it.lproj/Localizable.strings +++ b/App/Shared/Resources/it.lproj/Localizable.strings @@ -65,11 +65,6 @@ "Open Settings" = "Apri Impostazioni"; "Log your weight" = "Registra il peso"; "Open LogWeight to record today's weight." = "Apri LogWeight per registrare il peso di oggi."; -"Display" = "Visualizzazione"; -"Show trend arrow" = "Mostra freccia di tendenza"; -"Trending up" = "In aumento"; -"Trending down" = "In diminuzione"; -"Weight stable" = "Peso stabile"; "Cannot Delete Entry" = "Impossibile eliminare la voce"; "OK" = "OK"; "Delete" = "Elimina"; diff --git a/App/Shared/Resources/ja.lproj/Localizable.strings b/App/Shared/Resources/ja.lproj/Localizable.strings index 04d2191..5d97188 100644 --- a/App/Shared/Resources/ja.lproj/Localizable.strings +++ b/App/Shared/Resources/ja.lproj/Localizable.strings @@ -65,11 +65,6 @@ "Open Settings" = "設定を開く"; "Log your weight" = "体重を記録しましょう"; "Open LogWeight to record today's weight." = "LogWeightを開いて、今日の体重を記録してください。"; -"Display" = "表示"; -"Show trend arrow" = "トレンド矢印を表示"; -"Trending up" = "上昇傾向"; -"Trending down" = "下降傾向"; -"Weight stable" = "体重は安定"; "Cannot Delete Entry" = "記録を削除できません"; "OK" = "OK"; "Delete" = "削除"; diff --git a/App/Shared/Resources/ko.lproj/Localizable.strings b/App/Shared/Resources/ko.lproj/Localizable.strings index 5b8a274..5413d57 100644 --- a/App/Shared/Resources/ko.lproj/Localizable.strings +++ b/App/Shared/Resources/ko.lproj/Localizable.strings @@ -65,11 +65,6 @@ "Open Settings" = "설정 열기"; "Log your weight" = "체중을 기록하세요"; "Open LogWeight to record today's weight." = "LogWeight를 열어 오늘의 체중을 기록하세요."; -"Display" = "표시"; -"Show trend arrow" = "추세 화살표 표시"; -"Trending up" = "상승 추세"; -"Trending down" = "하락 추세"; -"Weight stable" = "체중 안정"; "Cannot Delete Entry" = "기록을 삭제할 수 없음"; "OK" = "확인"; "Delete" = "삭제"; diff --git a/App/Shared/Resources/nl.lproj/Localizable.strings b/App/Shared/Resources/nl.lproj/Localizable.strings index 0735a21..936c5e7 100644 --- a/App/Shared/Resources/nl.lproj/Localizable.strings +++ b/App/Shared/Resources/nl.lproj/Localizable.strings @@ -65,11 +65,6 @@ "Open Settings" = "Open Instellingen"; "Log your weight" = "Log je gewicht"; "Open LogWeight to record today's weight." = "Open LogWeight om het gewicht van vandaag vast te leggen."; -"Display" = "Weergave"; -"Show trend arrow" = "Trendpijl tonen"; -"Trending up" = "Stijgende trend"; -"Trending down" = "Dalende trend"; -"Weight stable" = "Gewicht stabiel"; "Cannot Delete Entry" = "Invoer kan niet worden verwijderd"; "OK" = "OK"; "Delete" = "Verwijderen"; diff --git a/App/Shared/Resources/pt-BR.lproj/Localizable.strings b/App/Shared/Resources/pt-BR.lproj/Localizable.strings index 9319abf..abfd8d3 100644 --- a/App/Shared/Resources/pt-BR.lproj/Localizable.strings +++ b/App/Shared/Resources/pt-BR.lproj/Localizable.strings @@ -65,11 +65,6 @@ "Open Settings" = "Abrir Ajustes"; "Log your weight" = "Registre seu peso"; "Open LogWeight to record today's weight." = "Abra o LogWeight para registrar o peso de hoje."; -"Display" = "Exibição"; -"Show trend arrow" = "Mostrar seta de tendência"; -"Trending up" = "Em alta"; -"Trending down" = "Em queda"; -"Weight stable" = "Peso estável"; "Cannot Delete Entry" = "Não é possível excluir o registro"; "OK" = "OK"; "Delete" = "Excluir"; diff --git a/App/Shared/Resources/zh-Hans.lproj/Localizable.strings b/App/Shared/Resources/zh-Hans.lproj/Localizable.strings index 6a27d6a..7f1615f 100644 --- a/App/Shared/Resources/zh-Hans.lproj/Localizable.strings +++ b/App/Shared/Resources/zh-Hans.lproj/Localizable.strings @@ -65,11 +65,6 @@ "Open Settings" = "打开设置"; "Log your weight" = "记录体重"; "Open LogWeight to record today's weight." = "打开 LogWeight 记录今日体重。"; -"Display" = "显示"; -"Show trend arrow" = "显示趋势箭头"; -"Trending up" = "上升趋势"; -"Trending down" = "下降趋势"; -"Weight stable" = "体重稳定"; "Cannot Delete Entry" = "无法删除记录"; "OK" = "好"; "Delete" = "删除"; diff --git a/App/Shared/Resources/zh-Hant.lproj/Localizable.strings b/App/Shared/Resources/zh-Hant.lproj/Localizable.strings index c95f4c4..e25dda3 100644 --- a/App/Shared/Resources/zh-Hant.lproj/Localizable.strings +++ b/App/Shared/Resources/zh-Hant.lproj/Localizable.strings @@ -65,11 +65,6 @@ "Open Settings" = "打開設定"; "Log your weight" = "記錄體重"; "Open LogWeight to record today's weight." = "打開 LogWeight 記錄今日體重。"; -"Display" = "顯示"; -"Show trend arrow" = "顯示趨勢箭頭"; -"Trending up" = "上升趨勢"; -"Trending down" = "下降趨勢"; -"Weight stable" = "體重穩定"; "Cannot Delete Entry" = "無法刪除記錄"; "OK" = "好"; "Delete" = "刪除"; diff --git a/App/Shared/Views/SettingsView.swift b/App/Shared/Views/SettingsView.swift index d7e99d7..9d2fa1a 100644 --- a/App/Shared/Views/SettingsView.swift +++ b/App/Shared/Views/SettingsView.swift @@ -9,7 +9,6 @@ struct SettingsView: View { @AppStorage(SettingsKey.unitPreference) private var unitPreferenceRaw: String = WeightUnit.kilograms.rawValue @AppStorage(SettingsKey.defaultEntryMode) private var defaultEntryModeRaw: String = DefaultEntryMode.lastSaved.rawValue @AppStorage(SettingsKey.hapticsEnabled) private var hapticsEnabled: Bool = true - @AppStorage(SettingsKey.trendArrowEnabled) private var trendArrowEnabled: Bool = true @AppStorage(SettingsKey.reminderEnabled) private var reminderEnabled: Bool = false @AppStorage(SettingsKey.reminderHour) private var reminderHour: Int = DailyReminderSettings.defaultHour @AppStorage(SettingsKey.reminderMinute) private var reminderMinute: Int = DailyReminderSettings.defaultMinute @@ -48,11 +47,6 @@ struct SettingsView: View { .accessibilityIdentifier("settings.haptics") } - Section("Display") { - Toggle("Show trend arrow", isOn: $trendArrowEnabled) - .accessibilityIdentifier("settings.trendArrow") - } - #if os(iOS) Section("Reminders") { Toggle("Daily reminder", isOn: reminderEnabledBinding) @@ -126,10 +120,6 @@ struct SettingsView: View { WeightDisplayPreferences.mirrorUnitPreferenceToAppGroup() WidgetTimelineRefresh.reloadEntryAndChartWidgets() } - .onChange(of: trendArrowEnabled) { _, _ in - TrendArrowPreferences.mirrorToAppGroup() - WidgetTimelineRefresh.reloadEntryAndChartWidgets() - } #if os(iOS) .task { reminderAuthStatus = await reminderScheduler.authorizationStatus() diff --git a/App/Shared/Widget/WeightTrendArrow.swift b/App/Shared/Widget/WeightTrendArrow.swift deleted file mode 100644 index 9fe7bdf..0000000 --- a/App/Shared/Widget/WeightTrendArrow.swift +++ /dev/null @@ -1,61 +0,0 @@ -import SwiftUI -import LogWeightCore - -private enum TrendArrowPreferenceStore { - nonisolated(unsafe) static let userDefaults = - UserDefaults(suiteName: SharedWeightEntryStore.appGroupIdentifier) ?? .standard -} - -/// Compact trend indicator for widgets and history headers. -struct WeightTrendArrow: View { - let direction: WeightTrendDirection - var font: Font = .caption2.weight(.semibold) - - @AppStorage(SettingsKey.trendArrowEnabled, store: TrendArrowPreferenceStore.userDefaults) - private var trendArrowEnabled = true - - /// Shared style for Home Screen widget headers (gray, subheadline size). - static func widget(direction: WeightTrendDirection) -> WeightTrendArrow { - WeightTrendArrow(direction: direction, font: .subheadline.weight(.semibold)) - } - - var body: some View { - if trendArrowEnabled { - trendContent - } else { - EmptyView() - } - } - - @ViewBuilder - private var trendContent: some View { - switch direction { - case .unknown: - EmptyView() - case .up: - trendSymbol("arrow.up", accessibilityLabel: "Trending up") - case .down: - trendSymbol("arrow.down", accessibilityLabel: "Trending down") - case .flat: - trendSymbol("minus", accessibilityLabel: "Weight stable") - } - } - - private func trendSymbol(_ name: String, accessibilityLabel: String) -> some View { - Image(systemName: name) - .font(font) - .foregroundStyle(.secondary) - .accessibilityLabel(accessibilityLabel) - } -} - -#if DEBUG -#Preview("Trend arrows") { - HStack(spacing: 12) { - WeightTrendArrow(direction: .up) - WeightTrendArrow(direction: .down) - WeightTrendArrow(direction: .flat) - } - .padding() -} -#endif diff --git a/App/Shared/Widget/WidgetTimelineRefresh.swift b/App/Shared/Widget/WidgetTimelineRefresh.swift index 224baa5..cf12a45 100644 --- a/App/Shared/Widget/WidgetTimelineRefresh.swift +++ b/App/Shared/Widget/WidgetTimelineRefresh.swift @@ -24,9 +24,8 @@ enum WidgetTimelineRefresh { static func syncEntryStoreAndReloadWidgets(store: HealthKitStore) async { #if os(iOS) - let weights = (try? await store.recentWeights(limit: WeightTrendCache.entryWidgetSampleLimit)) ?? [] + let weights = (try? await store.recentWeights(limit: 1)) ?? [] SharedWeightEntryStore.syncFromLatestWeight(weights.first) - WeightTrendCache.update(from: weights) reloadEntryAndChartWidgets() #endif } diff --git a/App/Watch/Views/WatchSettingsView.swift b/App/Watch/Views/WatchSettingsView.swift index 35f9ba1..9d60c30 100644 --- a/App/Watch/Views/WatchSettingsView.swift +++ b/App/Watch/Views/WatchSettingsView.swift @@ -1,5 +1,4 @@ import SwiftUI -import WidgetKit import LogWeightCore /// Minimal watch settings (unit + haptics). Full settings remain on iPhone. @@ -7,7 +6,6 @@ struct WatchSettingsView: View { @AppStorage(SettingsKey.unitPreference) private var unitPreferenceRaw: String = WeightUnit.kilograms.rawValue @AppStorage(SettingsKey.hapticsEnabled) private var hapticsEnabled: Bool = true - @AppStorage(SettingsKey.trendArrowEnabled) private var trendArrowEnabled: Bool = true @Environment(\.dismiss) private var dismiss var body: some View { @@ -24,9 +22,6 @@ struct WatchSettingsView: View { Section("Feedback") { Toggle("Haptics on save", isOn: $hapticsEnabled) } - Section("Display") { - Toggle("Show trend arrow", isOn: $trendArrowEnabled) - } Section("About") { Text("Made with 🩷🩵 by Juuronina GbR.") .font(.footnote) @@ -41,10 +36,6 @@ struct WatchSettingsView: View { Button("Done") { dismiss() } } } - .onChange(of: trendArrowEnabled) { _, _ in - TrendArrowPreferences.mirrorToAppGroup() - WidgetCenter.shared.reloadTimelines(ofKind: LogWeightWidgetConstants.watchKind) - } } } } diff --git a/Packages/LogWeightCore/Sources/LogWeightCore/Models/WeightTrendEvaluator.swift b/Packages/LogWeightCore/Sources/LogWeightCore/Models/WeightTrendEvaluator.swift deleted file mode 100644 index 30a209a..0000000 --- a/Packages/LogWeightCore/Sources/LogWeightCore/Models/WeightTrendEvaluator.swift +++ /dev/null @@ -1,122 +0,0 @@ -import Foundation - -/// Direction of recent body-weight change for compact UI (widgets, complications). -public enum WeightTrendDirection: Sendable, Equatable { - case up - case down - case flat - case unknown -} - -/// Tunables for `WeightTrendEvaluator.direction`. -public struct WeightTrendConfiguration: Sendable, Equatable { - public var minimumSampleCount: Int - public var minimumSpanDays: Double - /// Slopes with magnitude below this (kg/day) are treated as flat. - public var flatSlopeThresholdKgPerDay: Double - - public init( - minimumSampleCount: Int = 3, - minimumSpanDays: Double = 7, - flatSlopeThresholdKgPerDay: Double = 0.02 - ) { - self.minimumSampleCount = minimumSampleCount - self.minimumSpanDays = minimumSpanDays - self.flatSlopeThresholdKgPerDay = flatSlopeThresholdKgPerDay - } - - /// Relaxed gates for short chart windows (e.g. 1W widget) so a visible trend can be shown. - public static func forChartRange(_ range: ChartTimeRange) -> WeightTrendConfiguration { - switch range { - case .oneWeek: - WeightTrendConfiguration( - minimumSampleCount: 2, - minimumSpanDays: 1, - flatSlopeThresholdKgPerDay: 0.015 - ) - case .oneMonth: - WeightTrendConfiguration( - minimumSampleCount: 3, - minimumSpanDays: 4, - flatSlopeThresholdKgPerDay: 0.02 - ) - case .threeMonths, .sixMonths: - WeightTrendConfiguration( - minimumSampleCount: 3, - minimumSpanDays: 7, - flatSlopeThresholdKgPerDay: 0.02 - ) - case .oneYear, .all: - WeightTrendConfiguration() - } - } -} - -/// Estimates whether weight is trending up, down, or flat using OLS slope over a time window. -public enum WeightTrendEvaluator { - private static let secondsPerDay: Double = 86_400 - - public static func direction( - weights: [Weight], - windowStart: Date?, - referenceDate: Date = .now, - configuration: WeightTrendConfiguration = WeightTrendConfiguration() - ) -> WeightTrendDirection { - let inWindow = weights.filter { weight in - weight.recordedAt <= referenceDate - && (windowStart.map { weight.recordedAt >= $0 } ?? true) - } - let sorted = inWindow.sorted { $0.recordedAt < $1.recordedAt } - - guard sorted.count >= configuration.minimumSampleCount else { - return .unknown - } - - guard let first = sorted.first, let last = sorted.last else { - return .unknown - } - - let spanDays = last.recordedAt.timeIntervalSince(first.recordedAt) / Self.secondsPerDay - guard spanDays >= configuration.minimumSpanDays else { - return .unknown - } - - guard let slopeKgPerDay = linearSlopeKgPerDay(samples: sorted, origin: first.recordedAt) else { - return .unknown - } - - let threshold = configuration.flatSlopeThresholdKgPerDay - if slopeKgPerDay > threshold { - return .up - } - if slopeKgPerDay < -threshold { - return .down - } - return .flat - } - - /// Ordinary least-squares slope (kg/day) with x = days since `origin`. - private static func linearSlopeKgPerDay(samples: [Weight], origin: Date) -> Double? { - guard samples.count >= 2 else { return nil } - - var sumX = 0.0 - var sumY = 0.0 - var sumXX = 0.0 - var sumXY = 0.0 - let n = Double(samples.count) - - for sample in samples { - let x = sample.recordedAt.timeIntervalSince(origin) / secondsPerDay - let y = sample.valueInKilograms - sumX += x - sumY += y - sumXX += x * x - sumXY += x * y - } - - let denominator = n * sumXX - sumX * sumX - guard denominator > 0 else { return nil } - - return (n * sumXY - sumX * sumY) / denominator - } -} diff --git a/Packages/LogWeightCore/Sources/LogWeightCore/Settings/SettingsKeys.swift b/Packages/LogWeightCore/Sources/LogWeightCore/Settings/SettingsKeys.swift index 6c94abc..2b39512 100644 --- a/Packages/LogWeightCore/Sources/LogWeightCore/Settings/SettingsKeys.swift +++ b/Packages/LogWeightCore/Sources/LogWeightCore/Settings/SettingsKeys.swift @@ -12,7 +12,6 @@ public enum SettingsKey { public static let unitPreference = "logweight_unit_preference" public static let defaultEntryMode = "logweight_default_entry_mode" public static let hapticsEnabled = "logweight_haptics_enabled" - public static let trendArrowEnabled = "logweight_trend_arrow_enabled" public static let reminderEnabled = "logweight_reminder_enabled" public static let reminderHour = "logweight_reminder_hour" public static let reminderMinute = "logweight_reminder_minute" diff --git a/Packages/LogWeightCore/Sources/LogWeightCore/Settings/SettingsMigrator.swift b/Packages/LogWeightCore/Sources/LogWeightCore/Settings/SettingsMigrator.swift index 4840b81..950a988 100644 --- a/Packages/LogWeightCore/Sources/LogWeightCore/Settings/SettingsMigrator.swift +++ b/Packages/LogWeightCore/Sources/LogWeightCore/Settings/SettingsMigrator.swift @@ -23,6 +23,5 @@ public enum SettingsMigrator { private static func mirrorWidgetPreferences(standardDefaults: UserDefaults) { WeightDisplayPreferences.mirrorUnitPreferenceToAppGroup(standardDefaults: standardDefaults) - TrendArrowPreferences.mirrorToAppGroup(standardDefaults: standardDefaults) } } diff --git a/Packages/LogWeightCore/Sources/LogWeightCore/Settings/TrendArrowPreferences.swift b/Packages/LogWeightCore/Sources/LogWeightCore/Settings/TrendArrowPreferences.swift deleted file mode 100644 index a20967e..0000000 --- a/Packages/LogWeightCore/Sources/LogWeightCore/Settings/TrendArrowPreferences.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Foundation - -/// Whether trend arrows appear in widgets, complications, and the History chart header. -public enum TrendArrowPreferences { - /// Reads app-group defaults first (widgets), then standard defaults (app / watch). Defaults to enabled. - public static func isEnabled( - sharedDefaults: UserDefaults? = UserDefaults(suiteName: SharedWeightEntryStore.appGroupIdentifier), - standardDefaults: UserDefaults = .standard - ) -> Bool { - for defaults in [sharedDefaults, standardDefaults].compactMap({ $0 }) { - if let enabled = bool(from: defaults) { - return enabled - } - } - return true - } - - /// Copies the app preference into the App Group so widget extensions can read it. - public static func mirrorToAppGroup( - standardDefaults: UserDefaults = .standard, - sharedDefaults: UserDefaults? = UserDefaults(suiteName: SharedWeightEntryStore.appGroupIdentifier) - ) { - guard let shared = sharedDefaults else { return } - let enabled = standardDefaults.object(forKey: SettingsKey.trendArrowEnabled) as? Bool ?? true - shared.set(enabled, forKey: SettingsKey.trendArrowEnabled) - } - - private static func bool(from defaults: UserDefaults) -> Bool? { - guard defaults.object(forKey: SettingsKey.trendArrowEnabled) != nil else { - return nil - } - return defaults.bool(forKey: SettingsKey.trendArrowEnabled) - } -} diff --git a/Packages/LogWeightCore/Sources/LogWeightCore/Widget/WeightTrendCache.swift b/Packages/LogWeightCore/Sources/LogWeightCore/Widget/WeightTrendCache.swift deleted file mode 100644 index 697a293..0000000 --- a/Packages/LogWeightCore/Sources/LogWeightCore/Widget/WeightTrendCache.swift +++ /dev/null @@ -1,64 +0,0 @@ -import Foundation - -/// Cached 28-day weight trend for widget extensions (read synchronously from the App Group). -public enum WeightTrendCache { - private static let directionKey = "logweight_cached_trend_direction" - /// Matches the entry widget's HealthKit window. - public static let entryWidgetLookbackDays = 28 - public static let entryWidgetSampleLimit = 40 - - public static func save( - _ direction: WeightTrendDirection, - userDefaults: UserDefaults? = UserDefaults(suiteName: SharedWeightEntryStore.appGroupIdentifier) - ) { - guard let defaults = userDefaults else { return } - defaults.set(direction.storedValue, forKey: directionKey) - } - - public static func load( - userDefaults: UserDefaults? = UserDefaults(suiteName: SharedWeightEntryStore.appGroupIdentifier) - ) -> WeightTrendDirection { - guard - let defaults = userDefaults, - let raw = defaults.string(forKey: directionKey) - else { - return .unknown - } - return WeightTrendDirection(storedValue: raw) - } - - public static func update( - from weights: [Weight], - referenceDate: Date = .now, - calendar: Calendar = .current, - userDefaults: UserDefaults? = UserDefaults(suiteName: SharedWeightEntryStore.appGroupIdentifier) - ) { - let windowStart = calendar.date(byAdding: .day, value: -entryWidgetLookbackDays, to: referenceDate) - let direction = WeightTrendEvaluator.direction( - weights: weights, - windowStart: windowStart, - referenceDate: referenceDate - ) - save(direction, userDefaults: userDefaults) - } -} - -extension WeightTrendDirection { - var storedValue: String { - switch self { - case .up: "up" - case .down: "down" - case .flat: "flat" - case .unknown: "unknown" - } - } - - init(storedValue: String) { - switch storedValue { - case "up": self = .up - case "down": self = .down - case "flat": self = .flat - default: self = .unknown - } - } -} diff --git a/Packages/LogWeightCore/Tests/LogWeightCoreTests/TrendArrowPreferencesTests.swift b/Packages/LogWeightCore/Tests/LogWeightCoreTests/TrendArrowPreferencesTests.swift deleted file mode 100644 index cb7f1c4..0000000 --- a/Packages/LogWeightCore/Tests/LogWeightCoreTests/TrendArrowPreferencesTests.swift +++ /dev/null @@ -1,37 +0,0 @@ -import XCTest -@testable import LogWeightCore - -final class TrendArrowPreferencesTests: XCTestCase { - func testIsEnabledDefaultsToTrueWhenUnset() { - let shared = UserDefaults(suiteName: "TrendArrowPreferencesTests.unset.shared")! - let standard = UserDefaults(suiteName: "TrendArrowPreferencesTests.unset.standard")! - shared.removePersistentDomain(forName: "TrendArrowPreferencesTests.unset.shared") - standard.removePersistentDomain(forName: "TrendArrowPreferencesTests.unset.standard") - - XCTAssertTrue(TrendArrowPreferences.isEnabled(sharedDefaults: shared, standardDefaults: standard)) - } - - func testIsEnabledReadsSharedDefaultsFirst() { - let shared = UserDefaults(suiteName: "TrendArrowPreferencesTests.shared")! - let standard = UserDefaults(suiteName: "TrendArrowPreferencesTests.standard")! - shared.removePersistentDomain(forName: "TrendArrowPreferencesTests.shared") - standard.removePersistentDomain(forName: "TrendArrowPreferencesTests.standard") - - shared.set(false, forKey: SettingsKey.trendArrowEnabled) - standard.set(true, forKey: SettingsKey.trendArrowEnabled) - - XCTAssertFalse(TrendArrowPreferences.isEnabled(sharedDefaults: shared, standardDefaults: standard)) - } - - func testMirrorToAppGroup() { - let shared = UserDefaults(suiteName: "TrendArrowPreferencesTests.mirror")! - let standard = UserDefaults(suiteName: "TrendArrowPreferencesTests.mirrorStandard")! - shared.removePersistentDomain(forName: "TrendArrowPreferencesTests.mirror") - standard.removePersistentDomain(forName: "TrendArrowPreferencesTests.mirrorStandard") - - standard.set(false, forKey: SettingsKey.trendArrowEnabled) - TrendArrowPreferences.mirrorToAppGroup(standardDefaults: standard, sharedDefaults: shared) - - XCTAssertFalse(shared.bool(forKey: SettingsKey.trendArrowEnabled)) - } -} diff --git a/Packages/LogWeightCore/Tests/LogWeightCoreTests/WeightTrendCacheTests.swift b/Packages/LogWeightCore/Tests/LogWeightCoreTests/WeightTrendCacheTests.swift deleted file mode 100644 index ebb65b0..0000000 --- a/Packages/LogWeightCore/Tests/LogWeightCoreTests/WeightTrendCacheTests.swift +++ /dev/null @@ -1,29 +0,0 @@ -import XCTest -@testable import LogWeightCore - -final class WeightTrendCacheTests: XCTestCase { - private var defaults: UserDefaults! - - override func setUp() { - super.setUp() - let suiteName = "WeightTrendCacheTests.\(UUID().uuidString)" - defaults = UserDefaults(suiteName: suiteName)! - defaults.removePersistentDomain(forName: suiteName) - } - - func testSaveAndLoadRoundTrip() { - WeightTrendCache.save(.up, userDefaults: defaults) - XCTAssertEqual(WeightTrendCache.load(userDefaults: defaults), .up) - } - - func testLoadWhenMissingReturnsUnknown() { - XCTAssertEqual(WeightTrendCache.load(userDefaults: defaults), .unknown) - } - - func testUpdateFromWeightsPersistsDirection() { - let reference = ScreenshotFixture.referenceDate - let weights = ScreenshotFixture.linearTrend30Days.samples(now: reference) - WeightTrendCache.update(from: weights, referenceDate: reference, userDefaults: defaults) - XCTAssertEqual(WeightTrendCache.load(userDefaults: defaults), .down) - } -} diff --git a/Packages/LogWeightCore/Tests/LogWeightCoreTests/WeightTrendEvaluatorTests.swift b/Packages/LogWeightCore/Tests/LogWeightCoreTests/WeightTrendEvaluatorTests.swift deleted file mode 100644 index cf1e227..0000000 --- a/Packages/LogWeightCore/Tests/LogWeightCoreTests/WeightTrendEvaluatorTests.swift +++ /dev/null @@ -1,146 +0,0 @@ -import XCTest -@testable import LogWeightCore - -final class WeightTrendEvaluatorTests: XCTestCase { - private let reference = ScreenshotFixture.referenceDate - - func testLinearTrend30DaysIsDown() { - let samples = ScreenshotFixture.linearTrend30Days.samples(now: reference) - let windowStart = Calendar.current.date(byAdding: .day, value: -28, to: reference) - - let direction = WeightTrendEvaluator.direction( - weights: samples, - windowStart: windowStart, - referenceDate: reference - ) - - XCTAssertEqual(direction, .down) - } - - func testTooFewSamplesReturnsUnknown() { - let samples = [ - Weight(valueInKilograms: 75.0, recordedAt: reference.addingTimeInterval(-3 * 86_400)), - Weight(valueInKilograms: 74.5, recordedAt: reference) - ] - let windowStart = Calendar.current.date(byAdding: .day, value: -28, to: reference) - - let direction = WeightTrendEvaluator.direction( - weights: samples, - windowStart: windowStart, - referenceDate: reference - ) - - XCTAssertEqual(direction, .unknown) - } - - func testSpanTooShortReturnsUnknown() { - let samples = (0..<4).map { index in - Weight( - valueInKilograms: 75.0 + Double(index) * 0.5, - recordedAt: reference.addingTimeInterval(Double(index) * 86_400) - ) - } - let windowStart = Calendar.current.date(byAdding: .day, value: -28, to: reference) - - let direction = WeightTrendEvaluator.direction( - weights: samples, - windowStart: windowStart, - referenceDate: reference - ) - - XCTAssertEqual(direction, .unknown) - } - - func testFlatPlateauReturnsFlat() { - let calendar = Calendar(identifier: .gregorian) - let samples = (0..<10).map { offset in - let date = calendar.date(byAdding: .day, value: -(9 - offset) * 3, to: reference) ?? reference - return Weight(valueInKilograms: 75.0, recordedAt: date) - } - let windowStart = calendar.date(byAdding: .day, value: -28, to: reference) - - let direction = WeightTrendEvaluator.direction( - weights: samples, - windowStart: windowStart, - referenceDate: reference - ) - - XCTAssertEqual(direction, .flat) - } - - func testClearUpwardTrend() { - let calendar = Calendar(identifier: .gregorian) - let samples = (0..<8).map { offset in - let date = calendar.date(byAdding: .day, value: -(7 - offset) * 4, to: reference) ?? reference - return Weight(valueInKilograms: 70.0 + Double(offset) * 0.4, recordedAt: date) - } - let windowStart = calendar.date(byAdding: .day, value: -28, to: reference) - - let direction = WeightTrendEvaluator.direction( - weights: samples, - windowStart: windowStart, - referenceDate: reference - ) - - XCTAssertEqual(direction, .up) - } - - func testSamplesOutsideWindowAreIgnored() { - let calendar = Calendar(identifier: .gregorian) - let old = Weight( - valueInKilograms: 90.0, - recordedAt: calendar.date(byAdding: .day, value: -60, to: reference) ?? reference - ) - let recent = (0..<5).map { offset in - let date = calendar.date(byAdding: .day, value: -(4 - offset) * 5, to: reference) ?? reference - return Weight(valueInKilograms: 75.0 + Double(offset) * 0.5, recordedAt: date) - } - let windowStart = calendar.date(byAdding: .day, value: -28, to: reference) - - let direction = WeightTrendEvaluator.direction( - weights: [old] + recent, - windowStart: windowStart, - referenceDate: reference - ) - - XCTAssertEqual(direction, .up) - } - - func testOneWeekChartConfigurationAllowsShortSpan() { - let calendar = Calendar(identifier: .gregorian) - let samples = [ - Weight( - valueInKilograms: 72.0, - recordedAt: calendar.date(byAdding: .day, value: -2, to: reference) ?? reference - ), - Weight(valueInKilograms: 67.9, recordedAt: reference) - ] - let windowStart = ChartTimeRange.oneWeek.cutoffDate(referenceDate: reference) - - let direction = WeightTrendEvaluator.direction( - weights: samples, - windowStart: windowStart, - referenceDate: reference, - configuration: .forChartRange(.oneWeek) - ) - - XCTAssertEqual(direction, .down) - } - - func testFutureSamplesExcluded() { - let future = Weight( - valueInKilograms: 100.0, - recordedAt: reference.addingTimeInterval(86_400) - ) - let samples = ScreenshotFixture.linearTrend30Days.samples(now: reference) + [future] - let windowStart = Calendar.current.date(byAdding: .day, value: -28, to: reference) - - let direction = WeightTrendEvaluator.direction( - weights: samples, - windowStart: windowStart, - referenceDate: reference - ) - - XCTAssertEqual(direction, .down) - } -} diff --git a/project.yml b/project.yml index 90cab9a..c2493c1 100644 --- a/project.yml +++ b/project.yml @@ -71,7 +71,6 @@ targets: - "Resources/Info.plist" - "Resources/LogWeightWatchWidget.entitlements" - "Resources/PrivacyInfo.xcprivacy" - - path: App/Shared/Widget/WeightTrendArrow.swift resources: - path: App/Shared/Resources - path: App/WatchWidget/Resources/PrivacyInfo.xcprivacy