update tournament search feature

xcode16
Raz 1 year ago
parent 708e0aa481
commit 8e301a4f24
  1. 15
      PadelClub/Data/Federal/FederalTournament.swift
  2. 1
      PadelClub/Data/Federal/FederalTournamentHolder.swift
  3. 10
      PadelClub/Data/Tournament.swift
  4. 28
      PadelClub/ViewModel/FederalDataViewModel.swift
  5. 3
      PadelClub/Views/Components/GenericDestinationPickerView.swift
  6. 352
      PadelClub/Views/Navigation/Agenda/ActivityView.swift
  7. 256
      PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift
  8. 20
      PadelClub/Views/Shared/TournamentFilterView.swift

@ -7,10 +7,23 @@ import Foundation
import CoreLocation import CoreLocation
import LeStorage import LeStorage
enum DayPeriod { enum DayPeriod: CaseIterable, Identifiable {
var id: Self { self }
case all case all
case weekend case weekend
case week case week
func localizedDayPeriodLabel() -> String {
switch self {
case .all:
return "n'importe"
case .week:
return "la semaine"
case .weekend:
return "le week-end"
}
}
} }
// MARK: - FederalTournament // MARK: - FederalTournament

@ -16,6 +16,7 @@ protocol FederalTournamentHolder {
func clubLabel() -> String func clubLabel() -> String
func subtitleLabel() -> String func subtitleLabel() -> String
var dayDuration: Int { get } var dayDuration: Int { get }
var dayPeriod: DayPeriod { get }
} }
extension FederalTournamentHolder { extension FederalTournamentHolder {

@ -2134,6 +2134,16 @@ extension Tournament: FederalTournamentHolder {
self self
] ]
} }
var dayPeriod: DayPeriod {
let day = startDate.get(.weekday)
switch day {
case 2...6:
return .week
default:
return .weekend
}
}
} }
extension Tournament: TournamentBuildHolder { extension Tournament: TournamentBuildHolder {

@ -20,7 +20,9 @@ class FederalDataViewModel {
var selectedClubs: Set<String> = Set() var selectedClubs: Set<String> = Set()
var id: UUID = UUID() var id: UUID = UUID()
var searchAttemptCount: Int = 0 var searchAttemptCount: Int = 0
var dayDuration: Int?
var dayPeriod: DayPeriod = .all
func filterStatus() -> String { func filterStatus() -> String {
var labels: [String] = [] var labels: [String] = []
labels.append(contentsOf: levels.map { $0.localizedLabel() }) labels.append(contentsOf: levels.map { $0.localizedLabel() })
@ -32,6 +34,12 @@ class FederalDataViewModel {
} }
labels.append(contentsOf: clubNames) labels.append(contentsOf: clubNames)
if dayPeriod != .all {
labels.append(dayPeriod.localizedDayPeriodLabel())
}
if let dayDuration {
labels.append("max " + dayDuration.formatted() + " jour" + dayDuration.pluralSuffix)
}
return labels.joined(separator: ", ") return labels.joined(separator: ", ")
} }
@ -56,11 +64,13 @@ class FederalDataViewModel {
categories.removeAll() categories.removeAll()
ageCategories.removeAll() ageCategories.removeAll()
selectedClubs.removeAll() selectedClubs.removeAll()
dayPeriod = .all
dayDuration = nil
id = UUID() id = UUID()
} }
func areFiltersEnabled() -> Bool { func areFiltersEnabled() -> Bool {
(levels.isEmpty && categories.isEmpty && ageCategories.isEmpty && selectedClubs.isEmpty) == false (levels.isEmpty && categories.isEmpty && ageCategories.isEmpty && selectedClubs.isEmpty && dayPeriod == .all && dayDuration == nil) == false
} }
var filteredFederalTournaments: [FederalTournamentHolder] { var filteredFederalTournaments: [FederalTournamentHolder] {
@ -80,6 +90,10 @@ class FederalDataViewModel {
(ageCategories.isEmpty || tournament.tournaments.anySatisfy({ ageCategories.contains($0.age) })) (ageCategories.isEmpty || tournament.tournaments.anySatisfy({ ageCategories.contains($0.age) }))
&& &&
(selectedClubs.isEmpty || selectedClubs.contains(tournament.codeClub!)) (selectedClubs.isEmpty || selectedClubs.contains(tournament.codeClub!))
&&
(dayPeriod == .all || (dayPeriod != .all && dayPeriod == tournament.dayPeriod))
&&
(dayDuration == nil || (dayDuration != nil && dayDuration == tournament.dayDuration))
}) })
} }
@ -90,7 +104,11 @@ class FederalDataViewModel {
(categories.isEmpty || categories.contains(tournament.category)) (categories.isEmpty || categories.contains(tournament.category))
&& &&
(ageCategories.isEmpty || ageCategories.contains(tournament.age)) (ageCategories.isEmpty || ageCategories.contains(tournament.age))
&&
(dayPeriod == .all || (dayPeriod != .all && dayPeriod == tournament.dayPeriod))
&&
(dayDuration == nil || (dayDuration != nil && dayDuration == tournament.dayDuration))
if let codeClub = tournament.club()?.code { if let codeClub = tournament.club()?.code {
return firstPart && (selectedClubs.isEmpty || selectedClubs.contains(codeClub)) return firstPart && (selectedClubs.isEmpty || selectedClubs.contains(codeClub))
} else { } else {
@ -106,6 +124,10 @@ class FederalDataViewModel {
(ageCategories.isEmpty || ageCategories.contains(build.age)) (ageCategories.isEmpty || ageCategories.contains(build.age))
&& &&
(selectedClubs.isEmpty || selectedClubs.contains(tournament.codeClub!)) (selectedClubs.isEmpty || selectedClubs.contains(tournament.codeClub!))
&&
(dayPeriod == .all || (dayPeriod != .all && dayPeriod == tournament.dayPeriod))
&&
(dayDuration == nil || (dayDuration != nil && dayDuration == tournament.dayDuration))
} }
func gatherTournaments(clubs: [Club], startDate: Date, endDate: Date? = nil) async throws { func gatherTournaments(clubs: [Club], startDate: Date, endDate: Date? = nil) async throws {

@ -23,6 +23,7 @@ struct GenericDestinationPickerView<T: Identifiable & Selectable & Equatable >:
} label: { } label: {
Image(systemName: "wrench.and.screwdriver") Image(systemName: "wrench.and.screwdriver")
.foregroundColor(selectedDestination == nil ? .white : .black) .foregroundColor(selectedDestination == nil ? .white : .black)
.contentShape(Capsule())
} }
.padding() .padding()
.background { .background {
@ -41,9 +42,11 @@ struct GenericDestinationPickerView<T: Identifiable & Selectable & Equatable >:
if let systemImage = destination.systemImage() { if let systemImage = destination.systemImage() {
Image(systemName: systemImage) Image(systemName: systemImage)
.foregroundStyle(selectedDestination?.id == destination.id ? .white : .black) .foregroundStyle(selectedDestination?.id == destination.id ? .white : .black)
.contentShape(Capsule())
} else { } else {
Text(destination.selectionLabel(index: index)) Text(destination.selectionLabel(index: index))
.foregroundStyle(selectedDestination?.id == destination.id ? .white : .black) .foregroundStyle(selectedDestination?.id == destination.id ? .white : .black)
.contentShape(Capsule())
} }
} }
.padding() .padding()

@ -84,39 +84,26 @@ struct ActivityView: View {
.buttonBorderShape(.capsule) .buttonBorderShape(.capsule)
} }
@ViewBuilder
func _listView() -> some View {
switch navigation.agendaDestination! {
case .activity:
List {
EventListView(tournaments: runningTournaments, sortAscending: true)
}
case .history:
List {
EventListView(tournaments: endedTournaments, sortAscending: false)
}
case .tenup:
List {
EventListView(tournaments: federalDataViewModel.federalTournaments, sortAscending: true)
.id(uuid)
}
case .around:
List {
EventListView(tournaments: federalDataViewModel.searchedFederalTournaments, sortAscending: true)
.id(uuid)
}
}
}
var body: some View { var body: some View {
@Bindable var navigation = navigation @Bindable var navigation = navigation
NavigationStack(path: $navigation.path) { NavigationStack(path: $navigation.path) {
VStack(spacing: 0) { VStack(spacing: 0) {
GenericDestinationPickerView(selectedDestination: $navigation.agendaDestination, destinations: AgendaDestination.allCases, nilDestinationIsValid: false) GenericDestinationPickerView(selectedDestination: $navigation.agendaDestination, destinations: AgendaDestination.allCases, nilDestinationIsValid: false)
_listView() List {
.environment(\.viewStyle, viewStyle) switch navigation.agendaDestination! {
case .activity:
EventListView(tournaments: runningTournaments, sortAscending: true)
case .history:
EventListView(tournaments: endedTournaments, sortAscending: false)
case .tenup:
EventListView(tournaments: federalDataViewModel.federalTournaments, sortAscending: true)
.id(uuid)
case .around:
EventListView(tournaments: federalDataViewModel.searchedFederalTournaments, sortAscending: true)
}
}
.environment(\.viewStyle, viewStyle)
.environment(federalDataViewModel) .environment(federalDataViewModel)
.overlay { .overlay {
if let error, navigation.agendaDestination == .tenup { if let error, navigation.agendaDestination == .tenup {
@ -142,10 +129,14 @@ struct ActivityView: View {
} description: { } description: {
Text("Aucun tournoi ne correspond aux fitres que vous avez choisis : \(federalDataViewModel.filterStatus())") Text("Aucun tournoi ne correspond aux fitres que vous avez choisis : \(federalDataViewModel.filterStatus())")
} actions: { } actions: {
RowButtonView("modifier vos filtres") { FooterButtonView("supprimer vos filtres") {
federalDataViewModel.removeFilters() federalDataViewModel.removeFilters()
} }
.padding(.horizontal) .padding(.horizontal)
FooterButtonView("modifier vos filtres") {
presentFilterView = true
}
.padding(.horizontal)
} }
} else { } else {
_dataEmptyView() _dataEmptyView()
@ -153,166 +144,213 @@ struct ActivityView: View {
} }
} }
} }
//.searchable(text: $searchText) }
.onAppear { presentToolbar = true } //.searchable(text: $searchText)
.onDisappear { presentToolbar = false } .onAppear { presentToolbar = true }
.sheet(isPresented: $displaySearchView) { .onDisappear { presentToolbar = false }
NavigationStack { .refreshable {
TournamentLookUpView() if navigation.agendaDestination == .tenup {
.environment(federalDataViewModel) federalDataViewModel.federalTournaments.removeAll()
} NetworkFederalService.shared.formId = ""
_gatherFederalTournaments()
} }
.sheet(item: $newTournament) { tournament in }
EventCreationView(tournaments: [tournament], selectedClub: federalDataViewModel.selectedClub()) .task {
.environment(navigation) if navigation.agendaDestination == .tenup
.tint(.master) && dataStore.user.hasTenupClubs() == true
&& federalDataViewModel.federalTournaments.isEmpty {
_gatherFederalTournaments()
} }
.refreshable { }
if navigation.agendaDestination == .tenup { .onChange(of: navigation.agendaDestination) {
federalDataViewModel.federalTournaments.removeAll() if tournaments.isEmpty, viewStyle == .calendar {
NetworkFederalService.shared.formId = "" viewStyle = .list
_gatherFederalTournaments()
}
} }
.task {
if navigation.agendaDestination == .tenup if navigation.agendaDestination == .tenup
&& dataStore.user.hasTenupClubs() == true && dataStore.user.hasTenupClubs() == true
&& federalDataViewModel.federalTournaments.isEmpty { && federalDataViewModel.federalTournaments.isEmpty {
_gatherFederalTournaments() _gatherFederalTournaments()
}
} }
.onChange(of: navigation.agendaDestination) { }
if navigation.agendaDestination == .tenup .onChange(of: presentFilterView, { old, new in
&& dataStore.user.hasTenupClubs() == true if old == true, new == false { //closing filter view
&& federalDataViewModel.federalTournaments.isEmpty { if tournaments.isEmpty, viewStyle == .calendar {
_gatherFederalTournaments() viewStyle = .list
} }
} }
.toolbar { })
ToolbarItemGroup(placement: .topBarLeading) { .toolbarTitleDisplayMode(.large)
Button { .navigationTitle(TabDestination.activity.title)
switch viewStyle { .navigationDestination(for: Tournament.self) { tournament in
case .list: TournamentView(tournament: tournament)
viewStyle = .calendar }
case .calendar: .toolbar {
viewStyle = .list ToolbarItemGroup(placement: .topBarLeading) {
} Button {
} label: { switch viewStyle {
Image(systemName: "calendar.circle") case .list:
.resizable() viewStyle = .calendar
.scaledToFit() case .calendar:
.frame(minHeight: 28) viewStyle = .list
}
.symbolVariant(viewStyle == .calendar ? .fill : .none)
Button {
presentFilterView.toggle()
} label: {
Image(systemName: "line.3.horizontal.decrease.circle")
.resizable()
.scaledToFit()
.frame(minHeight: 28)
} }
.symbolVariant(federalDataViewModel.areFiltersEnabled() ? .fill : .none) } label: {
Image(systemName: "calendar.circle")
_pasteView() .resizable()
.scaledToFit()
.frame(minHeight: 32)
} }
.symbolVariant(viewStyle == .calendar ? .fill : .none)
ToolbarItem(placement: .topBarTrailing) {
Button { Button {
newTournament = Tournament.newEmptyInstance() presentFilterView.toggle()
} label: {
} label: { Image(systemName: "line.3.horizontal.decrease.circle")
Image(systemName: "plus.circle.fill") .resizable()
.resizable() .scaledToFit()
.scaledToFit() .frame(minHeight: 32)
.frame(minHeight: 28)
}
} }
.symbolVariant(federalDataViewModel.areFiltersEnabled() ? .fill : .none)
if presentToolbar { _pasteView()
if navigation.agendaDestination == .around, federalDataViewModel.searchedFederalTournaments.isEmpty == false { }
let filteredSearchedFederalTournaments = federalDataViewModel.filteredSearchedFederalTournaments
ToolbarItem(placement: .topBarTrailing) {
let status : String = filteredSearchedFederalTournaments.count.formatted() + " tournoi" + filteredSearchedFederalTournaments.count.pluralSuffix Button {
newTournament = Tournament.newEmptyInstance()
ToolbarItem(placement: .bottomBar) {
VStack { } label: {
Text(status) Image(systemName: "plus.circle.fill")
FooterButtonView("modifier les critères de recherche") { .resizable()
.scaledToFit()
.frame(minHeight: 32)
}
}
if presentToolbar, tournaments.isEmpty == false {
ToolbarItemGroup(placement: .bottomBar) {
VStack(spacing: 0) {
let searchStatus = _searchStatus()
if searchStatus.isEmpty == false {
Text(_searchStatus())
.font(.footnote)
.foregroundStyle(.secondary)
}
HStack {
if navigation.agendaDestination == .around {
FooterButtonView("modifier votre recherche") {
displaySearchView = true displaySearchView = true
} }
if federalDataViewModel.areFiltersEnabled() {
Text("ou")
}
}
if federalDataViewModel.areFiltersEnabled() {
FooterButtonView(_filterButtonTitle()) {
presentFilterView = true
}
} }
.font(.footnote)
}
} else if federalDataViewModel.areFiltersEnabled() {
ToolbarItem(placement: .status) {
Text(federalDataViewModel.filterStatus())
} }
.padding(.bottom, 8)
} }
} }
} }
.navigationTitle(TabDestination.activity.title) }
.navigationDestination(for: Tournament.self) { tournament in .sheet(isPresented: $presentFilterView) {
TournamentView(tournament: tournament) TournamentFilterView(federalDataViewModel: federalDataViewModel)
} .environment(navigation)
.sheet(isPresented: $presentFilterView) { .tint(.master)
TournamentFilterView(federalDataViewModel: federalDataViewModel) }
.environment(navigation) .sheet(isPresented: $presentClubSearchView, onDismiss: {
.tint(.master) if dataStore.user.hasTenupClubs() == true {
} federalDataViewModel.federalTournaments.removeAll()
.sheet(isPresented: $presentClubSearchView, onDismiss: { navigation.agendaDestination = .tenup
if dataStore.user.hasTenupClubs() == true {
federalDataViewModel.federalTournaments.removeAll()
navigation.agendaDestination = .tenup
}
}) {
ClubImportView()
.tint(.master)
} }
}) {
ClubImportView()
.tint(.master)
} }
} .sheet(isPresented: $displaySearchView) {
.sheet(item: $quickAccessScreen) { screen in
switch screen {
case .inscription(let pasteString):
NavigationStack { NavigationStack {
List { TournamentLookUpView()
Section { .environment(federalDataViewModel)
Text(pasteString) }
} header: { }
Text("Contenu du presse-papier") .sheet(item: $newTournament) { tournament in
} EventCreationView(tournaments: [tournament], selectedClub: federalDataViewModel.selectedClub())
.environment(navigation)
Section { .tint(.master)
ForEach(getRunningTournaments()) { tournament in }
NavigationLink { .sheet(item: $quickAccessScreen) { screen in
AddTeamView(tournament: tournament, pasteString: pasteString, editedTeam: nil) switch screen {
} label: { case .inscription(let pasteString):
VStack(alignment: .leading) { NavigationStack {
Text(tournament.tournamentTitle()) List {
Text(tournament.formattedDate()).foregroundStyle(.secondary) Section {
Text(pasteString)
} header: {
Text("Contenu du presse-papier")
}
Section {
ForEach(getRunningTournaments()) { tournament in
NavigationLink {
AddTeamView(tournament: tournament, pasteString: pasteString, editedTeam: nil)
} label: {
VStack(alignment: .leading) {
Text(tournament.tournamentTitle())
Text(tournament.formattedDate()).foregroundStyle(.secondary)
}
} }
} }
} header: {
Text("À coller dans la liste d'inscription")
} }
} header: {
Text("À coller dans la liste d'inscription")
} }
} .toolbar {
.toolbar { ToolbarItem(placement: .topBarLeading) {
ToolbarItem(placement: .topBarLeading) { Button("Fermer") {
Button("Fermer") { self.quickAccessScreen = nil
self.quickAccessScreen = nil }
} }
} }
.navigationTitle("Choix du tournoi")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
} }
.navigationTitle("Choix du tournoi")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
} }
} }
} }
} }
private func _searchStatus() -> String {
var searchStatus : [String] = []
if navigation.agendaDestination == .around, federalDataViewModel.searchedFederalTournaments.isEmpty == false {
let filteredSearchedFederalTournaments = federalDataViewModel.filteredSearchedFederalTournaments
let status : String = filteredSearchedFederalTournaments.count.formatted() + " tournoi" + filteredSearchedFederalTournaments.count.pluralSuffix
searchStatus.append(status)
}
if federalDataViewModel.areFiltersEnabled(), tournaments.isEmpty == false {
searchStatus.append(federalDataViewModel.filterStatus())
}
return searchStatus.joined(separator: " ")
}
private func _filterButtonTitle() -> String {
var prefix = "modifier "
if navigation.agendaDestination == .around, federalDataViewModel.searchedFederalTournaments.isEmpty == false {
prefix = ""
}
return prefix + "vos filtres"
}
private func _gatherFederalTournaments() { private func _gatherFederalTournaments() {
isGatheringFederalTournaments = true isGatheringFederalTournaments = true
Task { Task {
@ -422,7 +460,7 @@ struct ActivityView: View {
} description: { } description: {
Text("Aucun tournoi ne correspond aux critères sélectionnés.") Text("Aucun tournoi ne correspond aux critères sélectionnés.")
} actions: { } actions: {
RowButtonView("Modifier vos critères de recherche") { FooterButtonView("modifier vos critères de recherche") {
displaySearchView = true displaySearchView = true
} }
.padding() .padding()

@ -15,10 +15,6 @@ struct TournamentLookUpView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@State private var searchField: String = "" @State private var searchField: String = ""
@State private var sectionedTournaments: [String: [FederalTournament]] = [:]
@State private var dayPeriod: DayPeriod = .all
@State private var duration: Int = 3
@State var page: Int = 0 @State var page: Int = 0
@State var total: Int = 0 @State var total: Int = 0
@ -32,56 +28,49 @@ struct TournamentLookUpView: View {
@AppStorage("lastCity") private var city: String = "" @AppStorage("lastCity") private var city: String = ""
@State private var ligue: String = "" @State private var ligue: String = ""
@AppStorage("lastDistance") private var distance: Double = 30 @AppStorage("lastDistance") private var distance: Double = 30
@AppStorage("lastSortingOption") private var sortingOption: String = "_DIST_" @AppStorage("lastSortingOption") private var sortingOption: String = "dateDebut+asc"
@State private var requestedToGetAllPages: Bool = false @State private var requestedToGetAllPages: Bool = false
@AppStorage("lastNationalCup") private var nationalCup: Bool = false @AppStorage("lastNationalCup") private var nationalCup: Bool = false
@State private var revealSearchParameters: Bool = true @State private var revealSearchParameters: Bool = true
@State private var searchScope = FederalTournamentSearchScope.all @State private var presentAlert: Bool = false
var tournaments: [FederalTournament] { var tournaments: [FederalTournament] {
federalDataViewModel.searchedFederalTournaments federalDataViewModel.searchedFederalTournaments
} }
func canShowTournament(_ tournament: FederalTournament) -> Bool {
guard tournament.dayDuration <= duration else { return false }
guard (tournament.dayPeriod == dayPeriod && dayPeriod != .all) || dayPeriod == .all else { return false }
if searchField.isEmpty {
return true
} else {
return tournament.validForSearch(searchField, scope: searchScope)
}
}
var body: some View { var body: some View {
List { List {
searchParametersView searchParametersView
}
if tournaments.isEmpty == false && tournaments.count < total && total >= 200 && requestedToGetAllPages == false { .alert("Attention", isPresented: $presentAlert, actions: {
Section { Button {
Text("Il y a beacoup de tournois pour cette requête, êtes-vous sûr de vouloir tout récupérer ? Sinon essayez d'affiner votre recherche.") presentAlert = false
Button { requestedToGetAllPages = true
requestedToGetAllPages = true page += 1
page += 1 searching = true
searching = true Task {
Task { await getNewPage()
await getNewPage() searching = false
searching = false dismiss()
buildSectionedData()
}
} label: {
Label("Tout voir", systemImage: "arrow.down.circle")
}
} }
} label: {
Label("Tout voir", systemImage: "arrow.down.circle")
} }
} Button("Annuler") {
revealSearchParameters = true
presentAlert = false
}
}, message: {
Text("Il y a beacoup de tournois pour cette requête, êtes-vous sûr de vouloir tout récupérer ? Sinon essayez d'affiner votre recherche.")
})
.toolbarBackground(.visible, for: .bottomBar, .navigationBar) .toolbarBackground(.visible, for: .bottomBar, .navigationBar)
.navigationTitle("Chercher un tournoi") .navigationTitle("Chercher un tournoi")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.onChange(of: locationManager.city, perform: { newValue in .onChange(of: locationManager.city) {
if let newValue, city.isEmpty { if let newValue = locationManager.city, city.isEmpty {
city = newValue city = newValue
} }
}) }
.toolbarTitleDisplayMode(.large) .toolbarTitleDisplayMode(.large)
.toolbar { .toolbar {
ToolbarItem(placement: .bottomBar) { ToolbarItem(placement: .bottomBar) {
@ -90,14 +79,6 @@ struct TournamentLookUpView: View {
runSearch() runSearch()
} }
.disabled(searching) .disabled(searching)
} else if searchField.isEmpty == false && searchScope != .all {
let count = _totalVisibleEpreuves().count
VStack {
Text(searchField)
.foregroundStyle(.secondary)
Text(count.formatted() + " tournoi" + count.pluralSuffix)
}
.font(.caption)
} else if searching { } else if searching {
HStack(spacing: 20) { HStack(spacing: 20) {
Spacer() Spacer()
@ -109,25 +90,13 @@ struct TournamentLookUpView: View {
} }
Spacer() Spacer()
} }
} else {
let count = _totalVisibleEpreuves().count
Text(count.formatted() + " tournoi" + count.pluralSuffix)
.font(.caption)
} }
} }
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
Menu { Menu {
#if DEBUG
if tournaments.isEmpty == false { if tournaments.isEmpty == false {
Section { Section {
let preview = SharePreview(Text("Ma recherche de tournois"), icon: Image("PadelClub_logo_fondclair_transparent"))
ShareLink(item: renderedImage ?? Image(systemName: "photo"), preview: preview) {
if renderedImage == nil {
ProgressView()
} else {
Label("Par image (20max)", systemImage: "square.and.arrow.up")
.labelStyle(.titleAndIcon)
}
}
ShareLink(item: pastedTournaments) { ShareLink(item: pastedTournaments) {
Label("Par texte", systemImage: "square.and.arrow.up") Label("Par texte", systemImage: "square.and.arrow.up")
.labelStyle(.titleAndIcon) .labelStyle(.titleAndIcon)
@ -141,7 +110,9 @@ struct TournamentLookUpView: View {
} }
} }
Divider() Divider()
Button { #endif
Button(role: .destructive) {
tournamentLevels = Set() tournamentLevels = Set()
tournamentCategories = Set() tournamentCategories = Set()
city = "" city = ""
@ -150,8 +121,10 @@ struct TournamentLookUpView: View {
distance = 30 distance = 30
startDate = Date() startDate = Date()
endDate = Calendar.current.date(byAdding: .month, value: 3, to: Date())! endDate = Calendar.current.date(byAdding: .month, value: 3, to: Date())!
sortingOption = "_DIST_" sortingOption = "dateDebut+asc"
revealSearchParameters = true revealSearchParameters = true
federalDataViewModel.searchedFederalTournaments = []
federalDataViewModel.searchAttemptCount = 0
} label: { } label: {
Text("Ré-initialiser la recherche") Text("Ré-initialiser la recherche")
} }
@ -169,75 +142,6 @@ struct TournamentLookUpView: View {
Set(tournaments.map { $0.japMessage }).joined(separator: "\n") Set(tournaments.map { $0.japMessage }).joined(separator: "\n")
} }
private func isTypeLookedAfter(_ type: any TournamentBuildHolder) -> Bool {
if levels.contains(where: { level in
type.level == level
}) || levels.isEmpty {
if categories.contains(where: { category in
type.category == category
}) || categories.isEmpty {
return true
}
}
return false
}
@Environment(\.displayScale) var displayScale
@State private var renderedImage: Image?
@MainActor
func render() {
let renderer = ImageRenderer(content: tournamentsView)
renderer.scale = displayScale
renderer.isOpaque = true
if let uiImage = renderer.uiImage {
renderedImage = Image(uiImage: uiImage)
}
}
@ViewBuilder
private var tournamentsView: some View {
let tournaments = tournaments.prefix(20)
VStack {
ForEach(tournaments.indices, id: \.self) { tournamentIndex in
let tournament = tournaments[tournamentIndex]
HStack(alignment: .center) {
VStack(alignment: .leading) {
Text(tournament.libelle ?? "unknown").font(.headline)
if let club = tournament.nomClub {
Text(club)
.font(.footnote)
.lineLimit(1)
}
}
Spacer()
VStack(alignment: .trailing) {
if let startDate = tournament.dateDebut {
Text(startDate.monthYearFormatted)
HStack {
Text(startDate.formatted(.dateTime.weekday()))
Text(startDate.formatted(.dateTime.day())).font(.largeTitle)
}
}
if let distance = tournament.distanceEnMetres {
let measurement = Measurement(value: distance / 1000, unit: UnitLength.kilometers)
Text(measurement.formatted()).font(.caption)
}
}
}
.padding()
.foregroundColor(Color.black)
.background {
tournamentIndex%2 == 0 ? Color.mint : Color.cyan
}
}
}
.padding()
}
private var clubsFound: [String] { private var clubsFound: [String] {
Set(tournaments.compactMap { $0.nomClub }).sorted() Set(tournaments.compactMap { $0.nomClub }).sorted()
} }
@ -253,18 +157,17 @@ struct TournamentLookUpView: View {
federalDataViewModel.searchedFederalTournaments = [] federalDataViewModel.searchedFederalTournaments = []
searching = true searching = true
requestedToGetAllPages = false requestedToGetAllPages = false
renderedImage = nil
federalDataViewModel.searchAttemptCount += 1 federalDataViewModel.searchAttemptCount += 1
Task { Task {
await getNewPage() await getNewPage()
searching = false searching = false
dismiss() if tournaments.isEmpty == false && tournaments.count < total && total >= 200 && requestedToGetAllPages == false {
presentAlert = true
} else {
dismiss()
}
} }
} }
func buildSectionedData() {
sectionedTournaments = FederalTournament.sectionedData(from: tournaments)
}
private var distanceLimit: Measurement<UnitLength> { private var distanceLimit: Measurement<UnitLength> {
distanceLimit(distance: distance) distanceLimit(distance: distance)
@ -308,15 +211,9 @@ struct TournamentLookUpView: View {
print("count", count, total, tournaments.count, page) print("count", count, total, tournaments.count, page)
total = count total = count
if renderedImage == nil {
render()
}
if tournaments.count < count && page < total / 30 { if tournaments.count < count && page < total / 30 {
if total < 200 || requestedToGetAllPages { if total < 200 || requestedToGetAllPages {
page += 1 page += 1
await MainActor.run() {
buildSectionedData()
}
await getNewPage() await getNewPage()
} }
} else { } else {
@ -361,12 +258,10 @@ struct TournamentLookUpView: View {
city = "" city = ""
locationManager.location = nil locationManager.location = nil
locationManager.city = nil locationManager.city = nil
dayPeriod = .all
duration = 3
distance = 30 distance = 30
startDate = Date() startDate = Date()
endDate = Calendar.current.date(byAdding: .month, value: 3, to: Date())! endDate = Calendar.current.date(byAdding: .month, value: 3, to: Date())!
sortingOption = "_DIST_" sortingOption = "dateDebut+asc"
revealSearchParameters = true revealSearchParameters = true
} label: { } label: {
Label("Ré-initialiser la recherche", systemImage: "xmark.circle") Label("Ré-initialiser la recherche", systemImage: "xmark.circle")
@ -376,27 +271,27 @@ struct TournamentLookUpView: View {
@ViewBuilder @ViewBuilder
var searchParametersView: some View { var searchParametersView: some View {
@Bindable var federalDataViewModel = federalDataViewModel
Section { Section {
DatePicker("Début", selection: $startDate, displayedComponents: .date) DatePicker("Début", selection: $startDate, displayedComponents: .date)
DatePicker("Fin", selection: $endDate, displayedComponents: .date) DatePicker("Fin", selection: $endDate, displayedComponents: .date)
Picker(selection: $federalDataViewModel.dayDuration) {
Picker(selection: $duration) { Text("aucune").tag(nil as Int?)
Text("Aucune").tag(7) Text(1.formatted()).tag(1 as Int?)
Text(1.formatted()).tag(1) Text(2.formatted()).tag(2 as Int?)
Text(2.formatted()).tag(2) Text(3.formatted()).tag(3 as Int?)
Text(3.formatted()).tag(3)
} label: { } label: {
Text("Durée max (en jours)") Text("Durée max (en jours)")
} }
Picker(selection: $dayPeriod) { Picker(selection: $federalDataViewModel.dayPeriod) {
Text("N'importe").tag(DayPeriod.all) ForEach(DayPeriod.allCases) {
Text("le weekend").tag(DayPeriod.weekend) Text($0.localizedDayPeriodLabel()).tag($0)
Text("la semaine").tag(DayPeriod.week) }
} label: { } label: {
Text("En semaine ou week-end") Text("En semaine ou week-end")
} }
HStack { HStack {
TextField("Ville", text: $city) TextField("Ville", text: $city)
if let city = locationManager.city { if let city = locationManager.city {
@ -585,53 +480,4 @@ struct TournamentLookUpView: View {
return "Distance" return "Distance"
} }
} }
func _totalVisibleTournaments(_ date: Date? = nil) -> [FederalTournament] {
if let date {
if let tournaments = sectionedTournaments[URL.importDateFormatter.string(from: date)] {
let allTournaments = tournaments.filter({ canShowTournament($0) }).filter({ tournament in
if tournament.tournaments.count > 1 {
return tournament.tournaments.anySatisfy { isTypeLookedAfter($0) }
} else {
return true
}
})
return allTournaments
} else {
return []
}
} else {
let allTournaments = sectionedTournaments.values.flatMap({ $0 }).filter({ canShowTournament($0) }).filter({ tournament in
if tournament.tournaments.count > 1 {
return tournament.tournaments.anySatisfy { isTypeLookedAfter($0) }
} else {
return true
}
})
return allTournaments
}
}
func _totalVisibleEpreuves(_ date: Date? = nil) -> [any TournamentBuildHolder] {
if let date {
if let tournaments = sectionedTournaments[URL.importDateFormatter.string(from: date)] {
let allTournaments = tournaments
.filter({ canShowTournament($0) })
.compactMap({ $0.tournaments })
.flatMap({ $0 })
.filter({ isTypeLookedAfter($0) })
return allTournaments
} else {
return []
}
} else {
let allTournaments = sectionedTournaments.values.flatMap({ $0 })
.filter({ canShowTournament($0) })
.compactMap({ $0.tournaments })
.flatMap({ $0 })
.filter({ isTypeLookedAfter($0) })
return allTournaments
}
}
} }

@ -28,6 +28,26 @@ struct TournamentFilterView: View {
var body: some View { var body: some View {
NavigationView { NavigationView {
Form { Form {
Section {
Picker(selection: $federalDataViewModel.dayDuration) {
Text("aucune").tag(nil as Int?)
Text(1.formatted()).tag(1 as Int?)
Text(2.formatted()).tag(2 as Int?)
Text(3.formatted()).tag(3 as Int?)
} label: {
Text("Durée max (en jours)")
}
Picker(selection: $federalDataViewModel.dayPeriod) {
ForEach(DayPeriod.allCases) {
Text($0.localizedDayPeriodLabel()).tag($0)
}
} label: {
Text("En semaine ou week-end")
}
}
Section { Section {
ForEach(TournamentLevel.allCases) { level in ForEach(TournamentLevel.allCases) { level in
LabeledContent { LabeledContent {

Loading…
Cancel
Save