stat + ipad navigation

release
Laurent 3 years ago
parent d85761a82f
commit e3839be022
  1. 9
      LeCountdown/Stats/Context+Calculations.swift
  2. 8
      LeCountdown/Stats/Filter.swift
  3. 42
      LeCountdown/Stats/Stat.swift
  4. 17
      LeCountdown/Utils/Date+Extensions.swift
  5. 2
      LeCountdown/Views/ContentView.swift
  6. 10
      LeCountdown/Views/HomeView.swift
  7. 20
      LeCountdown/Views/PresetsView.swift
  8. 23
      LeCountdown/Views/Stats/ActivityView.swift
  9. 2
      LeCountdown/Views/Stats/RecordsView.swift
  10. 42
      LeCountdown/Views/Stats/StatsView.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

@ -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"
}

@ -64,14 +64,7 @@ enum Stat: Int, CaseIterable {
return nil
}
}
// var keyPath: KeyPath<Record, NSNumber> {
// 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
}
}

@ -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) }
}

@ -125,7 +125,7 @@ struct ContentView<T : AbstractTimer>: View {
// }
// }
// }
ToolbarItem(placement: .navigationBarLeading) {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
withAnimation {
self.isEditing.toggle()

@ -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<AbstractTimer>()
.environment(\.managedObjectContext, viewContext)
.environmentObject(Conductor.maestro)

@ -155,15 +155,17 @@ struct PresetsView: View {
var tabSelection: Binding<Int>
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] {

@ -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)
// }
}
}
}

@ -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)

@ -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)))

Loading…
Cancel
Save