diff --git a/ios/ClimbingActivityWidget/Info.plist b/ios/ClimbingActivityWidget/Info.plist index ae9f1ab..79b8a72 100644 --- a/ios/ClimbingActivityWidget/Info.plist +++ b/ios/ClimbingActivityWidget/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + 1.0.0 CFBundleVersion 1 NSExtension diff --git a/ios/OpenClimb.xcodeproj/LiveActivityManager.swift b/ios/OpenClimb.xcodeproj/LiveActivityManager.swift index 8e66542..d148bc8 100644 --- a/ios/OpenClimb.xcodeproj/LiveActivityManager.swift +++ b/ios/OpenClimb.xcodeproj/LiveActivityManager.swift @@ -21,7 +21,7 @@ final class LiveActivityManager { do { currentActivity = try Activity.request( attributes: attributes, - contentState: initialContentState, + content: .init(state: initialContentState, staleDate: nil), pushType: nil ) } catch { @@ -38,13 +38,13 @@ final class LiveActivityManager { totalAttempts: totalAttempts, completedProblems: completedProblems ) - await currentActivity.update(using: updatedContentState, alertConfiguration: nil) + await currentActivity.update(.init(state: updatedContentState, staleDate: nil)) } /// Call this when a ClimbSession ends to end the Live Activity func endLiveActivity() async { guard let currentActivity else { return } - await currentActivity.end(using: nil, dismissalPolicy: .immediate) + await currentActivity.end(nil, dismissalPolicy: .immediate) self.currentActivity = nil } } diff --git a/ios/OpenClimb.xcodeproj/project.pbxproj b/ios/OpenClimb.xcodeproj/project.pbxproj index 80e8460..314e0a1 100644 --- a/ios/OpenClimb.xcodeproj/project.pbxproj +++ b/ios/OpenClimb.xcodeproj/project.pbxproj @@ -477,7 +477,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.atridad.OpenClimb.SessionStatusLive; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -506,7 +506,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.atridad.OpenClimb.SessionStatusLive; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; diff --git a/ios/OpenClimb.xcodeproj/project.xcworkspace/xcuserdata/atridad.xcuserdatad/UserInterfaceState.xcuserstate b/ios/OpenClimb.xcodeproj/project.xcworkspace/xcuserdata/atridad.xcuserdatad/UserInterfaceState.xcuserstate index 4811c96..b948fda 100644 Binary files a/ios/OpenClimb.xcodeproj/project.xcworkspace/xcuserdata/atridad.xcuserdatad/UserInterfaceState.xcuserstate and b/ios/OpenClimb.xcodeproj/project.xcworkspace/xcuserdata/atridad.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/ios/OpenClimb.xcodeproj/xcuserdata/atridad.xcuserdatad/xcschemes/xcschememanagement.plist b/ios/OpenClimb.xcodeproj/xcuserdata/atridad.xcuserdatad/xcschemes/xcschememanagement.plist index 53d0216..29372fd 100644 --- a/ios/OpenClimb.xcodeproj/xcuserdata/atridad.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/ios/OpenClimb.xcodeproj/xcuserdata/atridad.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ SessionStatusLiveExtension.xcscheme_^#shared#^_ orderHint - 0 + 1 diff --git a/ios/OpenClimb/ContentView.swift b/ios/OpenClimb/ContentView.swift index 15d44e6..276c850 100644 --- a/ios/OpenClimb/ContentView.swift +++ b/ios/OpenClimb/ContentView.swift @@ -43,8 +43,8 @@ struct ContentView: View { .tag(4) } .environmentObject(dataManager) - .onChange(of: scenePhase) { newPhase in - if newPhase == .active { + .onChange(of: scenePhase) { + if scenePhase == .active { dataManager.onAppBecomeActive() } } diff --git a/ios/OpenClimb/Models/DataModels.swift b/ios/OpenClimb/Models/DataModels.swift index 2cbb042..c395279 100644 --- a/ios/OpenClimb/Models/DataModels.swift +++ b/ios/OpenClimb/Models/DataModels.swift @@ -1,4 +1,3 @@ - import Foundation import SwiftUI @@ -493,13 +492,14 @@ struct Attempt: Identifiable, Codable, Hashable { } func updated( - result: AttemptResult? = nil, highestHold: String? = nil, notes: String? = nil, + problemId: UUID? = nil, result: AttemptResult? = nil, highestHold: String? = nil, + notes: String? = nil, duration: Int? = nil, restTime: Int? = nil ) -> Attempt { return Attempt( id: self.id, sessionId: self.sessionId, - problemId: self.problemId, + problemId: problemId ?? self.problemId, result: result ?? self.result, highestHold: highestHold ?? self.highestHold, notes: notes ?? self.notes, diff --git a/ios/OpenClimb/Utils/AppIconHelper.swift b/ios/OpenClimb/Utils/AppIconHelper.swift index f5f6c62..b21132a 100644 --- a/ios/OpenClimb/Utils/AppIconHelper.swift +++ b/ios/OpenClimb/Utils/AppIconHelper.swift @@ -1,4 +1,3 @@ - import Combine import SwiftUI @@ -82,9 +81,9 @@ struct IconAppearanceModifier: ViewModifier { func body(content: Content) -> some View { content - .onChange(of: colorScheme) { _, newColorScheme in - iconHelper.updateDarkModeStatus(for: newColorScheme) - onChange(iconHelper.getRecommendedIconVariant(for: newColorScheme)) + .onChange(of: colorScheme) { + iconHelper.updateDarkModeStatus(for: colorScheme) + onChange(iconHelper.getRecommendedIconVariant(for: colorScheme)) } .onAppear { iconHelper.updateDarkModeStatus(for: colorScheme) diff --git a/ios/OpenClimb/ViewModels/ClimbingDataManager.swift b/ios/OpenClimb/ViewModels/ClimbingDataManager.swift index 57b26bf..13f8a04 100644 --- a/ios/OpenClimb/ViewModels/ClimbingDataManager.swift +++ b/ios/OpenClimb/ViewModels/ClimbingDataManager.swift @@ -1020,7 +1020,7 @@ extension ClimbingDataManager { private func updateLiveActivityForActiveSession() { guard let activeSession = activeSession, activeSession.status == .active, - let gym = gym(withId: activeSession.gymId) + let _ = gym(withId: activeSession.gymId) else { return } diff --git a/ios/OpenClimb/ViewModels/LiveActivityManager.swift b/ios/OpenClimb/ViewModels/LiveActivityManager.swift index 97c0a3f..efa406e 100644 --- a/ios/OpenClimb/ViewModels/LiveActivityManager.swift +++ b/ios/OpenClimb/ViewModels/LiveActivityManager.swift @@ -45,7 +45,7 @@ final class LiveActivityManager { do { let activity = try Activity.request( attributes: attributes, - contentState: initialContentState, + content: .init(state: initialContentState, staleDate: nil), pushType: nil ) self.currentActivity = activity @@ -81,12 +81,8 @@ final class LiveActivityManager { completedProblems: completedProblems ) - do { - await currentActivity.update(using: updatedContentState, alertConfiguration: nil) - print("✅ Live Activity updated successfully") - } catch { - print("❌ Failed to update live activity: \(error)") - } + await currentActivity.update(.init(state: updatedContentState, staleDate: nil)) + print("✅ Live Activity updated successfully") } /// Call this when a ClimbSession ends to end the Live Activity @@ -98,14 +94,9 @@ final class LiveActivityManager { print("🔴 Ending Live Activity: \(currentActivity.id)") - do { - await currentActivity.end(using: nil, dismissalPolicy: .immediate) - self.currentActivity = nil - print("✅ Live Activity ended successfully") - } catch { - print("❌ Failed to end live activity: \(error)") - self.currentActivity = nil - } + await currentActivity.end(nil, dismissalPolicy: .immediate) + self.currentActivity = nil + print("✅ Live Activity ended successfully") } /// Check if Live Activities are available and authorized diff --git a/ios/OpenClimb/Views/AddEdit/AddAttemptView.swift b/ios/OpenClimb/Views/AddEdit/AddAttemptView.swift index a89a576..e113476 100644 --- a/ios/OpenClimb/Views/AddEdit/AddAttemptView.swift +++ b/ios/OpenClimb/Views/AddEdit/AddAttemptView.swift @@ -1,4 +1,3 @@ - import SwiftUI struct AddAttemptView: View { @@ -610,36 +609,25 @@ struct EditAttemptView: View { var body: some View { NavigationView { Form { - Section("Problem") { + Section("Select Problem") { if availableProblems.isEmpty { Text("No problems available") .foregroundColor(.secondary) } else { - ForEach(availableProblems, id: \.id) { problem in - HStack { - VStack(alignment: .leading, spacing: 4) { - Text(problem.name ?? "Unnamed Problem") - .font(.headline) - - Text( - "\(problem.difficulty.system.displayName): \(problem.difficulty.grade)" - ) - .font(.subheadline) - .foregroundColor(.blue) + LazyVGrid( + columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: 2), + spacing: 8 + ) { + ForEach(availableProblems, id: \.id) { problem in + ProblemSelectionCard( + problem: problem, + isSelected: selectedProblem?.id == problem.id + ) { + selectedProblem = problem } - - Spacer() - - if selectedProblem?.id == problem.id { - Image(systemName: "checkmark.circle.fill") - .foregroundColor(.blue) - } - } - .contentShape(Rectangle()) - .onTapGesture { - selectedProblem = problem } } + .padding(.vertical, 8) } } @@ -724,6 +712,7 @@ struct EditAttemptView: View { guard selectedProblem != nil else { return } let updatedAttempt = attempt.updated( + problemId: selectedProblem?.id, result: selectedResult, highestHold: highestHold.isEmpty ? nil : highestHold, notes: notes.isEmpty ? nil : notes, diff --git a/ios/OpenClimb/Views/LiveActivityDebugView.swift b/ios/OpenClimb/Views/LiveActivityDebugView.swift index 88843b6..708aef5 100644 --- a/ios/OpenClimb/Views/LiveActivityDebugView.swift +++ b/ios/OpenClimb/Views/LiveActivityDebugView.swift @@ -128,7 +128,7 @@ struct LiveActivityDebugView: View { .frame(maxWidth: .infinity, alignment: .leading) .padding(8) .id("bottom") - .onChange(of: debugOutput) { _ in + .onChange(of: debugOutput) { withAnimation { proxy.scrollTo("bottom", anchor: .bottom) } diff --git a/ios/SessionStatusLive/SessionStatusLive.swift b/ios/SessionStatusLive/SessionStatusLive.swift index afa3c22..54cba42 100644 --- a/ios/SessionStatusLive/SessionStatusLive.swift +++ b/ios/SessionStatusLive/SessionStatusLive.swift @@ -15,7 +15,7 @@ struct ClimbingStatsProvider: TimelineProvider { ClimbingStatsEntry( date: Date(), weeklyAttempts: 42, - todayAttempts: 8, + weeklySessions: 5, currentStreak: 3, favoriteGym: "Summit Climbing" ) @@ -25,7 +25,7 @@ struct ClimbingStatsProvider: TimelineProvider { let entry = ClimbingStatsEntry( date: Date(), weeklyAttempts: 42, - todayAttempts: 8, + weeklySessions: 5, currentStreak: 3, favoriteGym: "Summit Climbing" ) @@ -41,7 +41,7 @@ struct ClimbingStatsProvider: TimelineProvider { let entry = ClimbingStatsEntry( date: currentDate, weeklyAttempts: stats.weeklyAttempts, - todayAttempts: stats.todayAttempts, + weeklySessions: stats.weeklySessions, currentStreak: stats.currentStreak, favoriteGym: stats.favoriteGym ) @@ -60,7 +60,7 @@ struct ClimbingStatsProvider: TimelineProvider { let attempts = try? JSONDecoder().decode([WidgetAttempt].self, from: attemptsData) else { return ClimbingStats( - weeklyAttempts: 0, todayAttempts: 0, currentStreak: 0, favoriteGym: "No Data") + weeklyAttempts: 0, weeklySessions: 0, currentStreak: 0, favoriteGym: "No Data") } // Load sessions for streak calculation @@ -74,16 +74,16 @@ struct ClimbingStatsProvider: TimelineProvider { let calendar = Calendar.current let now = Date() let weekAgo = calendar.date(byAdding: .day, value: -7, to: now)! - let startOfToday = calendar.startOfDay(for: now) + _ = calendar.startOfDay(for: now) // Calculate weekly attempts let weeklyAttempts = attempts.filter { attempt in attempt.timestamp >= weekAgo }.count - // Calculate today's attempts - let todayAttempts = attempts.filter { attempt in - attempt.timestamp >= startOfToday + // Calculate weekly sessions + let weeklySessions = sessions.filter { session in + session.date >= weekAgo && session.status == "COMPLETED" }.count // Calculate current streak (consecutive days with sessions) @@ -94,7 +94,7 @@ struct ClimbingStatsProvider: TimelineProvider { return ClimbingStats( weeklyAttempts: weeklyAttempts, - todayAttempts: todayAttempts, + weeklySessions: weeklySessions, currentStreak: currentStreak, favoriteGym: favoriteGym ) @@ -144,14 +144,14 @@ struct ClimbingStatsProvider: TimelineProvider { struct ClimbingStatsEntry: TimelineEntry { let date: Date let weeklyAttempts: Int - let todayAttempts: Int + let weeklySessions: Int let currentStreak: Int let favoriteGym: String } struct ClimbingStats { let weeklyAttempts: Int - let todayAttempts: Int + let weeklySessions: Int let currentStreak: Int let favoriteGym: String } @@ -176,7 +176,7 @@ struct SmallWidgetView: View { let entry: ClimbingStatsEntry var body: some View { - VStack(spacing: 8) { + VStack(spacing: 12) { // Header HStack { if let uiImage = UIImage(named: "AppIcon") { @@ -190,51 +190,37 @@ struct SmallWidgetView: View { .foregroundColor(.accentColor) } Spacer() - Text("This Week") - .font(.caption) - .foregroundColor(.secondary) - } - - // Main stat - weekly attempts - VStack(spacing: 2) { - Text("\(entry.weeklyAttempts)") - .font(.largeTitle) - .fontWeight(.bold) - .foregroundColor(.primary) - Text("Attempts") + Text("Weekly") .font(.caption) .foregroundColor(.secondary) } Spacer() - // Bottom stats - HStack { - VStack(alignment: .leading, spacing: 2) { - Text("\(entry.todayAttempts)") - .font(.headline) - .fontWeight(.semibold) - Text("Today") - .font(.caption2) - .foregroundColor(.secondary) + // Main stats - weekly attempts and sessions + VStack(spacing: 16) { + HStack(spacing: 8) { + Image(systemName: "flame.fill") + .foregroundColor(.orange) + .font(.title2) + Text("\(entry.weeklyAttempts)") + .font(.title) + .fontWeight(.bold) + .foregroundColor(.primary) } - Spacer() - - VStack(alignment: .trailing, spacing: 2) { - HStack(spacing: 2) { - Text("\(entry.currentStreak)") - .font(.headline) - .fontWeight(.semibold) - Image(systemName: "flame.fill") - .foregroundColor(.orange) - .font(.caption) - } - Text("Day Streak") - .font(.caption2) - .foregroundColor(.secondary) + HStack(spacing: 8) { + Image(systemName: "play.fill") + .foregroundColor(.blue) + .font(.title2) + Text("\(entry.weeklySessions)") + .font(.title) + .fontWeight(.bold) + .foregroundColor(.primary) } } + + Spacer() } .padding() } @@ -244,7 +230,7 @@ struct MediumWidgetView: View { let entry: ClimbingStatsEntry var body: some View { - VStack(spacing: 12) { + VStack(spacing: 16) { // Header HStack { HStack(spacing: 6) { @@ -258,69 +244,41 @@ struct MediumWidgetView: View { .font(.title2) .foregroundColor(.accentColor) } - Text("Climbing Stats") + Text("Weekly") .font(.headline) .fontWeight(.semibold) } Spacer() - Text("This Week") - .font(.caption) - .foregroundColor(.secondary) } - // Main stats row - HStack(spacing: 20) { - VStack(spacing: 4) { - Text("\(entry.weeklyAttempts)") - .font(.title) - .fontWeight(.bold) - .foregroundColor(.primary) - Text("Total Attempts") - .font(.caption) - .foregroundColor(.secondary) - } - - VStack(spacing: 4) { - Text("\(entry.todayAttempts)") - .font(.title) - .fontWeight(.bold) - .foregroundColor(.blue) - Text("Today") - .font(.caption) - .foregroundColor(.secondary) - } - - VStack(spacing: 4) { - HStack(spacing: 4) { - Text("\(entry.currentStreak)") - .font(.title) - .fontWeight(.bold) - .foregroundColor(.orange) + // Main stats row - weekly attempts and sessions + HStack(spacing: 40) { + VStack(spacing: 8) { + HStack(spacing: 8) { Image(systemName: "flame.fill") .foregroundColor(.orange) - .font(.title3) + .font(.title2) + Text("\(entry.weeklyAttempts)") + .font(.title) + .fontWeight(.bold) + .foregroundColor(.primary) + } + } + + VStack(spacing: 8) { + HStack(spacing: 8) { + Image(systemName: "play.fill") + .foregroundColor(.blue) + .font(.title2) + Text("\(entry.weeklySessions)") + .font(.title) + .fontWeight(.bold) + .foregroundColor(.primary) } - Text("Day Streak") - .font(.caption) - .foregroundColor(.secondary) } } Spacer() - - // Bottom info - HStack { - VStack(alignment: .leading, spacing: 2) { - Text("Favorite Gym") - .font(.caption2) - .foregroundColor(.secondary) - Text(entry.favoriteGym) - .font(.caption) - .fontWeight(.medium) - .lineLimit(1) - } - Spacer() - } } .padding() } @@ -367,14 +325,14 @@ struct WidgetGym: Codable { ClimbingStatsEntry( date: .now, weeklyAttempts: 42, - todayAttempts: 8, + weeklySessions: 5, currentStreak: 3, favoriteGym: "Summit Climbing" ) ClimbingStatsEntry( date: .now, weeklyAttempts: 58, - todayAttempts: 12, + weeklySessions: 8, currentStreak: 5, favoriteGym: "Boulder Zone" )