diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 1d4d23d..c1d13cd 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -217,6 +217,7 @@ FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0A2BAF3D4C00A9A3BD /* TeamPickerView.swift */; }; FF967D0D2BAF3EB300A9A3BD /* MatchDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0C2BAF3EB200A9A3BD /* MatchDateView.swift */; }; FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0E2BAF63B000A9A3BD /* PlayerBlockView.swift */; }; + FF9AC3952BE3627B00C2E883 /* GroupStageTeamReplacementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9AC3942BE3627B00C2E883 /* GroupStageTeamReplacementView.swift */; }; FFA1B1292BB71773006CE248 /* PadelClubButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA1B1282BB71773006CE248 /* PadelClubButtonView.swift */; }; FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */; }; FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; }; @@ -513,6 +514,7 @@ FF967D0A2BAF3D4C00A9A3BD /* TeamPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamPickerView.swift; sourceTree = ""; }; FF967D0C2BAF3EB200A9A3BD /* MatchDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchDateView.swift; sourceTree = ""; }; FF967D0E2BAF63B000A9A3BD /* PlayerBlockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerBlockView.swift; sourceTree = ""; }; + FF9AC3942BE3627B00C2E883 /* GroupStageTeamReplacementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageTeamReplacementView.swift; sourceTree = ""; }; FFA1B1282BB71773006CE248 /* PadelClubButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubButtonView.swift; sourceTree = ""; }; FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileImportManager.swift; sourceTree = ""; }; FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudConvert.swift; sourceTree = ""; }; @@ -1099,7 +1101,8 @@ FF967CFA2BAEE13800A9A3BD /* GroupStageView.swift */, FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */, FF5DA18E2BB9268800A33061 /* GroupStageSettingsView.swift */, - FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */, + FF9AC3932BE3625D00C2E883 /* Components */, + FF9AC3922BE3625200C2E883 /* Shared */, ); path = GroupStage; sourceTree = ""; @@ -1128,6 +1131,22 @@ path = Team; sourceTree = ""; }; + FF9AC3922BE3625200C2E883 /* Shared */ = { + isa = PBXGroup; + children = ( + FF9AC3942BE3627B00C2E883 /* GroupStageTeamReplacementView.swift */, + ); + path = Shared; + sourceTree = ""; + }; + FF9AC3932BE3625D00C2E883 /* Components */ = { + isa = PBXGroup; + children = ( + FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */, + ); + path = Components; + sourceTree = ""; + }; FFC83D4B2BB807C200750834 /* Round */ = { isa = PBXGroup; children = ( @@ -1572,6 +1591,7 @@ C425D4012B6D249D002A7B48 /* PadelClubApp.swift in Sources */, FF8F26432BADFE5B00650388 /* TournamentSettingsView.swift in Sources */, C49EF03C2BE15AF80077B5AA /* String+Crypto.swift in Sources */, + FF9AC3952BE3627B00C2E883 /* GroupStageTeamReplacementView.swift in Sources */, FF4C7F022BBBD7150031B6A3 /* TabItemModifier.swift in Sources */, FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */, FFD784042B91C280000F62A6 /* EmptyActivityView.swift in Sources */, diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index 5db8453..e8213f4 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -8,6 +8,7 @@ import Foundation import LeStorage import Algorithms +import SwiftUI @Observable class GroupStage: ModelObject, Storable { @@ -309,6 +310,10 @@ extension GroupStage: Selectable { runningMatches().count } + func badgeValueColor() -> Color? { + return nil + } + func badgeImage() -> Badge? { hasEnded() ? .checkmark : nil } diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index a5851fc..de57eed 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -7,6 +7,7 @@ import Foundation import LeStorage +import SwiftUI @Observable class Round: ModelObject, Storable { @@ -503,6 +504,10 @@ extension Round: Selectable { } } + func badgeValueColor() -> Color? { + return nil + } + func badgeImage() -> Badge? { hasEnded() ? .checkmark : nil } diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index 57eb810..1b8b5d2 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -214,6 +214,38 @@ class TeamRegistration: ModelObject, Storable { groupStagePosition != nil && bracketPosition != nil } + typealias TeamRange = (left: TeamRegistration?, right: TeamRegistration?) + + func replacementRange() -> TeamRange? { + guard let tournamentObject = tournamentObject() else { return nil } + guard let index = tournamentObject.indexOf(team: self) else { return nil } + let selectedSortedTeams = tournamentObject.selectedSortedTeams() + let left = selectedSortedTeams[safe: index - 1] + let right = selectedSortedTeams[safe: index + 1] + return (left: left, right: right) + } + + func replacementRangeExtended() -> TeamRange? { + guard let tournamentObject = tournamentObject() else { return nil } + guard let groupStagePosition else { return nil } + let selectedSortedTeams = tournamentObject.selectedSortedTeams() + var left: TeamRegistration? = nil + if groupStagePosition == 0 { + left = tournamentObject.seeds().last + } else { + let previousHat = selectedSortedTeams.filter({ $0.groupStagePosition == groupStagePosition - 1 }).sorted(by: \.weight) + left = previousHat.last + } + var right: TeamRegistration? = nil + if groupStagePosition == tournamentObject.teamsPerGroupStage - 1 { + right = nil + } else { + let previousHat = selectedSortedTeams.filter({ $0.groupStagePosition == groupStagePosition + 1 }).sorted(by: \.weight) + right = previousHat.first + } + return (left: left, right: right) + } + typealias AreInIncreasingOrder = (PlayerRegistration, PlayerRegistration) -> Bool func players() -> [PlayerRegistration] { diff --git a/PadelClub/ViewModel/AgendaDestination.swift b/PadelClub/ViewModel/AgendaDestination.swift index 8f30534..ba19f71 100644 --- a/PadelClub/ViewModel/AgendaDestination.swift +++ b/PadelClub/ViewModel/AgendaDestination.swift @@ -6,6 +6,7 @@ // import Foundation +import SwiftUI enum AgendaDestination: CaseIterable, Identifiable, Selectable { var id: Self { self } @@ -56,6 +57,15 @@ enum AgendaDestination: CaseIterable, Identifiable, Selectable { } } + func badgeValueColor() -> Color? { + switch self { + case .history: + return .green + default: + return nil + } + } + func badgeImage() -> Badge? { switch self { case .activity: diff --git a/PadelClub/ViewModel/SearchViewModel.swift b/PadelClub/ViewModel/SearchViewModel.swift index e906c6d..dc5d794 100644 --- a/PadelClub/ViewModel/SearchViewModel.swift +++ b/PadelClub/ViewModel/SearchViewModel.swift @@ -15,6 +15,7 @@ class SearchViewModel: ObservableObject, Identifiable { var clubName: String? = nil var ligueName: String? = nil var showFemaleInMaleAssimilation: Bool = false + var hidePlayers: [String]? @Published var debouncableText: String = "" @Published var searchText: String = "" @@ -67,6 +68,10 @@ class SearchViewModel: ObservableObject, Identifiable { return message.joined(separator: "\n") } + func codeClubs() -> [String] { + DataStore.shared.clubs.compactMap { $0.code } + } + func getCodeClub() -> String? { if let codeClub { return codeClub } // if let userCodeClub = user?.player?.codeClub { return userCodeClub } @@ -97,7 +102,7 @@ class SearchViewModel: ObservableObject, Identifiable { return "dans cette ligue" case .club: return "dans ce club" - case .favorite: + case .favoriteClubs, .favoritePlayers: return "dans mes favoris" } } @@ -110,8 +115,10 @@ class SearchViewModel: ObservableObject, Identifiable { return (ligueName)?.capitalized ?? "Ma ligue" case .club: return (clubName)?.capitalized ?? "Mon club" - case .favorite: - return "Mes favoris" + case .favoriteClubs: + return "Clubs favoris" + case .favoritePlayers: + return "Joueurs favoris" } } @@ -135,10 +142,11 @@ class SearchViewModel: ObservableObject, Identifiable { func orPredicate() -> NSPredicate? { var predicates : [NSPredicate] = [] - let searchText = searchText.canonicalVersion + let canonicalVersionWithoutPunctuation = searchText.canonicalVersion + let canonicalVersionWithPunctuation = searchText.canonicalVersionWithPunctuation switch tokens.first { case .none: - if searchText.isEmpty == false { + if canonicalVersionWithoutPunctuation.isEmpty == false { let wordsPredicates = wordsPredicates() if let wordsPredicates { predicates.append(wordsPredicates) @@ -148,32 +156,32 @@ class SearchViewModel: ObservableObject, Identifiable { } } case .ligue: - if searchText.isEmpty { + if canonicalVersionWithoutPunctuation.isEmpty { predicates.append(NSPredicate(format: "ligueName == nil")) } else { - predicates.append(NSPredicate(format: "ligueName contains[cd] %@", searchText)) + predicates.append(NSPredicate(format: "ligueName contains[cd] %@", canonicalVersionWithoutPunctuation)) } case .club: - if searchText.isEmpty { + if canonicalVersionWithoutPunctuation.isEmpty { predicates.append(NSPredicate(format: "clubName == nil")) } else { - predicates.append(NSPredicate(format: "clubName contains[cd] %@", searchText)) + predicates.append(NSPredicate(format: "clubName contains[cd] %@", canonicalVersionWithoutPunctuation)) } case .rankMoreThan: - if searchText.isEmpty || Int(searchText) == 0 { + if canonicalVersionWithoutPunctuation.isEmpty || Int(canonicalVersionWithoutPunctuation) == 0 { predicates.append(NSPredicate(format: "rank == 0")) } else { - predicates.append(NSPredicate(format: "rank >= %@", searchText)) + predicates.append(NSPredicate(format: "rank >= %@", canonicalVersionWithoutPunctuation)) } case .rankLessThan: - if searchText.isEmpty || Int(searchText) == 0 { + if canonicalVersionWithoutPunctuation.isEmpty || Int(canonicalVersionWithoutPunctuation) == 0 { predicates.append(NSPredicate(format: "rank == 0")) } else { - predicates.append(NSPredicate(format: "rank <= %@", searchText)) + predicates.append(NSPredicate(format: "rank <= %@", canonicalVersionWithoutPunctuation)) } case .rankBetween: - let values = searchText.components(separatedBy: ",") - if searchText.isEmpty || values.count != 2 { + let values = canonicalVersionWithPunctuation.components(separatedBy: ",") + if canonicalVersionWithPunctuation.isEmpty || values.count != 2 { predicates.append(NSPredicate(format: "rank == 0")) } else { predicates.append(NSPredicate(format: "rank BETWEEN {%@,%@}", values.first!, values.last!)) @@ -220,10 +228,17 @@ class SearchViewModel: ObservableObject, Identifiable { } else { predicates.append(NSPredicate(format: "clubCode == nil")) } - case .favorite: + case .favoriteClubs: + predicates.append(NSPredicate(format: "clubCode IN %@", codeClubs())) + case .favoritePlayers: + //todo predicates.append(NSPredicate(format: "license == nil")) } + if hidePlayers?.isEmpty == false { + predicates.append(NSPredicate(format: "NOT (license IN %@)", hidePlayers!)) + } + return NSCompoundPredicate(andPredicateWithSubpredicates: predicates.compactMap({ $0 })) } @@ -341,7 +356,8 @@ enum DataSet: Int, CaseIterable, Identifiable { case national case ligue case club - case favorite + case favoriteClubs + case favoritePlayers var id: Int { rawValue } func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { @@ -352,7 +368,7 @@ enum DataSet: Int, CaseIterable, Identifiable { return "Ligue" case .club: return "Club" - case .favorite: + case .favoritePlayers, .favoriteClubs: return "Favori" } } @@ -365,7 +381,7 @@ enum DataSet: Int, CaseIterable, Identifiable { return [.club, .rankMoreThan, .rankLessThan, .rankBetween] case .club: return [.rankMoreThan, .rankLessThan, .rankBetween] - case .favorite: + case .favoritePlayers, .favoriteClubs: return [.rankMoreThan, .rankLessThan, .rankBetween] } } diff --git a/PadelClub/ViewModel/Selectable.swift b/PadelClub/ViewModel/Selectable.swift index 7158823..201a4b9 100644 --- a/PadelClub/ViewModel/Selectable.swift +++ b/PadelClub/ViewModel/Selectable.swift @@ -12,6 +12,7 @@ protocol Selectable { func selectionLabel() -> String func badgeValue() -> Int? func badgeImage() -> Badge? + func badgeValueColor() -> Color? } enum Badge { diff --git a/PadelClub/Views/Components/GenericDestinationPickerView.swift b/PadelClub/Views/Components/GenericDestinationPickerView.swift index 1db550d..80cc909 100644 --- a/PadelClub/Views/Components/GenericDestinationPickerView.swift +++ b/PadelClub/Views/Components/GenericDestinationPickerView.swift @@ -56,7 +56,7 @@ struct GenericDestinationPickerView: View { .offset(x: 3, y: 3) } else if let count = destination.badgeValue(), count > 0 { Image(systemName: count <= 50 ? "\(count).circle.fill" : "plus.circle.fill") - .foregroundColor(.red) + .foregroundColor(destination.badgeValueColor() ?? .red) .imageScale(.medium) .background ( Color(.systemBackground) diff --git a/PadelClub/Views/GroupStage/GroupStageTeamView.swift b/PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift similarity index 83% rename from PadelClub/Views/GroupStage/GroupStageTeamView.swift rename to PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift index 7653ef3..afd06f7 100644 --- a/PadelClub/Views/GroupStage/GroupStageTeamView.swift +++ b/PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift @@ -22,17 +22,22 @@ struct GroupStageTeamView: View { } if groupStage.tournamentObject()?.hasEnded() == false { + Section { + NavigationLink { + GroupStageTeamReplacementView(team: team) + } label: { + Text("Chercher à remplacer") + } + } + Section { if team.qualified == false { - RowButtonView("Qualifier l'équipe") { + RowButtonView("Qualifier l'équipe", role: .destructive) { team.qualified = true -// team.bracketPosition = nil + //team.bracketPosition = nil _save() } - } - } - Section { - if team.qualified { + } else { RowButtonView("Annuler la qualification", role: .destructive) { team.qualified = false team.bracketPosition = nil @@ -41,12 +46,6 @@ struct GroupStageTeamView: View { } } - Section { - RowButtonView("Remplacement") { - - } - } - Section { RowButtonView("Retirer de la poule", role: .destructive) { team.groupStagePosition = nil diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index 340ad0e..4c25ba7 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -42,6 +42,10 @@ struct GroupStagesView: View { } } + func badgeValueColor() -> Color? { + return nil + } + func badgeImage() -> Badge? { nil } diff --git a/PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift b/PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift new file mode 100644 index 0000000..678e308 --- /dev/null +++ b/PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift @@ -0,0 +1,140 @@ +// +// GroupStageTeamReplacementView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 02/05/2024. +// + +import SwiftUI + +struct GroupStageTeamReplacementView: View { + var team: TeamRegistration + let teamRange: TeamRegistration.TeamRange? + let teamRangeExtended: TeamRegistration.TeamRange? + @State private var selectedPlayer: PlayerRegistration? + + init(team: TeamRegistration) { + self.team = team + self.teamRange = team.replacementRange() + self.teamRangeExtended = team.replacementRangeExtended() + } + + private func _getWeight() -> Int { + guard let selectedPlayer else { return 0 } + return team.weight - selectedPlayer.weight + } + + private func _searchableRange(_ teamRange: TeamRegistration.TeamRange) -> String { + let left = teamRange.left?.weight + let right = teamRange.right?.weight + let sides = [left, right].compactMap({ $0 }).map { $0 - _getWeight() } + return sides.map({ String($0) }).joined(separator: ",") + } + + private func _searchToken(_ teamRange: TeamRegistration.TeamRange) -> SearchToken { + if teamRange.left == nil { return .rankLessThan } + if teamRange.right == nil { return .rankMoreThan } + return .rankBetween + } + + var body: some View { + List { + Section { + Picker(selection: $selectedPlayer) { + HStack { + Text("Toute l'équipe") + Spacer() + Text(team.weight.formatted()).bold() + } + .tag(nil as PlayerRegistration?) + ForEach(team.players()) { player in + HStack { + Text(player.playerLabel()) + Spacer() + Text(player.rankLabel()).bold() + } + .tag(player as PlayerRegistration?) + } + } label: { + Text("Remplacement de l'équipe ou d'un joueur particulier") + } + .labelsHidden() + .pickerStyle(.inline) + } footer: { + Text("Remplacement de l'équipe ou d'un joueur particulier") + } + + if let teamRange { + Section { + TeamRangeView(teamRange: teamRange, playerWeight: _getWeight()) + if selectedPlayer != nil { + _searchLinkView(teamRange) + } + } header: { + Text("Même position en poule") + } footer: { + Text("Intervalle de poids d'équipe pour que le remplacement n'affecte pas les poules") + } + } + if let teamRangeExtended { + Section { + TeamRangeView(teamRange: teamRangeExtended, playerWeight: _getWeight()) + if selectedPlayer != nil { + _searchLinkView(teamRangeExtended) + } + } header: { + Text("Même ligne en poule") + } footer: { + Text("Intervalle de poids d'équipe respecte le tirage au sort par chapeau") + } + } + } + .headerProminence(.increased) + .navigationTitle("Remplacement") + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + } + + private func _searchLinkView(_ teamRange: TeamRegistration.TeamRange) -> some View { + NavigationLink { + let tournament = team.tournamentObject() + SelectablePlayerListView(searchField: _searchableRange(teamRange), dataSet: .favoriteClubs, filterOption: tournament?.tournamentCategory.playerFilterOption ?? .all, sortOption: .rank, showFemaleInMaleAssimilation: true, tokens: [_searchToken(teamRange)], hidePlayers: tournament?.selectedPlayers().compactMap { $0.licenceId }) + } label: { + Text("Chercher dans vos clubs favoris") + } + } + + struct TeamRangeView: View { + let teamRange: TeamRegistration.TeamRange + let playerWeight: Int + + @ViewBuilder + var body: some View { + HStack { + TeamRangeSideView(team: teamRange.left, playerWeight: -playerWeight) + Spacer() + Image(systemName: "arrowshape.forward.fill") + Spacer() + TeamRangeSideView(team: teamRange.right, playerWeight: -playerWeight) + } + } + } + + struct TeamRangeSideView: View { + let team: TeamRegistration? + let playerWeight: Int + + @ViewBuilder + var body: some View { + if let team { + Text((team.weight + playerWeight).formatted()).font(.largeTitle) + } else { + Text("Aucune limite") + } + } + } +} + +#Preview { + GroupStageTeamReplacementView(team: TeamRegistration.mock()) +} diff --git a/PadelClub/Views/Match/Components/MatchDateView.swift b/PadelClub/Views/Match/Components/MatchDateView.swift index 4f00cb7..65727c7 100644 --- a/PadelClub/Views/Match/Components/MatchDateView.swift +++ b/PadelClub/Views/Match/Components/MatchDateView.swift @@ -6,29 +6,41 @@ // import SwiftUI +import LeStorage struct MatchDateView: View { @EnvironmentObject var dataStore: DataStore var match: Match var showPrefix: Bool = false + private var isReady: Bool + private var hasWalkoutTeam: Bool + private var hasEnded: Bool + + init(match: Match, showPrefix: Bool) { + self.match = match + self.showPrefix = showPrefix + self.isReady = match.isReady() + self.hasWalkoutTeam = match.hasWalkoutTeam() + self.hasEnded = match.hasEnded() + } var body: some View { Menu { - if match.startDate == nil && match.isReady() { + if match.startDate == nil && isReady { Button("Démarrer") { match.startDate = Date() - save() + _save() } Button("Échauffement") { match.startDate = Calendar.current.date(byAdding: .minute, value: 5, to: Date()) - save() + _save() } } else { - if match.isReady() { + if isReady { Button("Démarrer maintenant") { match.startDate = Date() match.endDate = nil - save() + _save() } } else { let tournament = match.currentTournament() @@ -36,13 +48,13 @@ struct MatchDateView: View { Button("Décaler de \(estimatedDuration) minutes") { match.startDate = match.startDate?.addingTimeInterval(Double(estimatedDuration) * 60.0) match.endDate = nil - save() + _save() } } Button("Retirer l'horaire") { match.startDate = nil match.endDate = nil - save() + _save() } } } label: { @@ -55,7 +67,7 @@ struct MatchDateView: View { var label: some View { HStack { VStack(alignment: .trailing) { - if match.hasWalkoutTeam() == false { + if hasWalkoutTeam == false { if let startDate = match.startDate, match.endDate == nil { if startDate.timeIntervalSinceNow < 0 { if showPrefix { @@ -110,7 +122,7 @@ struct MatchDateView: View { .underline() } - if match.startDate == nil && match.hasEnded() == false { + if match.startDate == nil && hasEnded == false { Text("démarrage").font(.footnote).foregroundStyle(.secondary) Text("non défini") .foregroundStyle(Color.master) @@ -121,17 +133,12 @@ struct MatchDateView: View { } } - func save() { + private func _save() { do { try dataStore.matches.addOrUpdate(instance: match) } catch { - // Replace this implementation with code to handle the error appropriately. - // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - let nsError = error as NSError - fatalError("Unresolved error \(nsError), \(nsError.userInfo)") + Logger.error(error) } } - - } diff --git a/PadelClub/Views/Match/Components/PlayerBlockView.swift b/PadelClub/Views/Match/Components/PlayerBlockView.swift index 070928e..330797d 100644 --- a/PadelClub/Views/Match/Components/PlayerBlockView.swift +++ b/PadelClub/Views/Match/Components/PlayerBlockView.swift @@ -13,13 +13,16 @@ struct PlayerBlockView: View { let team: TeamRegistration? let color: Color let width: CGFloat + let teamScore: TeamScore? init(match: Match, teamPosition: TeamPosition, color: Color, width: CGFloat) { self.match = match self.teamPosition = teamPosition - self.team = match.team(teamPosition) + let theTeam = match.team(teamPosition) + self.team = theTeam self.color = color self.width = width + self.teamScore = match.teamScore(ofTeam: theTeam) } var names: [String]? { @@ -39,18 +42,18 @@ struct PlayerBlockView: View { } var scores: [String] { - match.teamScore(ofTeam: team)?.score?.components(separatedBy: ",") ?? [] + teamScore?.score?.components(separatedBy: ",") ?? [] } private func _defaultLabel() -> String { - return teamPosition.localizedLabel() + teamPosition.localizedLabel() } var body: some View { HStack { VStack(alignment: .leading) { if let names { - if let teamScore = match.teamScore(ofTeam: team), teamScore.luckyLoser != nil { + if let teamScore, teamScore.luckyLoser != nil { Text("Repêchée").italic().font(.caption) } diff --git a/PadelClub/Views/Match/MatchSummaryView.swift b/PadelClub/Views/Match/MatchSummaryView.swift index c336c7e..83aa4a9 100644 --- a/PadelClub/Views/Match/MatchSummaryView.swift +++ b/PadelClub/Views/Match/MatchSummaryView.swift @@ -10,25 +10,39 @@ import SwiftUI struct MatchSummaryView: View { var match: Match let matchViewStyle: MatchViewStyle + let matchTitle: String + let roundTitle: String? + let courtName: String? + let spacing: CGFloat + let padding: CGFloat + let color: Color + let width: CGFloat - var entrantLabelOne: String { - "match.longLabelTeamOne" - } + init(match: Match, matchViewStyle: MatchViewStyle) { + self.match = match + self.matchViewStyle = matchViewStyle + self.padding = matchViewStyle == .plainStyle ? 0 : 8 + self.spacing = matchViewStyle == .plainStyle ? 8 : 0 + self.width = matchViewStyle == .plainStyle ? 1 : 2 + self.color = Color(white: 0.9) - var entrantLabelTwo: String { - "match.longLabelTeamTwo" - } - - var color: Color { - return Color(white: 0.9) - //matchViewStyle == .plainStyle ? Color(uiColor: .tertiaryLabel) : Color(uiColor: .secondaryLabel) + if let groupStage = match.groupStageObject { + self.roundTitle = groupStage.groupStageTitle() + } else if let round = match.roundObject { + self.roundTitle = round.roundTitle() + } else { + self.roundTitle = nil + } + + self.matchTitle = match.matchTitle() + + if let court = match.courtName(), match.hasEnded() == false { + self.courtName = court + } else { + self.courtName = nil + } } - var width: CGFloat { -// return 2.0 - matchViewStyle == .plainStyle ? 1 : 2 - } - var body: some View { matchSummaryView // .contextMenu { @@ -59,37 +73,33 @@ struct MatchSummaryView: View { VStack(alignment: .leading) { if matchViewStyle != .plainStyle { if matchViewStyle == .feedStyle, let tournament = match.currentTournament() { - tournamentHeaderView(tournament) + Text(tournament.tournamentTitle()) } HStack { - if match.isGroupStage() { - if let groupStage = match.groupStageObject { - Text(groupStage.groupStageTitle()) - } else if let round = match.roundObject { - Text(round.roundTitle()) - } - Text(match.matchTitle()) + if let roundTitle { + Text(roundTitle) } + Text(matchTitle) Spacer() - if let court = match.courtName(), match.hasEnded() == false { + if let courtName { Spacer() - Text(court) + Text(courtName) } } .lineLimit(1) } HStack(spacing: 0) { - VStack(alignment: .leading, spacing: matchViewStyle == .plainStyle ? 8 : 0) { + VStack(alignment: .leading, spacing: spacing) { PlayerBlockView(match: match, teamPosition: .one, color: color, width: width) - .padding(matchViewStyle == .plainStyle ? 0 : 8) + .padding(padding) if width == 1 { Divider() } else { Divider().frame(height: width).overlay(color) } PlayerBlockView(match: match, teamPosition: .two, color: color, width: width) - .padding(matchViewStyle == .plainStyle ? 0 : 8) + .padding(padding) } } .overlay { @@ -106,149 +116,9 @@ struct MatchSummaryView: View { } } } - .padding(.vertical, matchViewStyle != .plainStyle ? 8 : 0) + .padding(.vertical, padding) .monospacedDigit() } - - @ViewBuilder - func tournamentHeaderView(_ currentTournament: Tournament) -> some View { - return Text(currentTournament.tournamentTitle()) - VStack(alignment: .leading) { -// HStack { -// ZStack(alignment: .leading) { -// Text("Poule 9").font(.title3).bold().opacity(0) -// Text(currentTournament.tournamentLevel.localizedLabel) -// } -// if let tournamentTitle = currentTournament.title, tournamentTitle.isEmpty == false { -// Text(tournamentTitle) -// } -// -// Spacer() -// if let startDate = match.startDate { -// if let endDate = match.endDate { -// let duration = Duration( -// secondsComponent: Int64(endDate.timeIntervalSince(startDate)), -// attosecondsComponent: 0 -// ).formatted(.units(allowed: [.hours, .minutes], width: .narrow)) -// Text("durée") -// } else if startDate.timeIntervalSinceNow < 0 { -// Text("en cours") -// } else { -// Text("prévu à") -// } -// } else { -// if let endDate = match.endDate { -// Text("a fini à") -// } -// } -// } -// -// HStack { -// ZStack(alignment: .leading) { -// Text("Poule 9").opacity(0) -// Text(match.shortTitleLabel) -// } -// if let score = match.score, match.hasEnded() { -// Text(score.label) -// } else if match.fieldIndex > 0 { -// Text("terrain #\(match.fieldIndex)") -// } -// -// Spacer() -// if let startDate = match.startDate { -// if let endDate = match.endDate { -// let duration = Duration( -// secondsComponent: Int64(endDate.timeIntervalSince(startDate)), -// attosecondsComponent: 0 -// ).formatted(.units(allowed: [.hours, .minutes], width: .narrow)) -// Text(duration) -// } else if startDate.timeIntervalSinceNow < 0 { -// Text(startDate, style: .timer) -// } else { -// Text(startDate.formatted(date: .omitted, time: .shortened)) -// } -// } else { -// if let endDate = match.endDate { -// Text(endDate.formattedAsHourMinute()) -// } -// } -// } -// .font(.title3) -// -// HStack { -// ZStack(alignment: .leading) { -// Text("Poule 9").font(.title3).bold().opacity(0) -// VStack { -// Text(currentTournament.tournamentCategory.localizedLabel) -// } -// } -// -// if let winnerEntrant = match.winnerEntrant { -// Text(winnerEntrant.longLabelPlayerOne) -// } else if match.isBracketMatch { -// Text(match.subtitleLabel) -// } else if let entrantOne = match.entrantOne()?.team?.firstPairLastNames { -// Text(entrantOne) -// } -// -// Spacer() -// if let startDate = match.startDate { -// if let endDate = match.endDate { -// let duration = Duration( -// secondsComponent: Int64(endDate.timeIntervalSince(startDate)), -// attosecondsComponent: 0 -// ).formatted(.units(allowed: [.hours, .minutes], width: .narrow)) -// Text(startDate.formattedAsHourMinute()) -// } else if startDate.timeIntervalSinceNow < 0 { -// Text(startDate.formattedAsHourMinute()) -// } else { -// Text(startDate.formatted(.dateTime.day().month())) -// } -// } else { -// if let endDate = match.endDate { -// Text("début") -// } -// } -// } -// .font(.caption) -// -// HStack { -// ZStack(alignment: .leading) { -// Text("Poule 9").font(.title3).bold().opacity(0) -// VStack { -// Text(currentTournament.tournamentAgeLabel) -// } -// } -// -// if let winnerEntrant = match.winnerEntrant { -// Text(winnerEntrant.longLabelPlayerTwo) -// } else if match.isBracketMatch { -// } else if let entrant = match.entrantTwo()?.team?.firstPairLastNames { -// Text(entrant) -// } -// -// Spacer() -// if let startDate = match.startDate { -// if let endDate = match.endDate { -// let duration = Duration( -// secondsComponent: Int64(endDate.timeIntervalSince(startDate)), -// attosecondsComponent: 0 -// ).formatted(.units(allowed: [.hours, .minutes], width: .narrow)) -// Text(startDate.formatted(.dateTime.day().month().year())) -// } else if startDate.timeIntervalSinceNow < 0 { -// Text(startDate.formatted(.dateTime.day().month().year())) -// } else { -// Text(startDate.formatted(.dateTime.year())) -// } -// } else { -// if let endDate = match.endDate { -// Text("non défini") -// } -// } -// } -// .font(.caption) - } - } } #Preview { diff --git a/PadelClub/Views/Player/Components/EditablePlayerView.swift b/PadelClub/Views/Player/Components/EditablePlayerView.swift index 2238174..2765df4 100644 --- a/PadelClub/Views/Player/Components/EditablePlayerView.swift +++ b/PadelClub/Views/Player/Components/EditablePlayerView.swift @@ -35,12 +35,12 @@ struct EditablePlayerView: View { @ViewBuilder func computedPlayerView(_ player: PlayerRegistration) -> some View { - VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: 0.0) { ImportedPlayerView(player: player) - HStack { - Text(player.isImported() ? "importé" : "non importé") - Text(player.formattedLicense().isLicenseNumber ? "valide" : "non valide") - } +// HStack { +// Text(player.isImported() ? "importé via beach padel" : "") +// Text(player.formattedLicense().isLicenseNumber ? "licence valide" : "non valide") +// }.font(.footnote) HStack { Menu { if let number = player.phoneNumber?.replacingOccurrences(of: " ", with: ""), let url = URL(string: "tel:\(number)") { diff --git a/PadelClub/Views/Round/LoserRoundsView.swift b/PadelClub/Views/Round/LoserRoundsView.swift index 0921324..0039f81 100644 --- a/PadelClub/Views/Round/LoserRoundsView.swift +++ b/PadelClub/Views/Round/LoserRoundsView.swift @@ -43,6 +43,10 @@ extension LoserRound { return rounds.flatMap { $0.playedMatches() }.filter({ $0.isRunning() }).count } + func badgeValueColor() -> Color? { + return nil + } + func badgeImage() -> Badge? { return rounds.allSatisfy({ $0.hasEnded() }) ? .checkmark : nil } diff --git a/PadelClub/Views/Shared/SelectablePlayerListView.swift b/PadelClub/Views/Shared/SelectablePlayerListView.swift index 318d7b9..149057b 100644 --- a/PadelClub/Views/Shared/SelectablePlayerListView.swift +++ b/PadelClub/Views/Shared/SelectablePlayerListView.swift @@ -34,12 +34,14 @@ struct SelectablePlayerListView: View { return URL.importDateFormatter.date(from: lastDataSource) } - init(allowSelection: Int = 0, searchField: String? = nil, user: User? = nil, dataSet: DataSet = .national, filterOption: PlayerFilterOption = .all, hideAssimilation: Bool = false, ascending: Bool = true, sortOption: SortOption = .rank, fromPlayer: FederalPlayer? = nil, codeClub: String? = nil, ligue: String? = nil, showFemaleInMaleAssimilation: Bool = false, playerSelectionAction: PlayerSelectionAction? = nil, contentUnavailableAction: ContentUnavailableAction? = nil) { + init(allowSelection: Int = 0, searchField: String? = nil, user: User? = nil, dataSet: DataSet = .national, filterOption: PlayerFilterOption = .all, hideAssimilation: Bool = false, ascending: Bool = true, sortOption: SortOption = .rank, fromPlayer: FederalPlayer? = nil, codeClub: String? = nil, ligue: String? = nil, showFemaleInMaleAssimilation: Bool = false, tokens: [SearchToken] = [], hidePlayers: [String]? = nil, playerSelectionAction: PlayerSelectionAction? = nil, contentUnavailableAction: ContentUnavailableAction? = nil) { self.allowSelection = allowSelection -// self.searchText = searchField ?? "" self.playerSelectionAction = playerSelectionAction self.contentUnavailableAction = contentUnavailableAction let searchViewModel = SearchViewModel() + searchViewModel.tokens = tokens + searchViewModel.searchText = searchField ?? "" + searchViewModel.debouncableText = searchField ?? "" searchViewModel.showFemaleInMaleAssimilation = showFemaleInMaleAssimilation searchViewModel.searchText = searchField ?? "" searchViewModel.isPresented = allowSelection != 0 @@ -53,6 +55,7 @@ struct SelectablePlayerListView: View { searchViewModel.hideAssimilation = hideAssimilation searchViewModel.ascending = ascending searchViewModel.sortOption = sortOption + searchViewModel.hidePlayers = hidePlayers _searchViewModel = StateObject(wrappedValue: searchViewModel) } diff --git a/PadelClub/Views/Team/Components/TeamWeightView.swift b/PadelClub/Views/Team/Components/TeamWeightView.swift index 72e1696..deb2c3b 100644 --- a/PadelClub/Views/Team/Components/TeamWeightView.swift +++ b/PadelClub/Views/Team/Components/TeamWeightView.swift @@ -10,6 +10,13 @@ import SwiftUI struct TeamWeightView: View { var team: TeamRegistration var teamPosition: TeamPosition? = nil + var teamIndex: Int? + + init(team: TeamRegistration, teamPosition: TeamPosition? = nil) { + self.team = team + self.teamPosition = teamPosition + self.teamIndex = team.tournamentObject()?.indexOf(team: team) + } var body: some View { VStack(alignment: .trailing, spacing: 0) { @@ -18,8 +25,8 @@ struct TeamWeightView: View { .monospacedDigit() .font(.caption) } - if let teams = team.tournamentObject()?.selectedSortedTeams(), let index = team.index(in: teams) { - Text("#" + (index + 1).formatted(.number.precision(.integerLength(2...3)))) + if let teamIndex { + Text("#" + (teamIndex + 1).formatted(.number.precision(.integerLength(2...3)))) .monospacedDigit() } if teamPosition == .two { diff --git a/PadelClub/Views/Tournament/Screen/TournamentCallView.swift b/PadelClub/Views/Tournament/Screen/TournamentCallView.swift index 8a8dfa3..6b9d463 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentCallView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentCallView.swift @@ -40,6 +40,10 @@ enum CallDestination: Identifiable, Selectable { } } + func badgeValueColor() -> Color? { + return nil + } + func badgeImage() -> Badge? { switch self { case .seeds(let tournament): diff --git a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift index fffdb15..26276dc 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift @@ -50,6 +50,10 @@ enum CashierDestination: Identifiable, Selectable { } } + func badgeValueColor() -> Color? { + return nil + } + func badgeImage() -> Badge? { switch self { case .summary: diff --git a/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift b/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift index c6cfa8c..f16199d 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift @@ -42,6 +42,10 @@ enum ScheduleDestination: String, Identifiable, Selectable { nil } + func badgeValueColor() -> Color? { + return nil + } + func badgeImage() -> Badge? { nil } diff --git a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift index 3cd848e..f81916d 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift @@ -32,6 +32,10 @@ enum TournamentSettings: Identifiable, Selectable { nil } + func badgeValueColor() -> Color? { + return nil + } + func badgeImage() -> Badge? { switch self { case .club(let tournament):