Files
HealthImport/HealthImport/Tabs/WorkoutTab.swift
2024-04-15 15:55:10 +02:00

151 lines
5.0 KiB
Swift

import SwiftUI
import HealthKit
import HealthDB
import SFSafeSymbols
struct WorkoutTab: View {
@EnvironmentObject
var database: Database
@State var navigationPath: NavigationPath = .init()
@State var filteredActivityType: HKWorkoutActivityType? = nil
@State var workouts: [(title: String, workouts: [Workout])] = []
@State var workoutTypeCounts: [(type: HKWorkoutActivityType, count: Int)] = []
var body: some View {
NavigationStack(path: $navigationPath) {
VStack {
List {
WorkoutTypeSelection(selected: $filteredActivityType, available: $workoutTypeCounts)
.listRowSeparator(.hidden)
ForEach(workouts, id: \.title) { month in
Section {
ForEach(month.workouts) { workout in
WorkoutListRow(workout: workout)
.overlay(
NavigationLink(value: workout) { }
.opacity(0))
.listRowSeparator(.hidden)
.listRowInsets(.init(top: 0, leading: 16, bottom: 5, trailing: 16))
}
} header: {
Text(month.title)
.font(.title3)
.fontWeight(.bold)
.foregroundStyle(.primary)
} footer: {
Text("")
}
}
}
.listStyle(.plain)
}
.navigationTitle("Workouts")
.navigationDestination(for: Workout.self) {
WorkoutDetailView(workout: $0)
}
.refreshable {
reloadAsync()
}
.onChange(of: database.file, reload)
.onChange(of: filteredActivityType, reload)
.onAppear(perform: {
if workouts.isEmpty {
reload()
}
})
}
.preferredColorScheme(.dark)
}
private func reload() {
Task {
reloadAsync()
}
}
private func reloadAsync() {
guard let store = database.store else {
DispatchQueue.main.async {
self.workouts = []
self.workoutTypeCounts = []
}
return
}
loadWorkoutTypes(in: store)
loadWorkouts(in: store)
}
private func loadWorkouts(in store: HealthDatabase) {
do {
let workouts: [Workout]
if let filteredActivityType {
workouts = try store.workouts(type: filteredActivityType)
} else {
workouts = try store.workouts()
}
print("Loaded \(workouts.count) workouts")
let calendar = Calendar.current
var sortedIntoMonths = [(title: String, workouts: [Workout])]()
var currentMonth: String? = nil
var currentWorkouts = [Workout]()
for workout in workouts.sorted(ascending: false, using: { $0.endDate }) {
let date = workout.endDate
let month = calendar.component(.month, from: date)
let year = calendar.component(.year, from: date)
let title = "\(calendar.monthSymbols[month-1]) \(year)"
guard let lastMonth = currentMonth else {
currentMonth = title
currentWorkouts = [workout]
continue
}
guard lastMonth == title else {
sortedIntoMonths.append((lastMonth, currentWorkouts))
currentMonth = title
currentWorkouts = [workout]
continue
}
currentWorkouts.append(workout)
}
if let currentMonth, !currentWorkouts.isEmpty {
sortedIntoMonths.append((currentMonth, currentWorkouts))
}
DispatchQueue.main.async {
self.workouts = sortedIntoMonths
}
} catch {
print("Failed to load workouts: \(error)")
DispatchQueue.main.async {
self.workouts = []
}
}
}
private func loadWorkoutTypes(in store: HealthDatabase) {
do {
let types = try store.store.workoutTypeFrequencies()
.sorted(ascending: false) { $0.value }
.map { (type: $0.key, count: $0.value) }
DispatchQueue.main.async {
self.workoutTypeCounts = types
}
} catch {
print("Failed to get workout frequencies: \(error)")
DispatchQueue.main.async {
self.workoutTypeCounts = []
}
}
}
}
#Preview {
WorkoutTab()
.environmentObject(Database.empty)
}