[iOS & Android] iOS 1.3.0 & Android 1.8.0
All checks were successful
OpenClimb Docker Deploy / build-and-push (push) Successful in 2m13s

This commit is contained in:
2025-10-09 21:00:12 -06:00
parent 603a683ab2
commit 6a39d23f28
15 changed files with 643 additions and 207 deletions

View File

@@ -23,6 +23,22 @@ struct AddAttemptView: View {
@State private var selectedPhotos: [PhotosPickerItem] = []
@State private var imageData: [Data] = []
enum SheetType: Identifiable {
case photoOptions
case camera
var id: Int {
switch self {
case .photoOptions: return 0
case .camera: return 1
}
}
}
@State private var activeSheet: SheetType?
@State private var showPhotoPicker = false
@State private var isPhotoPickerActionPending = false
private var activeProblems: [Problem] {
dataManager.activeProblems(forGym: gym.id)
}
@@ -78,6 +94,56 @@ struct AddAttemptView: View {
.onChange(of: selectedDifficultySystem) {
resetGradeIfNeeded()
}
.onChange(of: selectedPhotos) {
Task {
await loadSelectedPhotos()
}
}
.photosPicker(
isPresented: $showPhotoPicker,
selection: $selectedPhotos,
maxSelectionCount: 5 - imageData.count,
matching: .images
)
.sheet(
item: $activeSheet,
onDismiss: {
if isPhotoPickerActionPending {
showPhotoPicker = true
isPhotoPickerActionPending = false
}
}
) { sheetType in
switch sheetType {
case .photoOptions:
PhotoOptionSheet(
selectedPhotos: $selectedPhotos,
imageData: $imageData,
maxImages: 5,
onCameraSelected: {
activeSheet = .camera
},
onPhotoLibrarySelected: {
isPhotoPickerActionPending = true
},
onDismiss: {
activeSheet = nil
}
)
case .camera:
CameraImagePicker(
isPresented: Binding(
get: { activeSheet == .camera },
set: { if !$0 { activeSheet = nil } }
)
) { capturedImage in
if let jpegData = capturedImage.jpegData(compressionQuality: 0.8) {
imageData.append(jpegData)
}
}
}
}
}
@ViewBuilder
@@ -216,11 +282,9 @@ struct AddAttemptView: View {
}
Section("Photos (Optional)") {
PhotosPicker(
selection: $selectedPhotos,
maxSelectionCount: 5,
matching: .images
) {
Button(action: {
activeSheet = .photoOptions
}) {
HStack {
Image(systemName: "camera.fill")
.foregroundColor(.blue)
@@ -240,11 +304,7 @@ struct AddAttemptView: View {
}
.padding(.vertical, 4)
}
.onChange(of: selectedPhotos) { _, _ in
Task {
await loadSelectedPhotos()
}
}
.disabled(imageData.count >= 5)
if !imageData.isEmpty {
ScrollView(.horizontal, showsIndicators: false) {
@@ -378,6 +438,21 @@ struct AddAttemptView: View {
}
}
private func loadSelectedPhotos() async {
var newImageData: [Data] = []
for item in selectedPhotos {
if let data = try? await item.loadTransferable(type: Data.self) {
newImageData.append(data)
}
}
await MainActor.run {
imageData.append(contentsOf: newImageData)
selectedPhotos.removeAll()
}
}
private func saveAttempt() {
if showingCreateProblem {
let difficulty = DifficultyGrade(
@@ -436,19 +511,6 @@ struct AddAttemptView: View {
dismiss()
}
private func loadSelectedPhotos() async {
var newImageData: [Data] = []
for item in selectedPhotos {
if let data = try? await item.loadTransferable(type: Data.self) {
newImageData.append(data)
}
}
await MainActor.run {
imageData = newImageData
}
}
}
struct ProblemSelectionRow: View {
@@ -696,6 +758,22 @@ struct EditAttemptView: View {
@State private var selectedPhotos: [PhotosPickerItem] = []
@State private var imageData: [Data] = []
enum SheetType: Identifiable {
case photoOptions
case camera
var id: Int {
switch self {
case .photoOptions: return 0
case .camera: return 1
}
}
}
@State private var activeSheet: SheetType?
@State private var showPhotoPicker = false
@State private var isPhotoPickerActionPending = false
private var availableProblems: [Problem] {
guard let session = dataManager.session(withId: attempt.sessionId) else {
return []
@@ -772,6 +850,56 @@ struct EditAttemptView: View {
.onChange(of: selectedDifficultySystem) {
resetGradeIfNeeded()
}
.onChange(of: selectedPhotos) {
Task {
await loadSelectedPhotos()
}
}
.photosPicker(
isPresented: $showPhotoPicker,
selection: $selectedPhotos,
maxSelectionCount: 5 - imageData.count,
matching: .images
)
.sheet(
item: $activeSheet,
onDismiss: {
if isPhotoPickerActionPending {
showPhotoPicker = true
isPhotoPickerActionPending = false
}
}
) { sheetType in
switch sheetType {
case .photoOptions:
PhotoOptionSheet(
selectedPhotos: $selectedPhotos,
imageData: $imageData,
maxImages: 5,
onCameraSelected: {
activeSheet = .camera
},
onPhotoLibrarySelected: {
isPhotoPickerActionPending = true
},
onDismiss: {
activeSheet = nil
}
)
case .camera:
CameraImagePicker(
isPresented: Binding(
get: { activeSheet == .camera },
set: { if !$0 { activeSheet = nil } }
)
) { capturedImage in
if let jpegData = capturedImage.jpegData(compressionQuality: 0.8) {
imageData.append(jpegData)
}
}
}
}
}
@ViewBuilder
@@ -910,11 +1038,9 @@ struct EditAttemptView: View {
}
Section("Photos (Optional)") {
PhotosPicker(
selection: $selectedPhotos,
maxSelectionCount: 5,
matching: .images
) {
Button(action: {
activeSheet = .photoOptions
}) {
HStack {
Image(systemName: "camera.fill")
.foregroundColor(.blue)
@@ -934,11 +1060,7 @@ struct EditAttemptView: View {
}
.padding(.vertical, 4)
}
.onChange(of: selectedPhotos) { _, _ in
Task {
await loadSelectedPhotos()
}
}
.disabled(imageData.count >= 5)
if !imageData.isEmpty {
ScrollView(.horizontal, showsIndicators: false) {
@@ -1074,6 +1196,21 @@ struct EditAttemptView: View {
}
}
private func loadSelectedPhotos() async {
var newImageData: [Data] = []
for item in selectedPhotos {
if let data = try? await item.loadTransferable(type: Data.self) {
newImageData.append(data)
}
}
await MainActor.run {
imageData.append(contentsOf: newImageData)
selectedPhotos.removeAll()
}
}
private func updateAttempt() {
if showingCreateProblem {
guard let gym = gym else { return }
@@ -1131,19 +1268,6 @@ struct EditAttemptView: View {
dismiss()
}
private func loadSelectedPhotos() async {
var newImageData: [Data] = []
for item in selectedPhotos {
if let data = try? await item.loadTransferable(type: Data.self) {
newImageData.append(data)
}
}
await MainActor.run {
imageData = newImageData
}
}
}
#Preview {