diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 8d31700..19cae54 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -169,6 +169,8 @@ FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; }; FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; }; FFC1E10C2BAC7FB0008D6F59 /* ClubImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E10B2BAC7FB0008D6F59 /* ClubImportView.swift */; }; + FFC83D4F2BB807D100750834 /* RoundsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D4E2BB807D100750834 /* RoundsView.swift */; }; + FFC83D512BB8087E00750834 /* RoundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D502BB8087E00750834 /* RoundView.swift */; }; FFD783FD2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD783FC2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift */; }; FFD783FF2B91BA42000F62A6 /* PadelClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */; }; FFD784022B91C1B4000F62A6 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD784012B91C1B4000F62A6 /* WelcomeView.swift */; }; @@ -394,6 +396,8 @@ FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = ""; }; FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkFederalService.swift; sourceTree = ""; }; FFC1E10B2BAC7FB0008D6F59 /* ClubImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubImportView.swift; sourceTree = ""; }; + FFC83D4E2BB807D100750834 /* RoundsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundsView.swift; sourceTree = ""; }; + FFC83D502BB8087E00750834 /* RoundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundView.swift; sourceTree = ""; }; FFD783FC2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgendaDestinationPickerView.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 = ""; }; @@ -547,6 +551,7 @@ FF39719B2B8DE04B004C4E75 /* Navigation */, FF8F26392BAD526A00650388 /* Event */, FF1DC54D2BAB34FA00FD8220 /* Club */, + FFC83D4B2BB807C200750834 /* Round */, FF967CF92BAEE11500A9A3BD /* GroupStage */, FF967CFE2BAEEF5A00A9A3BD /* Match */, FF967D072BAF3D3000A9A3BD /* Team */, @@ -868,6 +873,15 @@ path = Team; sourceTree = ""; }; + FFC83D4B2BB807C200750834 /* Round */ = { + isa = PBXGroup; + children = ( + FFC83D4E2BB807D100750834 /* RoundsView.swift */, + FFC83D502BB8087E00750834 /* RoundView.swift */, + ); + path = Round; + sourceTree = ""; + }; FFD783FB2B91B919000F62A6 /* Agenda */ = { isa = PBXGroup; children = ( @@ -1125,6 +1139,7 @@ FF8F263F2BAD7D5C00650388 /* Event.swift in Sources */, FF089EBF2BB0B14600F0AEC7 /* FileImportView.swift in Sources */, C4A47D9F2B7D0BCE00ADC637 /* StepperView.swift in Sources */, + FFC83D4F2BB807D100750834 /* RoundsView.swift in Sources */, FF1CBC1B2BB53D1F0036DAAB /* FederalTournament.swift in Sources */, FF8F26412BADFC8700650388 /* TournamentInitView.swift in Sources */, C4A47D8A2B7BBB6500ADC637 /* SubscriptionView.swift in Sources */, @@ -1145,6 +1160,7 @@ FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */, FF0EC5202BB16F680056B6D1 /* SwiftParser.swift in Sources */, C4A47DA92B85F82100ADC637 /* ChangePasswordView.swift in Sources */, + FFC83D512BB8087E00750834 /* RoundView.swift in Sources */, FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */, FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */, FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */, diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index b06f625..4923833 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -44,7 +44,7 @@ class GroupStage: ModelObject, Storable { Store.main.findById(tournament) } - func title(_ displayStyle: DisplayStyle = .wide) -> String { + func groupStageTitle(_ displayStyle: DisplayStyle = .wide) -> String { switch displayStyle { case .wide: return "Poule \(index + 1)" diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index cb3459e..36c47fc 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -43,14 +43,37 @@ class Match: ModelObject, Storable { self.order = order } - func title(_ displayStyle: DisplayStyle = .wide) -> String { + func indexInRound() -> Int { + if groupStage != nil { + return index + } + return RoundRule.matchIndexWithinRound(fromMatchIndex: index) + } + + func matchTitle(_ displayStyle: DisplayStyle = .wide) -> String { switch displayStyle { case .wide: - return "Match \(index + 1)" + return "Match \(indexInRound() + 1)" case .short: - return "#\(index + 1)" + return "#\(indexInRound() + 1)" } } + + func topPreviousRoundMatches() -> Int { + index * 2 + 1 + } + + func bottomPreviousRoundMatches() -> Int { + (index + 1) * 2 + } + + func previousMatches() -> [Match] { + guard let roundObject else { return [] } + return Store.main.filter { match in + (match.index == topPreviousRoundMatches() || match.index == bottomPreviousRoundMatches()) + && match.round == roundObject.previousRound()?.id + }.sorted(by: \.index) + } var matchFormat: MatchFormat { get { diff --git a/PadelClub/Data/MockData.swift b/PadelClub/Data/MockData.swift index ada3ae4..17429d1 100644 --- a/PadelClub/Data/MockData.swift +++ b/PadelClub/Data/MockData.swift @@ -17,6 +17,12 @@ extension Club { } } +extension Round { + static func mock() -> Round { + Round(tournament: "", index: 0) + } +} + extension Tournament { static func mock() -> Tournament { Tournament(groupStageSortMode: .snake, teamSorting: .inscriptionDate, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .senior) diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index 6f7c859..8442c08 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -18,18 +18,44 @@ class Round: ModelObject, Storable { var loser: String? var format: Int? - internal init(id: String = Store.randomId(), tournament: String, index: Int, loser: String? = nil, format: Int? = nil) { - self.id = id + internal init(tournament: String, index: Int, loser: String? = nil, format: Int? = nil) { self.tournament = tournament self.index = index self.loser = loser self.format = format } + func hasStarted() -> Bool { + matches.anySatisfy({ $0.hasStarted() }) + } + + func hasEnded() -> Bool { + matches.allSatisfy({ $0.hasEnded() }) + } + var matches: [Match] { Store.main.filter { $0.round == self.id } } + func previousRound() -> Round? { + Store.main.filter(isIncluded: { $0.tournament == tournament && $0.index == index + 1 }).first + } + + func nextRound() -> Round? { + Store.main.filter(isIncluded: { $0.tournament == tournament && $0.index == index - 1 }).first + } + + func roundTitle(_ displayStyle: DisplayStyle = .wide) -> String { + RoundRule.roundName(fromRoundIndex: index) + } + + func roundStatus() -> String { + if hasStarted() && hasEnded() == false { + return "en cours" + } else { + return "à démarrer" + } + } var loserRound: Round? { guard let loser else { return nil } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 161d934..5b8cdcc 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -139,6 +139,15 @@ class Tournament : ModelObject, Storable { Store.main.filter { $0.tournament == self.id }.sorted(by: \.index) } + func getActiveRound() -> Round? { + let rounds = rounds() + return rounds.filter({ $0.hasStarted() && $0.hasEnded() == false }).sorted(by: \.index).reversed().first ?? rounds.first + } + + func rounds() -> [Round] { + Store.main.filter { $0.tournament == self.id }.sorted(by: \.index).reversed() + } + func sortedTeams() -> [TeamRegistration] { let teams = selectedSortedTeams() return teams + waitingListTeams(in: teams) @@ -177,7 +186,7 @@ class Tournament : ModelObject, Storable { } let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) - print(id, title(), duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + print(id, tournamentTitle(), duration.formatted(.units(allowed: [.seconds, .milliseconds]))) return _sortedTeams } @@ -248,7 +257,7 @@ class Tournament : ModelObject, Storable { } //todo - var rounds: Int { + func roundCount() -> Int { 4 } @@ -367,7 +376,7 @@ class Tournament : ModelObject, Storable { unsortedTeams().first(where: { $0.includes(players) }) } - func title(_ displayStyle: DisplayStyle = .wide) -> String { + func tournamentTitle(_ displayStyle: DisplayStyle = .wide) -> String { [tournamentLevel.localizedLabel(displayStyle), tournamentCategory.localizedLabel(displayStyle), name].compactMap({ $0 }).joined(separator: " ") } @@ -416,6 +425,14 @@ class Tournament : ModelObject, Storable { } + func bracketStatus() -> String { + if let round = getActiveRound() { + return [round.roundTitle(), round.roundStatus()].joined(separator: " ") + } else { + return "à construire" + } + } + func groupStageStatus() -> String { let runningGroupStages = groupStages().filter({ $0.isRunning() }) if groupStagesAreOver() { return "terminées" } @@ -442,6 +459,7 @@ class Tournament : ModelObject, Storable { func buildStructure() { buildGroupStages() + buildBracket() } func buildGroupStages() { @@ -461,6 +479,41 @@ class Tournament : ModelObject, Storable { refreshGroupStages() } + func bracketTeamCount() -> Int { + let bracketTeamCount = teamCount - (teamsPerGroupStage - qualifiedPerGroupStage) * groupStageCount + (groupStageAdditionalQualified * (groupStageCount > 0 ? 1 : 0)) + return bracketTeamCount + } + + func buildBracket() { + try? DataStore.shared.rounds.delete(contentOfs: rounds()) + +// if let loserBrackets { +// removeFromLoserBrackets(loserBrackets) +// } + + unsortedTeams().forEach({ $0.bracketPosition = nil }) + let roundCount = RoundRule.numberOfRounds(forTeams: bracketTeamCount()) + + let rounds = (0.. 0 { switch groupStageOrderingMode { case .random: - setBrackets(randomize: true) + setGroupStage(randomize: true) case .snake: - setBrackets(randomize: false) + setGroupStage(randomize: false) case .swiss: - setBrackets(randomize: true) + setGroupStage(randomize: true) } } } - func setBrackets(randomize: Bool) { + func setGroupStage(randomize: Bool) { let groupStages = groupStages() let numberOfBracketsAsInt = groupStages.count // let teamsPerBracket = teamsPerBracket @@ -604,7 +657,7 @@ class Tournament : ModelObject, Storable { } func loserBracketSmartMatchFormat(_ roundIndex: Int) -> MatchFormat { - let idx = rounds - roundIndex + let idx = roundCount() - roundIndex let format = tournamentLevel.federalFormatForLoserBracketRound(idx) if loserBracketMatchFormat.rank > format.rank { return format @@ -623,7 +676,7 @@ class Tournament : ModelObject, Storable { } func roundSmartMatchFormat(_ roundIndex: Int) -> MatchFormat { - let idx = rounds - roundIndex + let idx = roundCount() - roundIndex let format = tournamentLevel.federalFormatForBracketRound(idx) if matchFormat.rank > format.rank { return format @@ -652,7 +705,7 @@ class Tournament : ModelObject, Storable { override func deleteDependencies() throws { try Store.main.deleteDependencies(items: self.unsortedTeams()) try Store.main.deleteDependencies(items: self.groupStages()) - //try Store.main.deleteDependencies(items: self.rounds()) + try Store.main.deleteDependencies(items: self.rounds()) } } diff --git a/PadelClub/Manager/PadelRule.swift b/PadelClub/Manager/PadelRule.swift index a4a8af5..d244c41 100644 --- a/PadelClub/Manager/PadelRule.swift +++ b/PadelClub/Manager/PadelRule.swift @@ -524,28 +524,28 @@ enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable { } } - enum NewBallSystem { - case perField - case perMatch(fromRound: Int?) - - func localizedLabel(loserBracket: Bool = false) -> String { - switch self { - case .perField: - return "3 / piste" - case .perMatch(let fromRound): - if fromRound != nil { - if loserBracket { - return "3 / match pour les perdants des \(RoundLabel.shortLabels[fromRound!].lowercased())s" - } else { - return "3 / match à partir des \(RoundLabel.shortLabels[fromRound!].lowercased())s" - } - } else { - return "3 / match" - } - } - } - } - +// enum NewBallSystem { +// case perField +// case perMatch(fromRound: Int?) +// +// func localizedLabel(loserBracket: Bool = false) -> String { +// switch self { +// case .perField: +// return "3 / piste" +// case .perMatch(let fromRound): +// if fromRound != nil { +// if loserBracket { +// return "3 / match pour les perdants des \(RoundLabel.shortLabels[fromRound!].lowercased())s" +// } else { +// return "3 / match à partir des \(RoundLabel.shortLabels[fromRound!].lowercased())s" +// } +// } else { +// return "3 / match" +// } +// } +// } +// } +// func minimumFormatFinalTableAndQualifier(roundIndex: Int) -> MatchFormat? { switch self { case .p25: @@ -591,30 +591,30 @@ enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable { } - func newBallsFinalTable() -> NewBallSystem? { - switch self { - case .p25, .p100: - return .perField - case .p250: - return .perMatch(fromRound: 1) //demi - case .p500: - return .perMatch(fromRound: 2) //quart - case .p1000, .p1500, .p2000: - return .perMatch(fromRound: nil) - } - } - - func newBallsLoserBracket() -> NewBallSystem? { - switch self { - case .p25, .p100: - return nil - case .p250: - return .perMatch(fromRound: 1) //demi - case .p500, .p1000, .p1500, .p2000: - return .perMatch(fromRound: 2) //quart - } - } - +// func newBallsFinalTable() -> NewBallSystem? { +// switch self { +// case .p25, .p100: +// return .perField +// case .p250: +// return .perMatch(fromRound: 1) //demi +// case .p500: +// return .perMatch(fromRound: 2) //quart +// case .p1000, .p1500, .p2000: +// return .perMatch(fromRound: nil) +// } +// } +// +// func newBallsLoserBracket() -> NewBallSystem? { +// switch self { +// case .p25, .p100: +// return nil +// case .p250: +// return .perMatch(fromRound: 1) //demi +// case .p500, .p1000, .p1500, .p2000: +// return .perMatch(fromRound: 2) //quart +// } +// } +// } enum TournamentCategory: Int, Hashable, Codable, CaseIterable, Identifiable { @@ -1378,12 +1378,7 @@ enum PlayersCountRange: Int, CaseIterable { } } -enum RoundLabel { - static let labels = ["Finale", "Demi-finale", "Quart de finale", "8ème de finale", "16ème de finale", "32ème de finale", "64ème de finale"] - static let shortLabels = ["Finale", "Demi", "Quart", "8ème", "16ème", "32ème", "64ème"] - static let colors = ["#d4afb9", "#d1cfe2", "#9cadce", "#7ec4cf", "#daeaf6", "#caffbf"] - static let freeMatchLabels = ["Finale", "Demi-finale", "Quart de finale", "8ème de finale", "16ème de finale", "32ème de finale", "Poule"] - +enum RoundRule { static func loserBrackets(index: Int) -> [String] { switch index { case 1: @@ -1394,6 +1389,46 @@ enum RoundLabel { return ["#9/#10", "#11/#12", "#13/#14", "#15/#16", "#17/#18", "#19/#20", "#21/#22", "#23/#24", "#25/#26", "#27/#28", "#29/#30", "#31/#32"] } } + + static func teamsInFirstRound(forTeams teams: Int) -> Int { + Int(pow(2.0, ceil(log2(Double(teams))))) + } + + static func numberOfMatches(forTeams teams: Int) -> Int { + teamsInFirstRound(forTeams: teams) - 1 + } + + static func numberOfRounds(forTeams teams: Int) -> Int { + Int(log2(Double(teamsInFirstRound(forTeams: teams)))) + } + + static func roundIndex(fromMatchIndex matchIndex: Int) -> Int { + Int(log2(Double(matchIndex + 1))) + } + + static func matchIndexWithinRound(fromMatchIndex matchIndex: Int) -> Int { + let roundIndex = roundIndex(fromMatchIndex: matchIndex) + let matchIndexWithinRound = matchIndex - (Int(pow(2.0, Double(roundIndex))) - 1) + return matchIndexWithinRound + } + + static func roundName(fromMatchIndex matchIndex: Int) -> String { + let roundIndex = roundIndex(fromMatchIndex: matchIndex) + return roundName(fromRoundIndex: roundIndex) + } + + static func roundName(fromRoundIndex roundIndex: Int) -> String { + switch roundIndex { + case 0: + return "Finale" + case 1: + return "Demi-finale" + case 2: + return "Quart de finale" + default: + return "\(Int(pow(2.0, Double(roundIndex))))ème" + } + } } enum AnimationType: Int, CaseIterable, Hashable, Identifiable { diff --git a/PadelClub/Views/ClubView.swift b/PadelClub/Views/ClubView.swift index 8057a73..219d105 100644 --- a/PadelClub/Views/ClubView.swift +++ b/PadelClub/Views/ClubView.swift @@ -13,7 +13,7 @@ struct ClubView: View { var body: some View { List(club.tournaments) { tournament in - Text(tournament.title()) + Text(tournament.tournamentTitle()) }.navigationTitle(club.name) } } diff --git a/PadelClub/Views/GroupStage/GroupStageView.swift b/PadelClub/Views/GroupStage/GroupStageView.swift index 23b05ef..c45b87e 100644 --- a/PadelClub/Views/GroupStage/GroupStageView.swift +++ b/PadelClub/Views/GroupStage/GroupStageView.swift @@ -66,9 +66,9 @@ struct GroupStageView: View { } header: { HStack { if groupStage.isBroadcasted() { - Label(groupStage.title(), systemImage: "airplayvideo") + Label(groupStage.groupStageTitle(), systemImage: "airplayvideo") } else { - Text(groupStage.title()) + Text(groupStage.groupStageTitle()) } Spacer() if let startDate = groupStage.startDate { diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index f21ef4a..a49dfc7 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -22,7 +22,7 @@ struct GroupStagesView: View { case -1: return "Toutes les poules" default: - return tournament.groupStages()[selectedGroupStageIndex].title() + return tournament.groupStages()[selectedGroupStageIndex].groupStageTitle() } } // @@ -166,7 +166,7 @@ struct GroupStagesView: View { MatchRowView(match: match, setupSeedContext: false, matchViewStyle: .sectionedStandardStyle) } } header: { - Text("Matchs de la " + groupStage.title()) + Text("Matchs de la " + groupStage.groupStageTitle()) } } } @@ -178,7 +178,7 @@ struct GroupStagesView: View { Picker(selection: $selectedGroupStageIndex) { Text("Toutes").tag(-1) ForEach(tournament.groupStages()) { groupStage in - Text(groupStage.title(.short)).tag(groupStage.index) + Text(groupStage.groupStageTitle(.short)).tag(groupStage.index) } } label: { @@ -189,7 +189,7 @@ struct GroupStagesView: View { Picker(selection: $selectedGroupStageIndex) { Image(systemName: "square.stack").tag(-1) ForEach(tournament.groupStages()) { groupStage in - Text(groupStage.title(.short)).tag(groupStage.index) + Text(groupStage.groupStageTitle(.short)).tag(groupStage.index) } } label: { @@ -200,7 +200,7 @@ struct GroupStagesView: View { Picker(selection: $selectedGroupStageIndex) { Text("Voir toutes les poules").tag(-1) ForEach(tournament.groupStages()) { groupStage in - Text(groupStage.title()).tag(groupStage.index) + Text(groupStage.groupStageTitle()).tag(groupStage.index) } } label: { Text("\(tournament.groupStages().count.formatted()) poules") diff --git a/PadelClub/Views/Match/MatchDetailView.swift b/PadelClub/Views/Match/MatchDetailView.swift index bbdd3c6..0215b38 100644 --- a/PadelClub/Views/Match/MatchDetailView.swift +++ b/PadelClub/Views/Match/MatchDetailView.swift @@ -304,7 +304,7 @@ struct MatchDetailView: View { // } // } // } - .navigationTitle(match.title()) + .navigationTitle(match.matchTitle()) .navigationBarTitleDisplayMode(.large) } diff --git a/PadelClub/Views/Match/MatchSummaryView.swift b/PadelClub/Views/Match/MatchSummaryView.swift index 1e2a7dc..9b9e90a 100644 --- a/PadelClub/Views/Match/MatchSummaryView.swift +++ b/PadelClub/Views/Match/MatchSummaryView.swift @@ -59,7 +59,7 @@ struct MatchSummaryView: View { HStack { if match.isGroupStage() && matchViewStyle != .feedStyle { if let groupStage = match.groupStageObject, matchViewStyle == .standardStyle { - Text(groupStage.title()) + Text(groupStage.groupStageTitle()) } // if let index = match.entrantOne()?.bracketPositions?.first, let index2 = match.entrantTwo()?.bracketPositions?.first { // Text("#\(index) contre #\(index2)") @@ -68,7 +68,7 @@ struct MatchSummaryView: View { if matchViewStyle == .feedStyle { //tournamentHeaderView(currentTournament) } else if matchViewStyle != .sectionedStandardStyle { - Text(match.title(.short)) + Text(match.matchTitle(.short)) } } if matchViewStyle == .standardStyle || matchViewStyle == .sectionedStandardStyle diff --git a/PadelClub/Views/Navigation/Agenda/CalendarView.swift b/PadelClub/Views/Navigation/Agenda/CalendarView.swift index 0eab370..90dcdd2 100644 --- a/PadelClub/Views/Navigation/Agenda/CalendarView.swift +++ b/PadelClub/Views/Navigation/Agenda/CalendarView.swift @@ -99,7 +99,7 @@ struct CalendarView: View { // ForEach(tournamentsByDay) { tournament in // NavigationLink(value: tournament) { // HStack { -// Text(tournament.title()) +// Text(tournament.tournamentTitle()) // Spacer() // Text(tournament.sortedTeams().count.formatted()) // } diff --git a/PadelClub/Views/Round/RoundView.swift b/PadelClub/Views/Round/RoundView.swift new file mode 100644 index 0000000..0ddfe48 --- /dev/null +++ b/PadelClub/Views/Round/RoundView.swift @@ -0,0 +1,29 @@ +// +// RoundView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 30/03/2024. +// + +import SwiftUI + +struct RoundView: View { + var round: Round + + var body: some View { + List { + ForEach(round.matches) { match in + Section { + MatchRowView(match: match, setupSeedContext: false, matchViewStyle: .sectionedStandardStyle) + } header: { + Text(match.matchTitle()) + } + } + } + .headerProminence(.increased) + } +} + +#Preview { + RoundView(round: Round.mock()) +} diff --git a/PadelClub/Views/Round/RoundsView.swift b/PadelClub/Views/Round/RoundsView.swift new file mode 100644 index 0000000..4787023 --- /dev/null +++ b/PadelClub/Views/Round/RoundsView.swift @@ -0,0 +1,80 @@ +// +// RoundsView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 30/03/2024. +// + +import SwiftUI + +protocol Selectable { + func selectionLabel() -> String +} + +extension Round: Selectable { + func selectionLabel() -> String { + roundTitle() + } +} + +struct GenericDestinationPickerView: View { + @Binding var selectedDestination: T? + let destinations: [T] + + var body: some View { + ScrollView(.horizontal) { + HStack { + ForEach(destinations) { destination in + Button { + selectedDestination = destination + } label: { + Text(destination.selectionLabel()) + } + .padding() + .background { + Capsule() + .fill(Color.white) + .opacity(selectedDestination?.id == destination.id ? 1.0 : 0.5) + } + .buttonStyle(.plain) + } + } + .fixedSize() + .padding(8) + } + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .background(Material.ultraThinMaterial) + .overlay { + VStack(spacing: 0) { + Spacer() + Divider() + } + } + } +} + +struct RoundsView: View { + var tournament: Tournament + @State private var selectedRound: Round? + + init(tournament: Tournament) { + self.tournament = tournament + _selectedRound = State(wrappedValue: tournament.getActiveRound()) + } + + var body: some View { + VStack(spacing: 0) { + GenericDestinationPickerView(selectedDestination: $selectedRound, destinations: tournament.rounds()) + if let selectedRound { + RoundView(round: selectedRound) + } + } + .navigationTitle(selectedRound?.roundTitle() ?? "") + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + } +} + +#Preview { + RoundsView(tournament: Tournament.mock()) +} diff --git a/PadelClub/Views/Tournament/Screen/Screen.swift b/PadelClub/Views/Tournament/Screen/Screen.swift index f743966..bf665a4 100644 --- a/PadelClub/Views/Tournament/Screen/Screen.swift +++ b/PadelClub/Views/Tournament/Screen/Screen.swift @@ -10,6 +10,7 @@ import Foundation enum Screen: String, Codable { case inscription case groupStage + case round case settings case structure } diff --git a/PadelClub/Views/Tournament/Screen/TableStructureView.swift b/PadelClub/Views/Tournament/Screen/TableStructureView.swift index 85d5e60..7314258 100644 --- a/PadelClub/Views/Tournament/Screen/TableStructureView.swift +++ b/PadelClub/Views/Tournament/Screen/TableStructureView.swift @@ -234,6 +234,8 @@ struct TableStructureView: View { } private func _save(rebuildEverything: Bool = false) { + _verifyValueIntegrity() + do { let requirements = Set(updatedElements.compactMap { $0.requiresRebuilding }) diff --git a/PadelClub/Views/Tournament/TournamentRunningView.swift b/PadelClub/Views/Tournament/TournamentRunningView.swift index 872e790..d29ee90 100644 --- a/PadelClub/Views/Tournament/TournamentRunningView.swift +++ b/PadelClub/Views/Tournament/TournamentRunningView.swift @@ -21,6 +21,17 @@ struct TournamentRunningView: View { } } } + + Section { + NavigationLink(value: Screen.round) { + LabeledContent { + Text(tournament.bracketStatus()) + } label: { + Text("Tableau") + } + } + } + } } diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index 21151a9..43fe7c6 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -76,6 +76,8 @@ struct TournamentView: View { InscriptionManagerView(tournament: tournament) case .groupStage: GroupStagesView() + case .round: + RoundsView(tournament: tournament) } } .environment(tournament) @@ -84,7 +86,7 @@ struct TournamentView: View { .toolbar { ToolbarItem(placement: .principal) { VStack { - Text(tournament.title()).font(.headline) + Text(tournament.tournamentTitle()).font(.headline) Text(tournament.formattedDate()) .font(.subheadline).foregroundStyle(.secondary) }