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"
)