parent
969fa5094f
commit
d6e87daa3d
@ -0,0 +1,100 @@ |
||||
// |
||||
// OngoingContainerView.swift |
||||
// PadelClub |
||||
// |
||||
// Created by razmig on 07/11/2024. |
||||
// |
||||
|
||||
import SwiftUI |
||||
import LeStorage |
||||
|
||||
@Observable |
||||
class OngoingViewModel { |
||||
static let shared = OngoingViewModel() |
||||
|
||||
var destination: OngoingDestination? = .running |
||||
var hideUnconfirmedMatches: Bool = false |
||||
var hideNotReadyMatches: Bool = false |
||||
|
||||
func areFiltersEnabled() -> Bool { |
||||
hideUnconfirmedMatches || hideNotReadyMatches |
||||
} |
||||
|
||||
let defaultSorting : [MySortDescriptor<Match>] = [.keyPath(\Match.startDate!), .keyPath(\Match.index), .keyPath(\Match.courtIndexForSorting)] |
||||
|
||||
var runningAndNextMatches: [Match] { |
||||
DataStore.shared.runningAndNextMatches().sorted(using: defaultSorting, order: .ascending) |
||||
} |
||||
|
||||
var filteredRunningAndNextMatches: [Match] { |
||||
if destination == .followUp { |
||||
return runningAndNextMatches.filter({ |
||||
(hideUnconfirmedMatches == false || hideUnconfirmedMatches == true && $0.confirmed) |
||||
&& (hideNotReadyMatches == false || hideNotReadyMatches == true && $0.isReady() ) |
||||
}) |
||||
} else { |
||||
return runningAndNextMatches |
||||
} |
||||
} |
||||
} |
||||
|
||||
struct OngoingContainerView: View { |
||||
@Environment(NavigationViewModel.self) private var navigation: NavigationViewModel |
||||
@State private var showMatchPicker: Bool = false |
||||
|
||||
var body: some View { |
||||
@Bindable var navigation = navigation |
||||
@Bindable var ongoingViewModel = OngoingViewModel.shared |
||||
NavigationStack(path: $navigation.ongoingPath) { |
||||
VStack(spacing: 0) { |
||||
GenericDestinationPickerView(selectedDestination: $ongoingViewModel.destination, destinations: OngoingDestination.allCases, nilDestinationIsValid: false) |
||||
|
||||
switch ongoingViewModel.destination! { |
||||
case .running, .followUp: |
||||
OngoingView() |
||||
case .court, .free: |
||||
OngoingCourtView() |
||||
} |
||||
} |
||||
.toolbarBackground(.visible, for: .bottomBar) |
||||
.navigationBarTitleDisplayMode(.inline) |
||||
.toolbarBackground(.visible, for: .navigationBar) |
||||
.navigationTitle("Programmation") |
||||
.toolbar { |
||||
if ongoingViewModel.destination == .followUp { |
||||
ToolbarItem(placement: .topBarLeading) { |
||||
Menu { |
||||
Toggle(isOn: $ongoingViewModel.hideUnconfirmedMatches) { |
||||
Text("masquer non confirmés") |
||||
} |
||||
Toggle(isOn: $ongoingViewModel.hideNotReadyMatches) { |
||||
Text("masquer incomplets") |
||||
} |
||||
} label: { |
||||
Image(systemName: "line.3.horizontal.decrease.circle") |
||||
.resizable() |
||||
.scaledToFit() |
||||
.frame(minHeight: 32) |
||||
} |
||||
.symbolVariant(ongoingViewModel.areFiltersEnabled() ? .fill : .none) |
||||
} |
||||
} |
||||
ToolbarItem(placement: .topBarTrailing) { |
||||
Button { |
||||
showMatchPicker = true |
||||
} label: { |
||||
Image(systemName: "rectangle.stack.badge.plus") |
||||
.resizable() |
||||
.scaledToFit() |
||||
.frame(minHeight: 32) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
.environment(ongoingViewModel) |
||||
.sheet(isPresented: $showMatchPicker, content: { |
||||
FollowUpMatchView(selectedCourt: nil, allMatches: ongoingViewModel.runningAndNextMatches, autoDismiss: false) |
||||
.tint(.master) |
||||
}) |
||||
} |
||||
} |
||||
@ -0,0 +1,121 @@ |
||||
// |
||||
// OngoingDestination.swift |
||||
// PadelClub |
||||
// |
||||
// Created by razmig on 07/11/2024. |
||||
// |
||||
import SwiftUI |
||||
|
||||
enum OngoingDestination: Int, CaseIterable, Identifiable, Selectable, Equatable { |
||||
var id: Int { self.rawValue } |
||||
|
||||
static func == (lhs: OngoingDestination, rhs: OngoingDestination) -> Bool { |
||||
return lhs.id == rhs.id |
||||
} |
||||
|
||||
case running |
||||
case followUp |
||||
case court |
||||
case free |
||||
|
||||
var runningAndNextMatches: [Match] { |
||||
if self == .followUp { |
||||
OngoingViewModel.shared.filteredRunningAndNextMatches |
||||
} else { |
||||
OngoingViewModel.shared.runningAndNextMatches |
||||
} |
||||
} |
||||
|
||||
var sortedMatches: [Match] { |
||||
return runningAndNextMatches.filter({ self.shouldDisplay($0) }) |
||||
} |
||||
|
||||
var filteredMatches: [Match] { |
||||
sortedMatches.filter({ OngoingDestination.running.shouldDisplay($0) }) |
||||
} |
||||
|
||||
var sortedCourtIndex: [Int?] { |
||||
let courtUsed = sortedMatches.grouped(by: { $0.courtIndex }).keys |
||||
let sortedNumbers = courtUsed.sorted { (a, b) -> Bool in |
||||
switch (a, b) { |
||||
case (nil, _): return false |
||||
case (_, nil): return true |
||||
case let (a?, b?): return a < b |
||||
} |
||||
} |
||||
return sortedNumbers |
||||
} |
||||
|
||||
func contentUnavailable() -> some View { |
||||
switch self { |
||||
case .running: |
||||
ContentUnavailableView("Aucun match en cours", systemImage: "figure.tennis", description: Text("Tous vos matchs en cours seront visibles ici, quelque soit le tournoi.")) |
||||
case .followUp: |
||||
ContentUnavailableView("Aucun match à suivre", systemImage: "figure.tennis", description: Text("Tous vos matchs planifiés et confirmés, seront visibles ici, quelque soit le tournoi.")) |
||||
case .court: |
||||
ContentUnavailableView("Aucun match en cours", systemImage: "sportscourt", description: Text("Tous vos terrains correspondant aux matchs en cours seront visibles ici, quelque soit le tournoi.")) |
||||
case .free: |
||||
ContentUnavailableView("Aucun terrain libre", systemImage: "sportscourt", description: Text("Les terrains libres seront visibles ici, quelque soit le tournoi.")) |
||||
} |
||||
} |
||||
|
||||
func localizedFilterModeLabel() -> String { |
||||
switch self { |
||||
case .running: |
||||
return "En cours" |
||||
case .followUp: |
||||
return "À suivre" |
||||
case .court: |
||||
return "Terrains" |
||||
case .free: |
||||
return "Libres" |
||||
} |
||||
} |
||||
|
||||
func shouldDisplay(_ match: Match) -> Bool { |
||||
switch self { |
||||
case .running: |
||||
return match.isRunning() |
||||
case .court, .free: |
||||
return true |
||||
case .followUp: |
||||
return match.isRunning() == false |
||||
} |
||||
} |
||||
|
||||
func selectionLabel(index: Int) -> String { |
||||
localizedFilterModeLabel() |
||||
} |
||||
|
||||
func systemImage() -> String? { |
||||
switch self { |
||||
default: |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
func badgeValue() -> Int? { |
||||
switch self { |
||||
case .running: |
||||
sortedMatches.count |
||||
case .followUp: |
||||
sortedMatches.count |
||||
case .court: |
||||
sortedCourtIndex.filter({ index in |
||||
filteredMatches.filter({ $0.courtIndex == index }).isEmpty == false |
||||
}).count |
||||
case .free: |
||||
sortedCourtIndex.filter({ index in |
||||
filteredMatches.filter({ $0.courtIndex == index }).isEmpty |
||||
}).count |
||||
} |
||||
} |
||||
|
||||
func badgeValueColor() -> Color? { |
||||
nil |
||||
} |
||||
|
||||
func badgeImage() -> Badge? { |
||||
nil |
||||
} |
||||
} |
||||
Loading…
Reference in new issue