diff --git a/LeCountdown/Model/Model+Extensions.swift b/LeCountdown/Model/Model+Extensions.swift index 8749fb5..d345508 100644 --- a/LeCountdown/Model/Model+Extensions.swift +++ b/LeCountdown/Model/Model+Extensions.swift @@ -67,6 +67,8 @@ extension Stopwatch { extension Record { + var count: Double { return 1.0 } + public override func didChangeValue(forKey key: String) { super.didChangeValue(forKey: key) @@ -98,7 +100,19 @@ extension Record { return "no details" } } - + + func point(stat: Stat) -> Point? { + if let start { + switch stat { + case .count: + return Point(date: start, value: NSNumber(value: 1)) + case .totalDuration, .averageDuration: + return Point(date: start, value: NSNumber(value: self.duration)) + } + } + return nil + } + } extension Activity { diff --git a/LeCountdown/Stats/Context+Calculations.swift b/LeCountdown/Stats/Context+Calculations.swift index e6151d3..eefe617 100644 --- a/LeCountdown/Stats/Context+Calculations.swift +++ b/LeCountdown/Stats/Context+Calculations.swift @@ -36,7 +36,10 @@ extension NSManagedObjectContext { let fetchRequest = NSFetchRequest(entityName: "Record") + let basePredicate = NSPredicate(format: "start != nil") + var predicates: [NSPredicate] = [] + predicates.append(basePredicate) predicates.append(NSPredicate(format: "activity = %@", activity)) if let filter { predicates.append(filter.predicate) @@ -74,7 +77,14 @@ extension NSManagedObjectContext { } if let value { - let sv = StatValue(stat: stat, value: value) + let request = Record.fetchRequest() + if let filter { + request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [basePredicate, filter.predicate]) + } + let records: [Record] = try self.fetch(request) + let points = records.compactMap { $0.point(stat:stat) } + + let sv = StatValue(stat: stat, value: value, records: points) statValues.append(sv) } } diff --git a/LeCountdown/Stats/Stat.swift b/LeCountdown/Stats/Stat.swift index 742e74e..d8ece52 100644 --- a/LeCountdown/Stats/Stat.swift +++ b/LeCountdown/Stats/Stat.swift @@ -55,6 +55,22 @@ enum Stat: Int, CaseIterable { return .doubleAttributeType } } + +// var keyPath: KeyPath { +// switch self { +// case .count: return \Record.count +// case .averageDuration, .totalDuration: return \Record.duration +// } +// } + +} + +struct Point: Identifiable { + var date: Date + var value: NSNumber + + var id: Date { date } + } struct StatValue: Identifiable { @@ -63,6 +79,7 @@ struct StatValue: Identifiable { var stat: Stat var value: NSDecimalNumber + var records: [Point] = [] var formattedValue: String { diff --git a/LeCountdown/Views/Stats/ActivityView.swift b/LeCountdown/Views/Stats/ActivityView.swift index aba267b..202fa8e 100644 --- a/LeCountdown/Views/Stats/ActivityView.swift +++ b/LeCountdown/Views/Stats/ActivityView.swift @@ -43,11 +43,9 @@ struct ActivityView: View { StatsView(activity: self.activity) .environment(\.managedObjectContext, viewContext) - .background(.red) - .foregroundColor(.white) RecordsView(activity: self.activity) .environment(\.managedObjectContext, viewContext) - .frame(maxWidth: .infinity) +// .frame(maxWidth: .infinity) } } diff --git a/LeCountdown/Views/Stats/StatsView.swift b/LeCountdown/Views/Stats/StatsView.swift index 57b4c1f..594438c 100644 --- a/LeCountdown/Views/Stats/StatsView.swift +++ b/LeCountdown/Views/Stats/StatsView.swift @@ -7,6 +7,7 @@ import SwiftUI import CoreData +import Charts class StatModel: ObservableObject { @@ -59,11 +60,11 @@ struct StatsView: View { } else { VStack(alignment: .leading) { ForEach(self.model.statValues) { statValue in - StatView(name: statValue.stat.localizedName, value: statValue.formattedValue).padding(.vertical) + StatGraphView(statValue: statValue) +// .padding(.vertical) } } } - Spacer() } .onAppear() { self.model.compute(activity: self.activity, filter: self.filter) @@ -75,13 +76,43 @@ struct StatsView: View { struct StatView: View { - var name: String - var value: String + var statValue: StatValue var body: some View { VStack(alignment: .leading) { - Text(self.name.uppercased()).font(.footnote) - Text(self.value).font(.system(.title, weight: .bold)) + Text(self.statValue.stat.localizedName.uppercased()) + .font(.footnote) + Text(self.statValue.formattedValue) + .font(.system(.title, weight: .bold)) + } + } + +} + +struct StatGraphView: View { + + var statValue: StatValue + + var body: some View { + VStack { + HStack { + Text(self.statValue.stat.localizedName.uppercased()) +// .font(.footnote) + Text(self.statValue.formattedValue) +// .font(.system(.title, weight: .bold)) + } + Chart(self.statValue.records) { point in + + let stat: Stat = self.statValue.stat + switch stat { + case .count, .totalDuration: + BarMark(x: .value("name", point.date, unit: .month), + y: .value("value", point.value.doubleValue)) + default: + LineMark(x: .value("name", point.date, unit: .month), + y: .value("value", point.value.doubleValue)) + } + } } } @@ -89,7 +120,7 @@ struct StatView: View { struct StatView_Previews: PreviewProvider { static var previews: some View { - StatView(name: "Duration", value: "3") + StatView(statValue: StatValue(stat: .count, value: NSDecimalNumber(integerLiteral: 3))) } }