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