iOS and Android dependency updates and optimizations
This commit is contained in:
@@ -259,7 +259,7 @@ struct ProgressChartSection: View {
|
||||
.font(.title)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Text("No data available for \(selectedSystem.displayName) system")
|
||||
Text("No data available.")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
@@ -57,34 +57,29 @@ struct GymDetailView: View {
|
||||
.navigationTitle(gym?.name ?? "Gym Details")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
if gym != nil {
|
||||
Menu {
|
||||
Button {
|
||||
// Navigate to edit view
|
||||
} label: {
|
||||
Label("Edit Gym", systemImage: "pencil")
|
||||
}
|
||||
|
||||
Button(role: .destructive) {
|
||||
showingDeleteAlert = true
|
||||
} label: {
|
||||
Label("Delete Gym", systemImage: "trash")
|
||||
}
|
||||
Button {
|
||||
showingDeleteAlert = true
|
||||
} label: {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
Image(systemName: "trash")
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert("Delete Gym", isPresented: $showingDeleteAlert) {
|
||||
Button("Cancel", role: .cancel) {}
|
||||
.confirmationDialog(
|
||||
"Delete Gym",
|
||||
isPresented: $showingDeleteAlert,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete", role: .destructive) {
|
||||
if let gym = gym {
|
||||
dataManager.deleteGym(gym)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
Button("Cancel", role: .cancel) {}
|
||||
} message: {
|
||||
Text(
|
||||
"Are you sure you want to delete this gym? This will also delete all problems and sessions associated with this gym."
|
||||
@@ -131,7 +126,6 @@ struct GymHeaderCard: View {
|
||||
}
|
||||
}
|
||||
|
||||
// Supported Climb Types
|
||||
if !gym.supportedClimbTypes.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Climb Types")
|
||||
@@ -157,7 +151,6 @@ struct GymHeaderCard: View {
|
||||
}
|
||||
}
|
||||
|
||||
// Difficulty Systems
|
||||
if !gym.difficultySystems.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Difficulty Systems")
|
||||
@@ -330,6 +323,12 @@ struct SessionRowCard: View {
|
||||
let session: ClimbSession
|
||||
@EnvironmentObject var dataManager: ClimbingDataManager
|
||||
|
||||
private static let dateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .medium
|
||||
return formatter
|
||||
}()
|
||||
|
||||
private var sessionAttempts: [Attempt] {
|
||||
dataManager.attempts(forSession: session.id)
|
||||
}
|
||||
@@ -357,7 +356,7 @@ struct SessionRowCard: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("\(formatDate(session.date)) • \(sessionAttempts.count) attempts")
|
||||
Text("\(Self.dateFormatter.string(from: session.date)) • \(sessionAttempts.count) attempts")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
@@ -377,12 +376,6 @@ struct SessionRowCard: View {
|
||||
.shadow(color: .black.opacity(0.05), radius: 2, x: 0, y: 1)
|
||||
)
|
||||
}
|
||||
|
||||
private func formatDate(_ date: Date) -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .medium
|
||||
return formatter.string(from: date)
|
||||
}
|
||||
}
|
||||
|
||||
struct EmptyGymStateView: View {
|
||||
|
||||
@@ -61,7 +61,7 @@ struct ProblemDetailView: View {
|
||||
.navigationTitle("Problem Details")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
if problem != nil {
|
||||
Menu {
|
||||
Button {
|
||||
@@ -81,14 +81,18 @@ struct ProblemDetailView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert("Delete Problem", isPresented: $showingDeleteAlert) {
|
||||
Button("Cancel", role: .cancel) {}
|
||||
.confirmationDialog(
|
||||
"Delete Problem",
|
||||
isPresented: $showingDeleteAlert,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete", role: .destructive) {
|
||||
if let problem = problem {
|
||||
dataManager.deleteProblem(problem)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
Button("Cancel", role: .cancel) {}
|
||||
} message: {
|
||||
Text(
|
||||
"Are you sure you want to delete this problem? This will also delete all attempts associated with this problem."
|
||||
@@ -227,6 +231,12 @@ struct ProgressSummaryCard: View {
|
||||
let firstSuccess: (date: Date, result: AttemptResult)?
|
||||
@EnvironmentObject var themeManager: ThemeManager
|
||||
|
||||
private static let dateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .medium
|
||||
return formatter
|
||||
}()
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("Progress Summary")
|
||||
@@ -251,7 +261,7 @@ struct ProgressSummaryCard: View {
|
||||
.fontWeight(.medium)
|
||||
|
||||
Text(
|
||||
"\(formatDate(firstSuccess.date)) (\(firstSuccess.result.displayName))"
|
||||
"\(Self.dateFormatter.string(from: firstSuccess.date)) (\(firstSuccess.result.displayName))"
|
||||
)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(themeManager.accentColor)
|
||||
@@ -266,12 +276,6 @@ struct ProgressSummaryCard: View {
|
||||
.fill(.regularMaterial)
|
||||
)
|
||||
}
|
||||
|
||||
private func formatDate(_ date: Date) -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .medium
|
||||
return formatter.string(from: date)
|
||||
}
|
||||
}
|
||||
|
||||
struct PhotosSection: View {
|
||||
@@ -360,6 +364,12 @@ struct AttemptHistoryCard: View {
|
||||
let session: ClimbSession
|
||||
@EnvironmentObject var dataManager: ClimbingDataManager
|
||||
|
||||
private static let dateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .medium
|
||||
return formatter
|
||||
}()
|
||||
|
||||
private var gym: Gym? {
|
||||
dataManager.gym(withId: session.gymId)
|
||||
}
|
||||
@@ -368,7 +378,7 @@ struct AttemptHistoryCard: View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(formatDate(session.date))
|
||||
Text(Self.dateFormatter.string(from: session.date))
|
||||
.font(.headline)
|
||||
.fontWeight(.semibold)
|
||||
|
||||
@@ -403,12 +413,6 @@ struct AttemptHistoryCard: View {
|
||||
.shadow(color: .black.opacity(0.05), radius: 2, x: 0, y: 1)
|
||||
)
|
||||
}
|
||||
|
||||
private func formatDate(_ date: Date) -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .medium
|
||||
return formatter.string(from: date)
|
||||
}
|
||||
}
|
||||
|
||||
struct ImageViewerView: View {
|
||||
|
||||
@@ -46,7 +46,7 @@ struct SessionDetailView: View {
|
||||
.listRowInsets(EdgeInsets(top: 6, leading: 16, bottom: 6, trailing: 16))
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
|
||||
|
||||
if session.status == .active && musicService.isMusicEnabled && musicService.isAuthorized {
|
||||
MusicControlCard()
|
||||
.listRowInsets(EdgeInsets(top: 6, leading: 16, bottom: 6, trailing: 16))
|
||||
@@ -134,7 +134,7 @@ struct SessionDetailView: View {
|
||||
.navigationTitle("Session Details")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
if let session = session {
|
||||
if session.status == .active {
|
||||
Button("End Session") {
|
||||
@@ -143,15 +143,12 @@ struct SessionDetailView: View {
|
||||
}
|
||||
.foregroundColor(.orange)
|
||||
} else {
|
||||
Menu {
|
||||
Button(role: .destructive) {
|
||||
showingDeleteAlert = true
|
||||
} label: {
|
||||
Label("Delete Session", systemImage: "trash")
|
||||
}
|
||||
Button {
|
||||
showingDeleteAlert = true
|
||||
} label: {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
Image(systemName: "trash")
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,7 +185,7 @@ struct SessionDetailView: View {
|
||||
Button(action: { showingAddAttempt = true }) {
|
||||
Image(systemName: "plus")
|
||||
.font(.title2)
|
||||
.foregroundColor(.white) // Keep white for contrast on colored button
|
||||
.foregroundColor(.white)
|
||||
.frame(width: 56, height: 56)
|
||||
.background(Circle().fill(themeManager.accentColor))
|
||||
.shadow(radius: 4)
|
||||
@@ -196,14 +193,18 @@ struct SessionDetailView: View {
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
.alert("Delete Session", isPresented: $showingDeleteAlert) {
|
||||
Button("Cancel", role: .cancel) {}
|
||||
.confirmationDialog(
|
||||
"Delete Session",
|
||||
isPresented: $showingDeleteAlert,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete", role: .destructive) {
|
||||
if let session = session {
|
||||
dataManager.deleteSession(session)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
Button("Cancel", role: .cancel) {}
|
||||
} message: {
|
||||
Text(
|
||||
"Are you sure you want to delete this session? This will also delete all attempts associated with this session."
|
||||
@@ -239,6 +240,12 @@ struct SessionHeaderCard: View {
|
||||
let stats: SessionStats
|
||||
@EnvironmentObject var themeManager: ThemeManager
|
||||
|
||||
private static let dateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .full
|
||||
return formatter
|
||||
}()
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
@@ -246,7 +253,7 @@ struct SessionHeaderCard: View {
|
||||
.font(.title)
|
||||
.fontWeight(.bold)
|
||||
|
||||
Text(formatDate(session.date))
|
||||
Text(Self.dateFormatter.string(from: session.date))
|
||||
.font(.title2)
|
||||
.foregroundColor(themeManager.accentColor)
|
||||
|
||||
@@ -273,7 +280,6 @@ struct SessionHeaderCard: View {
|
||||
}
|
||||
}
|
||||
|
||||
// Status indicator
|
||||
HStack {
|
||||
Image(systemName: session.status == .active ? "play.fill" : "checkmark.circle.fill")
|
||||
.foregroundColor(session.status == .active ? .green : themeManager.accentColor)
|
||||
@@ -298,12 +304,6 @@ struct SessionHeaderCard: View {
|
||||
.fill(.regularMaterial)
|
||||
)
|
||||
}
|
||||
|
||||
private func formatDate(_ date: Date) -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .full
|
||||
return formatter.string(from: date)
|
||||
}
|
||||
}
|
||||
|
||||
struct SessionStatsCard: View {
|
||||
@@ -356,8 +356,6 @@ struct StatItem: View {
|
||||
}
|
||||
}
|
||||
|
||||
// AttemptsSection removed as it is now integrated into the main List
|
||||
|
||||
struct AttemptCard: View {
|
||||
let attempt: Attempt
|
||||
let problem: Problem
|
||||
@@ -404,7 +402,7 @@ struct AttemptCard: View {
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(Color(uiColor: .secondarySystemGroupedBackground)) // Better contrast in light mode
|
||||
.fill(Color(uiColor: .secondarySystemGroupedBackground))
|
||||
.shadow(color: .black.opacity(0.05), radius: 2, x: 0, y: 1)
|
||||
)
|
||||
}
|
||||
@@ -451,7 +449,7 @@ struct SessionStats {
|
||||
|
||||
struct MusicControlCard: View {
|
||||
@EnvironmentObject var musicService: MusicService
|
||||
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Image(systemName: "music.note")
|
||||
@@ -460,11 +458,11 @@ struct MusicControlCard: View {
|
||||
.frame(width: 40, height: 40)
|
||||
.background(Color.pink.opacity(0.1))
|
||||
.clipShape(Circle())
|
||||
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Music")
|
||||
.font(.headline)
|
||||
|
||||
|
||||
if let playlistId = musicService.selectedPlaylistId,
|
||||
let playlist = musicService.playlists.first(where: { $0.id.rawValue == playlistId }) {
|
||||
Text(playlist.name)
|
||||
@@ -476,9 +474,9 @@ struct MusicControlCard: View {
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Spacer()
|
||||
|
||||
|
||||
Button(action: {
|
||||
musicService.togglePlayback()
|
||||
}) {
|
||||
|
||||
@@ -77,16 +77,23 @@ struct GymsList: View {
|
||||
.tint(.indigo)
|
||||
}
|
||||
}
|
||||
.alert("Delete Gym", isPresented: .constant(gymToDelete != nil)) {
|
||||
Button("Cancel", role: .cancel) {
|
||||
gymToDelete = nil
|
||||
}
|
||||
.confirmationDialog(
|
||||
"Delete Gym",
|
||||
isPresented: .init(
|
||||
get: { gymToDelete != nil },
|
||||
set: { if !$0 { gymToDelete = nil } }
|
||||
),
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete", role: .destructive) {
|
||||
if let gym = gymToDelete {
|
||||
dataManager.deleteGym(gym)
|
||||
gymToDelete = nil
|
||||
}
|
||||
}
|
||||
Button("Cancel", role: .cancel) {
|
||||
gymToDelete = nil
|
||||
}
|
||||
} message: {
|
||||
Text(
|
||||
"Are you sure you want to delete this gym? This will also delete all associated problems and sessions."
|
||||
|
||||
@@ -80,7 +80,7 @@ struct ProblemsView: View {
|
||||
if cachedFilteredProblems.isEmpty {
|
||||
VStack(spacing: 0) {
|
||||
headerContent
|
||||
|
||||
|
||||
EmptyProblemsView(
|
||||
isEmpty: dataManager.problems.isEmpty,
|
||||
isFiltered: !dataManager.problems.isEmpty
|
||||
@@ -209,16 +209,23 @@ struct ProblemsView: View {
|
||||
.sheet(item: $problemToEdit) { problem in
|
||||
AddEditProblemView(problemId: problem.id)
|
||||
}
|
||||
.alert("Delete Problem", isPresented: .constant(problemToDelete != nil)) {
|
||||
Button("Cancel", role: .cancel) {
|
||||
problemToDelete = nil
|
||||
}
|
||||
.confirmationDialog(
|
||||
"Delete Problem",
|
||||
isPresented: .init(
|
||||
get: { problemToDelete != nil },
|
||||
set: { if !$0 { problemToDelete = nil } }
|
||||
),
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete", role: .destructive) {
|
||||
if let problem = problemToDelete {
|
||||
dataManager.deleteProblem(problem)
|
||||
problemToDelete = nil
|
||||
}
|
||||
}
|
||||
Button("Cancel", role: .cancel) {
|
||||
problemToDelete = nil
|
||||
}
|
||||
} message: {
|
||||
Text(
|
||||
"Are you sure you want to delete this problem? This will also delete all associated attempts."
|
||||
@@ -550,7 +557,7 @@ struct FilterSheet: View {
|
||||
let filteredProblems: [Problem]
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@EnvironmentObject var themeManager: ThemeManager
|
||||
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ScrollView {
|
||||
@@ -582,7 +589,7 @@ struct FilterSheet: View {
|
||||
.foregroundColor(themeManager.accentColor)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
if selectedClimbType != nil || selectedGym != nil {
|
||||
Button(action: {
|
||||
|
||||
@@ -62,7 +62,6 @@ struct SessionsView: View {
|
||||
)
|
||||
}
|
||||
|
||||
// View mode toggle
|
||||
if !dataManager.sessions.isEmpty || dataManager.activeSession != nil {
|
||||
Button(action: {
|
||||
withAnimation(.easeInOut(duration: 0.2)) {
|
||||
@@ -116,7 +115,6 @@ struct SessionsList: View {
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
// Active session banner section
|
||||
if let activeSession = dataManager.activeSession,
|
||||
let gym = dataManager.gym(withId: activeSession.gymId)
|
||||
{
|
||||
@@ -130,7 +128,6 @@ struct SessionsList: View {
|
||||
}
|
||||
}
|
||||
|
||||
// Completed sessions section
|
||||
if !completedSessions.isEmpty {
|
||||
Section {
|
||||
ForEach(completedSessions) { session in
|
||||
@@ -156,16 +153,23 @@ struct SessionsList: View {
|
||||
}
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
.alert("Delete Session", isPresented: .constant(sessionToDelete != nil)) {
|
||||
Button("Cancel", role: .cancel) {
|
||||
sessionToDelete = nil
|
||||
}
|
||||
.confirmationDialog(
|
||||
"Delete Session",
|
||||
isPresented: .init(
|
||||
get: { sessionToDelete != nil },
|
||||
set: { if !$0 { sessionToDelete = nil } }
|
||||
),
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete", role: .destructive) {
|
||||
if let session = sessionToDelete {
|
||||
dataManager.deleteSession(session)
|
||||
sessionToDelete = nil
|
||||
}
|
||||
}
|
||||
Button("Cancel", role: .cancel) {
|
||||
sessionToDelete = nil
|
||||
}
|
||||
} message: {
|
||||
Text(
|
||||
"Are you sure you want to delete this session? This will also delete all attempts associated with this session."
|
||||
@@ -178,18 +182,8 @@ struct ActiveSessionBanner: View {
|
||||
let session: ClimbSession
|
||||
let gym: Gym
|
||||
@EnvironmentObject var dataManager: ClimbingDataManager
|
||||
@State private var navigateToDetail = false
|
||||
|
||||
// Access MusicService via DataManager if possible, or EnvironmentObject if injected
|
||||
// Since DataManager holds MusicService, we can access it through there if we expose it or inject it.
|
||||
// In SettingsView we saw .environmentObject(dataManager.musicService).
|
||||
// We should probably inject it here too or access via dataManager if it's public.
|
||||
// Let's check ClimbingDataManager again. It has `let musicService = MusicService.shared`.
|
||||
// But it's not @Published so it won't trigger updates unless we observe the service itself.
|
||||
// The best way is to use @EnvironmentObject var musicService: MusicService
|
||||
// and ensure it's injected in the parent view.
|
||||
|
||||
@EnvironmentObject var musicService: MusicService
|
||||
@State private var navigateToDetail = false
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
@@ -213,7 +207,7 @@ struct ActiveSessionBanner: View {
|
||||
.foregroundColor(.secondary)
|
||||
.monospacedDigit()
|
||||
}
|
||||
|
||||
|
||||
if musicService.isMusicEnabled && musicService.isAuthorized {
|
||||
Button(action: {
|
||||
musicService.togglePlayback()
|
||||
@@ -251,7 +245,6 @@ struct ActiveSessionBanner: View {
|
||||
.fill(.green.opacity(0.1))
|
||||
.stroke(.green.opacity(0.3), lineWidth: 1)
|
||||
)
|
||||
|
||||
.navigationDestination(isPresented: $navigateToDetail) {
|
||||
SessionDetailView(sessionId: session.id)
|
||||
}
|
||||
@@ -262,6 +255,12 @@ struct SessionRow: View {
|
||||
let session: ClimbSession
|
||||
@EnvironmentObject var dataManager: ClimbingDataManager
|
||||
|
||||
private static let dateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .medium
|
||||
return formatter
|
||||
}()
|
||||
|
||||
private var gym: Gym? {
|
||||
dataManager.gym(withId: session.gymId)
|
||||
}
|
||||
@@ -275,7 +274,7 @@ struct SessionRow: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(formatDate(session.date))
|
||||
Text(Self.dateFormatter.string(from: session.date))
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
@@ -295,12 +294,6 @@ struct SessionRow: View {
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
|
||||
private func formatDate(_ date: Date) -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .medium
|
||||
return formatter.string(from: date)
|
||||
}
|
||||
}
|
||||
|
||||
struct EmptySessionsView: View {
|
||||
|
||||
Reference in New Issue
Block a user