From fbca6c3e21c5803af4f28a944fcbfbabcb65d2bd Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 3 Sep 2024 11:11:13 +0200 Subject: [PATCH 01/23] b3 --- PadelClub.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index b938fc5..be6c020 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -1960,7 +1960,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\""; @@ -2010,7 +2010,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; From a1f53ab8fb6222cc4b527e1d83da4896f5dfab94 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 3 Sep 2024 14:11:06 +0200 Subject: [PATCH 02/23] xcode 16 --- PadelClub.xcodeproj/project.pbxproj | 27 +++---------------- .../xcschemes/PadelClub Raw.xcscheme | 2 +- .../xcshareddata/xcschemes/PadelClub.xcscheme | 2 +- 3 files changed, 5 insertions(+), 26 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index be6c020..9df465b 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -1456,7 +1456,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1520; - LastUpgradeCheck = 1530; + LastUpgradeCheck = 1600; TargetAttributes = { C425D3FC2B6D249D002A7B48 = { CreatedOnToolsVersion = 15.2; @@ -1838,6 +1838,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -1867,14 +1868,12 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", @@ -1892,7 +1891,6 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; @@ -1901,6 +1899,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -1930,7 +1929,6 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -1961,13 +1959,9 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 3; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; - ENABLE_MODULE_VERIFIER = YES; - ENABLE_PREVIEWS = YES; - GCC_OPTIMIZATION_LEVEL = fast; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = PadelClub/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Padel Club"; @@ -1986,17 +1980,12 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0.4; - MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; - OTHER_SWIFT_FLAGS = "-Onone"; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -2014,8 +2003,6 @@ DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; - ENABLE_MODULE_VERIFIER = YES; - ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = PadelClub/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Padel Club"; @@ -2034,16 +2021,12 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0.4; - MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; - OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-function-bodies=5 -Xfrontend -warn-long-expression-type-checking=20 -Xfrontend -warn-long-function-bodies=50"; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -2052,7 +2035,6 @@ C425D4252B6D249E002A7B48 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -2072,7 +2054,6 @@ C425D4262B6D249E002A7B48 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -2092,7 +2073,6 @@ C425D4282B6D249E002A7B48 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 526E96RFNP; @@ -2110,7 +2090,6 @@ C425D4292B6D249E002A7B48 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 526E96RFNP; diff --git a/PadelClub.xcodeproj/xcshareddata/xcschemes/PadelClub Raw.xcscheme b/PadelClub.xcodeproj/xcshareddata/xcschemes/PadelClub Raw.xcscheme index c2b6b23..ec242ea 100644 --- a/PadelClub.xcodeproj/xcshareddata/xcschemes/PadelClub Raw.xcscheme +++ b/PadelClub.xcodeproj/xcshareddata/xcschemes/PadelClub Raw.xcscheme @@ -1,6 +1,6 @@ Date: Thu, 5 Sep 2024 12:43:51 +0200 Subject: [PATCH 03/23] fix regression --- PadelClub.xcodeproj/project.pbxproj | 8 ++++---- PadelClub/Data/Tournament.swift | 2 +- PadelClub/ViewModel/SeedInterval.swift | 8 ++++++++ PadelClub/Views/Match/MatchSetupView.swift | 2 +- PadelClub/Views/Round/RoundView.swift | 4 ++-- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 9df465b..42b5196 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -1958,7 +1958,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; @@ -1979,7 +1979,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.4; + MARKETING_VERSION = 1.0.5; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1999,7 +1999,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; @@ -2020,7 +2020,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.4; + MARKETING_VERSION = 1.0.5; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 5ab4fd8..3fe8d03 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1211,7 +1211,7 @@ defer { others.forEach { round in print("round", round.roundTitle()) if let interval = round.seedInterval() { - print("interval", interval.localizedLabel()) + print("interval", interval.localizedInterval()) let playedMatches = round.playedMatches().filter { $0.disabled == false || $0.isReady() } print("playedMatches", playedMatches.count) let winners = playedMatches.compactMap({ $0.winningTeamId }).filter({ ids.contains($0) == false }) diff --git a/PadelClub/ViewModel/SeedInterval.swift b/PadelClub/ViewModel/SeedInterval.swift index e34f620..65641a3 100644 --- a/PadelClub/ViewModel/SeedInterval.swift +++ b/PadelClub/ViewModel/SeedInterval.swift @@ -57,4 +57,12 @@ struct SeedInterval: Hashable, Comparable { return "Place \(first) à \(last)" } } + + func localizedInterval(_ displayStyle: DisplayStyle = .wide) -> String { + if dimension < 2 { + return "#\(first) / #\(last)" + } else { + return "#\(first) à #\(last)" + } + } } diff --git a/PadelClub/Views/Match/MatchSetupView.swift b/PadelClub/Views/Match/MatchSetupView.swift index 296c4b4..65a98f7 100644 --- a/PadelClub/Views/Match/MatchSetupView.swift +++ b/PadelClub/Views/Match/MatchSetupView.swift @@ -138,7 +138,7 @@ struct MatchSetupView: View { } } } label: { - Label(seedGroup.localizedLabel(), systemImage: "dice") + Label(seedGroup.localizedInterval(), systemImage: "dice") } } } label: { diff --git a/PadelClub/Views/Round/RoundView.swift b/PadelClub/Views/Round/RoundView.swift index 3891599..3e52112 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -123,7 +123,7 @@ struct RoundView: View { if availableSeeds.isEmpty == false, let availableSeedGroup { Section { - RowButtonView("Placer \(availableSeedGroup.localizedLabel())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) { + RowButtonView("Placer \(availableSeedGroup.localizedInterval())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) { Task { tournament.setSeeds(inRoundIndex: upperRound.round.index, inSeedGroup: availableSeedGroup) _save() @@ -137,7 +137,7 @@ struct RoundView: View { if (availableSeedGroup.isFixed() == false) { Section { - RowButtonView("Tirage au sort \(availableSeedGroup.localizedLabel()) visuel") { + RowButtonView("Tirage au sort \(availableSeedGroup.localizedInterval()) visuel") { self.selectedSeedGroup = availableSeedGroup } } footer: { From 0860fb5bcace0cf042f25f26caf14cf3d9bac648 Mon Sep 17 00:00:00 2001 From: Raz Date: Fri, 6 Sep 2024 11:34:42 +0200 Subject: [PATCH 04/23] v1.0.6 --- PadelClub/Data/TeamRegistration.swift | 8 +++ PadelClub/Data/Tournament.swift | 2 +- .../Views/Calling/TeamsCallingView.swift | 49 ++++++++++++++++++- .../Views/Navigation/Umpire/UmpireView.swift | 2 +- .../Views/Shared/ImportedPlayerView.swift | 5 +- .../Shared/SelectablePlayerListView.swift | 12 ++--- PadelClub/Views/Team/EditingTeamView.swift | 16 ++++++ .../Tournament/Screen/BroadcastView.swift | 8 ++- 8 files changed, 90 insertions(+), 12 deletions(-) diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index c5db5d1..1c90fef 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -498,6 +498,14 @@ final class TeamRegistration: ModelObject, Storable { return self.tournamentStore.matches.first(where: { $0.round == initialRoundObject.id && $0.index == bracketPosition / 2 }) } + func toggleSummonConfirmation() { + if confirmationDate == nil { confirmationDate = Date() } + else { confirmationDate = nil } + } + + func didConfirmSummon() -> Bool { + confirmationDate != nil + } func tournamentObject() -> Tournament? { return Store.main.findById(tournament) diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 3fe8d03..7ad53ff 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -113,7 +113,7 @@ final class Tournament : ModelObject, Storable { self.startDate = startDate self.endDate = endDate self.creationDate = creationDate - self.isPrivate = isPrivate + self.isPrivate = Guard.main.purchasedTransactions.isEmpty self.groupStageFormat = groupStageFormat self.roundFormat = roundFormat self.loserRoundFormat = loserRoundFormat diff --git a/PadelClub/Views/Calling/TeamsCallingView.swift b/PadelClub/Views/Calling/TeamsCallingView.swift index 656351a..4575acb 100644 --- a/PadelClub/Views/Calling/TeamsCallingView.swift +++ b/PadelClub/Views/Calling/TeamsCallingView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import LeStorage struct TeamsCallingView: View { @Environment(Tournament.self) var tournament: Tournament @@ -15,7 +16,23 @@ struct TeamsCallingView: View { let teams = tournament.selectedSortedTeams() Section { ForEach(teams) { team in - TeamRowView(team: team, displayCallDate: true) + Menu { + _menuOptions(team: team) + } label: { + HStack { + TeamRowView(team: team, displayCallDate: true) + if team.called() { + Spacer() + Menu { + _menuOptions(team: team) + } label: { + LabelOptions().labelStyle(.iconOnly) + } + } + } + } + .buttonStyle(.plain) + .listRowView(isActive: team.didConfirmSummon(), color: .green, hideColorVariation: true) } } } @@ -25,4 +42,34 @@ struct TeamsCallingView: View { .toolbarBackground(.visible, for: .navigationBar) } + @ViewBuilder + func _menuOptions(team: TeamRegistration) -> some View { + Button { + team.toggleSummonConfirmation() + do { + try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) + } + } label: { + if team.didConfirmSummon() { + Label("Confirmation reçue", systemImage: "checkmark.circle.fill").foregroundStyle(.green) + } else { + Label("Confirmation reçue", systemImage: "circle").foregroundStyle(.logoRed) + } + } + Divider() + + Button(role: .destructive) { + team.callDate = nil + do { + try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) + } + } label: { + Text("Effacer la date de convocation") + } + + } } diff --git a/PadelClub/Views/Navigation/Umpire/UmpireView.swift b/PadelClub/Views/Navigation/Umpire/UmpireView.swift index 945d2d7..ac79547 100644 --- a/PadelClub/Views/Navigation/Umpire/UmpireView.swift +++ b/PadelClub/Views/Navigation/Umpire/UmpireView.swift @@ -56,7 +56,7 @@ struct UmpireView: View { Section { if let currentPlayerData { //todo palmares - ImportedPlayerView(player: currentPlayerData) + ImportedPlayerView(player: currentPlayerData, showProgression: true) // NavigationLink { // // } label: { diff --git a/PadelClub/Views/Shared/ImportedPlayerView.swift b/PadelClub/Views/Shared/ImportedPlayerView.swift index 70b41e6..fd99bf8 100644 --- a/PadelClub/Views/Shared/ImportedPlayerView.swift +++ b/PadelClub/Views/Shared/ImportedPlayerView.swift @@ -11,7 +11,7 @@ struct ImportedPlayerView: View { let player: PlayerHolder var index: Int? = nil var showFemaleInMaleAssimilation: Bool = false - @State var showProgression: Bool = false + var showProgression: Bool = false var body: some View { VStack(alignment: .leading) { @@ -54,7 +54,7 @@ struct ImportedPlayerView: View { } } - if player.getProgression() != 0 { + if showProgression, player.getProgression() != 0 { HStack(alignment: .top, spacing: 2) { Text("(") Text(player.getProgression().formatted(.number.sign(strategy: .always()))) @@ -77,6 +77,7 @@ struct ImportedPlayerView: View { } } } + .lineLimit(1) if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() { HStack(alignment: .top, spacing: 2) { diff --git a/PadelClub/Views/Shared/SelectablePlayerListView.swift b/PadelClub/Views/Shared/SelectablePlayerListView.swift index b0f195d..b5d9a2d 100644 --- a/PadelClub/Views/Shared/SelectablePlayerListView.swift +++ b/PadelClub/Views/Shared/SelectablePlayerListView.swift @@ -364,7 +364,7 @@ struct MySearchView: View { let array = Array(searchViewModel.selectedPlayers) Section { ForEach(array) { player in - ImportedPlayerView(player: player, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation) + ImportedPlayerView(player: player, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation, showProgression: true) } .onDelete { indexSet in for index in indexSet { @@ -379,7 +379,7 @@ struct MySearchView: View { } else { Section { ForEach(players, id: \.self) { player in - ImportedPlayerView(player: player, index: nil, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation) + ImportedPlayerView(player: player, index: nil, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation, showProgression: true) } } header: { if players.isEmpty == false { @@ -398,7 +398,7 @@ struct MySearchView: View { Button { searchViewModel.selectedPlayers.insert(player) } label: { - ImportedPlayerView(player: player, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation) + ImportedPlayerView(player: player, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation, showProgression: true) } .buttonStyle(.plain) } @@ -412,7 +412,7 @@ struct MySearchView: View { Section { ForEach(players.indices, id: \.self) { index in let player = players[index] - ImportedPlayerView(player: player, index: searchViewModel.showIndex() ? (index + 1) : nil, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation) + ImportedPlayerView(player: player, index: searchViewModel.showIndex() ? (index + 1) : nil, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation, showProgression: true) } } header: { if players.isEmpty == false { @@ -429,13 +429,13 @@ struct MySearchView: View { Button { searchViewModel.selectedPlayers.insert(player) } label: { - ImportedPlayerView(player: player, index: searchViewModel.showIndex() ? (index + 1) : nil, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation) + ImportedPlayerView(player: player, index: searchViewModel.showIndex() ? (index + 1) : nil, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation, showProgression: true) .contentShape(Rectangle()) } .frame(maxWidth: .infinity) .buttonStyle(.plain) } else { - ImportedPlayerView(player: player, index: searchViewModel.showIndex() ? (index + 1) : nil, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation) + ImportedPlayerView(player: player, index: searchViewModel.showIndex() ? (index + 1) : nil, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation, showProgression: true) } } } header: { diff --git a/PadelClub/Views/Team/EditingTeamView.swift b/PadelClub/Views/Team/EditingTeamView.swift index 492822e..d3ac838 100644 --- a/PadelClub/Views/Team/EditingTeamView.swift +++ b/PadelClub/Views/Team/EditingTeamView.swift @@ -76,6 +76,22 @@ struct EditingTeamView: View { } label: { Text("Convocation") } + + Button { + team.toggleSummonConfirmation() + do { + try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) + } + } label: { + if team.didConfirmSummon() { + Label("Confirmation reçue", systemImage: "checkmark.circle.fill").foregroundStyle(.green) + } else { + Label("Confirmation reçue", systemImage: "circle").foregroundStyle(.logoRed) + } + } + } else { Text("Cette équipe n'a pas été convoquée") } diff --git a/PadelClub/Views/Tournament/Screen/BroadcastView.swift b/PadelClub/Views/Tournament/Screen/BroadcastView.swift index 54648cc..573f093 100644 --- a/PadelClub/Views/Tournament/Screen/BroadcastView.swift +++ b/PadelClub/Views/Tournament/Screen/BroadcastView.swift @@ -108,9 +108,15 @@ 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((tournament.isPrivate && Guard.main.purchasedTransactions.isEmpty)) } footer: { - let footerString = "Le tournoi sera masqué sur le site [Padel Club](\(URLs.main.rawValue))" + let verb : String = tournament.isPrivate ? "est" : "sera" + let footerString = " Le tournoi \(verb) masqué sur le site [Padel Club](\(URLs.main.rawValue))" Text(.init(footerString)) } From 143a17f5b4997b196935bda633dc12ed5f0f25b8 Mon Sep 17 00:00:00 2001 From: Raz Date: Fri, 6 Sep 2024 11:41:19 +0200 Subject: [PATCH 05/23] fix debug --- PadelClub/Views/Tournament/Screen/BroadcastView.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/PadelClub/Views/Tournament/Screen/BroadcastView.swift b/PadelClub/Views/Tournament/Screen/BroadcastView.swift index 573f093..836197d 100644 --- a/PadelClub/Views/Tournament/Screen/BroadcastView.swift +++ b/PadelClub/Views/Tournament/Screen/BroadcastView.swift @@ -113,7 +113,10 @@ struct BroadcastView: View { .foregroundStyle(.logoRed) } } + #if DEBUG + #else .disabled((tournament.isPrivate && Guard.main.purchasedTransactions.isEmpty)) + #endif } footer: { let verb : String = tournament.isPrivate ? "est" : "sera" let footerString = " Le tournoi \(verb) masqué sur le site [Padel Club](\(URLs.main.rawValue))" From a442cd934d7fba0a451f049179ce787b4b86cd0e Mon Sep 17 00:00:00 2001 From: Raz Date: Fri, 6 Sep 2024 12:25:53 +0200 Subject: [PATCH 06/23] phrasing --- PadelClub/Views/Team/EditingTeamView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PadelClub/Views/Team/EditingTeamView.swift b/PadelClub/Views/Team/EditingTeamView.swift index e8b2450..5a03ef6 100644 --- a/PadelClub/Views/Team/EditingTeamView.swift +++ b/PadelClub/Views/Team/EditingTeamView.swift @@ -79,7 +79,7 @@ struct EditingTeamView: View { Toggle(isOn: confirmationReceived) { Text("Confirmation reçue") - Text("L'équipe vous a confirmé leur convocation") + Text("L'équipe vous a confirmé votre convocation") } } else { From 9a2d293b41e87fbbdd649fa3a82e9833378f1ae2 Mon Sep 17 00:00:00 2001 From: Raz Date: Sun, 8 Sep 2024 10:16:07 +0200 Subject: [PATCH 07/23] wip --- PadelClub.xcodeproj/project.pbxproj | 8 +- PadelClub/Data/GroupStage.swift | 5 + PadelClub/Data/Match.swift | 45 +++++-- PadelClub/Data/Round.swift | 47 +++++-- PadelClub/Data/Tournament.swift | 36 ++++-- PadelClub/Extensions/Array+Extensions.swift | 29 +++++ PadelClub/Utils/PadelRule.swift | 7 + PadelClub/ViewModel/SeedInterval.swift | 4 +- .../Views/GroupStage/GroupStageView.swift | 2 +- .../GroupStage/GroupStagesSettingsView.swift | 50 ++++++- .../Views/GroupStage/GroupStagesView.swift | 19 ++- .../LoserBracketFromGroupStageView.swift | 122 ++++++++++++++++++ .../LoserGroupStageSettingsView.swift | 85 ------------ .../Match/Components/PlayerBlockView.swift | 4 +- PadelClub/Views/Match/MatchSetupView.swift | 61 +++++---- .../Views/Round/LoserRoundSettingsView.swift | 27 +++- PadelClub/Views/Round/LoserRoundView.swift | 8 +- PadelClub/Views/Round/LoserRoundsView.swift | 4 + PadelClub/Views/Round/RoundSettingsView.swift | 14 +- PadelClub/Views/Team/TeamPickerView.swift | 68 +++++----- PadelClub/Views/Team/TeamRowView.swift | 11 ++ .../Screen/TournamentRankView.swift | 39 ++++-- 22 files changed, 479 insertions(+), 216 deletions(-) create mode 100644 PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift delete mode 100644 PadelClub/Views/GroupStage/LoserGroupStageSettingsView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 24c98c6..eea28e6 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -79,7 +79,6 @@ FF1162872BD004AD000C4809 /* EditingTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162862BD004AD000C4809 /* EditingTeamView.swift */; }; FF11628A2BD05247000C4809 /* DateUpdateManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162892BD05247000C4809 /* DateUpdateManagerView.swift */; }; FF11628C2BD05267000C4809 /* LoserRoundStepScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF11628B2BD05267000C4809 /* LoserRoundStepScheduleEditorView.swift */; }; - FF135BF92C2FCB8300C9247A /* LoserGroupStageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF135BF82C2FCB8300C9247A /* LoserGroupStageSettingsView.swift */; }; FF1CBC1B2BB53D1F0036DAAB /* FederalTournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC182BB53D1F0036DAAB /* FederalTournament.swift */; }; FF1CBC1D2BB53DC10036DAAB /* Calendar+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC1C2BB53DC10036DAAB /* Calendar+Extensions.swift */; }; FF1CBC1F2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */; }; @@ -147,6 +146,7 @@ FF5DA19B2BB9662200A33061 /* TournamentSeedEditing.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */; }; FF6087EA2BE25EF1004E1E47 /* TournamentStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */; }; FF6087EC2BE26A2F004E1E47 /* BroadcastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087EB2BE26A2F004E1E47 /* BroadcastView.swift */; }; + FF6525C32C8C61B400B9498E /* LoserBracketFromGroupStageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6525C22C8C61B400B9498E /* LoserBracketFromGroupStageView.swift */; }; FF663FBE2BE019EC0031AE83 /* TournamentFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */; }; FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; }; FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */; }; @@ -425,7 +425,6 @@ FF1162862BD004AD000C4809 /* EditingTeamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditingTeamView.swift; sourceTree = ""; }; FF1162892BD05247000C4809 /* DateUpdateManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateUpdateManagerView.swift; sourceTree = ""; }; FF11628B2BD05267000C4809 /* LoserRoundStepScheduleEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoserRoundStepScheduleEditorView.swift; sourceTree = ""; }; - FF135BF82C2FCB8300C9247A /* LoserGroupStageSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoserGroupStageSettingsView.swift; sourceTree = ""; }; FF1CBC182BB53D1F0036DAAB /* FederalTournament.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalTournament.swift; sourceTree = ""; }; FF1CBC1C2BB53DC10036DAAB /* Calendar+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Calendar+Extensions.swift"; sourceTree = ""; }; FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalTournamentSearchScope.swift; sourceTree = ""; }; @@ -493,6 +492,7 @@ FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentSeedEditing.swift; sourceTree = ""; }; FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentStatusView.swift; sourceTree = ""; }; FF6087EB2BE26A2F004E1E47 /* BroadcastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BroadcastView.swift; sourceTree = ""; }; + FF6525C22C8C61B400B9498E /* LoserBracketFromGroupStageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoserBracketFromGroupStageView.swift; sourceTree = ""; }; FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentFilterView.swift; sourceTree = ""; }; FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowButtonView.swift; sourceTree = ""; }; FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentButtonView.swift; sourceTree = ""; }; @@ -1196,7 +1196,7 @@ FF967CFA2BAEE13800A9A3BD /* GroupStageView.swift */, FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */, FF5DA18E2BB9268800A33061 /* GroupStagesSettingsView.swift */, - FF135BF82C2FCB8300C9247A /* LoserGroupStageSettingsView.swift */, + FF6525C22C8C61B400B9498E /* LoserBracketFromGroupStageView.swift */, FF9AC3932BE3625D00C2E883 /* Components */, FF9AC3922BE3625200C2E883 /* Shared */, ); @@ -1594,7 +1594,6 @@ FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */, FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */, FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */, - FF135BF92C2FCB8300C9247A /* LoserGroupStageSettingsView.swift in Sources */, FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */, C4C01D982C481C0C0059087C /* CapsuleViewModifier.swift in Sources */, FF6087EC2BE26A2F004E1E47 /* BroadcastView.swift in Sources */, @@ -1689,6 +1688,7 @@ FFF8ACCD2B92367B008466FA /* FederalPlayer.swift in Sources */, FFBF06602BBD9F6D009D6715 /* NavigationViewModel.swift in Sources */, FF6EC9092B947A5300EA7F5A /* FixedWidthInteger+Extensions.swift in Sources */, + FF6525C32C8C61B400B9498E /* LoserBracketFromGroupStageView.swift in Sources */, FF5D30512BD94E1000F2B93D /* ImportedPlayer+Extensions.swift in Sources */, FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */, FF089EBB2BB0120700F0AEC7 /* PlayerPopoverView.swift in Sources */, diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index ac2d9c7..32e07b2 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -383,6 +383,11 @@ final class GroupStage: ModelObject, Storable { return data.joined(separator: "\n") } + + func finalPosition(ofTeam team: TeamRegistration) -> Int? { + guard hasEnded() else { return nil } + return teams(true).firstIndex(of: team) + } override func deleteDependencies() throws { let matches = self._matches() diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index 148abb1..1e8c918 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -121,6 +121,10 @@ defer { print("func matchTitle", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) } #endif + + if roundObject?.groupStageLoserBracket == true { + return "\(index)\(index.ordinalFormattedSuffix()) place" + } if let groupStageObject { return groupStageObject.localizedMatchUpLabel(for: index) } @@ -361,9 +365,17 @@ defer { return false } - func _toggleMatchDisableState(_ state: Bool, forward: Bool = false) { + func _toggleMatchDisableState(_ state: Bool, forward: Bool = false, single: Bool = false) { //if disabled == state { return } disabled = state + + if disabled { + do { + try self.tournamentStore.teamScores.delete(contentOfs: teamScores) + } catch { + Logger.error(error) + } + } if state == true { let teams = teams() for team in teams { @@ -384,12 +396,14 @@ defer { Logger.error(error) } - _toggleLoserMatchDisableState(state) - if forward { - _toggleForwardMatchDisableState(state) - } else { - topPreviousRoundMatch()?._toggleMatchDisableState(state) - bottomPreviousRoundMatch()?._toggleMatchDisableState(state) + if single == false { + _toggleLoserMatchDisableState(state) + if forward { + _toggleForwardMatchDisableState(state) + } else { + topPreviousRoundMatch()?._toggleMatchDisableState(state) + bottomPreviousRoundMatch()?._toggleMatchDisableState(state) + } } } @@ -837,7 +851,22 @@ defer { } var isLoserBracket: Bool { - return roundObject?.parent != nil + if let roundObject { + if roundObject.parent != nil || roundObject.groupStageLoserBracket { + return true + } + } + return false + } + + var matchType: MatchType { + if isLoserBracket { + return .loserBracket + } else if isGroupStage() { + return .groupStage + } else { + return .bracket + } } enum CodingKeys: String, CodingKey { diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index 27eee27..0cc15be 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -22,13 +22,15 @@ final class Round: ModelObject, Storable { var parent: String? private(set) var format: MatchFormat? var startDate: Date? + var groupStageLoserBracket: Bool = false - internal init(tournament: String, index: Int, parent: String? = nil, matchFormat: MatchFormat? = nil, startDate: Date? = nil) { + internal init(tournament: String, index: Int, parent: String? = nil, matchFormat: MatchFormat? = nil, startDate: Date? = nil, groupStageLoserBracket: Bool = false) { self.tournament = tournament self.index = index self.parent = parent self.format = matchFormat self.startDate = startDate + self.groupStageLoserBracket = groupStageLoserBracket } // MARK: - Computed dependencies @@ -67,7 +69,7 @@ final class Round: ModelObject, Storable { } func hasEnded() -> Bool { - if parent == nil { + if isUpperBracket() { return playedMatches().anySatisfy({ $0.hasEnded() == false }) == false } else { return enabledMatches().anySatisfy({ $0.hasEnded() == false }) == false @@ -184,13 +186,13 @@ defer { case .two: if let luckyLoser = match.teamScores.first(where: { $0.luckyLoser == match.index * 2 + 1 }) { return luckyLoser.team - } else if let previousMatch = bottomPreviousRoundMatch(ofMatch: match, previousRound: previousRound) { + } else if groupStageLoserBracket == false, let previousMatch = bottomPreviousRoundMatch(ofMatch: match, previousRound: previousRound) { if let teamId = previousMatch.winningTeamId { return self.tournamentStore.teamRegistrations.findById(teamId) } else if previousMatch.disabled { return previousMatch.teams().first } - } else if let parent = upperBracketBottomMatch(ofMatchIndex: match.index, previousRound: previousRound)?.losingTeamId { + } else if groupStageLoserBracket == false, let parent = upperBracketBottomMatch(ofMatchIndex: match.index, previousRound: previousRound)?.losingTeamId { return tournamentStore.findById(parent) } } @@ -292,7 +294,7 @@ defer { // } func playedMatches() -> [Match] { - if parent == nil { + if isUpperBracket() { return enabledMatches() } else { return _matches() @@ -477,15 +479,19 @@ defer { } #endif - if parent == nil { + if isUpperBracket() { if index == 0 { return SeedInterval(first: 1, last: 2) } let initialMatchIndexFromRoundIndex = RoundRule.matchIndex(fromRoundIndex: index) let seedsAfterThisRound : [TeamRegistration] = self.tournamentStore.teamRegistrations.filter { $0.bracketPosition != nil && ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex } - let playedMatches = playedMatches() - let seedInterval = SeedInterval(first: playedMatches.count + seedsAfterThisRound.count + 1, last: playedMatches.count * 2 + seedsAfterThisRound.count) + + let playedMatches = playedMatches().count + let minimumMatches = initialMode ? RoundRule.numberOfMatches(forRoundIndex: index) : playedMatches * 2 + //print("playedMatches \(playedMatches)", initialMode, parent, parentRound?.roundTitle(), seedsAfterThisRound.count) + let seedInterval = SeedInterval(first: playedMatches + seedsAfterThisRound.count + 1, last: minimumMatches + seedsAfterThisRound.count) + //print(seedInterval.localizedLabel()) return seedInterval } @@ -496,7 +502,7 @@ defer { return previousRound.seedInterval(initialMode: initialMode) } } else if let parentRound { - if parentRound.parent == nil { + if parentRound.isUpperBracket() { return parentRound.seedInterval(initialMode: initialMode) } return parentRound.seedInterval(initialMode: initialMode)?.chunks()?.last @@ -506,6 +512,10 @@ defer { } func roundTitle(_ displayStyle: DisplayStyle = .wide, initialMode: Bool = false) -> String { + if groupStageLoserBracket { + return "Classement Poules" + } + if parent != nil { if let seedInterval = seedInterval(initialMode: initialMode) { return seedInterval.localizedLabel(displayStyle) @@ -557,11 +567,11 @@ defer { } func isUpperBracket() -> Bool { - return parent == nil + return parent == nil && groupStageLoserBracket == false } func isLoserBracket() -> Bool { - return parent != nil + return parent != nil || groupStageLoserBracket } func deleteLoserBracket() { @@ -670,6 +680,18 @@ defer { case _parent = "parent" case _format = "format" case _startDate = "startDate" + case _groupStageLoserBracket = "groupStageLoserBracket" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decode(String.self, forKey: ._id) + tournament = try container.decode(String.self, forKey: ._tournament) + index = try container.decode(Int.self, forKey: ._index) + parent = try container.decodeIfPresent(String.self, forKey: ._parent) + format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format) + startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate) + groupStageLoserBracket = try container.decodeIfPresent(Bool.self, forKey: ._groupStageLoserBracket) ?? false } func encode(to encoder: Encoder) throws { @@ -678,7 +700,8 @@ defer { try container.encode(id, forKey: ._id) try container.encode(tournament, forKey: ._tournament) try container.encode(index, forKey: ._index) - + try container.encode(groupStageLoserBracket, forKey: ._groupStageLoserBracket) + if let parent = parent { try container.encode(parent, forKey: ._parent) } else { diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 7ad53ff..9d9fa18 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -809,7 +809,7 @@ defer { } func rounds() -> [Round] { - let rounds: [Round] = self.tournamentStore.rounds.filter { $0.parent == nil } + let rounds: [Round] = self.tournamentStore.rounds.filter { $0.isUpperBracket() } return rounds.sorted(by: \.index).reversed() } @@ -1224,7 +1224,7 @@ defer { _removeStrings(from: &teams, stringsToRemove: disabledIds) teams[interval.last] = disabledIds let teamNames : [String] = disabledIds.compactMap { - let t : TeamRegistration? = Store.main.findById($0) + let t : TeamRegistration? = tournamentStore.teamRegistrations.findById($0) return t }.map { $0.canonicalName } print("winners.isEmpty", "\(interval.last) : ", teamNames) @@ -1237,7 +1237,7 @@ defer { _removeStrings(from: &teams, stringsToRemove: winners) teams[interval.first + winners.count - 1] = winners let teamNames : [String] = winners.compactMap { - let t: TeamRegistration? = Store.main.findById($0) + let t: TeamRegistration? = tournamentStore.teamRegistrations.findById($0) return t }.map { $0.canonicalName } print("winners", "\(interval.last + winners.count - 1) : ", teamNames) @@ -1246,25 +1246,32 @@ defer { if losers.isEmpty == false { _removeStrings(from: &teams, stringsToRemove: losers) - teams[interval.last] = losers + teams[interval.first + winners.count] = losers let loserTeamNames : [String] = losers.compactMap { - let t: TeamRegistration? = Store.main.findById($0) + let t: TeamRegistration? = tournamentStore.teamRegistrations.findById($0) return t }.map { $0.canonicalName } - print("losers", "\(interval.last) : ", loserTeamNames) + print("losers", "\(interval.first + winners.count) : ", loserTeamNames) losers.forEach { ids.insert($0) } } } } } + groupStageLoserBracket()?.playedMatches().forEach({ match in + if match.hasEnded() { + teams.setOrAppend(match.winningTeamId, at: match.index) + teams.setOrAppend(match.losingTeamId, at: match.index + 1) + } + }) + let groupStages = groupStages() let baseRank = teamCount - groupStageSpots() + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified - + let alreadyPlaceTeams = Array(teams.values.flatMap({ $0 })) groupStages.forEach { groupStage in let groupStageTeams = groupStage.teams(true) for (index, team) in groupStageTeams.enumerated() { - if team.qualified == false { + if team.qualified == false && alreadyPlaceTeams.contains(team.id) == false { let groupStageWidth = max(((index == qualifiedPerGroupStage) ? groupStageCount - groupStageAdditionalQualified : groupStageCount) * (index - qualifiedPerGroupStage), 0) let _index = baseRank + groupStageWidth + 1 @@ -1934,10 +1941,7 @@ defer { } func tournamentWinner() -> TeamRegistration? { - let finals: Round? = self.tournamentStore.rounds.first(where: { $0.index == 0 && $0.parent == nil }) - -// let rounds: [Round] = Store.main.filter(isIncluded: { $0.index == 0 && $0.tournament == id && $0.parent == nil }) -// let final: Round? = .first + let finals: Round? = self.tournamentStore.rounds.first(where: { $0.index == 0 && $0.isUpperBracket() }) return finals?.playedMatches().first?.winner() } @@ -1998,6 +2002,14 @@ defer { return (Array(shouldBeInIt), Array(shouldNotBeInIt)) } + func groupStageLoserBracket() -> Round? { + tournamentStore.rounds.first(where: { $0.groupStageLoserBracket }) + } + + func groupStageLoserBracketsInitialPlace() -> Int { + selectedSortedTeams().filter({ $0.qualified || $0.bracketPosition != nil }).count + 1 + } + // MARK: - func insertOnServer() throws { diff --git a/PadelClub/Extensions/Array+Extensions.swift b/PadelClub/Extensions/Array+Extensions.swift index 62b8d4b..f1ab768 100644 --- a/PadelClub/Extensions/Array+Extensions.swift +++ b/PadelClub/Extensions/Array+Extensions.swift @@ -18,6 +18,16 @@ extension Array { return first(where: { p($0) }) != nil //return !self.allSatisfy { !p($0) } } + + // Check if the number of elements in the sequence is even + var isEven: Bool { + return self.count % 2 == 0 + } + + // Check if the number of elements in the sequence is odd + var isOdd: Bool { + return self.count % 2 != 0 + } } extension Array where Element: Equatable { @@ -49,3 +59,22 @@ extension Array where Element: CustomStringConvertible { } } + +extension Dictionary where Key == Int, Value == [String] { + mutating func setOrAppend(_ element: String?, at key: Int) { + // Check if the element is nil; do nothing if it is + guard let element = element else { + return + } + + // Check if the key exists in the dictionary + if var array = self[key] { + // If it exists, append the element to the array + array.append(element) + self[key] = array + } else { + // If it doesn't exist, create a new array with the element + self[key] = [element] + } + } +} diff --git a/PadelClub/Utils/PadelRule.swift b/PadelClub/Utils/PadelRule.swift index 8c65472..9672531 100644 --- a/PadelClub/Utils/PadelRule.swift +++ b/PadelClub/Utils/PadelRule.swift @@ -291,6 +291,13 @@ enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable { self.init(rawValue: value) } + + func pointsRange(first: Int, last: Int, teamsCount: Int) -> String { + let range = [points(for: last - 1, count: teamsCount), + points(for: first - 1, count: teamsCount)] + return range.map { $0.formatted(.number.sign(strategy: .always())) }.joined(separator: " / ") + " pts" + } + func hideWeight() -> Bool { switch self { case .unlisted: diff --git a/PadelClub/ViewModel/SeedInterval.swift b/PadelClub/ViewModel/SeedInterval.swift index 01e7519..32ab726 100644 --- a/PadelClub/ViewModel/SeedInterval.swift +++ b/PadelClub/ViewModel/SeedInterval.swift @@ -12,9 +12,7 @@ struct SeedInterval: Hashable, Comparable { let last: Int func pointsRange(tournamentLevel: TournamentLevel, teamsCount: Int) -> String { - let range = [tournamentLevel.points(for: last - 1, count: teamsCount), - tournamentLevel.points(for: first - 1, count: teamsCount)] - return range.map { $0.formatted(.number.sign(strategy: .always())) }.joined(separator: " / ") + " pts" + tournamentLevel.pointsRange(first: first, last: last, teamsCount: teamsCount) } static func <(lhs: SeedInterval, rhs: SeedInterval) -> Bool { diff --git a/PadelClub/Views/GroupStage/GroupStageView.swift b/PadelClub/Views/GroupStage/GroupStageView.swift index c82bdab..62c58c6 100644 --- a/PadelClub/Views/GroupStage/GroupStageView.swift +++ b/PadelClub/Views/GroupStage/GroupStageView.swift @@ -202,7 +202,7 @@ struct GroupStageView: View { VStack(alignment: .leading, spacing: 0) { Text("#\(index + 1)") .font(.caption) - TeamPickerView(groupStagePosition: index, teamPicked: { team in + TeamPickerView(groupStagePosition: index, matchTypeContext: .groupStage, teamPicked: { team in print(team.pasteData()) team.groupStage = groupStage.id team.groupStagePosition = index diff --git a/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift b/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift index 8b3770c..e0f29dc 100644 --- a/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift @@ -72,6 +72,30 @@ struct GroupStagesSettingsView: View { Text("Suite à un changement dans votre liste d'inscrits, veuillez vérifier l'intégrité de vos poules et valider que tout est ok.") } } + + Section { + if tournament.groupStageLoserBracket() == nil { + RowButtonView("Ajouter des matchs de classements", role: .destructive) { + let round = Round(tournament: tournament.id, index: 0, matchFormat: tournament.loserRoundFormat, groupStageLoserBracket: true) + + do { + try tournamentStore.rounds.addOrUpdate(instance: round) + } catch { + Logger.error(error) + } + + } + } else if let groupStageLoserBracket = tournament.groupStageLoserBracket() { + RowButtonView("Supprimer les matchs de classements", role: .destructive) { + do { + try groupStageLoserBracket.deleteDependencies() + try tournamentStore.rounds.delete(instance: groupStageLoserBracket) + } catch { + Logger.error(error) + } + } + } + } #if DEBUG Section { @@ -81,12 +105,6 @@ struct GroupStagesSettingsView: View { } #endif -// NavigationLink { -// LoserGroupStageSettingsView(tournament: tournament) -// } label: { -// Text("Match de perdant de poules") -// } - Section { RowButtonView("Retirer tous les horaires", role: .destructive) { let matches = tournament.groupStages().flatMap({ $0._matches() }) @@ -155,6 +173,26 @@ struct GroupStagesSettingsView: View { } } } + + Section { + RowButtonView("Retirer tout le monde", role: .destructive) { + tournament.groupStages().forEach { groupStage in + let teams = groupStage.teams() + teams.forEach { team in + team.groupStagePosition = nil + team.groupStage = nil + groupStage._matches().forEach({ $0.updateTeamScores() }) + } + do { + try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) + } catch { + Logger.error(error) + } + } + } + } footer: { + Text("Toutes les équipes seront retirées et les scores des matchs seront perdus.") + } } .overlay(alignment: .bottom) { diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index 2260f7d..bfc447e 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -18,12 +18,15 @@ struct GroupStagesView: View { } case all(Tournament) + case loserBracket(Round) case groupStage(GroupStage) var id: String { switch self { case .all: return "all-group-stage" + case .loserBracket(let loserBracket): + return loserBracket.id case .groupStage(let groupStage): return groupStage.id } @@ -33,6 +36,8 @@ struct GroupStagesView: View { switch self { case .all: return "Tout" + case .loserBracket: + return "Classement" case .groupStage(let groupStage): return groupStage.groupStageTitle() } @@ -42,6 +47,8 @@ struct GroupStagesView: View { switch self { case .all: return nil + case .loserBracket(let loserBracket): + return loserBracket.badgeValue() case .groupStage(let groupStage): return groupStage.badgeValue() } @@ -59,6 +66,8 @@ struct GroupStagesView: View { } else { return nil } + case .loserBracket(let loserBracket): + return loserBracket.badgeImage() case .groupStage(let groupStage): return groupStage.badgeImage() } @@ -69,9 +78,10 @@ struct GroupStagesView: View { tournament.groupStagesMatches() } + @State private var isEditingLoserBracketGroupStage: Bool + init(tournament: Tournament) { self.tournament = tournament - if tournament.shouldVerifyGroupStage { _selectedDestination = State(wrappedValue: nil) } else if tournament.unsortedTeams().filter({ $0.groupStagePosition != nil }).isEmpty { @@ -82,11 +92,15 @@ struct GroupStagesView: View { _selectedDestination = State(wrappedValue: .groupStage(gs)) } } + _isEditingLoserBracketGroupStage = .init(wrappedValue: tournament.groupStageLoserBracket()?._matches().isEmpty ?? false) } func allDestinations() -> [GroupStageDestination] { var allDestinations : [GroupStageDestination] = [.all(tournament)] let groupStageDestinations : [GroupStageDestination] = tournament.groupStages().map { GroupStageDestination.groupStage($0) } + if let loserBracket = tournament.groupStageLoserBracket() { + allDestinations.insert(.loserBracket(loserBracket), at: 0) + } allDestinations.append(contentsOf: groupStageDestinations) return allDestinations } @@ -142,6 +156,9 @@ struct GroupStagesView: View { .navigationTitle("Toutes les poules") case .groupStage(let groupStage): GroupStageView(groupStage: groupStage).id(groupStage.id) + case .loserBracket(let loserBracket): + LoserBracketFromGroupStageView(loserBracket: loserBracket).id(loserBracket.id) + .environment(\.isEditingTournamentSeed, $isEditingLoserBracketGroupStage) case nil: GroupStagesSettingsView() .navigationTitle("Réglages") diff --git a/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift b/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift new file mode 100644 index 0000000..d3a4e09 --- /dev/null +++ b/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift @@ -0,0 +1,122 @@ +// +// LoserBracketFromGroupStageView.swift +// PadelClub +// +// Created by razmig on 07/09/2024. +// + +import SwiftUI +import LeStorage + +struct LoserBracketFromGroupStageView: View { + + @Environment(\.isEditingTournamentSeed) var isEditingTournamentSeed + @Environment(Tournament.self) var tournament: Tournament + @EnvironmentObject var dataStore: DataStore + @Environment(NavigationViewModel.self) private var navigation: NavigationViewModel + + @State var loserBracket: Round + + var tournamentStore: TournamentStore { + return self.tournament.tournamentStore + } + + var body: some View { + List { + let displayableMatches = loserBracket.playedMatches().sorted(by: \.index) + + if isEditingTournamentSeed.wrappedValue == true { + Section { + RowButtonView("Ajouter un match", role: .destructive) { + let placeCount = tournament.groupStageLoserBracketsInitialPlace() + displayableMatches.count * 2 + let match = Match(round: loserBracket.id, index: placeCount, matchFormat: loserBracket.matchFormat) + match.name = "\(placeCount)\(placeCount.ordinalFormattedSuffix()) place" + do { + try tournamentStore.matches.addOrUpdate(instance: match) + } catch { + Logger.error(error) + } + } + } + } + + ForEach(displayableMatches) { match in + Section { + MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle) + } header: { + let tournamentTeamCount = tournament.teamCount + let seedIntervalPointRange = tournament.tournamentLevel.pointsRange(first: match.index, last: match.index + 1, teamsCount: tournamentTeamCount) + HStack { + Text(match.matchTitle(.wide)) + Spacer() + Text(seedIntervalPointRange) + .font(.caption) + } + } footer: { + if isEditingTournamentSeed.wrappedValue == true { + HStack { + if match.index > tournament.groupStageLoserBracketsInitialPlace() { + FooterButtonView("même place qu'au-dessus") { + match.index -= 2 + do { + try tournamentStore.matches.addOrUpdate(instance: match) + } catch { + Logger.error(error) + } + } + Spacer() + } + FooterButtonView("effacer", role: .destructive) { + do { + try match.deleteDependencies() + try tournamentStore.matches.delete(instance: match) + } catch { + Logger.error(error) + } + } + } + } + } + } + + Section { + if displayableMatches.isEmpty == false && isEditingTournamentSeed.wrappedValue == true { + Section { + RowButtonView("Effacer tous les matchs", role: .destructive) { + _deleteAllMatches() + } + } footer: { + Text("Efface tous les matchs de classement de poules ci-dessus.") + } + } + } + } + .headerProminence(.increased) + .navigationTitle("Classement de poules") + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button(isEditingTournamentSeed.wrappedValue == true ? "Valider" : "Modifier") { + if isEditingTournamentSeed.wrappedValue == true { + isEditingTournamentSeed.wrappedValue = false + } else { + isEditingTournamentSeed.wrappedValue = true + } + } + } + } + } + + private func _deleteAllMatches() { + let displayableMatches = loserBracket.playedMatches().sorted(by: \.index) + + do { + for match in displayableMatches { + try match.deleteDependencies() + } + try tournamentStore.matches.delete(contentOfs: displayableMatches) + } catch { + Logger.error(error) + } + + } +} diff --git a/PadelClub/Views/GroupStage/LoserGroupStageSettingsView.swift b/PadelClub/Views/GroupStage/LoserGroupStageSettingsView.swift deleted file mode 100644 index 78ccdd0..0000000 --- a/PadelClub/Views/GroupStage/LoserGroupStageSettingsView.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// LoserGroupStageSettingsView.swift -// PadelClub -// -// Created by Razmig Sarkissian on 29/06/2024. -// - -import SwiftUI - -extension Round { - var isGroupStageLoserBracket: Bool { - return false - } -} - -extension Tournament { - func groupStageLoserBrackets() -> [Round] { - [] - } - - func removeGroupStageLoserBrackets() { - - } -} - -struct LoserGroupStageSettingsView: View { - var tournament: Tournament - @State private var loserGroupStageBracketType: Int? = nil - @State private var losers : Set = Set() - @Environment(\.editMode) private var editMode - - var body: some View { - List(selection: $losers) { - if tournament.groupStageLoserBrackets().isEmpty == false { - //for each all rounds without parent and loserGroupStage, ability to delete them - Section { - RowButtonView("Effacer", role: .destructive) { - tournament.removeGroupStageLoserBrackets() - } - } - } - - if self.editMode?.wrappedValue == .active { - Section { - //rajouter + toolbar valider / cancel - ForEach(tournament.groupStageTeams().filter({ $0.qualified == false })) { team in - TeamRowView(team: team).tag(team) - } - } header: { - Text("Sélection des perdants de poules") - } - } else { - Section { - RowButtonView("Ajouter un match de perdant") { - self.editMode?.wrappedValue = .active - } - } footer: { - Text("Permet d'ajouter un match de perdant de poules.") - } - } - } - .toolbar { - if self.editMode?.wrappedValue == .active { - ToolbarItem(placement: .topBarLeading) { - Button("Annuler") { - self.editMode?.wrappedValue = .inactive - } - } - - ToolbarItem(placement: .topBarTrailing) { - Button("Valider") { - self.editMode?.wrappedValue = .inactive - //tournament.createGroupStageLoserBracket() - } - } - } - } - .navigationTitle("Match de perdant de poules") - .navigationBarBackButtonHidden(self.editMode?.wrappedValue == .active) - .navigationBarTitleDisplayMode(.inline) - .toolbar(.visible, for: .navigationBar) - .headerProminence(.increased) - .toolbarBackground(.visible, for: .navigationBar) - } -} diff --git a/PadelClub/Views/Match/Components/PlayerBlockView.swift b/PadelClub/Views/Match/Components/PlayerBlockView.swift index cc3ea7b..444aa94 100644 --- a/PadelClub/Views/Match/Components/PlayerBlockView.swift +++ b/PadelClub/Views/Match/Components/PlayerBlockView.swift @@ -8,7 +8,7 @@ import SwiftUI struct PlayerBlockView: View { - var match: Match + @State var match: Match let teamPosition: TeamPosition let team: TeamRegistration? let color: Color @@ -52,7 +52,7 @@ struct PlayerBlockView: View { HStack { VStack(alignment: .leading) { if let names { - if let teamScore, teamScore.luckyLoser != nil { + if let teamScore, teamScore.luckyLoser != nil, match.isLoserBracket == false { Text("Repêchée").italic().font(.caption) } diff --git a/PadelClub/Views/Match/MatchSetupView.swift b/PadelClub/Views/Match/MatchSetupView.swift index 65a98f7..2b3ce78 100644 --- a/PadelClub/Views/Match/MatchSetupView.swift +++ b/PadelClub/Views/Match/MatchSetupView.swift @@ -13,13 +13,17 @@ struct MatchSetupView: View { @EnvironmentObject var dataStore: DataStore - var match: Match + @State var match: Match @State private var seedGroup: SeedInterval? var tournamentStore: TournamentStore { return match.tournamentStore } + var matchTypeContext: MatchType { + match.matchType + } + @ViewBuilder var body: some View { ForEach(TeamPosition.allCases) { teamPosition in @@ -38,7 +42,7 @@ struct MatchSetupView: View { if let team, teamScore?.walkOut == nil { VStack(alignment: .leading, spacing: 0) { - if let teamScore, teamScore.luckyLoser != nil { + if let teamScore, teamScore.luckyLoser != nil, match.isLoserBracket == false { Text("Repêchée").italic().font(.caption) } Menu { @@ -59,9 +63,9 @@ struct MatchSetupView: View { } HStack { let luckyLosers = walkOutSpot ? match.luckyLosers() : [] - TeamPickerView(shouldConfirm: shouldConfirm, groupStagePosition: nil, luckyLosers: luckyLosers, teamPicked: { team in + TeamPickerView(shouldConfirm: shouldConfirm, groupStagePosition: nil, matchTypeContext: matchTypeContext, luckyLosers: luckyLosers, teamPicked: { team in print(team.pasteData()) - if walkOutSpot || team.bracketPosition != nil { + if walkOutSpot || team.bracketPosition != nil || matchTypeContext == .loserBracket { match.setLuckyLoser(team: team, teamPosition: teamPosition) do { try tournamentStore.matches.addOrUpdate(instance: match) @@ -82,7 +86,7 @@ struct MatchSetupView: View { } } }) - if let tournament = match.currentTournament() { + if matchTypeContext == .bracket, let tournament = match.currentTournament() { let availableQualifiedTeams = tournament.availableQualifiedTeams() let availableSeedGroups = tournament.availableSeedGroups() Text("ou") @@ -146,30 +150,33 @@ struct MatchSetupView: View { .underline() } .disabled(availableSeedGroups.isEmpty && walkOutSpot == false && availableQualifiedTeams.isEmpty) - Spacer() - if match.isSeedLocked(atTeamPosition: teamPosition) { - Button { - match.unlockSeedPosition(atTeamPosition: teamPosition) - do { - try tournamentStore.matches.addOrUpdate(instance: match) - } catch { - Logger.error(error) + + if matchTypeContext == .bracket { + Spacer() + if match.isSeedLocked(atTeamPosition: teamPosition) { + Button { + match.unlockSeedPosition(atTeamPosition: teamPosition) + do { + try tournamentStore.matches.addOrUpdate(instance: match) + } catch { + Logger.error(error) + } + } label: { + Text("Libérer") + .underline() } - } label: { - Text("Libérer") - .underline() - } - } else { - ConfirmButtonView(shouldConfirm: shouldConfirm, message: MatchSetupView.confirmationMessage) { - _ = match.lockAndGetSeedPosition(atTeamPosition: teamPosition) - do { - try tournamentStore.matches.addOrUpdate(instance: match) - } catch { - Logger.error(error) + } else { + ConfirmButtonView(shouldConfirm: shouldConfirm, message: MatchSetupView.confirmationMessage) { + _ = match.lockAndGetSeedPosition(atTeamPosition: teamPosition) + do { + try tournamentStore.matches.addOrUpdate(instance: match) + } catch { + Logger.error(error) + } + } label: { + Text("Réserver") + .underline() } - } label: { - Text("Réserver") - .underline() } } } diff --git a/PadelClub/Views/Round/LoserRoundSettingsView.swift b/PadelClub/Views/Round/LoserRoundSettingsView.swift index b8e9ba9..2e24481 100644 --- a/PadelClub/Views/Round/LoserRoundSettingsView.swift +++ b/PadelClub/Views/Round/LoserRoundSettingsView.swift @@ -16,11 +16,31 @@ struct LoserRoundSettingsView: View { var body: some View { List { + Section { + RowButtonView(isEditingTournamentSeed.wrappedValue == true ? "Terminer l'édition" : "Éditer les tours joués") { + isEditingTournamentSeed.wrappedValue.toggle() + } + } + + Section { + RowButtonView("Synchroniser les noms des matchs") { + let allRoundMatches = upperBracketRound.loserRounds.flatMap({ $0.allMatches + }) + allRoundMatches.forEach({ $0.name = $0.roundTitle() }) + do { + try self.tournament.tournamentStore.matches.addOrUpdate(contentOfs: allRoundMatches) + } catch { + Logger.error(error) + } + } + } + Section { RowButtonView("Effacer les matchs de classements", role: .destructive) { upperBracketRound.round.deleteLoserBracket() } } + .disabled(upperBracketRound.round.loserRounds().isEmpty) Section { RowButtonView("Créer les matchs de classements", role: .destructive) { @@ -30,12 +50,7 @@ struct LoserRoundSettingsView: View { } } } - - Section { - RowButtonView(isEditingTournamentSeed.wrappedValue == true ? "Terminer l'édition" : "Éditer les tours joués") { - isEditingTournamentSeed.wrappedValue.toggle() - } - } + .disabled(upperBracketRound.round.loserRounds().isEmpty == false) //todo proposer ici l'impression des matchs de classements peut-être? } diff --git a/PadelClub/Views/Round/LoserRoundView.swift b/PadelClub/Views/Round/LoserRoundView.swift index 017db3d..eb7b2fa 100644 --- a/PadelClub/Views/Round/LoserRoundView.swift +++ b/PadelClub/Views/Round/LoserRoundView.swift @@ -53,13 +53,13 @@ struct LoserRoundView: View { if isEditingTournamentSeed.wrappedValue == true { RowButtonView(match.disabled ? "Jouer ce match" : "Ne pas jouer ce match", role: .destructive) { - match._toggleMatchDisableState(!match.disabled) + match._toggleMatchDisableState(!match.disabled, single: true) } } } } header: { HStack { - if let seedInterval = loserRound.seedInterval() { + if let seedInterval = loserRound.seedInterval(initialMode: isEditingTournamentSeed.wrappedValue == true) { Text(seedInterval.localizedLabel(.wide)) let seedIntervalPointRange = seedInterval.pointsRange(tournamentLevel: tournament.tournamentLevel, teamsCount: tournament.teamCount) Spacer() @@ -67,13 +67,13 @@ struct LoserRoundView: View { .font(.caption) } else { if let previousRound = loserRound.previousRound() { - if let seedInterval = previousRound.seedInterval() { + if let seedInterval = previousRound.seedInterval(initialMode: isEditingTournamentSeed.wrappedValue == true) { Text(seedInterval.localizedLabel()) } else { Text("no previous round") } } else if let parentRound = loserRound.parentRound { - if let seedInterval = parentRound.seedInterval() { + if let seedInterval = parentRound.seedInterval(initialMode: isEditingTournamentSeed.wrappedValue == true) { Text(seedInterval.localizedLabel()) } else { Text("no parent round") diff --git a/PadelClub/Views/Round/LoserRoundsView.swift b/PadelClub/Views/Round/LoserRoundsView.swift index 4e583c3..f1ac8bb 100644 --- a/PadelClub/Views/Round/LoserRoundsView.swift +++ b/PadelClub/Views/Round/LoserRoundsView.swift @@ -167,6 +167,10 @@ struct LoserRoundsView: View { init(upperBracketRound: UpperRound) { self.upperBracketRound = upperBracketRound _selectedRound = State(wrappedValue: upperBracketRound.loserRounds.first(where: { $0.rounds.anySatisfy({ $0.getActiveLoserRound() != nil }) }) ?? upperBracketRound.loserRounds.first(where: { $0.shouldBeDisplayed })) + + if upperBracketRound.loserRounds.allSatisfy({ $0.shouldBeDisplayed == false }) { + _isEditingTournamentSeed = .init(wrappedValue: true) + } } var destinations: [LoserRound] { diff --git a/PadelClub/Views/Round/RoundSettingsView.swift b/PadelClub/Views/Round/RoundSettingsView.swift index ee049c7..e23846f 100644 --- a/PadelClub/Views/Round/RoundSettingsView.swift +++ b/PadelClub/Views/Round/RoundSettingsView.swift @@ -92,7 +92,7 @@ struct RoundSettingsView: View { Section { let roundIndex = tournament.rounds().count - RowButtonView("Ajouter " + RoundRule.roundName(fromRoundIndex: roundIndex)) { + RowButtonView("Ajouter " + RoundRule.roundName(fromRoundIndex: roundIndex), role: .destructive) { let round = Round(tournament: tournament.id, index: roundIndex, matchFormat: tournament.matchFormat) let matchCount = RoundRule.numberOfMatches(forRoundIndex: roundIndex) let matchStartIndex = RoundRule.matchIndex(fromRoundIndex: roundIndex) @@ -153,6 +153,18 @@ struct RoundSettingsView: View { } } } + + Section { + RowButtonView("Synchroniser les noms des matchs") { + let allRoundMatches = tournament.allRoundMatches() + allRoundMatches.forEach({ $0.name = $0.roundTitle() }) + do { + try self.tournament.tournamentStore.matches.addOrUpdate(contentOfs: allRoundMatches) + } catch { + Logger.error(error) + } + } + } } .toolbar { ToolbarItem(placement: .topBarTrailing) { diff --git a/PadelClub/Views/Team/TeamPickerView.swift b/PadelClub/Views/Team/TeamPickerView.swift index 4eb1980..aa891f9 100644 --- a/PadelClub/Views/Team/TeamPickerView.swift +++ b/PadelClub/Views/Team/TeamPickerView.swift @@ -14,8 +14,11 @@ struct TeamPickerView: View { @State private var confirmTeam: TeamRegistration? @State private var presentTeamPickerView: Bool = false @State private var searchField: String = "" + @State private var sortOrder: SortOrder = .ascending + var shouldConfirm: Bool = false var groupStagePosition: Int? = nil + var matchTypeContext: MatchType = .bracket var luckyLosers: [TeamRegistration] = [] let teamPicked: ((TeamRegistration) -> (Void)) @@ -43,40 +46,20 @@ struct TeamPickerView: View { Text("Même ligne en poule") } } - let teams = tournament.selectedSortedTeams() - if luckyLosers.isEmpty == false { - Section { - _teamListView(luckyLosers.sorted(by: \.weight)) - } header: { - Text("Repêchage") - } - } + _sectionView(luckyLosers.sorted(by: \.weight, order: sortOrder), title: "Repêchage") let qualified = tournament.availableQualifiedTeams() - if qualified.isEmpty == false { - Section { - _teamListView(qualified.sorted(by: \.weight)) - } header: { - Text("Qualifiées entrants") - } - } + _sectionView(qualified.sorted(by: \.weight, order: sortOrder), title: "Qualifiées entrants") + - Section { - _teamListView(teams.filter({ $0.availableForSeedPick() }).sorted(by: \.weight).reversed()) - } header: { - Text("Disponible") - } - Section { - _teamListView(teams.filter({ $0.inGroupStage() }).sorted(by: \.groupStagePosition!).reversed()) - } header: { - Text("Déjà placée en poule") - } - Section { - _teamListView(teams.filter({ $0.inRound() }).sorted(by: \.bracketPosition!).reversed()) - } header: { - Text("Déjà placée dans le tableau") + let teams = tournament.selectedSortedTeams() + if matchTypeContext == .loserBracket { + _sectionView(teams.filter({ $0.inGroupStage() && $0.qualified == false }).sorted(by: \.weight, order: sortOrder), title: "Non qualifié de poules") } - + + _sectionView(teams.filter({ $0.availableForSeedPick() }).sorted(by: \.weight, order: sortOrder), title: "Disponible") + _sectionView(teams.filter({ $0.inGroupStage() }).sorted(by: \.weight, order: sortOrder), title: "Déjà placée en poule") + _sectionView(teams.filter({ $0.inRound() }).sorted(by: \.weight, order: sortOrder), title: "Déjà placée dans le tableau") } .searchable(text: $searchField, placement: .navigationBarDrawer(displayMode: .always)) .keyboardType(.alphabet) @@ -84,11 +67,36 @@ struct TeamPickerView: View { .navigationTitle("Choisir une équipe") .toolbarBackground(.visible, for: .navigationBar) .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Picker(selection: $sortOrder) { + Label("Poids", systemImage: "arrow.up").tag(SortOrder.ascending) + .labelStyle(.titleAndIcon) + Label("Poids", systemImage: "arrow.down").tag(SortOrder.descending) + .labelStyle(.titleAndIcon) + } label: { + Label("Trier", systemImage: "arrow.up.arrow.down") + } + .pickerStyle(.menu) + } + } + .headerProminence(.increased) } .tint(.master) } } + @ViewBuilder + private func _sectionView(_ teams: [TeamRegistration], title: String) -> some View { + if teams.isEmpty == false { + Section { + _teamListView(teams) + } header: { + Text(title) + } + } + } + private func _teamListView(_ teams: [TeamRegistration]) -> some View { ForEach(teams) { team in if searchField.isEmpty || team.contains(searchField) { diff --git a/PadelClub/Views/Team/TeamRowView.swift b/PadelClub/Views/Team/TeamRowView.swift index 0b3550d..533d4bc 100644 --- a/PadelClub/Views/Team/TeamRowView.swift +++ b/PadelClub/Views/Team/TeamRowView.swift @@ -21,6 +21,17 @@ struct TeamRowView: View { Text(name).foregroundStyle(.secondary) } + if let groupStage = team.groupStageObject() { + HStack { + Text(groupStage.groupStageTitle()) + if let finalPosition = groupStage.finalPosition(ofTeam: team) { + Text((finalPosition + 1).ordinalFormatted()) + } + } + } else if let round = team.initialRound() { + Text(round.roundTitle(.wide)) + } + if team.players().isEmpty == false { ForEach(team.players()) { player in Text(player.playerLabel()) diff --git a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift index 88b0031..e491aec 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift @@ -192,26 +192,37 @@ struct TournamentRankView: View { .fontWeight(.bold) Text(key.ordinalFormattedSuffix()).font(.caption) } + if let index = tournament.indexOf(team: team) { - let rankingDifference = index - (key - 1) - if rankingDifference > 0 { - HStack(spacing: 0.0) { - Text(rankingDifference.formatted(.number.sign(strategy: .always()))) - .monospacedDigit() - Image(systemName: "arrowtriangle.up.fill") - .imageScale(.small) - } - .foregroundColor(.green) - } else if rankingDifference < 0 { + ZStack { HStack(spacing: 0.0) { - Text(rankingDifference.formatted(.number.sign(strategy: .always()))) + Text(tournament.teamCount.formatted(.number.sign(strategy: .always()))) .monospacedDigit() Image(systemName: "arrowtriangle.down.fill") .imageScale(.small) } - .foregroundColor(.logoRed) - } else { - Text("--") + .opacity(0) + + let rankingDifference = index - (key - 1) + if rankingDifference > 0 { + HStack(spacing: 0.0) { + Text(rankingDifference.formatted(.number.sign(strategy: .always()))) + .monospacedDigit() + Image(systemName: "arrowtriangle.up.fill") + .imageScale(.small) + } + .foregroundColor(.green) + } else if rankingDifference < 0 { + HStack(spacing: 0.0) { + Text(rankingDifference.formatted(.number.sign(strategy: .always()))) + .monospacedDigit() + Image(systemName: "arrowtriangle.down.fill") + .imageScale(.small) + } + .foregroundColor(.logoRed) + } else { + Text("--") + } } } } From 40fc73e6bfdf9ab7b51c7d71db2de614b9c39088 Mon Sep 17 00:00:00 2001 From: Raz Date: Sun, 8 Sep 2024 15:39:40 +0200 Subject: [PATCH 08/23] fix new feature loser bracket groupstages --- PadelClub/Data/Tournament.swift | 15 ++-- PadelClub/Utils/PadelRule.swift | 4 +- .../Views/GroupStage/GroupStagesView.swift | 12 ++-- .../LoserBracketFromGroupStageView.swift | 70 +++++++++++++------ PadelClub/Views/Match/EditSharingView.swift | 26 ++++--- PadelClub/Views/Match/MatchRowView.swift | 3 +- PadelClub/Views/Match/MatchSetupView.swift | 8 +++ PadelClub/Views/Match/MatchSummaryView.swift | 3 +- PadelClub/Views/Team/TeamPickerView.swift | 31 +++++--- 9 files changed, 115 insertions(+), 57 deletions(-) diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 9d9fa18..1d034d6 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1258,12 +1258,15 @@ defer { } } - groupStageLoserBracket()?.playedMatches().forEach({ match in - if match.hasEnded() { - teams.setOrAppend(match.winningTeamId, at: match.index) - teams.setOrAppend(match.losingTeamId, at: match.index + 1) - } - }) + if let groupStageLoserBracketPlayedMatches = groupStageLoserBracket()?.playedMatches() { + groupStageLoserBracketPlayedMatches.forEach({ match in + if match.hasEnded() { + let sameMatchIndexCount = groupStageLoserBracketPlayedMatches.filter({ $0.index == match.index }).count + teams.setOrAppend(match.winningTeamId, at: match.index) + teams.setOrAppend(match.losingTeamId, at: match.index + sameMatchIndexCount) + } + }) + } let groupStages = groupStages() let baseRank = teamCount - groupStageSpots() + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified diff --git a/PadelClub/Utils/PadelRule.swift b/PadelClub/Utils/PadelRule.swift index 9672531..d5b13b4 100644 --- a/PadelClub/Utils/PadelRule.swift +++ b/PadelClub/Utils/PadelRule.swift @@ -293,8 +293,8 @@ enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable { func pointsRange(first: Int, last: Int, teamsCount: Int) -> String { - let range = [points(for: last - 1, count: teamsCount), - points(for: first - 1, count: teamsCount)] + let range = [points(for: first - 1, count: teamsCount), + points(for: last - 1, count: teamsCount)] return range.map { $0.formatted(.number.sign(strategy: .always())) }.joined(separator: " / ") + " pts" } diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index bfc447e..0e148a5 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -9,9 +9,10 @@ import SwiftUI import LeStorage struct GroupStagesView: View { - var tournament: Tournament + @State var tournament: Tournament @State private var selectedDestination: GroupStageDestination? - + @EnvironmentObject var dataStore: DataStore + enum GroupStageDestination: Selectable, Identifiable, Equatable { static func == (lhs: GroupStagesView.GroupStageDestination, rhs: GroupStagesView.GroupStageDestination) -> Bool { lhs.id == rhs.id @@ -67,6 +68,7 @@ struct GroupStagesView: View { return nil } case .loserBracket(let loserBracket): + if loserBracket._matches().isEmpty { return nil } return loserBracket.badgeImage() case .groupStage(let groupStage): return groupStage.badgeImage() @@ -77,9 +79,7 @@ struct GroupStagesView: View { var allMatches: [Match] { tournament.groupStagesMatches() } - - @State private var isEditingLoserBracketGroupStage: Bool - + init(tournament: Tournament) { self.tournament = tournament if tournament.shouldVerifyGroupStage { @@ -92,7 +92,6 @@ struct GroupStagesView: View { _selectedDestination = State(wrappedValue: .groupStage(gs)) } } - _isEditingLoserBracketGroupStage = .init(wrappedValue: tournament.groupStageLoserBracket()?._matches().isEmpty ?? false) } func allDestinations() -> [GroupStageDestination] { @@ -158,7 +157,6 @@ struct GroupStagesView: View { GroupStageView(groupStage: groupStage).id(groupStage.id) case .loserBracket(let loserBracket): LoserBracketFromGroupStageView(loserBracket: loserBracket).id(loserBracket.id) - .environment(\.isEditingTournamentSeed, $isEditingLoserBracketGroupStage) case nil: GroupStagesSettingsView() .navigationTitle("Réglages") diff --git a/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift b/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift index d3a4e09..7e85d61 100644 --- a/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift +++ b/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift @@ -10,32 +10,31 @@ import LeStorage struct LoserBracketFromGroupStageView: View { - @Environment(\.isEditingTournamentSeed) var isEditingTournamentSeed @Environment(Tournament.self) var tournament: Tournament @EnvironmentObject var dataStore: DataStore @Environment(NavigationViewModel.self) private var navigation: NavigationViewModel - @State var loserBracket: Round + @State private var isEditingLoserBracketGroupStage: Bool + + init(loserBracket: Round) { + self.loserBracket = loserBracket + _isEditingLoserBracketGroupStage = .init(wrappedValue: loserBracket._matches().isEmpty) + } var tournamentStore: TournamentStore { return self.tournament.tournamentStore } + var displayableMatches: [Match] { + loserBracket.playedMatches().sorted(by: \.index) + } + var body: some View { List { - let displayableMatches = loserBracket.playedMatches().sorted(by: \.index) - - if isEditingTournamentSeed.wrappedValue == true { + if isEditingLoserBracketGroupStage == true && displayableMatches.isEmpty == false { Section { RowButtonView("Ajouter un match", role: .destructive) { - let placeCount = tournament.groupStageLoserBracketsInitialPlace() + displayableMatches.count * 2 - let match = Match(round: loserBracket.id, index: placeCount, matchFormat: loserBracket.matchFormat) - match.name = "\(placeCount)\(placeCount.ordinalFormattedSuffix()) place" - do { - try tournamentStore.matches.addOrUpdate(instance: match) - } catch { - Logger.error(error) - } + _addNewMatch() } } } @@ -43,9 +42,10 @@ struct LoserBracketFromGroupStageView: View { ForEach(displayableMatches) { match in Section { MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle) + .environment(\.isEditingTournamentSeed, $isEditingLoserBracketGroupStage) } header: { let tournamentTeamCount = tournament.teamCount - let seedIntervalPointRange = tournament.tournamentLevel.pointsRange(first: match.index, last: match.index + 1, teamsCount: tournamentTeamCount) + let seedIntervalPointRange = tournament.tournamentLevel.pointsRange(first: match.index, last: match.index + displayableMatches.filter({ $0.index == match.index }).count, teamsCount: tournamentTeamCount) HStack { Text(match.matchTitle(.wide)) Spacer() @@ -53,7 +53,7 @@ struct LoserBracketFromGroupStageView: View { .font(.caption) } } footer: { - if isEditingTournamentSeed.wrappedValue == true { + if isEditingLoserBracketGroupStage == true { HStack { if match.index > tournament.groupStageLoserBracketsInitialPlace() { FooterButtonView("même place qu'au-dessus") { @@ -80,7 +80,7 @@ struct LoserBracketFromGroupStageView: View { } Section { - if displayableMatches.isEmpty == false && isEditingTournamentSeed.wrappedValue == true { + if displayableMatches.count > 1 && isEditingLoserBracketGroupStage == true { Section { RowButtonView("Effacer tous les matchs", role: .destructive) { _deleteAllMatches() @@ -91,21 +91,49 @@ struct LoserBracketFromGroupStageView: View { } } } + .overlay { + if displayableMatches.isEmpty { + ContentUnavailableView { + Label("Aucun match de classement", systemImage: "figure.tennis") + } description: { + Text("Vous n'avez créé aucun match de classement entre les perdants de poules.") + } actions: { + RowButtonView("Ajouter un match") { + isEditingLoserBracketGroupStage = true + _addNewMatch() + } + .padding(.horizontal) + } + } + } .headerProminence(.increased) .navigationTitle("Classement de poules") .toolbar { ToolbarItem(placement: .topBarTrailing) { - Button(isEditingTournamentSeed.wrappedValue == true ? "Valider" : "Modifier") { - if isEditingTournamentSeed.wrappedValue == true { - isEditingTournamentSeed.wrappedValue = false - } else { - isEditingTournamentSeed.wrappedValue = true + if displayableMatches.isEmpty == false { + Button(isEditingLoserBracketGroupStage == true ? "Valider" : "Modifier") { + if isEditingLoserBracketGroupStage == true { + isEditingLoserBracketGroupStage = false + } else { + isEditingLoserBracketGroupStage = true + } } } } } } + private func _addNewMatch() { + let placeCount = tournament.groupStageLoserBracketsInitialPlace() + displayableMatches.count * 2 + let match = Match(round: loserBracket.id, index: placeCount, matchFormat: loserBracket.matchFormat) + match.name = "\(placeCount)\(placeCount.ordinalFormattedSuffix()) place" + do { + try tournamentStore.matches.addOrUpdate(instance: match) + } catch { + Logger.error(error) + } + } + private func _deleteAllMatches() { let displayableMatches = loserBracket.playedMatches().sorted(by: \.index) diff --git a/PadelClub/Views/Match/EditSharingView.swift b/PadelClub/Views/Match/EditSharingView.swift index 33c0116..651e419 100644 --- a/PadelClub/Views/Match/EditSharingView.swift +++ b/PadelClub/Views/Match/EditSharingView.swift @@ -24,17 +24,19 @@ struct EditSharingView: View { func shareMessage(displayRank: Bool, displayTeamName: Bool) -> String { var messageData: [String] = [] - var locAndTime: String = "" - if let courtName = match.courtName() { - locAndTime.append("\(courtName)") - } - - if let startDate = match.startDate { - locAndTime = locAndTime + " à " + startDate.formattedAsHourMinute() - } - - if locAndTime.isEmpty == false { - messageData.append(locAndTime) + if match.hasEnded() == false { + var locAndTime: String? + if let courtName = match.courtName() { + locAndTime = "\(courtName)" + } + + if let startDate = match.startDate { + locAndTime = [locAndTime, startDate.formattedAsHourMinute()].compactMap({ $0 }).joined(separator: " à ") + } + + if let locAndTime, locAndTime.isEmpty == false { + messageData.append(locAndTime) + } } if let tournament = match.currentTournament() { @@ -52,6 +54,8 @@ struct EditSharingView: View { let players = "\(labelOne)\ncontre\n\(labelTwo)" messageData.append(players) + messageData.append(match.scoreLabel()) + return messageData.joined(separator: "\n") } diff --git a/PadelClub/Views/Match/MatchRowView.swift b/PadelClub/Views/Match/MatchRowView.swift index 3c486a8..954da7f 100644 --- a/PadelClub/Views/Match/MatchRowView.swift +++ b/PadelClub/Views/Match/MatchRowView.swift @@ -9,7 +9,8 @@ import SwiftUI struct MatchRowView: View { - var match: Match + @EnvironmentObject var dataStore: DataStore + @State var match: Match let matchViewStyle: MatchViewStyle var title: String? = nil diff --git a/PadelClub/Views/Match/MatchSetupView.swift b/PadelClub/Views/Match/MatchSetupView.swift index 2b3ce78..1998374 100644 --- a/PadelClub/Views/Match/MatchSetupView.swift +++ b/PadelClub/Views/Match/MatchSetupView.swift @@ -214,6 +214,14 @@ struct MatchSetupView: View { } catch { Logger.error(error) } + } else if match.isLoserBracket { + if let score = match.teamScore(ofTeam: team) { + do { + try tournamentStore.teamScores.delete(instance: score) + } catch { + Logger.error(error) + } + } } else { match.teamWillBeWalkOut(team) do { diff --git a/PadelClub/Views/Match/MatchSummaryView.swift b/PadelClub/Views/Match/MatchSummaryView.swift index 16fd9bf..1e4bda1 100644 --- a/PadelClub/Views/Match/MatchSummaryView.swift +++ b/PadelClub/Views/Match/MatchSummaryView.swift @@ -8,7 +8,8 @@ import SwiftUI struct MatchSummaryView: View { - var match: Match + @EnvironmentObject var dataStore: DataStore + @State var match: Match let matchViewStyle: MatchViewStyle let matchTitle: String let roundTitle: String? diff --git a/PadelClub/Views/Team/TeamPickerView.swift b/PadelClub/Views/Team/TeamPickerView.swift index aa891f9..a9b143c 100644 --- a/PadelClub/Views/Team/TeamPickerView.swift +++ b/PadelClub/Views/Team/TeamPickerView.swift @@ -68,19 +68,34 @@ struct TeamPickerView: View { .toolbarBackground(.visible, for: .navigationBar) .navigationBarTitleDisplayMode(.inline) .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button("Annuler", role: .cancel) { + presentTeamPickerView = false + } + } ToolbarItem(placement: .topBarTrailing) { - Picker(selection: $sortOrder) { - Label("Poids", systemImage: "arrow.up").tag(SortOrder.ascending) - .labelStyle(.titleAndIcon) - Label("Poids", systemImage: "arrow.down").tag(SortOrder.descending) - .labelStyle(.titleAndIcon) + Menu { + Section { + Picker(selection: $sortOrder) { + Label("Trier les équipes par poids croissant", systemImage: "chevron.up").tag(SortOrder.ascending) + .labelStyle(.titleAndIcon) + Label("Trier les équipes par poids décroissant", systemImage: "chevron.down").tag(SortOrder.descending) + .labelStyle(.titleAndIcon) + } label: { + Text("Trier les équipes par poids") + } + .pickerStyle(.inline) + } header: { + Text("Trier les équipes par poids") + } } label: { - Label("Trier", systemImage: "arrow.up.arrow.down") + HStack { + Text("Poids") + Image(systemName: sortOrder == .ascending ? "chevron.up" : "chevron.down") + } } - .pickerStyle(.menu) } } - .headerProminence(.increased) } .tint(.master) } From 708e0aa48113e690ec5ff52b8be4d79710431117 Mon Sep 17 00:00:00 2001 From: Raz Date: Sun, 8 Sep 2024 20:47:31 +0200 Subject: [PATCH 09/23] wip around me tab --- PadelClub.xcodeproj/project.pbxproj | 4 + .../Data/Federal/FederalTournament.swift | 13 +- .../Federal/FederalTournamentHolder.swift | 1 + PadelClub/Data/Tournament.swift | 4 + .../Utils/Network/NetworkFederalService.swift | 60 ++ PadelClub/ViewModel/AgendaDestination.swift | 42 +- .../ViewModel/FederalDataViewModel.swift | 27 +- PadelClub/ViewModel/Selectable.swift | 5 + .../GenericDestinationPickerView.swift | 9 +- .../Navigation/Agenda/ActivityView.swift | 179 +++-- .../Navigation/Agenda/EventListView.swift | 4 +- .../Agenda/TournamentLookUpView.swift | 637 ++++++++++++++++++ .../Views/Shared/TournamentFilterView.swift | 79 ++- 13 files changed, 964 insertions(+), 100 deletions(-) create mode 100644 PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index eea28e6..8fcbf80 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -247,6 +247,7 @@ FFCFC0182BBC5A6800B82851 /* SetLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0172BBC5A6800B82851 /* SetLabelView.swift */; }; FFCFC01A2BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0192BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift */; }; FFCFC01C2BBC5AAA00B82851 /* SetDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */; }; + FFD655D82C8DE27400E5B35E /* TournamentLookUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD655D72C8DE27400E5B35E /* TournamentLookUpView.swift */; }; FFD783FF2B91BA42000F62A6 /* PadelClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */; }; FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */; }; FFDB1C732BB2CFE900F1E467 /* MySortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */; }; @@ -593,6 +594,7 @@ FFCFC0172BBC5A6800B82851 /* SetLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetLabelView.swift; sourceTree = ""; }; FFCFC0192BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchTypeSmallSelectionView.swift; sourceTree = ""; }; FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDescriptor.swift; sourceTree = ""; }; + FFD655D72C8DE27400E5B35E /* TournamentLookUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentLookUpView.swift; sourceTree = ""; }; FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubView.swift; sourceTree = ""; }; FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; @@ -1291,6 +1293,7 @@ FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */, FF59FFB22B90EFAC0061EFF9 /* EventListView.swift */, FF5D0D8A2BB4D1E3005CB568 /* CalendarView.swift */, + FFD655D72C8DE27400E5B35E /* TournamentLookUpView.swift */, ); path = Agenda; sourceTree = ""; @@ -1557,6 +1560,7 @@ FF1CBC1B2BB53D1F0036DAAB /* FederalTournament.swift in Sources */, FF8F26412BADFC8700650388 /* TournamentInitView.swift in Sources */, C4A47D8A2B7BBB6500ADC637 /* SubscriptionView.swift in Sources */, + FFD655D82C8DE27400E5B35E /* TournamentLookUpView.swift in Sources */, FF1DC5572BAB3AED00FD8220 /* ClubsView.swift in Sources */, FFE103122C366E5900684FC9 /* ImagePickerView.swift in Sources */, FF8F264F2BAE0B9600650388 /* MatchTypeSelectionView.swift in Sources */, diff --git a/PadelClub/Data/Federal/FederalTournament.swift b/PadelClub/Data/Federal/FederalTournament.swift index 0a473ee..bd7e342 100644 --- a/PadelClub/Data/Federal/FederalTournament.swift +++ b/PadelClub/Data/Federal/FederalTournament.swift @@ -126,10 +126,22 @@ struct FederalTournament: Identifiable, Codable { ?? [] } + var federalClub: FederalClub? { + if let codeClub { + return FederalClub(federalClubCode: codeClub, federalClubName: clubLabel()) + } else { + return nil + } + } + var shareMessage: String { [libelle, dateDebut?.formatted(date: .complete, time: .omitted)].compactMap({$0}).joined(separator: "\n") + "\n" } + var japMessage: String { + [nomClub, jugeArbitre?.nom, jugeArbitre?.prenom, courrielEngagement, installation?.telephone].compactMap({$0}).joined(separator: ";") + } + func validForSearch(_ searchText: String, scope: FederalTournamentSearchScope) -> Bool { var trimmedSearchText = searchText.lowercased().trimmingCharacters(in: .whitespaces).folding(options: .diacriticInsensitive, locale: .current) trimmedSearchText = trimmedSearchText.replaceCharactersFromSet(characterSet: .punctuationCharacters, replacementString: " ") @@ -156,7 +168,6 @@ extension FederalTournament: FederalTournamentHolder { // var importedId: Int { id } var holderId: String { id.string } - func clubLabel() -> String { nomClub ?? villeEngagement ?? installation?.nom ?? "" } diff --git a/PadelClub/Data/Federal/FederalTournamentHolder.swift b/PadelClub/Data/Federal/FederalTournamentHolder.swift index 4350d27..c5181a2 100644 --- a/PadelClub/Data/Federal/FederalTournamentHolder.swift +++ b/PadelClub/Data/Federal/FederalTournamentHolder.swift @@ -11,6 +11,7 @@ protocol FederalTournamentHolder { var holderId: String { get } var startDate: Date { get } var endDate: Date? { get } + var codeClub: String? { get } var tournaments: [any TournamentBuildHolder] { get } func clubLabel() -> String func subtitleLabel() -> String diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 1d034d6..a3aa857 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -2115,6 +2115,10 @@ extension Tournament: Hashable { } extension Tournament: FederalTournamentHolder { + var codeClub: String? { + club()?.code + } + var holderId: String { id } func clubLabel() -> String { diff --git a/PadelClub/Utils/Network/NetworkFederalService.swift b/PadelClub/Utils/Network/NetworkFederalService.swift index 666b049..7933f55 100644 --- a/PadelClub/Utils/Network/NetworkFederalService.swift +++ b/PadelClub/Utils/Network/NetworkFederalService.swift @@ -194,4 +194,64 @@ recherche_type=club&club[autocomplete][value_container][value_field]=\(codeClub. print("no data found in html") } } + + func getAllFederalTournaments(sortingOption: String, page: Int, startDate: Date, endDate: Date, city: String, distance: Double, categories: [TournamentCategory], levels: [TournamentLevel], lat: String?, lng: String?, ages: [FederalTournamentAge], types: [FederalTournamentType], nationalCup: Bool) async throws -> [HttpCommand] { + + var cityParameter = "" + var searchType = "ligue" + if city.trimmed.isEmpty == false { + searchType = "ville" + cityParameter = city + } + + var levelsParameter = "" + if levels.isEmpty == false { + levelsParameter = levels.map { "categorie_tournoi[\($0.localizedLabel)]=\($0.localizedLabel)" }.joined(separator: "&") + "&" + } + + var categoriesParameter = "" + if categories.isEmpty == false { + categoriesParameter = categories.map { "epreuve[\($0.requestLabel)]=\($0.requestLabel)" }.joined(separator: "&") + "&" + } + + var agesParameter = "" + if ages.isEmpty == false { + agesParameter = ages.map { "categorie_age[\($0.rawValue)]=\($0.rawValue)" }.joined(separator: "&") + "&" + } + + var typesParameter = "" + if types.isEmpty == false { + typesParameter = types.map { "type[\($0.rawValue.capitalized)]=\($0.rawValue.capitalized)" }.joined(separator: "&") + "&" + } + + var npc = "" + if nationalCup { + npc = "&tournoi_npc=1" + } + + let parameters = """ +recherche_type=\(searchType)&ville%5Bautocomplete%5D%5Bcountry%5D=fr&ville%5Bautocomplete%5D%5Btextfield%5D=&ville%5Bautocomplete%5D%5Bvalue_container%5D%5Bvalue_field%5D=\(cityParameter)&ville%5Bautocomplete%5D%5Bvalue_container%5D%5Blabel_field%5D=\(cityParameter)&ville%5Bautocomplete%5D%5Bvalue_container%5D%5Blat_field%5D=\(lat ?? "")&ville%5Bautocomplete%5D%5Bvalue_container%5D%5Blng_field%5D=\(lng ?? "")&ville%5Bdistance%5D%5Bvalue_field%5D=\(Int(distance))&club%5Bautocomplete%5D%5Btextfield%5D=&club%5Bautocomplete%5D%5Bvalue_container%5D%5Bvalue_field%5D=&club%5Bautocomplete%5D%5Bvalue_container%5D%5Blabel_field%5D=&pratique=PADEL&date%5Bstart%5D=\(startDate.twoDigitsYearFormatted)&date%5Bend%5D=\(endDate.twoDigitsYearFormatted)&\(categoriesParameter)\(levelsParameter)\(agesParameter)\(typesParameter)\(npc)&page=\(page)&sort=\(sortingOption)&form_build_id=\(formId)&form_id=recherche_tournois_form&_triggering_element_name=submit_page&_triggering_element_value=Submit+page +""" + let postData = parameters.data(using: .utf8) + + var request = URLRequest(url: URL(string: "https://tenup.fft.fr/system/ajax")!,timeoutInterval: Double.infinity) + request.addValue("application/json, text/javascript, */*; q=0.01", forHTTPHeaderField: "Accept") + request.addValue("fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3", forHTTPHeaderField: "Accept-Language") + request.addValue("gzip, deflate, br", forHTTPHeaderField: "Accept-Encoding") + request.addValue("application/x-www-form-urlencoded; charset=UTF-8", forHTTPHeaderField: "Content-Type") + request.addValue("XMLHttpRequest", forHTTPHeaderField: "X-Requested-With") + request.addValue("https://tenup.fft.fr", forHTTPHeaderField: "Origin") + request.addValue("keep-alive", forHTTPHeaderField: "Connection") + request.addValue("https://tenup.fft.fr/recherche/tournois", forHTTPHeaderField: "Referer") + request.addValue("empty", forHTTPHeaderField: "Sec-Fetch-Dest") + request.addValue("cors", forHTTPHeaderField: "Sec-Fetch-Mode") + request.addValue("same-origin", forHTTPHeaderField: "Sec-Fetch-Site") + + request.httpMethod = "POST" + request.httpBody = postData + + + return try await runTenupTask(request: request) + } + } diff --git a/PadelClub/ViewModel/AgendaDestination.swift b/PadelClub/ViewModel/AgendaDestination.swift index 3708e3d..abe2126 100644 --- a/PadelClub/ViewModel/AgendaDestination.swift +++ b/PadelClub/ViewModel/AgendaDestination.swift @@ -18,12 +18,8 @@ enum AgendaDestination: Int, CaseIterable, Identifiable, Selectable, Equatable { case activity case history case tenup + case around - enum ViewStyle { - case list - case calendar - } - var localizedTitleKey: String { switch self { case .activity: @@ -32,6 +28,8 @@ enum AgendaDestination: Int, CaseIterable, Identifiable, Selectable, Equatable { return "Terminé" case .tenup: return "Tenup" + case .around: + return "Autour" } } @@ -39,14 +37,12 @@ enum AgendaDestination: Int, CaseIterable, Identifiable, Selectable, Equatable { localizedTitleKey } - var systemImage: String { + func systemImage() -> String? { switch self { - case .activity: - return "squares.leading.rectangle" - case .history: - return "book.closed" - case .tenup: - return "tennisball" + case .around: + return "location.magnifyingglass" + default: + return nil } } @@ -58,6 +54,9 @@ enum AgendaDestination: Int, CaseIterable, Identifiable, Selectable, Equatable { DataStore.shared.tournaments.filter { $0.endDate != nil && FederalDataViewModel.shared.isTournamentValidForFilters($0) }.count case .tenup: FederalDataViewModel.shared.filteredFederalTournaments.map { $0.tournaments.count }.reduce(0,+) + case .around: + nil + } } @@ -84,6 +83,25 @@ enum AgendaDestination: Int, CaseIterable, Identifiable, Selectable, Equatable { } else { return nil } + case .around: + return nil } } } + + +enum ViewStyle { + case list + case calendar +} + +struct ViewStyleKey: EnvironmentKey { + static let defaultValue: ViewStyle = .list +} + +extension EnvironmentValues { + var viewStyle: ViewStyle { + get { self[ViewStyleKey.self] } + set { self[ViewStyleKey.self] = newValue } + } +} diff --git a/PadelClub/ViewModel/FederalDataViewModel.swift b/PadelClub/ViewModel/FederalDataViewModel.swift index 387dc0a..0c3b349 100644 --- a/PadelClub/ViewModel/FederalDataViewModel.swift +++ b/PadelClub/ViewModel/FederalDataViewModel.swift @@ -13,11 +13,13 @@ class FederalDataViewModel { static let shared = FederalDataViewModel() var federalTournaments: [FederalTournament] = [] + var searchedFederalTournaments: [FederalTournament] = [] var levels: Set = Set() var categories: Set = Set() var ageCategories: Set = Set() var selectedClubs: Set = Set() var id: UUID = UUID() + var searchAttemptCount: Int = 0 func filterStatus() -> String { var labels: [String] = [] @@ -33,6 +35,14 @@ class FederalDataViewModel { return labels.joined(separator: ", ") } + var searchedClubs: [FederalClub] { + searchedFederalTournaments.compactMap { ft in + ft.federalClub + }.uniqued { fc in + fc.federalClubCode + }.sorted(by: \.federalClubName) + } + func selectedClub() -> Club? { if selectedClubs.isEmpty == false { return DataStore.shared.clubs.first(where: { $0.code == selectedClubs.first! }) @@ -53,8 +63,16 @@ class FederalDataViewModel { (levels.isEmpty && categories.isEmpty && ageCategories.isEmpty && selectedClubs.isEmpty) == false } - var filteredFederalTournaments: [FederalTournament] { - federalTournaments.filter({ tournament in + var filteredFederalTournaments: [FederalTournamentHolder] { + filteredFederalTournaments(from: federalTournaments) + } + + var filteredSearchedFederalTournaments: [FederalTournamentHolder] { + filteredFederalTournaments(from: searchedFederalTournaments) + } + + func filteredFederalTournaments(from tournaments: [any FederalTournamentHolder]) -> [FederalTournamentHolder] { + tournaments.filter({ tournament in (levels.isEmpty || tournament.tournaments.anySatisfy({ levels.contains($0.level) })) && (categories.isEmpty || tournament.tournaments.anySatisfy({ categories.contains($0.category) })) @@ -103,3 +121,8 @@ class FederalDataViewModel { } } +struct FederalClub: Identifiable { + var id: String { federalClubCode } + var federalClubCode: String + var federalClubName: String +} diff --git a/PadelClub/ViewModel/Selectable.swift b/PadelClub/ViewModel/Selectable.swift index e29e072..6734828 100644 --- a/PadelClub/ViewModel/Selectable.swift +++ b/PadelClub/ViewModel/Selectable.swift @@ -14,9 +14,14 @@ protocol Selectable { func badgeImage() -> Badge? func badgeValueColor() -> Color? func displayImageIfValueZero() -> Bool + func systemImage() -> String? } extension Selectable { + func systemImage() -> String? { + return nil + } + func displayImageIfValueZero() -> Bool { return false } diff --git a/PadelClub/Views/Components/GenericDestinationPickerView.swift b/PadelClub/Views/Components/GenericDestinationPickerView.swift index 999a564..7002dc9 100644 --- a/PadelClub/Views/Components/GenericDestinationPickerView.swift +++ b/PadelClub/Views/Components/GenericDestinationPickerView.swift @@ -38,8 +38,13 @@ struct GenericDestinationPickerView: Button { selectedDestination = destination } label: { - Text(destination.selectionLabel(index: index)) - .foregroundStyle(selectedDestination?.id == destination.id ? .white : .black) + if let systemImage = destination.systemImage() { + Image(systemName: systemImage) + .foregroundStyle(selectedDestination?.id == destination.id ? .white : .black) + } else { + Text(destination.selectionLabel(index: index)) + .foregroundStyle(selectedDestination?.id == destination.id ? .white : .black) + } } .padding() .background { diff --git a/PadelClub/Views/Navigation/Agenda/ActivityView.swift b/PadelClub/Views/Navigation/Agenda/ActivityView.swift index 09ac9c4..41d96d6 100644 --- a/PadelClub/Views/Navigation/Agenda/ActivityView.swift +++ b/PadelClub/Views/Navigation/Agenda/ActivityView.swift @@ -16,12 +16,13 @@ struct ActivityView: View { @State private var presentFilterView: Bool = false @State private var presentToolbar: Bool = false @State private var newTournament: Tournament? - @State private var viewStyle: AgendaDestination.ViewStyle = .list + @State private var viewStyle: ViewStyle = .list @State private var isGatheringFederalTournaments: Bool = false @State private var error: Error? @State private var uuid: UUID = UUID() @State private var presentClubSearchView: Bool = false @State private var quickAccessScreen: QuickAccessScreen? = nil + @State private var displaySearchView: Bool = false enum QuickAccessScreen : Identifiable, Hashable { case inscription(pasteString: String) @@ -67,6 +68,8 @@ struct ActivityView: View { return endedTournaments case .tenup: return federalDataViewModel.filteredFederalTournaments + case .around: + return federalDataViewModel.filteredSearchedFederalTournaments } } @@ -81,23 +84,39 @@ struct ActivityView: View { .buttonBorderShape(.capsule) } + @ViewBuilder + func _listView() -> some View { + switch navigation.agendaDestination! { + case .activity: + List { + EventListView(tournaments: runningTournaments, sortAscending: true) + } + case .history: + List { + EventListView(tournaments: endedTournaments, sortAscending: false) + } + case .tenup: + List { + EventListView(tournaments: federalDataViewModel.federalTournaments, sortAscending: true) + .id(uuid) + } + case .around: + List { + EventListView(tournaments: federalDataViewModel.searchedFederalTournaments, sortAscending: true) + .id(uuid) + } + } + } + + var body: some View { @Bindable var navigation = navigation NavigationStack(path: $navigation.path) { VStack(spacing: 0) { GenericDestinationPickerView(selectedDestination: $navigation.agendaDestination, destinations: AgendaDestination.allCases, nilDestinationIsValid: false) - List { - switch navigation.agendaDestination! { - case .activity: - EventListView(tournaments: runningTournaments, viewStyle: viewStyle, sortAscending: true) - case .history: - EventListView(tournaments: endedTournaments, viewStyle: viewStyle, sortAscending: false) - case .tenup: - EventListView(tournaments: federalDataViewModel.federalTournaments, viewStyle: viewStyle, sortAscending: true) - .id(uuid) - } - } + _listView() + .environment(\.viewStyle, viewStyle) .environment(federalDataViewModel) .overlay { if let error, navigation.agendaDestination == .tenup { @@ -119,11 +138,11 @@ struct ActivityView: View { ContentUnavailableView.search(text: searchText) } else if federalDataViewModel.areFiltersEnabled() { ContentUnavailableView { - Text("Aucun résultat") + Text("Aucun tournoi") } description: { - Text(federalDataViewModel.filterStatus()) + Text("Aucun tournoi ne correspond aux fitres que vous avez choisis : \(federalDataViewModel.filterStatus())") } actions: { - RowButtonView("supprimer le filtre") { + RowButtonView("modifier vos filtres") { federalDataViewModel.removeFilters() } .padding(.horizontal) @@ -137,6 +156,12 @@ struct ActivityView: View { //.searchable(text: $searchText) .onAppear { presentToolbar = true } .onDisappear { presentToolbar = false } + .sheet(isPresented: $displaySearchView) { + NavigationStack { + TournamentLookUpView() + .environment(federalDataViewModel) + } + } .sheet(item: $newTournament) { tournament in EventCreationView(tournaments: [tournament], selectedClub: federalDataViewModel.selectedClub()) .environment(navigation) @@ -164,52 +189,65 @@ struct ActivityView: View { } } .toolbar { - if presentToolbar { - //let _activityStatus = _activityStatus() - if federalDataViewModel.areFiltersEnabled() { - ToolbarItem(placement: .status) { - Text(federalDataViewModel.filterStatus()) + ToolbarItemGroup(placement: .topBarLeading) { + Button { + switch viewStyle { + case .list: + viewStyle = .calendar + case .calendar: + viewStyle = .list } + } label: { + Image(systemName: "calendar.circle") + .resizable() + .scaledToFit() + .frame(minHeight: 28) } + .symbolVariant(viewStyle == .calendar ? .fill : .none) - ToolbarItemGroup(placement: .topBarLeading) { - Button { - switch viewStyle { - case .list: - viewStyle = .calendar - case .calendar: - viewStyle = .list - } - } label: { - Image(systemName: "calendar.circle") - .resizable() - .scaledToFit() - .frame(minHeight: 28) - } - .symbolVariant(viewStyle == .calendar ? .fill : .none) - - Button { - presentFilterView.toggle() - } label: { - Image(systemName: "line.3.horizontal.decrease.circle") - .resizable() - .scaledToFit() - .frame(minHeight: 28) - } - .symbolVariant(federalDataViewModel.areFiltersEnabled() ? .fill : .none) - - _pasteView() + Button { + presentFilterView.toggle() + } label: { + Image(systemName: "line.3.horizontal.decrease.circle") + .resizable() + .scaledToFit() + .frame(minHeight: 28) } + .symbolVariant(federalDataViewModel.areFiltersEnabled() ? .fill : .none) - ToolbarItem(placement: .topBarTrailing) { - Button { - newTournament = Tournament.newEmptyInstance() - - } label: { - Image(systemName: "plus.circle.fill") - .resizable() - .scaledToFit() - .frame(minHeight: 28) + _pasteView() + } + + ToolbarItem(placement: .topBarTrailing) { + Button { + newTournament = Tournament.newEmptyInstance() + + } label: { + Image(systemName: "plus.circle.fill") + .resizable() + .scaledToFit() + .frame(minHeight: 28) + } + } + + if presentToolbar { + if navigation.agendaDestination == .around, federalDataViewModel.searchedFederalTournaments.isEmpty == false { + let filteredSearchedFederalTournaments = federalDataViewModel.filteredSearchedFederalTournaments + + let status : String = filteredSearchedFederalTournaments.count.formatted() + " tournoi" + filteredSearchedFederalTournaments.count.pluralSuffix + + ToolbarItem(placement: .bottomBar) { + VStack { + Text(status) + FooterButtonView("modifier les critères de recherche") { + displaySearchView = true + } + } + .font(.footnote) + } + } else if federalDataViewModel.areFiltersEnabled() { + ToolbarItem(placement: .status) { + Text(federalDataViewModel.filterStatus()) } } } @@ -298,6 +336,8 @@ struct ActivityView: View { _endedEmptyView() case .tenup: _tenupEmptyView() + case .around: + _searchTenupEmptyView() } } @@ -363,6 +403,33 @@ struct ActivityView: View { } } + @ViewBuilder + private func _searchTenupEmptyView() -> some View { + if federalDataViewModel.searchAttemptCount == 0 { + ContentUnavailableView { + Label("Recherche de tournoi", systemImage: "magnifyingglass") + } description: { + Text("Chercher les tournois autour de vous pour vous aidez à mieux selectionner ce que vous pouvez proposer.") + } actions: { + RowButtonView("Lancer la recherche") { + displaySearchView = true + } + .padding() + } + } else { + ContentUnavailableView { + Label("Aucun tournoi", systemImage: "shield.slash") + } description: { + Text("Aucun tournoi ne correspond aux critères sélectionnés.") + } actions: { + RowButtonView("Modifier vos critères de recherche") { + displaySearchView = true + } + .padding() + } + } + } + } //#Preview { diff --git a/PadelClub/Views/Navigation/Agenda/EventListView.swift b/PadelClub/Views/Navigation/Agenda/EventListView.swift index 4c89482..f146b31 100644 --- a/PadelClub/Views/Navigation/Agenda/EventListView.swift +++ b/PadelClub/Views/Navigation/Agenda/EventListView.swift @@ -12,13 +12,13 @@ struct EventListView: View { @EnvironmentObject var dataStore: DataStore @Environment(NavigationViewModel.self) var navigation: NavigationViewModel @Environment(FederalDataViewModel.self) var federalDataViewModel: FederalDataViewModel + @Environment(\.viewStyle) var viewStyle let tournaments: [FederalTournamentHolder] - let viewStyle: AgendaDestination.ViewStyle let sortAscending: Bool var body: some View { - let groupedTournamentsByDate = Dictionary(grouping: navigation.agendaDestination == .tenup ? federalDataViewModel.filteredFederalTournaments : tournaments) { $0.startDate.startOfMonth } + let groupedTournamentsByDate = Dictionary(grouping: federalDataViewModel.filteredFederalTournaments(from: tournaments)) { $0.startDate.startOfMonth } switch viewStyle { case .list: ForEach(groupedTournamentsByDate.keys.sorted(by: sortAscending ? { $0 < $1 } : { $0 > $1 }), id: \.self) { section in diff --git a/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift b/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift new file mode 100644 index 0000000..188f08e --- /dev/null +++ b/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift @@ -0,0 +1,637 @@ +// +// TournamentLookUpView.swift +// PadelClub +// +// Created by razmig on 08/09/2024. +// + +import SwiftUI +import CoreLocation +import CoreLocationUI + +struct TournamentLookUpView: View { + @Environment(FederalDataViewModel.self) var federalDataViewModel: FederalDataViewModel + @StateObject var locationManager = LocationManager() + @Environment(\.dismiss) private var dismiss + + @State private var searchField: String = "" + @State private var sectionedTournaments: [String: [FederalTournament]] = [:] + @State private var dayPeriod: DayPeriod = .all + @State private var duration: Int = 3 + + @State var page: Int = 0 + @State var total: Int = 0 + + @State private var tournamentCategories = Set() + @State private var tournamentLevels = Set() + @State private var tournamentAges = Set() + @State private var tournamentTypes = Set() + @State private var searching: Bool = false + @State private var startDate: Date = Date() + @State private var endDate: Date = Calendar.current.date(byAdding: .month, value: 3, to: Date())! + @AppStorage("lastCity") private var city: String = "" + @State private var ligue: String = "" + @AppStorage("lastDistance") private var distance: Double = 30 + @AppStorage("lastSortingOption") private var sortingOption: String = "_DIST_" + @State private var requestedToGetAllPages: Bool = false + @AppStorage("lastNationalCup") private var nationalCup: Bool = false + @State private var revealSearchParameters: Bool = true + @State private var searchScope = FederalTournamentSearchScope.all + + var tournaments: [FederalTournament] { + federalDataViewModel.searchedFederalTournaments + } + + func canShowTournament(_ tournament: FederalTournament) -> Bool { + guard tournament.dayDuration <= duration else { return false } + guard (tournament.dayPeriod == dayPeriod && dayPeriod != .all) || dayPeriod == .all else { return false } + if searchField.isEmpty { + return true + } else { + return tournament.validForSearch(searchField, scope: searchScope) + } + } + + var body: some View { + List { + searchParametersView + + if tournaments.isEmpty == false && tournaments.count < total && total >= 200 && requestedToGetAllPages == false { + Section { + Text("Il y a beacoup de tournois pour cette requête, êtes-vous sûr de vouloir tout récupérer ? Sinon essayez d'affiner votre recherche.") + Button { + requestedToGetAllPages = true + page += 1 + searching = true + Task { + await getNewPage() + searching = false + buildSectionedData() + } + } label: { + Label("Tout voir", systemImage: "arrow.down.circle") + } + } + } + } + .toolbarBackground(.visible, for: .bottomBar, .navigationBar) + .navigationTitle("Chercher un tournoi") + .navigationBarTitleDisplayMode(.inline) + .onChange(of: locationManager.city, perform: { newValue in + if let newValue, city.isEmpty { + city = newValue + } + }) + .toolbarTitleDisplayMode(.large) + .toolbar { + ToolbarItem(placement: .bottomBar) { + if revealSearchParameters { + FooterButtonView("Lancer la recherche") { + runSearch() + } + .disabled(searching) + } else if searchField.isEmpty == false && searchScope != .all { + let count = _totalVisibleEpreuves().count + VStack { + Text(searchField) + .foregroundStyle(.secondary) + Text(count.formatted() + " tournoi" + count.pluralSuffix) + } + .font(.caption) + } else if searching { + HStack(spacing: 20) { + Spacer() + ProgressView() + if total > 0 { + let percent = Double(tournaments.count) / Double(total) + Text(percent.formatted(.percent.precision(.significantDigits(1...3))) + " en récupération de Tenup") + .font(.caption) + } + Spacer() + } + } else { + let count = _totalVisibleEpreuves().count + Text(count.formatted() + " tournoi" + count.pluralSuffix) + .font(.caption) + } + } + ToolbarItem(placement: .topBarTrailing) { + Menu { + if tournaments.isEmpty == false { + Section { + let preview = SharePreview(Text("Ma recherche de tournois"), icon: Image("PadelClub_logo_fondclair_transparent")) + ShareLink(item: renderedImage ?? Image(systemName: "photo"), preview: preview) { + if renderedImage == nil { + ProgressView() + } else { + Label("Par image (20max)", systemImage: "square.and.arrow.up") + .labelStyle(.titleAndIcon) + } + } + ShareLink(item: pastedTournaments) { + Label("Par texte", systemImage: "square.and.arrow.up") + .labelStyle(.titleAndIcon) + } + ShareLink(item: japList) { + Label("JAP liste", systemImage: "square.and.arrow.up") + .labelStyle(.titleAndIcon) + } + } header: { + Text("Partager les résultats") + } + } + Divider() + Button { + tournamentLevels = Set() + tournamentCategories = Set() + city = "" + locationManager.location = nil + locationManager.city = nil + distance = 30 + startDate = Date() + endDate = Calendar.current.date(byAdding: .month, value: 3, to: Date())! + sortingOption = "_DIST_" + revealSearchParameters = true + } label: { + Text("Ré-initialiser la recherche") + } + } label: { + Label("Options", systemImage: "ellipsis.circle") + } + } + } + } + var pastedTournaments: String { + tournaments.map { $0.shareMessage }.joined() + } + + var japList: String { + Set(tournaments.map { $0.japMessage }).joined(separator: "\n") + } + + private func isTypeLookedAfter(_ type: any TournamentBuildHolder) -> Bool { + if levels.contains(where: { level in + type.level == level + }) || levels.isEmpty { + if categories.contains(where: { category in + type.category == category + }) || categories.isEmpty { + + return true + } + } + return false + } + + + @Environment(\.displayScale) var displayScale + @State private var renderedImage: Image? + + @MainActor + func render() { + let renderer = ImageRenderer(content: tournamentsView) + renderer.scale = displayScale + renderer.isOpaque = true + if let uiImage = renderer.uiImage { + renderedImage = Image(uiImage: uiImage) + } + } + + @ViewBuilder + private var tournamentsView: some View { + let tournaments = tournaments.prefix(20) + VStack { + ForEach(tournaments.indices, id: \.self) { tournamentIndex in + let tournament = tournaments[tournamentIndex] + HStack(alignment: .center) { + VStack(alignment: .leading) { + Text(tournament.libelle ?? "unknown").font(.headline) + if let club = tournament.nomClub { + Text(club) + .font(.footnote) + .lineLimit(1) + } + } + Spacer() + VStack(alignment: .trailing) { + if let startDate = tournament.dateDebut { + Text(startDate.monthYearFormatted) + HStack { + Text(startDate.formatted(.dateTime.weekday())) + Text(startDate.formatted(.dateTime.day())).font(.largeTitle) + } + } + if let distance = tournament.distanceEnMetres { + let measurement = Measurement(value: distance / 1000, unit: UnitLength.kilometers) + Text(measurement.formatted()).font(.caption) + } + } + } + .padding() + .foregroundColor(Color.black) + .background { + tournamentIndex%2 == 0 ? Color.mint : Color.cyan + } + } + } + .padding() + } + + + private var clubsFound: [String] { + Set(tournaments.compactMap { $0.nomClub }).sorted() + } + + private var liguesFound: [String] { + Set(tournaments.compactMap { $0.nomLigue }).sorted() + } + + private func runSearch() { + revealSearchParameters = false + total = 0 + page = 0 + federalDataViewModel.searchedFederalTournaments = [] + searching = true + requestedToGetAllPages = false + renderedImage = nil + federalDataViewModel.searchAttemptCount += 1 + Task { + await getNewPage() + searching = false + dismiss() + } + } + + func buildSectionedData() { + sectionedTournaments = FederalTournament.sectionedData(from: tournaments) + } + + private var distanceLimit: Measurement { + distanceLimit(distance: distance) + } + + private func distanceLimit(distance: Double) -> Measurement { + Measurement(value: distance, unit: .kilometers) + } + + private var categories: [TournamentCategory] { + tournamentCategories.compactMap { TournamentCategory(rawValue: $0) } + } + + private var levels: [TournamentLevel] { + tournamentLevels.compactMap { TournamentLevel(rawValue: $0) } + } + + private var ages: [FederalTournamentAge] { + tournamentAges.compactMap { FederalTournamentAge(rawValue: $0) } + } + + private var types: [FederalTournamentType] { + tournamentTypes.compactMap { FederalTournamentType(rawValue: $0) } + } + + func getNewPage() async { + do { + if NetworkFederalService.shared.formId.isEmpty { + await getNewBuildForm() + } else { + let commands = try await NetworkFederalService.shared.getAllFederalTournaments(sortingOption: sortingOption, page: page, startDate: startDate, endDate: endDate, city: city, distance: distance, categories: categories, levels: levels, lat: locationManager.location?.coordinate.latitude.formatted(.number.locale(Locale(identifier: "us"))), lng: locationManager.location?.coordinate.longitude.formatted(.number.locale(Locale(identifier: "us"))), ages: ages, types: types, nationalCup: nationalCup) + let resultCommand = commands.first(where: { $0.results != nil }) + if let newTournaments = resultCommand?.results?.items { + newTournaments.forEach { ft in + if tournaments.contains(where: { $0.id == ft.id }) == false { + federalDataViewModel.searchedFederalTournaments.append(ft) + } + } + } + if let count = resultCommand?.results?.nb_results { + print("count", count, total, tournaments.count, page) + total = count + + if renderedImage == nil { + render() + } + if tournaments.count < count && page < total / 30 { + if total < 200 || requestedToGetAllPages { + page += 1 + await MainActor.run() { + buildSectionedData() + } + await getNewPage() + } + } else { + print("finished") + } + } else { + print("total results not found") + } + } + } catch { + print("getNewPage", error) + await getNewBuildForm() + } + } + + func getNewBuildForm() async { + do { + try await NetworkFederalService.shared.getNewBuildForm() + await getNewPage() + } catch { + print("getNewBuildForm", error) + } + } + + @ViewBuilder + var searchContollerView: some View { + Section { + Button { + runSearch() + } label: { + HStack { + Label("Chercher un tournoi", systemImage: "magnifyingglass") + if searching { + Spacer() + ProgressView() + } + } + } + Button { + tournamentLevels = Set() + tournamentCategories = Set() + city = "" + locationManager.location = nil + locationManager.city = nil + dayPeriod = .all + duration = 3 + distance = 30 + startDate = Date() + endDate = Calendar.current.date(byAdding: .month, value: 3, to: Date())! + sortingOption = "_DIST_" + revealSearchParameters = true + } label: { + Label("Ré-initialiser la recherche", systemImage: "xmark.circle") + } + } + } + + @ViewBuilder + var searchParametersView: some View { + Section { + DatePicker("Début", selection: $startDate, displayedComponents: .date) + DatePicker("Fin", selection: $endDate, displayedComponents: .date) + + Picker(selection: $duration) { + Text("Aucune").tag(7) + Text(1.formatted()).tag(1) + Text(2.formatted()).tag(2) + Text(3.formatted()).tag(3) + } label: { + Text("Durée max (en jours)") + } + + Picker(selection: $dayPeriod) { + Text("N'importe").tag(DayPeriod.all) + Text("le weekend").tag(DayPeriod.weekend) + Text("la semaine").tag(DayPeriod.week) + } label: { + Text("En semaine ou week-end") + } + + HStack { + TextField("Ville", text: $city) + if let city = locationManager.city { + Divider() + Text(city).italic() + } + if locationManager.requestStarted { + ProgressView() + } else { + LocationButton { + locationManager.requestLocation() + } + .symbolVariant(.fill) + .foregroundColor (Color.white) + .cornerRadius (20) + .font(.system(size: 12)) + } + } + + Picker(selection: $distance) { + Text(distanceLimit(distance:30).formatted()).tag(30.0) + Text(distanceLimit(distance:50).formatted()).tag(50.0) + Text(distanceLimit(distance:60).formatted()).tag(60.0) + Text(distanceLimit(distance:90).formatted()).tag(90.0) + Text(distanceLimit(distance:150).formatted()).tag(150.0) + Text(distanceLimit(distance:200).formatted()).tag(200.0) + Text(distanceLimit(distance:400).formatted()).tag(400.0) + Text("Aucune").tag(3000.0) + } label: { + Text("Distance max") + } + + Picker(selection: $sortingOption) { + Text("Distance").tag("_DIST_") + Text("Date de début").tag("dateDebut+asc") + Text("Date de fin").tag("dateFin+asc") + } label: { + Text("Trier par") + } + + NavigationLink { + List(TournamentCategory.allCases, selection: $tournamentCategories) { type in + Text(type.localizedLabel()) + } + .navigationTitle("Catégories") + .environment(\.editMode, Binding.constant(EditMode.active)) + } label: { + HStack { + Text("Catégorie") + Spacer() + categoriesLabel + .foregroundStyle(.secondary) + } + } + + NavigationLink { + List(TournamentLevel.allCases, selection: $tournamentLevels) { type in + Text(type.localizedLabel()) + } + .navigationTitle("Niveaux") + .environment(\.editMode, Binding.constant(EditMode.active)) + } label: { + HStack { + Text("Niveau") + Spacer() + levelsLabel + .foregroundStyle(.secondary) + } + } + + NavigationLink { + List(FederalTournamentAge.allCases, selection: $tournamentAges) { type in + Text(type.localizedLabel()) + } + .navigationTitle("Limites d'âge") + .environment(\.editMode, Binding.constant(EditMode.active)) + } label: { + HStack { + Text("Limite d'âge") + Spacer() + if tournamentAges.isEmpty || tournamentAges.count == FederalTournamentAge.allCases.count { + Text("Tous les âges") + .foregroundStyle(.secondary) + } else { + Text(ages.map({ $0.localizedLabel()}).joined(separator: ", ")) + .foregroundStyle(.secondary) + } + } + } + + NavigationLink { + List(FederalTournamentType.allCases, selection: $tournamentTypes) { type in + Text(type.localizedLabel()) + } + .navigationTitle("Types de tournoi") + .environment(\.editMode, Binding.constant(EditMode.active)) + } label: { + HStack { + Text("Type de tournoi") + Spacer() + if tournamentTypes.isEmpty || tournamentTypes.count == FederalTournamentType.allCases.count { + Text("Tous les types") + .foregroundStyle(.secondary) + } else { + Text(types.map({ $0.localizedLabel()}).joined(separator: ", ")) + .foregroundStyle(.secondary) + } + } + } + + Picker(selection: $nationalCup) { + Text("N'importe").tag(false) + Text("Uniquement").tag(true) + } label: { + Text("Circuit National Padel Cup") + } + } header: { + Text("Critères de recherche") + } + .headerProminence(.increased) + .disabled(searching) + } + + var categoriesLabel: some View { + if tournamentCategories.isEmpty || tournamentCategories.count == TournamentCategory.allCases.count { + Text("Toutes les catégories") + } else { + Text(categories.map({ $0.localizedLabel() }).joined(separator: ", ")) + } + } + + var levelsLabel: some View { + if tournamentLevels.isEmpty || tournamentLevels.count == TournamentLevel.allCases.count { + Text("Tous les niveaux") + } else { + Text(levels.map({ $0.localizedLabel() }).joined(separator: ", ")) + } + } + + @ViewBuilder + var searchParametersSummaryView: some View { + VStack(alignment: .leading) { + HStack { + Text("Lieu") + Spacer() + Text(city) + if distance >= 3000 { + Text("sans limite de distance") + } else { + Text("à moins de " + distanceLimit.formatted()) + } + } + HStack { + Text("Période") + Spacer() + Text("Du") + Text(startDate.twoDigitsYearFormatted) + Text("Au") + Text(endDate.twoDigitsYearFormatted) + } + HStack { + Text("Niveau") + Spacer() + levelsLabel + } + HStack { + Text("Catégorie") + Spacer() + categoriesLabel + } + + HStack { + Text("Tri") + Spacer() + Text(sortingOptionLabel) + } + } + } + + var sortingOptionLabel: String { + switch sortingOption { + case "_DIST_": return "Distance" + case "dateDebut+asc": return "Date de début" + case "dateFin+asc": return "Date de fin" + default: + return "Distance" + } + } + + + func _totalVisibleTournaments(_ date: Date? = nil) -> [FederalTournament] { + if let date { + if let tournaments = sectionedTournaments[URL.importDateFormatter.string(from: date)] { + let allTournaments = tournaments.filter({ canShowTournament($0) }).filter({ tournament in + if tournament.tournaments.count > 1 { + return tournament.tournaments.anySatisfy { isTypeLookedAfter($0) } + } else { + return true + } + }) + return allTournaments + } else { + return [] + } + } else { + let allTournaments = sectionedTournaments.values.flatMap({ $0 }).filter({ canShowTournament($0) }).filter({ tournament in + if tournament.tournaments.count > 1 { + return tournament.tournaments.anySatisfy { isTypeLookedAfter($0) } + } else { + return true + } + }) + return allTournaments + } + } + + func _totalVisibleEpreuves(_ date: Date? = nil) -> [any TournamentBuildHolder] { + if let date { + if let tournaments = sectionedTournaments[URL.importDateFormatter.string(from: date)] { + let allTournaments = tournaments + .filter({ canShowTournament($0) }) + .compactMap({ $0.tournaments }) + .flatMap({ $0 }) + .filter({ isTypeLookedAfter($0) }) + return allTournaments + } else { + return [] + } + } else { + let allTournaments = sectionedTournaments.values.flatMap({ $0 }) + .filter({ canShowTournament($0) }) + .compactMap({ $0.tournaments }) + .flatMap({ $0 }) + .filter({ isTypeLookedAfter($0) }) + return allTournaments + } + } +} diff --git a/PadelClub/Views/Shared/TournamentFilterView.swift b/PadelClub/Views/Shared/TournamentFilterView.swift index d9bcb65..e72377c 100644 --- a/PadelClub/Views/Shared/TournamentFilterView.swift +++ b/PadelClub/Views/Shared/TournamentFilterView.swift @@ -9,12 +9,13 @@ import SwiftUI struct TournamentFilterView: View { @EnvironmentObject var dataStore: DataStore + @Environment(NavigationViewModel.self) private var navigation @Environment(\.dismiss) private var dismiss @State private var levels: Set @State private var categories: Set @State private var ageCategories: Set @State private var selectedClubs: Set - var federalDataViewModel: FederalDataViewModel + @State private var federalDataViewModel: FederalDataViewModel init(federalDataViewModel: FederalDataViewModel) { self.federalDataViewModel = federalDataViewModel @@ -27,30 +28,6 @@ struct TournamentFilterView: View { var body: some View { NavigationView { Form { - let clubs : [Club] = dataStore.user.clubsObjects() - if clubs.filter({ $0.code != nil }).isEmpty == false { - Section { - ForEach(clubs.filter({ $0.code != nil })) { club in - LabeledContent { - Button { - if selectedClubs.contains(club.code!) { - selectedClubs.remove(club.code!) - } else { - selectedClubs.insert(club.code!) - } - } label: { - if selectedClubs.contains(club.code!) { - Image(systemName: "checkmark.circle.fill") - } - } - } label: { - Text(club.clubTitle()) - } - } - } header: { - Text("Clubs") - } - } Section { ForEach(TournamentLevel.allCases) { level in LabeledContent { @@ -116,6 +93,58 @@ struct TournamentFilterView: View { } header: { Text("Catégories d'âge") } + + if navigation.agendaDestination == .around { + let clubs : [FederalClub] = federalDataViewModel.searchedClubs + if clubs.isEmpty == false { + Section { + ForEach(clubs) { club in + LabeledContent { + Button { + if selectedClubs.contains(club.federalClubCode) { + selectedClubs.remove(club.federalClubCode) + } else { + selectedClubs.insert(club.federalClubCode) + } + } label: { + if selectedClubs.contains(club.federalClubCode) { + Image(systemName: "checkmark.circle.fill") + } + } + } label: { + Text(club.federalClubName) + } + } + } header: { + Text("Clubs") + } + } + } else { + let clubs : [Club] = dataStore.user.clubsObjects().filter({ $0.code != nil }) + if clubs.isEmpty == false { + Section { + ForEach(clubs) { club in + LabeledContent { + Button { + if selectedClubs.contains(club.code!) { + selectedClubs.remove(club.code!) + } else { + selectedClubs.insert(club.code!) + } + } label: { + if selectedClubs.contains(club.code!) { + Image(systemName: "checkmark.circle.fill") + } + } + } label: { + Text(club.clubTitle()) + } + } + } header: { + Text("Clubs") + } + } + } } .toolbar { ToolbarItem(placement: .topBarLeading) { From 8e301a4f2418b0c43edb699103123c22d94f1ad4 Mon Sep 17 00:00:00 2001 From: Raz Date: Mon, 9 Sep 2024 13:30:11 +0200 Subject: [PATCH 10/23] update tournament search feature --- .../Data/Federal/FederalTournament.swift | 15 +- .../Federal/FederalTournamentHolder.swift | 1 + PadelClub/Data/Tournament.swift | 10 + .../ViewModel/FederalDataViewModel.swift | 28 +- .../GenericDestinationPickerView.swift | 3 + .../Navigation/Agenda/ActivityView.swift | 352 ++++++++++-------- .../Agenda/TournamentLookUpView.swift | 256 +++---------- .../Views/Shared/TournamentFilterView.swift | 20 + 8 files changed, 319 insertions(+), 366 deletions(-) diff --git a/PadelClub/Data/Federal/FederalTournament.swift b/PadelClub/Data/Federal/FederalTournament.swift index bd7e342..0cce922 100644 --- a/PadelClub/Data/Federal/FederalTournament.swift +++ b/PadelClub/Data/Federal/FederalTournament.swift @@ -7,10 +7,23 @@ import Foundation import CoreLocation import LeStorage -enum DayPeriod { +enum DayPeriod: CaseIterable, Identifiable { + var id: Self { self } + case all case weekend case week + + func localizedDayPeriodLabel() -> String { + switch self { + case .all: + return "n'importe" + case .week: + return "la semaine" + case .weekend: + return "le week-end" + } + } } // MARK: - FederalTournament diff --git a/PadelClub/Data/Federal/FederalTournamentHolder.swift b/PadelClub/Data/Federal/FederalTournamentHolder.swift index c5181a2..b2e3890 100644 --- a/PadelClub/Data/Federal/FederalTournamentHolder.swift +++ b/PadelClub/Data/Federal/FederalTournamentHolder.swift @@ -16,6 +16,7 @@ protocol FederalTournamentHolder { func clubLabel() -> String func subtitleLabel() -> String var dayDuration: Int { get } + var dayPeriod: DayPeriod { get } } extension FederalTournamentHolder { diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index a3aa857..abb008c 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -2134,6 +2134,16 @@ extension Tournament: FederalTournamentHolder { self ] } + + var dayPeriod: DayPeriod { + let day = startDate.get(.weekday) + switch day { + case 2...6: + return .week + default: + return .weekend + } + } } extension Tournament: TournamentBuildHolder { diff --git a/PadelClub/ViewModel/FederalDataViewModel.swift b/PadelClub/ViewModel/FederalDataViewModel.swift index 0c3b349..e83dc5b 100644 --- a/PadelClub/ViewModel/FederalDataViewModel.swift +++ b/PadelClub/ViewModel/FederalDataViewModel.swift @@ -20,7 +20,9 @@ class FederalDataViewModel { var selectedClubs: Set = Set() var id: UUID = UUID() var searchAttemptCount: Int = 0 - + var dayDuration: Int? + var dayPeriod: DayPeriod = .all + func filterStatus() -> String { var labels: [String] = [] labels.append(contentsOf: levels.map { $0.localizedLabel() }) @@ -32,6 +34,12 @@ class FederalDataViewModel { } labels.append(contentsOf: clubNames) + if dayPeriod != .all { + labels.append(dayPeriod.localizedDayPeriodLabel()) + } + if let dayDuration { + labels.append("max " + dayDuration.formatted() + " jour" + dayDuration.pluralSuffix) + } return labels.joined(separator: ", ") } @@ -56,11 +64,13 @@ class FederalDataViewModel { categories.removeAll() ageCategories.removeAll() selectedClubs.removeAll() + dayPeriod = .all + dayDuration = nil id = UUID() } func areFiltersEnabled() -> Bool { - (levels.isEmpty && categories.isEmpty && ageCategories.isEmpty && selectedClubs.isEmpty) == false + (levels.isEmpty && categories.isEmpty && ageCategories.isEmpty && selectedClubs.isEmpty && dayPeriod == .all && dayDuration == nil) == false } var filteredFederalTournaments: [FederalTournamentHolder] { @@ -80,6 +90,10 @@ class FederalDataViewModel { (ageCategories.isEmpty || tournament.tournaments.anySatisfy({ ageCategories.contains($0.age) })) && (selectedClubs.isEmpty || selectedClubs.contains(tournament.codeClub!)) + && + (dayPeriod == .all || (dayPeriod != .all && dayPeriod == tournament.dayPeriod)) + && + (dayDuration == nil || (dayDuration != nil && dayDuration == tournament.dayDuration)) }) } @@ -90,7 +104,11 @@ class FederalDataViewModel { (categories.isEmpty || categories.contains(tournament.category)) && (ageCategories.isEmpty || ageCategories.contains(tournament.age)) - + && + (dayPeriod == .all || (dayPeriod != .all && dayPeriod == tournament.dayPeriod)) + && + (dayDuration == nil || (dayDuration != nil && dayDuration == tournament.dayDuration)) + if let codeClub = tournament.club()?.code { return firstPart && (selectedClubs.isEmpty || selectedClubs.contains(codeClub)) } else { @@ -106,6 +124,10 @@ class FederalDataViewModel { (ageCategories.isEmpty || ageCategories.contains(build.age)) && (selectedClubs.isEmpty || selectedClubs.contains(tournament.codeClub!)) + && + (dayPeriod == .all || (dayPeriod != .all && dayPeriod == tournament.dayPeriod)) + && + (dayDuration == nil || (dayDuration != nil && dayDuration == tournament.dayDuration)) } func gatherTournaments(clubs: [Club], startDate: Date, endDate: Date? = nil) async throws { diff --git a/PadelClub/Views/Components/GenericDestinationPickerView.swift b/PadelClub/Views/Components/GenericDestinationPickerView.swift index 7002dc9..a7f4871 100644 --- a/PadelClub/Views/Components/GenericDestinationPickerView.swift +++ b/PadelClub/Views/Components/GenericDestinationPickerView.swift @@ -23,6 +23,7 @@ struct GenericDestinationPickerView: } label: { Image(systemName: "wrench.and.screwdriver") .foregroundColor(selectedDestination == nil ? .white : .black) + .contentShape(Capsule()) } .padding() .background { @@ -41,9 +42,11 @@ struct GenericDestinationPickerView: if let systemImage = destination.systemImage() { Image(systemName: systemImage) .foregroundStyle(selectedDestination?.id == destination.id ? .white : .black) + .contentShape(Capsule()) } else { Text(destination.selectionLabel(index: index)) .foregroundStyle(selectedDestination?.id == destination.id ? .white : .black) + .contentShape(Capsule()) } } .padding() diff --git a/PadelClub/Views/Navigation/Agenda/ActivityView.swift b/PadelClub/Views/Navigation/Agenda/ActivityView.swift index 41d96d6..e6085f7 100644 --- a/PadelClub/Views/Navigation/Agenda/ActivityView.swift +++ b/PadelClub/Views/Navigation/Agenda/ActivityView.swift @@ -84,39 +84,26 @@ struct ActivityView: View { .buttonBorderShape(.capsule) } - @ViewBuilder - func _listView() -> some View { - switch navigation.agendaDestination! { - case .activity: - List { - EventListView(tournaments: runningTournaments, sortAscending: true) - } - case .history: - List { - EventListView(tournaments: endedTournaments, sortAscending: false) - } - case .tenup: - List { - EventListView(tournaments: federalDataViewModel.federalTournaments, sortAscending: true) - .id(uuid) - } - case .around: - List { - EventListView(tournaments: federalDataViewModel.searchedFederalTournaments, sortAscending: true) - .id(uuid) - } - } - } - - var body: some View { @Bindable var navigation = navigation NavigationStack(path: $navigation.path) { VStack(spacing: 0) { GenericDestinationPickerView(selectedDestination: $navigation.agendaDestination, destinations: AgendaDestination.allCases, nilDestinationIsValid: false) - _listView() - .environment(\.viewStyle, viewStyle) + List { + switch navigation.agendaDestination! { + case .activity: + EventListView(tournaments: runningTournaments, sortAscending: true) + case .history: + EventListView(tournaments: endedTournaments, sortAscending: false) + case .tenup: + EventListView(tournaments: federalDataViewModel.federalTournaments, sortAscending: true) + .id(uuid) + case .around: + EventListView(tournaments: federalDataViewModel.searchedFederalTournaments, sortAscending: true) + } + } + .environment(\.viewStyle, viewStyle) .environment(federalDataViewModel) .overlay { if let error, navigation.agendaDestination == .tenup { @@ -142,10 +129,14 @@ struct ActivityView: View { } description: { Text("Aucun tournoi ne correspond aux fitres que vous avez choisis : \(federalDataViewModel.filterStatus())") } actions: { - RowButtonView("modifier vos filtres") { + FooterButtonView("supprimer vos filtres") { federalDataViewModel.removeFilters() } .padding(.horizontal) + FooterButtonView("modifier vos filtres") { + presentFilterView = true + } + .padding(.horizontal) } } else { _dataEmptyView() @@ -153,166 +144,213 @@ struct ActivityView: View { } } } - //.searchable(text: $searchText) - .onAppear { presentToolbar = true } - .onDisappear { presentToolbar = false } - .sheet(isPresented: $displaySearchView) { - NavigationStack { - TournamentLookUpView() - .environment(federalDataViewModel) - } + } + //.searchable(text: $searchText) + .onAppear { presentToolbar = true } + .onDisappear { presentToolbar = false } + .refreshable { + if navigation.agendaDestination == .tenup { + federalDataViewModel.federalTournaments.removeAll() + NetworkFederalService.shared.formId = "" + _gatherFederalTournaments() } - .sheet(item: $newTournament) { tournament in - EventCreationView(tournaments: [tournament], selectedClub: federalDataViewModel.selectedClub()) - .environment(navigation) - .tint(.master) + } + .task { + if navigation.agendaDestination == .tenup + && dataStore.user.hasTenupClubs() == true + && federalDataViewModel.federalTournaments.isEmpty { + _gatherFederalTournaments() } - .refreshable { - if navigation.agendaDestination == .tenup { - federalDataViewModel.federalTournaments.removeAll() - NetworkFederalService.shared.formId = "" - _gatherFederalTournaments() - } + } + .onChange(of: navigation.agendaDestination) { + if tournaments.isEmpty, viewStyle == .calendar { + viewStyle = .list } - .task { - if navigation.agendaDestination == .tenup - && dataStore.user.hasTenupClubs() == true - && federalDataViewModel.federalTournaments.isEmpty { - _gatherFederalTournaments() - } + + if navigation.agendaDestination == .tenup + && dataStore.user.hasTenupClubs() == true + && federalDataViewModel.federalTournaments.isEmpty { + _gatherFederalTournaments() } - .onChange(of: navigation.agendaDestination) { - if navigation.agendaDestination == .tenup - && dataStore.user.hasTenupClubs() == true - && federalDataViewModel.federalTournaments.isEmpty { - _gatherFederalTournaments() + } + .onChange(of: presentFilterView, { old, new in + if old == true, new == false { //closing filter view + if tournaments.isEmpty, viewStyle == .calendar { + viewStyle = .list } } - .toolbar { - ToolbarItemGroup(placement: .topBarLeading) { - Button { - switch viewStyle { - case .list: - viewStyle = .calendar - case .calendar: - viewStyle = .list - } - } label: { - Image(systemName: "calendar.circle") - .resizable() - .scaledToFit() - .frame(minHeight: 28) - } - .symbolVariant(viewStyle == .calendar ? .fill : .none) - - Button { - presentFilterView.toggle() - } label: { - Image(systemName: "line.3.horizontal.decrease.circle") - .resizable() - .scaledToFit() - .frame(minHeight: 28) + }) + .toolbarTitleDisplayMode(.large) + .navigationTitle(TabDestination.activity.title) + .navigationDestination(for: Tournament.self) { tournament in + TournamentView(tournament: tournament) + } + .toolbar { + ToolbarItemGroup(placement: .topBarLeading) { + Button { + switch viewStyle { + case .list: + viewStyle = .calendar + case .calendar: + viewStyle = .list } - .symbolVariant(federalDataViewModel.areFiltersEnabled() ? .fill : .none) - - _pasteView() + } label: { + Image(systemName: "calendar.circle") + .resizable() + .scaledToFit() + .frame(minHeight: 32) } - - ToolbarItem(placement: .topBarTrailing) { - Button { - newTournament = Tournament.newEmptyInstance() - - } label: { - Image(systemName: "plus.circle.fill") - .resizable() - .scaledToFit() - .frame(minHeight: 28) - } + .symbolVariant(viewStyle == .calendar ? .fill : .none) + + Button { + presentFilterView.toggle() + } label: { + Image(systemName: "line.3.horizontal.decrease.circle") + .resizable() + .scaledToFit() + .frame(minHeight: 32) } + .symbolVariant(federalDataViewModel.areFiltersEnabled() ? .fill : .none) - if presentToolbar { - if navigation.agendaDestination == .around, federalDataViewModel.searchedFederalTournaments.isEmpty == false { - let filteredSearchedFederalTournaments = federalDataViewModel.filteredSearchedFederalTournaments - - let status : String = filteredSearchedFederalTournaments.count.formatted() + " tournoi" + filteredSearchedFederalTournaments.count.pluralSuffix - - ToolbarItem(placement: .bottomBar) { - VStack { - Text(status) - FooterButtonView("modifier les critères de recherche") { + _pasteView() + } + + ToolbarItem(placement: .topBarTrailing) { + Button { + newTournament = Tournament.newEmptyInstance() + + } label: { + Image(systemName: "plus.circle.fill") + .resizable() + .scaledToFit() + .frame(minHeight: 32) + } + } + + if presentToolbar, tournaments.isEmpty == false { + ToolbarItemGroup(placement: .bottomBar) { + VStack(spacing: 0) { + let searchStatus = _searchStatus() + if searchStatus.isEmpty == false { + Text(_searchStatus()) + .font(.footnote) + .foregroundStyle(.secondary) + } + + HStack { + if navigation.agendaDestination == .around { + FooterButtonView("modifier votre recherche") { displaySearchView = true } + + if federalDataViewModel.areFiltersEnabled() { + Text("ou") + } + } + + if federalDataViewModel.areFiltersEnabled() { + FooterButtonView(_filterButtonTitle()) { + presentFilterView = true + } + } - .font(.footnote) - } - } else if federalDataViewModel.areFiltersEnabled() { - ToolbarItem(placement: .status) { - Text(federalDataViewModel.filterStatus()) } + .padding(.bottom, 8) } } } - .navigationTitle(TabDestination.activity.title) - .navigationDestination(for: Tournament.self) { tournament in - TournamentView(tournament: tournament) - } - .sheet(isPresented: $presentFilterView) { - TournamentFilterView(federalDataViewModel: federalDataViewModel) - .environment(navigation) - .tint(.master) - } - .sheet(isPresented: $presentClubSearchView, onDismiss: { - if dataStore.user.hasTenupClubs() == true { - federalDataViewModel.federalTournaments.removeAll() - navigation.agendaDestination = .tenup - } - }) { - ClubImportView() - .tint(.master) + } + .sheet(isPresented: $presentFilterView) { + TournamentFilterView(federalDataViewModel: federalDataViewModel) + .environment(navigation) + .tint(.master) + } + .sheet(isPresented: $presentClubSearchView, onDismiss: { + if dataStore.user.hasTenupClubs() == true { + federalDataViewModel.federalTournaments.removeAll() + navigation.agendaDestination = .tenup } + }) { + ClubImportView() + .tint(.master) } - } - .sheet(item: $quickAccessScreen) { screen in - switch screen { - case .inscription(let pasteString): + .sheet(isPresented: $displaySearchView) { NavigationStack { - List { - Section { - Text(pasteString) - } header: { - Text("Contenu du presse-papier") - } - - Section { - ForEach(getRunningTournaments()) { tournament in - NavigationLink { - AddTeamView(tournament: tournament, pasteString: pasteString, editedTeam: nil) - } label: { - VStack(alignment: .leading) { - Text(tournament.tournamentTitle()) - Text(tournament.formattedDate()).foregroundStyle(.secondary) + TournamentLookUpView() + .environment(federalDataViewModel) + } + } + .sheet(item: $newTournament) { tournament in + EventCreationView(tournaments: [tournament], selectedClub: federalDataViewModel.selectedClub()) + .environment(navigation) + .tint(.master) + } + .sheet(item: $quickAccessScreen) { screen in + switch screen { + case .inscription(let pasteString): + NavigationStack { + List { + Section { + Text(pasteString) + } header: { + Text("Contenu du presse-papier") + } + + Section { + ForEach(getRunningTournaments()) { tournament in + NavigationLink { + AddTeamView(tournament: tournament, pasteString: pasteString, editedTeam: nil) + } label: { + VStack(alignment: .leading) { + Text(tournament.tournamentTitle()) + Text(tournament.formattedDate()).foregroundStyle(.secondary) + } } } + } header: { + Text("À coller dans la liste d'inscription") } - } header: { - Text("À coller dans la liste d'inscription") } - } - .toolbar { - ToolbarItem(placement: .topBarLeading) { - Button("Fermer") { - self.quickAccessScreen = nil + .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button("Fermer") { + self.quickAccessScreen = nil + } } } + .navigationTitle("Choix du tournoi") + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) } - .navigationTitle("Choix du tournoi") - .navigationBarTitleDisplayMode(.inline) - .toolbarBackground(.visible, for: .navigationBar) } } } } + private func _searchStatus() -> String { + var searchStatus : [String] = [] + if navigation.agendaDestination == .around, federalDataViewModel.searchedFederalTournaments.isEmpty == false { + let filteredSearchedFederalTournaments = federalDataViewModel.filteredSearchedFederalTournaments + + let status : String = filteredSearchedFederalTournaments.count.formatted() + " tournoi" + filteredSearchedFederalTournaments.count.pluralSuffix + searchStatus.append(status) + } + + if federalDataViewModel.areFiltersEnabled(), tournaments.isEmpty == false { + searchStatus.append(federalDataViewModel.filterStatus()) + } + + return searchStatus.joined(separator: " ") + } + + private func _filterButtonTitle() -> String { + var prefix = "modifier " + if navigation.agendaDestination == .around, federalDataViewModel.searchedFederalTournaments.isEmpty == false { + prefix = "" + } + return prefix + "vos filtres" + } + private func _gatherFederalTournaments() { isGatheringFederalTournaments = true Task { @@ -422,7 +460,7 @@ struct ActivityView: View { } description: { Text("Aucun tournoi ne correspond aux critères sélectionnés.") } actions: { - RowButtonView("Modifier vos critères de recherche") { + FooterButtonView("modifier vos critères de recherche") { displaySearchView = true } .padding() diff --git a/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift b/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift index 188f08e..f622231 100644 --- a/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift +++ b/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift @@ -15,10 +15,6 @@ struct TournamentLookUpView: View { @Environment(\.dismiss) private var dismiss @State private var searchField: String = "" - @State private var sectionedTournaments: [String: [FederalTournament]] = [:] - @State private var dayPeriod: DayPeriod = .all - @State private var duration: Int = 3 - @State var page: Int = 0 @State var total: Int = 0 @@ -32,56 +28,49 @@ struct TournamentLookUpView: View { @AppStorage("lastCity") private var city: String = "" @State private var ligue: String = "" @AppStorage("lastDistance") private var distance: Double = 30 - @AppStorage("lastSortingOption") private var sortingOption: String = "_DIST_" + @AppStorage("lastSortingOption") private var sortingOption: String = "dateDebut+asc" @State private var requestedToGetAllPages: Bool = false @AppStorage("lastNationalCup") private var nationalCup: Bool = false @State private var revealSearchParameters: Bool = true - @State private var searchScope = FederalTournamentSearchScope.all + @State private var presentAlert: Bool = false var tournaments: [FederalTournament] { federalDataViewModel.searchedFederalTournaments } - - func canShowTournament(_ tournament: FederalTournament) -> Bool { - guard tournament.dayDuration <= duration else { return false } - guard (tournament.dayPeriod == dayPeriod && dayPeriod != .all) || dayPeriod == .all else { return false } - if searchField.isEmpty { - return true - } else { - return tournament.validForSearch(searchField, scope: searchScope) - } - } - + var body: some View { List { searchParametersView - - if tournaments.isEmpty == false && tournaments.count < total && total >= 200 && requestedToGetAllPages == false { - Section { - Text("Il y a beacoup de tournois pour cette requête, êtes-vous sûr de vouloir tout récupérer ? Sinon essayez d'affiner votre recherche.") - Button { - requestedToGetAllPages = true - page += 1 - searching = true - Task { - await getNewPage() - searching = false - buildSectionedData() - } - } label: { - Label("Tout voir", systemImage: "arrow.down.circle") - } + } + .alert("Attention", isPresented: $presentAlert, actions: { + Button { + presentAlert = false + requestedToGetAllPages = true + page += 1 + searching = true + Task { + await getNewPage() + searching = false + dismiss() } + } label: { + Label("Tout voir", systemImage: "arrow.down.circle") } - } + Button("Annuler") { + revealSearchParameters = true + presentAlert = false + } + }, message: { + Text("Il y a beacoup de tournois pour cette requête, êtes-vous sûr de vouloir tout récupérer ? Sinon essayez d'affiner votre recherche.") + }) .toolbarBackground(.visible, for: .bottomBar, .navigationBar) .navigationTitle("Chercher un tournoi") .navigationBarTitleDisplayMode(.inline) - .onChange(of: locationManager.city, perform: { newValue in - if let newValue, city.isEmpty { + .onChange(of: locationManager.city) { + if let newValue = locationManager.city, city.isEmpty { city = newValue } - }) + } .toolbarTitleDisplayMode(.large) .toolbar { ToolbarItem(placement: .bottomBar) { @@ -90,14 +79,6 @@ struct TournamentLookUpView: View { runSearch() } .disabled(searching) - } else if searchField.isEmpty == false && searchScope != .all { - let count = _totalVisibleEpreuves().count - VStack { - Text(searchField) - .foregroundStyle(.secondary) - Text(count.formatted() + " tournoi" + count.pluralSuffix) - } - .font(.caption) } else if searching { HStack(spacing: 20) { Spacer() @@ -109,25 +90,13 @@ struct TournamentLookUpView: View { } Spacer() } - } else { - let count = _totalVisibleEpreuves().count - Text(count.formatted() + " tournoi" + count.pluralSuffix) - .font(.caption) } } ToolbarItem(placement: .topBarTrailing) { Menu { +#if DEBUG if tournaments.isEmpty == false { Section { - let preview = SharePreview(Text("Ma recherche de tournois"), icon: Image("PadelClub_logo_fondclair_transparent")) - ShareLink(item: renderedImage ?? Image(systemName: "photo"), preview: preview) { - if renderedImage == nil { - ProgressView() - } else { - Label("Par image (20max)", systemImage: "square.and.arrow.up") - .labelStyle(.titleAndIcon) - } - } ShareLink(item: pastedTournaments) { Label("Par texte", systemImage: "square.and.arrow.up") .labelStyle(.titleAndIcon) @@ -141,7 +110,9 @@ struct TournamentLookUpView: View { } } Divider() - Button { +#endif + + Button(role: .destructive) { tournamentLevels = Set() tournamentCategories = Set() city = "" @@ -150,8 +121,10 @@ struct TournamentLookUpView: View { distance = 30 startDate = Date() endDate = Calendar.current.date(byAdding: .month, value: 3, to: Date())! - sortingOption = "_DIST_" + sortingOption = "dateDebut+asc" revealSearchParameters = true + federalDataViewModel.searchedFederalTournaments = [] + federalDataViewModel.searchAttemptCount = 0 } label: { Text("Ré-initialiser la recherche") } @@ -169,75 +142,6 @@ struct TournamentLookUpView: View { Set(tournaments.map { $0.japMessage }).joined(separator: "\n") } - private func isTypeLookedAfter(_ type: any TournamentBuildHolder) -> Bool { - if levels.contains(where: { level in - type.level == level - }) || levels.isEmpty { - if categories.contains(where: { category in - type.category == category - }) || categories.isEmpty { - - return true - } - } - return false - } - - - @Environment(\.displayScale) var displayScale - @State private var renderedImage: Image? - - @MainActor - func render() { - let renderer = ImageRenderer(content: tournamentsView) - renderer.scale = displayScale - renderer.isOpaque = true - if let uiImage = renderer.uiImage { - renderedImage = Image(uiImage: uiImage) - } - } - - @ViewBuilder - private var tournamentsView: some View { - let tournaments = tournaments.prefix(20) - VStack { - ForEach(tournaments.indices, id: \.self) { tournamentIndex in - let tournament = tournaments[tournamentIndex] - HStack(alignment: .center) { - VStack(alignment: .leading) { - Text(tournament.libelle ?? "unknown").font(.headline) - if let club = tournament.nomClub { - Text(club) - .font(.footnote) - .lineLimit(1) - } - } - Spacer() - VStack(alignment: .trailing) { - if let startDate = tournament.dateDebut { - Text(startDate.monthYearFormatted) - HStack { - Text(startDate.formatted(.dateTime.weekday())) - Text(startDate.formatted(.dateTime.day())).font(.largeTitle) - } - } - if let distance = tournament.distanceEnMetres { - let measurement = Measurement(value: distance / 1000, unit: UnitLength.kilometers) - Text(measurement.formatted()).font(.caption) - } - } - } - .padding() - .foregroundColor(Color.black) - .background { - tournamentIndex%2 == 0 ? Color.mint : Color.cyan - } - } - } - .padding() - } - - private var clubsFound: [String] { Set(tournaments.compactMap { $0.nomClub }).sorted() } @@ -253,18 +157,17 @@ struct TournamentLookUpView: View { federalDataViewModel.searchedFederalTournaments = [] searching = true requestedToGetAllPages = false - renderedImage = nil federalDataViewModel.searchAttemptCount += 1 Task { await getNewPage() searching = false - dismiss() + if tournaments.isEmpty == false && tournaments.count < total && total >= 200 && requestedToGetAllPages == false { + presentAlert = true + } else { + dismiss() + } } } - - func buildSectionedData() { - sectionedTournaments = FederalTournament.sectionedData(from: tournaments) - } private var distanceLimit: Measurement { distanceLimit(distance: distance) @@ -308,15 +211,9 @@ struct TournamentLookUpView: View { print("count", count, total, tournaments.count, page) total = count - if renderedImage == nil { - render() - } if tournaments.count < count && page < total / 30 { if total < 200 || requestedToGetAllPages { page += 1 - await MainActor.run() { - buildSectionedData() - } await getNewPage() } } else { @@ -361,12 +258,10 @@ struct TournamentLookUpView: View { city = "" locationManager.location = nil locationManager.city = nil - dayPeriod = .all - duration = 3 distance = 30 startDate = Date() endDate = Calendar.current.date(byAdding: .month, value: 3, to: Date())! - sortingOption = "_DIST_" + sortingOption = "dateDebut+asc" revealSearchParameters = true } label: { Label("Ré-initialiser la recherche", systemImage: "xmark.circle") @@ -376,27 +271,27 @@ struct TournamentLookUpView: View { @ViewBuilder var searchParametersView: some View { + @Bindable var federalDataViewModel = federalDataViewModel Section { DatePicker("Début", selection: $startDate, displayedComponents: .date) DatePicker("Fin", selection: $endDate, displayedComponents: .date) - - Picker(selection: $duration) { - Text("Aucune").tag(7) - Text(1.formatted()).tag(1) - Text(2.formatted()).tag(2) - Text(3.formatted()).tag(3) + Picker(selection: $federalDataViewModel.dayDuration) { + Text("aucune").tag(nil as Int?) + Text(1.formatted()).tag(1 as Int?) + Text(2.formatted()).tag(2 as Int?) + Text(3.formatted()).tag(3 as Int?) } label: { Text("Durée max (en jours)") } - - Picker(selection: $dayPeriod) { - Text("N'importe").tag(DayPeriod.all) - Text("le weekend").tag(DayPeriod.weekend) - Text("la semaine").tag(DayPeriod.week) + + Picker(selection: $federalDataViewModel.dayPeriod) { + ForEach(DayPeriod.allCases) { + Text($0.localizedDayPeriodLabel()).tag($0) + } } label: { Text("En semaine ou week-end") } - + HStack { TextField("Ville", text: $city) if let city = locationManager.city { @@ -585,53 +480,4 @@ struct TournamentLookUpView: View { return "Distance" } } - - - func _totalVisibleTournaments(_ date: Date? = nil) -> [FederalTournament] { - if let date { - if let tournaments = sectionedTournaments[URL.importDateFormatter.string(from: date)] { - let allTournaments = tournaments.filter({ canShowTournament($0) }).filter({ tournament in - if tournament.tournaments.count > 1 { - return tournament.tournaments.anySatisfy { isTypeLookedAfter($0) } - } else { - return true - } - }) - return allTournaments - } else { - return [] - } - } else { - let allTournaments = sectionedTournaments.values.flatMap({ $0 }).filter({ canShowTournament($0) }).filter({ tournament in - if tournament.tournaments.count > 1 { - return tournament.tournaments.anySatisfy { isTypeLookedAfter($0) } - } else { - return true - } - }) - return allTournaments - } - } - - func _totalVisibleEpreuves(_ date: Date? = nil) -> [any TournamentBuildHolder] { - if let date { - if let tournaments = sectionedTournaments[URL.importDateFormatter.string(from: date)] { - let allTournaments = tournaments - .filter({ canShowTournament($0) }) - .compactMap({ $0.tournaments }) - .flatMap({ $0 }) - .filter({ isTypeLookedAfter($0) }) - return allTournaments - } else { - return [] - } - } else { - let allTournaments = sectionedTournaments.values.flatMap({ $0 }) - .filter({ canShowTournament($0) }) - .compactMap({ $0.tournaments }) - .flatMap({ $0 }) - .filter({ isTypeLookedAfter($0) }) - return allTournaments - } - } } diff --git a/PadelClub/Views/Shared/TournamentFilterView.swift b/PadelClub/Views/Shared/TournamentFilterView.swift index e72377c..a312db0 100644 --- a/PadelClub/Views/Shared/TournamentFilterView.swift +++ b/PadelClub/Views/Shared/TournamentFilterView.swift @@ -28,6 +28,26 @@ struct TournamentFilterView: View { var body: some View { NavigationView { Form { + + Section { + Picker(selection: $federalDataViewModel.dayDuration) { + Text("aucune").tag(nil as Int?) + Text(1.formatted()).tag(1 as Int?) + Text(2.formatted()).tag(2 as Int?) + Text(3.formatted()).tag(3 as Int?) + } label: { + Text("Durée max (en jours)") + } + + Picker(selection: $federalDataViewModel.dayPeriod) { + ForEach(DayPeriod.allCases) { + Text($0.localizedDayPeriodLabel()).tag($0) + } + } label: { + Text("En semaine ou week-end") + } + } + Section { ForEach(TournamentLevel.allCases) { level in LabeledContent { From 8fc6f09908e74bea9ca5a4f1f0cda8d465a8e824 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Mon, 9 Sep 2024 19:03:30 +0200 Subject: [PATCH 11/23] clean up --- PadelClub/Data/Tournament.swift | 12 +-- .../LoserBracketFromGroupStageView.swift | 102 ++++++++++++++---- PadelClub/Views/Round/RoundSettingsView.swift | 3 +- 3 files changed, 87 insertions(+), 30 deletions(-) diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index abb008c..a4726bc 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -348,19 +348,19 @@ final class Tournament : ModelObject, Storable { override func deleteDependencies() throws { let store = self.tournamentStore - let teams = self.unsortedTeams() + let teams = self.tournamentStore.teamRegistrations for team in teams { try team.deleteDependencies() } store.teamRegistrations.deleteDependencies(teams) - let groups = self.groupStages() + let groups = self.tournamentStore.groupStages for group in groups { try group.deleteDependencies() } store.groupStages.deleteDependencies(groups) - let rounds = self.rounds() + let rounds = self.self.tournamentStore.rounds for round in rounds { try round.deleteDependencies() } @@ -2019,13 +2019,13 @@ defer { DataStore.shared.tournaments.writeChangeAndInsertOnServer(instance: self) - for teamRegistration in self.unsortedTeams() { + for teamRegistration in self.tournamentStore.teamRegistrations { teamRegistration.insertOnServer() } - for groupStage in self.groupStages() { + for groupStage in self.tournamentStore.groupStages { groupStage.insertOnServer() } - for round in self.allRounds() { + for round in self.tournamentStore.rounds { round.insertOnServer() } diff --git a/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift b/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift index 7e85d61..08efa0e 100644 --- a/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift +++ b/PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift @@ -54,31 +54,10 @@ struct LoserBracketFromGroupStageView: View { } } footer: { if isEditingLoserBracketGroupStage == true { - HStack { - if match.index > tournament.groupStageLoserBracketsInitialPlace() { - FooterButtonView("même place qu'au-dessus") { - match.index -= 2 - do { - try tournamentStore.matches.addOrUpdate(instance: match) - } catch { - Logger.error(error) - } - } - Spacer() - } - FooterButtonView("effacer", role: .destructive) { - do { - try match.deleteDependencies() - try tournamentStore.matches.delete(instance: match) - } catch { - Logger.error(error) - } - } - } + GroupStageLoserBracketMatchFooterView(match: match, samePlaceThanAboveOption: displayableMatches.count > 1) } } } - Section { if displayableMatches.count > 1 && isEditingLoserBracketGroupStage == true { Section { @@ -124,7 +103,8 @@ struct LoserBracketFromGroupStageView: View { } private func _addNewMatch() { - let placeCount = tournament.groupStageLoserBracketsInitialPlace() + displayableMatches.count * 2 + let currentGroupStageLoserBracketsInitialPlace = tournament.groupStageLoserBracketsInitialPlace() + let placeCount = displayableMatches.isEmpty ? currentGroupStageLoserBracketsInitialPlace : max(currentGroupStageLoserBracketsInitialPlace, displayableMatches.map({ $0.index }).max()! + 2) let match = Match(round: loserBracket.id, index: placeCount, matchFormat: loserBracket.matchFormat) match.name = "\(placeCount)\(placeCount.ordinalFormattedSuffix()) place" do { @@ -148,3 +128,79 @@ struct LoserBracketFromGroupStageView: View { } } + +struct GroupStageLoserBracketMatchFooterView: View { + @Environment(Tournament.self) var tournament: Tournament + @Bindable var match: Match + let samePlaceThanAboveOption: Bool + @State private var selectPlacePlayed: Bool = false + @State private var index: Int = 0 + + var body: some View { + HStack { + Menu { + if samePlaceThanAboveOption { + Button("Même place qu'au-dessus") { + _updateIndex(match.index-2) + } + } + Button("Choisir la place") { + index = match.index + selectPlacePlayed = true + } + } label: { + Text("Éditer la place jouée") + .underline() + } + + Spacer() + FooterButtonView("Effacer", role: .destructive) { + do { + try match.deleteDependencies() + try match.tournamentStore.matches.delete(instance: match) + } catch { + Logger.error(error) + } + } + } + .alert("Place jouée", isPresented: $selectPlacePlayed) { + TextField("Place jouée", value: $index, format: .number) + .keyboardType(.numberPad) + .multilineTextAlignment(.trailing) + .onSubmit { + _updateIndex(index) + } + Button("Confirmer") { + _updateIndex(index) + } + Button("Annuler", role: .cancel) { + } + } + } + + private func _updateIndex(_ newIndex: Int) { + let newIndexValidated = max(1,abs(newIndex)) + let teamScores = match.teamScores + teamScores.forEach { ts in + if let luckyLoser = ts.luckyLoser { + ts.luckyLoser = (luckyLoser - match.index * 2) % 2 + newIndexValidated * 2 + } + } + + match.index = newIndexValidated + + match.name = "\(newIndexValidated)\(newIndexValidated.ordinalFormattedSuffix()) place" + + + do { + try match.tournamentStore.teamScores.addOrUpdate(contentOfs: teamScores) + } catch { + Logger.error(error) + } + do { + try match.tournamentStore.matches.addOrUpdate(instance: match) + } catch { + Logger.error(error) + } + } +} diff --git a/PadelClub/Views/Round/RoundSettingsView.swift b/PadelClub/Views/Round/RoundSettingsView.swift index e23846f..6efa24b 100644 --- a/PadelClub/Views/Round/RoundSettingsView.swift +++ b/PadelClub/Views/Round/RoundSettingsView.swift @@ -132,7 +132,8 @@ struct RoundSettingsView: View { Logger.error(error) } round.buildLoserBracket() - matches.filter { $0.disabled }.forEach { $0._toggleLoserMatchDisableState(true) + matches.filter { $0.disabled }.forEach { + $0._toggleLoserMatchDisableState(true) } } } From 470aa619d623a4fb9b19009b68967cff96787e01 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Mon, 9 Sep 2024 20:40:39 +0200 Subject: [PATCH 12/23] add tournament subscribe --- PadelClub.xcodeproj/project.pbxproj | 4 + .../Coredata/ImportedPlayer+Extensions.swift | 6 +- .../Data/Federal/FederalTournament.swift | 12 ++ PadelClub/Utils/PadelRule.swift | 6 +- .../Agenda/TournamentSubscriptionView.swift | 194 ++++++++++++++++++ .../Views/Shared/TournamentFilterView.swift | 4 +- .../Shared/TournamentCellView.swift | 12 +- 7 files changed, 231 insertions(+), 7 deletions(-) create mode 100644 PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 8fcbf80..8c9f213 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -162,6 +162,7 @@ FF70916A2B90F95E00AB08DA /* DateBoxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7091692B90F95E00AB08DA /* DateBoxView.swift */; }; FF70916C2B91005400AB08DA /* TournamentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF70916B2B91005400AB08DA /* TournamentView.swift */; }; FF70916E2B9108C600AB08DA /* InscriptionManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF70916D2B9108C600AB08DA /* InscriptionManagerView.swift */; }; + FF8044AC2C8F676D00A49A52 /* TournamentSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8044AB2C8F676D00A49A52 /* TournamentSubscriptionView.swift */; }; FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */; }; FF82CFC92B9132AF00B0CAF2 /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */; }; FF8E1CE62C006E0200184680 /* Alphabet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8E1CE52C006E0200184680 /* Alphabet.swift */; }; @@ -509,6 +510,7 @@ FF7091692B90F95E00AB08DA /* DateBoxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateBoxView.swift; sourceTree = ""; }; FF70916B2B91005400AB08DA /* TournamentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentView.swift; sourceTree = ""; }; FF70916D2B9108C600AB08DA /* InscriptionManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InscriptionManagerView.swift; sourceTree = ""; }; + FF8044AB2C8F676D00A49A52 /* TournamentSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentSubscriptionView.swift; sourceTree = ""; }; FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrganizedTournamentView.swift; sourceTree = ""; }; FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = ""; }; FF8E1CE52C006E0200184680 /* Alphabet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alphabet.swift; sourceTree = ""; }; @@ -1294,6 +1296,7 @@ FF59FFB22B90EFAC0061EFF9 /* EventListView.swift */, FF5D0D8A2BB4D1E3005CB568 /* CalendarView.swift */, FFD655D72C8DE27400E5B35E /* TournamentLookUpView.swift */, + FF8044AB2C8F676D00A49A52 /* TournamentSubscriptionView.swift */, ); path = Agenda; sourceTree = ""; @@ -1594,6 +1597,7 @@ FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */, FF0EC5202BB16F680056B6D1 /* SwiftParser.swift in Sources */, C4A47DA92B85F82100ADC637 /* ChangePasswordView.swift in Sources */, + FF8044AC2C8F676D00A49A52 /* TournamentSubscriptionView.swift in Sources */, FFC83D512BB8087E00750834 /* RoundView.swift in Sources */, FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */, FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */, diff --git a/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift b/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift index 6dc4216..12ba149 100644 --- a/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift +++ b/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift @@ -57,7 +57,11 @@ extension ImportedPlayer: PlayerHolder { func isMalePlayer() -> Bool { male } - + + func pasteData() -> String { + return [firstName?.capitalized, lastName?.capitalized, license].compactMap({ $0 }).joined(separator: " ") + } + func isNotFromCurrentDate() -> Bool { if let importDate, importDate != SourceFileManager.shared.lastDataSourceDate() { return true diff --git a/PadelClub/Data/Federal/FederalTournament.swift b/PadelClub/Data/Federal/FederalTournament.swift index 0cce922..a535417 100644 --- a/PadelClub/Data/Federal/FederalTournament.swift +++ b/PadelClub/Data/Federal/FederalTournament.swift @@ -155,6 +155,18 @@ struct FederalTournament: Identifiable, Codable { [nomClub, jugeArbitre?.nom, jugeArbitre?.prenom, courrielEngagement, installation?.telephone].compactMap({$0}).joined(separator: ";") } + func umpireLabel() -> String { + [jugeArbitre?.nom, jugeArbitre?.prenom].compactMap({$0}).joined(separator: " ") + } + + func phoneLabel() -> String { + [installation?.telephone].compactMap({$0}).joined(separator: " ") + } + + func mailLabel() -> String { + [courrielEngagement].compactMap({$0}).joined(separator: " ") + } + func validForSearch(_ searchText: String, scope: FederalTournamentSearchScope) -> Bool { var trimmedSearchText = searchText.lowercased().trimmingCharacters(in: .whitespaces).folding(options: .diacriticInsensitive, locale: .current) trimmedSearchText = trimmedSearchText.replaceCharactersFromSet(characterSet: .punctuationCharacters, replacementString: " ") diff --git a/PadelClub/Utils/PadelRule.swift b/PadelClub/Utils/PadelRule.swift index d5b13b4..a30fd68 100644 --- a/PadelClub/Utils/PadelRule.swift +++ b/PadelClub/Utils/PadelRule.swift @@ -6,6 +6,7 @@ // import Foundation +import LeStorage enum RankSource: Hashable { case national @@ -33,7 +34,8 @@ protocol TournamentBuildHolder: Identifiable { } struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable { - var id: String { identifier } + var uniqueId: String = Store.randomId() + var id: String { uniqueId } let category: TournamentCategory let level: TournamentLevel let age: FederalTournamentAge @@ -42,7 +44,7 @@ struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable { // var japLastName: String? = nil func buildHolderTitle() -> String { - localizedLabel() + computedLabel } var identifier: String { diff --git a/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift b/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift new file mode 100644 index 0000000..b7afe67 --- /dev/null +++ b/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift @@ -0,0 +1,194 @@ +// +// TournamentSubscriptionView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 09/09/2024. +// + +import SwiftUI + +struct TournamentSubscriptionView: View { + @EnvironmentObject var networkMonitor: NetworkMonitor + + let federalTournament: FederalTournament + let build: any TournamentBuildHolder + + @State private var selectedPlayers: [ImportedPlayer] + @State private var contactType: ContactType? = nil + @State private var sentError: ContactManagerError? = nil + + init(federalTournament: FederalTournament, build: any TournamentBuildHolder, user: User) { + self.federalTournament = federalTournament + self.build = build + _selectedPlayers = .init(wrappedValue: [user.currentPlayerData()].compactMap({ $0 })) + } + + var body: some View { + List { + Section { + LabeledContent("Tournoi") { + Text(federalTournament.libelle ?? "Tournoi") + } + LabeledContent("Club") { + Text(federalTournament.clubLabel()) + } + LabeledContent("Épreuve") { + Text(build.buildHolderTitle()) + } + } header: { + Text("Informations") + } + + Section { + ForEach(selectedPlayers) { teamPlayer in + NavigationLink { + SelectablePlayerListView(allowSelection: 1, playerSelectionAction: { players in + if let player = players.first { + selectedPlayers.remove(elements: [teamPlayer]) + selectedPlayers.append(player) + } + }) + } label: { + ImportedPlayerView(player: teamPlayer) + } + } + + if selectedPlayers.count < 2 { + NavigationLink { + SelectablePlayerListView(allowSelection: 1, playerSelectionAction: { players in + if let player = players.first { + selectedPlayers.append(player) + } + }) + } label: { + Text("Choisir un partenaire") + } + } + } header: { + HStack { + Text("Poids") + Spacer() + Text(selectedPlayers.map { $0.rank }.reduce(0, +).formatted()) + } + } + + Section { + LabeledContent("JAP") { + Text(federalTournament.umpireLabel()) + } + LabeledContent("Mail") { + Text(federalTournament.mailLabel()) + } + LabeledContent("Téléphone") { + Text(federalTournament.phoneLabel()) + } + } + + let teams = selectedPlayers.map { $0.pasteData() }.joined(separator: "\n") + let body = [[build.buildHolderTitle(), federalTournament.computedStartDate].compacted().joined(separator: " "), teams].compactMap { $0 }.joined(separator: "\n") + "\n" + let subject = [build.buildHolderTitle(), federalTournament.nomClub].compacted().joined(separator: " ") + if let courrielEngagement = federalTournament.courrielEngagement { + Section { + RowButtonView("Contacter par email") { + contactType = .mail(date: nil, recipients: [courrielEngagement], bccRecipients: nil, body: body, subject: subject, tournamentBuild: build as? TournamentBuild) + } + } + } + + if let installation = federalTournament.installation, let telephone = installation.telephone { + if telephone.isMobileNumber() { + let body = [[build.buildHolderTitle(), federalTournament.nomClub].compacted().joined(separator: " "), federalTournament.computedStartDate, teams].compacted().joined(separator: "\n") + "\n" + Section { + RowButtonView("Contacter par message") { + contactType = .message(date: nil, recipients: [telephone], body: body, tournamentBuild: build as? TournamentBuild) + } + } + } else { + let number = telephone.replacingOccurrences(of: " ", with: "") + if let url = URL(string: "tel:\(number)") { + Link(destination: url) { + Label("Appeler", systemImage: "phone") + } + } + } + } + + } + .alert("Un problème est survenu", isPresented: messageSentFailed) { + Button("OK") { + } + } message: { + Text(_networkErrorMessage) + } + .sheet(item: $contactType) { contactType in + Group { + switch contactType { + case .message(_, let recipients, let body, _): + MessageComposeView(recipients: recipients, body: body) { result in + switch result { + case .cancelled: + break + case .failed: + self.sentError = .messageFailed + case .sent: + if networkMonitor.connected == false { + self.sentError = .messageNotSent + } + @unknown default: + break + } + } + case .mail(_, let recipients, let bccRecipients, let body, let subject, _): + MailComposeView(recipients: recipients, bccRecipients: bccRecipients, body: body, subject: subject) { result in + switch result { + case .cancelled, .saved: + self.contactType = nil + case .failed: + self.contactType = nil + self.sentError = .mailFailed + case .sent: + if networkMonitor.connected == false { + self.contactType = nil + self.sentError = .mailNotSent + } + @unknown default: + break + } + } + } + } + .tint(.master) + } + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + .navigationTitle("Détail du tournoi") + } + + var messageSentFailed: Binding { + Binding { + sentError != nil + } set: { newValue in + if newValue == false { + sentError = nil + } + } + } + + private var _networkErrorMessage: String { + var errors: [String] = [] + + if networkMonitor.connected == false { + errors.append("L'appareil n'est pas connecté à internet.") + } + if sentError == .mailNotSent { + errors.append("Le mail est dans la boîte d'envoi de l'app Mail. Vérifiez son état dans l'app Mail avant d'essayer de le renvoyer.") + } + if (sentError == .messageFailed || sentError == .messageNotSent) { + errors.append("Le SMS n'a pas été envoyé") + } + if sentError == .mailFailed { + errors.append("Le mail n'a pas été envoyé") + } + return errors.joined(separator: "\n") + } +} diff --git a/PadelClub/Views/Shared/TournamentFilterView.swift b/PadelClub/Views/Shared/TournamentFilterView.swift index a312db0..9d0a2c3 100644 --- a/PadelClub/Views/Shared/TournamentFilterView.swift +++ b/PadelClub/Views/Shared/TournamentFilterView.swift @@ -71,7 +71,7 @@ struct TournamentFilterView: View { } Section { - ForEach(TournamentCategory.allCases) { category in + ForEach([TournamentCategory.men, TournamentCategory.women, TournamentCategory.mix]) { category in LabeledContent { Button { if categories.contains(category) { @@ -85,7 +85,7 @@ struct TournamentFilterView: View { } } } label: { - Text(category.localizedLabel(.title)) + Text(category.localizedLabel(.wide)) } } } header: { diff --git a/PadelClub/Views/Tournament/Shared/TournamentCellView.swift b/PadelClub/Views/Tournament/Shared/TournamentCellView.swift index 5014e02..1e9ab62 100644 --- a/PadelClub/Views/Tournament/Shared/TournamentCellView.swift +++ b/PadelClub/Views/Tournament/Shared/TournamentCellView.swift @@ -23,7 +23,15 @@ struct TournamentCellView: View { var body: some View { ForEach(tournament.tournaments, id: \.id) { build in - _buildView(build, existingTournament: event?.existingBuild(build)) + if navigation.agendaDestination == .around, let federalTournament = tournament as? FederalTournament { + NavigationLink { + TournamentSubscriptionView(federalTournament: federalTournament, build: build, user: dataStore.user) + } label: { + _buildView(build, existingTournament: event?.existingBuild(build)) + } + } else { + _buildView(build, existingTournament: event?.existingBuild(build)) + } } } @@ -98,7 +106,7 @@ struct TournamentCellView: View { } else if let teamCount { Text(teamCount.formatted()) } - } else if let federalTournament = tournament as? FederalTournament { + } else if let federalTournament = tournament as? FederalTournament, navigation.agendaDestination != .around { Button { _createOrShow(federalTournament: federalTournament, existingTournament: existingTournament, build: build) } label: { From 68338f580ac41dcc08bca478fe259d6a175f5396 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 10 Sep 2024 10:09:08 +0200 Subject: [PATCH 13/23] clean up new features --- .../Coredata/ImportedPlayer+Extensions.swift | 2 +- PadelClub/Data/User.swift | 7 +++ PadelClub/Extensions/Array+Extensions.swift | 16 ++++++ .../Extensions/Calendar+Extensions.swift | 20 +++++++ .../ViewModel/FederalDataViewModel.swift | 8 +-- .../Navigation/Agenda/ActivityView.swift | 33 +++++++----- .../Navigation/Agenda/CalendarView.swift | 11 +++- .../Navigation/Agenda/EventListView.swift | 38 +++++++++---- .../Agenda/TournamentLookUpView.swift | 6 +-- .../Agenda/TournamentSubscriptionView.swift | 54 ++++++++++++++----- 10 files changed, 149 insertions(+), 46 deletions(-) diff --git a/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift b/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift index 12ba149..1745db9 100644 --- a/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift +++ b/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift @@ -59,7 +59,7 @@ extension ImportedPlayer: PlayerHolder { } func pasteData() -> String { - return [firstName?.capitalized, lastName?.capitalized, license].compactMap({ $0 }).joined(separator: " ") + return [firstName?.capitalized, lastName?.capitalized, license?.computedLicense].compactMap({ $0 }).joined(separator: " ") } func isNotFromCurrentDate() -> Bool { diff --git a/PadelClub/Data/User.swift b/PadelClub/Data/User.swift index 6ce9d08..d40e126 100644 --- a/PadelClub/Data/User.swift +++ b/PadelClub/Data/User.swift @@ -75,6 +75,13 @@ class User: ModelObject, UserBase, Storable { return "Sportivement,\n\(firstName) \(lastName), votre JAP." } + func fullName() -> String? { + guard firstName.isEmpty == false && lastName.isEmpty == false else { + return nil + } + return "\(firstName) \(lastName)" + } + func hasTenupClubs() -> Bool { self.clubsObjects().filter({ $0.code != nil }).isEmpty == false } diff --git a/PadelClub/Extensions/Array+Extensions.swift b/PadelClub/Extensions/Array+Extensions.swift index f1ab768..baadf6c 100644 --- a/PadelClub/Extensions/Array+Extensions.swift +++ b/PadelClub/Extensions/Array+Extensions.swift @@ -78,3 +78,19 @@ extension Dictionary where Key == Int, Value == [String] { } } } + + +extension Array where Element == String { + func formatList(maxDisplay: Int = 2) -> [String] { + // Check if the array has fewer or equal elements than the maximum display limit + if self.count <= maxDisplay { + // Join all elements with commas + return self + } else { + // Join only the first `maxDisplay` elements and add "et plus" + let displayedItems = self.prefix(maxDisplay) + let remainingCount = self.count - maxDisplay + return displayedItems.dropLast() + [displayedItems.last! + " et \(remainingCount) de plus"] + } + } +} diff --git a/PadelClub/Extensions/Calendar+Extensions.swift b/PadelClub/Extensions/Calendar+Extensions.swift index 3e04ff3..47971b5 100644 --- a/PadelClub/Extensions/Calendar+Extensions.swift +++ b/PadelClub/Extensions/Calendar+Extensions.swift @@ -49,3 +49,23 @@ extension Calendar { return sportYear } } + +extension Calendar { + // Add or subtract months from a date + func addMonths(_ months: Int, to date: Date) -> Date { + return self.date(byAdding: .month, value: months, to: date)! + } + + // Generate a list of month start dates between two dates + func generateMonthRange(startDate: Date, endDate: Date) -> [Date] { + var dates: [Date] = [] + var currentDate = startDate + + while currentDate <= endDate { + dates.append(currentDate) + currentDate = self.addMonths(1, to: currentDate) + } + + return dates + } +} diff --git a/PadelClub/ViewModel/FederalDataViewModel.swift b/PadelClub/ViewModel/FederalDataViewModel.swift index e83dc5b..010b211 100644 --- a/PadelClub/ViewModel/FederalDataViewModel.swift +++ b/PadelClub/ViewModel/FederalDataViewModel.swift @@ -25,15 +25,15 @@ class FederalDataViewModel { func filterStatus() -> String { var labels: [String] = [] - labels.append(contentsOf: levels.map { $0.localizedLabel() }) - labels.append(contentsOf: categories.map { $0.localizedLabel() }) - labels.append(contentsOf: ageCategories.map { $0.localizedLabel() }) + labels.append(contentsOf: levels.map { $0.localizedLabel() }.formatList()) + labels.append(contentsOf: categories.map { $0.localizedLabel() }.formatList()) + labels.append(contentsOf: ageCategories.map { $0.localizedLabel() }.formatList()) let clubNames = selectedClubs.compactMap { codeClub in let club: Club? = DataStore.shared.clubs.first(where: { $0.code == codeClub }) return club?.clubTitle(.short) } - labels.append(contentsOf: clubNames) + labels.append(contentsOf: clubNames.formatList()) if dayPeriod != .all { labels.append(dayPeriod.localizedDayPeriodLabel()) } diff --git a/PadelClub/Views/Navigation/Agenda/ActivityView.swift b/PadelClub/Views/Navigation/Agenda/ActivityView.swift index e6085f7..596b360 100644 --- a/PadelClub/Views/Navigation/Agenda/ActivityView.swift +++ b/PadelClub/Views/Navigation/Agenda/ActivityView.swift @@ -90,17 +90,24 @@ struct ActivityView: View { VStack(spacing: 0) { GenericDestinationPickerView(selectedDestination: $navigation.agendaDestination, destinations: AgendaDestination.allCases, nilDestinationIsValid: false) - List { - switch navigation.agendaDestination! { - case .activity: - EventListView(tournaments: runningTournaments, sortAscending: true) - case .history: - EventListView(tournaments: endedTournaments, sortAscending: false) - case .tenup: - EventListView(tournaments: federalDataViewModel.federalTournaments, sortAscending: true) - .id(uuid) - case .around: - EventListView(tournaments: federalDataViewModel.searchedFederalTournaments, sortAscending: true) + ScrollViewReader { proxy in + List { + switch navigation.agendaDestination! { + case .activity: + EventListView(tournaments: runningTournaments, sortAscending: true) + case .history: + EventListView(tournaments: endedTournaments, sortAscending: false) + case .tenup: + EventListView(tournaments: federalDataViewModel.federalTournaments, sortAscending: true) + .id(uuid) + case .around: + EventListView(tournaments: federalDataViewModel.searchedFederalTournaments, sortAscending: true) + } + } + .onChange(of: navigation.agendaDestination) { + withAnimation { + proxy.scrollTo(0, anchor: .center) + } } } .environment(\.viewStyle, viewStyle) @@ -227,7 +234,7 @@ struct ActivityView: View { } } - if presentToolbar, tournaments.isEmpty == false { + if presentToolbar, tournaments.isEmpty == false, federalDataViewModel.areFiltersEnabled() || navigation.agendaDestination == .around { ToolbarItemGroup(placement: .bottomBar) { VStack(spacing: 0) { let searchStatus = _searchStatus() @@ -447,7 +454,7 @@ struct ActivityView: View { ContentUnavailableView { Label("Recherche de tournoi", systemImage: "magnifyingglass") } description: { - Text("Chercher les tournois autour de vous pour vous aidez à mieux selectionner ce que vous pouvez proposer.") + Text("Chercher les tournois autour de vous pour mieux décider les tournois à proposer dans votre club. Padel Club vous facilite même l'inscription !") } actions: { RowButtonView("Lancer la recherche") { displaySearchView = true diff --git a/PadelClub/Views/Navigation/Agenda/CalendarView.swift b/PadelClub/Views/Navigation/Agenda/CalendarView.swift index a39f63f..474fb28 100644 --- a/PadelClub/Views/Navigation/Agenda/CalendarView.swift +++ b/PadelClub/Views/Navigation/Agenda/CalendarView.swift @@ -91,8 +91,15 @@ struct CalendarView: View { Menu { ForEach(tournament.tournaments, id: \.id) { build in if federalDataViewModel.isFederalTournamentValidForFilters(tournament, build: build) { - Button(build.buildHolderTitle()) { - _createOrShow(federalTournament: tournament, existingTournament: event(forTournament: tournament)?.existingBuild(build), build: build) + + if navigation.agendaDestination == .around { + NavigationLink(build.buildHolderTitle()) { + TournamentSubscriptionView(federalTournament: tournament, build: build, user: dataStore.user) + } + } else { + Button(build.buildHolderTitle()) { + _createOrShow(federalTournament: tournament, existingTournament: event(forTournament: tournament)?.existingBuild(build), build: build) + } } } } diff --git a/PadelClub/Views/Navigation/Agenda/EventListView.swift b/PadelClub/Views/Navigation/Agenda/EventListView.swift index f146b31..3a8a53b 100644 --- a/PadelClub/Views/Navigation/Agenda/EventListView.swift +++ b/PadelClub/Views/Navigation/Agenda/EventListView.swift @@ -21,7 +21,10 @@ struct EventListView: View { let groupedTournamentsByDate = Dictionary(grouping: federalDataViewModel.filteredFederalTournaments(from: tournaments)) { $0.startDate.startOfMonth } switch viewStyle { case .list: - ForEach(groupedTournamentsByDate.keys.sorted(by: sortAscending ? { $0 < $1 } : { $0 > $1 }), id: \.self) { section in + let nextMonths = groupedTournamentsByDate.keys.sorted(by: sortAscending ? { $0 < $1 } : { $0 > $1 }) + + ForEach(nextMonths.indices, id: \.self) { sectionIndex in + let section = nextMonths[sectionIndex] if let _tournaments = groupedTournamentsByDate[section]?.sorted(by: sortAscending ? { $0.startDate < $1.startDate } : { $0.startDate > $1.startDate } ) { Section { @@ -34,11 +37,14 @@ struct EventListView: View { Text("\(count.formatted()) tournoi" + count.pluralSuffix) } } + .id(sectionIndex) .headerProminence(.increased) } } case .calendar: - ForEach(_nextMonths(), id: \.self) { section in + let nextMonths = _nextMonths() + ForEach(nextMonths.indices, id: \.self) { sectionIndex in + let section = nextMonths[sectionIndex] let _tournaments = groupedTournamentsByDate[section] ?? [] Section { CalendarView(date: section, tournaments: _tournaments).id(federalDataViewModel.id) @@ -50,6 +56,7 @@ struct EventListView: View { Text("\(count.formatted()) tournoi" + count.pluralSuffix) } } + .id(sectionIndex) .headerProminence(.increased) .task { if navigation.agendaDestination == .tenup @@ -77,16 +84,25 @@ struct EventListView: View { } private func _nextMonths() -> [Date] { - var result: [Date] = [] - var currentDate = Date().startOfMonth - - // Generate 100 future months - for _ in 0..<12 { - result.append(currentDate) - currentDate = Calendar.current.date(byAdding: .month, value: 1, to: currentDate)! + let currentDate = Date().startOfMonth + let uniqueDates = tournaments.map { $0.startDate.startOfMonth }.uniqued().sorted() + let firstMonthOfDate = uniqueDates.first + let lastMonthOfDate = uniqueDates.last + let calendar = Calendar.current + + if let firstMonthOfDate, let lastMonthOfDate { + if navigation.agendaDestination == .history { + return calendar.generateMonthRange(startDate: firstMonthOfDate, endDate: lastMonthOfDate).reversed() + } else if navigation.agendaDestination == .around || navigation.agendaDestination == .tenup { + return calendar.generateMonthRange(startDate: firstMonthOfDate, endDate: lastMonthOfDate) + } else { + let min = min(currentDate, firstMonthOfDate) + let max = max(currentDate, lastMonthOfDate) + return calendar.generateMonthRange(startDate: min, endDate: calendar.addMonths(3, to: max)) + } + } else { + return calendar.generateMonthRange(startDate: currentDate, endDate: calendar.addMonths(3, to: currentDate)) } - - return result } private func _listView(_ tournaments: [FederalTournamentHolder]) -> some View { diff --git a/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift b/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift index f622231..d4e8ca7 100644 --- a/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift +++ b/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift @@ -27,10 +27,10 @@ struct TournamentLookUpView: View { @State private var endDate: Date = Calendar.current.date(byAdding: .month, value: 3, to: Date())! @AppStorage("lastCity") private var city: String = "" @State private var ligue: String = "" - @AppStorage("lastDistance") private var distance: Double = 30 - @AppStorage("lastSortingOption") private var sortingOption: String = "dateDebut+asc" + @State private var distance: Double = 30 + @State private var sortingOption: String = "dateDebut+asc" @State private var requestedToGetAllPages: Bool = false - @AppStorage("lastNationalCup") private var nationalCup: Bool = false + @State private var nationalCup: Bool = false @State private var revealSearchParameters: Bool = true @State private var presentAlert: Bool = false diff --git a/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift b/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift index b7afe67..70467d6 100644 --- a/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift +++ b/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift @@ -12,6 +12,7 @@ struct TournamentSubscriptionView: View { let federalTournament: FederalTournament let build: any TournamentBuildHolder + let user: User @State private var selectedPlayers: [ImportedPlayer] @State private var contactType: ContactType? = nil @@ -20,6 +21,7 @@ struct TournamentSubscriptionView: View { init(federalTournament: FederalTournament, build: any TournamentBuildHolder, user: User) { self.federalTournament = federalTournament self.build = build + self.user = user _selectedPlayers = .init(wrappedValue: [user.currentPlayerData()].compactMap({ $0 })) } @@ -84,36 +86,43 @@ struct TournamentSubscriptionView: View { } } - let teams = selectedPlayers.map { $0.pasteData() }.joined(separator: "\n") - let body = [[build.buildHolderTitle(), federalTournament.computedStartDate].compacted().joined(separator: " "), teams].compactMap { $0 }.joined(separator: "\n") + "\n" - let subject = [build.buildHolderTitle(), federalTournament.nomClub].compacted().joined(separator: " ") if let courrielEngagement = federalTournament.courrielEngagement { Section { RowButtonView("Contacter par email") { - contactType = .mail(date: nil, recipients: [courrielEngagement], bccRecipients: nil, body: body, subject: subject, tournamentBuild: build as? TournamentBuild) + contactType = .mail(date: nil, recipients: [courrielEngagement], bccRecipients: nil, body: messageBody, subject: messageSubject, tournamentBuild: build as? TournamentBuild) } } } if let installation = federalTournament.installation, let telephone = installation.telephone { if telephone.isMobileNumber() { - let body = [[build.buildHolderTitle(), federalTournament.nomClub].compacted().joined(separator: " "), federalTournament.computedStartDate, teams].compacted().joined(separator: "\n") + "\n" Section { RowButtonView("Contacter par message") { - contactType = .message(date: nil, recipients: [telephone], body: body, tournamentBuild: build as? TournamentBuild) + contactType = .message(date: nil, recipients: [telephone], body: messageBodyShort, tournamentBuild: build as? TournamentBuild) } } - } else { - let number = telephone.replacingOccurrences(of: " ", with: "") - if let url = URL(string: "tel:\(number)") { - Link(destination: url) { - Label("Appeler", systemImage: "phone") - } + } + let number = telephone.replacingOccurrences(of: " ", with: "") + if let url = URL(string: "tel:\(number)") { + Link(destination: url) { + Label("Appeler", systemImage: "phone") } } } } + .toolbar(content: { + Menu { + Link(destination: URL(string:"https://tenup.fft.fr/tournoi/\(federalTournament.id)")!) { + Label("Voir sur Tenup", systemImage: "tennisball") + } + ShareLink(item: federalTournament.shareMessage) { + Label("Partager les infos", systemImage: "info") + } + } label: { + LabelOptions() + } + }) .alert("Un problème est survenu", isPresented: messageSentFailed) { Button("OK") { } @@ -164,6 +173,27 @@ struct TournamentSubscriptionView: View { .navigationTitle("Détail du tournoi") } + var teamsString: String { + selectedPlayers.map { $0.pasteData() }.joined(separator: "\n") + } + + var messageBody: String { + let bonjourOuBonsoir = Date().timeOfDay.hello + let bonneSoireeOuBonneJournee = Date().timeOfDay.goodbye + let body = [["\(bonjourOuBonsoir),\n\nJe souhaiterais inscrire mon équipe au tournoi : ", build.buildHolderTitle(), "du", federalTournament.computedStartDate, "au", federalTournament.clubLabel() + ".\n"].compacted().joined(separator: " "), teamsString, "\nCordialement,\n", user.fullName() ?? bonneSoireeOuBonneJournee, "----------------------------------\nCe message a été préparé grâce à l'application Padel Club !\nVotre tournoi n'est pas encore dessus ? \(URLs.main.rawValue)", "Téléchargez l'app : \(URLs.appStore.rawValue)", "En savoir plus : \(URLs.appDescription.rawValue)"].compactMap { $0 }.joined(separator: "\n") + "\n" + return body + } + + var messageBodyShort: String { + let body = [[build.buildHolderTitle(), federalTournament.clubLabel()].compacted().joined(separator: " "), federalTournament.computedStartDate, teamsString].compacted().joined(separator: "\n") + "\n" + return body + } + + var messageSubject: String { + let subject = [build.buildHolderTitle(), federalTournament.clubLabel()].compacted().joined(separator: " ") + return subject + } + var messageSentFailed: Binding { Binding { sentError != nil From 3cdc8e945ec0a49c60fdacbd73acfed81086d853 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 10 Sep 2024 11:07:37 +0200 Subject: [PATCH 14/23] fix merge --- PadelClub.xcodeproj/project.pbxproj | 1 - 1 file changed, 1 deletion(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 75d9eb8..3fdc2b1 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -423,7 +423,6 @@ FF8045B72C90379300A49A52 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = FF0CA5742BDA4AE10080E843 /* PrivacyInfo.xcprivacy */; }; FF8045B82C90379300A49A52 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C425D4042B6D249E002A7B48 /* Assets.xcassets */; }; FF8045BA2C90379300A49A52 /* LeStorage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C49EF0372BDFF3000077B5AA /* LeStorage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; ->>>>>>> main FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */; }; FF82CFC92B9132AF00B0CAF2 /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */; }; FF8E1CE62C006E0200184680 /* Alphabet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8E1CE52C006E0200184680 /* Alphabet.swift */; }; From b8262de8bf40d051bdc0ad725307c6ece82059af Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 10 Sep 2024 11:14:39 +0200 Subject: [PATCH 15/23] fix merge --- PadelClub.xcodeproj/project.pbxproj | 24 +++++++------------ .../xcschemes/PadelClub-TestFlight.xcscheme | 2 +- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 3fdc2b1..06c3c78 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -148,6 +148,9 @@ FF6087EC2BE26A2F004E1E47 /* BroadcastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087EB2BE26A2F004E1E47 /* BroadcastView.swift */; }; FF6525C32C8C61B400B9498E /* LoserBracketFromGroupStageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6525C22C8C61B400B9498E /* LoserBracketFromGroupStageView.swift */; }; FF663FBE2BE019EC0031AE83 /* TournamentFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */; }; + FF6D2D392C90444A00CCF14B /* TournamentLookUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD655D72C8DE27400E5B35E /* TournamentLookUpView.swift */; }; + FF6D2D3A2C90446700CCF14B /* TournamentSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8044AB2C8F676D00A49A52 /* TournamentSubscriptionView.swift */; }; + FF6D2D3B2C90447900CCF14B /* LoserBracketFromGroupStageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6525C22C8C61B400B9498E /* LoserBracketFromGroupStageView.swift */; }; FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; }; FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */; }; FF6EC8FE2B94792300EA7F5A /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FD2B94792300EA7F5A /* Screen.swift */; }; @@ -214,7 +217,7 @@ FF8044E42C90379300A49A52 /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; }; FF8044E52C90379300A49A52 /* SendToAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */; }; FF8044E62C90379300A49A52 /* EventCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263A2BAD528600650388 /* EventCreationView.swift */; }; - FF8044E72C90379300A49A52 /* LoserGroupStageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF135BF82C2FCB8300C9247A /* LoserGroupStageSettingsView.swift */; }; + FF8044E72C90379300A49A52 /* (null) in Sources */ = {isa = PBXBuildFile; }; FF8044E82C90379300A49A52 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; }; FF8044E92C90379300A49A52 /* CapsuleViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C01D972C481C0C0059087C /* CapsuleViewModifier.swift */; }; FF8044EA2C90379300A49A52 /* BroadcastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087EB2BE26A2F004E1E47 /* BroadcastView.swift */; }; @@ -2156,12 +2159,14 @@ FF8044B92C90379300A49A52 /* LoserRoundStepScheduleEditorView.swift in Sources */, FF8044BA2C90379300A49A52 /* ClubCourtSetupView.swift in Sources */, FF8044BB2C90379300A49A52 /* Patcher.swift in Sources */, + FF6D2D3B2C90447900CCF14B /* LoserBracketFromGroupStageView.swift in Sources */, FF8044BC2C90379300A49A52 /* FileImportView.swift in Sources */, FF8044BD2C90379300A49A52 /* StepperView.swift in Sources */, FF8044BE2C90379300A49A52 /* RoundsView.swift in Sources */, FF8044BF2C90379300A49A52 /* FederalTournament.swift in Sources */, FF8044C02C90379300A49A52 /* TournamentInitView.swift in Sources */, FF8044C12C90379300A49A52 /* SubscriptionView.swift in Sources */, + FF6D2D392C90444A00CCF14B /* TournamentLookUpView.swift in Sources */, FF8044C22C90379300A49A52 /* ClubsView.swift in Sources */, FF8044C32C90379300A49A52 /* ImagePickerView.swift in Sources */, FF8044C42C90379300A49A52 /* MatchTypeSelectionView.swift in Sources */, @@ -2195,11 +2200,12 @@ FF8044E02C90379300A49A52 /* AppSettings.swift in Sources */, FF8044E12C90379300A49A52 /* SwiftParser.swift in Sources */, FF8044E22C90379300A49A52 /* ChangePasswordView.swift in Sources */, + FF6D2D3A2C90446700CCF14B /* TournamentSubscriptionView.swift in Sources */, FF8044E32C90379300A49A52 /* RoundView.swift in Sources */, FF8044E42C90379300A49A52 /* RowButtonView.swift in Sources */, FF8044E52C90379300A49A52 /* SendToAllView.swift in Sources */, FF8044E62C90379300A49A52 /* EventCreationView.swift in Sources */, - FF8044E72C90379300A49A52 /* LoserGroupStageSettingsView.swift in Sources */, + FF8044E72C90379300A49A52 /* (null) in Sources */, FF8044E82C90379300A49A52 /* LocationManager.swift in Sources */, FF8044E92C90379300A49A52 /* CapsuleViewModifier.swift in Sources */, FF8044EA2C90379300A49A52 /* BroadcastView.swift in Sources */, @@ -2702,9 +2708,6 @@ DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; - ENABLE_MODULE_VERIFIER = YES; - ENABLE_PREVIEWS = YES; - GCC_OPTIMIZATION_LEVEL = fast; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = "Padel Club"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports"; @@ -2722,17 +2725,12 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0.7; - MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; - OTHER_SWIFT_FLAGS = "-Onone"; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -2750,8 +2748,6 @@ DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; - ENABLE_MODULE_VERIFIER = YES; - ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = "Padel Club"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports"; @@ -2769,8 +2765,6 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0.7; - MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; - OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-function-bodies=5 -Xfrontend -warn-long-expression-type-checking=20 -Xfrontend -warn-long-function-bodies=50"; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2778,8 +2772,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = TESTFLIGHT; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/PadelClub.xcodeproj/xcshareddata/xcschemes/PadelClub-TestFlight.xcscheme b/PadelClub.xcodeproj/xcshareddata/xcschemes/PadelClub-TestFlight.xcscheme index 523da7c..a0de132 100644 --- a/PadelClub.xcodeproj/xcshareddata/xcschemes/PadelClub-TestFlight.xcscheme +++ b/PadelClub.xcodeproj/xcshareddata/xcschemes/PadelClub-TestFlight.xcscheme @@ -31,7 +31,7 @@ shouldAutocreateTestPlan = "YES"> Date: Tue, 10 Sep 2024 11:15:33 +0200 Subject: [PATCH 16/23] 1.0.8 --- PadelClub.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 06c3c78..7f21655 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -2570,7 +2570,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.5; + MARKETING_VERSION = 1.0.8; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2611,7 +2611,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.5; + MARKETING_VERSION = 1.0.8; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From 550aa91c56dfc53baa8bf999932a0d6a63234bbc Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 10 Sep 2024 12:21:15 +0200 Subject: [PATCH 17/23] change display name for testflight target --- PadelClub.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 7f21655..42743ed 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -2703,13 +2703,13 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_CFBundleDisplayName = "Padel Club"; + INFOPLIST_KEY_CFBundleDisplayName = "Padel Club (Beta)"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports"; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; INFOPLIST_KEY_NSCameraUsageDescription = "En autorisant l'application à utiliser la caméra, vous pourrez prendre des photos des rencontres"; @@ -2724,7 +2724,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.7; + MARKETING_VERSION = 1.0.8; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2744,12 +2744,12 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_CFBundleDisplayName = "Padel Club"; + INFOPLIST_KEY_CFBundleDisplayName = "Padel Club (Beta)"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports"; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; INFOPLIST_KEY_NSCameraUsageDescription = "En autorisant l'application à utiliser la caméra, vous pourrez prendre des photos des rencontres"; @@ -2764,7 +2764,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.7; + MARKETING_VERSION = 1.0.8; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From 07460ba67a8187277894eb177c43423e0af6f80f Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 10 Sep 2024 12:42:17 +0200 Subject: [PATCH 18/23] fix plist issue --- PadelClub.xcodeproj/project.pbxproj | 1110 ++++++++--------- ...xcscheme => PadelClub TestFlight.xcscheme} | 14 +- 2 files changed, 567 insertions(+), 557 deletions(-) rename PadelClub.xcodeproj/xcshareddata/xcschemes/{PadelClub-TestFlight.xcscheme => PadelClub TestFlight.xcscheme} (80%) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 42743ed..6785d4a 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -148,9 +148,6 @@ FF6087EC2BE26A2F004E1E47 /* BroadcastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087EB2BE26A2F004E1E47 /* BroadcastView.swift */; }; FF6525C32C8C61B400B9498E /* LoserBracketFromGroupStageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6525C22C8C61B400B9498E /* LoserBracketFromGroupStageView.swift */; }; FF663FBE2BE019EC0031AE83 /* TournamentFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */; }; - FF6D2D392C90444A00CCF14B /* TournamentLookUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD655D72C8DE27400E5B35E /* TournamentLookUpView.swift */; }; - FF6D2D3A2C90446700CCF14B /* TournamentSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8044AB2C8F676D00A49A52 /* TournamentSubscriptionView.swift */; }; - FF6D2D3B2C90447900CCF14B /* LoserBracketFromGroupStageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6525C22C8C61B400B9498E /* LoserBracketFromGroupStageView.swift */; }; FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; }; FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */; }; FF6EC8FE2B94792300EA7F5A /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FD2B94792300EA7F5A /* Screen.swift */; }; @@ -165,267 +162,269 @@ FF70916A2B90F95E00AB08DA /* DateBoxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7091692B90F95E00AB08DA /* DateBoxView.swift */; }; FF70916C2B91005400AB08DA /* TournamentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF70916B2B91005400AB08DA /* TournamentView.swift */; }; FF70916E2B9108C600AB08DA /* InscriptionManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF70916D2B9108C600AB08DA /* InscriptionManagerView.swift */; }; + FF70FAC32C90584900129CC2 /* UserCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D862B7BA36D00ADC637 /* UserCreationView.swift */; }; + FF70FAC42C90584900129CC2 /* TabDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7091652B90F0B000AB08DA /* TabDestination.swift */; }; + FF70FAC52C90584900129CC2 /* CashierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9267F72BCE78C70080F940 /* CashierView.swift */; }; + FF70FAC62C90584900129CC2 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263E2BAD7D5C00650388 /* Event.swift */; }; + FF70FAC72C90584900129CC2 /* PlayerHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30522BD94E2E00F2B93D /* PlayerHolder.swift */; }; + FF70FAC82C90584900129CC2 /* LoserRoundStepScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF11628B2BD05267000C4809 /* LoserRoundStepScheduleEditorView.swift */; }; + FF70FAC92C90584900129CC2 /* ClubCourtSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF53FBB72BFB302B0051D4C3 /* ClubCourtSetupView.swift */; }; + FF70FACA2C90584900129CC2 /* Patcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B3A1542C2581DA0078EAA8 /* Patcher.swift */; }; + FF70FACB2C90584900129CC2 /* FileImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EBE2BB0B14600F0AEC7 /* FileImportView.swift */; }; + FF70FACC2C90584900129CC2 /* StepperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D9E2B7D0BCE00ADC637 /* StepperView.swift */; }; + FF70FACD2C90584900129CC2 /* RoundsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D4E2BB807D100750834 /* RoundsView.swift */; }; + FF70FACE2C90584900129CC2 /* FederalTournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC182BB53D1F0036DAAB /* FederalTournament.swift */; }; + FF70FACF2C90584900129CC2 /* TournamentInitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26402BADFC8700650388 /* TournamentInitView.swift */; }; + FF70FAD02C90584900129CC2 /* SubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D892B7BBB6500ADC637 /* SubscriptionView.swift */; }; + FF70FAD12C90584900129CC2 /* TournamentLookUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD655D72C8DE27400E5B35E /* TournamentLookUpView.swift */; }; + FF70FAD22C90584900129CC2 /* ClubsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5562BAB3AED00FD8220 /* ClubsView.swift */; }; + FF70FAD32C90584900129CC2 /* ImagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE103112C366E5900684FC9 /* ImagePickerView.swift */; }; + FF70FAD42C90584900129CC2 /* MatchTypeSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F264E2BAE0B9600650388 /* MatchTypeSelectionView.swift */; }; + FF70FAD52C90584900129CC2 /* MatchSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D052BAF3C4200A9A3BD /* MatchSetupView.swift */; }; + FF70FAD62C90584900129CC2 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6B42B9248200002987F /* NetworkManager.swift */; }; + FF70FAD72C90584900129CC2 /* EventLinksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */; }; + FF70FAD82C90584900129CC2 /* SeedInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */; }; + FF70FAD92C90584900129CC2 /* TournamentClubSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE02BD0EB9000A86CF8 /* TournamentClubSettingsView.swift */; }; + FF70FADA2C90584900129CC2 /* GroupStageTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */; }; + FF70FADB2C90584900129CC2 /* RoundSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */; }; + FF70FADC2C90584900129CC2 /* SupportButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */; }; + FF70FADD2C90584900129CC2 /* TournamentBroadcastRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */; }; + FF70FADE2C90584900129CC2 /* TeamWeightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025ADE2BD0CE0A00A86CF8 /* TeamWeightView.swift */; }; + FF70FADF2C90584900129CC2 /* SeedsCallingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9268002BCE94920080F940 /* SeedsCallingView.swift */; }; + FF70FAE02C90584900129CC2 /* CallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9268082BCEDC2C0080F940 /* CallView.swift */; }; + FF70FAE12C90584900129CC2 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D732BB41DF8005CB568 /* Color+Extensions.swift */; }; + FF70FAE22C90584900129CC2 /* EditSharingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE1030F2C366DCD00684FC9 /* EditSharingView.swift */; }; + FF70FAE32C90584900129CC2 /* TournamentCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7091672B90F79F00AB08DA /* TournamentCellView.swift */; }; + FF70FAE42C90584900129CC2 /* Sequence+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC9032B9479F500EA7F5A /* Sequence+Extensions.swift */; }; + FF70FAE52C90584900129CC2 /* CashierDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9267F92BCE78EB0080F940 /* CashierDetailView.swift */; }; + FF70FAE62C90584900129CC2 /* EventClubSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE103072C353B7600684FC9 /* EventClubSettingsView.swift */; }; + FF70FAE72C90584900129CC2 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DB22B86387500ADC637 /* AccountView.swift */; }; + FF70FAE82C90584900129CC2 /* PlayersWithoutContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCEDA4B2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift */; }; + FF70FAE92C90584900129CC2 /* TeamsCallingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCD16B22C3E5E590092707B /* TeamsCallingView.swift */; }; + FF70FAEA2C90584900129CC2 /* Calendar+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC1C2BB53DC10036DAAB /* Calendar+Extensions.swift */; }; + FF70FAEB2C90584900129CC2 /* TeamScore.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CEF2BAECC0A00A9A3BD /* TeamScore.swift */; }; + FF70FAEC2C90584900129CC2 /* EditablePlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162822BCFBE4E000C4809 /* EditablePlayerView.swift */; }; + FF70FAED2C90584900129CC2 /* PlayerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162842BD00279000C4809 /* PlayerDetailView.swift */; }; + FF70FAEE2C90584900129CC2 /* ListRowViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D752BB428B2005CB568 /* ListRowViewModifier.swift */; }; + FF70FAEF2C90584900129CC2 /* PresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FF2B94794700EA7F5A /* PresentationContext.swift */; }; + FF70FAF02C90584900129CC2 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */; }; + FF70FAF12C90584900129CC2 /* SwiftParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0EC51D2BB16F680056B6D1 /* SwiftParser.swift */; }; + FF70FAF22C90584900129CC2 /* ChangePasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DA82B85F82100ADC637 /* ChangePasswordView.swift */; }; + FF70FAF32C90584900129CC2 /* TournamentSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8044AB2C8F676D00A49A52 /* TournamentSubscriptionView.swift */; }; + FF70FAF42C90584900129CC2 /* RoundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D502BB8087E00750834 /* RoundView.swift */; }; + FF70FAF52C90584900129CC2 /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; }; + FF70FAF62C90584900129CC2 /* SendToAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */; }; + FF70FAF72C90584900129CC2 /* EventCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263A2BAD528600650388 /* EventCreationView.swift */; }; + FF70FAF82C90584900129CC2 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; }; + FF70FAF92C90584900129CC2 /* CapsuleViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C01D972C481C0C0059087C /* CapsuleViewModifier.swift */; }; + FF70FAFA2C90584900129CC2 /* BroadcastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087EB2BE26A2F004E1E47 /* BroadcastView.swift */; }; + FF70FAFB2C90584900129CC2 /* SchedulerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF964542BC266CF00EEF017 /* SchedulerView.swift */; }; + FF70FAFC2C90584900129CC2 /* PadelClubButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA1B1282BB71773006CE248 /* PadelClubButtonView.swift */; }; + FF70FAFD2C90584900129CC2 /* TournamentSeedEditing.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */; }; + FF70FAFE2C90584900129CC2 /* TournamentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF70916B2B91005400AB08DA /* TournamentView.swift */; }; + FF70FAFF2C90584900129CC2 /* OngoingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30552BD95B1100F2B93D /* OngoingView.swift */; }; + FF70FB002C90584900129CC2 /* CreateClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5542BAB36DD00FD8220 /* CreateClubView.swift */; }; + FF70FB012C90584900129CC2 /* APICallsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4607A7C2C04DDE2004CB781 /* APICallsListView.swift */; }; + FF70FB022C90584900129CC2 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; }; + FF70FB032C90584900129CC2 /* DurationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AEE2BD1AE9400A86CF8 /* DurationSettingsView.swift */; }; + FF70FB042C90584900129CC2 /* AppScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AEC2BD1513700A86CF8 /* AppScreen.swift */; }; + FF70FB052C90584900129CC2 /* CourtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B022BD85E2400B29808 /* CourtView.swift */; }; + FF70FB062C90584900129CC2 /* PointSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC00D2BBC3D4600B82851 /* PointSelectionView.swift */; }; + FF70FB072C90584900129CC2 /* TeamRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EB52BB00A3800F0AEC7 /* TeamRowView.swift */; }; + FF70FB082C90584900129CC2 /* ContactManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF92680A2BCEE3E10080F940 /* ContactManager.swift */; }; + FF70FB092C90584900129CC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD2F22C412681000DBD9A /* AppDelegate.swift */; }; + FF70FB0A2C90584900129CC2 /* SubscriptionInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0252BD80AE80077B5AA /* SubscriptionInfoView.swift */; }; + FF70FB0B2C90584900129CC2 /* EditScoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0012BBC39A600B82851 /* EditScoreView.swift */; }; + FF70FB0C2C90584900129CC2 /* TournamentOrganizerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7091612B90F04300AB08DA /* TournamentOrganizerView.swift */; }; + FF70FB0D2C90584900129CC2 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF92680C2BCEE5EA0080F940 /* NetworkMonitor.swift */; }; + FF70FB0E2C90584900129CC2 /* TournamentRunningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF52BAED51600A9A3BD /* TournamentRunningView.swift */; }; + FF70FB0F2C90584900129CC2 /* TournamentDatePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F264A2BAE0B4100650388 /* TournamentDatePickerView.swift */; }; + FF70FB102C90584900129CC2 /* CourtAvailabilitySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF116E22BD2AF4800A33B06 /* CourtAvailabilitySettingsView.swift */; }; + FF70FB112C90584900129CC2 /* ConfirmButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8C2BF2C7601E80046B243 /* ConfirmButtonView.swift */; }; + FF70FB122C90584900129CC2 /* PasswordField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FC2E262C2AABC90021F3BF /* PasswordField.swift */; }; + FF70FB132C90584900129CC2 /* MatchRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CFF2BAEEF6400A9A3BD /* MatchRowView.swift */; }; + FF70FB142C90584900129CC2 /* Locale+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44B79102BBDA63A00906534 /* Locale+Extensions.swift */; }; + FF70FB152C90584900129CC2 /* HtmlService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B732BFA00FC000B4573 /* HtmlService.swift */; }; + FF70FB162C90584900129CC2 /* GroupStage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CE72BAEC70100A9A3BD /* GroupStage.swift */; }; + FF70FB172C90584900129CC2 /* TournamentCashierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162802BCF945C000C4809 /* TournamentCashierView.swift */; }; + FF70FB182C90584900129CC2 /* StoreManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D8D2B7BBBEC00ADC637 /* StoreManager.swift */; }; + FF70FB192C90584900129CC2 /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6BA2B9256D50002987F /* SearchViewModel.swift */; }; + FF70FB1A2C90584900129CC2 /* PlayerRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF12BAECC0B00A9A3BD /* PlayerRegistration.swift */; }; + FF70FB1B2C90584900129CC2 /* ImportedPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6BE2B92577A0002987F /* ImportedPlayerView.swift */; }; + FF70FB1C2C90584900129CC2 /* EditingTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162862BD004AD000C4809 /* EditingTeamView.swift */; }; + FF70FB1D2C90584900129CC2 /* NetworkManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC9052B947A1000EA7F5A /* NetworkManagerError.swift */; }; + FF70FB1E2C90584900129CC2 /* Tournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D592B6D383C00ADC637 /* Tournament.swift */; }; + FF70FB1F2C90584900129CC2 /* TournamentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FC2E2A2C2C0E4D0021F3BF /* TournamentStore.swift */; }; + FF70FB202C90584900129CC2 /* LoserRoundSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5647122C0B6F380081F995 /* LoserRoundSettingsView.swift */; }; + FF70FB212C90584900129CC2 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3795652B9399AA004EA093 /* Persistence.swift */; }; + FF70FB222C90584900129CC2 /* CloseDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCF76062C3BE9BC006C8C3D /* CloseDatePicker.swift */; }; + FF70FB232C90584900129CC2 /* BarButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DF49A2BD8D23900822FA0 /* BarButtonView.swift */; }; + FF70FB242C90584900129CC2 /* PlanningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF9644F2BC25E3700EEF017 /* PlanningView.swift */; }; + FF70FB252C90584900129CC2 /* Match.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CEB2BAECB9900A9A3BD /* Match.swift */; }; + FF70FB262C90584900129CC2 /* TournamentLevelPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26492BAE0B4100650388 /* TournamentLevelPickerView.swift */; }; + FF70FB272C90584900129CC2 /* FederalTournamentHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC212BB53E590036DAAB /* FederalTournamentHolder.swift */; }; + FF70FB282C90584900129CC2 /* DataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */; }; + FF70FB292C90584900129CC2 /* SetDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */; }; + FF70FB2A2C90584900129CC2 /* TeamHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AD72BD0C10F00A86CF8 /* TeamHeaderView.swift */; }; + FF70FB2B2C90584900129CC2 /* OrganizedTournamentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */; }; + FF70FB2C2C90584900129CC2 /* RoundScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF964562BC26B3400EEF017 /* RoundScheduleEditorView.swift */; }; + FF70FB2D2C90584900129CC2 /* EventListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB22B90EFAC0061EFF9 /* EventListView.swift */; }; + FF70FB2E2C90584900129CC2 /* TournamentConfiguratorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263C2BAD627A00650388 /* TournamentConfiguratorView.swift */; }; + FF70FB2F2C90584900129CC2 /* ClubImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E10B2BAC7FB0008D6F59 /* ClubImportView.swift */; }; + FF70FB302C90584900129CC2 /* UnderlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF558C622C6CDD020071F9AE /* UnderlineView.swift */; }; + FF70FB312C90584900129CC2 /* MatchScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */; }; + FF70FB322C90584900129CC2 /* CallMessageCustomizationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162792BCF8109000C4809 /* CallMessageCustomizationView.swift */; }; + FF70FB332C90584900129CC2 /* TournamentStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */; }; + FF70FB342C90584900129CC2 /* MatchTeamDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025ADA2BD0C2D000A86CF8 /* MatchTeamDetailView.swift */; }; + FF70FB352C90584900129CC2 /* GenericDestinationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */; }; + FF70FB362C90584900129CC2 /* DateInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF116E02BD2A9B600A33B06 /* DateInterval.swift */; }; + FF70FB372C90584900129CC2 /* TableStructureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26532BAE1E4400650388 /* TableStructureView.swift */; }; + FF70FB382C90584900129CC2 /* Purchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45BAE432BCA753E002EEC8A /* Purchase.swift */; }; + FF70FB392C90584900129CC2 /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FD2B94792300EA7F5A /* Screen.swift */; }; + FF70FB3A2C90584900129CC2 /* Round.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CED2BAECBD700A9A3BD /* Round.swift */; }; + FF70FB3B2C90584900129CC2 /* FederalDataViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5BAF6D2BE0B3C8008B4B7E /* FederalDataViewModel.swift */; }; + FF70FB3C2C90584900129CC2 /* AgendaDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3F74FE2B91A2D4004CFE0E /* AgendaDestination.swift */; }; + FF70FB3D2C90584900129CC2 /* PadelClubApp.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */; }; + FF70FB3E2C90584900129CC2 /* SetInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0152BBC5A4C00B82851 /* SetInputView.swift */; }; + FF70FB3F2C90584900129CC2 /* ButtonValidateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF03C932BD91D0C00B516FC /* ButtonValidateView.swift */; }; + FF70FB402C90584900129CC2 /* ClubRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D882BB4935C005CB568 /* ClubRowView.swift */; }; + FF70FB412C90584900129CC2 /* ClubDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5502BAB351300FD8220 /* ClubDetailView.swift */; }; + FF70FB422C90584900129CC2 /* GroupStageCallingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9268022BCE94A30080F940 /* GroupStageCallingView.swift */; }; + FF70FB432C90584900129CC2 /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0432BE286780077B5AA /* Key.swift */; }; + FF70FB442C90584900129CC2 /* CashierSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF11627C2BCF941A000C4809 /* CashierSettingsView.swift */; }; + FF70FB452C90584900129CC2 /* LoserRoundScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFFCDE0D2BCC833600317DEF /* LoserRoundScheduleEditorView.swift */; }; + FF70FB462C90584900129CC2 /* Club.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D622B6D3D6500ADC637 /* Club.swift */; }; + FF70FB472C90584900129CC2 /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC90A2B947AC000EA7F5A /* Array+Extensions.swift */; }; + FF70FB482C90584900129CC2 /* ToolboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB82B90EFD70061EFF9 /* ToolboxView.swift */; }; + FF70FB492C90584900129CC2 /* Alphabet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8E1CE52C006E0200184680 /* Alphabet.swift */; }; + FF70FB4A2C90584900129CC2 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD82B923F3C008466FA /* String+Extensions.swift */; }; + FF70FB4B2C90584900129CC2 /* GroupStageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCB74122C4625BB008384D0 /* GroupStageSettingsView.swift */; }; + FF70FB4C2C90584900129CC2 /* TournamentGeneralSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE42BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift */; }; + FF70FB4D2C90584900129CC2 /* LoserRoundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC2DCB12BBE75D40046DB9F /* LoserRoundView.swift */; }; + FF70FB4E2C90584900129CC2 /* AddTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF90FC1C2C44FB3E009339B2 /* AddTeamView.swift */; }; + FF70FB4F2C90584900129CC2 /* PlayerPayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9267FB2BCE84870080F940 /* PlayerPayView.swift */; }; + FF70FB502C90584900129CC2 /* PlanningByCourtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2B51542C7A4DAF00FFF126 /* PlanningByCourtView.swift */; }; + FF70FB512C90584900129CC2 /* FileImportManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */; }; + FF70FB522C90584900129CC2 /* TournamentButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */; }; + FF70FB532C90584900129CC2 /* FederalPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACCC2B92367B008466FA /* FederalPlayer.swift */; }; + FF70FB542C90584900129CC2 /* NavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */; }; + FF70FB552C90584900129CC2 /* FixedWidthInteger+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC9082B947A5300EA7F5A /* FixedWidthInteger+Extensions.swift */; }; + FF70FB562C90584900129CC2 /* LoserBracketFromGroupStageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6525C22C8C61B400B9498E /* LoserBracketFromGroupStageView.swift */; }; + FF70FB572C90584900129CC2 /* ImportedPlayer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30502BD94E1000F2B93D /* ImportedPlayer+Extensions.swift */; }; + FF70FB582C90584900129CC2 /* ClubSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */; }; + FF70FB592C90584900129CC2 /* PlayerPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EBA2BB0120700F0AEC7 /* PlayerPopoverView.swift */; }; + FF70FB5A2C90584900129CC2 /* InscriptionManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF70916D2B9108C600AB08DA /* InscriptionManagerView.swift */; }; + FF70FB5B2C90584900129CC2 /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */; }; + FF70FB5C2C90584900129CC2 /* MySortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */; }; + FF70FB5D2C90584900129CC2 /* CalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D8A2BB4D1E3005CB568 /* CalendarView.swift */; }; + FF70FB5E2C90584900129CC2 /* FederalTournamentSearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */; }; + FF70FB5F2C90584900129CC2 /* TournamentFieldsManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26462BAE0ACB00650388 /* TournamentFieldsManagerView.swift */; }; + FF70FB602C90584900129CC2 /* PrintSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B812BFA0124000B4573 /* PrintSettingsView.swift */; }; + FF70FB612C90584900129CC2 /* TournamentMatchFormatsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE22BD0EBA900A86CF8 /* TournamentMatchFormatsSettingsView.swift */; }; + FF70FB622C90584900129CC2 /* DateUpdateManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162892BD05247000C4809 /* DateUpdateManagerView.swift */; }; + FF70FB632C90584900129CC2 /* MatchTypeSmallSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0192BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift */; }; + FF70FB642C90584900129CC2 /* MonthData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE82BD1307E00A86CF8 /* MonthData.swift */; }; + FF70FB652C90584900129CC2 /* MenuWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */; }; + FF70FB662C90584900129CC2 /* TournamentBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B6C2BF9E60B000B4573 /* TournamentBuildView.swift */; }; + FF70FB672C90584900129CC2 /* TeamPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0A2BAF3D4C00A9A3BD /* TeamPickerView.swift */; }; + FF70FB682C90584900129CC2 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; }; + FF70FB692C90584900129CC2 /* EventTournamentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */; }; + FF70FB6A2C90584900129CC2 /* DisplayContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC55A2BAB80C400FD8220 /* DisplayContext.swift */; }; + FF70FB6B2C90584900129CC2 /* TournamentCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9268062BCE94D90080F940 /* TournamentCallView.swift */; }; + FF70FB6C2C90584900129CC2 /* LoserRoundsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC2DCB32BBE9ECD0046DB9F /* LoserRoundsView.swift */; }; + FF70FB6D2C90584900129CC2 /* GroupStagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */; }; + FF70FB6E2C90584900129CC2 /* PadelClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */; }; + FF70FB6F2C90584900129CC2 /* URLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF01A2BD6A1E80077B5AA /* URLs.swift */; }; + FF70FB702C90584900129CC2 /* MatchDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0132BBC59FC00B82851 /* MatchDescriptor.swift */; }; + FF70FB712C90584900129CC2 /* TournamentFormatSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26482BAE0B4100650388 /* TournamentFormatSelectionView.swift */; }; + FF70FB722C90584900129CC2 /* MatchListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065D2BBD8040009D6715 /* MatchListView.swift */; }; + FF70FB732C90584900129CC2 /* PadelClubApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C425D4002B6D249D002A7B48 /* PadelClubApp.swift */; }; + FF70FB742C90584900129CC2 /* TournamentSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26422BADFE5B00650388 /* TournamentSettingsView.swift */; }; + FF70FB752C90584900129CC2 /* String+Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF03B2BE15AF80077B5AA /* String+Crypto.swift */; }; + FF70FB762C90584900129CC2 /* GroupStageTeamReplacementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9AC3942BE3627B00C2E883 /* GroupStageTeamReplacementView.swift */; }; + FF70FB772C90584900129CC2 /* TabItemModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4C7F012BBBD7150031B6A3 /* TabItemModifier.swift */; }; + FF70FB782C90584900129CC2 /* DeferredViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */; }; + FF70FB792C90584900129CC2 /* TournamentScheduleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0E0B6C2BC254C6005F00A9 /* TournamentScheduleView.swift */; }; + FF70FB7A2C90584900129CC2 /* MatchFormatStorageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AF02BD1AEBD00A86CF8 /* MatchFormatStorageView.swift */; }; + FF70FB7B2C90584900129CC2 /* UmpireView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3F74F52B919E45004CFE0E /* UmpireView.swift */; }; + FF70FB7C2C90584900129CC2 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DAC2B85FCCD00ADC637 /* User.swift */; }; + FF70FB7D2C90584900129CC2 /* MatchSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D002BAEF0B400A9A3BD /* MatchSummaryView.swift */; }; + FF70FB7E2C90584900129CC2 /* TournamentDurationManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26442BAE0A3400650388 /* TournamentDurationManagerView.swift */; }; + FF70FB7F2C90584900129CC2 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5522BAB354A00FD8220 /* MockData.swift */; }; + FF70FB802C90584900129CC2 /* TeamDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D082BAF3D4000A9A3BD /* TeamDetailView.swift */; }; + FF70FB812C90584900129CC2 /* GroupStagesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA18E2BB9268800A33061 /* GroupStagesSettingsView.swift */; }; + FF70FB822C90584900129CC2 /* TournamentFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */; }; + FF70FB832C90584900129CC2 /* HtmlGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B722BFA00FB000B4573 /* HtmlGenerator.swift */; }; + FF70FB842C90584900129CC2 /* PadelRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26352BAD523300650388 /* PadelRule.swift */; }; + FF70FB852C90584900129CC2 /* TeamRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF02BAECC0B00A9A3BD /* TeamRegistration.swift */; }; + FF70FB862C90584900129CC2 /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACDA2B923F48008466FA /* Date+Extensions.swift */; }; + FF70FB872C90584900129CC2 /* GroupStageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CFA2BAEE13800A9A3BD /* GroupStageView.swift */; }; + FF70FB882C90584900129CC2 /* Tips.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5582BAB767000FD8220 /* Tips.swift */; }; + FF70FB892C90584900129CC2 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB62B90EFBF0061EFF9 /* MainView.swift */; }; + FF70FB8A2C90584900129CC2 /* MatchDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0C2BAF3EB200A9A3BD /* MatchDateView.swift */; }; + FF70FB8B2C90584900129CC2 /* PlanningSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF964522BC262B000EEF017 /* PlanningSettingsView.swift */; }; + FF70FB8C2C90584900129CC2 /* MatchScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF527D52BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift */; }; + FF70FB8D2C90584900129CC2 /* FortuneWheelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91AF82BD6A09100B29808 /* FortuneWheelView.swift */; }; + FF70FB8E2C90584900129CC2 /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD52B923960008466FA /* URL+Extensions.swift */; }; + FF70FB8F2C90584900129CC2 /* LoadingViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C493B37D2C10AD3600862481 /* LoadingViewModifier.swift */; }; + FF70FB902C90584900129CC2 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EBC2BB0287D00F0AEC7 /* PlayerView.swift */; }; + FF70FB912C90584900129CC2 /* MatchDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D022BAEF0C000A9A3BD /* MatchDetailView.swift */; }; + FF70FB922C90584900129CC2 /* ExportFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF1D2CA2C4A22B200C8D33D /* ExportFormat.swift */; }; + FF70FB932C90584900129CC2 /* PlayerBlockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0E2BAF63B000A9A3BD /* PlayerBlockView.swift */; }; + FF70FB942C90584900129CC2 /* StoreItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D8F2B7BBBEC00ADC637 /* StoreItem.swift */; }; + FF70FB952C90584900129CC2 /* Selectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8702BBADDE200A0EF4F /* Selectable.swift */; }; + FF70FB962C90584900129CC2 /* PointView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0112BBC3E1A00B82851 /* PointView.swift */; }; + FF70FB972C90584900129CC2 /* ClubHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC202BB53E590036DAAB /* ClubHolder.swift */; }; + FF70FB982C90584900129CC2 /* EventSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41852BF75FDA001B24CB /* EventSettingsView.swift */; }; + FF70FB992C90584900129CC2 /* InscriptionInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D772BB42C5B005CB568 /* InscriptionInfoView.swift */; }; + FF70FB9A2C90584900129CC2 /* SelectablePlayerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6BC2B9256E10002987F /* SelectablePlayerListView.swift */; }; + FF70FB9B2C90584900129CC2 /* MatchFormatPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26502BAE0BAD00650388 /* MatchFormatPickerView.swift */; }; + FF70FB9C2C90584900129CC2 /* TournamentRankView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5BAF712BE19274008B4B7E /* TournamentRankView.swift */; }; + FF70FB9D2C90584900129CC2 /* NumberFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D862BB48AFD005CB568 /* NumberFormatter+Extensions.swift */; }; + FF70FB9E2C90584900129CC2 /* SetLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0172BBC5A6800B82851 /* SetLabelView.swift */; }; + FF70FB9F2C90584900129CC2 /* DebugSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4489BE12C05BF5000043F3D /* DebugSettingsView.swift */; }; + FF70FBA02C90584900129CC2 /* GroupStageScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF9645A2BC2D53B00EEF017 /* GroupStageScheduleEditorView.swift */; }; + FF70FBA12C90584900129CC2 /* EventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41812BF73EB3001B24CB /* EventView.swift */; }; + FF70FBA22C90584900129CC2 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DA52B83948E00ADC637 /* LoginView.swift */; }; + FF70FBA32C90584900129CC2 /* Court.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B002BD85C2F00B29808 /* Court.swift */; }; + FF70FBA42C90584900129CC2 /* Labels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF72BAEDF0000A9A3BD /* Labels.swift */; }; + FF70FBA52C90584900129CC2 /* CopyPasteButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCB74162C480411008384D0 /* CopyPasteButtonView.swift */; }; + FF70FBA62C90584900129CC2 /* PlayerSexPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EB32BB0020000F0AEC7 /* PlayerSexPickerView.swift */; }; + FF70FBA72C90584900129CC2 /* TournamentInscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B702BF9EFE9000B4573 /* TournamentInscriptionView.swift */; }; + FF70FBA82C90584900129CC2 /* CallSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9267FE2BCE94830080F940 /* CallSettingsView.swift */; }; + FF70FBA92C90584900129CC2 /* FooterButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025ADC2BD0C94300A86CF8 /* FooterButtonView.swift */; }; + FF70FBAA2C90584900129CC2 /* RankCalculatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D822BB48997005CB568 /* RankCalculatorView.swift */; }; + FF70FBAB2C90584900129CC2 /* DateBoxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7091692B90F95E00AB08DA /* DateBoxView.swift */; }; + FF70FBAC2C90584900129CC2 /* LearnMoreSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D6F2BB3EFA5005CB568 /* LearnMoreSheetView.swift */; }; + FF70FBAD2C90584900129CC2 /* SourceFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD32B92392C008466FA /* SourceFileManager.swift */; }; + FF70FBAE2C90584900129CC2 /* PListReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC6F582BE92D88000CEAB4 /* PListReader.swift */; }; + FF70FBAF2C90584900129CC2 /* UpdateSourceRankDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0EC5212BB173E70056B6D1 /* UpdateSourceRankDateView.swift */; }; + FF70FBB02C90584900129CC2 /* GlobalSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE62BD1111000A86CF8 /* GlobalSettingsView.swift */; }; + FF70FBB12C90584900129CC2 /* Guard.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D8E2B7BBBEC00ADC637 /* Guard.swift */; }; + FF70FBB22C90584900129CC2 /* PurchaseListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0182BD694290077B5AA /* PurchaseListView.swift */; }; + FF70FBB42C90584900129CC2 /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = FF70FABE2C90584900129CC2 /* Algorithms */; }; + FF70FBB52C90584900129CC2 /* Zip in Frameworks */ = {isa = PBXBuildFile; productRef = FF70FAC02C90584900129CC2 /* Zip */; }; + FF70FBB62C90584900129CC2 /* LeStorage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C49EF0372BDFF3000077B5AA /* LeStorage.framework */; }; + FF70FBB82C90584900129CC2 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C425D4072B6D249E002A7B48 /* Preview Assets.xcassets */; }; + FF70FBB92C90584900129CC2 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */; }; + FF70FBBA2C90584900129CC2 /* tournament-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7F2BFA0105000B4573 /* tournament-template.html */; }; + FF70FBBB2C90584900129CC2 /* local.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = FF2B51602C7E302C00FFF126 /* local.sqlite */; }; + FF70FBBC2C90584900129CC2 /* groupstagescore-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7B2BFA0105000B4573 /* groupstagescore-template.html */; }; + FF70FBBD2C90584900129CC2 /* player-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7E2BFA0105000B4573 /* player-template.html */; }; + FF70FBBE2C90584900129CC2 /* groupstagerow-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7A2BFA0105000B4573 /* groupstagerow-template.html */; }; + FF70FBBF2C90584900129CC2 /* hiddenplayer-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7C2BFA0105000B4573 /* hiddenplayer-template.html */; }; + FF70FBC02C90584900129CC2 /* bracket-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B762BFA0105000B4573 /* bracket-template.html */; }; + FF70FBC12C90584900129CC2 /* groupstagecol-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B782BFA0105000B4573 /* groupstagecol-template.html */; }; + FF70FBC22C90584900129CC2 /* groupstage-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B772BFA0105000B4573 /* groupstage-template.html */; }; + FF70FBC32C90584900129CC2 /* groupstageentrant-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B792BFA0105000B4573 /* groupstageentrant-template.html */; }; + FF70FBC42C90584900129CC2 /* match-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7D2BFA0105000B4573 /* match-template.html */; }; + FF70FBC52C90584900129CC2 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFF0241D2BF48B15001F14B4 /* Localizable.strings */; }; + FF70FBC62C90584900129CC2 /* SyncedProducts.storekit in Resources */ = {isa = PBXBuildFile; fileRef = C45BAE3A2BC6DF10002EEC8A /* SyncedProducts.storekit */; }; + FF70FBC72C90584900129CC2 /* local.plist in Resources */ = {isa = PBXBuildFile; fileRef = C4EC6F562BE92CAC000CEAB4 /* local.plist */; }; + FF70FBC82C90584900129CC2 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = FF0CA5742BDA4AE10080E843 /* PrivacyInfo.xcprivacy */; }; + FF70FBC92C90584900129CC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C425D4042B6D249E002A7B48 /* Assets.xcassets */; }; + FF70FBCB2C90584900129CC2 /* LeStorage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C49EF0372BDFF3000077B5AA /* LeStorage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; FF8044AC2C8F676D00A49A52 /* TournamentSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8044AB2C8F676D00A49A52 /* TournamentSubscriptionView.swift */; }; - FF8044B42C90379300A49A52 /* UserCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D862B7BA36D00ADC637 /* UserCreationView.swift */; }; - FF8044B52C90379300A49A52 /* TabDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7091652B90F0B000AB08DA /* TabDestination.swift */; }; - FF8044B62C90379300A49A52 /* CashierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9267F72BCE78C70080F940 /* CashierView.swift */; }; - FF8044B72C90379300A49A52 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263E2BAD7D5C00650388 /* Event.swift */; }; - FF8044B82C90379300A49A52 /* PlayerHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30522BD94E2E00F2B93D /* PlayerHolder.swift */; }; - FF8044B92C90379300A49A52 /* LoserRoundStepScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF11628B2BD05267000C4809 /* LoserRoundStepScheduleEditorView.swift */; }; - FF8044BA2C90379300A49A52 /* ClubCourtSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF53FBB72BFB302B0051D4C3 /* ClubCourtSetupView.swift */; }; - FF8044BB2C90379300A49A52 /* Patcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B3A1542C2581DA0078EAA8 /* Patcher.swift */; }; - FF8044BC2C90379300A49A52 /* FileImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EBE2BB0B14600F0AEC7 /* FileImportView.swift */; }; - FF8044BD2C90379300A49A52 /* StepperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D9E2B7D0BCE00ADC637 /* StepperView.swift */; }; - FF8044BE2C90379300A49A52 /* RoundsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D4E2BB807D100750834 /* RoundsView.swift */; }; - FF8044BF2C90379300A49A52 /* FederalTournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC182BB53D1F0036DAAB /* FederalTournament.swift */; }; - FF8044C02C90379300A49A52 /* TournamentInitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26402BADFC8700650388 /* TournamentInitView.swift */; }; - FF8044C12C90379300A49A52 /* SubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D892B7BBB6500ADC637 /* SubscriptionView.swift */; }; - FF8044C22C90379300A49A52 /* ClubsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5562BAB3AED00FD8220 /* ClubsView.swift */; }; - FF8044C32C90379300A49A52 /* ImagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE103112C366E5900684FC9 /* ImagePickerView.swift */; }; - FF8044C42C90379300A49A52 /* MatchTypeSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F264E2BAE0B9600650388 /* MatchTypeSelectionView.swift */; }; - FF8044C52C90379300A49A52 /* MatchSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D052BAF3C4200A9A3BD /* MatchSetupView.swift */; }; - FF8044C62C90379300A49A52 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6B42B9248200002987F /* NetworkManager.swift */; }; - FF8044C72C90379300A49A52 /* EventLinksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */; }; - FF8044C82C90379300A49A52 /* SeedInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8742BBADDF700A0EF4F /* SeedInterval.swift */; }; - FF8044C92C90379300A49A52 /* TournamentClubSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE02BD0EB9000A86CF8 /* TournamentClubSettingsView.swift */; }; - FF8044CA2C90379300A49A52 /* GroupStageTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */; }; - FF8044CB2C90379300A49A52 /* RoundSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */; }; - FF8044CC2C90379300A49A52 /* SupportButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */; }; - FF8044CD2C90379300A49A52 /* TournamentBroadcastRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */; }; - FF8044CE2C90379300A49A52 /* TeamWeightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025ADE2BD0CE0A00A86CF8 /* TeamWeightView.swift */; }; - FF8044CF2C90379300A49A52 /* SeedsCallingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9268002BCE94920080F940 /* SeedsCallingView.swift */; }; - FF8044D02C90379300A49A52 /* CallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9268082BCEDC2C0080F940 /* CallView.swift */; }; - FF8044D12C90379300A49A52 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D732BB41DF8005CB568 /* Color+Extensions.swift */; }; - FF8044D22C90379300A49A52 /* EditSharingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE1030F2C366DCD00684FC9 /* EditSharingView.swift */; }; - FF8044D32C90379300A49A52 /* TournamentCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7091672B90F79F00AB08DA /* TournamentCellView.swift */; }; - FF8044D42C90379300A49A52 /* Sequence+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC9032B9479F500EA7F5A /* Sequence+Extensions.swift */; }; - FF8044D52C90379300A49A52 /* CashierDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9267F92BCE78EB0080F940 /* CashierDetailView.swift */; }; - FF8044D62C90379300A49A52 /* EventClubSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE103072C353B7600684FC9 /* EventClubSettingsView.swift */; }; - FF8044D72C90379300A49A52 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DB22B86387500ADC637 /* AccountView.swift */; }; - FF8044D82C90379300A49A52 /* PlayersWithoutContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCEDA4B2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift */; }; - FF8044D92C90379300A49A52 /* TeamsCallingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCD16B22C3E5E590092707B /* TeamsCallingView.swift */; }; - FF8044DA2C90379300A49A52 /* Calendar+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC1C2BB53DC10036DAAB /* Calendar+Extensions.swift */; }; - FF8044DB2C90379300A49A52 /* TeamScore.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CEF2BAECC0A00A9A3BD /* TeamScore.swift */; }; - FF8044DC2C90379300A49A52 /* EditablePlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162822BCFBE4E000C4809 /* EditablePlayerView.swift */; }; - FF8044DD2C90379300A49A52 /* PlayerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162842BD00279000C4809 /* PlayerDetailView.swift */; }; - FF8044DE2C90379300A49A52 /* ListRowViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D752BB428B2005CB568 /* ListRowViewModifier.swift */; }; - FF8044DF2C90379300A49A52 /* PresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FF2B94794700EA7F5A /* PresentationContext.swift */; }; - FF8044E02C90379300A49A52 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */; }; - FF8044E12C90379300A49A52 /* SwiftParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0EC51D2BB16F680056B6D1 /* SwiftParser.swift */; }; - FF8044E22C90379300A49A52 /* ChangePasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DA82B85F82100ADC637 /* ChangePasswordView.swift */; }; - FF8044E32C90379300A49A52 /* RoundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D502BB8087E00750834 /* RoundView.swift */; }; - FF8044E42C90379300A49A52 /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; }; - FF8044E52C90379300A49A52 /* SendToAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */; }; - FF8044E62C90379300A49A52 /* EventCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263A2BAD528600650388 /* EventCreationView.swift */; }; - FF8044E72C90379300A49A52 /* (null) in Sources */ = {isa = PBXBuildFile; }; - FF8044E82C90379300A49A52 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; }; - FF8044E92C90379300A49A52 /* CapsuleViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C01D972C481C0C0059087C /* CapsuleViewModifier.swift */; }; - FF8044EA2C90379300A49A52 /* BroadcastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087EB2BE26A2F004E1E47 /* BroadcastView.swift */; }; - FF8044EB2C90379300A49A52 /* SchedulerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF964542BC266CF00EEF017 /* SchedulerView.swift */; }; - FF8044EC2C90379300A49A52 /* PadelClubButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA1B1282BB71773006CE248 /* PadelClubButtonView.swift */; }; - FF8044ED2C90379300A49A52 /* TournamentSeedEditing.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */; }; - FF8044EE2C90379300A49A52 /* TournamentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF70916B2B91005400AB08DA /* TournamentView.swift */; }; - FF8044EF2C90379300A49A52 /* OngoingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30552BD95B1100F2B93D /* OngoingView.swift */; }; - FF8044F02C90379300A49A52 /* CreateClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5542BAB36DD00FD8220 /* CreateClubView.swift */; }; - FF8044F12C90379300A49A52 /* APICallsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4607A7C2C04DDE2004CB781 /* APICallsListView.swift */; }; - FF8044F22C90379300A49A52 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; }; - FF8044F32C90379300A49A52 /* DurationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AEE2BD1AE9400A86CF8 /* DurationSettingsView.swift */; }; - FF8044F42C90379300A49A52 /* AppScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AEC2BD1513700A86CF8 /* AppScreen.swift */; }; - FF8044F52C90379300A49A52 /* CourtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B022BD85E2400B29808 /* CourtView.swift */; }; - FF8044F62C90379300A49A52 /* PointSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC00D2BBC3D4600B82851 /* PointSelectionView.swift */; }; - FF8044F72C90379300A49A52 /* TeamRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EB52BB00A3800F0AEC7 /* TeamRowView.swift */; }; - FF8044F82C90379300A49A52 /* ContactManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF92680A2BCEE3E10080F940 /* ContactManager.swift */; }; - FF8044F92C90379300A49A52 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD2F22C412681000DBD9A /* AppDelegate.swift */; }; - FF8044FA2C90379300A49A52 /* SubscriptionInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0252BD80AE80077B5AA /* SubscriptionInfoView.swift */; }; - FF8044FB2C90379300A49A52 /* EditScoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0012BBC39A600B82851 /* EditScoreView.swift */; }; - FF8044FC2C90379300A49A52 /* TournamentOrganizerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7091612B90F04300AB08DA /* TournamentOrganizerView.swift */; }; - FF8044FD2C90379300A49A52 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF92680C2BCEE5EA0080F940 /* NetworkMonitor.swift */; }; - FF8044FE2C90379300A49A52 /* TournamentRunningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF52BAED51600A9A3BD /* TournamentRunningView.swift */; }; - FF8044FF2C90379300A49A52 /* TournamentDatePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F264A2BAE0B4100650388 /* TournamentDatePickerView.swift */; }; - FF8045002C90379300A49A52 /* CourtAvailabilitySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF116E22BD2AF4800A33B06 /* CourtAvailabilitySettingsView.swift */; }; - FF8045012C90379300A49A52 /* ConfirmButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8C2BF2C7601E80046B243 /* ConfirmButtonView.swift */; }; - FF8045022C90379300A49A52 /* PasswordField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FC2E262C2AABC90021F3BF /* PasswordField.swift */; }; - FF8045032C90379300A49A52 /* MatchRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CFF2BAEEF6400A9A3BD /* MatchRowView.swift */; }; - FF8045042C90379300A49A52 /* Locale+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44B79102BBDA63A00906534 /* Locale+Extensions.swift */; }; - FF8045052C90379300A49A52 /* HtmlService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B732BFA00FC000B4573 /* HtmlService.swift */; }; - FF8045062C90379300A49A52 /* GroupStage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CE72BAEC70100A9A3BD /* GroupStage.swift */; }; - FF8045072C90379300A49A52 /* TournamentCashierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162802BCF945C000C4809 /* TournamentCashierView.swift */; }; - FF8045082C90379300A49A52 /* StoreManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D8D2B7BBBEC00ADC637 /* StoreManager.swift */; }; - FF8045092C90379300A49A52 /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6BA2B9256D50002987F /* SearchViewModel.swift */; }; - FF80450A2C90379300A49A52 /* PlayerRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF12BAECC0B00A9A3BD /* PlayerRegistration.swift */; }; - FF80450B2C90379300A49A52 /* ImportedPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6BE2B92577A0002987F /* ImportedPlayerView.swift */; }; - FF80450C2C90379300A49A52 /* EditingTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162862BD004AD000C4809 /* EditingTeamView.swift */; }; - FF80450D2C90379300A49A52 /* NetworkManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC9052B947A1000EA7F5A /* NetworkManagerError.swift */; }; - FF80450E2C90379300A49A52 /* Tournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D592B6D383C00ADC637 /* Tournament.swift */; }; - FF80450F2C90379300A49A52 /* TournamentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FC2E2A2C2C0E4D0021F3BF /* TournamentStore.swift */; }; - FF8045102C90379300A49A52 /* LoserRoundSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5647122C0B6F380081F995 /* LoserRoundSettingsView.swift */; }; - FF8045112C90379300A49A52 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3795652B9399AA004EA093 /* Persistence.swift */; }; - FF8045122C90379300A49A52 /* CloseDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCF76062C3BE9BC006C8C3D /* CloseDatePicker.swift */; }; - FF8045132C90379300A49A52 /* BarButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DF49A2BD8D23900822FA0 /* BarButtonView.swift */; }; - FF8045142C90379300A49A52 /* PlanningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF9644F2BC25E3700EEF017 /* PlanningView.swift */; }; - FF8045152C90379300A49A52 /* Match.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CEB2BAECB9900A9A3BD /* Match.swift */; }; - FF8045162C90379300A49A52 /* TournamentLevelPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26492BAE0B4100650388 /* TournamentLevelPickerView.swift */; }; - FF8045172C90379300A49A52 /* FederalTournamentHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC212BB53E590036DAAB /* FederalTournamentHolder.swift */; }; - FF8045182C90379300A49A52 /* DataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */; }; - FF8045192C90379300A49A52 /* SetDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */; }; - FF80451A2C90379300A49A52 /* TeamHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AD72BD0C10F00A86CF8 /* TeamHeaderView.swift */; }; - FF80451B2C90379300A49A52 /* OrganizedTournamentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */; }; - FF80451C2C90379300A49A52 /* RoundScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF964562BC26B3400EEF017 /* RoundScheduleEditorView.swift */; }; - FF80451D2C90379300A49A52 /* EventListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB22B90EFAC0061EFF9 /* EventListView.swift */; }; - FF80451E2C90379300A49A52 /* TournamentConfiguratorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263C2BAD627A00650388 /* TournamentConfiguratorView.swift */; }; - FF80451F2C90379300A49A52 /* ClubImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E10B2BAC7FB0008D6F59 /* ClubImportView.swift */; }; - FF8045202C90379300A49A52 /* UnderlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF558C622C6CDD020071F9AE /* UnderlineView.swift */; }; - FF8045212C90379300A49A52 /* MatchScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */; }; - FF8045222C90379300A49A52 /* CallMessageCustomizationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162792BCF8109000C4809 /* CallMessageCustomizationView.swift */; }; - FF8045232C90379300A49A52 /* TournamentStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */; }; - FF8045242C90379300A49A52 /* MatchTeamDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025ADA2BD0C2D000A86CF8 /* MatchTeamDetailView.swift */; }; - FF8045252C90379300A49A52 /* GenericDestinationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */; }; - FF8045262C90379300A49A52 /* DateInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF116E02BD2A9B600A33B06 /* DateInterval.swift */; }; - FF8045272C90379300A49A52 /* TableStructureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26532BAE1E4400650388 /* TableStructureView.swift */; }; - FF8045282C90379300A49A52 /* Purchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45BAE432BCA753E002EEC8A /* Purchase.swift */; }; - FF8045292C90379300A49A52 /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FD2B94792300EA7F5A /* Screen.swift */; }; - FF80452A2C90379300A49A52 /* Round.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CED2BAECBD700A9A3BD /* Round.swift */; }; - FF80452B2C90379300A49A52 /* FederalDataViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5BAF6D2BE0B3C8008B4B7E /* FederalDataViewModel.swift */; }; - FF80452C2C90379300A49A52 /* AgendaDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3F74FE2B91A2D4004CFE0E /* AgendaDestination.swift */; }; - FF80452D2C90379300A49A52 /* PadelClubApp.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */; }; - FF80452E2C90379300A49A52 /* SetInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0152BBC5A4C00B82851 /* SetInputView.swift */; }; - FF80452F2C90379300A49A52 /* ButtonValidateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF03C932BD91D0C00B516FC /* ButtonValidateView.swift */; }; - FF8045302C90379300A49A52 /* ClubRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D882BB4935C005CB568 /* ClubRowView.swift */; }; - FF8045312C90379300A49A52 /* ClubDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5502BAB351300FD8220 /* ClubDetailView.swift */; }; - FF8045322C90379300A49A52 /* GroupStageCallingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9268022BCE94A30080F940 /* GroupStageCallingView.swift */; }; - FF8045332C90379300A49A52 /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0432BE286780077B5AA /* Key.swift */; }; - FF8045342C90379300A49A52 /* CashierSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF11627C2BCF941A000C4809 /* CashierSettingsView.swift */; }; - FF8045352C90379300A49A52 /* LoserRoundScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFFCDE0D2BCC833600317DEF /* LoserRoundScheduleEditorView.swift */; }; - FF8045362C90379300A49A52 /* Club.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D622B6D3D6500ADC637 /* Club.swift */; }; - FF8045372C90379300A49A52 /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC90A2B947AC000EA7F5A /* Array+Extensions.swift */; }; - FF8045382C90379300A49A52 /* ToolboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB82B90EFD70061EFF9 /* ToolboxView.swift */; }; - FF8045392C90379300A49A52 /* Alphabet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8E1CE52C006E0200184680 /* Alphabet.swift */; }; - FF80453A2C90379300A49A52 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD82B923F3C008466FA /* String+Extensions.swift */; }; - FF80453B2C90379300A49A52 /* GroupStageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCB74122C4625BB008384D0 /* GroupStageSettingsView.swift */; }; - FF80453C2C90379300A49A52 /* TournamentGeneralSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE42BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift */; }; - FF80453D2C90379300A49A52 /* LoserRoundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC2DCB12BBE75D40046DB9F /* LoserRoundView.swift */; }; - FF80453E2C90379300A49A52 /* AddTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF90FC1C2C44FB3E009339B2 /* AddTeamView.swift */; }; - FF80453F2C90379300A49A52 /* PlayerPayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9267FB2BCE84870080F940 /* PlayerPayView.swift */; }; - FF8045402C90379300A49A52 /* PlanningByCourtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2B51542C7A4DAF00FFF126 /* PlanningByCourtView.swift */; }; - FF8045412C90379300A49A52 /* FileImportManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */; }; - FF8045422C90379300A49A52 /* TournamentButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */; }; - FF8045432C90379300A49A52 /* FederalPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACCC2B92367B008466FA /* FederalPlayer.swift */; }; - FF8045442C90379300A49A52 /* NavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */; }; - FF8045452C90379300A49A52 /* FixedWidthInteger+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC9082B947A5300EA7F5A /* FixedWidthInteger+Extensions.swift */; }; - FF8045462C90379300A49A52 /* ImportedPlayer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30502BD94E1000F2B93D /* ImportedPlayer+Extensions.swift */; }; - FF8045472C90379300A49A52 /* ClubSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */; }; - FF8045482C90379300A49A52 /* PlayerPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EBA2BB0120700F0AEC7 /* PlayerPopoverView.swift */; }; - FF8045492C90379300A49A52 /* InscriptionManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF70916D2B9108C600AB08DA /* InscriptionManagerView.swift */; }; - FF80454A2C90379300A49A52 /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */; }; - FF80454B2C90379300A49A52 /* MySortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */; }; - FF80454C2C90379300A49A52 /* CalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D8A2BB4D1E3005CB568 /* CalendarView.swift */; }; - FF80454D2C90379300A49A52 /* FederalTournamentSearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */; }; - FF80454E2C90379300A49A52 /* TournamentFieldsManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26462BAE0ACB00650388 /* TournamentFieldsManagerView.swift */; }; - FF80454F2C90379300A49A52 /* PrintSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B812BFA0124000B4573 /* PrintSettingsView.swift */; }; - FF8045502C90379300A49A52 /* TournamentMatchFormatsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE22BD0EBA900A86CF8 /* TournamentMatchFormatsSettingsView.swift */; }; - FF8045512C90379300A49A52 /* DateUpdateManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162892BD05247000C4809 /* DateUpdateManagerView.swift */; }; - FF8045522C90379300A49A52 /* MatchTypeSmallSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0192BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift */; }; - FF8045532C90379300A49A52 /* MonthData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE82BD1307E00A86CF8 /* MonthData.swift */; }; - FF8045542C90379300A49A52 /* MenuWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */; }; - FF8045552C90379300A49A52 /* TournamentBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B6C2BF9E60B000B4573 /* TournamentBuildView.swift */; }; - FF8045562C90379300A49A52 /* TeamPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0A2BAF3D4C00A9A3BD /* TeamPickerView.swift */; }; - FF8045572C90379300A49A52 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; }; - FF8045582C90379300A49A52 /* EventTournamentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */; }; - FF8045592C90379300A49A52 /* DisplayContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC55A2BAB80C400FD8220 /* DisplayContext.swift */; }; - FF80455A2C90379300A49A52 /* TournamentCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9268062BCE94D90080F940 /* TournamentCallView.swift */; }; - FF80455B2C90379300A49A52 /* LoserRoundsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC2DCB32BBE9ECD0046DB9F /* LoserRoundsView.swift */; }; - FF80455C2C90379300A49A52 /* GroupStagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */; }; - FF80455D2C90379300A49A52 /* PadelClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */; }; - FF80455E2C90379300A49A52 /* URLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF01A2BD6A1E80077B5AA /* URLs.swift */; }; - FF80455F2C90379300A49A52 /* MatchDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0132BBC59FC00B82851 /* MatchDescriptor.swift */; }; - FF8045602C90379300A49A52 /* TournamentFormatSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26482BAE0B4100650388 /* TournamentFormatSelectionView.swift */; }; - FF8045612C90379300A49A52 /* MatchListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065D2BBD8040009D6715 /* MatchListView.swift */; }; - FF8045622C90379300A49A52 /* PadelClubApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C425D4002B6D249D002A7B48 /* PadelClubApp.swift */; }; - FF8045632C90379300A49A52 /* TournamentSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26422BADFE5B00650388 /* TournamentSettingsView.swift */; }; - FF8045642C90379300A49A52 /* String+Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF03B2BE15AF80077B5AA /* String+Crypto.swift */; }; - FF8045652C90379300A49A52 /* GroupStageTeamReplacementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9AC3942BE3627B00C2E883 /* GroupStageTeamReplacementView.swift */; }; - FF8045662C90379300A49A52 /* TabItemModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4C7F012BBBD7150031B6A3 /* TabItemModifier.swift */; }; - FF8045672C90379300A49A52 /* DeferredViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */; }; - FF8045682C90379300A49A52 /* TournamentScheduleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0E0B6C2BC254C6005F00A9 /* TournamentScheduleView.swift */; }; - FF8045692C90379300A49A52 /* MatchFormatStorageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AF02BD1AEBD00A86CF8 /* MatchFormatStorageView.swift */; }; - FF80456A2C90379300A49A52 /* UmpireView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3F74F52B919E45004CFE0E /* UmpireView.swift */; }; - FF80456B2C90379300A49A52 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DAC2B85FCCD00ADC637 /* User.swift */; }; - FF80456C2C90379300A49A52 /* MatchSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D002BAEF0B400A9A3BD /* MatchSummaryView.swift */; }; - FF80456D2C90379300A49A52 /* TournamentDurationManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26442BAE0A3400650388 /* TournamentDurationManagerView.swift */; }; - FF80456E2C90379300A49A52 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5522BAB354A00FD8220 /* MockData.swift */; }; - FF80456F2C90379300A49A52 /* TeamDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D082BAF3D4000A9A3BD /* TeamDetailView.swift */; }; - FF8045702C90379300A49A52 /* GroupStagesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA18E2BB9268800A33061 /* GroupStagesSettingsView.swift */; }; - FF8045712C90379300A49A52 /* TournamentFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */; }; - FF8045722C90379300A49A52 /* HtmlGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B722BFA00FB000B4573 /* HtmlGenerator.swift */; }; - FF8045732C90379300A49A52 /* PadelRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26352BAD523300650388 /* PadelRule.swift */; }; - FF8045742C90379300A49A52 /* TeamRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF02BAECC0B00A9A3BD /* TeamRegistration.swift */; }; - FF8045752C90379300A49A52 /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACDA2B923F48008466FA /* Date+Extensions.swift */; }; - FF8045762C90379300A49A52 /* GroupStageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CFA2BAEE13800A9A3BD /* GroupStageView.swift */; }; - FF8045772C90379300A49A52 /* Tips.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC5582BAB767000FD8220 /* Tips.swift */; }; - FF8045782C90379300A49A52 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB62B90EFBF0061EFF9 /* MainView.swift */; }; - FF8045792C90379300A49A52 /* MatchDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0C2BAF3EB200A9A3BD /* MatchDateView.swift */; }; - FF80457A2C90379300A49A52 /* PlanningSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF964522BC262B000EEF017 /* PlanningSettingsView.swift */; }; - FF80457B2C90379300A49A52 /* MatchScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF527D52BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift */; }; - FF80457C2C90379300A49A52 /* FortuneWheelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91AF82BD6A09100B29808 /* FortuneWheelView.swift */; }; - FF80457D2C90379300A49A52 /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD52B923960008466FA /* URL+Extensions.swift */; }; - FF80457E2C90379300A49A52 /* LoadingViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C493B37D2C10AD3600862481 /* LoadingViewModifier.swift */; }; - FF80457F2C90379300A49A52 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EBC2BB0287D00F0AEC7 /* PlayerView.swift */; }; - FF8045802C90379300A49A52 /* MatchDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D022BAEF0C000A9A3BD /* MatchDetailView.swift */; }; - FF8045812C90379300A49A52 /* ExportFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF1D2CA2C4A22B200C8D33D /* ExportFormat.swift */; }; - FF8045822C90379300A49A52 /* PlayerBlockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0E2BAF63B000A9A3BD /* PlayerBlockView.swift */; }; - FF8045832C90379300A49A52 /* StoreItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D8F2B7BBBEC00ADC637 /* StoreItem.swift */; }; - FF8045842C90379300A49A52 /* Selectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB9C8702BBADDE200A0EF4F /* Selectable.swift */; }; - FF8045852C90379300A49A52 /* PointView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0112BBC3E1A00B82851 /* PointView.swift */; }; - FF8045862C90379300A49A52 /* ClubHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC202BB53E590036DAAB /* ClubHolder.swift */; }; - FF8045872C90379300A49A52 /* EventSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41852BF75FDA001B24CB /* EventSettingsView.swift */; }; - FF8045882C90379300A49A52 /* InscriptionInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D772BB42C5B005CB568 /* InscriptionInfoView.swift */; }; - FF8045892C90379300A49A52 /* SelectablePlayerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4AB6BC2B9256E10002987F /* SelectablePlayerListView.swift */; }; - FF80458A2C90379300A49A52 /* MatchFormatPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26502BAE0BAD00650388 /* MatchFormatPickerView.swift */; }; - FF80458B2C90379300A49A52 /* TournamentRankView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5BAF712BE19274008B4B7E /* TournamentRankView.swift */; }; - FF80458C2C90379300A49A52 /* NumberFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D862BB48AFD005CB568 /* NumberFormatter+Extensions.swift */; }; - FF80458D2C90379300A49A52 /* SetLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0172BBC5A6800B82851 /* SetLabelView.swift */; }; - FF80458E2C90379300A49A52 /* DebugSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4489BE12C05BF5000043F3D /* DebugSettingsView.swift */; }; - FF80458F2C90379300A49A52 /* GroupStageScheduleEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF9645A2BC2D53B00EEF017 /* GroupStageScheduleEditorView.swift */; }; - FF8045902C90379300A49A52 /* EventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41812BF73EB3001B24CB /* EventView.swift */; }; - FF8045912C90379300A49A52 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DA52B83948E00ADC637 /* LoginView.swift */; }; - FF8045922C90379300A49A52 /* Court.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B002BD85C2F00B29808 /* Court.swift */; }; - FF8045932C90379300A49A52 /* Labels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967CF72BAEDF0000A9A3BD /* Labels.swift */; }; - FF8045942C90379300A49A52 /* CopyPasteButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCB74162C480411008384D0 /* CopyPasteButtonView.swift */; }; - FF8045952C90379300A49A52 /* PlayerSexPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EB32BB0020000F0AEC7 /* PlayerSexPickerView.swift */; }; - FF8045962C90379300A49A52 /* TournamentInscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B702BF9EFE9000B4573 /* TournamentInscriptionView.swift */; }; - FF8045972C90379300A49A52 /* CallSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9267FE2BCE94830080F940 /* CallSettingsView.swift */; }; - FF8045982C90379300A49A52 /* FooterButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025ADC2BD0C94300A86CF8 /* FooterButtonView.swift */; }; - FF8045992C90379300A49A52 /* RankCalculatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D822BB48997005CB568 /* RankCalculatorView.swift */; }; - FF80459A2C90379300A49A52 /* DateBoxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7091692B90F95E00AB08DA /* DateBoxView.swift */; }; - FF80459B2C90379300A49A52 /* LearnMoreSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D6F2BB3EFA5005CB568 /* LearnMoreSheetView.swift */; }; - FF80459C2C90379300A49A52 /* SourceFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF8ACD32B92392C008466FA /* SourceFileManager.swift */; }; - FF80459D2C90379300A49A52 /* PListReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC6F582BE92D88000CEAB4 /* PListReader.swift */; }; - FF80459E2C90379300A49A52 /* UpdateSourceRankDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0EC5212BB173E70056B6D1 /* UpdateSourceRankDateView.swift */; }; - FF80459F2C90379300A49A52 /* GlobalSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AE62BD1111000A86CF8 /* GlobalSettingsView.swift */; }; - FF8045A02C90379300A49A52 /* Guard.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D8E2B7BBBEC00ADC637 /* Guard.swift */; }; - FF8045A12C90379300A49A52 /* PurchaseListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0182BD694290077B5AA /* PurchaseListView.swift */; }; - FF8045A32C90379300A49A52 /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = FF8044AF2C90379300A49A52 /* Algorithms */; }; - FF8045A42C90379300A49A52 /* Zip in Frameworks */ = {isa = PBXBuildFile; productRef = FF8044B12C90379300A49A52 /* Zip */; }; - FF8045A52C90379300A49A52 /* LeStorage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C49EF0372BDFF3000077B5AA /* LeStorage.framework */; }; - FF8045A72C90379300A49A52 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C425D4072B6D249E002A7B48 /* Preview Assets.xcassets */; }; - FF8045A82C90379300A49A52 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */; }; - FF8045A92C90379300A49A52 /* tournament-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7F2BFA0105000B4573 /* tournament-template.html */; }; - FF8045AA2C90379300A49A52 /* local.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = FF2B51602C7E302C00FFF126 /* local.sqlite */; }; - FF8045AB2C90379300A49A52 /* groupstagescore-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7B2BFA0105000B4573 /* groupstagescore-template.html */; }; - FF8045AC2C90379300A49A52 /* player-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7E2BFA0105000B4573 /* player-template.html */; }; - FF8045AD2C90379300A49A52 /* groupstagerow-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7A2BFA0105000B4573 /* groupstagerow-template.html */; }; - FF8045AE2C90379300A49A52 /* hiddenplayer-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7C2BFA0105000B4573 /* hiddenplayer-template.html */; }; - FF8045AF2C90379300A49A52 /* bracket-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B762BFA0105000B4573 /* bracket-template.html */; }; - FF8045B02C90379300A49A52 /* groupstagecol-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B782BFA0105000B4573 /* groupstagecol-template.html */; }; - FF8045B12C90379300A49A52 /* groupstage-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B772BFA0105000B4573 /* groupstage-template.html */; }; - FF8045B22C90379300A49A52 /* groupstageentrant-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B792BFA0105000B4573 /* groupstageentrant-template.html */; }; - FF8045B32C90379300A49A52 /* match-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7D2BFA0105000B4573 /* match-template.html */; }; - FF8045B42C90379300A49A52 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFF0241D2BF48B15001F14B4 /* Localizable.strings */; }; - FF8045B52C90379300A49A52 /* SyncedProducts.storekit in Resources */ = {isa = PBXBuildFile; fileRef = C45BAE3A2BC6DF10002EEC8A /* SyncedProducts.storekit */; }; - FF8045B62C90379300A49A52 /* local.plist in Resources */ = {isa = PBXBuildFile; fileRef = C4EC6F562BE92CAC000CEAB4 /* local.plist */; }; - FF8045B72C90379300A49A52 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = FF0CA5742BDA4AE10080E843 /* PrivacyInfo.xcprivacy */; }; - FF8045B82C90379300A49A52 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C425D4042B6D249E002A7B48 /* Assets.xcassets */; }; - FF8045BA2C90379300A49A52 /* LeStorage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C49EF0372BDFF3000077B5AA /* LeStorage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */; }; FF82CFC92B9132AF00B0CAF2 /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */; }; FF8E1CE62C006E0200184680 /* Alphabet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8E1CE52C006E0200184680 /* Alphabet.swift */; }; @@ -570,13 +569,13 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; - FF8045B92C90379300A49A52 /* Embed Frameworks */ = { + FF70FBCA2C90584900129CC2 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( - FF8045BA2C90379300A49A52 /* LeStorage.framework in Embed Frameworks */, + FF70FBCB2C90584900129CC2 /* LeStorage.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -784,8 +783,8 @@ FF7091692B90F95E00AB08DA /* DateBoxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateBoxView.swift; sourceTree = ""; }; FF70916B2B91005400AB08DA /* TournamentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentView.swift; sourceTree = ""; }; FF70916D2B9108C600AB08DA /* InscriptionManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InscriptionManagerView.swift; sourceTree = ""; }; + FF70FBCF2C90584900129CC2 /* PadelClub TestFlight.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PadelClub TestFlight.app"; sourceTree = BUILT_PRODUCTS_DIR; }; FF8044AB2C8F676D00A49A52 /* TournamentSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentSubscriptionView.swift; sourceTree = ""; }; - FF8045BE2C90379300A49A52 /* PadelClub TestFlight.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PadelClub TestFlight.app"; sourceTree = BUILT_PRODUCTS_DIR; }; FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrganizedTournamentView.swift; sourceTree = ""; }; FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = ""; }; FF8E1CE52C006E0200184680 /* Alphabet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alphabet.swift; sourceTree = ""; }; @@ -928,13 +927,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - FF8045A22C90379300A49A52 /* Frameworks */ = { + FF70FBB32C90584900129CC2 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - FF8045A32C90379300A49A52 /* Algorithms in Frameworks */, - FF8045A42C90379300A49A52 /* Zip in Frameworks */, - FF8045A52C90379300A49A52 /* LeStorage.framework in Frameworks */, + FF70FBB42C90584900129CC2 /* Algorithms in Frameworks */, + FF70FBB52C90584900129CC2 /* Zip in Frameworks */, + FF70FBB62C90584900129CC2 /* LeStorage.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -958,7 +957,7 @@ C425D3FD2B6D249D002A7B48 /* PadelClub.app */, C425D40D2B6D249E002A7B48 /* PadelClubTests.xctest */, C425D4172B6D249E002A7B48 /* PadelClubUITests.xctest */, - FF8045BE2C90379300A49A52 /* PadelClub TestFlight.app */, + FF70FBCF2C90584900129CC2 /* PadelClub TestFlight.app */, ); name = Products; sourceTree = ""; @@ -1740,14 +1739,14 @@ productReference = C425D4172B6D249E002A7B48 /* PadelClubUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; - FF8044AE2C90379300A49A52 /* PadelClub TestFlight */ = { + FF70FABD2C90584900129CC2 /* PadelClub TestFlight */ = { isa = PBXNativeTarget; - buildConfigurationList = FF8045BB2C90379300A49A52 /* Build configuration list for PBXNativeTarget "PadelClub TestFlight" */; + buildConfigurationList = FF70FBCC2C90584900129CC2 /* Build configuration list for PBXNativeTarget "PadelClub TestFlight" */; buildPhases = ( - FF8044B32C90379300A49A52 /* Sources */, - FF8045A22C90379300A49A52 /* Frameworks */, - FF8045A62C90379300A49A52 /* Resources */, - FF8045B92C90379300A49A52 /* Embed Frameworks */, + FF70FAC22C90584900129CC2 /* Sources */, + FF70FBB32C90584900129CC2 /* Frameworks */, + FF70FBB72C90584900129CC2 /* Resources */, + FF70FBCA2C90584900129CC2 /* Embed Frameworks */, ); buildRules = ( ); @@ -1755,11 +1754,11 @@ ); name = "PadelClub TestFlight"; packageProductDependencies = ( - FF8044AF2C90379300A49A52 /* Algorithms */, - FF8044B12C90379300A49A52 /* Zip */, + FF70FABE2C90584900129CC2 /* Algorithms */, + FF70FAC02C90584900129CC2 /* Zip */, ); productName = PadelClub; - productReference = FF8045BE2C90379300A49A52 /* PadelClub TestFlight.app */; + productReference = FF70FBCF2C90584900129CC2 /* PadelClub TestFlight.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -1806,7 +1805,7 @@ C425D3FC2B6D249D002A7B48 /* PadelClub */, C425D40C2B6D249E002A7B48 /* PadelClubTests */, C425D4162B6D249E002A7B48 /* PadelClubUITests */, - FF8044AE2C90379300A49A52 /* PadelClub TestFlight */, + FF70FABD2C90584900129CC2 /* PadelClub TestFlight */, ); }; /* End PBXProject section */ @@ -1851,28 +1850,28 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - FF8045A62C90379300A49A52 /* Resources */ = { + FF70FBB72C90584900129CC2 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - FF8045A72C90379300A49A52 /* Preview Assets.xcassets in Resources */, - FF8045A82C90379300A49A52 /* Launch Screen.storyboard in Resources */, - FF8045A92C90379300A49A52 /* tournament-template.html in Resources */, - FF8045AA2C90379300A49A52 /* local.sqlite in Resources */, - FF8045AB2C90379300A49A52 /* groupstagescore-template.html in Resources */, - FF8045AC2C90379300A49A52 /* player-template.html in Resources */, - FF8045AD2C90379300A49A52 /* groupstagerow-template.html in Resources */, - FF8045AE2C90379300A49A52 /* hiddenplayer-template.html in Resources */, - FF8045AF2C90379300A49A52 /* bracket-template.html in Resources */, - FF8045B02C90379300A49A52 /* groupstagecol-template.html in Resources */, - FF8045B12C90379300A49A52 /* groupstage-template.html in Resources */, - FF8045B22C90379300A49A52 /* groupstageentrant-template.html in Resources */, - FF8045B32C90379300A49A52 /* match-template.html in Resources */, - FF8045B42C90379300A49A52 /* Localizable.strings in Resources */, - FF8045B52C90379300A49A52 /* SyncedProducts.storekit in Resources */, - FF8045B62C90379300A49A52 /* local.plist in Resources */, - FF8045B72C90379300A49A52 /* PrivacyInfo.xcprivacy in Resources */, - FF8045B82C90379300A49A52 /* Assets.xcassets in Resources */, + FF70FBB82C90584900129CC2 /* Preview Assets.xcassets in Resources */, + FF70FBB92C90584900129CC2 /* Launch Screen.storyboard in Resources */, + FF70FBBA2C90584900129CC2 /* tournament-template.html in Resources */, + FF70FBBB2C90584900129CC2 /* local.sqlite in Resources */, + FF70FBBC2C90584900129CC2 /* groupstagescore-template.html in Resources */, + FF70FBBD2C90584900129CC2 /* player-template.html in Resources */, + FF70FBBE2C90584900129CC2 /* groupstagerow-template.html in Resources */, + FF70FBBF2C90584900129CC2 /* hiddenplayer-template.html in Resources */, + FF70FBC02C90584900129CC2 /* bracket-template.html in Resources */, + FF70FBC12C90584900129CC2 /* groupstagecol-template.html in Resources */, + FF70FBC22C90584900129CC2 /* groupstage-template.html in Resources */, + FF70FBC32C90584900129CC2 /* groupstageentrant-template.html in Resources */, + FF70FBC42C90584900129CC2 /* match-template.html in Resources */, + FF70FBC52C90584900129CC2 /* Localizable.strings in Resources */, + FF70FBC62C90584900129CC2 /* SyncedProducts.storekit in Resources */, + FF70FBC72C90584900129CC2 /* local.plist in Resources */, + FF70FBC82C90584900129CC2 /* PrivacyInfo.xcprivacy in Resources */, + FF70FBC92C90584900129CC2 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2147,251 +2146,250 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - FF8044B32C90379300A49A52 /* Sources */ = { + FF70FAC22C90584900129CC2 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - FF8044B42C90379300A49A52 /* UserCreationView.swift in Sources */, - FF8044B52C90379300A49A52 /* TabDestination.swift in Sources */, - FF8044B62C90379300A49A52 /* CashierView.swift in Sources */, - FF8044B72C90379300A49A52 /* Event.swift in Sources */, - FF8044B82C90379300A49A52 /* PlayerHolder.swift in Sources */, - FF8044B92C90379300A49A52 /* LoserRoundStepScheduleEditorView.swift in Sources */, - FF8044BA2C90379300A49A52 /* ClubCourtSetupView.swift in Sources */, - FF8044BB2C90379300A49A52 /* Patcher.swift in Sources */, - FF6D2D3B2C90447900CCF14B /* LoserBracketFromGroupStageView.swift in Sources */, - FF8044BC2C90379300A49A52 /* FileImportView.swift in Sources */, - FF8044BD2C90379300A49A52 /* StepperView.swift in Sources */, - FF8044BE2C90379300A49A52 /* RoundsView.swift in Sources */, - FF8044BF2C90379300A49A52 /* FederalTournament.swift in Sources */, - FF8044C02C90379300A49A52 /* TournamentInitView.swift in Sources */, - FF8044C12C90379300A49A52 /* SubscriptionView.swift in Sources */, - FF6D2D392C90444A00CCF14B /* TournamentLookUpView.swift in Sources */, - FF8044C22C90379300A49A52 /* ClubsView.swift in Sources */, - FF8044C32C90379300A49A52 /* ImagePickerView.swift in Sources */, - FF8044C42C90379300A49A52 /* MatchTypeSelectionView.swift in Sources */, - FF8044C52C90379300A49A52 /* MatchSetupView.swift in Sources */, - FF8044C62C90379300A49A52 /* NetworkManager.swift in Sources */, - FF8044C72C90379300A49A52 /* EventLinksView.swift in Sources */, - FF8044C82C90379300A49A52 /* SeedInterval.swift in Sources */, - FF8044C92C90379300A49A52 /* TournamentClubSettingsView.swift in Sources */, - FF8044CA2C90379300A49A52 /* GroupStageTeamView.swift in Sources */, - FF8044CB2C90379300A49A52 /* RoundSettingsView.swift in Sources */, - FF8044CC2C90379300A49A52 /* SupportButtonView.swift in Sources */, - FF8044CD2C90379300A49A52 /* TournamentBroadcastRowView.swift in Sources */, - FF8044CE2C90379300A49A52 /* TeamWeightView.swift in Sources */, - FF8044CF2C90379300A49A52 /* SeedsCallingView.swift in Sources */, - FF8044D02C90379300A49A52 /* CallView.swift in Sources */, - FF8044D12C90379300A49A52 /* Color+Extensions.swift in Sources */, - FF8044D22C90379300A49A52 /* EditSharingView.swift in Sources */, - FF8044D32C90379300A49A52 /* TournamentCellView.swift in Sources */, - FF8044D42C90379300A49A52 /* Sequence+Extensions.swift in Sources */, - FF8044D52C90379300A49A52 /* CashierDetailView.swift in Sources */, - FF8044D62C90379300A49A52 /* EventClubSettingsView.swift in Sources */, - FF8044D72C90379300A49A52 /* AccountView.swift in Sources */, - FF8044D82C90379300A49A52 /* PlayersWithoutContactView.swift in Sources */, - FF8044D92C90379300A49A52 /* TeamsCallingView.swift in Sources */, - FF8044DA2C90379300A49A52 /* Calendar+Extensions.swift in Sources */, - FF8044DB2C90379300A49A52 /* TeamScore.swift in Sources */, - FF8044DC2C90379300A49A52 /* EditablePlayerView.swift in Sources */, - FF8044DD2C90379300A49A52 /* PlayerDetailView.swift in Sources */, - FF8044DE2C90379300A49A52 /* ListRowViewModifier.swift in Sources */, - FF8044DF2C90379300A49A52 /* PresentationContext.swift in Sources */, - FF8044E02C90379300A49A52 /* AppSettings.swift in Sources */, - FF8044E12C90379300A49A52 /* SwiftParser.swift in Sources */, - FF8044E22C90379300A49A52 /* ChangePasswordView.swift in Sources */, - FF6D2D3A2C90446700CCF14B /* TournamentSubscriptionView.swift in Sources */, - FF8044E32C90379300A49A52 /* RoundView.swift in Sources */, - FF8044E42C90379300A49A52 /* RowButtonView.swift in Sources */, - FF8044E52C90379300A49A52 /* SendToAllView.swift in Sources */, - FF8044E62C90379300A49A52 /* EventCreationView.swift in Sources */, - FF8044E72C90379300A49A52 /* (null) in Sources */, - FF8044E82C90379300A49A52 /* LocationManager.swift in Sources */, - FF8044E92C90379300A49A52 /* CapsuleViewModifier.swift in Sources */, - FF8044EA2C90379300A49A52 /* BroadcastView.swift in Sources */, - FF8044EB2C90379300A49A52 /* SchedulerView.swift in Sources */, - FF8044EC2C90379300A49A52 /* PadelClubButtonView.swift in Sources */, - FF8044ED2C90379300A49A52 /* TournamentSeedEditing.swift in Sources */, - FF8044EE2C90379300A49A52 /* TournamentView.swift in Sources */, - FF8044EF2C90379300A49A52 /* OngoingView.swift in Sources */, - FF8044F02C90379300A49A52 /* CreateClubView.swift in Sources */, - FF8044F12C90379300A49A52 /* APICallsListView.swift in Sources */, - FF8044F22C90379300A49A52 /* NetworkFederalService.swift in Sources */, - FF8044F32C90379300A49A52 /* DurationSettingsView.swift in Sources */, - FF8044F42C90379300A49A52 /* AppScreen.swift in Sources */, - FF8044F52C90379300A49A52 /* CourtView.swift in Sources */, - FF8044F62C90379300A49A52 /* PointSelectionView.swift in Sources */, - FF8044F72C90379300A49A52 /* TeamRowView.swift in Sources */, - FF8044F82C90379300A49A52 /* ContactManager.swift in Sources */, - FF8044F92C90379300A49A52 /* AppDelegate.swift in Sources */, - FF8044FA2C90379300A49A52 /* SubscriptionInfoView.swift in Sources */, - FF8044FB2C90379300A49A52 /* EditScoreView.swift in Sources */, - FF8044FC2C90379300A49A52 /* TournamentOrganizerView.swift in Sources */, - FF8044FD2C90379300A49A52 /* NetworkMonitor.swift in Sources */, - FF8044FE2C90379300A49A52 /* TournamentRunningView.swift in Sources */, - FF8044FF2C90379300A49A52 /* TournamentDatePickerView.swift in Sources */, - FF8045002C90379300A49A52 /* CourtAvailabilitySettingsView.swift in Sources */, - FF8045012C90379300A49A52 /* ConfirmButtonView.swift in Sources */, - FF8045022C90379300A49A52 /* PasswordField.swift in Sources */, - FF8045032C90379300A49A52 /* MatchRowView.swift in Sources */, - FF8045042C90379300A49A52 /* Locale+Extensions.swift in Sources */, - FF8045052C90379300A49A52 /* HtmlService.swift in Sources */, - FF8045062C90379300A49A52 /* GroupStage.swift in Sources */, - FF8045072C90379300A49A52 /* TournamentCashierView.swift in Sources */, - FF8045082C90379300A49A52 /* StoreManager.swift in Sources */, - FF8045092C90379300A49A52 /* SearchViewModel.swift in Sources */, - FF80450A2C90379300A49A52 /* PlayerRegistration.swift in Sources */, - FF80450B2C90379300A49A52 /* ImportedPlayerView.swift in Sources */, - FF80450C2C90379300A49A52 /* EditingTeamView.swift in Sources */, - FF80450D2C90379300A49A52 /* NetworkManagerError.swift in Sources */, - FF80450E2C90379300A49A52 /* Tournament.swift in Sources */, - FF80450F2C90379300A49A52 /* TournamentStore.swift in Sources */, - FF8045102C90379300A49A52 /* LoserRoundSettingsView.swift in Sources */, - FF8045112C90379300A49A52 /* Persistence.swift in Sources */, - FF8045122C90379300A49A52 /* CloseDatePicker.swift in Sources */, - FF8045132C90379300A49A52 /* BarButtonView.swift in Sources */, - FF8045142C90379300A49A52 /* PlanningView.swift in Sources */, - FF8045152C90379300A49A52 /* Match.swift in Sources */, - FF8045162C90379300A49A52 /* TournamentLevelPickerView.swift in Sources */, - FF8045172C90379300A49A52 /* FederalTournamentHolder.swift in Sources */, - FF8045182C90379300A49A52 /* DataStore.swift in Sources */, - FF8045192C90379300A49A52 /* SetDescriptor.swift in Sources */, - FF80451A2C90379300A49A52 /* TeamHeaderView.swift in Sources */, - FF80451B2C90379300A49A52 /* OrganizedTournamentView.swift in Sources */, - FF80451C2C90379300A49A52 /* RoundScheduleEditorView.swift in Sources */, - FF80451D2C90379300A49A52 /* EventListView.swift in Sources */, - FF80451E2C90379300A49A52 /* TournamentConfiguratorView.swift in Sources */, - FF80451F2C90379300A49A52 /* ClubImportView.swift in Sources */, - FF8045202C90379300A49A52 /* UnderlineView.swift in Sources */, - FF8045212C90379300A49A52 /* MatchScheduler.swift in Sources */, - FF8045222C90379300A49A52 /* CallMessageCustomizationView.swift in Sources */, - FF8045232C90379300A49A52 /* TournamentStatusView.swift in Sources */, - FF8045242C90379300A49A52 /* MatchTeamDetailView.swift in Sources */, - FF8045252C90379300A49A52 /* GenericDestinationPickerView.swift in Sources */, - FF8045262C90379300A49A52 /* DateInterval.swift in Sources */, - FF8045272C90379300A49A52 /* TableStructureView.swift in Sources */, - FF8045282C90379300A49A52 /* Purchase.swift in Sources */, - FF8045292C90379300A49A52 /* Screen.swift in Sources */, - FF80452A2C90379300A49A52 /* Round.swift in Sources */, - FF80452B2C90379300A49A52 /* FederalDataViewModel.swift in Sources */, - FF80452C2C90379300A49A52 /* AgendaDestination.swift in Sources */, - FF80452D2C90379300A49A52 /* PadelClubApp.xcdatamodeld in Sources */, - FF80452E2C90379300A49A52 /* SetInputView.swift in Sources */, - FF80452F2C90379300A49A52 /* ButtonValidateView.swift in Sources */, - FF8045302C90379300A49A52 /* ClubRowView.swift in Sources */, - FF8045312C90379300A49A52 /* ClubDetailView.swift in Sources */, - FF8045322C90379300A49A52 /* GroupStageCallingView.swift in Sources */, - FF8045332C90379300A49A52 /* Key.swift in Sources */, - FF8045342C90379300A49A52 /* CashierSettingsView.swift in Sources */, - FF8045352C90379300A49A52 /* LoserRoundScheduleEditorView.swift in Sources */, - FF8045362C90379300A49A52 /* Club.swift in Sources */, - FF8045372C90379300A49A52 /* Array+Extensions.swift in Sources */, - FF8045382C90379300A49A52 /* ToolboxView.swift in Sources */, - FF8045392C90379300A49A52 /* Alphabet.swift in Sources */, - FF80453A2C90379300A49A52 /* String+Extensions.swift in Sources */, - FF80453B2C90379300A49A52 /* GroupStageSettingsView.swift in Sources */, - FF80453C2C90379300A49A52 /* TournamentGeneralSettingsView.swift in Sources */, - FF80453D2C90379300A49A52 /* LoserRoundView.swift in Sources */, - FF80453E2C90379300A49A52 /* AddTeamView.swift in Sources */, - FF80453F2C90379300A49A52 /* PlayerPayView.swift in Sources */, - FF8045402C90379300A49A52 /* PlanningByCourtView.swift in Sources */, - FF8045412C90379300A49A52 /* FileImportManager.swift in Sources */, - FF8045422C90379300A49A52 /* TournamentButtonView.swift in Sources */, - FF8045432C90379300A49A52 /* FederalPlayer.swift in Sources */, - FF8045442C90379300A49A52 /* NavigationViewModel.swift in Sources */, - FF8045452C90379300A49A52 /* FixedWidthInteger+Extensions.swift in Sources */, - FF8045462C90379300A49A52 /* ImportedPlayer+Extensions.swift in Sources */, - FF8045472C90379300A49A52 /* ClubSearchView.swift in Sources */, - FF8045482C90379300A49A52 /* PlayerPopoverView.swift in Sources */, - FF8045492C90379300A49A52 /* InscriptionManagerView.swift in Sources */, - FF80454A2C90379300A49A52 /* ActivityView.swift in Sources */, - FF80454B2C90379300A49A52 /* MySortDescriptor.swift in Sources */, - FF80454C2C90379300A49A52 /* CalendarView.swift in Sources */, - FF80454D2C90379300A49A52 /* FederalTournamentSearchScope.swift in Sources */, - FF80454E2C90379300A49A52 /* TournamentFieldsManagerView.swift in Sources */, - FF80454F2C90379300A49A52 /* PrintSettingsView.swift in Sources */, - FF8045502C90379300A49A52 /* TournamentMatchFormatsSettingsView.swift in Sources */, - FF8045512C90379300A49A52 /* DateUpdateManagerView.swift in Sources */, - FF8045522C90379300A49A52 /* MatchTypeSmallSelectionView.swift in Sources */, - FF8045532C90379300A49A52 /* MonthData.swift in Sources */, - FF8045542C90379300A49A52 /* MenuWarningView.swift in Sources */, - FF8045552C90379300A49A52 /* TournamentBuildView.swift in Sources */, - FF8045562C90379300A49A52 /* TeamPickerView.swift in Sources */, - FF8045572C90379300A49A52 /* CloudConvert.swift in Sources */, - FF8045582C90379300A49A52 /* EventTournamentsView.swift in Sources */, - FF8045592C90379300A49A52 /* DisplayContext.swift in Sources */, - FF80455A2C90379300A49A52 /* TournamentCallView.swift in Sources */, - FF80455B2C90379300A49A52 /* LoserRoundsView.swift in Sources */, - FF80455C2C90379300A49A52 /* GroupStagesView.swift in Sources */, - FF80455D2C90379300A49A52 /* PadelClubView.swift in Sources */, - FF80455E2C90379300A49A52 /* URLs.swift in Sources */, - FF80455F2C90379300A49A52 /* MatchDescriptor.swift in Sources */, - FF8045602C90379300A49A52 /* TournamentFormatSelectionView.swift in Sources */, - FF8045612C90379300A49A52 /* MatchListView.swift in Sources */, - FF8045622C90379300A49A52 /* PadelClubApp.swift in Sources */, - FF8045632C90379300A49A52 /* TournamentSettingsView.swift in Sources */, - FF8045642C90379300A49A52 /* String+Crypto.swift in Sources */, - FF8045652C90379300A49A52 /* GroupStageTeamReplacementView.swift in Sources */, - FF8045662C90379300A49A52 /* TabItemModifier.swift in Sources */, - FF8045672C90379300A49A52 /* DeferredViewModifier.swift in Sources */, - FF8045682C90379300A49A52 /* TournamentScheduleView.swift in Sources */, - FF8045692C90379300A49A52 /* MatchFormatStorageView.swift in Sources */, - FF80456A2C90379300A49A52 /* UmpireView.swift in Sources */, - FF80456B2C90379300A49A52 /* User.swift in Sources */, - FF80456C2C90379300A49A52 /* MatchSummaryView.swift in Sources */, - FF80456D2C90379300A49A52 /* TournamentDurationManagerView.swift in Sources */, - FF80456E2C90379300A49A52 /* MockData.swift in Sources */, - FF80456F2C90379300A49A52 /* TeamDetailView.swift in Sources */, - FF8045702C90379300A49A52 /* GroupStagesSettingsView.swift in Sources */, - FF8045712C90379300A49A52 /* TournamentFilterView.swift in Sources */, - FF8045722C90379300A49A52 /* HtmlGenerator.swift in Sources */, - FF8045732C90379300A49A52 /* PadelRule.swift in Sources */, - FF8045742C90379300A49A52 /* TeamRegistration.swift in Sources */, - FF8045752C90379300A49A52 /* Date+Extensions.swift in Sources */, - FF8045762C90379300A49A52 /* GroupStageView.swift in Sources */, - FF8045772C90379300A49A52 /* Tips.swift in Sources */, - FF8045782C90379300A49A52 /* MainView.swift in Sources */, - FF8045792C90379300A49A52 /* MatchDateView.swift in Sources */, - FF80457A2C90379300A49A52 /* PlanningSettingsView.swift in Sources */, - FF80457B2C90379300A49A52 /* MatchScheduleEditorView.swift in Sources */, - FF80457C2C90379300A49A52 /* FortuneWheelView.swift in Sources */, - FF80457D2C90379300A49A52 /* URL+Extensions.swift in Sources */, - FF80457E2C90379300A49A52 /* LoadingViewModifier.swift in Sources */, - FF80457F2C90379300A49A52 /* PlayerView.swift in Sources */, - FF8045802C90379300A49A52 /* MatchDetailView.swift in Sources */, - FF8045812C90379300A49A52 /* ExportFormat.swift in Sources */, - FF8045822C90379300A49A52 /* PlayerBlockView.swift in Sources */, - FF8045832C90379300A49A52 /* StoreItem.swift in Sources */, - FF8045842C90379300A49A52 /* Selectable.swift in Sources */, - FF8045852C90379300A49A52 /* PointView.swift in Sources */, - FF8045862C90379300A49A52 /* ClubHolder.swift in Sources */, - FF8045872C90379300A49A52 /* EventSettingsView.swift in Sources */, - FF8045882C90379300A49A52 /* InscriptionInfoView.swift in Sources */, - FF8045892C90379300A49A52 /* SelectablePlayerListView.swift in Sources */, - FF80458A2C90379300A49A52 /* MatchFormatPickerView.swift in Sources */, - FF80458B2C90379300A49A52 /* TournamentRankView.swift in Sources */, - FF80458C2C90379300A49A52 /* NumberFormatter+Extensions.swift in Sources */, - FF80458D2C90379300A49A52 /* SetLabelView.swift in Sources */, - FF80458E2C90379300A49A52 /* DebugSettingsView.swift in Sources */, - FF80458F2C90379300A49A52 /* GroupStageScheduleEditorView.swift in Sources */, - FF8045902C90379300A49A52 /* EventView.swift in Sources */, - FF8045912C90379300A49A52 /* LoginView.swift in Sources */, - FF8045922C90379300A49A52 /* Court.swift in Sources */, - FF8045932C90379300A49A52 /* Labels.swift in Sources */, - FF8045942C90379300A49A52 /* CopyPasteButtonView.swift in Sources */, - FF8045952C90379300A49A52 /* PlayerSexPickerView.swift in Sources */, - FF8045962C90379300A49A52 /* TournamentInscriptionView.swift in Sources */, - FF8045972C90379300A49A52 /* CallSettingsView.swift in Sources */, - FF8045982C90379300A49A52 /* FooterButtonView.swift in Sources */, - FF8045992C90379300A49A52 /* RankCalculatorView.swift in Sources */, - FF80459A2C90379300A49A52 /* DateBoxView.swift in Sources */, - FF80459B2C90379300A49A52 /* LearnMoreSheetView.swift in Sources */, - FF80459C2C90379300A49A52 /* SourceFileManager.swift in Sources */, - FF80459D2C90379300A49A52 /* PListReader.swift in Sources */, - FF80459E2C90379300A49A52 /* UpdateSourceRankDateView.swift in Sources */, - FF80459F2C90379300A49A52 /* GlobalSettingsView.swift in Sources */, - FF8045A02C90379300A49A52 /* Guard.swift in Sources */, - FF8045A12C90379300A49A52 /* PurchaseListView.swift in Sources */, + FF70FAC32C90584900129CC2 /* UserCreationView.swift in Sources */, + FF70FAC42C90584900129CC2 /* TabDestination.swift in Sources */, + FF70FAC52C90584900129CC2 /* CashierView.swift in Sources */, + FF70FAC62C90584900129CC2 /* Event.swift in Sources */, + FF70FAC72C90584900129CC2 /* PlayerHolder.swift in Sources */, + FF70FAC82C90584900129CC2 /* LoserRoundStepScheduleEditorView.swift in Sources */, + FF70FAC92C90584900129CC2 /* ClubCourtSetupView.swift in Sources */, + FF70FACA2C90584900129CC2 /* Patcher.swift in Sources */, + FF70FACB2C90584900129CC2 /* FileImportView.swift in Sources */, + FF70FACC2C90584900129CC2 /* StepperView.swift in Sources */, + FF70FACD2C90584900129CC2 /* RoundsView.swift in Sources */, + FF70FACE2C90584900129CC2 /* FederalTournament.swift in Sources */, + FF70FACF2C90584900129CC2 /* TournamentInitView.swift in Sources */, + FF70FAD02C90584900129CC2 /* SubscriptionView.swift in Sources */, + FF70FAD12C90584900129CC2 /* TournamentLookUpView.swift in Sources */, + FF70FAD22C90584900129CC2 /* ClubsView.swift in Sources */, + FF70FAD32C90584900129CC2 /* ImagePickerView.swift in Sources */, + FF70FAD42C90584900129CC2 /* MatchTypeSelectionView.swift in Sources */, + FF70FAD52C90584900129CC2 /* MatchSetupView.swift in Sources */, + FF70FAD62C90584900129CC2 /* NetworkManager.swift in Sources */, + FF70FAD72C90584900129CC2 /* EventLinksView.swift in Sources */, + FF70FAD82C90584900129CC2 /* SeedInterval.swift in Sources */, + FF70FAD92C90584900129CC2 /* TournamentClubSettingsView.swift in Sources */, + FF70FADA2C90584900129CC2 /* GroupStageTeamView.swift in Sources */, + FF70FADB2C90584900129CC2 /* RoundSettingsView.swift in Sources */, + FF70FADC2C90584900129CC2 /* SupportButtonView.swift in Sources */, + FF70FADD2C90584900129CC2 /* TournamentBroadcastRowView.swift in Sources */, + FF70FADE2C90584900129CC2 /* TeamWeightView.swift in Sources */, + FF70FADF2C90584900129CC2 /* SeedsCallingView.swift in Sources */, + FF70FAE02C90584900129CC2 /* CallView.swift in Sources */, + FF70FAE12C90584900129CC2 /* Color+Extensions.swift in Sources */, + FF70FAE22C90584900129CC2 /* EditSharingView.swift in Sources */, + FF70FAE32C90584900129CC2 /* TournamentCellView.swift in Sources */, + FF70FAE42C90584900129CC2 /* Sequence+Extensions.swift in Sources */, + FF70FAE52C90584900129CC2 /* CashierDetailView.swift in Sources */, + FF70FAE62C90584900129CC2 /* EventClubSettingsView.swift in Sources */, + FF70FAE72C90584900129CC2 /* AccountView.swift in Sources */, + FF70FAE82C90584900129CC2 /* PlayersWithoutContactView.swift in Sources */, + FF70FAE92C90584900129CC2 /* TeamsCallingView.swift in Sources */, + FF70FAEA2C90584900129CC2 /* Calendar+Extensions.swift in Sources */, + FF70FAEB2C90584900129CC2 /* TeamScore.swift in Sources */, + FF70FAEC2C90584900129CC2 /* EditablePlayerView.swift in Sources */, + FF70FAED2C90584900129CC2 /* PlayerDetailView.swift in Sources */, + FF70FAEE2C90584900129CC2 /* ListRowViewModifier.swift in Sources */, + FF70FAEF2C90584900129CC2 /* PresentationContext.swift in Sources */, + FF70FAF02C90584900129CC2 /* AppSettings.swift in Sources */, + FF70FAF12C90584900129CC2 /* SwiftParser.swift in Sources */, + FF70FAF22C90584900129CC2 /* ChangePasswordView.swift in Sources */, + FF70FAF32C90584900129CC2 /* TournamentSubscriptionView.swift in Sources */, + FF70FAF42C90584900129CC2 /* RoundView.swift in Sources */, + FF70FAF52C90584900129CC2 /* RowButtonView.swift in Sources */, + FF70FAF62C90584900129CC2 /* SendToAllView.swift in Sources */, + FF70FAF72C90584900129CC2 /* EventCreationView.swift in Sources */, + FF70FAF82C90584900129CC2 /* LocationManager.swift in Sources */, + FF70FAF92C90584900129CC2 /* CapsuleViewModifier.swift in Sources */, + FF70FAFA2C90584900129CC2 /* BroadcastView.swift in Sources */, + FF70FAFB2C90584900129CC2 /* SchedulerView.swift in Sources */, + FF70FAFC2C90584900129CC2 /* PadelClubButtonView.swift in Sources */, + FF70FAFD2C90584900129CC2 /* TournamentSeedEditing.swift in Sources */, + FF70FAFE2C90584900129CC2 /* TournamentView.swift in Sources */, + FF70FAFF2C90584900129CC2 /* OngoingView.swift in Sources */, + FF70FB002C90584900129CC2 /* CreateClubView.swift in Sources */, + FF70FB012C90584900129CC2 /* APICallsListView.swift in Sources */, + FF70FB022C90584900129CC2 /* NetworkFederalService.swift in Sources */, + FF70FB032C90584900129CC2 /* DurationSettingsView.swift in Sources */, + FF70FB042C90584900129CC2 /* AppScreen.swift in Sources */, + FF70FB052C90584900129CC2 /* CourtView.swift in Sources */, + FF70FB062C90584900129CC2 /* PointSelectionView.swift in Sources */, + FF70FB072C90584900129CC2 /* TeamRowView.swift in Sources */, + FF70FB082C90584900129CC2 /* ContactManager.swift in Sources */, + FF70FB092C90584900129CC2 /* AppDelegate.swift in Sources */, + FF70FB0A2C90584900129CC2 /* SubscriptionInfoView.swift in Sources */, + FF70FB0B2C90584900129CC2 /* EditScoreView.swift in Sources */, + FF70FB0C2C90584900129CC2 /* TournamentOrganizerView.swift in Sources */, + FF70FB0D2C90584900129CC2 /* NetworkMonitor.swift in Sources */, + FF70FB0E2C90584900129CC2 /* TournamentRunningView.swift in Sources */, + FF70FB0F2C90584900129CC2 /* TournamentDatePickerView.swift in Sources */, + FF70FB102C90584900129CC2 /* CourtAvailabilitySettingsView.swift in Sources */, + FF70FB112C90584900129CC2 /* ConfirmButtonView.swift in Sources */, + FF70FB122C90584900129CC2 /* PasswordField.swift in Sources */, + FF70FB132C90584900129CC2 /* MatchRowView.swift in Sources */, + FF70FB142C90584900129CC2 /* Locale+Extensions.swift in Sources */, + FF70FB152C90584900129CC2 /* HtmlService.swift in Sources */, + FF70FB162C90584900129CC2 /* GroupStage.swift in Sources */, + FF70FB172C90584900129CC2 /* TournamentCashierView.swift in Sources */, + FF70FB182C90584900129CC2 /* StoreManager.swift in Sources */, + FF70FB192C90584900129CC2 /* SearchViewModel.swift in Sources */, + FF70FB1A2C90584900129CC2 /* PlayerRegistration.swift in Sources */, + FF70FB1B2C90584900129CC2 /* ImportedPlayerView.swift in Sources */, + FF70FB1C2C90584900129CC2 /* EditingTeamView.swift in Sources */, + FF70FB1D2C90584900129CC2 /* NetworkManagerError.swift in Sources */, + FF70FB1E2C90584900129CC2 /* Tournament.swift in Sources */, + FF70FB1F2C90584900129CC2 /* TournamentStore.swift in Sources */, + FF70FB202C90584900129CC2 /* LoserRoundSettingsView.swift in Sources */, + FF70FB212C90584900129CC2 /* Persistence.swift in Sources */, + FF70FB222C90584900129CC2 /* CloseDatePicker.swift in Sources */, + FF70FB232C90584900129CC2 /* BarButtonView.swift in Sources */, + FF70FB242C90584900129CC2 /* PlanningView.swift in Sources */, + FF70FB252C90584900129CC2 /* Match.swift in Sources */, + FF70FB262C90584900129CC2 /* TournamentLevelPickerView.swift in Sources */, + FF70FB272C90584900129CC2 /* FederalTournamentHolder.swift in Sources */, + FF70FB282C90584900129CC2 /* DataStore.swift in Sources */, + FF70FB292C90584900129CC2 /* SetDescriptor.swift in Sources */, + FF70FB2A2C90584900129CC2 /* TeamHeaderView.swift in Sources */, + FF70FB2B2C90584900129CC2 /* OrganizedTournamentView.swift in Sources */, + FF70FB2C2C90584900129CC2 /* RoundScheduleEditorView.swift in Sources */, + FF70FB2D2C90584900129CC2 /* EventListView.swift in Sources */, + FF70FB2E2C90584900129CC2 /* TournamentConfiguratorView.swift in Sources */, + FF70FB2F2C90584900129CC2 /* ClubImportView.swift in Sources */, + FF70FB302C90584900129CC2 /* UnderlineView.swift in Sources */, + FF70FB312C90584900129CC2 /* MatchScheduler.swift in Sources */, + FF70FB322C90584900129CC2 /* CallMessageCustomizationView.swift in Sources */, + FF70FB332C90584900129CC2 /* TournamentStatusView.swift in Sources */, + FF70FB342C90584900129CC2 /* MatchTeamDetailView.swift in Sources */, + FF70FB352C90584900129CC2 /* GenericDestinationPickerView.swift in Sources */, + FF70FB362C90584900129CC2 /* DateInterval.swift in Sources */, + FF70FB372C90584900129CC2 /* TableStructureView.swift in Sources */, + FF70FB382C90584900129CC2 /* Purchase.swift in Sources */, + FF70FB392C90584900129CC2 /* Screen.swift in Sources */, + FF70FB3A2C90584900129CC2 /* Round.swift in Sources */, + FF70FB3B2C90584900129CC2 /* FederalDataViewModel.swift in Sources */, + FF70FB3C2C90584900129CC2 /* AgendaDestination.swift in Sources */, + FF70FB3D2C90584900129CC2 /* PadelClubApp.xcdatamodeld in Sources */, + FF70FB3E2C90584900129CC2 /* SetInputView.swift in Sources */, + FF70FB3F2C90584900129CC2 /* ButtonValidateView.swift in Sources */, + FF70FB402C90584900129CC2 /* ClubRowView.swift in Sources */, + FF70FB412C90584900129CC2 /* ClubDetailView.swift in Sources */, + FF70FB422C90584900129CC2 /* GroupStageCallingView.swift in Sources */, + FF70FB432C90584900129CC2 /* Key.swift in Sources */, + FF70FB442C90584900129CC2 /* CashierSettingsView.swift in Sources */, + FF70FB452C90584900129CC2 /* LoserRoundScheduleEditorView.swift in Sources */, + FF70FB462C90584900129CC2 /* Club.swift in Sources */, + FF70FB472C90584900129CC2 /* Array+Extensions.swift in Sources */, + FF70FB482C90584900129CC2 /* ToolboxView.swift in Sources */, + FF70FB492C90584900129CC2 /* Alphabet.swift in Sources */, + FF70FB4A2C90584900129CC2 /* String+Extensions.swift in Sources */, + FF70FB4B2C90584900129CC2 /* GroupStageSettingsView.swift in Sources */, + FF70FB4C2C90584900129CC2 /* TournamentGeneralSettingsView.swift in Sources */, + FF70FB4D2C90584900129CC2 /* LoserRoundView.swift in Sources */, + FF70FB4E2C90584900129CC2 /* AddTeamView.swift in Sources */, + FF70FB4F2C90584900129CC2 /* PlayerPayView.swift in Sources */, + FF70FB502C90584900129CC2 /* PlanningByCourtView.swift in Sources */, + FF70FB512C90584900129CC2 /* FileImportManager.swift in Sources */, + FF70FB522C90584900129CC2 /* TournamentButtonView.swift in Sources */, + FF70FB532C90584900129CC2 /* FederalPlayer.swift in Sources */, + FF70FB542C90584900129CC2 /* NavigationViewModel.swift in Sources */, + FF70FB552C90584900129CC2 /* FixedWidthInteger+Extensions.swift in Sources */, + FF70FB562C90584900129CC2 /* LoserBracketFromGroupStageView.swift in Sources */, + FF70FB572C90584900129CC2 /* ImportedPlayer+Extensions.swift in Sources */, + FF70FB582C90584900129CC2 /* ClubSearchView.swift in Sources */, + FF70FB592C90584900129CC2 /* PlayerPopoverView.swift in Sources */, + FF70FB5A2C90584900129CC2 /* InscriptionManagerView.swift in Sources */, + FF70FB5B2C90584900129CC2 /* ActivityView.swift in Sources */, + FF70FB5C2C90584900129CC2 /* MySortDescriptor.swift in Sources */, + FF70FB5D2C90584900129CC2 /* CalendarView.swift in Sources */, + FF70FB5E2C90584900129CC2 /* FederalTournamentSearchScope.swift in Sources */, + FF70FB5F2C90584900129CC2 /* TournamentFieldsManagerView.swift in Sources */, + FF70FB602C90584900129CC2 /* PrintSettingsView.swift in Sources */, + FF70FB612C90584900129CC2 /* TournamentMatchFormatsSettingsView.swift in Sources */, + FF70FB622C90584900129CC2 /* DateUpdateManagerView.swift in Sources */, + FF70FB632C90584900129CC2 /* MatchTypeSmallSelectionView.swift in Sources */, + FF70FB642C90584900129CC2 /* MonthData.swift in Sources */, + FF70FB652C90584900129CC2 /* MenuWarningView.swift in Sources */, + FF70FB662C90584900129CC2 /* TournamentBuildView.swift in Sources */, + FF70FB672C90584900129CC2 /* TeamPickerView.swift in Sources */, + FF70FB682C90584900129CC2 /* CloudConvert.swift in Sources */, + FF70FB692C90584900129CC2 /* EventTournamentsView.swift in Sources */, + FF70FB6A2C90584900129CC2 /* DisplayContext.swift in Sources */, + FF70FB6B2C90584900129CC2 /* TournamentCallView.swift in Sources */, + FF70FB6C2C90584900129CC2 /* LoserRoundsView.swift in Sources */, + FF70FB6D2C90584900129CC2 /* GroupStagesView.swift in Sources */, + FF70FB6E2C90584900129CC2 /* PadelClubView.swift in Sources */, + FF70FB6F2C90584900129CC2 /* URLs.swift in Sources */, + FF70FB702C90584900129CC2 /* MatchDescriptor.swift in Sources */, + FF70FB712C90584900129CC2 /* TournamentFormatSelectionView.swift in Sources */, + FF70FB722C90584900129CC2 /* MatchListView.swift in Sources */, + FF70FB732C90584900129CC2 /* PadelClubApp.swift in Sources */, + FF70FB742C90584900129CC2 /* TournamentSettingsView.swift in Sources */, + FF70FB752C90584900129CC2 /* String+Crypto.swift in Sources */, + FF70FB762C90584900129CC2 /* GroupStageTeamReplacementView.swift in Sources */, + FF70FB772C90584900129CC2 /* TabItemModifier.swift in Sources */, + FF70FB782C90584900129CC2 /* DeferredViewModifier.swift in Sources */, + FF70FB792C90584900129CC2 /* TournamentScheduleView.swift in Sources */, + FF70FB7A2C90584900129CC2 /* MatchFormatStorageView.swift in Sources */, + FF70FB7B2C90584900129CC2 /* UmpireView.swift in Sources */, + FF70FB7C2C90584900129CC2 /* User.swift in Sources */, + FF70FB7D2C90584900129CC2 /* MatchSummaryView.swift in Sources */, + FF70FB7E2C90584900129CC2 /* TournamentDurationManagerView.swift in Sources */, + FF70FB7F2C90584900129CC2 /* MockData.swift in Sources */, + FF70FB802C90584900129CC2 /* TeamDetailView.swift in Sources */, + FF70FB812C90584900129CC2 /* GroupStagesSettingsView.swift in Sources */, + FF70FB822C90584900129CC2 /* TournamentFilterView.swift in Sources */, + FF70FB832C90584900129CC2 /* HtmlGenerator.swift in Sources */, + FF70FB842C90584900129CC2 /* PadelRule.swift in Sources */, + FF70FB852C90584900129CC2 /* TeamRegistration.swift in Sources */, + FF70FB862C90584900129CC2 /* Date+Extensions.swift in Sources */, + FF70FB872C90584900129CC2 /* GroupStageView.swift in Sources */, + FF70FB882C90584900129CC2 /* Tips.swift in Sources */, + FF70FB892C90584900129CC2 /* MainView.swift in Sources */, + FF70FB8A2C90584900129CC2 /* MatchDateView.swift in Sources */, + FF70FB8B2C90584900129CC2 /* PlanningSettingsView.swift in Sources */, + FF70FB8C2C90584900129CC2 /* MatchScheduleEditorView.swift in Sources */, + FF70FB8D2C90584900129CC2 /* FortuneWheelView.swift in Sources */, + FF70FB8E2C90584900129CC2 /* URL+Extensions.swift in Sources */, + FF70FB8F2C90584900129CC2 /* LoadingViewModifier.swift in Sources */, + FF70FB902C90584900129CC2 /* PlayerView.swift in Sources */, + FF70FB912C90584900129CC2 /* MatchDetailView.swift in Sources */, + FF70FB922C90584900129CC2 /* ExportFormat.swift in Sources */, + FF70FB932C90584900129CC2 /* PlayerBlockView.swift in Sources */, + FF70FB942C90584900129CC2 /* StoreItem.swift in Sources */, + FF70FB952C90584900129CC2 /* Selectable.swift in Sources */, + FF70FB962C90584900129CC2 /* PointView.swift in Sources */, + FF70FB972C90584900129CC2 /* ClubHolder.swift in Sources */, + FF70FB982C90584900129CC2 /* EventSettingsView.swift in Sources */, + FF70FB992C90584900129CC2 /* InscriptionInfoView.swift in Sources */, + FF70FB9A2C90584900129CC2 /* SelectablePlayerListView.swift in Sources */, + FF70FB9B2C90584900129CC2 /* MatchFormatPickerView.swift in Sources */, + FF70FB9C2C90584900129CC2 /* TournamentRankView.swift in Sources */, + FF70FB9D2C90584900129CC2 /* NumberFormatter+Extensions.swift in Sources */, + FF70FB9E2C90584900129CC2 /* SetLabelView.swift in Sources */, + FF70FB9F2C90584900129CC2 /* DebugSettingsView.swift in Sources */, + FF70FBA02C90584900129CC2 /* GroupStageScheduleEditorView.swift in Sources */, + FF70FBA12C90584900129CC2 /* EventView.swift in Sources */, + FF70FBA22C90584900129CC2 /* LoginView.swift in Sources */, + FF70FBA32C90584900129CC2 /* Court.swift in Sources */, + FF70FBA42C90584900129CC2 /* Labels.swift in Sources */, + FF70FBA52C90584900129CC2 /* CopyPasteButtonView.swift in Sources */, + FF70FBA62C90584900129CC2 /* PlayerSexPickerView.swift in Sources */, + FF70FBA72C90584900129CC2 /* TournamentInscriptionView.swift in Sources */, + FF70FBA82C90584900129CC2 /* CallSettingsView.swift in Sources */, + FF70FBA92C90584900129CC2 /* FooterButtonView.swift in Sources */, + FF70FBAA2C90584900129CC2 /* RankCalculatorView.swift in Sources */, + FF70FBAB2C90584900129CC2 /* DateBoxView.swift in Sources */, + FF70FBAC2C90584900129CC2 /* LearnMoreSheetView.swift in Sources */, + FF70FBAD2C90584900129CC2 /* SourceFileManager.swift in Sources */, + FF70FBAE2C90584900129CC2 /* PListReader.swift in Sources */, + FF70FBAF2C90584900129CC2 /* UpdateSourceRankDateView.swift in Sources */, + FF70FBB02C90584900129CC2 /* GlobalSettingsView.swift in Sources */, + FF70FBB12C90584900129CC2 /* Guard.swift in Sources */, + FF70FBB22C90584900129CC2 /* PurchaseListView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2695,7 +2693,7 @@ }; name = Release; }; - FF8045BC2C90379300A49A52 /* Debug */ = { + FF70FBCD2C90584900129CC2 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -2703,12 +2701,13 @@ 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\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = PadelClub/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Padel Club (Beta)"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports"; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; @@ -2736,7 +2735,7 @@ }; name = Debug; }; - FF8045BD2C90379300A49A52 /* Release */ = { + FF70FBCE2C90584900129CC2 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -2744,11 +2743,12 @@ 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; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = PadelClub/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Padel Club (Beta)"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports"; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; @@ -2816,11 +2816,11 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - FF8045BB2C90379300A49A52 /* Build configuration list for PBXNativeTarget "PadelClub TestFlight" */ = { + FF70FBCC2C90584900129CC2 /* Build configuration list for PBXNativeTarget "PadelClub TestFlight" */ = { isa = XCConfigurationList; buildConfigurations = ( - FF8045BC2C90379300A49A52 /* Debug */, - FF8045BD2C90379300A49A52 /* Release */, + FF70FBCD2C90584900129CC2 /* Debug */, + FF70FBCE2C90584900129CC2 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -2836,7 +2836,7 @@ minimumVersion = 1.2.0; }; }; - FF8044B02C90379300A49A52 /* XCRemoteSwiftPackageReference "swift-algorithms" */ = { + FF70FABF2C90584900129CC2 /* XCRemoteSwiftPackageReference "swift-algorithms" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/apple/swift-algorithms.git"; requirement = { @@ -2844,7 +2844,7 @@ minimumVersion = 1.2.0; }; }; - FF8044B22C90379300A49A52 /* XCRemoteSwiftPackageReference "Zip" */ = { + FF70FAC12C90584900129CC2 /* XCRemoteSwiftPackageReference "Zip" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/marmelroy/Zip"; requirement = { @@ -2863,14 +2863,14 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - FF8044AF2C90379300A49A52 /* Algorithms */ = { + FF70FABE2C90584900129CC2 /* Algorithms */ = { isa = XCSwiftPackageProductDependency; - package = FF8044B02C90379300A49A52 /* XCRemoteSwiftPackageReference "swift-algorithms" */; + package = FF70FABF2C90584900129CC2 /* XCRemoteSwiftPackageReference "swift-algorithms" */; productName = Algorithms; }; - FF8044B12C90379300A49A52 /* Zip */ = { + FF70FAC02C90584900129CC2 /* Zip */ = { isa = XCSwiftPackageProductDependency; - package = FF8044B22C90379300A49A52 /* XCRemoteSwiftPackageReference "Zip" */; + package = FF70FAC12C90584900129CC2 /* XCRemoteSwiftPackageReference "Zip" */; productName = Zip; }; FF92660C2C241CE0002361A4 /* Zip */ = { diff --git a/PadelClub.xcodeproj/xcshareddata/xcschemes/PadelClub-TestFlight.xcscheme b/PadelClub.xcodeproj/xcshareddata/xcschemes/PadelClub TestFlight.xcscheme similarity index 80% rename from PadelClub.xcodeproj/xcshareddata/xcschemes/PadelClub-TestFlight.xcscheme rename to PadelClub.xcodeproj/xcshareddata/xcschemes/PadelClub TestFlight.xcscheme index a0de132..de0fb6b 100644 --- a/PadelClub.xcodeproj/xcshareddata/xcschemes/PadelClub-TestFlight.xcscheme +++ b/PadelClub.xcodeproj/xcshareddata/xcschemes/PadelClub TestFlight.xcscheme @@ -15,7 +15,7 @@ buildForAnalyzing = "YES"> @@ -44,7 +44,7 @@ runnableDebuggingMode = "0"> @@ -57,6 +57,16 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES"> + + + + From 89a70fbbba1b8519e4219132f7e46796d2e8ff6d Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Tue, 10 Sep 2024 12:52:08 +0200 Subject: [PATCH 19/23] change bundle id for testflight --- PadelClub.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 6785d4a..6607922 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -2701,7 +2701,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -2724,7 +2724,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0.8; - PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; + PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -2743,7 +2743,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -2765,7 +2765,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0.8; - PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; + PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; From 09d7852c876303ebe0e83b3903e3f51e24dc5851 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Wed, 11 Sep 2024 10:02:00 +0200 Subject: [PATCH 20/23] fix issue --- PadelClub/Data/Tournament.swift | 2 +- PadelClub/Views/GroupStage/GroupStageView.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index a4726bc..32dd89f 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -2010,7 +2010,7 @@ defer { } func groupStageLoserBracketsInitialPlace() -> Int { - selectedSortedTeams().filter({ $0.qualified || $0.bracketPosition != nil }).count + 1 + return teamCount - teamsPerGroupStage * groupStageCount + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified + 1 } // MARK: - diff --git a/PadelClub/Views/GroupStage/GroupStageView.swift b/PadelClub/Views/GroupStage/GroupStageView.swift index 62c58c6..5b04f71 100644 --- a/PadelClub/Views/GroupStage/GroupStageView.swift +++ b/PadelClub/Views/GroupStage/GroupStageView.swift @@ -197,7 +197,7 @@ struct GroupStageView: View { } } .listRowView(isActive: team.qualified, color: .master) - .listRowView(isActive: team.isHere(), color: .green, hideColorVariation: true) + .listRowView(isActive: team.isHere() && groupStage.hasEnded() == false, color: .green, hideColorVariation: true) } else { VStack(alignment: .leading, spacing: 0) { Text("#\(index + 1)") From 556f57004d73657bc7f236f6e9b9bb40242c053c Mon Sep 17 00:00:00 2001 From: Raz Date: Wed, 11 Sep 2024 12:26:23 +0200 Subject: [PATCH 21/23] fixes --- PadelClub/Data/Tournament.swift | 9 ++++++--- .../Views/GroupStage/GroupStagesView.swift | 2 +- PadelClub/Views/Round/RoundView.swift | 18 ++++++++++-------- .../Tournament/Screen/TournamentRankView.swift | 3 +++ .../Tournament/TournamentRunningView.swift | 3 ++- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 32dd89f..35c94eb 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1151,7 +1151,7 @@ defer { return allMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false }).sorted(by: \.computedStartDateForSorting) } - func finishedMatches(_ allMatches: [Match], limit: Int? = nil) -> [Match] { + func finishedMatches(_ allMatches: [Match], limit: Int?) -> [Match] { #if _DEBUG_TIME //DEBUGING TIME let start = Date() defer { @@ -1159,8 +1159,11 @@ defer { print("func tournament finishedMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) } #endif - let _limit = limit ?? courtCount - return Array(allMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed().prefix(_limit)) + if let limit { + return Array(allMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed().prefix(limit)) + } else { + return Array(allMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed()) + } } func teamsRanked() -> [TeamRegistration] { diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index 0e148a5..7274171 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -109,7 +109,7 @@ struct GroupStagesView: View { GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations(), nilDestinationIsValid: true) switch selectedDestination { case .all: - let finishedMatches = tournament.finishedMatches(allMatches) + let finishedMatches = tournament.finishedMatches(allMatches, limit: nil) List { if tournament.groupStageAdditionalQualified > 0 { diff --git a/PadelClub/Views/Round/RoundView.swift b/PadelClub/Views/Round/RoundView.swift index 3e52112..12bda95 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -74,15 +74,17 @@ struct RoundView: View { let bracketTip = BracketEditTip(nextRoundName: upperRound.round.nextRound()?.roundTitle()) TipView(bracketTip).tipStyle(tint: .green, asSection: true) - Section { - let leftToPlay = (RoundRule.numberOfMatches(forRoundIndex: upperRound.round.index) - disabledMatchesCount) - LabeledContent { - Text(leftToPlay.formatted()) - } label: { - Text("Match\(leftToPlay.pluralSuffix) à jouer \(upperRound.title)") + if upperRound.round.hasStarted() == false { + Section { + let leftToPlay = (RoundRule.numberOfMatches(forRoundIndex: upperRound.round.index) - disabledMatchesCount) + LabeledContent { + Text(leftToPlay.formatted()) + } label: { + Text("Match\(leftToPlay.pluralSuffix) à jouer \(upperRound.title)") + } + } footer: { + Text("\(disabledMatchesCount) match\(disabledMatchesCount.pluralSuffix) désactivé\(disabledMatchesCount.pluralSuffix) automatiquement") } - } footer: { - Text("\(disabledMatchesCount) match\(disabledMatchesCount.pluralSuffix) désactivé\(disabledMatchesCount.pluralSuffix) automatiquement") } } diff --git a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift index e491aec..baf9600 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift @@ -106,6 +106,7 @@ struct TournamentRankView: View { } } } + .id(calculating) .alert("Position", isPresented: isEditingTeam) { if let selectedTeam { @Bindable var team = selectedTeam @@ -154,6 +155,7 @@ struct TournamentRankView: View { } struct TeamRankCellView: View { + @EnvironmentObject var dataStore: DataStore @Environment(\.editMode) private var editMode @Environment(Tournament.self) var tournament: Tournament @State private var isEditingTeam: Bool = false @@ -306,6 +308,7 @@ struct TournamentRankView: View { calculating = true } + self.rankings.removeAll() let finalRanks = await tournament.finalRanking() finalRanks.keys.sorted().forEach { rank in if let rankedTeamIds = finalRanks[rank] { diff --git a/PadelClub/Views/Tournament/TournamentRunningView.swift b/PadelClub/Views/Tournament/TournamentRunningView.swift index 4763306..626a82e 100644 --- a/PadelClub/Views/Tournament/TournamentRunningView.swift +++ b/PadelClub/Views/Tournament/TournamentRunningView.swift @@ -21,7 +21,8 @@ struct TournamentRunningView: View { MatchListView(section: "en cours", matches: tournament.runningMatches(allMatches), hideWhenEmpty: tournament.hasEnded()) // MatchListView(section: "à lancer", matches: tournament.readyMatches(allMatches), isExpanded: false) // MatchListView(section: "disponible", matches: tournament.availableToStart(allMatches), isExpanded: false) - MatchListView(section: "terminés", matches: tournament.finishedMatches(allMatches), isExpanded: tournament.hasEnded()) + let finishedMatches = tournament.finishedMatches(allMatches, limit: tournament.courtCount) + MatchListView(section: "Dernier\(finishedMatches.count.pluralSuffix) match\(finishedMatches.count.pluralSuffix) terminé\(finishedMatches.count.pluralSuffix)", matches: finishedMatches, isExpanded: tournament.hasEnded()) } } From d4bae677d436eb74b686de4182e9d04c0481be8c Mon Sep 17 00:00:00 2001 From: Raz Date: Wed, 11 Sep 2024 14:51:13 +0200 Subject: [PATCH 22/23] fix stuff --- .../Cashier/Event/EventCreationView.swift | 2 +- .../Cashier/Event/EventSettingsView.swift | 2 +- .../Agenda/TournamentSubscriptionView.swift | 44 +++++++++++-------- .../Views/Tournament/TournamentInitView.swift | 7 ++- .../Views/Tournament/TournamentView.swift | 4 +- 5 files changed, 36 insertions(+), 23 deletions(-) diff --git a/PadelClub/Views/Cashier/Event/EventCreationView.swift b/PadelClub/Views/Cashier/Event/EventCreationView.swift index 913862f..5ba6f87 100644 --- a/PadelClub/Views/Cashier/Event/EventCreationView.swift +++ b/PadelClub/Views/Cashier/Event/EventCreationView.swift @@ -64,7 +64,7 @@ struct EventCreationView: View { } } - TextField("Nom de l'événement", text: $eventName, axis: .vertical) + TextField("Description de l'événement", text: $eventName, axis: .vertical) .lineLimit(2) .keyboardType(.alphabet) .multilineTextAlignment(.leading) diff --git a/PadelClub/Views/Cashier/Event/EventSettingsView.swift b/PadelClub/Views/Cashier/Event/EventSettingsView.swift index 593cfe8..58b5428 100644 --- a/PadelClub/Views/Cashier/Event/EventSettingsView.swift +++ b/PadelClub/Views/Cashier/Event/EventSettingsView.swift @@ -46,7 +46,7 @@ struct EventSettingsView: View { var body: some View { Form { Section { - TextField("Nom de l'événement", text: $eventName, axis: .vertical) + TextField("Description de l'événement", text: $eventName, axis: .vertical) .lineLimit(2) .keyboardType(.alphabet) .multilineTextAlignment(.leading) diff --git a/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift b/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift index 70467d6..41e586e 100644 --- a/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift +++ b/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift @@ -37,6 +37,16 @@ struct TournamentSubscriptionView: View { LabeledContent("Épreuve") { Text(build.buildHolderTitle()) } + + LabeledContent("JAP") { + Text(federalTournament.umpireLabel()) + } + LabeledContent("Mail") { + Text(federalTournament.mailLabel()) + } + LabeledContent("Téléphone") { + Text(federalTournament.phoneLabel()) + } } header: { Text("Informations") } @@ -67,28 +77,18 @@ struct TournamentSubscriptionView: View { } } } header: { - HStack { - Text("Poids") - Spacer() - Text(selectedPlayers.map { $0.rank }.reduce(0, +).formatted()) - } - } - - Section { - LabeledContent("JAP") { - Text(federalTournament.umpireLabel()) - } - LabeledContent("Mail") { - Text(federalTournament.mailLabel()) - } - LabeledContent("Téléphone") { - Text(federalTournament.phoneLabel()) + if selectedPlayers.isEmpty == false { + HStack { + Text("Poids de l'équipe") + Spacer() + Text(selectedPlayers.map { $0.rank }.reduce(0, +).formatted()) + } } } if let courrielEngagement = federalTournament.courrielEngagement { Section { - RowButtonView("Contacter par email") { + RowButtonView("S'inscrire par email") { contactType = .mail(date: nil, recipients: [courrielEngagement], bccRecipients: nil, body: messageBody, subject: messageSubject, tournamentBuild: build as? TournamentBuild) } } @@ -97,7 +97,7 @@ struct TournamentSubscriptionView: View { if let installation = federalTournament.installation, let telephone = installation.telephone { if telephone.isMobileNumber() { Section { - RowButtonView("Contacter par message") { + RowButtonView("S'inscrire par message") { contactType = .message(date: nil, recipients: [telephone], body: messageBodyShort, tournamentBuild: build as? TournamentBuild) } } @@ -108,6 +108,14 @@ struct TournamentSubscriptionView: View { Label("Appeler", systemImage: "phone") } } + + Section { + Text(messageBody) + } header: { + Text("Message preparé par Padel Club") + } footer: { + CopyPasteButtonView(pasteValue: messageBody) + } } } diff --git a/PadelClub/Views/Tournament/TournamentInitView.swift b/PadelClub/Views/Tournament/TournamentInitView.swift index 25fd1dd..969f6ff 100644 --- a/PadelClub/Views/Tournament/TournamentInitView.swift +++ b/PadelClub/Views/Tournament/TournamentInitView.swift @@ -22,7 +22,12 @@ struct TournamentInitView: View { LabeledContent { Text(tournaments.count.formatted() + " tournoi" + tournaments.count.pluralSuffix) } label: { - Text("Gestion de l'événement") + Text("Réglages de l'événement") + if let eventName = event.name, eventName.isEmpty == false { + Text(eventName).foregroundStyle(.secondary) + } else { + Text("Aucune description").foregroundStyle(.secondary) + } } } } diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index fa14f46..7dbe23b 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -131,7 +131,7 @@ struct TournamentView: View { } NavigationLink(value: Screen.event) { - Text("Gestion de l'événement") + Text("Réglages de l'événement") } } @@ -176,7 +176,7 @@ struct TournamentView: View { } NavigationLink(value: Screen.event) { - Text("Gestion de l'événement") + Text("Réglages de l'événement") } NavigationLink(value: Screen.settings) { LabelSettings() From 5469ce290c273ad7cd637ceac858ebfb3423aeed Mon Sep 17 00:00:00 2001 From: Raz Date: Wed, 11 Sep 2024 15:10:26 +0200 Subject: [PATCH 23/23] fix issues --- PadelClub/Extensions/String+Extensions.swift | 4 ++++ PadelClub/Utils/ContactManager.swift | 7 +++++-- PadelClub/Views/Calling/CallMessageCustomizationView.swift | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/PadelClub/Extensions/String+Extensions.swift b/PadelClub/Extensions/String+Extensions.swift index 92cc91a..b333e27 100644 --- a/PadelClub/Extensions/String+Extensions.swift +++ b/PadelClub/Extensions/String+Extensions.swift @@ -17,6 +17,10 @@ extension String { replaceCharactersFromSet(characterSet: .newlines).trimmingCharacters(in: .whitespacesAndNewlines) } + var trimmedMultiline: String { + self.trimmingCharacters(in: .whitespacesAndNewlines) + } + func replaceCharactersFromSet(characterSet: CharacterSet, replacementString: String = "") -> String { components(separatedBy: characterSet).joined(separator:replacementString) } diff --git a/PadelClub/Utils/ContactManager.swift b/PadelClub/Utils/ContactManager.swift index dd74dd5..07b23a2 100644 --- a/PadelClub/Utils/ContactManager.swift +++ b/PadelClub/Utils/ContactManager.swift @@ -30,7 +30,10 @@ enum ContactType: Identifiable { } extension ContactType { - static let defaultCustomMessage: String = "Il est conseillé de vous présenter 10 minutes avant de jouer.\nMerci de me confirmer votre présence avec votre nom et de prévenir votre partenaire." + static let defaultCustomMessage: String = +""" +Il est conseillé de vous présenter 10 minutes avant de jouer.\n\nMerci de me confirmer votre présence avec votre nom et de prévenir votre partenaire. +""" static let defaultAvailablePaymentMethods: String = "Règlement possible par chèque ou espèces." static func callingCustomMessage(source: String? = nil, tournament: Tournament?, startDate: Date?, roundLabel: String) -> String { @@ -77,7 +80,7 @@ extension ContactType { } var computedMessage: String { - [entryFeeMessage, message].compacted().map { $0.trimmed }.joined(separator: "\n\n") + [entryFeeMessage, message].compacted().map { $0.trimmedMultiline }.joined(separator: "\n\n") } let intro = reSummon ? "Suite à des forfaits, vous êtes finalement" : "Vous êtes" diff --git a/PadelClub/Views/Calling/CallMessageCustomizationView.swift b/PadelClub/Views/Calling/CallMessageCustomizationView.swift index e2f8999..93a1779 100644 --- a/PadelClub/Views/Calling/CallMessageCustomizationView.swift +++ b/PadelClub/Views/Calling/CallMessageCustomizationView.swift @@ -43,7 +43,7 @@ struct CallMessageCustomizationView: View { } var computedMessage: String { - [entryFeeMessage, customCallMessageBody].compacted().map { $0.trimmed }.joined(separator: "\n") + [entryFeeMessage, customCallMessageBody].compacted().map { $0.trimmedMultiline }.joined(separator: "\n") } var finalMessage: String? {