1.2.2 - "Bug fixes and improvements"

This commit is contained in:
2025-10-01 21:34:22 -06:00
parent ba1a7117d9
commit 4e42985135
15 changed files with 150 additions and 158 deletions

View File

@@ -396,7 +396,7 @@
CODE_SIGN_ENTITLEMENTS = OpenClimb/OpenClimb.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 12;
CURRENT_PROJECT_VERSION = 13;
DEVELOPMENT_TEAM = 4BC9Y2LL4B;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -416,7 +416,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.1;
MARKETING_VERSION = 1.2.2;
PRODUCT_BUNDLE_IDENTIFIER = com.atridad.OpenClimb;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -439,7 +439,7 @@
CODE_SIGN_ENTITLEMENTS = OpenClimb/OpenClimb.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 12;
CURRENT_PROJECT_VERSION = 13;
DEVELOPMENT_TEAM = 4BC9Y2LL4B;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -459,7 +459,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.1;
MARKETING_VERSION = 1.2.2;
PRODUCT_BUNDLE_IDENTIFIER = com.atridad.OpenClimb;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -481,7 +481,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = SessionStatusLiveExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 12;
CURRENT_PROJECT_VERSION = 13;
DEVELOPMENT_TEAM = 4BC9Y2LL4B;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SessionStatusLive/Info.plist;
@@ -492,7 +492,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.2.1;
MARKETING_VERSION = 1.2.2;
PRODUCT_BUNDLE_IDENTIFIER = com.atridad.OpenClimb.SessionStatusLive;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -511,7 +511,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = SessionStatusLiveExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 12;
CURRENT_PROJECT_VERSION = 13;
DEVELOPMENT_TEAM = 4BC9Y2LL4B;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SessionStatusLive/Info.plist;
@@ -522,7 +522,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.2.1;
MARKETING_VERSION = 1.2.2;
PRODUCT_BUNDLE_IDENTIFIER = com.atridad.OpenClimb.SessionStatusLive;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;

View File

@@ -1,4 +1,3 @@
import Combine
import SwiftUI
@@ -11,7 +10,7 @@ import SwiftUI
@State private var testResults: [String] = []
var body: some View {
NavigationView {
NavigationStack {
List {
StatusSection()
@@ -364,7 +363,7 @@ import SwiftUI
@Environment(\.colorScheme) private var colorScheme
var body: some View {
NavigationView {
NavigationStack {
VStack(spacing: 30) {
Text("Icon Appearance Comparison")
.font(.title2)

View File

@@ -42,7 +42,7 @@ struct AddAttemptView: View {
}
var body: some View {
NavigationView {
NavigationStack {
Form {
if !showingCreateProblem {
ProblemSelectionSection()
@@ -597,7 +597,7 @@ struct ProblemExpandedView: View {
@State private var selectedImageIndex = 0
var body: some View {
NavigationView {
NavigationStack {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
// Images
@@ -735,7 +735,7 @@ struct EditAttemptView: View {
}
var body: some View {
NavigationView {
NavigationStack {
Form {
if !showingCreateProblem {
ProblemSelectionSection()

View File

@@ -1,4 +1,3 @@
import SwiftUI
struct AddEditGymView: View {
@@ -34,7 +33,7 @@ struct AddEditGymView: View {
}
var body: some View {
NavigationView {
NavigationStack {
Form {
BasicInfoSection()
ClimbTypesSection()

View File

@@ -55,7 +55,7 @@ struct AddEditProblemView: View {
}
var body: some View {
NavigationView {
NavigationStack {
Form {
GymSelectionSection()
BasicInfoSection()

View File

@@ -1,4 +1,3 @@
import SwiftUI
struct AddEditSessionView: View {
@@ -21,7 +20,7 @@ struct AddEditSessionView: View {
}
var body: some View {
NavigationView {
NavigationStack {
Form {
GymSelectionSection()
SessionDetailsSection()

View File

@@ -4,7 +4,7 @@ struct AnalyticsView: View {
@EnvironmentObject var dataManager: ClimbingDataManager
var body: some View {
NavigationView {
NavigationStack {
ScrollView {
LazyVStack(spacing: 20) {
OverallStatsSection()

View File

@@ -420,7 +420,7 @@ struct ImageViewerView: View {
}
var body: some View {
NavigationView {
NavigationStack {
TabView(selection: $currentIndex) {
ForEach(imagePaths.indices, id: \.self) { index in
ProblemDetailImageFullView(imagePath: imagePaths[index])

View File

@@ -9,24 +9,11 @@ struct SessionDetailView: View {
@State private var showingAddAttempt = false
@State private var editingAttempt: Attempt?
@State private var attemptToDelete: Attempt?
@State private var currentTime = Date()
private var session: ClimbSession? {
dataManager.session(withId: sessionId)
}
private func startTimer() {
// Update every 5 seconds instead of 1 second for better performance
timer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { _ in
currentTime = Date()
}
}
private func stopTimer() {
timer?.invalidate()
timer = nil
}
private var gym: Gym? {
guard let session = session else { return nil }
return dataManager.gym(withId: session.gymId)
@@ -47,14 +34,12 @@ struct SessionDetailView: View {
calculateSessionStats()
}
@State private var timer: Timer?
var body: some View {
ScrollView {
LazyVStack(spacing: 20) {
if let session = session, let gym = gym {
SessionHeaderCard(
session: session, gym: gym, stats: sessionStats, currentTime: currentTime)
session: session, gym: gym, stats: sessionStats)
SessionStatsCard(stats: sessionStats)
@@ -69,12 +54,7 @@ struct SessionDetailView: View {
}
.padding()
}
.onAppear {
startTimer()
}
.onDisappear {
stopTimer()
}
.navigationTitle("Session Details")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
@@ -182,7 +162,6 @@ struct SessionHeaderCard: View {
let session: ClimbSession
let gym: Gym
let stats: SessionStats
let currentTime: Date
var body: some View {
VStack(alignment: .leading, spacing: 16) {
@@ -197,9 +176,13 @@ struct SessionHeaderCard: View {
if session.status == .active {
if let startTime = session.startTime {
Text("Duration: \(formatDuration(from: startTime, to: currentTime))")
Text("Duration: ")
.font(.subheadline)
.foregroundColor(.secondary)
+ Text(timerInterval: startTime...Date.distantFuture, countsDown: false)
.font(.subheadline)
.foregroundColor(.secondary)
.monospacedDigit()
}
} else if let duration = session.duration {
Text("Duration: \(duration) minutes")
@@ -246,20 +229,6 @@ struct SessionHeaderCard: View {
return formatter.string(from: date)
}
private func formatDuration(from start: Date, to end: Date) -> String {
let interval = end.timeIntervalSince(start)
let hours = Int(interval) / 3600
let minutes = Int(interval) % 3600 / 60
let seconds = Int(interval) % 60
if hours > 0 {
return String(format: "%dh %dm %ds", hours, minutes, seconds)
} else if minutes > 0 {
return String(format: "%dm %ds", minutes, seconds)
} else {
return String(format: "%ds", seconds)
}
}
}
struct SessionStatsCard: View {

View File

@@ -5,7 +5,7 @@ struct GymsView: View {
@State private var showingAddGym = false
var body: some View {
NavigationView {
NavigationStack {
VStack {
if dataManager.gyms.isEmpty {
EmptyGymsView()

View File

@@ -9,7 +9,7 @@ struct LiveActivityDebugView: View {
@State private var isTestRunning = false
var body: some View {
NavigationView {
NavigationStack {
VStack(alignment: .leading, spacing: 20) {
// Header

View File

@@ -6,6 +6,8 @@ struct ProblemsView: View {
@State private var selectedClimbType: ClimbType?
@State private var selectedGym: Gym?
@State private var searchText = ""
@State private var showingSearch = false
@FocusState private var isSearchFocused: Bool
private var filteredProblems: [Problem] {
var filtered = dataManager.problems
@@ -38,29 +40,67 @@ struct ProblemsView: View {
}
var body: some View {
NavigationView {
VStack(spacing: 0) {
if !dataManager.problems.isEmpty {
FilterSection(
selectedClimbType: $selectedClimbType,
selectedGym: $selectedGym,
filteredProblems: filteredProblems
)
.padding()
.background(.regularMaterial)
}
NavigationStack {
Group {
VStack(spacing: 0) {
if showingSearch {
HStack(spacing: 8) {
Image(systemName: "magnifyingglass")
.foregroundColor(.secondary)
.font(.system(size: 16, weight: .medium))
if filteredProblems.isEmpty {
EmptyProblemsView(
isEmpty: dataManager.problems.isEmpty,
isFiltered: !dataManager.problems.isEmpty
)
} else {
ProblemsList(problems: filteredProblems)
TextField("Search problems...", text: $searchText)
.textFieldStyle(.plain)
.font(.system(size: 16))
.focused($isSearchFocused)
.submitLabel(.search)
}
.padding(.horizontal, 12)
.padding(.vertical, 10)
.background {
if #available(iOS 18.0, *) {
RoundedRectangle(cornerRadius: 12)
.fill(.regularMaterial)
.overlay {
RoundedRectangle(cornerRadius: 12)
.stroke(.quaternary, lineWidth: 0.5)
}
} else {
RoundedRectangle(cornerRadius: 10)
.fill(Color(.systemGray6))
.overlay {
RoundedRectangle(cornerRadius: 10)
.stroke(Color(.systemGray4), lineWidth: 0.5)
}
}
}
.padding(.horizontal)
.padding(.top, 8)
.animation(.easeInOut(duration: 0.3), value: showingSearch)
}
if !dataManager.problems.isEmpty && !showingSearch {
FilterSection(
selectedClimbType: $selectedClimbType,
selectedGym: $selectedGym,
filteredProblems: filteredProblems
)
.padding()
.background(.regularMaterial)
}
if filteredProblems.isEmpty {
EmptyProblemsView(
isEmpty: dataManager.problems.isEmpty,
isFiltered: !dataManager.problems.isEmpty
)
} else {
ProblemsList(problems: filteredProblems)
}
}
}
.navigationTitle("Problems")
.searchable(text: $searchText, prompt: "Search problems...")
.navigationBarTitleDisplayMode(.automatic)
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
if dataManager.isSyncing {
@@ -81,6 +121,22 @@ struct ProblemsView: View {
)
}
Button(action: {
withAnimation(.easeInOut(duration: 0.3)) {
showingSearch.toggle()
if showingSearch {
isSearchFocused = true
} else {
searchText = ""
isSearchFocused = false
}
}
}) {
Image(systemName: showingSearch ? "xmark.circle.fill" : "magnifyingglass")
.font(.system(size: 16, weight: .medium))
.foregroundColor(showingSearch ? .secondary : .blue)
}
if !dataManager.gyms.isEmpty {
Button("Add") {
showingAddProblem = true

View File

@@ -6,7 +6,7 @@ struct SessionsView: View {
@State private var showingAddSession = false
var body: some View {
NavigationView {
NavigationStack {
Group {
if dataManager.sessions.isEmpty && dataManager.activeSession == nil {
EmptySessionsView()
@@ -53,7 +53,6 @@ struct SessionsView: View {
AddEditSessionView()
}
}
.navigationViewStyle(.stack)
}
}
@@ -129,11 +128,8 @@ struct ActiveSessionBanner: View {
let session: ClimbSession
let gym: Gym
@EnvironmentObject var dataManager: ClimbingDataManager
@State private var currentTime = Date()
@State private var navigateToDetail = false
@State private var timer: Timer?
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 4) {
@@ -151,9 +147,10 @@ struct ActiveSessionBanner: View {
.foregroundColor(.secondary)
if let startTime = session.startTime {
Text(formatDuration(from: startTime, to: currentTime))
Text(timerInterval: startTime...Date.distantFuture, countsDown: false)
.font(.caption)
.foregroundColor(.secondary)
.monospacedDigit()
}
}
.frame(maxWidth: .infinity, alignment: .leading)
@@ -180,42 +177,12 @@ struct ActiveSessionBanner: View {
.fill(.green.opacity(0.1))
.stroke(.green.opacity(0.3), lineWidth: 1)
)
.onAppear {
startTimer()
}
.onDisappear {
stopTimer()
}
.navigationDestination(isPresented: $navigateToDetail) {
SessionDetailView(sessionId: session.id)
}
}
private func formatDuration(from start: Date, to end: Date) -> String {
let interval = end.timeIntervalSince(start)
let hours = Int(interval) / 3600
let minutes = Int(interval) % 3600 / 60
let seconds = Int(interval) % 60
if hours > 0 {
return String(format: "%dh %dm %ds", hours, minutes, seconds)
} else if minutes > 0 {
return String(format: "%dm %ds", minutes, seconds)
} else {
return String(format: "%ds", seconds)
}
}
private func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { _ in
currentTime = Date()
}
}
private func stopTimer() {
timer?.invalidate()
timer = nil
}
}
struct SessionRow: View {

View File

@@ -11,49 +11,52 @@ struct SettingsView: View {
@State private var activeSheet: SheetType?
var body: some View {
List {
SyncSection()
.environmentObject(dataManager.syncService)
NavigationStack {
List {
SyncSection()
.environmentObject(dataManager.syncService)
DataManagementSection(
activeSheet: $activeSheet
)
DataManagementSection(
activeSheet: $activeSheet
)
AppInfoSection()
}
.navigationTitle("Settings")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
if dataManager.isSyncing {
HStack(spacing: 2) {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .blue))
.scaleEffect(0.6)
AppInfoSection()
}
.navigationTitle("Settings")
.navigationBarTitleDisplayMode(.automatic)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
if dataManager.isSyncing {
HStack(spacing: 2) {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .blue))
.scaleEffect(0.6)
}
.padding(.horizontal, 6)
.padding(.vertical, 3)
.background(
Circle()
.fill(.regularMaterial)
)
.transition(.scale.combined(with: .opacity))
.animation(
.easeInOut(duration: 0.2), value: dataManager.isSyncing
)
}
.padding(.horizontal, 6)
.padding(.vertical, 3)
.background(
Circle()
.fill(.regularMaterial)
)
.transition(.scale.combined(with: .opacity))
.animation(
.easeInOut(duration: 0.2), value: dataManager.isSyncing
)
}
}
}
.sheet(
item: Binding<SheetType?>(
get: { activeSheet },
set: { activeSheet = $0 }
)
) { sheetType in
switch sheetType {
case .export(let data):
ExportDataView(data: data)
case .importData:
ImportDataView()
.sheet(
item: Binding<SheetType?>(
get: { activeSheet },
set: { activeSheet = $0 }
)
) { sheetType in
switch sheetType {
case .export(let data):
ExportDataView(data: data)
case .importData:
ImportDataView()
}
}
}
}
@@ -191,7 +194,7 @@ struct ExportDataView: View {
@State private var isCreatingFile = true
var body: some View {
NavigationView {
NavigationStack {
VStack(spacing: 30) {
if isCreatingFile {
// Loading state - more prominent
@@ -498,7 +501,7 @@ struct SyncSettingsView: View {
@State private var testResultMessage = ""
var body: some View {
NavigationView {
NavigationStack {
Form {
Section {
TextField("Server URL", text: $serverURL)
@@ -691,7 +694,7 @@ struct ImportDataView: View {
@State private var showingDocumentPicker = false
var body: some View {
NavigationView {
NavigationStack {
VStack(spacing: 20) {
Image(systemName: "square.and.arrow.down")
.font(.system(size: 60))