From 645beb7171dcc901d78c903843cd2978e4d74471 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Sat, 25 May 2024 11:03:43 +0200 Subject: [PATCH 1/5] fix fortune wheel stuff --- PadelClub.xcodeproj/project.pbxproj | 4 +- PadelClub/Data/Match.swift | 8 +- PadelClub/Data/TeamRegistration.swift | 14 ++- .../Views/Components/FortuneWheelView.swift | 97 ++++++++++------ .../Components/GroupStageTeamView.swift | 32 +++--- .../Views/GroupStage/GroupStagesView.swift | 13 ++- PadelClub/Views/Round/RoundView.swift | 104 ++++++++++++------ PadelClub/Views/Round/RoundsView.swift | 5 +- PadelClub/Views/Subscription/Guard.swift | 8 +- .../Tournament/TournamentBuildView.swift | 2 + .../Tournament/TournamentRunningView.swift | 4 +- 11 files changed, 189 insertions(+), 102 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 7f616f5..285d8e7 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -1923,7 +1923,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 23; + CURRENT_PROJECT_VERSION = 24; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -1961,7 +1961,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 23; + CURRENT_PROJECT_VERSION = 24; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index e31d4b8..00fb5ee 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -588,15 +588,17 @@ class Match: ModelObject, Storable { } func hasSpaceLeft() -> Bool { - teams().count == 1 + teamScores.count < 2 } func isReady() -> Bool { - teams().count == 2 + teamScores.count == 2 +// teams().count == 2 } func isEmpty() -> Bool { - teams().isEmpty + teamScores.isEmpty +// teams().isEmpty } func hasEnded() -> Bool { diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index cf860e2..edf0ebd 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -117,7 +117,19 @@ class TeamRegistration: ModelObject, Storable { func teamScores() -> [TeamScore] { Store.main.filter(isIncluded: { $0.teamRegistration == id }) } + + func wins() -> [Match] { + Store.main.filter(isIncluded: { $0.winningTeamId == id }) + } + + func loses() -> [Match] { + Store.main.filter(isIncluded: { $0.losingTeamId == id }) + } + func matches() -> [Match] { + Store.main.filter(isIncluded: { $0.losingTeamId == id || $0.winningTeamId == id }) + } + var tournamentCategory: TournamentCategory { tournamentObject()?.tournamentCategory ?? .men } @@ -171,7 +183,7 @@ class TeamRegistration: ModelObject, Storable { } func canPlay() -> Bool { - teamScores().isEmpty == false || players().allSatisfy({ $0.hasPaid() || $0.hasArrived }) + matches().isEmpty == false || players().allSatisfy({ $0.hasPaid() || $0.hasArrived }) } func availableForSeedPick() -> Bool { diff --git a/PadelClub/Views/Components/FortuneWheelView.swift b/PadelClub/Views/Components/FortuneWheelView.swift index f0b8d60..abd7ece 100644 --- a/PadelClub/Views/Components/FortuneWheelView.swift +++ b/PadelClub/Views/Components/FortuneWheelView.swift @@ -61,16 +61,14 @@ struct SpinDrawView: View { let drawees: [any SpinDrawable] @State var segments: [any SpinDrawable] - let completion: ([DrawResult]) -> Void // Completion closure + var autoMode: Bool = false + let completion: ([DrawResult]) async -> Void // Completion closure @State private var drawCount: Int = 0 @State private var draws: [DrawResult] = [DrawResult]() @State private var drawOptions: [DrawOption] = [DrawOption]() @State private var selectedIndex: Int? - - var autoMode: Bool { - drawees.count > 1 - } + @State private var disabled: Bool = false var body: some View { List { @@ -79,7 +77,7 @@ struct SpinDrawView: View { _validationLabelView(drawee: drawCount, result: segments[draws.last!.drawIndex]) if autoMode == false || drawCount == drawees.count { RowButtonView("Valider le tirage") { - completion(draws) + await completion(draws) dismiss() } } else { @@ -92,27 +90,49 @@ struct SpinDrawView: View { } Section { - FortuneWheelContainerView(segments: drawOptions, autoMode: autoMode) { index in - self.selectedIndex = index - self.draws.append(DrawResult(drawee: drawCount, drawIndex: drawOptions[index].initialIndex)) - self.drawOptions.remove(at: index) - - if autoMode && drawCount < drawees.count { - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - self.drawCount += 1 - if drawOptions.count == 1 { - self.draws.append(DrawResult(drawee: self.drawCount, drawIndex: self.drawOptions[0].initialIndex)) - self.drawOptions.remove(at: 0) + ZStack { + FortuneWheelContainerView(segments: drawOptions, autoMode: autoMode) { index in + self.selectedIndex = index + self.draws.append(DrawResult(drawee: drawCount, drawIndex: drawOptions[index].initialIndex)) + self.drawOptions.remove(at: index) + + if autoMode && drawCount < drawees.count { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { self.drawCount += 1 - self.selectedIndex = nil - } else { - self.selectedIndex = nil + if drawOptions.count == 1 { + self.draws.append(DrawResult(drawee: self.drawCount, drawIndex: self.drawOptions[0].initialIndex)) + self.drawOptions.remove(at: 0) + self.drawCount += 1 + self.selectedIndex = nil + } else { + self.selectedIndex = nil + } } } } + .simultaneousGesture( + DragGesture().onChanged({ (value) in + disabled = true + }).onEnded({ (value) in + }) + ) + Rectangle() + .fill(.white.opacity(0.01)) + .clipShape(Circle()) + .allowsHitTesting(disabled) } .listRowBackground(Color.clear) .listRowSeparator(.hidden) + } footer: { + HStack { + Spacer() + if autoMode { + Text("Mode automatique") + } else { + Text("Lancer la roue en glissant avec le doigt").multilineTextAlignment(.center) + } + Spacer() + } } } else { Section { @@ -123,7 +143,7 @@ struct SpinDrawView: View { } RowButtonView("Valider les tirages") { - completion(draws) + await completion(draws) dismiss() } } @@ -142,6 +162,7 @@ struct SpinDrawView: View { Button("Annuler", role: .cancel) { dismiss() } + .disabled(disabled || autoMode) } } .navigationBarBackButtonHidden() @@ -151,6 +172,7 @@ struct SpinDrawView: View { .toolbar(.hidden, for: .tabBar) .listStyle(.insetGrouped) .scrollDisabled(true) + .interactiveDismissDisabled() .onAppear { for (index, segment) in segments.enumerated() { drawOptions.append(DrawOption(initialIndex: index, option: segment)) @@ -162,19 +184,24 @@ struct SpinDrawView: View { private func _segmentLabelView(segment: [String], horizontalAlignment: HorizontalAlignment = .leading) -> some View { VStack(alignment: horizontalAlignment, spacing: 0.0) { ForEach(segment, id: \.self) { string in - Text(string) + Text(string).font(.title3) .frame(maxWidth: .infinity) + .lineLimit(1) } } } @ViewBuilder private func _validationLabelView(drawee: Int, result: SpinDrawable) -> some View { - HStack(spacing: 0.0) { + VStack(spacing: 0.0) { let draw = drawees[drawee] - _segmentLabelView(segment: draw.segmentLabel(.wide), horizontalAlignment: .leading) - Image(systemName: "arrowshape.forward.fill") - _segmentLabelView(segment: result.segmentLabel(.wide), horizontalAlignment: .trailing) + _segmentLabelView(segment: draw.segmentLabel(.wide), horizontalAlignment: .center) + if result as? TeamRegistration != nil { + Image(systemName: "flag.2.crossed.fill").font(.largeTitle).foregroundColor(.logoRed) + } else { + Image(systemName: "arrowshape.down.fill").font(.largeTitle).foregroundColor(.logoRed) + } + _segmentLabelView(segment: result.segmentLabel(.wide), horizontalAlignment: .center) } } } @@ -200,26 +227,24 @@ struct FortuneWheelContainerView: View { .onAppear { if autoMode { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + rotation = 0 rollWheel() } } } .gesture( - DragGesture() - .onChanged { value in - // Calculate rotation based on the velocity of the drag - let initialVelocity = value.predictedEndTranslation.width / 10 // Adjust sensitivity - rotation += Double(initialVelocity) - } - .onEnded { value in - // Roll the wheel when drag ends - rollWheel() + DragGesture().onChanged({ (value) in + if value.translation.width < 0 { + rotation = Double(-value.translation.width) } + }).onEnded({ (value) in + rollWheel() + }) ) } func rollWheel() { - rotation = 0 + //rotation = 0 // Generate a random angle for the wheel to rotate let randomAngle = Double.random(in: 1440...2880) // Adjust range for more or less rotations diff --git a/PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift b/PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift index 0f47caa..2b59ba6 100644 --- a/PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift +++ b/PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift @@ -23,22 +23,22 @@ struct GroupStageTeamView: View { } if groupStage.tournamentObject()?.hasEnded() == false { - if team.qualified && team.bracketPosition == nil, let tournament = team.tournamentObject() { - Section { - NavigationLink { - SpinDrawView(drawees: [team], segments: tournament.matchesWithSpace()) { results in - - } - } label: { - Text("Tirage au sort visuel") - } - } - - Section { - RowButtonView("Tirage au sort automatique", role: .destructive) { - } - } - } +// if team.qualified && team.bracketPosition == nil, let tournament = team.tournamentObject() { +// Section { +// NavigationLink { +// SpinDrawView(drawees: [team], segments: tournament.matchesWithSpace()) { results in +// +// } +// } label: { +// Text("Tirage au sort visuel") +// } +// } +// +// Section { +// RowButtonView("Tirage au sort automatique", role: .destructive) { +// } +// } +// } if team.qualified == false { Section { diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index b09c5dc..fc1c9f6 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -51,8 +51,12 @@ struct GroupStagesView: View { } } + let allMatches: [Match] + init(tournament: Tournament) { self.tournament = tournament + self.allMatches = tournament.groupStages().flatMap({ $0.playedMatches() }) + if tournament.shouldVerifyGroupStage { _selectedDestination = State(wrappedValue: nil) } else if tournament.unsortedTeams().filter({ $0.groupStagePosition != nil }).isEmpty { @@ -77,11 +81,10 @@ struct GroupStagesView: View { GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations(), nilDestinationIsValid: true) switch selectedDestination { case .all: - let allGroupStages = tournament.groupStages() - let availableToStart = allGroupStages.flatMap({ $0.availableToStart() }) - let runningMatches = allGroupStages.flatMap({ $0.runningMatches() }) - let readyMatches = allGroupStages.flatMap({ $0.readyMatches() }) - let finishedMatches = allGroupStages.flatMap({ $0.finishedMatches() }) + let availableToStart = tournament.availableToStart(allMatches) + let runningMatches = tournament.runningMatches(allMatches) + let readyMatches = tournament.readyMatches(allMatches) + let finishedMatches = tournament.finishedMatches(allMatches) List { MatchListView(section: "disponible", matches: availableToStart, matchViewStyle: .standardStyle, isExpanded: false) diff --git a/PadelClub/Views/Round/RoundView.swift b/PadelClub/Views/Round/RoundView.swift index a2f99d7..9d9f344 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -31,6 +31,7 @@ struct RoundView: View { let availableQualifiedTeams = tournament.availableQualifiedTeams() let displayableMatches = round.displayableMatches().sorted(by: \.index) let spaceLeft = displayableMatches.filter({ $0.hasSpaceLeft() }) + let seedSpaceLeft = displayableMatches.filter({ $0.isEmpty() }) if isEditingTournamentSeed.wrappedValue == false { //(where: { $0.isDisabled() == false || isEditingTournamentSeed.wrappedValue }) if loserRounds.isEmpty == false { @@ -45,41 +46,82 @@ struct RoundView: View { } } } - } else if availableQualifiedTeams.isEmpty == false && spaceLeft.isEmpty == false { - NavigationLink("Tirer au sort la position d'un qualifié") { - SpinDrawView(drawees: availableQualifiedTeams, segments: spaceLeft) { results in - results.forEach { drawResult in - print(availableQualifiedTeams[drawResult.drawee].teamLabel()) - print(spaceLeft[drawResult.drawIndex].matchTitle()) - availableQualifiedTeams[drawResult.drawee].setSeedPosition(inSpot: spaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: true) + } else { + if let availableSeedGroup = tournament.seedGroupAvailable(atRoundIndex: round.index) { + Section { + RowButtonView("Placer \(availableSeedGroup.localizedLabel())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) { + tournament.setSeeds(inRoundIndex: round.index, inSeedGroup: availableSeedGroup) + _save() + if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty { + self.isEditingTournamentSeed.wrappedValue = false + } } - _save() - if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty { - self.isEditingTournamentSeed.wrappedValue = false + } footer: { + if availableSeedGroup.isFixed() == false { + Text("Le tirage au sort ne sera pas visuel. Toutes les équipes de ce chapeau seront tirées.") + } + } + + if (availableSeedGroup.isFixed() == false) { + Section { + RowButtonView("Tirage au sort \(availableSeedGroup.localizedLabel()) visuel") { + self.selectedSeedGroup = availableSeedGroup + } + } footer: { + Text("Le tirage au sort sera visuel et automatique, n'hésitez pas à enregistrer une vidéo de votre écran. Toutes les équipes de ce chapeau seront tirées les unes après les autres.") } } } - } else if let availableSeedGroup = tournament.seedGroupAvailable(atRoundIndex: round.index) { - Section { - RowButtonView("Placer \(availableSeedGroup.localizedLabel())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) { - tournament.setSeeds(inRoundIndex: round.index, inSeedGroup: availableSeedGroup) - _save() - if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty { - self.isEditingTournamentSeed.wrappedValue = false + + if availableQualifiedTeams.isEmpty == false && spaceLeft.isEmpty == false { + Section { + ForEach(availableQualifiedTeams) { team in + NavigationLink { + SpinDrawView(drawees: [team], segments: spaceLeft) { results in + Task { + results.forEach { drawResult in + team.setSeedPosition(inSpot: spaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: true) + } + _save() + if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty { + self.isEditingTournamentSeed.wrappedValue = false + } + } + } + } label: { + TeamRowView(team: team, displayCallDate: false) + } } + } header: { + Text("Tirage au sort visuel d'un qualifié").font(.subheadline) } } - if (availableSeedGroup.isFixed() == false) { + if tournament.availableSeeds().isEmpty == false && seedSpaceLeft.isEmpty == false { Section { - RowButtonView("Tirage au sort \(availableSeedGroup.localizedLabel()) visuel") { - self.selectedSeedGroup = availableSeedGroup + ForEach(tournament.availableSeeds()) { team in + NavigationLink { + SpinDrawView(drawees: [team], segments: seedSpaceLeft) { results in + Task { + results.forEach { drawResult in + team.setSeedPosition(inSpot: seedSpaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: false) + } + _save() + if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty { + self.isEditingTournamentSeed.wrappedValue = false + } + } + } + } label: { + TeamRowView(team: team, displayCallDate: false) + } } + } header: { + Text("Tirage au sort visuel d'une tête de série").font(.subheadline) } } } - ForEach(displayableMatches) { match in Section { MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle) @@ -100,20 +142,20 @@ struct RoundView: View { } } } - .sheet(isPresented: showVisualDrawView) { + .fullScreenCover(isPresented: showVisualDrawView) { if let availableSeedGroup = selectedSeedGroup { let seeds = tournament.seeds(inSeedGroup: availableSeedGroup) let availableSeedSpot = tournament.availableSeedSpot(inRoundIndex: round.index) NavigationStack { - SpinDrawView(drawees: seeds, segments: availableSeedSpot) { draws in - draws.forEach { drawResult in - print(seeds[drawResult.drawee].teamLabel()) - print(availableSeedSpot[drawResult.drawIndex].matchTitle()) - seeds[drawResult.drawee].setSeedPosition(inSpot: availableSeedSpot[drawResult.drawIndex], slot: nil, opposingSeeding: false) - } - _save() - if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty { - self.isEditingTournamentSeed.wrappedValue = false + SpinDrawView(drawees: seeds, segments: availableSeedSpot, autoMode: true) { draws in + Task { + draws.forEach { drawResult in + seeds[drawResult.drawee].setSeedPosition(inSpot: availableSeedSpot[drawResult.drawIndex], slot: nil, opposingSeeding: false) + } + _save() + if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty { + self.isEditingTournamentSeed.wrappedValue = false + } } } } diff --git a/PadelClub/Views/Round/RoundsView.swift b/PadelClub/Views/Round/RoundsView.swift index 08a6c72..eac0d07 100644 --- a/PadelClub/Views/Round/RoundsView.swift +++ b/PadelClub/Views/Round/RoundsView.swift @@ -14,12 +14,13 @@ struct RoundsView: View { init(tournament: Tournament) { self.tournament = tournament - if tournament.shouldVerifyBracket { + let availableSeeds = tournament.availableSeeds() + if tournament.shouldVerifyBracket && availableSeeds.isEmpty { _selectedRound = State(wrappedValue: nil) } else { _selectedRound = State(wrappedValue: tournament.getActiveRound()) } - if tournament.availableSeeds().isEmpty == false || tournament.availableQualifiedTeams().isEmpty == false { + if availableSeeds.isEmpty == false || tournament.availableQualifiedTeams().isEmpty == false { _isEditingTournamentSeed = State(wrappedValue: true) } } diff --git a/PadelClub/Views/Subscription/Guard.swift b/PadelClub/Views/Subscription/Guard.swift index 92d8fd1..ccc1f04 100644 --- a/PadelClub/Views/Subscription/Guard.swift +++ b/PadelClub/Views/Subscription/Guard.swift @@ -145,14 +145,14 @@ import LeStorage } var currentPlan: StoreItem? { -// #if DEBUG -// return .monthlyUnlimited -// #else + #if DEBUG + return .monthlyUnlimited + #else if let currentBestPlan = self.currentBestPlan, let plan = StoreItem(rawValue: currentBestPlan.productID) { return plan } return nil -// #endif + #endif } func userFilteredPurchases() -> [StoreKit.Transaction] { diff --git a/PadelClub/Views/Tournament/TournamentBuildView.swift b/PadelClub/Views/Tournament/TournamentBuildView.swift index 8749b6a..2c88f24 100644 --- a/PadelClub/Views/Tournament/TournamentBuildView.swift +++ b/PadelClub/Views/Tournament/TournamentBuildView.swift @@ -25,6 +25,7 @@ struct TournamentBuildView: View { NavigationLink(value: Screen.groupStage) { LabeledContent { Text(tournament.groupStageStatus()) + .multilineTextAlignment(.trailing) } label: { Text("Poules") if tournament.shouldVerifyGroupStage { @@ -38,6 +39,7 @@ struct TournamentBuildView: View { NavigationLink(value: Screen.round) { LabeledContent { Text(tournament.bracketStatus()) + .multilineTextAlignment(.trailing) } label: { Text("Tableau") if tournament.shouldVerifyBracket { diff --git a/PadelClub/Views/Tournament/TournamentRunningView.swift b/PadelClub/Views/Tournament/TournamentRunningView.swift index f0e4ec1..b6c6cdf 100644 --- a/PadelClub/Views/Tournament/TournamentRunningView.swift +++ b/PadelClub/Views/Tournament/TournamentRunningView.swift @@ -19,8 +19,8 @@ struct TournamentRunningView: View { @ViewBuilder var body: some View { MatchListView(section: "en cours", matches: tournament.runningMatches(allMatches)) - MatchListView(section: "à lancer", matches: tournament.readyMatches(allMatches), isExpanded: false) - MatchListView(section: "disponible", matches: tournament.availableToStart(allMatches), isExpanded: false) +// 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: false) } } From 71f4377f3770df390069f33a37b465d3c1ac1adf Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Sun, 26 May 2024 08:52:28 +0200 Subject: [PATCH 2/5] fix some stuff --- PadelClub/Data/GroupStage.swift | 19 +++-- PadelClub/Data/Match.swift | 12 ++-- PadelClub/Data/Tournament.swift | 11 ++- PadelClub/Utils/HtmlService.swift | 8 +-- PadelClub/Utils/Tips.swift | 2 +- .../Views/GroupStage/GroupStageView.swift | 72 ++++++++++++++----- .../Screen/InscriptionManagerView.swift | 13 ++-- 7 files changed, 90 insertions(+), 47 deletions(-) diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index 4e69732..29e48bb 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -170,21 +170,20 @@ class GroupStage: ModelObject, Storable { return _matches().first(where: { matchIndexes.contains($0.index) }) } - func availableToStart() -> [Match] { - let runningMatches = runningMatches() - return playedMatches().filter({ $0.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false }) + func availableToStart(playedMatches: [Match], in runningMatches: [Match]) -> [Match] { + return playedMatches.filter({ $0.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false }) } - func runningMatches() -> [Match] { - playedMatches().filter({ $0.isRunning() }) + func runningMatches(playedMatches: [Match]) -> [Match] { + playedMatches.filter({ $0.isRunning() }) } - func readyMatches() -> [Match] { - playedMatches().filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false }) + func readyMatches(playedMatches: [Match]) -> [Match] { + playedMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false }) } - func finishedMatches() -> [Match] { - playedMatches().filter({ $0.hasEnded() }) + func finishedMatches(playedMatches: [Match]) -> [Match] { + playedMatches.filter({ $0.hasEnded() }) } private func _matchOrder() -> [Int] { @@ -366,7 +365,7 @@ extension GroupStage: Selectable { } func badgeValue() -> Int? { - runningMatches().count + runningMatches(playedMatches: playedMatches()).count } func badgeValueColor() -> Color? { diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index 00fb5ee..fad2046 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -486,11 +486,11 @@ class Match: ModelObject, Storable { } catch { Logger.error(error) } + followingMatch()?.resetTeamScores() + _loserMatch()?.resetTeamScores() } - followingMatch()?.resetTeamScores() - _loserMatch()?.resetTeamScores() } - + func createTeamScores() -> [TeamScore] { let teamOne = team(.one) let teamTwo = team(.two) @@ -568,15 +568,15 @@ class Match: ModelObject, Storable { } func canBeStarted(inMatches matches: [Match]) -> Bool { - let teams = teams() + let teams = teamScores guard teams.count == 2 else { return false } guard hasEnded() == false else { return false } guard hasStarted() == false else { return false } - return teams.allSatisfy({ $0.canPlay() && isTeamPlaying($0, inMatches: matches) == false }) + return teams.compactMap({ $0.team }).allSatisfy({ $0.canPlay() && isTeamPlaying($0, inMatches: matches) == false }) } func isTeamPlaying(_ team: TeamRegistration, inMatches matches: [Match]) -> Bool { - matches.filter({ $0.teams().contains(team) }).isEmpty == false + matches.filter({ $0.teamScores.compactMap { $0.teamRegistration }.contains(team.id) }).isEmpty == false } var computedStartDateForSorting: Date { diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 9b4c985..1f82dcd 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1399,15 +1399,14 @@ class Tournament : ModelObject, Storable { print("Position \(index+1) Poule \(groupStages[jIndex].index)") chunks[index][jIndex].groupStage = groupStages[jIndex].id chunks[index][jIndex].groupStagePosition = index - - do { - try DataStore.shared.teamRegistrations.addOrUpdate(instance: chunks[index][jIndex]) - } catch { - Logger.error(error) - } } } + do { + try DataStore.shared.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams()) + } catch { + Logger.error(error) + } groupStages.forEach { $0.buildMatches() } } diff --git a/PadelClub/Utils/HtmlService.swift b/PadelClub/Utils/HtmlService.swift index 1c294ce..74de02b 100644 --- a/PadelClub/Utils/HtmlService.swift +++ b/PadelClub/Utils/HtmlService.swift @@ -84,7 +84,7 @@ enum HtmlService { if let playerOne = entrant.players()[safe: 0] { template = template.replacingOccurrences(of: "{{playerOne}}", with: playerOne.playerLabel()) if withRank { - template = template.replacingOccurrences(of: "{{weightOne}}", with: "(\(playerOne.formattedRank())") + template = template.replacingOccurrences(of: "{{weightOne}}", with: "(\(playerOne.formattedRank()))") } else { template = template.replacingOccurrences(of: "{{weightOne}}", with: "") } @@ -93,7 +93,7 @@ enum HtmlService { if let playerTwo = entrant.players()[safe: 1] { template = template.replacingOccurrences(of: "{{playerTwo}}", with: playerTwo.playerLabel()) if withRank { - template = template.replacingOccurrences(of: "{{weightTwo}}", with: "(\(playerTwo.formattedRank())") + template = template.replacingOccurrences(of: "{{weightTwo}}", with: "(\(playerTwo.formattedRank()))") } else { template = template.replacingOccurrences(of: "{{weightTwo}}", with: "") } @@ -135,7 +135,7 @@ enum HtmlService { if let playerOne = entrant.players()[safe: 0] { template = template.replacingOccurrences(of: "{{playerOne}}", with: playerOne.playerLabel(.short)) if withRank { - template = template.replacingOccurrences(of: "{{weightOne}}", with: "(\(playerOne.formattedRank())") + template = template.replacingOccurrences(of: "{{weightOne}}", with: "(\(playerOne.formattedRank()))") } else { template = template.replacingOccurrences(of: "{{weightOne}}", with: "") } @@ -144,7 +144,7 @@ enum HtmlService { if let playerTwo = entrant.players()[safe: 1] { template = template.replacingOccurrences(of: "{{playerTwo}}", with: playerTwo.playerLabel(.short)) if withRank { - template = template.replacingOccurrences(of: "{{weightTwo}}", with: "(\(playerTwo.formattedRank())") + template = template.replacingOccurrences(of: "{{weightTwo}}", with: "(\(playerTwo.formattedRank()))") } else { template = template.replacingOccurrences(of: "{{weightTwo}}", with: "") } diff --git a/PadelClub/Utils/Tips.swift b/PadelClub/Utils/Tips.swift index 07e4d32..5f7c555 100644 --- a/PadelClub/Utils/Tips.swift +++ b/PadelClub/Utils/Tips.swift @@ -336,7 +336,7 @@ struct TournamentPublishingTip: Tip { } var message: Text? { - Text("Padel Club vous permet de publier votre tournoi et rendre accessible à tous les résultats des matchs et l'évolution de l'événement. Les informations seront accessible sur le site Padel Club.") + Text("Padel Club vous permet de publier votre tournoi et rendre accessible à tous les résultats des matchs et l'évolution de l'événement. Les informations seront accessibles sur le site Padel Club.") } var image: Image? { diff --git a/PadelClub/Views/GroupStage/GroupStageView.swift b/PadelClub/Views/GroupStage/GroupStageView.swift index 6c79f5c..92c8c4c 100644 --- a/PadelClub/Views/GroupStage/GroupStageView.swift +++ b/PadelClub/Views/GroupStage/GroupStageView.swift @@ -16,9 +16,11 @@ struct GroupStageView: View { @State private var confirmRemoveAll: Bool = false @State private var confirmResetMatch: Bool = false @State private var groupStageName: String = "" - + let playedMatches: [Match] + init(groupStage: GroupStage) { self.groupStage = groupStage + self.playedMatches = groupStage.playedMatches() _groupStageName = State(wrappedValue: groupStage.groupStageTitle()) } @@ -44,21 +46,18 @@ struct GroupStageView: View { } .headerProminence(.increased) - MatchListView(section: "disponible", matches: groupStage.availableToStart()).id(UUID()) - MatchListView(section: "en cours", matches: groupStage.runningMatches()).id(UUID()) - MatchListView(section: "à lancer", matches: groupStage.readyMatches()).id(UUID()) - MatchListView(section: "terminés", matches: groupStage.finishedMatches(), isExpanded: false).id(UUID()) - } - .onChange(of: groupStageName) { - groupStage.name = groupStageName - _save() + let runningMatches = groupStage.runningMatches(playedMatches: playedMatches) + MatchListView(section: "disponible", matches: groupStage.availableToStart(playedMatches: playedMatches, in: runningMatches)) + MatchListView(section: "en cours", matches: runningMatches) + MatchListView(section: "à lancer", matches: groupStage.readyMatches(playedMatches: playedMatches)) + MatchListView(section: "terminés", matches: groupStage.finishedMatches(playedMatches: playedMatches), isExpanded: false) } .toolbar { ToolbarItem(placement: .topBarTrailing) { _groupStageMenuView() } } - .navigationTitle($groupStageName) + .navigationTitle(groupStage.groupStageTitle()) } private enum GroupStageSortingMode { @@ -178,12 +177,10 @@ struct GroupStageView: View { private func _groupStageMenuView() -> some View { Menu { - if groupStage.name != nil { - Button("Retirer le nom") { - groupStage.name = nil - groupStageName = groupStage.groupStageTitle() - _save() - } + NavigationLink { + GroupStageNameEditionView(groupStage: groupStage) + } label: { + Label("Renommer", systemImage: "pencil") } Button("Retirer tout le monde", role: .destructive) { confirmRemoveAll = true @@ -225,3 +222,46 @@ struct GroupStageView: View { } } } + +struct GroupStageNameEditionView: View { + @EnvironmentObject var dataStore: DataStore + let groupStage: GroupStage + @State private var groupStageName: String = "" + + var body: some View { + Form { + Section { + TextField("Nom de la poule", text: $groupStageName) + .keyboardType(.alphabet) + .frame(maxWidth: .infinity) + .onAppear(perform: { + groupStageName = groupStage.name ?? "" + }) + .onSubmit { + groupStageName = groupStageName.trimmed + groupStage.name = groupStageName + _save() + } + } footer: { + HStack { + Spacer() + FooterButtonView("retirer le nom") { + groupStage.name = nil + groupStageName = groupStage.groupStageTitle() + _save() + } + } + } + } + .navigationTitle(groupStage.groupStageTitle()) + .toolbarBackground(.visible, for: .navigationBar) + } + + private func _save() { + do { + try dataStore.groupStages.addOrUpdate(instance: groupStage) + } catch { + Logger.error(error) + } + } +} diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index 0205d0c..4835e18 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -608,15 +608,20 @@ struct InscriptionManagerView: View { Section { let unsortedTeams = tournament.unsortedTeams() let walkoutTeams = tournament.walkoutTeams() + let unsortedTeamsWithoutWO = tournament.unsortedTeamsWithoutWO() LabeledContent { - Text(unsortedTeams.count.formatted() + "/" + tournament.teamCount.formatted()).font(.largeTitle) + Text(unsortedTeamsWithoutWO.count.formatted() + "/" + tournament.teamCount.formatted()).font(.largeTitle) } label: { - Text("Paire\(unsortedTeams.count.pluralSuffix) inscrite\(unsortedTeams.count.pluralSuffix)") - Text("dont \(walkoutTeams.count) forfait\(walkoutTeams.count.pluralSuffix)") + Text("Paire\(unsortedTeamsWithoutWO.count.pluralSuffix) inscrite\(unsortedTeamsWithoutWO.count.pluralSuffix)") + } + + LabeledContent { + Text(walkoutTeams.count.formatted()).font(.largeTitle) + } label: { + Text("Forfait\(walkoutTeams.count.pluralSuffix)") } - let unsortedTeamsWithoutWO = tournament.unsortedTeamsWithoutWO() LabeledContent { Text(max(0, unsortedTeamsWithoutWO.count - tournament.teamCount).formatted()).font(.largeTitle) } label: { From 7c5ba47f72d4b83a12038483d81690ebda3141b7 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Sun, 26 May 2024 08:53:06 +0200 Subject: [PATCH 3/5] b25 --- 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 285d8e7..e9ff2e1 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -1923,7 +1923,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 24; + CURRENT_PROJECT_VERSION = 25; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -1961,7 +1961,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 24; + CURRENT_PROJECT_VERSION = 25; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; From a7bd1903130c83e13f920084ec769a36e9e95748 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Sun, 26 May 2024 13:04:53 +0200 Subject: [PATCH 4/5] attempt to fix lag stuff --- PadelClub/Data/GroupStage.swift | 7 ++++--- PadelClub/Data/Tournament.swift | 8 ++++++-- PadelClub/Views/GroupStage/GroupStageView.swift | 4 ++-- PadelClub/Views/GroupStage/GroupStagesView.swift | 6 +++--- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index 29e48bb..c8f5463 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -171,11 +171,12 @@ class GroupStage: ModelObject, Storable { } func availableToStart(playedMatches: [Match], in runningMatches: [Match]) -> [Match] { + return [] return playedMatches.filter({ $0.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false }) } func runningMatches(playedMatches: [Match]) -> [Match] { - playedMatches.filter({ $0.isRunning() }) + playedMatches.filter({ $0.isRunning() }).sorted(by: \.computedStartDateForSorting) } func readyMatches(playedMatches: [Match]) -> [Match] { @@ -183,7 +184,7 @@ class GroupStage: ModelObject, Storable { } func finishedMatches(playedMatches: [Match]) -> [Match] { - playedMatches.filter({ $0.hasEnded() }) + playedMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed() } private func _matchOrder() -> [Int] { @@ -365,7 +366,7 @@ extension GroupStage: Selectable { } func badgeValue() -> Int? { - runningMatches(playedMatches: playedMatches()).count + runningMatches(playedMatches: _matches()).count } func badgeValueColor() -> Color? { diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 1f82dcd..db7d353 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -929,8 +929,12 @@ class Tournament : ModelObject, Storable { return Calendar.current.compare(summonDate, to: expectedSummonDate, toGranularity: .minute) != ComparisonResult.orderedSame } - func availableToStart(_ allMatches: [Match]) -> [Match] { - let runningMatches = allMatches.filter({ $0.isRunning() && $0.isReady() }) + func groupStagesMatches() -> [Match] { + let groupStageIds = groupStages().map { $0.id } + return Store.main.filter(isIncluded: { $0.groupStage != nil && groupStageIds.contains($0.groupStage!) }) + } + + func availableToStart(_ allMatches: [Match], in runningMatches: [Match]) -> [Match] { return allMatches.filter({ $0.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false }).sorted(by: \.computedStartDateForSorting) } diff --git a/PadelClub/Views/GroupStage/GroupStageView.swift b/PadelClub/Views/GroupStage/GroupStageView.swift index 92c8c4c..0500678 100644 --- a/PadelClub/Views/GroupStage/GroupStageView.swift +++ b/PadelClub/Views/GroupStage/GroupStageView.swift @@ -15,13 +15,11 @@ struct GroupStageView: View { @State private var sortingMode: GroupStageSortingMode = .auto @State private var confirmRemoveAll: Bool = false @State private var confirmResetMatch: Bool = false - @State private var groupStageName: String = "" let playedMatches: [Match] init(groupStage: GroupStage) { self.groupStage = groupStage self.playedMatches = groupStage.playedMatches() - _groupStageName = State(wrappedValue: groupStage.groupStageTitle()) } var body: some View { @@ -224,6 +222,7 @@ struct GroupStageView: View { } struct GroupStageNameEditionView: View { + @Environment(\.dismiss) private var dismiss @EnvironmentObject var dataStore: DataStore let groupStage: GroupStage @State private var groupStageName: String = "" @@ -241,6 +240,7 @@ struct GroupStageNameEditionView: View { groupStageName = groupStageName.trimmed groupStage.name = groupStageName _save() + dismiss() } } footer: { HStack { diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index fc1c9f6..28753ac 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -55,7 +55,7 @@ struct GroupStagesView: View { init(tournament: Tournament) { self.tournament = tournament - self.allMatches = tournament.groupStages().flatMap({ $0.playedMatches() }) + self.allMatches = tournament.groupStagesMatches() if tournament.shouldVerifyGroupStage { _selectedDestination = State(wrappedValue: nil) @@ -81,14 +81,14 @@ struct GroupStagesView: View { GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations(), nilDestinationIsValid: true) switch selectedDestination { case .all: - let availableToStart = tournament.availableToStart(allMatches) let runningMatches = tournament.runningMatches(allMatches) + let availableToStart = tournament.availableToStart(allMatches, in: runningMatches) let readyMatches = tournament.readyMatches(allMatches) let finishedMatches = tournament.finishedMatches(allMatches) List { - MatchListView(section: "disponible", matches: availableToStart, matchViewStyle: .standardStyle, isExpanded: false) MatchListView(section: "en cours", matches: runningMatches, matchViewStyle: .standardStyle, isExpanded: false) + MatchListView(section: "disponible", matches: availableToStart, matchViewStyle: .standardStyle, isExpanded: false) MatchListView(section: "à lancer", matches: readyMatches, matchViewStyle: .standardStyle, isExpanded: false) MatchListView(section: "terminés", matches: finishedMatches, matchViewStyle: .standardStyle, isExpanded: false) } From 6ea9676a645c564a0026f67953fbaa2c0d036fea Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Mon, 27 May 2024 07:59:32 +0200 Subject: [PATCH 5/5] fix stuff --- PadelClub.xcodeproj/project.pbxproj | 4 ++ PadelClub/Data/GroupStage.swift | 2 +- PadelClub/Data/PlayerRegistration.swift | 4 +- PadelClub/Data/TeamRegistration.swift | 4 +- PadelClub/Data/Tournament.swift | 9 +-- .../HTML Templates/tournament-template.html | 4 +- PadelClub/Utils/HtmlGenerator.swift | 1 + PadelClub/Utils/HtmlService.swift | 8 +-- PadelClub/Utils/URLs.swift | 33 ++++++++++ PadelClub/Views/Calling/SendToAllView.swift | 50 ++++++++++++++-- .../Views/Cashier/Event/EventLinksView.swift | 60 +++++++++++++++++++ PadelClub/Views/Cashier/Event/EventView.swift | 9 ++- .../GroupStage/GroupStageSettingsView.swift | 49 ++++++++------- .../Tournament/Screen/BroadcastView.swift | 18 +++++- .../Components/TournamentStatusView.swift | 4 +- 15 files changed, 212 insertions(+), 47 deletions(-) create mode 100644 PadelClub/Views/Cashier/Event/EventLinksView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index e9ff2e1..8de65b6 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -141,6 +141,7 @@ FF1F4B8A2BFA02A4000B4573 /* groupstage-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B772BFA0105000B4573 /* groupstage-template.html */; }; FF1F4B8B2BFA02A4000B4573 /* groupstageentrant-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B792BFA0105000B4573 /* groupstageentrant-template.html */; }; FF1F4B8C2BFA02A4000B4573 /* match-template.html in Resources */ = {isa = PBXBuildFile; fileRef = FF1F4B7D2BFA0105000B4573 /* match-template.html */; }; + FF2B6F5E2C036A1500835EE7 /* EventLinksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */; }; FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */; }; FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */; }; FF3795662B9399AA004EA093 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3795652B9399AA004EA093 /* Persistence.swift */; }; @@ -460,6 +461,7 @@ FF1F4B7E2BFA0105000B4573 /* player-template.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "player-template.html"; sourceTree = ""; }; FF1F4B7F2BFA0105000B4573 /* tournament-template.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "tournament-template.html"; sourceTree = ""; }; FF1F4B812BFA0124000B4573 /* PrintSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrintSettingsView.swift; sourceTree = ""; }; + FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventLinksView.swift; sourceTree = ""; }; FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToAllView.swift; sourceTree = ""; }; FF3795612B9396D0004EA093 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; FF3795652B9399AA004EA093 /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; @@ -1209,6 +1211,7 @@ isa = PBXGroup; children = ( FFBF41812BF73EB3001B24CB /* EventView.swift */, + FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */, FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */, FFBF41852BF75FDA001B24CB /* EventSettingsView.swift */, FF8F263A2BAD528600650388 /* EventCreationView.swift */, @@ -1550,6 +1553,7 @@ FF8F264F2BAE0B9600650388 /* MatchTypeSelectionView.swift in Sources */, FF967D062BAF3C4200A9A3BD /* MatchSetupView.swift in Sources */, FF4AB6B52B9248200002987F /* NetworkManager.swift in Sources */, + FF2B6F5E2C036A1500835EE7 /* EventLinksView.swift in Sources */, FFB9C8752BBADDF700A0EF4F /* SeedInterval.swift in Sources */, FF025AE12BD0EB9000A86CF8 /* TournamentClubSettingsView.swift in Sources */, FFBF065C2BBD2657009D6715 /* GroupStageTeamView.swift in Sources */, diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index c8f5463..78dc605 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -50,7 +50,7 @@ class GroupStage: ModelObject, Storable { } func groupStageTitle(_ displayStyle: DisplayStyle = .wide) -> String { - if let name { return "Poule " + name } + if let name { return name } switch displayStyle { case .wide: return "Poule \(index + 1)" diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index f910364..db7f41b 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -74,8 +74,8 @@ class PlayerRegistration: ModelObject, Storable { } internal init(federalData: [String], sex: Int, sexUnknown: Bool) { - lastName = federalData[0].trimmed.capitalized - firstName = federalData[1].trimmed.uppercased() + lastName = federalData[0].trimmed.uppercased() + firstName = federalData[1].trimmed.capitalized birthdate = federalData[2] licenceId = federalData[3] clubName = federalData[4] diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index edf0ebd..eaf6cad 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -228,8 +228,10 @@ class TeamRegistration: ModelObject, Storable { func updatePlayers(_ players: Set, inTournamentCategory tournamentCategory: TournamentCategory) { + let previousPlayers = Set(unsortedPlayers()) + let playersToRemove = previousPlayers.subtracting(players) do { - try DataStore.shared.playerRegistrations.delete(contentOfs: unsortedPlayers()) + try DataStore.shared.playerRegistrations.delete(contentOfs: playersToRemove) } catch { Logger.error(error) } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index db7d353..aa1acfc 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -404,14 +404,9 @@ class Tournament : ModelObject, Storable { return false } } - - func shareURL() -> URL? { - return URLs.main.url.appending(path: "tournament/\(id)") - } - - func broadcastURL() -> URL? { - return URLs.main.url.appending(path: "tournament/\(id)/broadcast") + func shareURL(_ pageLink: PageLink = .matches) -> URL? { + return URLs.main.url.appending(path: "tournament/\(id)").appending(path: pageLink.path) } func courtUsed() -> [Int] { diff --git a/PadelClub/HTML Templates/tournament-template.html b/PadelClub/HTML Templates/tournament-template.html index 9bcb606..1d6f1b9 100644 --- a/PadelClub/HTML Templates/tournament-template.html +++ b/PadelClub/HTML Templates/tournament-template.html @@ -80,13 +80,13 @@ } .player { - font-size:28px; + font-size:26px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .hiddenPlayer { - font-size:28px; + font-size:26px; white-space: pre; overflow: hidden; text-overflow: ellipsis; diff --git a/PadelClub/Utils/HtmlGenerator.swift b/PadelClub/Utils/HtmlGenerator.swift index c8e8d79..d9a068d 100644 --- a/PadelClub/Utils/HtmlGenerator.swift +++ b/PadelClub/Utils/HtmlGenerator.swift @@ -103,6 +103,7 @@ class HtmlGenerator: ObservableObject { groupStageDone = 0 groupStageIsReady = false pdfDocument = PDFDocument() + rects.removeAll() try? FileManager.default.removeItem(at: pdfURL!) print("buildPDF", width, height, zoomLevel ?? 0) if let zoomLevel { diff --git a/PadelClub/Utils/HtmlService.swift b/PadelClub/Utils/HtmlService.swift index 74de02b..ea5a64b 100644 --- a/PadelClub/Utils/HtmlService.swift +++ b/PadelClub/Utils/HtmlService.swift @@ -66,7 +66,7 @@ enum HtmlService { } else { template = template.replacingOccurrences(of: "{{bracketStartDate}}", with: "") } - template = template.replacingOccurrences(of: "{{tournamentTitle}}", with: bracket.tournamentObject()!.tournamentTitle()) + template = template.replacingOccurrences(of: "{{tournamentTitle}}", with: bracket.tournamentObject()!.tournamentTitle(.short)) template = template.replacingOccurrences(of: "{{bracketTitle}}", with: bracket.groupStageTitle()) var col = "" @@ -133,7 +133,7 @@ enum HtmlService { case .player(let entrant): var template = html if let playerOne = entrant.players()[safe: 0] { - template = template.replacingOccurrences(of: "{{playerOne}}", with: playerOne.playerLabel(.short)) + template = template.replacingOccurrences(of: "{{playerOne}}", with: playerOne.playerLabel()) if withRank { template = template.replacingOccurrences(of: "{{weightOne}}", with: "(\(playerOne.formattedRank()))") } else { @@ -142,7 +142,7 @@ enum HtmlService { } if let playerTwo = entrant.players()[safe: 1] { - template = template.replacingOccurrences(of: "{{playerTwo}}", with: playerTwo.playerLabel(.short)) + template = template.replacingOccurrences(of: "{{playerTwo}}", with: playerTwo.playerLabel()) if withRank { template = template.replacingOccurrences(of: "{{weightTwo}}", with: "(\(playerTwo.formattedRank()))") } else { @@ -192,7 +192,7 @@ enum HtmlService { return bracket case .template(let tournament): var template = html - template = template.replacingOccurrences(of: "{{tournamentTitle}}", with: tournament.tournamentTitle()) + template = template.replacingOccurrences(of: "{{tournamentTitle}}", with: tournament.tournamentTitle(.short)) var brackets = "" for round in tournament.rounds() { brackets = brackets.appending(HtmlService.bracket(tournament: tournament, roundIndex: round.index).html(headName: headName, withRank: withRank, withScore: withScore)) diff --git a/PadelClub/Utils/URLs.swift b/PadelClub/Utils/URLs.swift index 194bb8a..e437869 100644 --- a/PadelClub/Utils/URLs.swift +++ b/PadelClub/Utils/URLs.swift @@ -21,3 +21,36 @@ enum URLs: String, Identifiable { } } + +enum PageLink: String, Identifiable, CaseIterable { + case teams = "Équipes" + case summons = "Convocations" + case groupStages = "Poules" + case matches = "Matchs" + case rankings = "Classement" + case broadcast = "Broadcast" + + var id: String { self.rawValue } + + func localizedLabel() -> String { + rawValue + } + + var path: String { + switch self { + case .matches: + return "" + case .teams: + return "teams" + case .summons: + return "summons" + case .rankings: + return "rankings" + case .groupStages: + return "group-stages" + case .broadcast: + return "broadcast" + } + } +} + diff --git a/PadelClub/Views/Calling/SendToAllView.swift b/PadelClub/Views/Calling/SendToAllView.swift index 38f1704..ec02e47 100644 --- a/PadelClub/Views/Calling/SendToAllView.swift +++ b/PadelClub/Views/Calling/SendToAllView.swift @@ -9,6 +9,8 @@ import SwiftUI import LeStorage struct SendToAllView: View { + @Environment(\.dismiss) var dismiss + @EnvironmentObject var dataStore: DataStore @Environment(Tournament.self) var tournament: Tournament @EnvironmentObject var networkMonitor: NetworkMonitor @@ -18,7 +20,8 @@ struct SendToAllView: View { @State private var sentError: ContactManagerError? = nil let addLink: Bool @State var cannotPayForTournament: Bool = false - + @State private var pageLink: PageLink = .teams + var messageSentFailed: Binding { Binding { sentError != nil @@ -69,12 +72,34 @@ struct SendToAllView: View { } } + if addLink { + Section { + let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings] + Picker(selection: $pageLink) { + ForEach(links) { pageLink in + Text(pageLink.localizedLabel()) + } + } label: { + Text("Choisir une page du tournoi en particulier") + } + .pickerStyle(.menu) + } + } + Section { RowButtonView("Contacter \(_totalString())") { self._contactAndPay() } } } + .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button("Annuler", role: .cancel) { + dismiss() + } + + } + } .environment(\.editMode, Binding.constant(EditMode.active)) .headerProminence(.increased) .navigationTitle("Préparation") @@ -141,7 +166,10 @@ struct SendToAllView: View { } func _teams() -> [TeamRegistration] { - _roundTeams() + _groupStagesTeams() + if _roundTeams().isEmpty && _groupStagesTeams().isEmpty { + return tournament.selectedSortedTeams() + } + return _roundTeams() + _groupStagesTeams() } func _roundTeams() -> [TeamRegistration] { @@ -172,11 +200,25 @@ struct SendToAllView: View { } } + func finalMessage() -> String { + var message = [String?]() + message.append("\n\n") + if addLink { + message.append(tournament.shareURL(pageLink)?.absoluteString) + } + + let signature = dataStore.user.summonsMessageSignature ?? dataStore.user.defaultSignature() + + message.append(signature) + + return message.compactMap { $0 }.joined(separator: "\n\n") + } + fileprivate func _contact() { if contactMethod == 0 { - contactType = .message(date: nil, recipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.phoneNumber }, body: addLink ? tournament.shareURL()?.absoluteString : nil, tournamentBuild: nil) + contactType = .message(date: nil, recipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.phoneNumber }, body: finalMessage(), tournamentBuild: nil) } else { - contactType = .mail(date: nil, recipients: tournament.umpireMail(), bccRecipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.email }, body: addLink ? tournament.shareURL()?.absoluteString : nil, subject: tournament.tournamentTitle(), tournamentBuild: nil) + contactType = .mail(date: nil, recipients: tournament.umpireMail(), bccRecipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.email }, body: finalMessage(), subject: tournament.tournamentTitle(), tournamentBuild: nil) } } diff --git a/PadelClub/Views/Cashier/Event/EventLinksView.swift b/PadelClub/Views/Cashier/Event/EventLinksView.swift new file mode 100644 index 0000000..1eb1358 --- /dev/null +++ b/PadelClub/Views/Cashier/Event/EventLinksView.swift @@ -0,0 +1,60 @@ +// +// EventLinksView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 26/05/2024. +// + +import SwiftUI + +struct EventLinksView: View { + let event: Event + @State private var pageLink: PageLink = .teams + + func eventLinksPasteData() -> String { + var link = [String]() + link.append(event.eventTitle()) + + event.tournaments.forEach({ tournament in + if let url = tournament.shareURL(pageLink) { + var tournamentLink = [String]() + tournamentLink.append(tournament.tournamentTitle()) + tournamentLink.append(url.absoluteString) + link.append(tournamentLink.joined(separator: "\n")) + } + }) + + return link.joined(separator: "\n\n") + } + + + var body: some View { + List { + Section { + let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings] + Picker(selection: $pageLink) { + ForEach(links) { pageLink in + Text(pageLink.localizedLabel()) + } + } label: { + Text("Choisir une page du tournoi en particulier") + } + .pickerStyle(.menu) + } + + let eventLinksPasteData = eventLinksPasteData() + Section { + Text(eventLinksPasteData) + .italic() + .multilineTextAlignment(.leading) + + + ShareLink("Partagez ce message", item: eventLinksPasteData) + } + } + } +} + +#Preview { + EventLinksView(event: Event.mock()) +} diff --git a/PadelClub/Views/Cashier/Event/EventView.swift b/PadelClub/Views/Cashier/Event/EventView.swift index c421f42..294d3df 100644 --- a/PadelClub/Views/Cashier/Event/EventView.swift +++ b/PadelClub/Views/Cashier/Event/EventView.swift @@ -9,6 +9,7 @@ import SwiftUI import LeStorage enum EventDestination: Identifiable, Selectable { + case links case tournaments(Event) case cashier @@ -18,6 +19,8 @@ enum EventDestination: Identifiable, Selectable { func selectionLabel() -> String { switch self { + case .links: + return "Liens" case .tournaments: return "Épreuves" case .cashier: @@ -27,6 +30,8 @@ enum EventDestination: Identifiable, Selectable { func badgeValue() -> Int? { switch self { + case .links: + return nil case .tournaments(let event): return event.tournaments.count case .cashier: @@ -49,7 +54,7 @@ struct EventView: View { @State private var selectedDestination: EventDestination? func allDestinations() -> [EventDestination] { - [.tournaments(event), .cashier] + [.links, .tournaments(event), .cashier] } var body: some View { @@ -60,6 +65,8 @@ struct EventView: View { EventSettingsView(event: event) case .some(let selectedEventDestination): switch selectedEventDestination { + case .links: + EventLinksView(event: event) case .tournaments(let event): EventTournamentsView(event: event) case .cashier: diff --git a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift index 6f175ae..e31a1cc 100644 --- a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift +++ b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift @@ -11,7 +11,6 @@ import LeStorage struct GroupStageSettingsView: View { @EnvironmentObject var dataStore: DataStore @Environment(Tournament.self) var tournament: Tournament - @State private var nameAlphabetical: Bool = false @State private var generationDone: Bool = false var body: some View { @@ -60,9 +59,36 @@ struct GroupStageSettingsView: View { Text("Redistribue les équipes par la méthode du serpentin") } - Toggle(isOn: $nameAlphabetical) { - Text("Nommer les poules alphabétiquement") + Section { + RowButtonView("Nommer les poules alphabétiquement", role: .destructive) { + let groupStages = tournament.groupStages() + groupStages.forEach { groupStage in + if let letter = Alphabet.letterForIndex(index: groupStage.index) { + groupStage.name = "Poule " + letter + } + } + do { + try dataStore.groupStages.addOrUpdate(contentOfs: groupStages) + } catch { + Logger.error(error) + } + } } + + Section { + RowButtonView("Supprimer les noms des poules", role: .destructive) { + let groupStages = tournament.groupStages() + groupStages.forEach { groupStage in + groupStage.name = nil + } + do { + try dataStore.groupStages.addOrUpdate(contentOfs: groupStages) + } catch { + Logger.error(error) + } + } + } + } .overlay(alignment: .bottom) { if generationDone { @@ -71,23 +97,6 @@ struct GroupStageSettingsView: View { .deferredRendering(for: .seconds(2)) } } - .onChange(of: nameAlphabetical) { - let groupStages = tournament.groupStages() - if nameAlphabetical { - groupStages.forEach { groupStage in - groupStage.name = Alphabet.letterForIndex(index: groupStage.index) - } - } else { - groupStages.forEach { groupStage in - groupStage.name = nil - } - } - do { - try dataStore.groupStages.addOrUpdate(contentOfs: groupStages) - } catch { - Logger.error(error) - } - } } diff --git a/PadelClub/Views/Tournament/Screen/BroadcastView.swift b/PadelClub/Views/Tournament/Screen/BroadcastView.swift index 027401c..b07a3b6 100644 --- a/PadelClub/Views/Tournament/Screen/BroadcastView.swift +++ b/PadelClub/Views/Tournament/Screen/BroadcastView.swift @@ -21,7 +21,8 @@ struct BroadcastView: View { let filter = CIFilter.qrCodeGenerator() @State private var urlToShow: String? @State private var tvMode: Bool = false - + @State private var pageLink: PageLink = .teams + let tournamentPublishingTip = TournamentPublishingTip() let tournamentTVBroadcastTip = TournamentTVBroadcastTip() @@ -180,15 +181,26 @@ struct BroadcastView: View { } } - if let url = tournament.shareURL() { + if let url = tournament.shareURL(pageLink) { LabeledContent { actionForURL(url) } label: { Text("Tournoi") + Text(pageLink.localizedLabel()) + } + } + + let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings] + Picker(selection: $pageLink) { + ForEach(links) { pageLink in + Text(pageLink.localizedLabel()).tag(pageLink) } + } label: { + Text("Modifier la page du tournoi à partager") } + .pickerStyle(.menu) - if let url = tournament.broadcastURL() { + if let url = tournament.shareURL(.broadcast) { LabeledContent { actionForURL(url) } label: { diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift index 3319c81..3796fc7 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift @@ -81,7 +81,7 @@ struct TournamentStatusView: View { dismiss() } } footer: { - Text(.init("Si votre tournoi n'a pas pu aboutir à cause de la météo ou autre, vous pouvez l'annuler et il ne sera pas comptabilisé. Toutes les données du tournoi seront conservées. Le tournoi visible sur [Padel Club](\(URLs.main)")) + Text(.init("Si votre tournoi n'a pas pu aboutir à cause de la météo ou autre, vous pouvez l'annuler et il ne sera pas comptabilisé. Toutes les données du tournoi seront conservées. Le tournoi visible sur [Padel Club](\(URLs.main.rawValue))")) } } @@ -90,7 +90,7 @@ struct TournamentStatusView: View { Text("Tournoi privé") } } footer: { - Text(.init("Le tournoi sera masqué sur le site [Padel Club](\(URLs.main)")) + Text(.init("Le tournoi sera masqué sur le site [Padel Club](\(URLs.main.rawValue))")) } } .toolbarBackground(.visible, for: .navigationBar)