diff --git a/WorkoutsPlus/ContentView.swift b/WorkoutsPlus/ContentView.swift index 00713dc..b6438bd 100644 --- a/WorkoutsPlus/ContentView.swift +++ b/WorkoutsPlus/ContentView.swift @@ -13,10 +13,82 @@ struct ContentView: View { var body: some View { TabView { - Text("Log") + NavigationView() { + List() { + NavigationLink(destination: Text("WorkoutLogDetails")) { + HStack(alignment: .top) { + Image(systemName: "figure.run") + .padding(.trailing) + .padding(.top) + VStack(alignment: .leading) { + HStack { + Text("23.01.1988") + Text("14:37 Uhr") + Spacer() + } + Text("Marathon") + .fontWeight(.semibold) + HStack { + Text("34 km/42 km") + Text("•") + Text("5:12 min/km") + }.foregroundStyle(.gray) + HStack { + Text("1 h") + Text("•") + Text("1.337 kcal") + } + .foregroundStyle(.gray) + } + } + } + NavigationLink(destination: Text("WorkoutLogDetails")) { + HStack { + Image(systemName: "figure.run") + .padding(.trailing) + VStack(alignment: .leading) { + HStack { + Text("23.01.1988") + Text("14:37 Uhr") + Spacer() + + } + Text("Recommended Routine") + .fontWeight(.semibold) + HStack { + Text("6/12 sets") + Text("•") + Text("50 %") + }.foregroundStyle(.gray) + HStack { + Text("57 m") + Text("•") + Text("357 kcal") + } + .foregroundStyle(.gray) + } + } + } + } + .listStyle(.plain) + .navigationBarTitle("Workout Logs") + .toolbar { + ToolbarItem(placement: .topBarLeading) { + EditButton() + } + ToolbarItem(placement: .topBarLeading) { + + } + } + } + .tabItem { + Image(systemName: "pencil.and.list.clipboard") + Text("Log") + } + Text("Training Plans") .tabItem { - Image(systemName: "pencil.and.list.clipboard") - Text("Log") + Image(systemName: "calendar.badge.clock") + Text("Plans & Goals") } WorkoutLibrary() .tabItem { diff --git a/WorkoutsPlus/Exercise/ExerciseLibrary.swift b/WorkoutsPlus/Exercise/ExerciseLibrary.swift index bffd1bd..13b4eba 100644 --- a/WorkoutsPlus/Exercise/ExerciseLibrary.swift +++ b/WorkoutsPlus/Exercise/ExerciseLibrary.swift @@ -12,12 +12,16 @@ struct ExerciseLibrary: View { @Environment(\.modelContext) private var modelContext @Query(sort: \ExerciseTemplate.name) private var exerciseTemplates: [ExerciseTemplate] - @State private var newExercise: ExerciseTemplate? + @State private var newExerciseTemplate: ExerciseTemplate = ExerciseTemplate("") + @State private var newExerciseName: String = "" + @State private var isAddingExerciseTemplate: Bool = false + + @FocusState private var isInputFieldFocused: Bool // TODO: Add search bar to the top var body: some View { - NavigationSplitView { + NavigationView { Group { if !exerciseTemplates.isEmpty { List { @@ -29,6 +33,27 @@ struct ExerciseLibrary: View { } } .onDelete(perform: deleteExercise) + if isAddingExerciseTemplate { + TextField("New Exercise", text: $newExerciseName, onCommit: { + newExerciseTemplate.name = newExerciseName + saveExercise(exerciseTemplate: newExerciseTemplate) + isAddingExerciseTemplate = false + }) + .focused($isInputFieldFocused) + } + Button(action: { + withAnimation { + newExerciseTemplate = ExerciseTemplate("") + newExerciseName = "" + isAddingExerciseTemplate = true + isInputFieldFocused = true + } + }) { + HStack { + Image(systemName: "plus.circle.fill") + Text("Add Item") + } + } } } else { ContentUnavailableView { @@ -38,35 +63,10 @@ struct ExerciseLibrary: View { } .navigationBarTitle("Exercises") .toolbar { - ToolbarItem(placement: .topBarLeading) { + ToolbarItem { EditButton() } - ToolbarItem { - Button(action: addExercise) { - Label("Add Exercise", systemImage: "plus") - } - } } - .sheet(item: $newExercise) { exercise in - NavigationStack { - AddExercise(exerciseTemplate: exercise) - } - // TODO: It's possible to add a boolean here ("Terms accepted y/n"). Maybe add this for empty string - .presentationDetents([.medium]) - .interactiveDismissDisabled() - } - } detail: { - // TODO: What does this Detail do? - Text("Select a workout") - .navigationTitle("Movie") - } - } - - private func addExercise() { - withAnimation { - let item = ExerciseTemplate("") - modelContext.insert(item) - newExercise = item } } diff --git a/WorkoutsPlus/Exercise/ExerciseTemplate.swift b/WorkoutsPlus/Exercise/ExerciseTemplate.swift index 88ea5e3..93932ab 100644 --- a/WorkoutsPlus/Exercise/ExerciseTemplate.swift +++ b/WorkoutsPlus/Exercise/ExerciseTemplate.swift @@ -11,7 +11,8 @@ import SwiftData @Model final class ExerciseTemplate { static var systemImage = "figure.run" - var name: String + @Attribute(.unique) var name: String + var metric: String = "reps" // var exerciseDescription: ExerciseDescription? var timestamp: Date = Date.now diff --git a/WorkoutsPlus/HardcodedData.swift b/WorkoutsPlus/HardcodedData.swift new file mode 100644 index 0000000..2558324 --- /dev/null +++ b/WorkoutsPlus/HardcodedData.swift @@ -0,0 +1,89 @@ +// +// HardcodedData.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 26.08.24. +// + +import SwiftUI + +let systemColors: [Color] = [ + .red, .orange, .yellow, .green,.mint, .cyan, .blue, .indigo, .purple, .pink, .gray, .black +] + +let fitnessIcons = [ + "figure.roll", + "figure.roll.runningpace", + "figure.american.football", + "figure.archery", + "figure.australian.football", + "figure.badminton", + "figure.barre", + "figure.baseball", + "figure.basketball", + "figure.bowling", + "figure.boxing", + "figure.climbing", + "figure.cooldown", + "figure.core.training", + "figure.cricket", + "figure.skiing.crosscountry", + "figure.cross.training", + "figure.curling", + "figure.dance", + "figure.disc.sports", + "figure.skiing.downhill", + "figure.elliptical", + "figure.equestrian.sports", + "figure.fencing", + "figure.fishing", + "figure.flexibility", + "figure.strengthtraining.functional", + "figure.golf", + "figure.gymnastics", + "figure.hand.cycling", + "figure.handball", + "figure.highintensity.intervaltraining", + "figure.hiking", + "figure.hockey", + "figure.hunting", + "figure.indoor.cycle", + "figure.jumprope", + "figure.kickboxing", + "figure.lacrosse", + "figure.martial.arts", + "figure.mind.and.body", + "figure.mixed.cardio", + "figure.open.water.swim", + "figure.outdoor.cycle", + "oar.2.crossed", + "figure.pickleball", + "figure.pilates", + "figure.play", + "figure.pool.swim", + "figure.racquetball", + "figure.rolling", + "figure.rower", + "figure.rugby", + "figure.sailing", + "figure.skating", + "figure.snowboarding", + "figure.soccer", + "figure.socialdance", + "figure.softball", + "figure.squash", + "figure.stair.stepper", + "figure.stairs", + "figure.step.training", + "figure.surfing", + "figure.table.tennis", + "figure.taichi", + "figure.tennis", + "figure.track.and.field", + "figure.strengthtraining.traditional", + "figure.volleyball", + "figure.water.fitness", + "figure.waterpolo", + "figure.wrestling", + "figure.yoga" +] diff --git a/WorkoutsPlus/Workout/AddExerciseToWorkout.swift b/WorkoutsPlus/Workout/AddExerciseToWorkout.swift index dd62cca..e1417a3 100644 --- a/WorkoutsPlus/Workout/AddExerciseToWorkout.swift +++ b/WorkoutsPlus/Workout/AddExerciseToWorkout.swift @@ -51,10 +51,10 @@ struct AddExerciseToWorkoutListItem: View { }) { HStack { Text(exerciseTemplate.name) - .foregroundColor(.black) + .foregroundStyle(.black) Spacer() Image(systemName: "plus.circle.fill") - .foregroundColor(.green) + .foregroundStyle(.green) } } } diff --git a/WorkoutsPlus/Workout/Workout.swift b/WorkoutsPlus/Workout/Workout.swift index 490ad73..b58d534 100644 --- a/WorkoutsPlus/Workout/Workout.swift +++ b/WorkoutsPlus/Workout/Workout.swift @@ -46,6 +46,7 @@ final class Workout { } static let sampleData: [Workout] = [ - Workout(name: "RR", exercises: Exercise.sampleData) + Workout(name: "Recommended Routine", exercises: Exercise.sampleData), + Workout(name: "Marathon Plan", exercises: Exercise.sampleData) ] } diff --git a/WorkoutsPlus/Workout/WorkoutDetail.swift b/WorkoutsPlus/Workout/WorkoutDetail.swift index 5a10461..7643600 100644 --- a/WorkoutsPlus/Workout/WorkoutDetail.swift +++ b/WorkoutsPlus/Workout/WorkoutDetail.swift @@ -38,7 +38,7 @@ struct WorkoutDetail: View { .navigationBarTitle("Edit \(workout.name)") .toolbar { - ToolbarItem { + ToolbarItem() { EditButton() } } @@ -83,7 +83,7 @@ struct WorkoutDetail: View { struct ExerciseListItem: View { var workout: Workout - var exercise: Exercise + @State var exercise: Exercise init(_ workout: Workout, _ exercise: Exercise ) { self.workout = workout @@ -92,18 +92,30 @@ struct ExerciseListItem: View { var body: some View { Button(action: { -// workout.addExercise(from: exercise) + // workout.addExercise(from: exercise) }) { HStack { + +// TextField("Enter Reps", text: $exercise.reps) +// .keyboardType(.numberPad) // Set the keyboard to number pad +// .padding() +// .frame(width: 100, height: 50) +// .background(Color(.systemGray6)) +// .cornerRadius(8) +// .multilineTextAlignment(.center) // Center the text +// .overlay( +// RoundedRectangle(cornerRadius: 8) +// .stroke(Color.gray, lineWidth: 1) +// ) Text(String(workout.exercises.filter { $0 == exercise }.count)) .font(.system(size: 14, weight: .bold)) - .foregroundColor(.white) + .foregroundStyle(.white) .frame(width: 20, height: 10) .padding(8) .background(Color.blue) .clipShape(RoundedRectangle(cornerRadius: 8)) Text(exercise.name) - .foregroundColor(.black) + .foregroundStyle(.black) Spacer() Image(systemName: "info.circle") } @@ -114,6 +126,6 @@ struct ExerciseListItem: View { #Preview { NavigationStack { WorkoutDetail(workout: Workout.sampleData.first!) - .modelContainer(SampleData.shared.modelContainer) + .modelContainer(SampleData.shared.modelContainer) } } diff --git a/WorkoutsPlus/Workout/WorkoutIconSelector.swift b/WorkoutsPlus/Workout/WorkoutIconSelector.swift new file mode 100644 index 0000000..b0b60fd --- /dev/null +++ b/WorkoutsPlus/Workout/WorkoutIconSelector.swift @@ -0,0 +1,67 @@ +// +// WorkoutIconSelector.swift +// WorkoutsPlus +// +// Created by Felix Förtsch on 26.08.24. +// + +import SwiftUI + +struct WorkoutIconSelector: View { + + @State private var selectedColor: Color = .black + @State private var selectedIcon: String? + @State private var searchText: String = "" + + var filteredIcons: [String] { + if searchText.isEmpty { + return fitnessIcons + } else { + return fitnessIcons.filter { $0.contains(searchText.lowercased()) } + } + } + + var body: some View { + ScrollView { + LazyVGrid(columns: [GridItem(.adaptive(minimum: 50))]) { + ForEach(systemColors, id: \.self) { color in + Button(action: { + selectedColor = color + }) { + Circle() + .fill(color) + .frame(width: 40, height: 40) + .overlay( + Circle() + .stroke(Color.white, lineWidth: selectedColor == color ? 4 : 0) + ) + } + } + } + .padding() + LazyVGrid(columns: [GridItem(.adaptive(minimum: 50, maximum: 100))]) { + ForEach(filteredIcons, id: \.self) { iconName in + Button(action: { + selectedIcon = iconName + }) { + Image(systemName: iconName) + .foregroundStyle(selectedColor) + .padding() + .background() + .cornerRadius(8) + } + } + } + .padding() + .searchable(text: $searchText) + } + + .navigationTitle("Select a Workout Icon") + } +} + +#Preview { + NavigationView() { + WorkoutIconSelector() + } +} diff --git a/WorkoutsPlus/Workout/WorkoutLibrary.swift b/WorkoutsPlus/Workout/WorkoutLibrary.swift index efa0238..8113e06 100644 --- a/WorkoutsPlus/Workout/WorkoutLibrary.swift +++ b/WorkoutsPlus/Workout/WorkoutLibrary.swift @@ -15,7 +15,7 @@ struct WorkoutLibrary: View { @State private var newWorkout: Workout? var body: some View { - NavigationSplitView { + NavigationView { Group { if !workouts.isEmpty { List { @@ -27,6 +27,9 @@ struct WorkoutLibrary: View { } } .onDelete(perform: deleteWorkout) + Button(action: addWorkout) { + Label("Add Workout", systemImage: "plus") + } } } else { ContentUnavailableView { @@ -34,16 +37,11 @@ struct WorkoutLibrary: View { } } } - .navigationBarTitle("Workout Templates") + .navigationBarTitle("Workouts") .toolbar { - ToolbarItem(placement: .topBarLeading) { + ToolbarItem() { EditButton() } - ToolbarItem { - Button(action: addWorkout) { - Label("Add Workout", systemImage: "plus") - } - } } .sheet(item: $newWorkout) { workout in NavigationStack { @@ -52,10 +50,6 @@ struct WorkoutLibrary: View { .presentationDetents([.medium]) .interactiveDismissDisabled() } - } detail: { - // TODO: What does this Detail do? - Text("Select a workout") - .navigationTitle("Movie") } }