Fixed Widget stats
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Binary file not shown.
@@ -12,7 +12,7 @@
|
||||
<key>SessionStatusLiveExtension.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user