diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index bc4d6c4..f9b5129 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -743,6 +743,7 @@ FFB1C98B2C10255100B154A7 /* TournamentBroadcastRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */; }; FFB9C8712BBADDE200A0EF4F /* Selectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8702BBADDE200A0EF4F /* Selectable.swift */; }; FFB9C8752BBADDF700A0EF4F /* SeedInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */; }; + FFBA2D2D2CA2CE9E00D5BBDD /* CodingContainer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C33F752C9B1EC5006316DE /* CodingContainer+Extensions.swift */; }; FFBF065C2BBD2657009D6715 /* GroupStageTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */; }; FFBF065E2BBD8040009D6715 /* MatchListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065D2BBD8040009D6715 /* MatchListView.swift */; }; FFBF06602BBD9F6D009D6715 /* NavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */; }; @@ -2671,6 +2672,7 @@ FF4CBFF82C996C0600151637 /* TabItemModifier.swift in Sources */, FF4CBFF92C996C0600151637 /* DeferredViewModifier.swift in Sources */, FF4CBFFA2C996C0600151637 /* TournamentScheduleView.swift in Sources */, + FFBA2D2D2CA2CE9E00D5BBDD /* CodingContainer+Extensions.swift in Sources */, FF4CBFFB2C996C0600151637 /* MatchFormatStorageView.swift in Sources */, FF4CBFFC2C996C0600151637 /* UmpireView.swift in Sources */, FF4CBFFD2C996C0600151637 /* User.swift in Sources */, @@ -3132,7 +3134,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3155,7 +3157,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.12; + MARKETING_VERSION = 1.0.14; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3176,7 +3178,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -3198,7 +3200,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.12; + MARKETING_VERSION = 1.0.14; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3291,7 +3293,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3313,7 +3315,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.10; + MARKETING_VERSION = 1.0.14; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3333,7 +3335,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 4; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -3354,7 +3356,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.10; + MARKETING_VERSION = 1.0.14; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3375,7 +3377,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3397,7 +3399,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.9; + MARKETING_VERSION = 1.0.14; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3417,7 +3419,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -3438,7 +3440,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.9; + MARKETING_VERSION = 1.0.14; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index da256cf..28c1cff 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -283,6 +283,11 @@ final class PlayerRegistration: ModelObject, Storable { } func setComputedRank(in tournament: Tournament) { + if tournament.isAnimation() { + computedRank = rank ?? 0 + return + } + let currentRank = rank ?? tournament.unrankValue(for: isMalePlayer()) ?? 70_000 switch tournament.tournamentCategory { case .men: diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 160911a..c91457b 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1063,6 +1063,11 @@ defer { registrationDate = previousTeamRegistrationDate } let newTeam = addTeam(team.players, registrationDate: registrationDate, name: team.name) + if isAnimation() { + if newTeam.weight == 0 { + newTeam.weight = team.index(in: teams) ?? 0 + } + } teamsToImport.append(newTeam) } } @@ -1396,10 +1401,14 @@ defer { } func tournamentTitle(_ displayStyle: DisplayStyle = .wide) -> String { - if tournamentLevel == .unlisted, displayStyle == .title, let name { - return name + if tournamentLevel == .unlisted, displayStyle == .title { + if let name { + return name + } else { + return tournamentLevel.localizedLevelLabel(.title) + } } - let title: String = [tournamentLevel.localizedLabel(displayStyle), tournamentCategory.localizedLabel(displayStyle), federalTournamentAge.localizedLabel(displayStyle)].joined(separator: " ") + let title: String = [tournamentLevel.localizedLevelLabel(displayStyle), tournamentCategory.localizedLabel(displayStyle), federalTournamentAge.localizedLabel(displayStyle)].filter({ $0.isEmpty == false }).joined(separator: " ") if displayStyle == .wide, let name { return [title, name].joined(separator: " - ") } else { @@ -1410,9 +1419,9 @@ defer { func localizedTournamentType() -> String { switch tournamentLevel { case .unlisted: - return tournamentLevel.localizedLabel(.short) + return tournamentLevel.localizedLevelLabel(.short) default: - return tournamentLevel.localizedLabel(.short) + tournamentCategory.localizedLabel(.short) + return tournamentLevel.localizedLevelLabel(.short) + tournamentCategory.localizedLabel(.short) } } @@ -1430,7 +1439,9 @@ defer { func formattedDate(_ displayStyle: DisplayStyle = .wide) -> String { switch displayStyle { - case .wide, .title: + case .title: + startDate.formatted(.dateTime.weekday(.abbreviated).day().month(.abbreviated).year()) + case .wide: startDate.formatted(date: Date.FormatStyle.DateStyle.complete, time: Date.FormatStyle.TimeStyle.omitted) case .short: startDate.formatted(date: .numeric, time: .omitted) @@ -1811,6 +1822,11 @@ defer { players.forEach { player in player.teamRegistration = team.id } + if isAnimation() { + if team.weight == 0 { + team.weight = unsortedTeams().count + } + } return team } @@ -1926,9 +1942,9 @@ defer { private func _defaultSorting() -> [MySortDescriptor] { switch teamSorting { case .rank: - [.keyPath(\TeamRegistration.initialWeight), .keyPath(\TeamRegistration.registrationDate!)] + [.keyPath(\TeamRegistration.initialWeight), .keyPath(\TeamRegistration.registrationDate!), .keyPath(\TeamRegistration.id)] case .inscriptionDate: - [.keyPath(\TeamRegistration.registrationDate!), .keyPath(\TeamRegistration.initialWeight)] + [.keyPath(\TeamRegistration.registrationDate!), .keyPath(\TeamRegistration.initialWeight), .keyPath(\TeamRegistration.id)] } } @@ -1938,7 +1954,7 @@ defer { && federalTournamentAge == build.age } - private let _currentSelectionSorting : [MySortDescriptor] = [.keyPath(\.weight), .keyPath(\.registrationDate!)] + private let _currentSelectionSorting : [MySortDescriptor] = [.keyPath(\.weight), .keyPath(\.registrationDate!), .keyPath(\.id)] private func _matchSchedulers() -> [MatchScheduler] { return self.tournamentStore.matchSchedulers.filter { $0.tournament == self.id } diff --git a/PadelClub/Extensions/String+Extensions.swift b/PadelClub/Extensions/String+Extensions.swift index 98567e7..ee29135 100644 --- a/PadelClub/Extensions/String+Extensions.swift +++ b/PadelClub/Extensions/String+Extensions.swift @@ -13,6 +13,10 @@ extension String { return (self.count > length) ? self.prefix(length) + trailing : self } + func prefixTrimmed(_ length: Int) -> String { + String(trimmed.prefix(length)) + } + var trimmed: String { replaceCharactersFromSet(characterSet: .newlines, replacementString: " ").trimmingCharacters(in: .whitespacesAndNewlines) } diff --git a/PadelClub/PadelClubApp.swift b/PadelClub/PadelClubApp.swift index befb5d3..ccf8660 100644 --- a/PadelClub/PadelClubApp.swift +++ b/PadelClub/PadelClubApp.swift @@ -100,7 +100,7 @@ print("Running in Release mode") //try? Tips.resetDatastore() try? Tips.configure([ - .displayFrequency(.immediate), + .displayFrequency(.daily), .datastoreLocation(.applicationDefault) ]) } diff --git a/PadelClub/Utils/FileImportManager.swift b/PadelClub/Utils/FileImportManager.swift index 7ee5ced..f61f0dd 100644 --- a/PadelClub/Utils/FileImportManager.swift +++ b/PadelClub/Utils/FileImportManager.swift @@ -434,39 +434,77 @@ class FileImportManager { let fetchRequest = ImportedPlayer.fetchRequest() let federalContext = PersistenceController.shared.localContainer.viewContext - let results: [TeamHolder] = lines.chunked(into: 2).map { team in + let results: [TeamHolder] = lines.chunked(byParameterAt: 1).map { team in var teamName: String? = nil let players = team.map { player in let data = player.components(separatedBy: separator) - let lastName : String = data[safe: 2]?.trimmed ?? "" - let firstName : String = data[safe: 3]?.trimmed ?? "" + let lastName : String = data[safe: 2]?.prefixTrimmed(50) ?? "" + let firstName : String = data[safe: 3]?.prefixTrimmed(50) ?? "" let sex: PlayerRegistration.PlayerSexType = data[safe: 0] == "f" ? PlayerRegistration.PlayerSexType.female : PlayerRegistration.PlayerSexType.male if data[safe: 1]?.trimmed != nil { teamName = data[safe: 1]?.trimmed } - let phoneNumber : String? = data[safe: 4]?.trimmed - let email : String? = data[safe: 5]?.trimmed + let phoneNumber : String? = data[safe: 4]?.trimmed.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines).prefixTrimmed(50) + let email : String? = data[safe: 5]?.prefixTrimmed(50) let rank : Int? = data[safe: 6]?.trimmed.toInt() - let licenceId : String? = data[safe: 7]?.trimmed - let club : String? = data[safe: 8]?.trimmed + let licenceId : String? = data[safe: 7]?.prefixTrimmed(50) + let club : String? = data[safe: 8]?.prefixTrimmed(200) let predicate = NSPredicate(format: "firstName like[cd] %@ && lastName like[cd] %@", firstName, lastName) fetchRequest.predicate = predicate let found = try? federalContext.fetch(fetchRequest).first if let found, autoSearch { let player = PlayerRegistration(importedPlayer: found) player.setComputedRank(in: tournament) + player.email = email + player.phoneNumber = phoneNumber return player } else { let player = PlayerRegistration(firstName: firstName, lastName: lastName, licenceId: licenceId, rank: rank, sex: sex, clubName: club, phoneNumber: phoneNumber, email: email) if rank == nil, autoSearch { player.setComputedRank(in: tournament) + } else { + player.computedRank = rank ?? 0 } return player } } - return TeamHolder(players: players, tournamentCategory: .men, tournamentAgeCategory: .senior, previousTeam: nil, name: teamName, tournament: tournament) + return TeamHolder(players: players, tournamentCategory: tournament.tournamentCategory, tournamentAgeCategory: tournament.federalTournamentAge, previousTeam: nil, name: teamName, tournament: tournament) } return results } } + +extension Array where Element == String { + /// Groups the array of CSV lines based on the same value at the specified column index. + /// If no key is found, it defaults to chunking the array into groups of 2 lines. + /// - Parameter index: The index of the CSV column to group by. + /// - Returns: An array of arrays, where each inner array contains lines grouped by the CSV parameter or by default chunks of 2. + func chunked(byParameterAt index: Int) -> [[String]] { + var groups: [String: [String]] = [:] + + for line in self { + let columns = line.split(separator: ";", omittingEmptySubsequences: false).map { String($0) } + if index < columns.count { + let key = columns[index] + + if groups[key] == nil { + groups[key] = [] + } + groups[key]?.append(line) + } else { + // Handle out-of-bounds by continuing + print("Warning: Index \(index) out of bounds for line: \(line)") + } + } + + // If no valid groups found, chunk into groups of 2 lines + if groups.isEmpty { + return self.chunked(into: 2) + } else { + // Append groups by parameter value, converting groups.values into an array of arrays + return groups.map { $0.value } + } + } +} + diff --git a/PadelClub/Utils/HtmlGenerator.swift b/PadelClub/Utils/HtmlGenerator.swift index af72534..4139f67 100644 --- a/PadelClub/Utils/HtmlGenerator.swift +++ b/PadelClub/Utils/HtmlGenerator.swift @@ -186,7 +186,7 @@ class HtmlGenerator: ObservableObject { .day() .dateSeparator(.dash)) - let name = tournament.tournamentLevel.localizedLabel() + "-" + tournament.tournamentCategory.importingRawValue + let name = tournament.tournamentLevel.localizedLevelLabel() + "-" + tournament.tournamentCategory.importingRawValue return pdfFolderURL.appendingPathComponent(stringDate + "-" + name + ".pdf") } diff --git a/PadelClub/Utils/LocationManager.swift b/PadelClub/Utils/LocationManager.swift index 3961bf6..0af0e36 100644 --- a/PadelClub/Utils/LocationManager.swift +++ b/PadelClub/Utils/LocationManager.swift @@ -16,7 +16,18 @@ class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate { @Published var postalCode: String? @Published var requestStarted: Bool = false @Published var userReadableCityOrZipcode: String = "" - @Published var lastError: Error? = nil + @Published var lastError: LocalizedError? = nil + + enum LocationError: LocalizedError { + case unknownError(error: Error) + + var errorDescription: String? { + switch self { + case .unknownError(let error): + return "Padel Club n'a pas réussi à vous localiser." + } + } + } override init() { super.init() @@ -49,7 +60,7 @@ class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate { func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print("locationManager didFailWithError", error) requestStarted = false - self.lastError = error + self.lastError = LocationError.unknownError(error: error) } func geocodeCity(cityOrZipcode: String, completion: @escaping (_ placemark: [CLPlacemark]?, _ error: Error?) -> Void) { diff --git a/PadelClub/Utils/PadelRule.swift b/PadelClub/Utils/PadelRule.swift index af5177c..802d741 100644 --- a/PadelClub/Utils/PadelRule.swift +++ b/PadelClub/Utils/PadelRule.swift @@ -48,7 +48,7 @@ struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable { } var identifier: String { - level.localizedLabel()+":"+category.localizedLabel()+":"+age.localizedLabel() + level.localizedLevelLabel()+":"+category.localizedLabel()+":"+age.localizedLabel() } var computedLabel: String { @@ -57,11 +57,11 @@ struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable { } func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { - level.localizedLabel() + category.localizedLabel(.short) + level.localizedLevelLabel() + category.localizedLabel(.short) } var localizedTitle: String { - level.localizedLabel() + " " + category.localizedLabel() + level.localizedLevelLabel() + " " + category.localizedLabel() } var localizedAge: String { @@ -72,7 +72,7 @@ struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable { extension TournamentBuild { init?(category: String, level: String, age: FederalTournamentAge = .senior) { - guard let levelFound = TournamentLevel.allCases.first(where: { $0.localizedLabel() == level }) else { return nil } + guard let levelFound = TournamentLevel.allCases.first(where: { $0.localizedLevelLabel() == level }) else { return nil } var c = category if c.hasPrefix("ME") { @@ -465,7 +465,7 @@ enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable { } } - func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { + func localizedLevelLabel(_ displayStyle: DisplayStyle = .wide) -> String { if self == .unlisted { return displayStyle == .title ? "Animation" : "Anim." } return String(describing: self).capitalized } @@ -837,7 +837,7 @@ enum TournamentCategory: Int, Hashable, Codable, CaseIterable, Identifiable { case .men: switch displayStyle { case .title: - return "DH" + return "Hommes" case .wide: return "Hommes" case .short: @@ -846,7 +846,7 @@ enum TournamentCategory: Int, Hashable, Codable, CaseIterable, Identifiable { case .women: switch displayStyle { case .title: - return "DD" + return "Dames" case .wide: return "Dames" case .short: @@ -855,7 +855,7 @@ enum TournamentCategory: Int, Hashable, Codable, CaseIterable, Identifiable { case .mix: switch displayStyle { case .title: - return "MX" + return "Mixte" case .wide: return "Mixte" case .short: diff --git a/PadelClub/Utils/Tips.swift b/PadelClub/Utils/Tips.swift index ae4c3c8..bb136dd 100644 --- a/PadelClub/Utils/Tips.swift +++ b/PadelClub/Utils/Tips.swift @@ -430,7 +430,7 @@ struct CreateAccountTip: Tip { Action(id: ActionKey.createAccount.rawValue, title: "Créer votre compte") //todo //Action(id: ActionKey.learnMore.rawValue, title: "En savoir plus") - Action(id: ActionKey.accessPadelClubWebPage.rawValue, title: "Jeter un oeil au site Padel Club") + Action(id: ActionKey.accessPadelClubWebPage.rawValue, title: "Voir le site Padel Club") } enum ActionKey: String { @@ -549,6 +549,29 @@ struct TeamsExportTip: Tip { } } +struct PlayerTournamentSearchTip: Tip { + var title: Text { + Text("Cherchez un tournoi autour de vous !") + } + + var message: Text? { + Text("Padel Club facilite la recherche de tournois et l'inscription !") + } + + var image: Image? { + Image(systemName: "trophy.circle") + } + + var actions: [Action] { + Action(id: ActionKey.selectAction.rawValue, title: "Éssayer") + } + + enum ActionKey: String { + case selectAction = "selectAction" + } + +} + struct TipStyleModifier: ViewModifier { @Environment(\.colorScheme) var colorScheme var tint: Color? diff --git a/PadelClub/ViewModel/AgendaDestination.swift b/PadelClub/ViewModel/AgendaDestination.swift index abe2126..96aff9d 100644 --- a/PadelClub/ViewModel/AgendaDestination.swift +++ b/PadelClub/ViewModel/AgendaDestination.swift @@ -7,6 +7,7 @@ import Foundation import SwiftUI +import TipKit enum AgendaDestination: Int, CaseIterable, Identifiable, Selectable, Equatable { var id: Int { self.rawValue } @@ -33,6 +34,15 @@ enum AgendaDestination: Int, CaseIterable, Identifiable, Selectable, Equatable { } } + func associatedTip() -> (any Tip)? { + switch self { + case .around: + return PlayerTournamentSearchTip() + default: + return nil + } + } + func selectionLabel(index: Int) -> String { localizedTitleKey } diff --git a/PadelClub/ViewModel/FederalDataViewModel.swift b/PadelClub/ViewModel/FederalDataViewModel.swift index 010b211..d4d6d24 100644 --- a/PadelClub/ViewModel/FederalDataViewModel.swift +++ b/PadelClub/ViewModel/FederalDataViewModel.swift @@ -25,7 +25,7 @@ class FederalDataViewModel { func filterStatus() -> String { var labels: [String] = [] - labels.append(contentsOf: levels.map { $0.localizedLabel() }.formatList()) + labels.append(contentsOf: levels.map { $0.localizedLevelLabel() }.formatList()) labels.append(contentsOf: categories.map { $0.localizedLabel() }.formatList()) labels.append(contentsOf: ageCategories.map { $0.localizedLabel() }.formatList()) let clubNames = selectedClubs.compactMap { codeClub in diff --git a/PadelClub/ViewModel/Selectable.swift b/PadelClub/ViewModel/Selectable.swift index 6734828..66309f9 100644 --- a/PadelClub/ViewModel/Selectable.swift +++ b/PadelClub/ViewModel/Selectable.swift @@ -7,6 +7,7 @@ import Foundation import SwiftUI +import TipKit protocol Selectable { func selectionLabel(index: Int) -> String @@ -15,9 +16,14 @@ protocol Selectable { func badgeValueColor() -> Color? func displayImageIfValueZero() -> Bool func systemImage() -> String? + func associatedTip() -> (any Tip)? } extension Selectable { + func associatedTip() -> (any Tip)? { + return nil + } + func systemImage() -> String? { return nil } @@ -54,3 +60,30 @@ enum Badge { } } } + +struct SelectionTipViewModifier: ViewModifier { + let selectable: Selectable + let action: () -> Void + func body(content: Content) -> some View { + if let tip = selectable.associatedTip() { + if #available(iOS 18.0, *) { + content + .popoverTip(tip, arrowEdge: .top) { _ in + action() + tip.invalidate(reason: .tipClosed) + } + } else { + content + } + } else { + content + } + } +} + +extension View { + func selectableTipViewModifier(selectable: Selectable, action: @escaping () -> Void) -> some View { + modifier(SelectionTipViewModifier(selectable: selectable, action: action)) + } +} + diff --git a/PadelClub/Views/Calling/CallMessageCustomizationView.swift b/PadelClub/Views/Calling/CallMessageCustomizationView.swift index 93a1779..569f420 100644 --- a/PadelClub/Views/Calling/CallMessageCustomizationView.swift +++ b/PadelClub/Views/Calling/CallMessageCustomizationView.swift @@ -95,6 +95,16 @@ struct CallMessageCustomizationView: View { } .headerProminence(.increased) .navigationBarTitleDisplayMode(.inline) + .navigationBarBackButtonHidden(focusedField != nil) + .toolbar(content: { + if focusedField != nil { + ToolbarItem(placement: .topBarLeading) { + Button("Annuler", role: .cancel) { + focusedField = nil + } + } + } + }) .toolbarBackground(.visible, for: .navigationBar) .navigationTitle("Message de convocation") .toolbar { diff --git a/PadelClub/Views/Cashier/Event/EventSettingsView.swift b/PadelClub/Views/Cashier/Event/EventSettingsView.swift index 58b5428..cad0f0a 100644 --- a/PadelClub/Views/Cashier/Event/EventSettingsView.swift +++ b/PadelClub/Views/Cashier/Event/EventSettingsView.swift @@ -27,7 +27,7 @@ struct EventSettingsView: View { link.append(tournaments.compactMap({ tournament in if let url = tournament.shareURL(pageLink) { var tournamentLink = [String]() - tournamentLink.append(tournament.tournamentTitle()) + tournamentLink.append(tournament.tournamentTitle(.title)) tournamentLink.append(url.absoluteString) return tournamentLink.joined(separator: "\n") } else { @@ -46,12 +46,14 @@ struct EventSettingsView: View { var body: some View { Form { Section { - TextField("Description de l'événement", text: $eventName, axis: .vertical) + TextField("Nom de l'événement", text: $eventName, axis: .vertical) .lineLimit(2) .keyboardType(.alphabet) .multilineTextAlignment(.leading) .frame(maxWidth: .infinity) .focused($textFieldIsFocus) + } header: { + Text("Nom de l'événement") } footer: { if eventName.isEmpty == false { FooterButtonView("effacer le nom") { @@ -85,6 +87,16 @@ struct EventSettingsView: View { } } } + .navigationBarBackButtonHidden(textFieldIsFocus) + .toolbar(content: { + if textFieldIsFocus { + ToolbarItem(placement: .topBarLeading) { + Button("Annuler", role: .cancel) { + textFieldIsFocus = false + } + } + } + }) .toolbar { if textFieldIsFocus { ToolbarItem(placement: .keyboard) { diff --git a/PadelClub/Views/Cashier/Event/TournamentConfiguratorView.swift b/PadelClub/Views/Cashier/Event/TournamentConfiguratorView.swift index 65ecfc3..9333ee9 100644 --- a/PadelClub/Views/Cashier/Event/TournamentConfiguratorView.swift +++ b/PadelClub/Views/Cashier/Event/TournamentConfiguratorView.swift @@ -22,11 +22,12 @@ struct TournamentConfigurationView: View { var body: some View { Picker(selection: $tournament.federalLevelCategory, label: Text("Niveau")) { ForEach(TournamentLevel.allCases) { type in - Text(type.localizedLabel(.title)).tag(type) + Text(type.localizedLevelLabel(.title)).tag(type) } } .onChange(of: tournament.federalLevelCategory) { if tournament.federalLevelCategory == .unlisted { + tournament.hideTeamsWeight = true tournament.federalCategory = .unlisted tournament.federalAgeCategory = .unlisted } else { diff --git a/PadelClub/Views/Club/ClubDetailView.swift b/PadelClub/Views/Club/ClubDetailView.swift index cb3b23b..b04b4f0 100644 --- a/PadelClub/Views/Club/ClubDetailView.swift +++ b/PadelClub/Views/Club/ClubDetailView.swift @@ -213,6 +213,16 @@ struct ClubDetailView: View { } } } + .navigationBarBackButtonHidden(focusedField != nil) + .toolbar(content: { + if focusedField != nil { + ToolbarItem(placement: .topBarLeading) { + Button("Annuler", role: .cancel) { + focusedField = nil + } + } + } + }) .keyboardType(.alphabet) .autocorrectionDisabled() .defaultFocus($focusedField, ._name, priority: .automatic) diff --git a/PadelClub/Views/Club/CourtView.swift b/PadelClub/Views/Club/CourtView.swift index 8986cc4..c4e89f8 100644 --- a/PadelClub/Views/Club/CourtView.swift +++ b/PadelClub/Views/Club/CourtView.swift @@ -12,6 +12,7 @@ struct CourtView: View { @EnvironmentObject var dataStore: DataStore @Bindable var court: Court @State private var name: String = "" + @FocusState var focusedField: Court.CodingKeys? init(court: Court) { self.court = court @@ -23,6 +24,7 @@ struct CourtView: View { Section { LabeledContent { TextField("Nom", text: $name) + .focused($focusedField, equals: ._name) .autocorrectionDisabled() .keyboardType(.alphabet) .multilineTextAlignment(.trailing) @@ -71,6 +73,16 @@ struct CourtView: View { Logger.error(error) } } + .navigationBarBackButtonHidden(focusedField != nil) + .toolbar(content: { + if focusedField != nil { + ToolbarItem(placement: .topBarLeading) { + Button("Annuler", role: .cancel) { + focusedField = nil + } + } + } + }) .navigationTitle(court.courtTitle()) .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.visible, for: .navigationBar) diff --git a/PadelClub/Views/Components/GenericDestinationPickerView.swift b/PadelClub/Views/Components/GenericDestinationPickerView.swift index a7f4871..59079c5 100644 --- a/PadelClub/Views/Components/GenericDestinationPickerView.swift +++ b/PadelClub/Views/Components/GenericDestinationPickerView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import TipKit struct GenericDestinationPickerView: View { @EnvironmentObject var dataStore: DataStore @@ -49,6 +50,9 @@ struct GenericDestinationPickerView: .contentShape(Capsule()) } } + .selectableTipViewModifier(selectable: destination) { + selectedDestination = destination + } .padding() .background { Capsule() diff --git a/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift index 948dfa6..64abb0f 100644 --- a/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift +++ b/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift @@ -20,7 +20,8 @@ struct GroupStageSettingsView: View { @State private var presentConfirmationButton: Bool = false @State private var size: Int @State private var courtIndex: Int - + @FocusState var focusedField: GroupStage.CodingKeys? + init(groupStage: GroupStage) { _groupStage = Bindable(groupStage) _groupStageName = .init(wrappedValue: groupStage.name ?? "") @@ -37,6 +38,8 @@ struct GroupStageSettingsView: View { Section { TextField("Nom de la poule", text: $groupStageName) .keyboardType(.alphabet) + .focused($focusedField, equals: ._name) + .submitLabel(.done) .frame(maxWidth: .infinity) .onSubmit { groupStageName = groupStageName.trimmed @@ -152,6 +155,16 @@ struct GroupStageSettingsView: View { presentConfirmationButton = true } } + .navigationBarBackButtonHidden(focusedField != nil) + .toolbar(content: { + if focusedField != nil { + ToolbarItem(placement: .topBarLeading) { + Button("Annuler", role: .cancel) { + focusedField = nil + } + } + } + }) .navigationTitle("Paramètres") .toolbarBackground(.visible, for: .navigationBar) } diff --git a/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift b/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift index 000ba3f..2426cf5 100644 --- a/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift +++ b/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift @@ -23,15 +23,44 @@ struct TournamentLookUpView: View { @State private var requestedToGetAllPages: Bool = false @State private var revealSearchParameters: Bool = true @State private var presentAlert: Bool = false + @State private var confirmSearch: Bool = false var tournaments: [FederalTournament] { federalDataViewModel.searchedFederalTournaments } + + var showLastError: Binding { + Binding { + locationManager.lastError != nil + } set: { value in + if value == false { + locationManager.lastError = nil + } + } + + } 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("Attention", isPresented: $presentAlert, actions: { Button { presentAlert = false @@ -70,7 +99,11 @@ struct TournamentLookUpView: View { ToolbarItem(placement: .bottomBar) { if revealSearchParameters { FooterButtonView("Lancer la recherche") { - runSearch() + if dataStore.appSettings.city.isEmpty { + confirmSearch = true + } else { + runSearch() + } } .disabled(searching) } else if searching { @@ -230,31 +263,6 @@ struct TournamentLookUpView: View { } } - @ViewBuilder - var searchContollerView: some View { - Section { - Button { - runSearch() - } label: { - HStack { - Label("Chercher un tournoi", systemImage: "magnifyingglass") - if searching { - Spacer() - ProgressView() - } - } - } - Button { - dataStore.appSettings.resetSearch() - locationManager.location = nil - locationManager.city = nil - revealSearchParameters = true - } label: { - Label("Ré-initialiser la recherche", systemImage: "xmark.circle") - } - } - } - @ViewBuilder var searchParametersView: some View { @Bindable var appSettings = dataStore.appSettings @@ -335,7 +343,7 @@ struct TournamentLookUpView: View { NavigationLink { List([TournamentLevel.p25, TournamentLevel.p100, TournamentLevel.p250, TournamentLevel.p500, TournamentLevel.p1000, TournamentLevel.p1500, TournamentLevel.p2000], selection: $appSettings.tournamentLevels) { type in - Text(type.localizedLabel()) + Text(type.localizedLevelLabel()) } .navigationTitle("Niveaux") .environment(\.editMode, Binding.constant(EditMode.active)) @@ -413,7 +421,7 @@ struct TournamentLookUpView: View { if dataStore.appSettings.tournamentLevels.isEmpty || dataStore.appSettings.tournamentLevels.count == TournamentLevel.allCases.count { Text("Tous les niveaux") } else { - Text(levels.map({ $0.localizedLabel() }).joined(separator: ", ")) + Text(levels.map({ $0.localizedLevelLabel() }).joined(separator: ", ")) } } diff --git a/PadelClub/Views/Navigation/Toolbox/RankCalculatorView.swift b/PadelClub/Views/Navigation/Toolbox/RankCalculatorView.swift index 0ebd1bf..28b4050 100644 --- a/PadelClub/Views/Navigation/Toolbox/RankCalculatorView.swift +++ b/PadelClub/Views/Navigation/Toolbox/RankCalculatorView.swift @@ -17,7 +17,7 @@ struct RankCalculatorView: View { Section { HStack { let ordinal = NumberFormatter.ordinal.string(from: NSNumber(value:rank))! - Text("\(ordinal) d'un \(tournamentLevel.localizedLabel()) de \(count.localizedLabel()) équipes:") + Text("\(ordinal) d'un \(tournamentLevel.localizedLevelLabel()) de \(count.localizedLabel()) équipes:") Spacer() Text(tournamentLevel.points(for: rank-1, count: count.rawValue).formatted(.number.sign(strategy: .always()))) } @@ -25,7 +25,7 @@ struct RankCalculatorView: View { Section { Picker(selection: $tournamentLevel) { ForEach(TournamentLevel.allCases) { level in - Text(level.localizedLabel()).tag(level) + Text(level.localizedLevelLabel()).tag(level) } } label: { Label("Niveau", systemImage: "gauge.medium") diff --git a/PadelClub/Views/Planning/PlanningSettingsView.swift b/PadelClub/Views/Planning/PlanningSettingsView.swift index 1286f4d..645faf0 100644 --- a/PadelClub/Views/Planning/PlanningSettingsView.swift +++ b/PadelClub/Views/Planning/PlanningSettingsView.swift @@ -52,7 +52,7 @@ struct PlanningSettingsView: View { Section { DatePicker(selection: $tournament.startDate) { - Text(tournament.startDate.formatted(.dateTime.weekday(.wide)).capitalized) + Text(tournament.startDate.formatted(.dateTime.weekday(.wide)).capitalized).lineLimit(1) } LabeledContent { StepperView(count: $tournament.dayDuration, minimum: 1) diff --git a/PadelClub/Views/Player/PlayerDetailView.swift b/PadelClub/Views/Player/PlayerDetailView.swift index 3d65b72..f80848e 100644 --- a/PadelClub/Views/Player/PlayerDetailView.swift +++ b/PadelClub/Views/Player/PlayerDetailView.swift @@ -168,17 +168,22 @@ struct PlayerDetailView: View { } } } - .scrollDismissesKeyboard(.immediately) .onChange(of: player.hasArrived) { _save() } .onChange(of: player.sex) { _save() } - .onChange(of: player.computedRank) { - player.team()?.updateWeight(inTournamentCategory: tournament.tournamentCategory) - _save() - } + .navigationBarBackButtonHidden(focusedField != nil) + .toolbar(content: { + if focusedField != nil { + ToolbarItem(placement: .topBarLeading) { + Button("Annuler", role: .cancel) { + focusedField = nil + } + } + } + }) .headerProminence(.increased) .navigationTitle("Édition") .navigationBarTitleDisplayMode(.inline) diff --git a/PadelClub/Views/Shared/TournamentFilterView.swift b/PadelClub/Views/Shared/TournamentFilterView.swift index 8e84a0b..6af4d7c 100644 --- a/PadelClub/Views/Shared/TournamentFilterView.swift +++ b/PadelClub/Views/Shared/TournamentFilterView.swift @@ -63,7 +63,7 @@ struct TournamentFilterView: View { } } } label: { - Text(level.localizedLabel(.title)) + Text(level.localizedLevelLabel(.title)) } } } header: { diff --git a/PadelClub/Views/Team/EditingTeamView.swift b/PadelClub/Views/Team/EditingTeamView.swift index 7e5f4be..bd32b32 100644 --- a/PadelClub/Views/Team/EditingTeamView.swift +++ b/PadelClub/Views/Team/EditingTeamView.swift @@ -21,7 +21,8 @@ struct EditingTeamView: View { @State private var registrationDate : Date @State private var callDate : Date @State private var name: String - + @FocusState private var focusedField: TeamRegistration.CodingKeys? + var messageSentFailed: Binding { Binding { sentError != nil @@ -137,9 +138,12 @@ struct EditingTeamView: View { })) { Text("Forfait") } - + } + + Section { TextField("Nom de l'équipe", text: $name) .autocorrectionDisabled() + .focused($focusedField, equals: ._name) .keyboardType(.alphabet) .frame(maxWidth: .infinity) .submitLabel(.done) @@ -153,6 +157,8 @@ struct EditingTeamView: View { _save() } + } header: { + Text("Nom de l'équipe") } Section { @@ -182,6 +188,16 @@ struct EditingTeamView: View { } } } + .navigationBarBackButtonHidden(focusedField != nil) + .toolbar(content: { + if focusedField != nil { + ToolbarItem(placement: .topBarLeading) { + Button("Annuler", role: .cancel) { + focusedField = nil + } + } + } + }) .alert("Un problème est survenu", isPresented: messageSentFailed) { Button("OK") { } @@ -200,8 +216,6 @@ struct EditingTeamView: View { case .failed: self.sentError = .messageFailed case .sent: - let uncalledTeams = team.getPhoneNumbers().isEmpty - if networkMonitor.connected == false { self.contactType = nil if team.getPhoneNumbers().isEmpty == false { @@ -232,8 +246,6 @@ struct EditingTeamView: View { self.contactType = nil self.sentError = .mailFailed case .sent: - let uncalledTeams = team.getMail().isEmpty - if networkMonitor.connected == false { self.contactType = nil if team.getMail().isEmpty == false { diff --git a/PadelClub/Views/Tournament/FileImportView.swift b/PadelClub/Views/Tournament/FileImportView.swift index c6644ff..99bf6b9 100644 --- a/PadelClub/Views/Tournament/FileImportView.swift +++ b/PadelClub/Views/Tournament/FileImportView.swift @@ -85,11 +85,18 @@ struct FileImportView: View { @State private var presentFormatHelperView: Bool = false @State private var validatedTournamentIds: Set = Set() + init(defaultFileProvider: FileImportManager.FileProvider = .frenchFederation) { + _fileProvider = .init(wrappedValue: defaultFileProvider) + } + var tournamentStore: TournamentStore { return self.tournament.tournamentStore } private func filteredTeams(tournament: Tournament) -> [FileImportManager.TeamHolder] { + if tournament.isAnimation() { + return teams.sorted(by: \.weight) + } return teams.filter { $0.tournamentCategory == tournament.tournamentCategory && $0.tournamentAgeCategory == tournament.federalTournamentAge }.sorted(by: \.weight) } @@ -309,10 +316,14 @@ struct FileImportView: View { LabeledContent { Text(_filteredTeams.count.formatted()) } label: { - Text("Équipe\(_filteredTeams.count.pluralSuffix) \(tournament.tournamentCategory.importingRawValue) \(tournament.federalTournamentAge.importingRawValue) détectée\(_filteredTeams.count.pluralSuffix)") + if tournament.isAnimation() { + Text("Équipe\(_filteredTeams.count.pluralSuffix) détectée\(_filteredTeams.count.pluralSuffix)") + } else { + Text("Équipe\(_filteredTeams.count.pluralSuffix) \(tournament.tournamentCategory.importingRawValue) \(tournament.federalTournamentAge.importingRawValue.lowercased()) détectée\(_filteredTeams.count.pluralSuffix)") + } } } footer: { - if previousTeams.isEmpty == false { + if previousTeams.isEmpty == false, tournament.isAnimation() == false { Text("La liste ci-dessous n'est qu'une indication d'évolution par rapport au seul poids d'équipe. Cela ne tient pas compte des dates d'inscriptions, WCs et autres éléments.").foregroundStyle(.logoRed) } } @@ -535,6 +546,9 @@ struct FileImportView: View { Section { HStack { VStack(alignment: .leading) { + if let teamName = team.name { + Text(teamName).foregroundStyle(.secondary) + } ForEach(team.players.sorted(by: \.computedRank)) { Text($0.playerLabel()) } diff --git a/PadelClub/Views/Tournament/Screen/AddTeamView.swift b/PadelClub/Views/Tournament/Screen/AddTeamView.swift index cd0f484..ca7ad1e 100644 --- a/PadelClub/Views/Tournament/Screen/AddTeamView.swift +++ b/PadelClub/Views/Tournament/Screen/AddTeamView.swift @@ -149,7 +149,7 @@ struct AddTeamView: View { } .toolbar { ToolbarItem(placement: .cancellationAction) { - Button("Terminer", role: .cancel) { + Button("Annuler", role: .cancel) { dismiss() } } @@ -258,6 +258,7 @@ struct AddTeamView: View { } private func _isDuplicate() -> Bool { + if tournament.isAnimation() { return false } let ids : [String?] = _currentSelectionIds() if tournament.selectedSortedTeams().anySatisfy({ $0.containsExactlyPlayerLicenses(ids) }) { return true @@ -391,8 +392,8 @@ struct AddTeamView: View { ForEach(createdPlayerIds.sorted(), id: \.self) { id in if let p = createdPlayers.first(where: { $0.id == id }) { VStack(alignment: .leading, spacing: 0) { - if let player = unsortedPlayers.first(where: { $0.licenceId == p.licenceId }), editedTeam?.includes(player: player) == false { - Text("Déjà inscrit !").foregroundStyle(.logoRed).bold() + if let player = unsortedPlayers.first(where: { ($0.licenceId == p.licenceId && $0.licenceId != nil) }), editedTeam?.includes(player: player) == false { + Text("Déjà inscrit !!").foregroundStyle(.logoRed).bold() } PlayerView(player: p).tag(p.id) .environment(tournament) @@ -420,7 +421,7 @@ struct AddTeamView: View { } else { RowButtonView("Confirmer") { _updateTeam(checkDuplicates: false) - editedTeam = nil + dismiss() } } } header: { diff --git a/PadelClub/Views/Tournament/Screen/BroadcastView.swift b/PadelClub/Views/Tournament/Screen/BroadcastView.swift index 661f69e..d687e29 100644 --- a/PadelClub/Views/Tournament/Screen/BroadcastView.swift +++ b/PadelClub/Views/Tournament/Screen/BroadcastView.swift @@ -42,7 +42,7 @@ struct BroadcastView: View { navigation.selectedTab = .umpire } - RowButtonView("Jeter un oeil au site Padel Club") { + RowButtonView("Voir le site Padel Club") { UIApplication.shared.open(URLs.main.url) } } @@ -104,12 +104,12 @@ struct BroadcastView: View { Section { Toggle(isOn: $tournament.isPrivate) { Text("Tournoi privé") - if (tournament.isPrivate && Guard.main.purchasedTransactions.isEmpty) { - Text("Vous devez disposer d'une offre pour rendre publique ce tournoi.") - .foregroundStyle(.logoRed) - } } - .disabled(_disablePrivateToggle()) + + Toggle(isOn: $tournament.hideTeamsWeight) { + Text("Masquer les poids des équipes") + } + } footer: { let verb : String = tournament.isPrivate ? "est" : "sera" let footerString = " Le tournoi \(verb) masqué sur le site [Padel Club](\(URLs.main.rawValue))" @@ -160,10 +160,6 @@ struct BroadcastView: View { Text("Publication prévue") } } - - Toggle(isOn: $tournament.hideTeamsWeight) { - Text("Masquer les poids des équipes") - } } header: { Text("Liste des équipes") } footer: { @@ -273,32 +269,34 @@ struct BroadcastView: View { } } .toolbar(content: { - ToolbarItem(placement: .topBarTrailing) { - Menu { - Section { - let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings, .broadcast, .clubBroadcast] - Picker(selection: $pageLink) { - ForEach(links) { pageLink in - Text(pageLink.localizedLabel()).tag(pageLink) + if StoreCenter.main.userId != nil, tournament.isPrivate == false, tournament.club() != nil { + ToolbarItem(placement: .topBarTrailing) { + Menu { + Section { + let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings, .broadcast, .clubBroadcast] + Picker(selection: $pageLink) { + ForEach(links) { pageLink in + Text(pageLink.localizedLabel()).tag(pageLink) + } + } label: { + Text("Choisir la page à partager") } - } label: { - Text("Choisir la page à partager") + .pickerStyle(.menu) + actionForURL(title: "Partager la page '" + pageLink.localizedLabel() + "'", url: tournament.shareURL(pageLink)) + } header: { + Text("Lien du tournoi à partager") } - .pickerStyle(.menu) - actionForURL(title: "Partager la page '" + pageLink.localizedLabel() + "'", url: tournament.shareURL(pageLink)) - } header: { - Text("Lien du tournoi à partager") - } - - Section { - let club = tournament.club() - actionForURL(title: (club == nil) ? "Aucun club indiqué pour ce tournoi" : club!.clubTitle(), description: "Page du club", url: club?.shareURL()) - actionForURL(title: "Padel Club", url: URLs.main.url) - } header: { - Text("Autres liens") + + Section { + let club = tournament.club() + actionForURL(title: (club == nil) ? "Aucun club indiqué pour ce tournoi" : club!.clubTitle(), description: "Page du club", url: club?.shareURL()) + actionForURL(title: "Padel Club", url: URLs.main.url) + } header: { + Text("Autres liens") + } + } label: { + Label("Partager les liens", systemImage: "square.and.arrow.up") } - } label: { - Label("Partager les liens", systemImage: "square.and.arrow.up") } } }) @@ -320,15 +318,7 @@ struct BroadcastView: View { _save() } } - - private func _disablePrivateToggle() -> Bool { - #if DEBUG - return false - #else - return (tournament.isPrivate && Guard.main.purchasedTransactions.isEmpty) - #endif - } - + private func _save() { do { if [tournament.publishTeams, tournament.publishSummons, tournament.publishBrackets, tournament.publishGroupStages].anySatisfy({ $0 == true }) { diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift index 35afb65..1258938 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift @@ -28,6 +28,26 @@ struct TournamentGeneralSettingsView: View { Section { TournamentDatePickerView() TournamentDurationManagerView() + LabeledContent { + TextField(tournament.isFree() ? "Gratuite" : "Inscription", value: $entryFee, format: .currency(code: Locale.current.currency?.identifier ?? "EUR")) + .keyboardType(.decimalPad) + .multilineTextAlignment(.trailing) + .frame(maxWidth: .infinity) + .focused($focusedField, equals: ._entryFee) + } label: { + Text("Inscription") + } + + } + + Section { + TextField("Nom du tournoi", text: $tournamentName, axis: .vertical) + .lineLimit(2) + .frame(maxWidth: .infinity) + .keyboardType(.alphabet) + .focused($focusedField, equals: ._name) + } header: { + Text("Nom du tournoi") } Section { @@ -70,27 +90,17 @@ struct TournamentGeneralSettingsView: View { Text(tournament.loserBracketMode.localizedLoserBracketModeDescription()) } } - - Section { - LabeledContent { - TextField(tournament.isFree() ? "Gratuite" : "Inscription", value: $entryFee, format: .currency(code: Locale.current.currency?.identifier ?? "EUR")) - .keyboardType(.decimalPad) - .multilineTextAlignment(.trailing) - .frame(maxWidth: .infinity) - .focused($focusedField, equals: ._entryFee) - } label: { - Text("Inscription") + } + .navigationBarBackButtonHidden(focusedField != nil) + .toolbar(content: { + if focusedField != nil { + ToolbarItem(placement: .topBarLeading) { + Button("Annuler", role: .cancel) { + focusedField = nil + } } } - - Section { - TextField("Nom du tournoi", text: $tournamentName, axis: .vertical) - .lineLimit(2) - .frame(maxWidth: .infinity) - .keyboardType(.alphabet) - .focused($focusedField, equals: ._name) - } - } + }) .toolbarBackground(.visible, for: .navigationBar) .toolbar { if focusedField != nil { diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentLevelPickerView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentLevelPickerView.swift index 5550b66..bc81b41 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentLevelPickerView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentLevelPickerView.swift @@ -15,13 +15,22 @@ struct TournamentLevelPickerView: View { Picker(selection: $tournament.tournamentLevel, label: Text("Niveau")) { ForEach(TournamentLevel.allCases) { type in - Text(type.localizedLabel(.title)).tag(type) + Text(type.localizedLevelLabel(.title)).tag(type) } } .onChange(of: tournament.federalLevelCategory) { if tournament.federalLevelCategory == .unlisted { + tournament.hideTeamsWeight = true tournament.federalCategory = .unlisted tournament.federalAgeCategory = .unlisted + } else { + tournament.hideTeamsWeight = false + if tournament.federalCategory == .unlisted { + tournament.federalCategory = .men + } + if tournament.federalAgeCategory == .unlisted { + tournament.federalAgeCategory = .senior + } } } diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index 353fd7e..16ddaf8 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -231,7 +231,7 @@ struct InscriptionManagerView: View { _setHash() }) { NavigationStack { - FileImportView() + FileImportView(defaultFileProvider: tournament.isAnimation() ? .custom : .frenchFederation) } .tint(.master) } @@ -308,44 +308,52 @@ struct InscriptionManagerView: View { .symbolVariant(filterMode == .all ? .none : .fill) } Menu { - if tournament.inscriptionClosed() == false { - Menu { - _sortingTypePickerView() - } label: { - Text("Méthode de sélection") - Text(tournament.teamSorting.localizedLabel()) - } - Divider() - rankingDateSourcePickerView(showDateInLabel: true) - - Divider() - Button { - tournament.lockRegistration() - _save() - } label: { - Label("Clôturer", systemImage: "lock") - } - Divider() - _sharingTeamsMenuView() - Button { - presentImportView = true - } label: { - Label("Importer beach-padel", systemImage: "square.and.arrow.down") - } - Link(destination: URLs.beachPadel.url) { - Label("beach-padel.app.fft.fr", systemImage: "safari") + if tournament.isAnimation() == false { + if tournament.inscriptionClosed() == false { + Menu { + _sortingTypePickerView() + } label: { + Text("Méthode de sélection") + Text(tournament.teamSorting.localizedLabel()) + } + Divider() + rankingDateSourcePickerView(showDateInLabel: true) + + Divider() + Button { + tournament.lockRegistration() + _save() + } label: { + Label("Clôturer", systemImage: "lock") + } + Divider() + _sharingTeamsMenuView() + Button { + presentImportView = true + } label: { + Label("Importer beach-padel", systemImage: "square.and.arrow.down") + } + Link(destination: URLs.beachPadel.url) { + Label("beach-padel.app.fft.fr", systemImage: "safari") + } + } else { + + _sharingTeamsMenuView() + + Divider() + + Button { + tournament.unlockRegistration() + _save() + } label: { + Label("Ré-ouvrir", systemImage: "lock.open") + } } } else { - - _sharingTeamsMenuView() - - Divider() - Button { - tournament.unlockRegistration() - _save() + presentImportView = true } label: { - Label("Ré-ouvrir", systemImage: "lock.open") + Label("Importer un fichier", systemImage: "square.and.arrow.down") } } } label: { @@ -438,8 +446,11 @@ struct InscriptionManagerView: View { if presentSearch == false { _informationView() - _rankHandlerView() - _relatedTips() + + if tournament.isAnimation() == false { + _rankHandlerView() + _relatedTips() + } } let teams = searchField.isEmpty ? filteredTeams : filteredTeams.filter({ $0.contains(searchField.canonicalVersion) }) @@ -650,7 +661,7 @@ struct InscriptionManagerView: View { .listRowSeparator(.hidden) let registrationIssues = tournament.registrationIssues() - if registrationIssues > 0 { + if tournament.isAnimation() == false, registrationIssues > 0 { NavigationLink { InscriptionInfoView() .environment(tournament) @@ -660,7 +671,7 @@ struct InscriptionManagerView: View { .foregroundStyle(.logoRed) .fontWeight(.bold) } label: { - Text("Problèmes détéctés") + Text("Problèmes détectés") } } } diff --git a/PadelClub/Views/Tournament/Screen/TableStructureView.swift b/PadelClub/Views/Tournament/Screen/TableStructureView.swift index b33820c..5ce64f2 100644 --- a/PadelClub/Views/Tournament/Screen/TableStructureView.swift +++ b/PadelClub/Views/Tournament/Screen/TableStructureView.swift @@ -94,6 +94,8 @@ struct TableStructureView: View { } label: { Text("Nombre de poules") } + } footer: { + Text("Vous pourrez modifier la taille de vos poules de manière spécifique dans l'écran des poules.") } if groupStageCount > 0 { diff --git a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift index b0ed71a..dc06df0 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift @@ -250,8 +250,9 @@ struct TournamentRankView: View { } } } + + Spacer() if tournament.isAnimation() == false && key > 0 { - Spacer() VStack(alignment: .trailing) { HStack(alignment: .lastTextBaseline, spacing: 0.0) { Text(tournament.tournamentLevel.points(for: key - 1, count: tournament.teamCount).formatted(.number.sign(strategy: .always()))) diff --git a/PadelClub/Views/Tournament/Shared/TournamentCellView.swift b/PadelClub/Views/Tournament/Shared/TournamentCellView.swift index 1e9ab62..94a7353 100644 --- a/PadelClub/Views/Tournament/Shared/TournamentCellView.swift +++ b/PadelClub/Views/Tournament/Shared/TournamentCellView.swift @@ -88,7 +88,7 @@ struct TournamentCellView: View { .font(.caption) } HStack(alignment: .bottom) { - Text(build.level.localizedLabel()) + Text(build.level.localizedLevelLabel()) .fontWeight(.semibold) if displayStyle == .wide { VStack(alignment: .leading, spacing: 0) { diff --git a/PadelClub/Views/Tournament/Subscription/Guard.swift b/PadelClub/Views/Tournament/Subscription/Guard.swift index 0ad429a..64cbdac 100644 --- a/PadelClub/Views/Tournament/Subscription/Guard.swift +++ b/PadelClub/Views/Tournament/Subscription/Guard.swift @@ -190,7 +190,7 @@ import LeStorage } func userFilteredPurchases() -> [StoreKit.Transaction] { - Logger.log("self.purchasedTransactions = \(self.purchasedTransactions.count)") +// Logger.log("self.purchasedTransactions = \(self.purchasedTransactions.count)") guard let userId = StoreCenter.main.userId, let currentUserUUID: UUID = UUID(uuidString: userId) else { return [] } @@ -279,7 +279,7 @@ import LeStorage let tournamentCreditCount = self._purchasedTournamentCount() // let notDeletedTournamentCount = DataStore.shared.tournaments.filter { $0.isDeleted == false }.count - Logger.log("unitlyPayed = \(unitlyPayed), purchased = \(tournamentCreditCount) ") +// Logger.log("unitlyPayed = \(unitlyPayed), purchased = \(tournamentCreditCount) ") return tournamentCreditCount - unitlyPayed } diff --git a/PadelClub/Views/Tournament/Subscription/PurchaseListView.swift b/PadelClub/Views/Tournament/Subscription/PurchaseListView.swift index b9960f1..e704e93 100644 --- a/PadelClub/Views/Tournament/Subscription/PurchaseListView.swift +++ b/PadelClub/Views/Tournament/Subscription/PurchaseListView.swift @@ -68,7 +68,7 @@ class PurchaseManager: ObservableObject { } } - Logger.log("purchases = \(rows)") +// Logger.log("purchases = \(rows)") self.purchaseRows = rows } diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index 7dbe23b..e12017a 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -141,7 +141,7 @@ struct TournamentView: View { ToolbarItem(placement: .principal) { VStack(spacing: -4.0) { Text(tournament.tournamentTitle(.title)).font(.headline) - Text(tournament.formattedDate()) + Text(tournament.formattedDate(.title)) .font(.subheadline).foregroundStyle(.secondary) } .popoverTip(tournamentSelectionTip)