diff --git a/TODO/WorkoutPlusWatch/Assets.xcassets/AccentColor.colorset/Contents.json b/TODO/WorkoutPlusWatch/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/TODO/WorkoutPlusWatch/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TODO/WorkoutPlusWatch/Assets.xcassets/AppIcon.appiconset/Contents.json b/TODO/WorkoutPlusWatch/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..49c81cd --- /dev/null +++ b/TODO/WorkoutPlusWatch/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TODO/WorkoutPlusWatch/Assets.xcassets/Contents.json b/TODO/WorkoutPlusWatch/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/TODO/WorkoutPlusWatch/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TODO/WorkoutPlusWatch/Views/ControlsView.swift b/TODO/WorkoutPlusWatch/Views/ControlsView.swift new file mode 100644 index 0000000..8f10fae --- /dev/null +++ b/TODO/WorkoutPlusWatch/Views/ControlsView.swift @@ -0,0 +1,30 @@ +import SwiftUI + +struct ControlsView: View { + var body: some View { + HStack { + VStack { + Button { + } label: { + Image(systemName: "xmark") + } + .tint(Color.red) + .font(.title2) + Text("End") + } + VStack { + Button { + } label: { + Image(systemName: "pause") + } + .tint(Color.yellow) + .font(.title2) + Text("Pause") + } + } + } +} + +#Preview { + ControlsView() +} diff --git a/TODO/WorkoutPlusWatch/Views/ElapsedTimeView.swift b/TODO/WorkoutPlusWatch/Views/ElapsedTimeView.swift new file mode 100644 index 0000000..61c81c8 --- /dev/null +++ b/TODO/WorkoutPlusWatch/Views/ElapsedTimeView.swift @@ -0,0 +1,47 @@ +import SwiftUI + +struct ElapsedTimeView: View { + var elapsedTime: TimeInterval = 0 + var showSubseconds: Bool = true + @State private var timeFormatter = ElapsedTimeFormatter() + + var body: some View { + Text(NSNumber(value: elapsedTime), formatter: timeFormatter) + .fontWeight(.semibold) + .onChange(of: showSubseconds) { _, newValue in + timeFormatter.showSubseconds = newValue + } + } +} + +class ElapsedTimeFormatter: Formatter { + let componentsFormatter: DateComponentsFormatter = { + let formatter = DateComponentsFormatter() + formatter.allowedUnits = [.minute, .second] + formatter.zeroFormattingBehavior = .pad + return formatter + }() + var showSubseconds = true + + override func string(for value: Any?) -> String? { + guard let time = value as? TimeInterval else { + return nil + } + + guard let formattedString = componentsFormatter.string(from: time) else { + return nil + } + + if showSubseconds { + let hundredths = Int((time.truncatingRemainder(dividingBy: 1)) * 100) + let decimalSeparator = Locale.current.decimalSeparator ?? "." + return String(format: "%@%@%0.2d", formattedString, decimalSeparator, hundredths) + } + + return formattedString + } +} + +#Preview { + ElapsedTimeView() +} diff --git a/TODO/WorkoutPlusWatch/Views/MetricsView.swift b/TODO/WorkoutPlusWatch/Views/MetricsView.swift new file mode 100644 index 0000000..f7fc307 --- /dev/null +++ b/TODO/WorkoutPlusWatch/Views/MetricsView.swift @@ -0,0 +1,37 @@ +import SwiftUI + +struct MetricsView: View { + var body: some View { + VStack(alignment: .leading) { + ElapsedTimeView() + Text( + Measurement( + value: 47, + unit: UnitEnergy.kilocalories) + .formatted( + .measurement( + width: .abbreviated, + usage: .workout, + numberFormatStyle: .number))) + Text(153.formatted(.number.precision(.fractionLength(0))) + " bpm") + Text( + Measurement( + value: 515, + unit: UnitLength.meters) + .formatted( + .measurement( + width: .abbreviated, + usage: .road))) + } + .font(.system(.title, design: .rounded) + .monospacedDigit() + .lowercaseSmallCaps()) + .frame(maxWidth: .infinity, alignment: .leading) + .ignoresSafeArea(edges: .bottom) + .scenePadding() + } +} + +#Preview { + MetricsView() +} diff --git a/TODO/WorkoutPlusWatch/Views/SessionPagingView.swift b/TODO/WorkoutPlusWatch/Views/SessionPagingView.swift new file mode 100644 index 0000000..2736419 --- /dev/null +++ b/TODO/WorkoutPlusWatch/Views/SessionPagingView.swift @@ -0,0 +1,22 @@ +import SwiftUI +import WatchKit + +struct SessionPagingView: View { + @State private var selection: Tab = .metrics + + enum Tab { + case controls, metrics, nowPlaying + } + + var body: some View { + TabView(selection: $selection) { + ControlsView().tag(Tab.controls) + MetricsView().tag(Tab.metrics) + NowPlayingView().tag(Tab.nowPlaying) + } + } +} + +#Preview { + SessionPagingView() +} diff --git a/TODO/WorkoutPlusWatch/Views/StartView.swift b/TODO/WorkoutPlusWatch/Views/StartView.swift new file mode 100644 index 0000000..38def58 --- /dev/null +++ b/TODO/WorkoutPlusWatch/Views/StartView.swift @@ -0,0 +1,57 @@ +import SwiftUI +import HealthKit + +struct StartView: View { + var workoutTypes: [HKWorkoutActivityType] = [ + .running, + .traditionalStrengthTraining, + .functionalStrengthTraining, + .yoga, + .cycling, + ] + + var body: some View { + NavigationStack { + List(workoutTypes) { workoutType in + NavigationLink { + SessionPagingView() + } label: { + Label(workoutType.name, systemImage: workoutType.iconName) + } + .padding(EdgeInsets(top: 15, leading: 5, bottom: 15, trailing: 5)) + } + .listStyle(.carousel) + .navigationBarTitle("Workout+") + } + } +} + +extension HKWorkoutActivityType: @retroactive Identifiable { + public var id: UInt { rawValue } + + var name: String { + switch self { + case .running: return "Run" + case .traditionalStrengthTraining: return "Strength" + case .functionalStrengthTraining: return "Functional" + case .yoga: return "Yoga" + case .cycling: return "Cycling" + default: return "Workout" + } + } + + var iconName: String { + switch self { + case .running: return "figure.run" + case .traditionalStrengthTraining: return "figure.strengthtraining.traditional" + case .functionalStrengthTraining: return "figure.strengthtraining.functional" + case .yoga: return "figure.yoga" + case .cycling: return "figure.outdoor.cycle" + default: return "figure.mixed.cardio" + } + } +} + +#Preview { + StartView() +} diff --git a/TODO/WorkoutPlusWatch/WorkoutPlusWatchApp.swift b/TODO/WorkoutPlusWatch/WorkoutPlusWatchApp.swift new file mode 100644 index 0000000..66741fb --- /dev/null +++ b/TODO/WorkoutPlusWatch/WorkoutPlusWatchApp.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct WorkoutPlusWatchApp: App { + var body: some Scene { + WindowGroup { + StartView() + } + } +}