add stuff for rounds

multistore
Razmig Sarkissian 2 years ago
parent c0fb636621
commit 97e5d641fb
  1. 16
      PadelClub.xcodeproj/project.pbxproj
  2. 2
      PadelClub/Data/GroupStage.swift
  3. 29
      PadelClub/Data/Match.swift
  4. 6
      PadelClub/Data/MockData.swift
  5. 30
      PadelClub/Data/Round.swift
  6. 73
      PadelClub/Data/Tournament.swift
  7. 139
      PadelClub/Manager/PadelRule.swift
  8. 2
      PadelClub/Views/ClubView.swift
  9. 4
      PadelClub/Views/GroupStage/GroupStageView.swift
  10. 10
      PadelClub/Views/GroupStage/GroupStagesView.swift
  11. 2
      PadelClub/Views/Match/MatchDetailView.swift
  12. 4
      PadelClub/Views/Match/MatchSummaryView.swift
  13. 2
      PadelClub/Views/Navigation/Agenda/CalendarView.swift
  14. 29
      PadelClub/Views/Round/RoundView.swift
  15. 80
      PadelClub/Views/Round/RoundsView.swift
  16. 1
      PadelClub/Views/Tournament/Screen/Screen.swift
  17. 2
      PadelClub/Views/Tournament/Screen/TableStructureView.swift
  18. 11
      PadelClub/Views/Tournament/TournamentRunningView.swift
  19. 4
      PadelClub/Views/Tournament/TournamentView.swift

@ -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 = "<group>"; };
FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkFederalService.swift; sourceTree = "<group>"; };
FFC1E10B2BAC7FB0008D6F59 /* ClubImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubImportView.swift; sourceTree = "<group>"; };
FFC83D4E2BB807D100750834 /* RoundsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundsView.swift; sourceTree = "<group>"; };
FFC83D502BB8087E00750834 /* RoundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundView.swift; sourceTree = "<group>"; };
FFD783FC2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgendaDestinationPickerView.swift; sourceTree = "<group>"; };
FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubView.swift; sourceTree = "<group>"; };
FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = "<group>"; };
@ -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 = "<group>";
};
FFC83D4B2BB807C200750834 /* Round */ = {
isa = PBXGroup;
children = (
FFC83D4E2BB807D100750834 /* RoundsView.swift */,
FFC83D502BB8087E00750834 /* RoundView.swift */,
);
path = Round;
sourceTree = "<group>";
};
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 */,

@ -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)"

@ -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 {

@ -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)

@ -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 }

@ -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..<roundCount).map { //index 0 is the final
Round(tournament: id, index: $0)
}
try? DataStore.shared.rounds.addOrUpdate(contentOfs: rounds)
let matchCount = RoundRule.numberOfMatches(forTeams: bracketTeamCount())
let matches = (0..<matchCount).map { //0 is final match
let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0)
let round = rounds[roundIndex]
return Match(round: round.id, index: $0, matchFormat: matchFormat)
}
print(matches.map {
(RoundRule.roundName(fromMatchIndex: $0.index), RoundRule.matchIndexWithinRound(fromMatchIndex: $0.index))
})
try? DataStore.shared.matches.addOrUpdate(contentOfs: matches)
}
func resetStructure() {
}
@ -478,16 +531,16 @@ class Tournament : ModelObject, Storable {
if groupStageCount > 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())
}
}

@ -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 {

@ -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)
}
}

@ -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 {

@ -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")

@ -304,7 +304,7 @@ struct MatchDetailView: View {
// }
// }
// }
.navigationTitle(match.title())
.navigationTitle(match.matchTitle())
.navigationBarTitleDisplayMode(.large)
}

@ -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

@ -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())
// }

@ -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())
}

@ -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<T: Identifiable & Selectable>: 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())
}

@ -10,6 +10,7 @@ import Foundation
enum Screen: String, Codable {
case inscription
case groupStage
case round
case settings
case structure
}

@ -234,6 +234,8 @@ struct TableStructureView: View {
}
private func _save(rebuildEverything: Bool = false) {
_verifyValueIntegrity()
do {
let requirements = Set(updatedElements.compactMap { $0.requiresRebuilding })

@ -21,6 +21,17 @@ struct TournamentRunningView: View {
}
}
}
Section {
NavigationLink(value: Screen.round) {
LabeledContent {
Text(tournament.bracketStatus())
} label: {
Text("Tableau")
}
}
}
}
}

@ -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)
}

Loading…
Cancel
Save