Fixed Widget stats

This commit is contained in:
2025-09-15 23:27:21 -06:00
parent 363fbd676a
commit b53dfb1aa5
13 changed files with 94 additions and 157 deletions

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>

View File

@@ -21,7 +21,7 @@ final class LiveActivityManager {
do {
currentActivity = try Activity<SessionActivityAttributes>.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
}
}

View File

@@ -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;

View File

@@ -12,7 +12,7 @@
<key>SessionStatusLiveExtension.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
<integer>1</integer>
</dict>
</dict>
</dict>

View File

@@ -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()
}
}

View File

@@ -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,

View File

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

View File

@@ -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
}

View File

@@ -45,7 +45,7 @@ final class LiveActivityManager {
do {
let activity = try Activity<SessionActivityAttributes>.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

View File

@@ -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,

View File

@@ -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)
}

View File

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