You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
528 lines
21 KiB
528 lines
21 KiB
//
|
|
// TournamentLookUpView.swift
|
|
// PadelClub
|
|
//
|
|
// Created by razmig on 08/09/2024.
|
|
//
|
|
|
|
import SwiftUI
|
|
import CoreLocation
|
|
import CoreLocationUI
|
|
|
|
struct TournamentLookUpView: View {
|
|
@EnvironmentObject var dataStore: DataStore
|
|
@Environment(FederalDataViewModel.self) var federalDataViewModel: FederalDataViewModel
|
|
@StateObject var locationManager = LocationManager()
|
|
@Environment(\.dismiss) private var dismiss
|
|
@FocusState private var isFocused: Bool
|
|
|
|
@State private var searchField: String = ""
|
|
@State var page: Int = 0
|
|
@State var total: Int = 0
|
|
@State private var showingSettingsAlert = false
|
|
@State private var searching: Bool = false
|
|
@State private var requestedToGetAllPages: Bool = false
|
|
@State private var revealSearchParameters: Bool = true
|
|
@State private var presentAlert: Bool = false
|
|
@State private var confirmSearch: Bool = false
|
|
@State private var locationRequested = false
|
|
|
|
var tournaments: [FederalTournament] {
|
|
federalDataViewModel.searchedFederalTournaments
|
|
}
|
|
|
|
var showLastError: Binding<Bool> {
|
|
Binding {
|
|
locationManager.lastError != nil
|
|
} set: { value in
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
List {
|
|
searchParametersView
|
|
}
|
|
.alert(isPresented: showLastError, error: locationManager.lastError as? LocationManager.LocationError, actions: {
|
|
Button("Annuler", role: .cancel) {
|
|
|
|
}
|
|
})
|
|
.confirmationDialog("Attention", isPresented: $confirmSearch, titleVisibility: .visible) {
|
|
Button("Cherchez quand même") {
|
|
requestedToGetAllPages = true
|
|
runSearch()
|
|
}
|
|
|
|
Button("Annuler", role: .cancel) {
|
|
|
|
}
|
|
} message: {
|
|
Text("Aucune ville n'a été indiqué, il est préférable de se localiser ou d'indiquer une ville pour réduire le nombre de résultat.")
|
|
}
|
|
.alert(isPresented: $showingSettingsAlert) {
|
|
Alert(
|
|
title: Text("Réglages"),
|
|
message: Text("Pour trouver les clubs autour de vous, vous devez l'autorisation à Padel Club de récupérer votre position."),
|
|
primaryButton: .default(Text("Ouvrir les réglages"), action: {
|
|
_openSettings()
|
|
}),
|
|
secondaryButton: .cancel()
|
|
)
|
|
}
|
|
.alert("Attention", isPresented: $presentAlert, actions: {
|
|
Button {
|
|
presentAlert = false
|
|
requestedToGetAllPages = true
|
|
page += 1
|
|
searching = true
|
|
Task {
|
|
await getNewPage()
|
|
searching = false
|
|
dismiss()
|
|
}
|
|
} label: {
|
|
Label("Tout voir", systemImage: "arrow.down.circle")
|
|
}
|
|
Button("Annuler") {
|
|
revealSearchParameters = true
|
|
presentAlert = false
|
|
}
|
|
}, message: {
|
|
if dataStore.appSettings.city.isEmpty {
|
|
Text("Il est préférable de se localiser ou d'indiquer une ville pour réduire le nombre de résultat.")
|
|
} else {
|
|
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)
|
|
.navigationTitle("Chercher un tournoi")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.onChange(of: locationManager.city) {
|
|
if locationRequested, let newValue = locationManager.city {
|
|
dataStore.appSettings.city = newValue
|
|
locationRequested = false
|
|
}
|
|
}
|
|
.toolbarTitleDisplayMode(.large)
|
|
.toolbar {
|
|
ToolbarItem(placement: .bottomBar) {
|
|
if revealSearchParameters {
|
|
FooterButtonView("Lancer la recherche") {
|
|
if dataStore.appSettings.city.isEmpty {
|
|
confirmSearch = true
|
|
} else {
|
|
runSearch()
|
|
}
|
|
}
|
|
.disabled(searching)
|
|
} else if searching {
|
|
HStack(spacing: 20) {
|
|
Spacer()
|
|
ProgressView()
|
|
if total > 0 {
|
|
let percent = Double(tournaments.count) / Double(total)
|
|
Text(percent.formatted(.percent.precision(.significantDigits(1...3))) + " en récupération de Tenup")
|
|
.font(.caption)
|
|
}
|
|
Spacer()
|
|
}
|
|
}
|
|
}
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Menu {
|
|
#if DEBUG
|
|
if tournaments.isEmpty == false {
|
|
Section {
|
|
ShareLink(item: pastedTournaments) {
|
|
Label("Par texte", systemImage: "square.and.arrow.up")
|
|
.labelStyle(.titleAndIcon)
|
|
}
|
|
ShareLink(item: japList) {
|
|
Label("JAP liste", systemImage: "square.and.arrow.up")
|
|
.labelStyle(.titleAndIcon)
|
|
}
|
|
} header: {
|
|
Text("Partager les résultats")
|
|
}
|
|
}
|
|
Divider()
|
|
#endif
|
|
|
|
Button(role: .destructive) {
|
|
dataStore.appSettings.resetSearch()
|
|
locationManager.location = nil
|
|
locationManager.city = nil
|
|
revealSearchParameters = true
|
|
federalDataViewModel.searchedFederalTournaments = []
|
|
federalDataViewModel.searchAttemptCount = 0
|
|
} label: {
|
|
Text("Ré-initialiser la recherche")
|
|
}
|
|
} label: {
|
|
Label("Options", systemImage: "ellipsis.circle")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var pastedTournaments: String {
|
|
tournaments.map { $0.shareMessage }.joined()
|
|
}
|
|
|
|
var japList: String {
|
|
Set(tournaments.map { $0.japMessage }).joined(separator: "\n")
|
|
}
|
|
|
|
private var clubsFound: [String] {
|
|
Set(tournaments.compactMap { $0.nomClub }).sorted()
|
|
}
|
|
|
|
private var liguesFound: [String] {
|
|
Set(tournaments.compactMap { $0.nomLigue }).sorted()
|
|
}
|
|
|
|
private func runSearch() {
|
|
dataStore.appSettingsStorage.write()
|
|
revealSearchParameters = false
|
|
total = 0
|
|
page = 0
|
|
federalDataViewModel.searchedFederalTournaments = []
|
|
searching = true
|
|
requestedToGetAllPages = false
|
|
federalDataViewModel.searchAttemptCount += 1
|
|
federalDataViewModel.dayPeriod = dataStore.appSettings.dayPeriod
|
|
federalDataViewModel.dayDuration = dataStore.appSettings.dayDuration
|
|
federalDataViewModel.levels = Set(levels)
|
|
federalDataViewModel.categories = Set(categories)
|
|
federalDataViewModel.ageCategories = Set(ages)
|
|
federalDataViewModel.lastError = nil
|
|
|
|
Task {
|
|
await getNewPage()
|
|
searching = false
|
|
if tournaments.isEmpty == false && tournaments.count < total && total >= 200 && requestedToGetAllPages == false {
|
|
presentAlert = true
|
|
} else {
|
|
dismiss()
|
|
}
|
|
}
|
|
}
|
|
|
|
private var distanceLimit: Measurement<UnitLength> {
|
|
distanceLimit(distance: dataStore.appSettings.distance)
|
|
}
|
|
|
|
private func distanceLimit(distance: Double) -> Measurement<UnitLength> {
|
|
Measurement(value: distance, unit: .kilometers)
|
|
}
|
|
|
|
private var categories: [TournamentCategory] {
|
|
dataStore.appSettings.tournamentCategories.compactMap { TournamentCategory(rawValue: $0) }
|
|
}
|
|
|
|
private var levels: [TournamentLevel] {
|
|
dataStore.appSettings.tournamentLevels.compactMap { TournamentLevel(rawValue: $0) }
|
|
}
|
|
|
|
private var ages: [FederalTournamentAge] {
|
|
dataStore.appSettings.tournamentAges.compactMap { FederalTournamentAge(rawValue: $0) }
|
|
}
|
|
|
|
private var types: [FederalTournamentType] {
|
|
dataStore.appSettings.tournamentTypes.compactMap { FederalTournamentType(rawValue: $0) }
|
|
}
|
|
|
|
func getNewPage() async {
|
|
do {
|
|
if NetworkFederalService.shared.formId.isEmpty {
|
|
await getNewBuildForm()
|
|
} else {
|
|
let commands = try await NetworkFederalService.shared.getAllFederalTournaments(sortingOption: dataStore.appSettings.sortingOption, page: page, startDate: dataStore.appSettings.startDate, endDate: dataStore.appSettings.endDate, city: dataStore.appSettings.city, distance: dataStore.appSettings.distance, categories: categories, levels: levels, lat: locationManager.location?.coordinate.latitude.formatted(.number.locale(Locale(identifier: "us"))), lng: locationManager.location?.coordinate.longitude.formatted(.number.locale(Locale(identifier: "us"))), ages: ages, types: types, nationalCup: dataStore.appSettings.nationalCup)
|
|
if commands.anySatisfy({ $0.command == "alert" }) {
|
|
federalDataViewModel.lastError = .maintenance
|
|
}
|
|
|
|
let resultCommand = commands.first(where: { $0.results != nil })
|
|
|
|
if let newTournaments = resultCommand?.results?.items {
|
|
newTournaments.forEach { ft in
|
|
// let isValid = ft.tournaments.anySatisfy({ build in
|
|
// let ageValid = ages.isEmpty ? true : ages.contains(build.age)
|
|
// let levelValid = levels.isEmpty ? true : levels.contains(build.level)
|
|
// let categoryValid = categories.isEmpty ? true : categories.contains(build.category)
|
|
// return ageValid && levelValid && categoryValid
|
|
// })
|
|
if tournaments.contains(where: { $0.id == ft.id }) == false {
|
|
federalDataViewModel.searchedFederalTournaments.append(ft)
|
|
}
|
|
}
|
|
}
|
|
if let count = resultCommand?.results?.nb_results {
|
|
print("count", count, total, tournaments.count, page)
|
|
total = count
|
|
|
|
if tournaments.count < count && page < total / 30 {
|
|
if total < 200 || requestedToGetAllPages {
|
|
page += 1
|
|
await getNewPage()
|
|
}
|
|
} else {
|
|
print("finished")
|
|
}
|
|
} else {
|
|
print("total results not found")
|
|
}
|
|
}
|
|
} catch {
|
|
print("getNewPage", error)
|
|
await getNewBuildForm()
|
|
}
|
|
}
|
|
|
|
func getNewBuildForm() async {
|
|
do {
|
|
try await NetworkFederalService.shared.getNewBuildForm()
|
|
await getNewPage()
|
|
} catch {
|
|
print("getNewBuildForm", error)
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
var searchParametersView: some View {
|
|
@Bindable var appSettings = dataStore.appSettings
|
|
Section {
|
|
DatePicker("Début", selection: $appSettings.startDate, displayedComponents: .date)
|
|
DatePicker("Fin", selection: $appSettings.endDate, displayedComponents: .date)
|
|
Picker(selection: $appSettings.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 souhaitée (en jours)")
|
|
}
|
|
|
|
Picker(selection: $appSettings.dayPeriod) {
|
|
ForEach(DayPeriod.allCases) {
|
|
Text($0.localizedDayPeriodLabel().capitalized).tag($0)
|
|
}
|
|
} label: {
|
|
Text("En semaine ou week-end")
|
|
}
|
|
|
|
HStack {
|
|
TextField("Ville", text: $appSettings.city)
|
|
.onSubmit(of: .text) {
|
|
locationManager.city = nil
|
|
locationManager.location = nil
|
|
}
|
|
.focused($isFocused)
|
|
.onChange(of: isFocused) {
|
|
if isFocused {
|
|
appSettings.city = ""
|
|
}
|
|
}
|
|
|
|
// if let city = locationManager.city {
|
|
// Divider()
|
|
// Text(city).italic()
|
|
// }
|
|
if locationManager.requestStarted {
|
|
ProgressView()
|
|
} else if locationManager.manager.authorizationStatus != .restricted {
|
|
LocationButton {
|
|
if locationManager.manager.authorizationStatus == .notDetermined {
|
|
locationRequested = true
|
|
locationManager.manager.requestWhenInUseAuthorization()
|
|
} else if locationManager.manager.authorizationStatus == .denied {
|
|
showingSettingsAlert = true
|
|
} else {
|
|
locationRequested = true
|
|
locationManager.requestLocation()
|
|
}
|
|
}
|
|
.symbolVariant(.fill)
|
|
.foregroundColor (Color.white)
|
|
.cornerRadius (20)
|
|
.font(.system(size: 12))
|
|
}
|
|
}
|
|
|
|
Picker(selection: $appSettings.distance) {
|
|
Text(distanceLimit(distance:30).formatted()).tag(30.0)
|
|
Text(distanceLimit(distance:50).formatted()).tag(50.0)
|
|
Text(distanceLimit(distance:60).formatted()).tag(60.0)
|
|
Text(distanceLimit(distance:90).formatted()).tag(90.0)
|
|
Text(distanceLimit(distance:150).formatted()).tag(150.0)
|
|
Text(distanceLimit(distance:200).formatted()).tag(200.0)
|
|
Text(distanceLimit(distance:400).formatted()).tag(400.0)
|
|
Text("Aucune").tag(3000.0)
|
|
} label: {
|
|
Text("Distance max")
|
|
}
|
|
|
|
Picker(selection: $appSettings.sortingOption) {
|
|
Text("Distance").tag("_DIST_")
|
|
Text("Date de début").tag("dateDebut+asc")
|
|
Text("Date de fin").tag("dateFin+asc")
|
|
} label: {
|
|
Text("Trier par")
|
|
}
|
|
|
|
NavigationLink {
|
|
List([TournamentCategory.men, TournamentCategory.women, TournamentCategory.mix], selection: $appSettings.tournamentCategories) { type in
|
|
Text(type.localizedCategoryLabel())
|
|
}
|
|
.navigationTitle("Catégories")
|
|
.environment(\.editMode, Binding.constant(EditMode.active))
|
|
} label: {
|
|
HStack {
|
|
Text("Catégorie")
|
|
Spacer()
|
|
categoriesLabel
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
|
|
NavigationLink {
|
|
List([TournamentLevel.p25, TournamentLevel.p100, TournamentLevel.p250, TournamentLevel.p500, TournamentLevel.p1000, TournamentLevel.p1500, TournamentLevel.p2000], selection: $appSettings.tournamentLevels) { type in
|
|
Text(type.localizedLevelLabel())
|
|
}
|
|
.navigationTitle("Niveaux")
|
|
.environment(\.editMode, Binding.constant(EditMode.active))
|
|
} label: {
|
|
HStack {
|
|
Text("Niveau")
|
|
Spacer()
|
|
levelsLabel
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
|
|
NavigationLink {
|
|
List([FederalTournamentAge.senior, FederalTournamentAge.a45, FederalTournamentAge.a55, FederalTournamentAge.a17_18, FederalTournamentAge.a15_16, FederalTournamentAge.a13_14, FederalTournamentAge.a11_12], selection: $appSettings.tournamentAges) { type in
|
|
Text(type.localizedFederalAgeLabel())
|
|
}
|
|
.navigationTitle("Limites d'âge")
|
|
.environment(\.editMode, Binding.constant(EditMode.active))
|
|
} label: {
|
|
HStack {
|
|
Text("Limite d'âge")
|
|
Spacer()
|
|
if dataStore.appSettings.tournamentAges.isEmpty || dataStore.appSettings.tournamentAges.count == FederalTournamentAge.allCases.count {
|
|
Text("Tous les âges")
|
|
.foregroundStyle(.secondary)
|
|
} else {
|
|
Text(ages.map({ $0.localizedFederalAgeLabel()}).joined(separator: ", "))
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
}
|
|
|
|
NavigationLink {
|
|
List(FederalTournamentType.allCases, selection: $appSettings.tournamentTypes) { type in
|
|
Text(type.localizedLabel())
|
|
}
|
|
.navigationTitle("Types de tournoi")
|
|
.environment(\.editMode, Binding.constant(EditMode.active))
|
|
} label: {
|
|
HStack {
|
|
Text("Type de tournoi")
|
|
Spacer()
|
|
if dataStore.appSettings.tournamentTypes.isEmpty || dataStore.appSettings.tournamentTypes.count == FederalTournamentType.allCases.count {
|
|
Text("Tous les types")
|
|
.foregroundStyle(.secondary)
|
|
} else {
|
|
Text(types.map({ $0.localizedLabel()}).joined(separator: ", "))
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
}
|
|
|
|
Picker(selection: $appSettings.nationalCup) {
|
|
Text("N'importe").tag(false)
|
|
Text("Uniquement").tag(true)
|
|
} label: {
|
|
Text("Circuit National Padel Cup")
|
|
}
|
|
} header: {
|
|
Text("Critères de recherche")
|
|
}
|
|
.headerProminence(.increased)
|
|
.disabled(searching)
|
|
}
|
|
|
|
var categoriesLabel: some View {
|
|
if dataStore.appSettings.tournamentCategories.isEmpty || dataStore.appSettings.tournamentCategories.count == TournamentCategory.allCases.count {
|
|
Text("Toutes les catégories")
|
|
} else {
|
|
Text(categories.map({ $0.localizedCategoryLabel() }).joined(separator: ", "))
|
|
}
|
|
}
|
|
|
|
var levelsLabel: some View {
|
|
if dataStore.appSettings.tournamentLevels.isEmpty || dataStore.appSettings.tournamentLevels.count == TournamentLevel.allCases.count {
|
|
Text("Tous les niveaux")
|
|
} else {
|
|
Text(levels.map({ $0.localizedLevelLabel() }).joined(separator: ", "))
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
var searchParametersSummaryView: some View {
|
|
VStack(alignment: .leading) {
|
|
HStack {
|
|
Text("Lieu")
|
|
Spacer()
|
|
Text(dataStore.appSettings.city)
|
|
if dataStore.appSettings.distance >= 3000 {
|
|
Text("sans limite de distance")
|
|
} else {
|
|
Text("à moins de " + distanceLimit.formatted())
|
|
}
|
|
}
|
|
HStack {
|
|
Text("Période")
|
|
Spacer()
|
|
Text("Du")
|
|
Text(dataStore.appSettings.startDate.twoDigitsYearFormatted)
|
|
Text("Au")
|
|
Text(dataStore.appSettings.endDate.twoDigitsYearFormatted)
|
|
}
|
|
HStack {
|
|
Text("Niveau")
|
|
Spacer()
|
|
levelsLabel
|
|
}
|
|
HStack {
|
|
Text("Catégorie")
|
|
Spacer()
|
|
categoriesLabel
|
|
}
|
|
|
|
HStack {
|
|
Text("Tri")
|
|
Spacer()
|
|
Text(sortingOptionLabel)
|
|
}
|
|
}
|
|
}
|
|
|
|
var sortingOptionLabel: String {
|
|
switch dataStore.appSettings.sortingOption {
|
|
case "_DIST_": return "Distance"
|
|
case "dateDebut+asc": return "Date de début"
|
|
case "dateFin+asc": return "Date de fin"
|
|
default:
|
|
return "Distance"
|
|
}
|
|
}
|
|
|
|
private func _openSettings() {
|
|
guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else {
|
|
return
|
|
}
|
|
UIApplication.shared.open(settingsURL)
|
|
}
|
|
|
|
}
|
|
|