You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
437 lines
15 KiB
437 lines
15 KiB
//
|
|
// SearchViewModel.swift
|
|
// Padel Tournament
|
|
//
|
|
// Created by Razmig Sarkissian on 07/02/2024.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
class DebouncableViewModel: ObservableObject {
|
|
@Published var debouncableText: String = ""
|
|
var debounceTrigger: Double = 0.15
|
|
}
|
|
|
|
class SearchViewModel: ObservableObject, Identifiable {
|
|
let id: UUID = UUID()
|
|
var allowSelection : Int = 0
|
|
var codeClub: String? = nil
|
|
var clubName: String? = nil
|
|
var ligueName: String? = nil
|
|
var showFemaleInMaleAssimilation: Bool = false
|
|
var hidePlayers: [String]?
|
|
|
|
@Published var debouncableText: String = ""
|
|
@Published var searchText: String = ""
|
|
@Published var task: DispatchWorkItem?
|
|
@Published var computedSearchText: String = ""
|
|
@Published var tokens = [SearchToken]()
|
|
@Published var suggestedTokens = [SearchToken]()
|
|
@Published var dataSet: DataSet = .national
|
|
@Published var filterOption = PlayerFilterOption.all
|
|
@Published var hideAssimilation: Bool = false
|
|
@Published var ascending: Bool = true
|
|
@Published var sortOption: SortOption = .rank
|
|
@Published var selectedPlayers: Set<ImportedPlayer> = Set()
|
|
@Published var filterSelectionEnabled: Bool = false
|
|
@Published var isPresented: Bool = false
|
|
|
|
var mostRecentDate: Date? = nil
|
|
|
|
var selectionIsOver: Bool {
|
|
if allowSingleSelection && selectedPlayers.count == 1 {
|
|
return true
|
|
} else if allowMultipleSelection && selectedPlayers.count == allowSelection {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
var allowMultipleSelection: Bool {
|
|
allowSelection > 1 || allowSelection == -1
|
|
}
|
|
|
|
var allowSingleSelection: Bool {
|
|
allowSelection == 1
|
|
}
|
|
|
|
var debounceTrigger: Double {
|
|
(dataSet == .national || dataSet == .ligue) ? 0.4 : 0.1
|
|
}
|
|
|
|
var throttleTrigger: Double {
|
|
(dataSet == .national || dataSet == .ligue) ? 0.15 : 0.1
|
|
}
|
|
|
|
var contentUnavailableMessage: String {
|
|
var message = ["Vérifiez l'ortographe ou lancez une nouvelle recherche."]
|
|
if tokens.isEmpty {
|
|
message.append("Il est possible que cette personne n'est joué aucun tournoi depuis les 12 derniers mois. Dans ce pas, Padel Club ne pourra pas le trouver.")
|
|
}
|
|
return message.joined(separator: "\n")
|
|
}
|
|
|
|
func codeClubs() -> [String] {
|
|
let clubs: [Club] = DataStore.shared.user.clubsObjects()
|
|
return clubs.compactMap { $0.code }
|
|
}
|
|
|
|
func getCodeClub() -> String? {
|
|
if let codeClub { return codeClub }
|
|
if let userCodeClub = DataStore.shared.user.currentPlayerData()?.clubCode { return userCodeClub }
|
|
return nil
|
|
}
|
|
|
|
func getLigueName() -> String? {
|
|
if let ligueName { return ligueName }
|
|
if let userLigueName = DataStore.shared.user.currentPlayerData()?.ligueName { return userLigueName }
|
|
return nil
|
|
}
|
|
|
|
func showIndex() -> Bool {
|
|
if (dataSet == .national || dataSet == .ligue) { return false }
|
|
if filterOption == .all { return false }
|
|
return true
|
|
}
|
|
|
|
func prompt(forDataSet: DataSet) -> String {
|
|
switch forDataSet {
|
|
case .national:
|
|
if let mostRecentDate {
|
|
return "base fédérale \(mostRecentDate.monthYearFormatted)"
|
|
} else {
|
|
return "rechercher"
|
|
}
|
|
case .ligue:
|
|
return "dans cette ligue"
|
|
case .club:
|
|
return "dans ce club"
|
|
case .favoriteClubs, .favoritePlayers:
|
|
return "dans mes favoris"
|
|
}
|
|
}
|
|
|
|
func label(forDataSet: DataSet) -> String {
|
|
switch forDataSet {
|
|
case .national:
|
|
return "National"
|
|
case .ligue:
|
|
return (ligueName)?.capitalized ?? "Ma ligue"
|
|
case .club:
|
|
return (clubName)?.capitalized ?? "Mon club"
|
|
case .favoriteClubs:
|
|
return "Clubs favoris"
|
|
case .favoritePlayers:
|
|
return "Joueurs favoris"
|
|
}
|
|
}
|
|
|
|
func words() -> [String] {
|
|
return searchText.canonicalVersionWithPunctuation.trimmed.components(separatedBy: .whitespaces)
|
|
}
|
|
|
|
func wordsPredicates() -> NSPredicate? {
|
|
let words = words().filter({ $0.isEmpty == false })
|
|
switch words.count {
|
|
case 2:
|
|
let predicates = [
|
|
NSPredicate(format: "canonicalLastName beginswith[cd] %@ AND canonicalFirstName beginswith[cd] %@", words[0], words[1]),
|
|
NSPredicate(format: "canonicalLastName beginswith[cd] %@ AND canonicalFirstName beginswith[cd] %@", words[1], words[0]),
|
|
]
|
|
return NSCompoundPredicate(orPredicateWithSubpredicates: predicates)
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func orPredicate() -> NSPredicate? {
|
|
var predicates : [NSPredicate] = []
|
|
let canonicalVersionWithoutPunctuation = searchText.canonicalVersion
|
|
let canonicalVersionWithPunctuation = searchText.canonicalVersionWithPunctuation
|
|
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))
|
|
}
|
|
predicates.append(NSPredicate(format: "canonicalFullName contains[cd] %@", canonicalVersionWithoutPunctuation))
|
|
let components = canonicalVersionWithoutPunctuation.split(separator: " ").sorted()
|
|
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!))
|
|
}
|
|
}
|
|
|
|
if predicates.isEmpty {
|
|
return nil
|
|
}
|
|
return NSCompoundPredicate(orPredicateWithSubpredicates: predicates)
|
|
}
|
|
|
|
func predicate() -> NSPredicate? {
|
|
var predicates : [NSPredicate?] = [
|
|
orPredicate(),
|
|
filterOption == .male ?
|
|
NSPredicate(format: "male == YES") :
|
|
nil,
|
|
filterOption == .female ?
|
|
NSPredicate(format: "male == NO") :
|
|
nil,
|
|
]
|
|
|
|
if let mostRecentDate {
|
|
//predicates.append(NSPredicate(format: "importDate == %@", mostRecentDate as CVarArg))
|
|
}
|
|
|
|
if hideAssimilation {
|
|
predicates.append(NSPredicate(format: "assimilation == %@", "Non"))
|
|
}
|
|
|
|
switch dataSet {
|
|
case .national:
|
|
break
|
|
case .ligue:
|
|
if let ligueName = getLigueName() {
|
|
predicates.append(NSPredicate(format: "ligueName == %@", ligueName))
|
|
} else {
|
|
predicates.append(NSPredicate(format: "ligueName == nil"))
|
|
}
|
|
case .club:
|
|
if let codeClub = getCodeClub() {
|
|
predicates.append(NSPredicate(format: "clubCode == %@", codeClub))
|
|
} else {
|
|
predicates.append(NSPredicate(format: "clubCode == nil"))
|
|
}
|
|
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 }))
|
|
}
|
|
|
|
|
|
func sortDescriptors() -> [SortDescriptor<ImportedPlayer>] {
|
|
sortOption.sortDescriptors(ascending, dataSet: dataSet)
|
|
}
|
|
|
|
func nsSortDescriptors() -> [NSSortDescriptor] {
|
|
sortDescriptors().map { NSSortDescriptor($0) }
|
|
}
|
|
}
|
|
|
|
enum SearchToken: String, CaseIterable, Identifiable {
|
|
case club = "club"
|
|
case ligue = "ligue"
|
|
case rankMoreThan = "rang >"
|
|
case rankLessThan = "rang <"
|
|
case rankBetween = "rang <>"
|
|
|
|
var id: String {
|
|
rawValue
|
|
}
|
|
|
|
var message: String {
|
|
switch self {
|
|
case .club:
|
|
return "Taper le nom d'un club pour y voir tous les joueurs ayant déjà joué un tournoi dans les 12 derniers mois."
|
|
case .ligue:
|
|
return "Taper le nom d'une ligue pour y voir tous les joueurs ayant déjà joué un tournoi dans les 12 derniers mois."
|
|
case .rankMoreThan:
|
|
return "Taper un nombre pour chercher les joueurs ayant un classement supérieur ou égale."
|
|
case .rankLessThan:
|
|
return "Taper un nombre pour chercher les joueurs ayant un classement inférieur ou égale."
|
|
case .rankBetween:
|
|
return "Taper deux nombres séparés par une virgule pour chercher les joueurs dans cette intervalle de classement"
|
|
}
|
|
}
|
|
|
|
var titleLabel: String {
|
|
switch self {
|
|
case .club:
|
|
return "Chercher un club"
|
|
case .ligue:
|
|
return "Chercher une ligue"
|
|
case .rankMoreThan, .rankLessThan:
|
|
return "Chercher un rang"
|
|
case .rankBetween:
|
|
return "Chercher une intervalle de classement"
|
|
}
|
|
}
|
|
|
|
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
|
|
switch self {
|
|
case .club:
|
|
return "Club"
|
|
case .ligue:
|
|
return "Ligue"
|
|
case .rankMoreThan:
|
|
return "Rang supérieur ou égale à"
|
|
case .rankLessThan:
|
|
return "Rang inférieur ou égale à"
|
|
case .rankBetween:
|
|
return "Rang entre deux valeurs"
|
|
}
|
|
}
|
|
|
|
var shortLocalizedLabel: String {
|
|
switch self {
|
|
case .club:
|
|
return "Club"
|
|
case .ligue:
|
|
return "Ligue"
|
|
case .rankMoreThan:
|
|
return "Rang ≥"
|
|
case .rankLessThan:
|
|
return "Rang ≤"
|
|
case .rankBetween:
|
|
return "Rang ≥,≤"
|
|
}
|
|
}
|
|
|
|
func icon() -> String {
|
|
switch self {
|
|
case .club:
|
|
return "house.and.flag.fill"
|
|
case .ligue:
|
|
return "house.and.flag.fill"
|
|
case .rankMoreThan:
|
|
return "figure.racquetball"
|
|
case .rankLessThan:
|
|
return "figure.racquetball"
|
|
case .rankBetween:
|
|
return "figure.racquetball"
|
|
}
|
|
}
|
|
|
|
var systemImage: String {
|
|
switch self {
|
|
case .club:
|
|
return "house.and.flag.fill"
|
|
case .ligue:
|
|
return "house.and.flag.fill"
|
|
case .rankMoreThan:
|
|
return "figure.racquetball"
|
|
case .rankLessThan:
|
|
return "figure.racquetball"
|
|
case .rankBetween:
|
|
return "figure.racquetball"
|
|
}
|
|
}
|
|
}
|
|
|
|
enum DataSet: Int, Identifiable {
|
|
case national
|
|
case ligue
|
|
case club
|
|
case favoriteClubs
|
|
case favoritePlayers
|
|
|
|
static let allCases : [DataSet] = [.national, .ligue, .club, .favoriteClubs]
|
|
|
|
var id: Int { rawValue }
|
|
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
|
|
switch self {
|
|
case .national:
|
|
return "National"
|
|
case .ligue:
|
|
return "Ligue"
|
|
case .club:
|
|
return "Club"
|
|
case .favoritePlayers, .favoriteClubs:
|
|
return "Favori"
|
|
}
|
|
}
|
|
|
|
var tokens: [SearchToken] {
|
|
switch self {
|
|
case .national:
|
|
return [.club, .ligue, .rankMoreThan, .rankLessThan, .rankBetween]
|
|
case .ligue:
|
|
return [.club, .rankMoreThan, .rankLessThan, .rankBetween]
|
|
case .club:
|
|
return [.rankMoreThan, .rankLessThan, .rankBetween]
|
|
case .favoritePlayers, .favoriteClubs:
|
|
return [.rankMoreThan, .rankLessThan, .rankBetween]
|
|
}
|
|
}
|
|
}
|
|
|
|
enum SortOption: Int, CaseIterable, Identifiable {
|
|
case name
|
|
case rank
|
|
case tournamentCount
|
|
case points
|
|
|
|
var id: Int { self.rawValue }
|
|
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
|
|
switch self {
|
|
case .name:
|
|
return "Nom"
|
|
case .rank:
|
|
return "Rang"
|
|
case .tournamentCount:
|
|
return "Tournoi"
|
|
case .points:
|
|
return "Points"
|
|
}
|
|
}
|
|
|
|
func sortDescriptors(_ ascending: Bool, dataSet: DataSet) -> [SortDescriptor<ImportedPlayer>] {
|
|
switch self {
|
|
case .name:
|
|
return [SortDescriptor(\ImportedPlayer.lastName, order: ascending ? .forward : .reverse), SortDescriptor(\ImportedPlayer.rank), SortDescriptor(\ImportedPlayer.assimilation)]
|
|
case .rank:
|
|
if (dataSet == .national || dataSet == .ligue) {
|
|
return [SortDescriptor(\ImportedPlayer.rank, order: ascending ? .forward : .reverse)]
|
|
} else {
|
|
return [SortDescriptor(\ImportedPlayer.rank, order: ascending ? .forward : .reverse), SortDescriptor(\ImportedPlayer.assimilation), SortDescriptor(\ImportedPlayer.lastName)]
|
|
}
|
|
case .tournamentCount:
|
|
return [SortDescriptor(\ImportedPlayer.tournamentCount, order: ascending ? .forward : .reverse), SortDescriptor(\ImportedPlayer.rank), SortDescriptor(\ImportedPlayer.assimilation), SortDescriptor(\ImportedPlayer.lastName)]
|
|
case .points:
|
|
return [SortDescriptor(\ImportedPlayer.points, order: ascending ? .forward : .reverse), SortDescriptor(\ImportedPlayer.rank), SortDescriptor(\ImportedPlayer.assimilation), SortDescriptor(\ImportedPlayer.lastName)]
|
|
}
|
|
}
|
|
}
|
|
|