diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 7cba62e..7e42983 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -3275,7 +3275,10 @@ INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; INFOPLIST_KEY_NSCalendarsUsageDescription = "Padel Club a besoin d'avoir accès à votre calendrier pour pouvoir y inscrire ce tournoi"; INFOPLIST_KEY_NSCameraUsageDescription = "En autorisant l'application à utiliser la caméra, vous pourrez prendre des photos des rencontres"; - INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club a besoin de votre position pour rechercher les clubs autour de vous."; + INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous."; + INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous."; + INFOPLIST_KEY_NSLocationUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen"; @@ -3286,7 +3289,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.39; + MARKETING_VERSION = 1.0.44; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3319,7 +3322,10 @@ INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; INFOPLIST_KEY_NSCalendarsUsageDescription = "Padel Club a besoin d'avoir accès à votre calendrier pour pouvoir y inscrire ce tournoi"; INFOPLIST_KEY_NSCameraUsageDescription = "En autorisant l'application à utiliser la caméra, vous pourrez prendre des photos des rencontres"; - INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club a besoin de votre position pour rechercher les clubs autour de vous."; + INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous."; + INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous."; + INFOPLIST_KEY_NSLocationUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen"; @@ -3330,7 +3336,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.39; + MARKETING_VERSION = 1.0.44; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3436,7 +3442,10 @@ INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; INFOPLIST_KEY_NSCalendarsUsageDescription = "Padel Club a besoin d'avoir accès à votre calendrier pour pouvoir y inscrire ce tournoi"; INFOPLIST_KEY_NSCameraUsageDescription = "En autorisant l'application à utiliser la caméra, vous pourrez prendre des photos des rencontres"; - INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club a besoin de votre position pour rechercher les clubs autour de vous."; + INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous."; + INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous."; + INFOPLIST_KEY_NSLocationUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen"; @@ -3480,7 +3489,10 @@ INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; INFOPLIST_KEY_NSCalendarsUsageDescription = "Padel Club a besoin d'avoir accès à votre calendrier pour pouvoir y inscrire ce tournoi"; INFOPLIST_KEY_NSCameraUsageDescription = "En autorisant l'application à utiliser la caméra, vous pourrez prendre des photos des rencontres"; - INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club a besoin de votre position pour rechercher les clubs autour de vous."; + INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous."; + INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous."; + INFOPLIST_KEY_NSLocationUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club utilise votre position simplement pour faciliter la recherche de tournois et de clubs autour de vous."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen"; diff --git a/PadelClub/Data/MatchScheduler.swift b/PadelClub/Data/MatchScheduler.swift index f45f08f..11865c2 100644 --- a/PadelClub/Data/MatchScheduler.swift +++ b/PadelClub/Data/MatchScheduler.swift @@ -229,14 +229,10 @@ final class MatchScheduler : ModelObject, Storable { return teamsAvailable })) - if rotationIndex > 0 { + if rotationIndex > 0, simultaneousStart == false { rotationMatches = rotationMatches.sorted(by: { if counts[$0.groupStageObject!.index] ?? 0 == counts[$1.groupStageObject!.index] ?? 0 { - if simultaneousStart { - return $0.groupStageObject!.orderedIndexOfMatch($0) < $1.groupStageObject!.orderedIndexOfMatch($1) - } else { - return $0.groupStageObject!.index < $1.groupStageObject!.index - } + return $0.groupStageObject!.index < $1.groupStageObject!.index } else { return counts[$0.groupStageObject!.index] ?? 0 < counts[$1.groupStageObject!.index] ?? 0 } diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index 4dbc043..e3dd433 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -239,8 +239,15 @@ final class PlayerRegistration: ModelObject, Storable { } } - @MainActor func updateRank(from sources: [CSVParser], lastRank: Int) async throws { +#if DEBUG_TIME //DEBUGING TIME +let start = Date() +defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func updateRank()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +} +#endif + if let dataFound = try await history(from: sources) { rank = dataFound.rankValue?.toInt() points = dataFound.points @@ -251,6 +258,14 @@ final class PlayerRegistration: ModelObject, Storable { } func history(from sources: [CSVParser]) async throws -> Line? { +#if DEBUG_TIME //DEBUGING TIME +let start = Date() +defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func history()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +} +#endif + guard let license = licenceId?.strippedLicense else { return try await historyFromName(from: sources) } @@ -276,6 +291,14 @@ final class PlayerRegistration: ModelObject, Storable { } func historyFromName(from sources: [CSVParser]) async throws -> Line? { +#if DEBUG_TIME //DEBUGING TIME +let start = Date() +defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func historyFromName()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +} +#endif + return await withTaskGroup(of: Line?.self) { group in for source in sources.filter({ $0.maleData == isMalePlayer() }) { group.addTask { [lastName, firstName] in diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index da189ec..84a4d5e 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -173,7 +173,7 @@ final class TeamRegistration: ModelObject, Storable { } func getPhoneNumbers() -> [String] { - return players().compactMap { $0.phoneNumber }.filter({ $0.isMobileNumber() }) + return players().compactMap { $0.phoneNumber }.filter({ $0.isEmpty == false }) } func getMail() -> [String] { diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index b55e575..fa6a2bf 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1136,7 +1136,7 @@ defer { } } - func registrationIssues() async -> Int { + func registrationIssues() -> Int { let players : [PlayerRegistration] = unsortedPlayers() let selectedTeams : [TeamRegistration] = selectedSortedTeams() let callDateIssue : [TeamRegistration] = selectedTeams.filter { $0.callDate != nil && isStartDateIsDifferentThanCallDate($0) } @@ -1394,6 +1394,14 @@ defer { Logger.error(error) } + if self.publishRankings == false { + self.publishRankings = true + do { + try DataStore.shared.tournaments.addOrUpdate(instance: self) + } catch { + Logger.error(error) + } + } return rankings } @@ -1449,6 +1457,15 @@ defer { } func updateRank(to newDate: Date?) async throws { + +#if DEBUG_TIME //DEBUGING TIME +let start = Date() +defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func updateRank()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) +} +#endif + guard let newDate else { return } rankSourceDate = newDate @@ -1472,8 +1489,8 @@ defer { let lastRankWoman = currentMonthData()?.femaleUnrankedValue let dataURLs = SourceFileManager.shared.allFiles.filter { $0.dateFromPath == newDate } let sources = dataURLs.map { CSVParser(url: $0) } - - try await unsortedPlayers().concurrentForEach { player in + let players = unsortedPlayers() + try await players.concurrentForEach { player in try await player.updateRank(from: sources, lastRank: (player.sex == .female ? lastRankWoman : lastRankMan) ?? 0) } } @@ -1486,7 +1503,7 @@ defer { return unsortedTeams().first(where: { $0.includes(players: players) }) } - func tournamentTitle(_ displayStyle: DisplayStyle = .wide) -> String { + func tournamentTitle(_ displayStyle: DisplayStyle = .wide, hideSenior: Bool = false) -> String { if tournamentLevel == .unlisted, displayStyle == .title { if let name { return name @@ -1494,7 +1511,13 @@ defer { return tournamentLevel.localizedLevelLabel(.title) } } - let title: String = [tournamentLevel.localizedLevelLabel(displayStyle), tournamentCategory.localizedLabel(displayStyle), federalTournamentAge.localizedFederalAgeLabel(displayStyle)].filter({ $0.isEmpty == false }).joined(separator: " ") + let displayStyleCategory = hideSenior ? .short : displayStyle + var levelCategory = [tournamentLevel.localizedLevelLabel(displayStyle), tournamentCategory.localizedLabel(displayStyle)] + if displayStyle == .short { + levelCategory = [tournamentLevel.localizedLevelLabel(displayStyle) + tournamentCategory.localizedLabel(displayStyle)] + } + let array = levelCategory + [federalTournamentAge.localizedFederalAgeLabel(displayStyleCategory)] + let title: String = array.filter({ $0.isEmpty == false }).joined(separator: " ") if displayStyle == .wide, let name { return [title, name].joined(separator: " - ") } else { @@ -2623,7 +2646,11 @@ extension Tournament { func deadline(for type: TournamentDeadlineType) -> Date? { guard [.p500, .p1000, .p1500, .p2000].contains(tournamentLevel) else { return nil } - if let date = Calendar.current.date(byAdding: .day, value: type.daysOffset, to: startDate) { + var daysOffset = type.daysOffset + if tournamentLevel == .p500 { + daysOffset += 7 + } + if let date = Calendar.current.date(byAdding: .day, value: daysOffset, to: startDate) { let startOfDay = Calendar.current.startOfDay(for: date) return Calendar.current.date(byAdding: type.timeOffset, to: startOfDay) } diff --git a/PadelClub/PadelClubApp.swift b/PadelClub/PadelClubApp.swift index 4cdba91..3482b96 100644 --- a/PadelClub/PadelClubApp.swift +++ b/PadelClub/PadelClubApp.swift @@ -102,7 +102,7 @@ print("Running in Release mode") //try? Tips.resetDatastore() try? Tips.configure([ - .displayFrequency(.daily), + .displayFrequency(.immediate), .datastoreLocation(.applicationDefault) ]) } diff --git a/PadelClub/Utils/ContactManager.swift b/PadelClub/Utils/ContactManager.swift index 8c620a7..a72204a 100644 --- a/PadelClub/Utils/ContactManager.swift +++ b/PadelClub/Utils/ContactManager.swift @@ -82,7 +82,7 @@ Il est conseillé de vous présenter 10 minutes avant de jouer.\n\nMerci de me c let date = startDate ?? tournament?.startDate ?? Date() if let tournament { - text = text.replacingOccurrences(of: "#titre", with: tournament.tournamentTitle(.short)) + text = text.replacingOccurrences(of: "#titre", with: tournament.tournamentTitle(.title, hideSenior: true)) text = text.replacingOccurrences(of: "#prix", with: tournament.entryFeeMessage) } @@ -132,7 +132,7 @@ Il est conseillé de vous présenter 10 minutes avant de jouer.\n\nMerci de me c let intro = reSummon ? "Suite à des forfaits, vous êtes finalement" : "Vous êtes" if let tournament { - return "Bonjour,\n\n\(intro) \(localizedCalled) pour jouer en \(roundLabel.lowercased()) du \(tournament.tournamentTitle(.short)) au \(clubName) le \(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(date.formatted(Date.FormatStyle().hour().minute())).\n\n" + computedMessage + "\n\n\(signature)" + return "Bonjour,\n\n\(intro) \(localizedCalled) pour jouer en \(roundLabel.lowercased()) du \(tournament.tournamentTitle(.title, hideSenior: true)) au \(clubName) le \(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(date.formatted(Date.FormatStyle().hour().minute())).\n\n" + computedMessage + "\n\n\(signature)" } else { return "Bonjour,\n\n\(intro) \(localizedCalled) \(roundLabel) au \(clubName) le \(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(date.formatted(Date.FormatStyle().hour().minute())).\n\nMerci de confirmer en répondant à ce message et de prévenir votre partenaire !\n\n\(signature)" } diff --git a/PadelClub/Utils/Tips.swift b/PadelClub/Utils/Tips.swift index bb136dd..ea69ae0 100644 --- a/PadelClub/Utils/Tips.swift +++ b/PadelClub/Utils/Tips.swift @@ -549,29 +549,36 @@ struct TeamsExportTip: Tip { } } -struct PlayerTournamentSearchTip: Tip { +struct TimeSlotMoveTip: Tip { var title: Text { - Text("Cherchez un tournoi autour de vous !") + Text("Réorganisez vos créneaux horaires !") } var message: Text? { - Text("Padel Club facilite la recherche de tournois et l'inscription !") + Text("Vous pouvez déplacer les créneaux horaires dans la liste en glissant-déposant.") } var image: Image? { - Image(systemName: "trophy.circle") + Image(systemName: "arrow.up.arrow.down.circle") } +} - var actions: [Action] { - Action(id: ActionKey.selectAction.rawValue, title: "Éssayer") +struct TimeSlotMoveOptionTip: Tip { + var title: Text { + Text("Réorganisez vos créneaux horaires !") } - enum ActionKey: String { - case selectAction = "selectAction" + var message: Text? { + Text("En cliquant ici, vous pouvez déplacer les créneaux horaires dans la liste en glissant-déposant.") + } + + var image: Image? { + Image(systemName: "sparkles") } - } + + struct TipStyleModifier: ViewModifier { @Environment(\.colorScheme) var colorScheme var tint: Color? diff --git a/PadelClub/Utils/URLs.swift b/PadelClub/Utils/URLs.swift index 20e1013..63f4489 100644 --- a/PadelClub/Utils/URLs.swift +++ b/PadelClub/Utils/URLs.swift @@ -31,8 +31,8 @@ enum URLs: String, Identifiable { case beachPadel = "https://beach-padel.app.fft.fr/beachja/index/" //case padelClub = "https://padelclub.app" case tenup = "https://tenup.fft.fr" - case padelCompetitionGeneralGuide = "https://fft-site.cdn.prismic.io/fft-site/Zqi2PB5LeNNTxlrS_1-REGLESGENERALESDELACOMPETITION-ANNEESPORTIVE2025.pdf" - case padelCompetitionSpecificGuide = "https://fft-site.cdn.prismic.io/fft-site/Zqi4ax5LeNNTxlsu_3-CAHIERDESCHARGESDESTOURNOIS-ANNEESPORTIVE2025.pdf" + case padelCompetitionGeneralGuide = "https://fft-site.cdn.prismic.io/fft-site/Z2mH0ZbqstJ98yso_CHAPITREIRèglesgénérales.pdf" + case padelCompetitionSpecificGuide = "https://fft-site.cdn.prismic.io/fft-site/Z2mHz5bqstJ98ysm_CHAPITREIIICahierdeschargesdestournois.pdf" case padelRules = "https://xlr.alwaysdata.net/static/rules/padel-rules-2024.pdf" case restingDischarge = "https://club.fft.fr/tennisfirmidecazeville/60120370_d/data_1/pdf/fo/formlairededechargederesponsabilitetournoidepadel.pdf" case appReview = "https://apps.apple.com/app/padel-club/id6484163558?mt=8&action=write-review" diff --git a/PadelClub/Views/Calling/CallMessageCustomizationView.swift b/PadelClub/Views/Calling/CallMessageCustomizationView.swift index 0ed9a54..2914565 100644 --- a/PadelClub/Views/Calling/CallMessageCustomizationView.swift +++ b/PadelClub/Views/Calling/CallMessageCustomizationView.swift @@ -56,7 +56,7 @@ struct CallMessageCustomizationView: View { var finalMessage: String? { let localizedCalled = "convoqué" + (tournament.tournamentCategory == .women ? "e" : "") + "s" - return "Bonjour,\n\nVous êtes \(localizedCalled) pour jouer en \(RoundRule.roundName(fromRoundIndex: 2).lowercased()) du \(tournament.tournamentTitle(.short)) au \(clubName) le \(tournament.startDate.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(tournament.startDate.formatted(Date.FormatStyle().hour().minute())).\n\n" + computedMessage + "\n\n\(customCallMessageSignature)" + return "Bonjour,\n\nVous êtes \(localizedCalled) pour jouer en \(RoundRule.roundName(fromRoundIndex: 2).lowercased()) du \(tournament.tournamentTitle(.title, hideSenior: true)) au \(clubName) le \(tournament.startDate.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(tournament.startDate.formatted(Date.FormatStyle().hour().minute())).\n\n" + computedMessage + "\n\n\(customCallMessageSignature)" } var body: some View { diff --git a/PadelClub/Views/Calling/CallView.swift b/PadelClub/Views/Calling/CallView.swift index 86f12af..1be6236 100644 --- a/PadelClub/Views/Calling/CallView.swift +++ b/PadelClub/Views/Calling/CallView.swift @@ -387,7 +387,7 @@ struct CallView: View { recipients: tournament.umpireMail(), bccRecipients: teams.flatMap { $0.getMail() }, body: finalMessage(reSummon: reSummon, forcedEmptyMessage: forcedEmptyMessage), - subject: tournament.tournamentTitle(), + subject: tournament.tournamentTitle(hideSenior: true), tournamentBuild: nil) } diff --git a/PadelClub/Views/Calling/Components/PlayersWithoutContactView.swift b/PadelClub/Views/Calling/Components/PlayersWithoutContactView.swift index cffae6e..92b38be 100644 --- a/PadelClub/Views/Calling/Components/PlayersWithoutContactView.swift +++ b/PadelClub/Views/Calling/Components/PlayersWithoutContactView.swift @@ -45,7 +45,7 @@ struct PlayersWithoutContactView: View { LabeledContent { Text(withoutPhones.count.formatted()) } label: { - Text("Joueurs sans téléphone portable") + Text("Joueurs sans téléphone portable français") } } } header: { diff --git a/PadelClub/Views/Calling/SendToAllView.swift b/PadelClub/Views/Calling/SendToAllView.swift index 9f6778d..d261f35 100644 --- a/PadelClub/Views/Calling/SendToAllView.swift +++ b/PadelClub/Views/Calling/SendToAllView.swift @@ -273,7 +273,7 @@ struct SendToAllView: View { if contactMethod == 0 { contactType = .message(date: nil, recipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.phoneNumber }, body: finalMessage(), tournamentBuild: nil) } else { - contactType = .mail(date: nil, recipients: tournament.umpireMail(), bccRecipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.email }, body: finalMessage(), subject: tournament.tournamentTitle(), tournamentBuild: nil) + contactType = .mail(date: nil, recipients: tournament.umpireMail(), bccRecipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.email }, body: finalMessage(), subject: tournament.tournamentTitle(hideSenior: true), tournamentBuild: nil) } } } diff --git a/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift b/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift index 783359c..f7228f6 100644 --- a/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift +++ b/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift @@ -18,7 +18,7 @@ struct TournamentLookUpView: View { @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 @@ -57,6 +57,16 @@ struct TournamentLookUpView: View { } 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 @@ -305,9 +315,15 @@ struct TournamentLookUpView: View { } if locationManager.requestStarted { ProgressView() - } else { + } else if locationManager.manager.authorizationStatus != .restricted { LocationButton { - locationManager.requestLocation() + if locationManager.manager.authorizationStatus == .notDetermined { + locationManager.manager.requestWhenInUseAuthorization() + } else if locationManager.manager.authorizationStatus == .denied { + showingSettingsAlert = true + } else { + locationManager.requestLocation() + } } .symbolVariant(.fill) .foregroundColor (Color.white) @@ -485,4 +501,12 @@ struct TournamentLookUpView: View { return "Distance" } } + + private func _openSettings() { + guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { + return + } + UIApplication.shared.open(settingsURL) + } + } diff --git a/PadelClub/Views/Planning/PlanningView.swift b/PadelClub/Views/Planning/PlanningView.swift index 3a6e07f..0f3193c 100644 --- a/PadelClub/Views/Planning/PlanningView.swift +++ b/PadelClub/Views/Planning/PlanningView.swift @@ -6,16 +6,21 @@ // import SwiftUI +import LeStorage +import TipKit struct PlanningView: View { + @EnvironmentObject var dataStore: DataStore @Environment(Tournament.self) var tournament: Tournament @State private var selectedDay: Date? @Binding var selectedScheduleDestination: ScheduleDestination? @State private var filterOption: PlanningFilterOption = .byDefault @State private var showFinishedMatches: Bool = false + @State private var enableMove: Bool = false let allMatches: [Match] + let timeSlotMoveOptionTip = TimeSlotMoveOptionTip() init(matches: [Match], selectedScheduleDestination: Binding) { self.allMatches = matches @@ -37,23 +42,7 @@ struct PlanningView: View { func keys(timeSlots: [Date:[Match]]) -> [Date] { timeSlots.keys.sorted() } - - enum PlanningFilterOption: Int, CaseIterable, Identifiable { - var id: Int { self.rawValue } - - case byDefault - case byCourt - func localizedPlanningLabel() -> String { - switch self { - case .byCourt: - return "Par terrain" - case .byDefault: - return "Par ordre des matchs" - } - } - } - private func _computedTitle(days: [Date]) -> String { if let selectedDay { return selectedDay.formatted(.dateTime.day().weekday().month()) @@ -71,8 +60,13 @@ struct PlanningView: View { let keys = self.keys(timeSlots: timeSlots) let days = self.days(timeSlots: timeSlots) let matches = matches - BySlotView(days: days, keys: keys, timeSlots: timeSlots, matches: matches, selectedDay: selectedDay, filterOption: filterOption, showFinishedMatches: showFinishedMatches) + let notSlots = matches.allSatisfy({ $0.startDate == nil }) + BySlotView(days: days, keys: keys, timeSlots: timeSlots, matches: matches, selectedDay: selectedDay) + .environment(\.filterOption, filterOption) + .environment(\.showFinishedMatches, showFinishedMatches) + .environment(\.enableMove, enableMove) .navigationTitle(Text(_computedTitle(days: days))) + .navigationBarBackButtonHidden(enableMove) .toolbar(content: { if days.count > 1 { ToolbarTitleMenu { @@ -89,42 +83,79 @@ struct PlanningView: View { Text("Jour") } .pickerStyle(.automatic) + .disabled(enableMove) } } + + if enableMove { + ToolbarItem(placement: .topBarLeading) { + Button("Annuler") { + enableMove = false + } + } + + ToolbarItem(placement: .topBarTrailing) { + Button("Sauver") { + do { + try self.tournament.tournamentStore.matches.addOrUpdate(contentOfs: allMatches) + } catch { + Logger.error(error) + } - ToolbarItemGroup(placement: .topBarTrailing) { - Menu { - Picker(selection: $showFinishedMatches) { - Text("Afficher tous les matchs").tag(true) - Text("Masquer les matchs terminés").tag(false) - } label: { - Text("Option de filtrage") + enableMove = false } - .labelsHidden() - .pickerStyle(.inline) - } label: { - Label("Filtrer", systemImage: "clock.badge.checkmark") - .symbolVariant(showFinishedMatches ? .fill : .none) } - Menu { - Picker(selection: $filterOption) { - ForEach(PlanningFilterOption.allCases) { - Text($0.localizedPlanningLabel()).tag($0) + + } else { + + ToolbarItemGroup(placement: .topBarTrailing) { + if notSlots == false { + Toggle(isOn: $enableMove) { + Label("Déplacer", systemImage: "rectangle.2.swap") + } + .popoverTip(timeSlotMoveOptionTip) + } + + Menu { + Section { + Picker(selection: $showFinishedMatches) { + Text("Afficher tous les matchs").tag(true) + Text("Masquer les matchs terminés").tag(false) + } label: { + Text("Option de filtrage") + } + .labelsHidden() + .pickerStyle(.inline) + } header: { + Text("Option de filtrage") + } + + Divider() + + Section { + Picker(selection: $filterOption) { + ForEach(PlanningFilterOption.allCases) { + Text($0.localizedPlanningLabel()).tag($0) + } + } label: { + Text("Option de triage") + } + .labelsHidden() + .pickerStyle(.inline) + } header: { + Text("Option de triage") + } } label: { - Text("Option de triage") + Label("Trier", systemImage: "line.3.horizontal.decrease.circle") + .symbolVariant(filterOption == .byCourt || showFinishedMatches ? .fill : .none) } - .labelsHidden() - .pickerStyle(.inline) - } label: { - Label("Trier", systemImage: "line.3.horizontal.decrease.circle") - .symbolVariant(filterOption == .byCourt ? .fill : .none) + } - } }) .overlay { - if matches.allSatisfy({ $0.startDate == nil }) { + if notSlots { ContentUnavailableView { Label("Aucun horaire défini", systemImage: "clock.badge.questionmark") } description: { @@ -140,86 +171,213 @@ struct PlanningView: View { struct BySlotView: View { @Environment(Tournament.self) var tournament: Tournament + @Environment(\.filterOption) private var filterOption + @Environment(\.showFinishedMatches) private var showFinishedMatches + @Environment(\.enableMove) private var enableMove + let days: [Date] let keys: [Date] - let timeSlots: [Date:[Match]] + let timeSlots: [Date: [Match]] let matches: [Match] let selectedDay: Date? - let filterOption: PlanningFilterOption - let showFinishedMatches: Bool + let timeSlotMoveTip = TimeSlotMoveTip() var body: some View { List { - if matches.allSatisfy({ $0.startDate == nil }) == false { + + if enableMove { + TipView(timeSlotMoveTip) + .tipStyle(tint: .logoYellow, asSection: true) + } + + if !matches.allSatisfy({ $0.startDate == nil }) { ForEach(days.filter({ selectedDay == nil || selectedDay == $0 }), id: \.self) { day in - Section { - ForEach(keys.filter({ $0.dayInt == day.dayInt }), id: \.self) { key in - if let _matches = timeSlots[key]?.sorted(by: filterOption == .byDefault ? \.computedOrder : \.courtIndexForSorting) { - DisclosureGroup { - ForEach(_matches) { match in - NavigationLink { - MatchDetailView(match: match) - .matchViewStyle(.sectionedStandardStyle) - - } label: { - LabeledContent { - if let courtName = match.courtName() { - Text(courtName) - } - } label: { - if let groupStage = match.groupStageObject { - Text(groupStage.groupStageTitle(.title)) - } else if let round = match.roundObject { - Text(round.roundTitle()) - } - Text(match.matchTitle()) - } - } - } - } label: { - _timeSlotView(key: key, matches: _matches) - } - } - } - } header: { - HStack { - if day.monthYearFormatted == Date.distantFuture.monthYearFormatted { - Text("Sans horaire") - } else { - Text(day.formatted(.dateTime.day().weekday().month())) - } - Spacer() - let count = _matchesCount(inDayInt: day.dayInt, timeSlots: timeSlots) - if showFinishedMatches { - Text(self._formattedMatchCount(count)) - } else { - Text(self._formattedMatchCount(count) + " restant\(count.pluralSuffix)") - } - } - } footer: { - if day.monthYearFormatted == Date.distantFuture.monthYearFormatted { - Text("Il s'agit des matchs qui n'ont pas réussi à être placé par Padel Club. Peut-être à cause de créneaux indisponibles, d'autres tournois ou des réglages.") - } - } - .headerProminence(.increased) + DaySectionView( + day: day, + keys: keys.filter({ $0.dayInt == day.dayInt }), + timeSlots: timeSlots, + selectedDay: selectedDay + ) } } } } + } + + + struct DaySectionView: View { + @Environment(Tournament.self) var tournament: Tournament + @Environment(\.filterOption) private var filterOption + @Environment(\.showFinishedMatches) private var showFinishedMatches + @Environment(\.enableMove) private var enableMove + + let day: Date + let keys: [Date] + let timeSlots: [Date: [Match]] + let selectedDay: Date? + + var body: some View { + Section { + ForEach(keys, id: \.self) { key in + TimeSlotSectionView( + key: key, + matches: timeSlots[key]?.sorted(by: filterOption == .byDefault ? \.computedOrder : \.courtIndexForSorting) ?? [] + ) + } + .onMove(perform: enableMove ? moveSection : nil) + } header: { + HeaderView(day: day, timeSlots: timeSlots) + } footer: { + if day.monthYearFormatted == Date.distantFuture.monthYearFormatted { + Text("Il s'agit des matchs qui n'ont pas réussi à être placé par Padel Club. Peut-être à cause de créneaux indisponibles, d'autres tournois ou des réglages.") + } + } + } - private func _matchesCount(inDayInt dayInt: Int, timeSlots: [Date:[Match]]) -> Int { + func moveSection(from source: IndexSet, to destination: Int) { + let daySlots = keys.filter { $0.dayInt == day.dayInt }.sorted() + + guard let sourceIdx = source.first, + sourceIdx < daySlots.count, + destination <= daySlots.count else { + return + } + + // Create a mutable copy of the time slots for this day + var slotsToUpdate = daySlots + + let updateRange = min(sourceIdx, destination)...max(sourceIdx, destination) + + // Perform the move in the array + let sourceTime = slotsToUpdate.remove(at: sourceIdx) + if sourceIdx < destination { + slotsToUpdate.insert(sourceTime, at: destination - 1) + } else { + slotsToUpdate.insert(sourceTime, at: destination) + } + + // Update matches by swapping their startDates + for index in updateRange { + // Find the new time slot for these matches + let oldStartTime = slotsToUpdate[index] + let newStartTime = daySlots[index] + guard let matchesToUpdate = timeSlots[oldStartTime] else { continue } + + // Update each match with the new start time + for match in matchesToUpdate { + match.startDate = newStartTime + } + } + } + } + + + struct TimeSlotSectionView: View { + @Environment(\.enableMove) private var enableMove + let key: Date + let matches: [Match] + + var body: some View { + if !matches.isEmpty { + if enableMove { + TimeSlotHeaderView(key: key, matches: matches) + } else { + DisclosureGroup { + MatchListView(matches: matches) + } label: { + TimeSlotHeaderView(key: key, matches: matches) + } + } + } + } + } + + struct MatchListView: View { + let matches: [Match] + + var body: some View { + ForEach(matches) { match in + NavigationLink { + MatchDetailView(match: match) + .matchViewStyle(.sectionedStandardStyle) + } label: { + MatchRowView(match: match) + } + } + } + } + + struct MatchRowView: View { + let match: Match + + var body: some View { + LabeledContent { + if let courtName = match.courtName() { + Text(courtName) + } + } label: { + if let groupStage = match.groupStageObject { + Text(groupStage.groupStageTitle(.title)) + } else if let round = match.roundObject { + Text(round.roundTitle()) + } + Text(match.matchTitle()) + } + } + } + + + struct HeaderView: View { + @Environment(\.filterOption) private var filterOption + @Environment(\.showFinishedMatches) private var showFinishedMatches + @Environment(\.enableMove) private var enableMove + + let day: Date + let timeSlots: [Date: [Match]] + + var body: some View { + HStack { + if day.monthYearFormatted == Date.distantFuture.monthYearFormatted { + Text("Sans horaire") + } else { + Text(day.formatted(.dateTime.day().weekday().month())) + } + Spacer() + let count = _matchesCount(inDayInt: day.dayInt, timeSlots: timeSlots) + if showFinishedMatches { + Text(_formattedMatchCount(count)) + } else { + Text("\(_formattedMatchCount(count)) restant\(count.pluralSuffix)") + } + } + } + + private func _matchesCount(inDayInt dayInt: Int, timeSlots: [Date: [Match]]) -> Int { timeSlots.filter { $0.key.dayInt == dayInt }.flatMap({ $0.value }).count } - - private func _timeSlotView(key: Date, matches: [Match]) -> some View { + + private func _formattedMatchCount(_ count: Int) -> String { + return "\(count.formatted()) match\(count.pluralSuffix)" + } + } + + struct TimeSlotHeaderView: View { + let key: Date + let matches: [Match] + @Environment(Tournament.self) var tournament: Tournament + + var body: some View { LabeledContent { - Text(self._formattedMatchCount(matches.count)) + Text("\(matches.count.formatted()) match\(matches.count.pluralSuffix)") } label: { if key.monthYearFormatted == Date.distantFuture.monthYearFormatted { Text("Aucun horaire") } else { - Text(key.formatted(date: .omitted, time: .shortened)).font(.title).fontWeight(.semibold) + Text(key.formatted(date: .omitted, time: .shortened)) + .font(.title) + .fontWeight(.semibold) } + if matches.count <= tournament.courtCount { let names = matches.sorted(by: \.computedOrder) .compactMap({ $0.roundTitle() }) @@ -232,15 +390,203 @@ struct PlanningView: View { } else { Text(matches.count.formatted().appending(" matchs")) } + } } - - fileprivate func _formattedMatchCount(_ count: Int) -> String { - return "\(count.formatted()) match\(count.pluralSuffix)" + } + + +// struct BySlotView: View { +// @Environment(Tournament.self) var tournament: Tournament +// let days: [Date] +// let keys: [Date] +// let timeSlots: [Date:[Match]] +// let matches: [Match] +// let selectedDay: Date? +// let filterOption: PlanningFilterOption +// let showFinishedMatches: Bool +// +// var body: some View { +// List { +// if matches.allSatisfy({ $0.startDate == nil }) == false { +// ForEach(days.filter({ selectedDay == nil || selectedDay == $0 }), id: \.self) { day in +// Section { +// ForEach(keys.filter({ $0.dayInt == day.dayInt }), id: \.self) { key in +// if let _matches = timeSlots[key]?.sorted(by: filterOption == .byDefault ? \.computedOrder : \.courtIndexForSorting) { +// DisclosureGroup { +// ForEach(_matches) { match in +// NavigationLink { +// MatchDetailView(match: match) +// .matchViewStyle(.sectionedStandardStyle) +// +// } label: { +// LabeledContent { +// if let courtName = match.courtName() { +// Text(courtName) +// } +// } label: { +// if let groupStage = match.groupStageObject { +// Text(groupStage.groupStageTitle(.title)) +// } else if let round = match.roundObject { +// Text(round.roundTitle()) +// } +// Text(match.matchTitle()) +// } +// } +// } +// } label: { +// _timeSlotView(key: key, matches: _matches) +// } +// } +// } +// .onMove(perform: moveSection) +// } header: { +// HStack { +// if day.monthYearFormatted == Date.distantFuture.monthYearFormatted { +// Text("Sans horaire") +// } else { +// Text(day.formatted(.dateTime.day().weekday().month())) +// } +// Spacer() +// let count = _matchesCount(inDayInt: day.dayInt, timeSlots: timeSlots) +// if showFinishedMatches { +// Text(self._formattedMatchCount(count)) +// } else { +// Text(self._formattedMatchCount(count) + " restant\(count.pluralSuffix)") +// } +// } +// } footer: { +// if day.monthYearFormatted == Date.distantFuture.monthYearFormatted { +// Text("Il s'agit des matchs qui n'ont pas réussi à être placé par Padel Club. Peut-être à cause de créneaux indisponibles, d'autres tournois ou des réglages.") +// } +// } +// .headerProminence(.increased) +// } +// } +// } +// } +// +// func moveSection(from source: IndexSet, to destination: Int) { +// let daySlots = keys.filter { selectedDay == nil || $0.dayInt == selectedDay?.dayInt }.sorted() +// +// guard let sourceIdx = source.first, +// sourceIdx < daySlots.count, +// destination <= daySlots.count else { +// return +// } +// +// // Create a mutable copy of the time slots for this day +// var slotsToUpdate = daySlots +// +// let updateRange = min(sourceIdx, destination)...max(sourceIdx, destination) - 1 +// print(updateRange) +// +// // Perform the move in the array +// let sourceTime = slotsToUpdate.remove(at: sourceIdx) +// if sourceIdx < destination { +// slotsToUpdate.insert(sourceTime, at: destination - 1) +// } else { +// slotsToUpdate.insert(sourceTime, at: destination) +// } +// +// // Update matches by swapping their startDates +// for index in updateRange { +// // Find the new time slot for these matches +// let oldStartTime = slotsToUpdate[index] +// let newStartTime = daySlots[index] +// guard let matchesToUpdate = timeSlots[oldStartTime] else { continue } +// print("moving", oldStartTime, "to", newStartTime) +// +// // Update each match with the new start time +// for match in matchesToUpdate { +// match.startDate = newStartTime +// } +// } +// +// try? self.tournament.tournamentStore.matches.addOrUpdate(contentOfs: matches) +// } +// +// +// private func _matchesCount(inDayInt dayInt: Int, timeSlots: [Date:[Match]]) -> Int { +// timeSlots.filter { $0.key.dayInt == dayInt }.flatMap({ $0.value }).count +// } +// +// private func _timeSlotView(key: Date, matches: [Match]) -> some View { +// LabeledContent { +// Text(self._formattedMatchCount(matches.count)) +// } label: { +// if key.monthYearFormatted == Date.distantFuture.monthYearFormatted { +// Text("Aucun horaire") +// } else { +// Text(key.formatted(date: .omitted, time: .shortened)).font(.title).fontWeight(.semibold) +// } +// if matches.count <= tournament.courtCount { +// let names = matches.sorted(by: \.computedOrder) +// .compactMap({ $0.roundTitle() }) +// .reduce(into: [String]()) { uniqueNames, name in +// if !uniqueNames.contains(name) { +// uniqueNames.append(name) +// } +// } +// Text(names.joined(separator: ", ")).lineLimit(1).truncationMode(.tail) +// } else { +// Text(matches.count.formatted().appending(" matchs")) +// } +// } +// } +// +// fileprivate func _formattedMatchCount(_ count: Int) -> String { +// return "\(count.formatted()) match\(count.pluralSuffix)" +// } +// } +} + +enum PlanningFilterOption: Int, CaseIterable, Identifiable { + var id: Int { self.rawValue } + + case byDefault + case byCourt + + func localizedPlanningLabel() -> String { + switch self { + case .byCourt: + return "Par terrain" + case .byDefault: + return "Par ordre des matchs" } } } -//#Preview { -// PlanningView(matches: [], selectedScheduleDestination: .constant(nil)) -//} + +struct FilterOptionKey: EnvironmentKey { + static let defaultValue: PlanningFilterOption = .byDefault +} + +extension EnvironmentValues { + var filterOption: PlanningFilterOption { + get { self[FilterOptionKey.self] } + set { self[FilterOptionKey.self] = newValue } + } +} + +struct ShowFinishedMatchesKey: EnvironmentKey { + static let defaultValue: Bool = false +} + +extension EnvironmentValues { + var showFinishedMatches: Bool { + get { self[ShowFinishedMatchesKey.self] } + set { self[ShowFinishedMatchesKey.self] = newValue } + } +} + +struct EnableMoveKey: EnvironmentKey { + static let defaultValue: Bool = false +} + +extension EnvironmentValues { + var enableMove: Bool { + get { self[EnableMoveKey.self] } + set { self[EnableMoveKey.self] = newValue } + } +} diff --git a/PadelClub/Views/Player/PlayerDetailView.swift b/PadelClub/Views/Player/PlayerDetailView.swift index 69c153a..88fe914 100644 --- a/PadelClub/Views/Player/PlayerDetailView.swift +++ b/PadelClub/Views/Player/PlayerDetailView.swift @@ -152,10 +152,6 @@ struct PlayerDetailView: View { Menu { CopyPasteButtonView(pasteValue: player.phoneNumber) PasteButtonView(text: $phoneNumber) - .onChange(of: phoneNumber) { - player.phoneNumber = phoneNumber.prefixTrimmed(50) - _save() - } } label: { Text("Téléphone") } @@ -177,10 +173,6 @@ struct PlayerDetailView: View { Menu { CopyPasteButtonView(pasteValue: player.email) PasteButtonView(text: $email) - .onChange(of: email) { - player.email = email.prefixTrimmed(50) - _save() - } } label: { Text("Email") } @@ -216,13 +208,6 @@ struct PlayerDetailView: View { } } } - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - ShareLink(item: player.pasteData()) { - Label("Partager", systemImage: "square.and.arrow.up") - } - } - } .onChange(of: player.hasArrived) { _save() } @@ -230,7 +215,17 @@ struct PlayerDetailView: View { _save() } .navigationBarBackButtonHidden(focusedField != nil) - .toolbar(content: { + .headerProminence(.increased) + .navigationTitle("Édition") + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + ShareLink(item: player.pasteData()) { + Label("Partager", systemImage: "square.and.arrow.up") + } + } + if focusedField != nil { ToolbarItem(placement: .topBarLeading) { Button("Annuler", role: .cancel) { @@ -238,14 +233,9 @@ struct PlayerDetailView: View { } } } - }) - .headerProminence(.increased) - .navigationTitle("Édition") - .navigationBarTitleDisplayMode(.inline) - .toolbarBackground(.visible, for: .navigationBar) - .toolbar { - if focusedField == ._rank || focusedField == ._computedRank { - ToolbarItem(placement: .keyboard) { + if focusedField == ._rank || focusedField == ._computedRank || focusedField == ._phoneNumber { + ToolbarItemGroup(placement: .keyboard) { + Spacer() Button("Valider") { if focusedField == ._rank { player.setComputedRank(in: tournament) @@ -254,6 +244,9 @@ struct PlayerDetailView: View { } else if focusedField == ._computedRank { player.team()?.updateWeight(inTournamentCategory: tournament.tournamentCategory) _save() + } else if focusedField == ._phoneNumber { + player.phoneNumber = phoneNumber.prefixTrimmed(50) + _save() } focusedField = nil } diff --git a/PadelClub/Views/Team/EditingTeamView.swift b/PadelClub/Views/Team/EditingTeamView.swift index f8cc5d5..1e6bad3 100644 --- a/PadelClub/Views/Team/EditingTeamView.swift +++ b/PadelClub/Views/Team/EditingTeamView.swift @@ -20,7 +20,6 @@ struct EditingTeamView: View { @State private var sentError: ContactManagerError? = nil @State private var showSubscriptionView: Bool = false @State private var registrationDate : Date - @State private var callDate : Date @State private var name: String @FocusState private var focusedField: TeamRegistration.CodingKeys? @@ -42,7 +41,6 @@ struct EditingTeamView: View { self.team = team _name = .init(wrappedValue: team.name ?? "") _registrationDate = State(wrappedValue: team.registrationDate ?? Date()) - _callDate = State(wrappedValue: team.callDate ?? Date()) } var body: some View { diff --git a/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift b/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift index 3fb55c6..97f605c 100644 --- a/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift @@ -9,7 +9,7 @@ import SwiftUI struct InscriptionInfoView: View { @EnvironmentObject var dataStore: DataStore - @Environment(Tournament.self) var tournament + let tournament: Tournament @State private var players : [PlayerRegistration] = [] @State private var selectedTeams : [TeamRegistration] = [] @@ -81,6 +81,7 @@ struct InscriptionInfoView: View { DisclosureGroup { ForEach(callDateIssue) { team in TeamCallView(team: team) + .environment(tournament) } } label: { LabeledContent { @@ -244,30 +245,30 @@ struct InscriptionInfoView: View { Text("importé du fichier beach-padel sans licence valide ou créé sans licence") } } - .task { - await _getIssues() + .onAppear { + DispatchQueue.main.async { + _getIssues() + } } .navigationTitle("Synthèse") .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.visible, for: .navigationBar) } - private func _getIssues() async { - Task { - players = tournament.unsortedPlayers() - selectedTeams = tournament.selectedSortedTeams() - callDateIssue = selectedTeams.filter { $0.callDate != nil && tournament.isStartDateIsDifferentThanCallDate($0) } - waitingList = tournament.waitingListTeams(in: selectedTeams, includingWalkOuts: true) - duplicates = tournament.duplicates(in: players) - homonyms = tournament.homonyms(in: players) - problematicPlayers = players.filter({ $0.sex == nil }) - inadequatePlayers = tournament.inadequatePlayers(in: players) - ageInadequatePlayers = tournament.ageInadequatePlayers(in: players) - let isImported = players.anySatisfy({ $0.isImported() }) - playersWithoutValidLicense = tournament.playersWithoutValidLicense(in: players, isImported: isImported) - entriesFromBeachPadel = tournament.unsortedTeams().filter({ $0.isImported() }) - playersMissing = selectedTeams.filter({ $0.unsortedPlayers().count < 2 }) - } + private func _getIssues() { + players = tournament.unsortedPlayers() + selectedTeams = tournament.selectedSortedTeams() + callDateIssue = selectedTeams.filter { $0.callDate != nil && tournament.isStartDateIsDifferentThanCallDate($0) } + waitingList = tournament.waitingListTeams(in: selectedTeams, includingWalkOuts: true) + duplicates = tournament.duplicates(in: players) + homonyms = tournament.homonyms(in: players) + problematicPlayers = players.filter({ $0.sex == nil }) + inadequatePlayers = tournament.inadequatePlayers(in: players) + ageInadequatePlayers = tournament.ageInadequatePlayers(in: players) + let isImported = players.anySatisfy({ $0.isImported() }) + playersWithoutValidLicense = tournament.playersWithoutValidLicense(in: players, isImported: isImported) + entriesFromBeachPadel = tournament.unsortedTeams().filter({ $0.isImported() }) + playersMissing = selectedTeams.filter({ $0.unsortedPlayers().count < 2 }) } } diff --git a/PadelClub/Views/Tournament/Screen/Components/UpdateSourceRankDateView.swift b/PadelClub/Views/Tournament/Screen/Components/UpdateSourceRankDateView.swift index 07f95d5..ceed51f 100644 --- a/PadelClub/Views/Tournament/Screen/Components/UpdateSourceRankDateView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/UpdateSourceRankDateView.swift @@ -43,30 +43,29 @@ struct UpdateSourceRankDateView: View { Task { do { try await tournament.updateRank(to: currentRankSourceDate) - try await MainActor.run { - tournament.unsortedPlayers().forEach { player in - player.setComputedRank(in: tournament) - } - - try tournamentStore.playerRegistrations.addOrUpdate(contentOfs: tournament.unsortedPlayers()) - - tournament.unsortedTeams().forEach { team in - team.setWeight(from: team.players(), inTournamentCategory: tournament.tournamentCategory) - if forceRefreshLockWeight { - team.lockedWeight = team.weight - } + let unsortedPlayers = tournament.unsortedPlayers() + tournament.unsortedPlayers().forEach { player in + player.setComputedRank(in: tournament) + } + + try tournamentStore.playerRegistrations.addOrUpdate(contentOfs: unsortedPlayers) + + let unsortedTeams = tournament.unsortedTeams() + unsortedTeams.forEach { team in + team.setWeight(from: team.players(), inTournamentCategory: tournament.tournamentCategory) + if forceRefreshLockWeight { + team.lockedWeight = team.weight } - - try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams()) - - try dataStore.tournaments.addOrUpdate(instance: tournament) - - updatingRank = false - confirmUpdateRank = false } + + try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams) + + try dataStore.tournaments.addOrUpdate(instance: tournament) } catch { Logger.error(error) } + updatingRank = false + confirmUpdateRank = false } }.disabled(updatingRank) diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index be075c8..775bdc2 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -173,8 +173,8 @@ struct InscriptionManagerView: View { self.teamsHash = _simpleHash(ids: selectedSortedTeams.map { $0.id }) } self.registrationIssues = nil - Task { - self.registrationIssues = await tournament.registrationIssues() + DispatchQueue.main.async { + self.registrationIssues = tournament.registrationIssues() } } @@ -718,14 +718,7 @@ struct InscriptionManagerView: View { if tournament.isAnimation() == false { NavigationLink { - InscriptionInfoView() - .environment(tournament) - .onDisappear { - self.registrationIssues = nil - Task { - self.registrationIssues = await tournament.registrationIssues() - } - } + InscriptionInfoView(tournament: tournament) } label: { LabeledContent { if let registrationIssues {