Stabilize iOS/watchOS/tvOS apps and add cross-platform audit remediation
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
//
|
||||
|
||||
@@ -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)"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user