From e3839be022998a5d5da878da75204da9f929ceac Mon Sep 17 00:00:00 2001 From: Laurent Date: Mon, 27 Feb 2023 17:15:38 +0100 Subject: [PATCH] stat + ipad navigation --- LeCountdown/Stats/Context+Calculations.swift | 9 ++--- LeCountdown/Stats/Filter.swift | 8 +--- LeCountdown/Stats/Stat.swift | 42 ++++++++++++++------ LeCountdown/Utils/Date+Extensions.swift | 17 ++++++++ LeCountdown/Views/ContentView.swift | 2 +- LeCountdown/Views/HomeView.swift | 10 ++--- LeCountdown/Views/PresetsView.swift | 20 +++++----- LeCountdown/Views/Stats/ActivityView.swift | 23 +++++------ LeCountdown/Views/Stats/RecordsView.swift | 2 +- LeCountdown/Views/Stats/StatsView.swift | 42 +++++++++++++------- 10 files changed, 107 insertions(+), 68 deletions(-) diff --git a/LeCountdown/Stats/Context+Calculations.swift b/LeCountdown/Stats/Context+Calculations.swift index bdfcf7a..3752b37 100644 --- a/LeCountdown/Stats/Context+Calculations.swift +++ b/LeCountdown/Stats/Context+Calculations.swift @@ -45,7 +45,6 @@ extension NSManagedObjectContext { predicates.append(filter.predicate) } fetchRequest.predicate = NSCompoundPredicate.init(andPredicateWithSubpredicates: predicates) - fetchRequest.propertiesToFetch = expressions fetchRequest.resultType = NSFetchRequestResultType.dictionaryResultType @@ -83,9 +82,10 @@ extension NSManagedObjectContext { request.sortDescriptors = [NSSortDescriptor(key: "start", ascending: true)] } let records: [Record] = try self.fetch(request) + let points: [Point] = records.compactMap { $0.point(stat:stat) } - let sv = StatValue(stat: stat, value: value, records: points) + let sv = StatValue(stat: stat, value: value, points: points) statValues.append(sv) } } @@ -99,12 +99,11 @@ fileprivate extension Record { func point(stat: Stat) -> Point? { if let start { - let day = start.startOfDay switch stat { case .count: - return Point(date: day, value: NSNumber(value: 1)) + return Point(date: start, value: NSNumber(value: 1)) case .totalDuration, .averageDuration: - return Point(date: day, value: NSNumber(value: self.duration / 3600.0)) + return Point(date: start, value: NSNumber(value: self.duration / 3600.0)) } } return nil diff --git a/LeCountdown/Stats/Filter.swift b/LeCountdown/Stats/Filter.swift index 849cc5b..1c9ddcb 100644 --- a/LeCountdown/Stats/Filter.swift +++ b/LeCountdown/Stats/Filter.swift @@ -76,16 +76,10 @@ struct Month { return NSDate() } - fileprivate static let _dateFormatter = { - let df = DateFormatter() - df.dateFormat = "MMMM yyyy" - return df - }() - var localizedString: String { let components = DateComponents(year: self.year, month: self.month) if let date = Calendar.current.date(from: components) { - return Month._dateFormatter.string(from: date) + return Date.monthYearFormatter.string(from: date) } return "invalid date" } diff --git a/LeCountdown/Stats/Stat.swift b/LeCountdown/Stats/Stat.swift index 2e5ae93..07caea9 100644 --- a/LeCountdown/Stats/Stat.swift +++ b/LeCountdown/Stats/Stat.swift @@ -64,14 +64,7 @@ enum Stat: Int, CaseIterable { return nil } } - -// var keyPath: KeyPath { -// switch self { -// case .count: return \Record.count -// case .averageDuration, .totalDuration: return \Record.duration -// } -// } - + } struct Point: Identifiable { @@ -80,10 +73,14 @@ struct Point: Identifiable { var id: Date { date } - var dateValue: Date { - return Date(timeIntervalSince1970: value.doubleValue) - } +} + +struct ChartPoint: Identifiable { + var index: Int + var label: String + var value: NSNumber + var id: Int { index } } struct StatValue: Identifiable { @@ -92,7 +89,7 @@ struct StatValue: Identifiable { var stat: Stat var value: NSDecimalNumber - var records: [Point] = [] + var points: [Point] = [] var formattedValue: String { @@ -105,4 +102,25 @@ struct StatValue: Identifiable { } } + + func chartPoint(timeFrame: TimeFrame) -> [ChartPoint] { + + var chartPoints: [ChartPoint] = [] + + for (i, point) in self.points.enumerated() { + + let identifier: String + switch timeFrame { + case .all: identifier = point.date.formattedYear + case .year: identifier = point.date.formattedMonth + case .month: identifier = point.date.formattedDay + } + + let cp = ChartPoint(index: i, label: identifier, value: point.value) + chartPoints.append(cp) + } + + return chartPoints + } + } diff --git a/LeCountdown/Utils/Date+Extensions.swift b/LeCountdown/Utils/Date+Extensions.swift index 508bcf9..1dfe57d 100644 --- a/LeCountdown/Utils/Date+Extensions.swift +++ b/LeCountdown/Utils/Date+Extensions.swift @@ -9,6 +9,19 @@ import Foundation extension Date { + static let monthYearFormatter = { + let df = DateFormatter() + df.dateFormat = "MMMM yyyy" + return df + }() + + static let dayFormatter = { + let df = DateFormatter() + df.dateStyle = .short + df.timeStyle = .none + return df + }() + var startOfDay: Date { return Calendar.current.startOfDay(for: self) } @@ -20,4 +33,8 @@ extension Date { var year: Int { self.get(.year) } var month: Int { self.get(.month) } + var formattedYear: String { return "\(self.year)" } + var formattedMonth: String { return Date.monthYearFormatter.string(from: self) } + var formattedDay: String { return Date.dayFormatter.string(from: self) } + } diff --git a/LeCountdown/Views/ContentView.swift b/LeCountdown/Views/ContentView.swift index 745b69d..824e0fb 100644 --- a/LeCountdown/Views/ContentView.swift +++ b/LeCountdown/Views/ContentView.swift @@ -125,7 +125,7 @@ struct ContentView: View { // } // } // } - ToolbarItem(placement: .navigationBarLeading) { + ToolbarItem(placement: .navigationBarTrailing) { Button { withAnimation { self.isEditing.toggle() diff --git a/LeCountdown/Views/HomeView.swift b/LeCountdown/Views/HomeView.swift index b76f651..caaaee3 100644 --- a/LeCountdown/Views/HomeView.swift +++ b/LeCountdown/Views/HomeView.swift @@ -61,12 +61,12 @@ struct RegularHomeView: View { var body: some View { NavigationView { - TabView(selection: $tabSelection) { - PresetsView(tabSelection: $tabSelection) - .environment(\.managedObjectContext, viewContext) - .tabItem { Label("Presets", systemImage: "globe") } - .tag(0) + PresetsView(tabSelection: $tabSelection) + .environment(\.managedObjectContext, viewContext) + .tabItem { Label("Presets", systemImage: "globe") } + .tag(0) + TabView(selection: $tabSelection) { ContentView() .environment(\.managedObjectContext, viewContext) .environmentObject(Conductor.maestro) diff --git a/LeCountdown/Views/PresetsView.swift b/LeCountdown/Views/PresetsView.swift index 14a806e..d185377 100644 --- a/LeCountdown/Views/PresetsView.swift +++ b/LeCountdown/Views/PresetsView.swift @@ -155,15 +155,17 @@ struct PresetsView: View { var tabSelection: Binding fileprivate func _columnCount() -> Int { - #if os(iOS) - if UIDevice.isPhoneIdiom { - return 2 - } else { - return 3 - } - #else - return 3 - #endif + return 2 + +// #if os(iOS) +// if UIDevice.isPhoneIdiom { +// return 2 +// } else { +// return 3 +// } +// #else +// return 3 +// #endif } fileprivate func _columns() -> [GridItem] { diff --git a/LeCountdown/Views/Stats/ActivityView.swift b/LeCountdown/Views/Stats/ActivityView.swift index e5371b0..a09a532 100644 --- a/LeCountdown/Views/Stats/ActivityView.swift +++ b/LeCountdown/Views/Stats/ActivityView.swift @@ -27,22 +27,17 @@ struct ActivityView: View { .pickerStyle(.segmented) .padding(.horizontal) -// List { + List { -// Section { -// StatsView(activity: self.activity, -// timeFrame: self.selectedTimeFrame) -// .environment(\.managedObjectContext, viewContext) -// } + StatsView(activity: self.activity, + timeFrame: self.selectedTimeFrame) + .environment(\.managedObjectContext, viewContext) -// Section { - - let filters = self.selectedTimeFrame.filters(context: viewContext) - - RecordsView(activity: self.activity, filters: filters) - .environment(\.managedObjectContext, viewContext) -// } -// } + let filters = self.selectedTimeFrame.filters(context: viewContext) + RecordsView(activity: self.activity, filters: filters) + .environment(\.managedObjectContext, viewContext) + // } + } } } diff --git a/LeCountdown/Views/Stats/RecordsView.swift b/LeCountdown/Views/Stats/RecordsView.swift index f0be1ed..b4b00d4 100644 --- a/LeCountdown/Views/Stats/RecordsView.swift +++ b/LeCountdown/Views/Stats/RecordsView.swift @@ -43,7 +43,7 @@ struct RecordsView: View { var body: some View { - List { + Section { ForEach(self.filters) { filter in RecordsSectionView(activity: self.activity, filter: filter) .environment(\.managedObjectContext, viewContext) diff --git a/LeCountdown/Views/Stats/StatsView.swift b/LeCountdown/Views/Stats/StatsView.swift index 774cb2f..632074a 100644 --- a/LeCountdown/Views/Stats/StatsView.swift +++ b/LeCountdown/Views/Stats/StatsView.swift @@ -53,16 +53,18 @@ struct StatsView: View { var body: some View { - VStack() { - - if self.model.isComputing { - ProgressView() - .progressViewStyle(CircularProgressViewStyle()) - } else { - VStack(alignment: .leading) { - ForEach(self.model.statValues) { statValue in - StatGraphView(statValue: statValue) -// .padding(.vertical) + Section { + VStack() { + + if self.model.isComputing { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + } else { + VStack(alignment: .leading) { + ForEach(self.model.statValues) { statValue in + StatGraphView(statValue: statValue, timeFrame: self.timeFrame) + // .padding(.vertical) + } } } } @@ -93,6 +95,7 @@ struct StatView: View { struct StatGraphView: View { var statValue: StatValue + var timeFrame: TimeFrame var body: some View { VStack { @@ -102,24 +105,35 @@ struct StatGraphView: View { Text(self.statValue.formattedValue) // .font(.system(.title, weight: .bold)) } - Chart(self.statValue.records) { point in + Chart(self.statValue.chartPoint(timeFrame: self.timeFrame)) { point in let stat: Stat = self.statValue.stat switch stat { case .count, .totalDuration: - BarMark(x: .value("date", point.date, unit: .day), + BarMark(x: .value("date", point.index), y: .value("value", point.value.doubleValue)) default: - LineMark(x: .value("date", point.date, unit: .day), + LineMark(x: .value("date", point.index), y: .value("value", point.value.doubleValue)) } - }.frame(height: 300.0) + }.frame(height: 200.0) } } } +struct StatGraphView_Previews: PreviewProvider { + + static let points: [Point] = [Point(date: Date(), value: 1), + Point(date: Date(), value: 1), + Point(date: Date(timeIntervalSince1970: 100000), value: 1)] + + static var previews: some View { + StatGraphView(statValue: StatValue(stat: .count, value: NSDecimalNumber(integerLiteral: 3), points: points), timeFrame: .all) + } +} + struct StatView_Previews: PreviewProvider { static var previews: some View { StatView(statValue: StatValue(stat: .count, value: NSDecimalNumber(integerLiteral: 3)))