From 4e5dc3ea12846f96170595d117e44458ebf4c3bc Mon Sep 17 00:00:00 2001 From: Raz Date: Wed, 12 Feb 2025 14:20:59 +0100 Subject: [PATCH] fix search stuff --- PadelClub/ViewModel/SearchViewModel.swift | 146 +++++++++++------- .../Navigation/Agenda/EventListView.swift | 8 +- PadelClub/Views/Round/RoundView.swift | 2 +- .../Shared/SelectablePlayerListView.swift | 48 +++++- .../Screen/TournamentRankView.swift | 2 +- .../Tournament/TournamentBuildView.swift | 2 +- .../Views/Tournament/TournamentView.swift | 2 +- 7 files changed, 143 insertions(+), 67 deletions(-) diff --git a/PadelClub/ViewModel/SearchViewModel.swift b/PadelClub/ViewModel/SearchViewModel.swift index cc16ead..5167d05 100644 --- a/PadelClub/ViewModel/SearchViewModel.swift +++ b/PadelClub/ViewModel/SearchViewModel.swift @@ -89,8 +89,31 @@ class SearchViewModel: ObservableObject, Identifiable { return nil } + func shouldIncludeSearchTextPredicate() -> Bool { + if allowMultipleSelection { + return true + } + + if allowSingleSelection { + return true + } + + if tokens.isEmpty == false || hideAssimilation || selectedAgeCategory != .unlisted { + return true + } + + return dataSet == .national && searchText.isEmpty == false && (tokens.isEmpty == true && hideAssimilation == false && selectedAgeCategory == .unlisted) + } + func showIndex() -> Bool { - if (dataSet == .national || dataSet == .ligue) { return isFiltering() } + if dataSet == .national { + if searchText.isEmpty == false && (tokens.isEmpty == true && hideAssimilation == false && selectedAgeCategory == .unlisted) { + return false + } else { + return isFiltering() + } + } + if (dataSet == .ligue) { return isFiltering() } if filterOption == .all { return isFiltering() } return true } @@ -149,66 +172,85 @@ class SearchViewModel: ObservableObject, Identifiable { } } + func searchTextPredicate() -> NSPredicate? { + var predicates : [NSPredicate] = [] + let allowedCharacterSet = CharacterSet.alphanumerics.union(.whitespaces) + let canonicalVersionWithoutPunctuation = searchText.canonicalVersion.components(separatedBy: allowedCharacterSet.inverted).joined().trimmed + + if canonicalVersionWithoutPunctuation.isEmpty == false { + let wordsPredicates = wordsPredicates() + if let wordsPredicates { + predicates.append(wordsPredicates) + } else { + predicates.append(NSPredicate(format: "license contains[cd] %@", canonicalVersionWithoutPunctuation)) + } + predicates.append(NSPredicate(format: "canonicalFullName contains[cd] %@", canonicalVersionWithoutPunctuation)) + let components = canonicalVersionWithoutPunctuation.split(separator: " ") + let pattern = components.joined(separator: ".*") + let predicate = NSPredicate(format: "canonicalFullName MATCHES[c] %@", pattern) + predicates.append(predicate) + } + if predicates.isEmpty { + return nil + } + return NSCompoundPredicate(orPredicateWithSubpredicates: predicates) + } + func orPredicate() -> NSPredicate? { var predicates : [NSPredicate] = [] let allowedCharacterSet = CharacterSet.alphanumerics.union(.whitespaces) let canonicalVersionWithoutPunctuation = searchText.canonicalVersion.components(separatedBy: allowedCharacterSet.inverted).joined().trimmed let canonicalVersionWithPunctuation = searchText.canonicalVersionWithPunctuation.trimmed - switch tokens.first { - case .none: - if canonicalVersionWithoutPunctuation.isEmpty == false { - let wordsPredicates = wordsPredicates() - if let wordsPredicates { - predicates.append(wordsPredicates) - } else { - predicates.append(NSPredicate(format: "license contains[cd] %@", canonicalVersionWithoutPunctuation)) + + if tokens.isEmpty { + if shouldIncludeSearchTextPredicate(), canonicalVersionWithoutPunctuation.isEmpty == false { + if let searchTextPredicate = searchTextPredicate() { + predicates.append(searchTextPredicate) } - predicates.append(NSPredicate(format: "canonicalFullName contains[cd] %@", canonicalVersionWithoutPunctuation)) - let components = canonicalVersionWithoutPunctuation.split(separator: " ") - let pattern = components.joined(separator: ".*") - let predicate = NSPredicate(format: "canonicalFullName MATCHES[c] %@", pattern) - predicates.append(predicate) } - case .ligue: - if canonicalVersionWithoutPunctuation.isEmpty { - predicates.append(NSPredicate(format: "ligueName == nil")) - } else { - predicates.append(NSPredicate(format: "ligueName contains[cd] %@", canonicalVersionWithoutPunctuation)) - } - case .club: - if canonicalVersionWithoutPunctuation.isEmpty { - predicates.append(NSPredicate(format: "clubName == nil")) - } else { - predicates.append(NSPredicate(format: "clubName contains[cd] %@", canonicalVersionWithoutPunctuation)) - } - case .rankMoreThan: - if canonicalVersionWithoutPunctuation.isEmpty || Int(canonicalVersionWithoutPunctuation) == 0 { - predicates.append(NSPredicate(format: "rank == 0")) - } else { - predicates.append(NSPredicate(format: "rank >= %@", canonicalVersionWithoutPunctuation)) - } - case .rankLessThan: - if canonicalVersionWithoutPunctuation.isEmpty || Int(canonicalVersionWithoutPunctuation) == 0 { - predicates.append(NSPredicate(format: "rank == 0")) - } else { - predicates.append(NSPredicate(format: "rank <= %@", canonicalVersionWithoutPunctuation)) - } - case .rankBetween: - 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!)) - } - case .age: - if canonicalVersionWithoutPunctuation.isEmpty || Int(canonicalVersionWithoutPunctuation) == 0 { - predicates.append(NSPredicate(format: "birthYear == 0")) - } else if let birthYear = Int(canonicalVersionWithoutPunctuation) { - predicates.append(NSPredicate(format: "birthYear == %@", birthYear.formattedAsRawString())) + } + for token in tokens { + switch token { + case .ligue: + if canonicalVersionWithoutPunctuation.isEmpty { + predicates.append(NSPredicate(format: "ligueName == nil")) + } else { + predicates.append(NSPredicate(format: "ligueName contains[cd] %@", canonicalVersionWithoutPunctuation)) + } + case .club: + if canonicalVersionWithoutPunctuation.isEmpty { + predicates.append(NSPredicate(format: "clubName == nil")) + } else { + predicates.append(NSPredicate(format: "clubName contains[cd] %@", canonicalVersionWithoutPunctuation)) + } + case .rankMoreThan: + if canonicalVersionWithoutPunctuation.isEmpty || Int(canonicalVersionWithoutPunctuation) == 0 { + predicates.append(NSPredicate(format: "rank == 0")) + } else { + predicates.append(NSPredicate(format: "rank >= %@", canonicalVersionWithoutPunctuation)) + } + case .rankLessThan: + if canonicalVersionWithoutPunctuation.isEmpty || Int(canonicalVersionWithoutPunctuation) == 0 { + predicates.append(NSPredicate(format: "rank == 0")) + } else { + predicates.append(NSPredicate(format: "rank <= %@", canonicalVersionWithoutPunctuation)) + } + case .rankBetween: + 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!)) + } + case .age: + if canonicalVersionWithoutPunctuation.isEmpty || Int(canonicalVersionWithoutPunctuation) == 0 { + predicates.append(NSPredicate(format: "birthYear == 0")) + } else if let birthYear = Int(canonicalVersionWithoutPunctuation) { + predicates.append(NSPredicate(format: "birthYear == %@", birthYear.formattedAsRawString())) + } + } - } - if predicates.isEmpty { return nil } diff --git a/PadelClub/Views/Navigation/Agenda/EventListView.swift b/PadelClub/Views/Navigation/Agenda/EventListView.swift index f9a7e2d..833f228 100644 --- a/PadelClub/Views/Navigation/Agenda/EventListView.swift +++ b/PadelClub/Views/Navigation/Agenda/EventListView.swift @@ -102,7 +102,7 @@ struct EventListView: View { Logger.error(error) } } label: { - Text("Les afficher tous sur Padel Club") + Text("Afficher ces tournois sur Padel Club") } Button { pcTournaments.forEach { tournament in @@ -114,11 +114,13 @@ struct EventListView: View { Logger.error(error) } } label: { - Text("Les masquer tous sur Padel Club") + Text("Masquer ces tournois sur Padel Club") } } label: { - Text("Options") + Text("Gérer la visibilité sur Padel Club") + .font(.caption) + .underline() } } diff --git a/PadelClub/Views/Round/RoundView.swift b/PadelClub/Views/Round/RoundView.swift index 23bf3d5..e5e0bd2 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -262,7 +262,7 @@ struct RoundView: View { .foregroundStyle(.green) } } label: { - Text("Classement final des équipes") + Text("Classement final") if tournament.publishRankings == false { Text("Vérifiez le classement avant de publier").foregroundStyle(.logoRed) } diff --git a/PadelClub/Views/Shared/SelectablePlayerListView.swift b/PadelClub/Views/Shared/SelectablePlayerListView.swift index 36feb3a..4210ef9 100644 --- a/PadelClub/Views/Shared/SelectablePlayerListView.swift +++ b/PadelClub/Views/Shared/SelectablePlayerListView.swift @@ -337,6 +337,21 @@ struct MySearchView: View { _searchViewModel = ObservedObject(wrappedValue: searchViewModel) _players = FetchRequest(sortDescriptors: searchViewModel.sortDescriptors(), predicate: searchViewModel.predicate()) } + + func searchedPlayers() -> [ImportedPlayer] { + if searchViewModel.searchText.isEmpty { + return Array(players) + } + + if let searchPredicate = searchViewModel.searchTextPredicate() { + let filteredPlayers = players.filter { player in + searchPredicate.evaluate(with: player) + } + return filteredPlayers + } + + return Array(players) + } var body: some View { playersView @@ -371,8 +386,6 @@ struct MySearchView: View { @ViewBuilder var playersView: some View { - let showProgression = true - let showFemaleInMaleAssimilation = searchViewModel.showFemaleInMaleAssimilation if searchViewModel.allowMultipleSelection { List(selection: $searchViewModel.selectedPlayers) { if searchViewModel.filterSelectionEnabled { @@ -423,7 +436,7 @@ struct MySearchView: View { } } .id(UUID()) - } else { + } else if searchViewModel.shouldIncludeSearchTextPredicate() { Section { ForEach(players.indices, id: \.self) { index in let player = players[index] @@ -435,26 +448,45 @@ struct MySearchView: View { } } .id(UUID()) + } else { + let filteredPlayers = searchedPlayers() + + Section { + ForEach(filteredPlayers.indices, id: \.self) { index in + let player = filteredPlayers[index] + let realIndex = searchViewModel.showIndex() ? players.firstIndex(of: player) : nil + let computedIndex = realIndex != nil ? realIndex! + 1 : nil + ImportedPlayerView(player: player, index: computedIndex, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation, showProgression: true) + } + } header: { + if filteredPlayers.isEmpty == false { + headerView() + } + } + .id(UUID()) } } else { + let filteredPlayers = searchedPlayers() Section { - ForEach(players.indices, id: \.self) { index in - let player = players[index] + ForEach(filteredPlayers.indices, id: \.self) { index in + let player = filteredPlayers[index] + let realIndex = searchViewModel.showIndex() ? players.firstIndex(of: player) : nil + let computedIndex = realIndex != nil ? realIndex! + 1 : nil if searchViewModel.allowSingleSelection { Button { searchViewModel.selectedPlayers.insert(player) } label: { - ImportedPlayerView(player: player, index: searchViewModel.showIndex() ? (index + 1) : nil, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation, showProgression: true) + ImportedPlayerView(player: player, index: computedIndex, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation, showProgression: true) .contentShape(Rectangle()) } .frame(maxWidth: .infinity) .buttonStyle(.plain) } else { - ImportedPlayerView(player: player, index: searchViewModel.showIndex() ? (index + 1) : nil, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation, showProgression: true) + ImportedPlayerView(player: player, index: computedIndex, showFemaleInMaleAssimilation: searchViewModel.showFemaleInMaleAssimilation, showProgression: true) } } } header: { - if players.isEmpty == false { + if filteredPlayers.isEmpty == false { headerView() } } diff --git a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift index 2a8e137..b3214ca 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentRankView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentRankView.swift @@ -71,7 +71,7 @@ struct TournamentRankView: View { } footer: { if let url = tournament.shareURL(.rankings) { Link(destination: url) { - Text("Voir la page des classements sur Padel Club") + Text("Voir les classements sur Padel Club") } } } diff --git a/PadelClub/Views/Tournament/TournamentBuildView.swift b/PadelClub/Views/Tournament/TournamentBuildView.swift index b76002c..d44e5aa 100644 --- a/PadelClub/Views/Tournament/TournamentBuildView.swift +++ b/PadelClub/Views/Tournament/TournamentBuildView.swift @@ -124,7 +124,7 @@ struct TournamentBuildView: View { .foregroundStyle(.green) } } label: { - Text("Classement final des équipes") + Text("Classement final") if tournament.publishRankings == false { Text("Vérifiez le classement avant de publier").foregroundStyle(.logoRed) } diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index f2a3ec4..92fea53 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -247,7 +247,7 @@ struct TournamentView: View { .foregroundStyle(.green) } } label: { - Text("Classement final des équipes") + Text("Classement final") if tournament.publishRankings == false { Text("Vérifiez le classement avant de publier").foregroundStyle(.logoRed) }