Stabilize iOS/watchOS/tvOS apps and add cross-platform audit remediation

This commit is contained in:
Trey t
2026-02-11 12:54:40 -06:00
parent e40275e694
commit acce712261
77 changed files with 2940 additions and 765 deletions

View File

@@ -32,6 +32,7 @@ struct ActionsView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.red)
.foregroundColor(.white)
.accessibilityLabel("Close workout")
if showAddToCalendar {
Button(action: {
@@ -44,6 +45,7 @@ struct ActionsView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.blue)
.foregroundColor(.white)
.accessibilityLabel("Plan workout")
}
Button(action: {
@@ -56,6 +58,7 @@ struct ActionsView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.green)
.foregroundColor(.white)
.accessibilityLabel("Start workout")
} else {
Button(action: {
showCompleteSheet.toggle()
@@ -67,6 +70,7 @@ struct ActionsView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.blue)
.foregroundColor(.white)
.accessibilityLabel("Complete workout")
Button(action: {
AudioEngine.shared.playFinished()
@@ -84,6 +88,7 @@ struct ActionsView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(bridgeModule.isPaused ? .mint : .yellow)
.foregroundColor(.white)
.accessibilityLabel(bridgeModule.isPaused ? "Resume workout" : "Pause workout")
Button(action: {
AudioEngine.shared.playFinished()
@@ -96,6 +101,7 @@ struct ActionsView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.green)
.foregroundColor(.white)
.accessibilityLabel("Next exercise")
}
}
.alert("Complete Workout", isPresented: $showCompleteSheet) {

View File

@@ -7,7 +7,7 @@
import SwiftUI
struct AddSupersetView: View {
@Binding var createWorkoutSuperSet: CreateWorkoutSuperSet
@ObservedObject var createWorkoutSuperSet: CreateWorkoutSuperSet
var viewModel: WorkoutViewModel
@Binding var selectedCreateWorkoutSuperSet: CreateWorkoutSuperSet?
@@ -18,8 +18,9 @@ struct AddSupersetView: View {
Text(createWorkoutExercise.exercise.name)
.font(.title2)
.frame(maxWidth: .infinity)
if createWorkoutExercise.exercise.side != nil && createWorkoutExercise.exercise.side!.count > 0 {
Text(createWorkoutExercise.exercise.side!)
if let side = createWorkoutExercise.exercise.side,
side.isEmpty == false {
Text(side)
.font(.title3)
.frame(maxWidth: .infinity, alignment: .center)
}

View File

@@ -14,12 +14,13 @@ struct AllExerciseView: View {
@Binding var filteredExercises: [Exercise]
var selectedExercise: ((Exercise) -> Void)
@State var avPlayer = AVPlayer(url: URL(string: "https://dev.werkout.fitness/media/exercise_videos/2_Dumbbell_Lateral_Lunges.mp4")!)
@State var avPlayer = AVPlayer(url: URL(string: "https://dev.werkout.fitness/media/exercise_videos/2_Dumbbell_Lateral_Lunges.mp4") ?? URL(fileURLWithPath: "/dev/null"))
@State private var currentVideoURL: URL?
@State var videoExercise: Exercise? {
didSet {
if let viddd = self.videoExercise?.videoURL,
let url = URL(string: BaseURLs.currentBaseURL + viddd) {
self.avPlayer = AVPlayer(url: url)
updatePlayer(for: url)
}
}
}
@@ -38,19 +39,20 @@ struct AllExerciseView: View {
Text(exercise.name)
.frame(maxWidth: .infinity, alignment: .leading)
if exercise.side != nil && !exercise.side!.isEmpty {
Text(exercise.side!)
if let side = exercise.side,
side.isEmpty == false {
Text(side)
.font(.footnote)
.frame(maxWidth: .infinity, alignment: .leading)
}
if exercise.equipmentRequired != nil && !exercise.equipmentRequired!.isEmpty {
if exercise.equipmentRequired?.isEmpty == false {
Text(exercise.spacedEquipmentRequired)
.font(.footnote)
.frame(maxWidth: .infinity, alignment: .leading)
}
if exercise.muscleGroups != nil && !exercise.muscleGroups!.isEmpty {
if exercise.muscleGroups?.isEmpty == false {
Text(exercise.spacedMuscleGroups)
.font(.footnote)
.frame(maxWidth: .infinity, alignment: .leading)
@@ -86,6 +88,23 @@ struct AllExerciseView: View {
avPlayer.play()
}
}
.onDisappear {
avPlayer.pause()
}
}
private func updatePlayer(for url: URL) {
if currentVideoURL == url {
avPlayer.seek(to: .zero)
avPlayer.isMuted = true
avPlayer.play()
return
}
currentVideoURL = url
avPlayer = AVPlayer(url: url)
avPlayer.isMuted = true
avPlayer.play()
}
}
//

View File

@@ -10,6 +10,7 @@ import SwiftUI
struct CompletedWorkoutsView: View {
@State var completedWorkouts: [CompletedWorkout]?
@State var showCompletedWorkouts: Bool = false
@State private var loadError: String?
var body: some View {
VStack(alignment: .leading) {
@@ -41,7 +42,11 @@ struct CompletedWorkoutsView: View {
}
} else {
Text("loading completed workouts")
if let loadError = loadError {
Text(loadError)
} else {
Text("loading completed workouts")
}
}
}
.onAppear{
@@ -58,9 +63,14 @@ struct CompletedWorkoutsView: View {
CompletedWorkoutFetchable().fetch(completion: { result in
switch result {
case .success(let model):
completedWorkouts = model
DispatchQueue.main.async {
completedWorkouts = model
loadError = nil
}
case .failure(let failure):
fatalError(failure.localizedDescription)
DispatchQueue.main.async {
loadError = "Unable to load workout history: \(failure.localizedDescription)"
}
}
})
}

View File

@@ -9,13 +9,13 @@ import SwiftUI
struct CreateWorkoutSupersetView: View {
@Binding var selectedCreateWorkoutSuperSet: CreateWorkoutSuperSet?
@Binding var showAddExercise: Bool
@Binding var superset: CreateWorkoutSuperSet
@ObservedObject var superset: CreateWorkoutSuperSet
@ObservedObject var viewModel: WorkoutViewModel
var body: some View {
Section(content: {
AddSupersetView(
createWorkoutSuperSet: $superset,
createWorkoutSuperSet: superset,
viewModel: viewModel,
selectedCreateWorkoutSuperSet: $selectedCreateWorkoutSuperSet)
}, header: {
@@ -34,23 +34,25 @@ struct CreateWorkoutSupersetView: View {
Spacer()
Button(action: {
selectedCreateWorkoutSuperSet = $superset.wrappedValue
showAddExercise.toggle()
selectedCreateWorkoutSuperSet = superset
showAddExercise = true
}, label: {
Image(systemName: "dumbbell.fill")
.font(.title2)
})
.accessibilityLabel("Add exercise")
.accessibilityHint("Adds an exercise to this superset")
Divider()
Button(action: {
viewModel.delete(superset: $superset.wrappedValue)
//viewModel.increaseRandomNumberForUpdating()
viewModel.objectWillChange.send()
viewModel.delete(superset: superset)
}, label: {
Image(systemName: "trash")
.font(.title2)
})
.accessibilityLabel("Delete superset")
.accessibilityHint("Removes this superset")
}
Divider()
@@ -59,18 +61,14 @@ struct CreateWorkoutSupersetView: View {
HStack {
Text("Rounds: ")
Text("\($superset.wrappedValue.numberOfRounds)")
.foregroundColor($superset.wrappedValue.numberOfRounds > 0 ? Color(uiColor: .label) : .red)
Text("\(superset.numberOfRounds)")
.foregroundColor(superset.numberOfRounds > 0 ? Color(uiColor: .label) : .red)
.bold()
}
}, onIncrement: {
$superset.wrappedValue.increaseNumberOfRounds()
//viewModel.increaseRandomNumberForUpdating()
viewModel.objectWillChange.send()
superset.increaseNumberOfRounds()
}, onDecrement: {
$superset.wrappedValue.decreaseNumberOfRounds()
//viewModel.increaseRandomNumberForUpdating()
viewModel.objectWillChange.send()
superset.decreaseNumberOfRounds()
})
}
}

View File

@@ -13,34 +13,38 @@ struct PlannedWorkoutView: View {
var body: some View {
List {
ForEach(workouts.sorted(by: { $0.onDate < $1.onDate }), id:\.workout.name) { plannedWorkout in
HStack {
VStack(alignment: .leading) {
Text(plannedWorkout.onDate.plannedDate?.weekDay ?? "-")
.font(.title)
ForEach(workouts.sorted(by: { $0.onDate < $1.onDate }), id: \.id) { plannedWorkout in
Button(action: {
selectedPlannedWorkout = plannedWorkout.workout
}, label: {
HStack {
VStack(alignment: .leading) {
Text(plannedWorkout.onDate.plannedDate?.weekDay ?? "-")
.font(.title)
Text(plannedWorkout.onDate.plannedDate?.monthString ?? "-")
.font(.title)
Text(plannedWorkout.onDate.plannedDate?.dateString ?? "-")
.font(.title)
}
Text(plannedWorkout.onDate.plannedDate?.monthString ?? "-")
.font(.title)
Divider()
Text(plannedWorkout.onDate.plannedDate?.dateString ?? "-")
.font(.title)
VStack {
Text(plannedWorkout.workout.name)
.font(.title)
.frame(maxWidth: .infinity, alignment: .leading)
Text(plannedWorkout.workout.description ?? "")
.font(.body)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
Divider()
VStack {
Text(plannedWorkout.workout.name)
.font(.title)
.frame(maxWidth: .infinity, alignment: .leading)
Text(plannedWorkout.workout.description ?? "")
.font(.body)
.frame(maxWidth: .infinity, alignment: .leading)
}
.onTapGesture {
selectedPlannedWorkout = plannedWorkout.workout
}
}
})
.buttonStyle(.plain)
.accessibilityLabel("Open planned workout \(plannedWorkout.workout.name)")
.accessibilityHint("Shows workout details")
}
}
}

View File

@@ -21,7 +21,8 @@ class PlayerUIView: UIView {
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
super.init(coder: coder)
self.playerSetup(player: AVPlayer())
}
init(player: AVPlayer) {
@@ -76,11 +77,20 @@ struct PlayerView: UIViewRepresentable {
}
func updateUIView(_ uiView: PlayerUIView, context: UIViewRepresentableContext<PlayerView>) {
if uiView.playerLayer.player !== player {
uiView.playerLayer.player?.pause()
}
uiView.playerLayer.player = player
//Add player observer.
uiView.setObserver()
}
static func dismantleUIView(_ uiView: PlayerUIView, coordinator: ()) {
uiView.playerLayer.player?.pause()
uiView.playerLayer.player = nil
NotificationCenter.default.removeObserver(uiView)
}
}
class VideoURLCreator {