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.
689 lines
27 KiB
689 lines
27 KiB
//
|
|
// AddTeamView.swift
|
|
// PadelClub
|
|
//
|
|
// Created by Razmig Sarkissian on 15/07/2024.
|
|
//
|
|
|
|
import SwiftUI
|
|
import LeStorage
|
|
import CoreData
|
|
|
|
struct AddTeamView: View {
|
|
|
|
@Environment(\.dismiss) var dismiss
|
|
|
|
private var fetchRequest: FetchRequest<ImportedPlayer>
|
|
private var fetchPlayers: FetchedResults<ImportedPlayer> { fetchRequest.wrappedValue }
|
|
|
|
var tournament: Tournament
|
|
var cancelShouldDismiss: Bool = false
|
|
enum FocusField: Hashable {
|
|
case pasteField
|
|
}
|
|
|
|
@FocusState private var focusedField: FocusField?
|
|
@State private var searchField: String = ""
|
|
@State private var presentSearch: Bool = false
|
|
@State private var presentPlayerSearch: Bool = false
|
|
@State private var presentPlayerCreation: Bool = false
|
|
@State private var presentImportView: Bool = false
|
|
@State private var createdPlayers: Set<PlayerRegistration> = Set()
|
|
@State private var createdPlayerIds: Set<String> = Set()
|
|
@State private var editedTeam: TeamRegistration?
|
|
@State private var pasteString: String?
|
|
@State private var selectionSearchField: String?
|
|
@State private var autoSelect: Bool = false
|
|
@State private var presentationCount: Int = 0
|
|
@State private var confirmDuplicate: Bool = false
|
|
@State private var homonyms: [PlayerRegistration] = []
|
|
@State private var confirmHomonym: Bool = false
|
|
@State private var editableTextField: String = ""
|
|
@State private var textHeight: CGFloat = 100 // Default height
|
|
@State private var hitsForSearch: [Int: Int] = [:]
|
|
@State private var searchForHit: Int = 0
|
|
@State private var displayWarningNotEnoughCharacter: Bool = false
|
|
@State private var testMessageIndex: Int = 0
|
|
@State private var presentLocalMultiplayerSearch: Bool = false
|
|
|
|
var tournamentStore: TournamentStore {
|
|
return self.tournament.tournamentStore
|
|
}
|
|
|
|
init(tournament: Tournament, pasteString: String? = nil, editedTeam: TeamRegistration? = nil) {
|
|
self.tournament = tournament
|
|
_editedTeam = .init(wrappedValue: editedTeam)
|
|
if let team = editedTeam {
|
|
var createdPlayers: Set<PlayerRegistration> = Set()
|
|
var createdPlayerIds: Set<String> = Set()
|
|
|
|
team.unsortedPlayers().forEach { player in
|
|
createdPlayers.insert(player)
|
|
createdPlayerIds.insert(player.id)
|
|
}
|
|
|
|
_createdPlayers = .init(wrappedValue: createdPlayers)
|
|
_createdPlayerIds = .init(wrappedValue: createdPlayerIds)
|
|
}
|
|
|
|
let request: NSFetchRequest<ImportedPlayer> = ImportedPlayer.fetchRequest()
|
|
request.sortDescriptors = [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)]
|
|
request.fetchLimit = 1000
|
|
if let pasteString {
|
|
_pasteString = .init(wrappedValue: pasteString)
|
|
request.predicate = SearchViewModel.pastePredicate(pasteField: pasteString, mostRecentDate: tournament.rankSourceDate, filterOption: tournament.tournamentCategory.playerFilterOption)
|
|
_autoSelect = .init(wrappedValue: true)
|
|
_editableTextField = .init(wrappedValue: pasteString)
|
|
_textHeight = .init(wrappedValue: Self._calculateHeight(text: pasteString))
|
|
cancelShouldDismiss = true
|
|
}
|
|
|
|
fetchRequest = FetchRequest(fetchRequest: request, animation: .default)
|
|
}
|
|
|
|
var body: some View {
|
|
if let pasteString, pasteString.isEmpty == false, fetchPlayers.isEmpty == false {
|
|
computedBody
|
|
.searchable(text: $searchField, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher dans les résultats"))
|
|
} else {
|
|
computedBody
|
|
}
|
|
}
|
|
|
|
var computedBody: some View {
|
|
List(selection: $createdPlayerIds) {
|
|
_buildingTeamView()
|
|
}
|
|
.onReceive(fetchPlayers.publisher.count()) { receivedCount in // <-- here
|
|
if let pasteString, pasteString.isEmpty == false, count == 2, autoSelect == true {
|
|
fetchPlayers.filter { hitForSearch($0, pasteString) >= hitTarget }.sorted(by: { hitForSearch($0, pasteString) > hitForSearch($1, pasteString) }).forEach { player in
|
|
createdPlayerIds.insert(player.license!)
|
|
}
|
|
autoSelect = false
|
|
}
|
|
}
|
|
.overlay(alignment: .bottom) {
|
|
if displayWarningNotEnoughCharacter {
|
|
Text("2 lettres mininum")
|
|
.toastFormatted()
|
|
.animation(.easeInOut(duration: 2.0), value: displayWarningNotEnoughCharacter)
|
|
.onAppear {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
|
displayWarningNotEnoughCharacter = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.alert("Présence d'homonyme", isPresented: $confirmHomonym) {
|
|
Button("Créer l'équipe quand même") {
|
|
_createTeam(checkDuplicates: false, checkHomonym: false)
|
|
}
|
|
|
|
Button("Annuler", role: .cancel) {
|
|
confirmHomonym = false
|
|
}
|
|
|
|
} message: {
|
|
let plural : String = homonyms.count > 1 ? "nt chacun" : ""
|
|
Text(homonyms.map({ $0.playerLabel() }).joined(separator: ", ") + " possède" + plural + " au moins un homonyme dans la base fédérale, vérifiez bien leur licence.")
|
|
}
|
|
.alert("Cette équipe existe déjà", isPresented: $confirmDuplicate) {
|
|
Button("Créer l'équipe quand même") {
|
|
_createTeam(checkDuplicates: false, checkHomonym: true)
|
|
}
|
|
|
|
Button("Annuler", role: .cancel) {
|
|
confirmDuplicate = false
|
|
}
|
|
|
|
} message: {
|
|
Text("Cette équipe existe déjà dans votre liste d'inscription.")
|
|
}
|
|
.sheet(isPresented: $presentLocalMultiplayerSearch) {
|
|
NavigationStack {
|
|
SelectablePlayerListView(allowSelection: -1, isPresented: false, searchField: searchField, dataSet: .club, filterOption: _filterOption(), showFemaleInMaleAssimilation: tournament.tournamentCategory.showFemaleInMaleAssimilation) { players in
|
|
players.forEach { player in
|
|
let newPlayer = PlayerRegistration(importedPlayer: player)
|
|
newPlayer.setComputedRank(in: tournament)
|
|
createdPlayers = Set<PlayerRegistration>()
|
|
createdPlayerIds = Set<String>()
|
|
createdPlayers.insert(newPlayer)
|
|
createdPlayerIds.insert(newPlayer.id)
|
|
_createTeam(checkDuplicates: false, checkHomonym: false)
|
|
}
|
|
|
|
} contentUnavailableAction: { searchViewModel in
|
|
presentLocalMultiplayerSearch = false
|
|
selectionSearchField = searchViewModel.searchText
|
|
}
|
|
}
|
|
.tint(.master)
|
|
|
|
}
|
|
.sheet(isPresented: $presentPlayerSearch) {
|
|
NavigationStack {
|
|
SelectablePlayerListView(allowSelection: 2 - _currentSelectionIds().count, isPresented: true, searchField: searchField, filterOption: _filterOption(), showFemaleInMaleAssimilation: tournament.tournamentCategory.showFemaleInMaleAssimilation) { players in
|
|
players.forEach { player in
|
|
let newPlayer = PlayerRegistration(importedPlayer: player)
|
|
newPlayer.setComputedRank(in: tournament)
|
|
createdPlayers.insert(newPlayer)
|
|
createdPlayerIds.insert(newPlayer.id)
|
|
}
|
|
} contentUnavailableAction: { searchViewModel in
|
|
presentPlayerSearch = false
|
|
selectionSearchField = searchViewModel.searchText
|
|
}
|
|
}
|
|
.tint(.master)
|
|
}
|
|
.sheet(isPresented: $presentPlayerCreation) {
|
|
PlayerPopoverView(sex: _addPlayerSex()) { p in
|
|
p.setComputedRank(in: tournament)
|
|
createdPlayers.insert(p)
|
|
createdPlayerIds.insert(p.id)
|
|
}
|
|
.tint(.master)
|
|
}
|
|
.sheet(item: $selectionSearchField, onDismiss: {
|
|
selectionSearchField = nil
|
|
}) { selectionSearchField in
|
|
PlayerPopoverView(source: selectionSearchField, sex: _addPlayerSex()) { p in
|
|
p.setComputedRank(in: tournament)
|
|
createdPlayers.insert(p)
|
|
createdPlayerIds.insert(p.id)
|
|
}
|
|
.tint(.master)
|
|
}
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) {
|
|
Button("Annuler", role: .cancel) {
|
|
dismiss()
|
|
}
|
|
}
|
|
|
|
if pasteString == nil {
|
|
ToolbarItem(placement: .bottomBar) {
|
|
PasteButton(payloadType: String.self) { strings in
|
|
let first = strings.first ?? ""
|
|
handlePasteString(first)
|
|
}
|
|
.foregroundStyle(.master)
|
|
.labelStyle(.titleAndIcon)
|
|
.buttonBorderShape(.capsule)
|
|
}
|
|
}
|
|
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Button {
|
|
let generalString = UIPasteboard.general.string ?? ""
|
|
|
|
#if targetEnvironment(simulator)
|
|
let s = testMessages[testMessageIndex % testMessages.count]
|
|
handlePasteString(s)
|
|
testMessageIndex += 1
|
|
#else
|
|
handlePasteString(generalString)
|
|
#endif
|
|
} label: {
|
|
Label("Coller", systemImage: "doc.on.clipboard").labelStyle(.iconOnly)
|
|
}
|
|
.foregroundStyle(.master)
|
|
.labelStyle(.iconOnly)
|
|
.buttonBorderShape(.capsule)
|
|
}
|
|
|
|
}
|
|
.navigationBarBackButtonHidden(true)
|
|
.toolbarBackground(.visible, for: .navigationBar)
|
|
.toolbarBackground(.automatic, for: .bottomBar)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.navigationTitle(editedTeam == nil ? "Ajouter une équipe" : "Modifier l'équipe")
|
|
.environment(\.editMode, Binding.constant(EditMode.active))
|
|
}
|
|
|
|
private func _isEditingTeam() -> Bool {
|
|
createdPlayerIds.isEmpty == false || editedTeam != nil || pasteString != nil
|
|
}
|
|
|
|
var unsortedPlayers: [PlayerRegistration] {
|
|
tournament.unsortedPlayers()
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func _managementView() -> some View {
|
|
Section {
|
|
RowButtonView("Ajouter via la base fédérale") {
|
|
presentPlayerSearch = true
|
|
}
|
|
} footer: {
|
|
if let rankSourceDate = tournament.rankSourceDate {
|
|
Text("Cherchez dans la base fédérale de \(rankSourceDate.monthYearFormatted), vous y trouverez tous les joueurs ayant participé à au moins un tournoi dans les 12 derniers mois.")
|
|
}
|
|
}
|
|
|
|
if tournament.isAnimation(), createdPlayers.isEmpty == true {
|
|
Section {
|
|
RowButtonView("Ajouter plusieurs joueurs du club") {
|
|
presentLocalMultiplayerSearch = true
|
|
}
|
|
} footer: {
|
|
Text("Crée une équipe par joueur sélectionné")
|
|
}
|
|
}
|
|
|
|
Section {
|
|
RowButtonView("Créer un non classé / non licencié") {
|
|
if let pasteString, pasteString.isEmpty == false {
|
|
selectionSearchField = pasteString
|
|
} else {
|
|
presentPlayerCreation = true
|
|
}
|
|
}
|
|
} footer: {
|
|
Text("Si le joueur n'a pas encore de licence ou n'a pas encore participé à une compétition, vous pouvez le créer vous-même.")
|
|
}
|
|
}
|
|
|
|
private func _addPlayerSex() -> Int {
|
|
switch tournament.tournamentCategory {
|
|
case .men, .unlisted:
|
|
return 1
|
|
case .women:
|
|
return 0
|
|
case .mix:
|
|
return 1
|
|
}
|
|
|
|
}
|
|
|
|
private func _filterOption() -> PlayerFilterOption {
|
|
return tournament.tournamentCategory.playerFilterOption
|
|
}
|
|
|
|
private func _currentSelection() -> Set<PlayerRegistration> {
|
|
var currentSelection = Set<PlayerRegistration>()
|
|
createdPlayerIds.compactMap { id in
|
|
fetchPlayers.first(where: { id == $0.license })
|
|
}.forEach { player in
|
|
let player = PlayerRegistration(importedPlayer: player)
|
|
player.setComputedRank(in: tournament)
|
|
currentSelection.insert(player)
|
|
}
|
|
|
|
createdPlayerIds.compactMap { id in
|
|
createdPlayers.first(where: { id == $0.id })
|
|
}.forEach {
|
|
currentSelection.insert($0)
|
|
}
|
|
return currentSelection
|
|
}
|
|
|
|
private func _currentSelectionIds() -> [String?] {
|
|
var currentSelection = [String?]()
|
|
createdPlayerIds.compactMap { id in
|
|
fetchPlayers.first(where: { id == $0.license })
|
|
}.forEach { player in
|
|
currentSelection.append(player.license)
|
|
}
|
|
|
|
createdPlayerIds.compactMap { id in
|
|
createdPlayers.first(where: { id == $0.id })
|
|
}.forEach {
|
|
currentSelection.append($0.licenceId)
|
|
}
|
|
return currentSelection
|
|
}
|
|
|
|
private func _isDuplicate() -> Bool {
|
|
if tournament.isAnimation() { return false }
|
|
let ids : [String?] = _currentSelectionIds()
|
|
if tournament.selectedSortedTeams().anySatisfy({ $0.containsExactlyPlayerLicenses(ids) }) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
private func _createTeam(checkDuplicates: Bool, checkHomonym: Bool) {
|
|
if checkDuplicates && _isDuplicate() {
|
|
confirmDuplicate = true
|
|
return
|
|
}
|
|
|
|
let players = _currentSelection()
|
|
|
|
if checkHomonym {
|
|
homonyms = players.filter({ $0.hasHomonym() })
|
|
if homonyms.isEmpty == false {
|
|
confirmHomonym = true
|
|
return
|
|
}
|
|
}
|
|
|
|
let team = tournament.addTeam(players)
|
|
do {
|
|
try self.tournamentStore.teamRegistrations.addOrUpdate(instance: team)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
do {
|
|
try self.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
|
|
pasteString = nil
|
|
editableTextField = ""
|
|
|
|
if team.players().count > 1 {
|
|
createdPlayers.removeAll()
|
|
createdPlayerIds.removeAll()
|
|
dismiss()
|
|
} else {
|
|
editedTeam = team
|
|
}
|
|
}
|
|
|
|
private func _updateTeam(checkDuplicates: Bool) {
|
|
guard let editedTeam else { return }
|
|
if checkDuplicates && _isDuplicate() {
|
|
confirmDuplicate = true
|
|
return
|
|
}
|
|
|
|
let players = _currentSelection()
|
|
editedTeam.updatePlayers(players, inTournamentCategory: tournament.tournamentCategory)
|
|
do {
|
|
try self.tournamentStore.teamRegistrations.addOrUpdate(instance: editedTeam)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
do {
|
|
try self.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
|
|
pasteString = nil
|
|
editableTextField = ""
|
|
|
|
if editedTeam.players().count > 1 {
|
|
dismiss()
|
|
}
|
|
}
|
|
|
|
// Calculating the height based on the content of the TextEditor
|
|
static private func _calculateHeight(text: String) -> CGFloat {
|
|
let size = CGSize(width: UIScreen.main.bounds.width - 32, height: .infinity)
|
|
let attributes: [NSAttributedString.Key: Any] = [.font: UIFont.systemFont(ofSize: 17)]
|
|
let boundingRect = text.boundingRect(
|
|
with: size,
|
|
options: .usesLineFragmentOrigin,
|
|
attributes: attributes,
|
|
context: nil
|
|
)
|
|
return max(boundingRect.height + 20, 40) // Add some padding and set a minimum height
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func _buildingTeamView() -> some View {
|
|
if let pasteString {
|
|
Section {
|
|
TextEditor(text: $editableTextField)
|
|
.frame(height: textHeight)
|
|
.onChange(of: editableTextField) {
|
|
textHeight = Self._calculateHeight(text: pasteString)
|
|
}
|
|
.focused($focusedField, equals: .pasteField)
|
|
.toolbar {
|
|
ToolbarItemGroup(placement: .keyboard) {
|
|
Button("Fermer", role: .cancel) {
|
|
self.editableTextField = pasteString
|
|
self.focusedField = nil
|
|
}
|
|
Spacer()
|
|
Button("Chercher") {
|
|
if editableTextField.count > 1 {
|
|
self.handlePasteString(editableTextField)
|
|
self.focusedField = nil
|
|
} else {
|
|
self.displayWarningNotEnoughCharacter = true
|
|
}
|
|
}
|
|
.buttonStyle(.bordered)
|
|
}
|
|
}
|
|
} header: {
|
|
Text("Contenu du presse-papier")
|
|
} footer: {
|
|
HStack {
|
|
FooterButtonView("éditer") {
|
|
self.focusedField = .pasteField
|
|
}
|
|
Spacer()
|
|
FooterButtonView("effacer le texte") {
|
|
self.focusedField = nil
|
|
self.editableTextField = ""
|
|
self.pasteString = nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Section {
|
|
ForEach(createdPlayerIds.sorted(), id: \.self) { id in
|
|
if let p = createdPlayers.first(where: { $0.id == id }) {
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
if let player = unsortedPlayers.first(where: { ($0.licenceId == p.licenceId && $0.licenceId != nil) }), editedTeam?.includes(player: player) == false {
|
|
Text("Déjà inscrit !").foregroundStyle(.logoRed).bold()
|
|
}
|
|
if tournament.isPlayerAgeInadequate(player: p) {
|
|
Text("Âge invalide !").foregroundStyle(.logoRed).bold()
|
|
}
|
|
if tournament.isPlayerRankInadequate(player: p) {
|
|
Text("Trop bien classé !").foregroundStyle(.logoRed).bold()
|
|
}
|
|
PlayerView(player: p).tag(p.id)
|
|
.environment(tournament)
|
|
}
|
|
}
|
|
if let p = fetchPlayers.first(where: { $0.license == id }) {
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
if let pasteString, pasteString.isEmpty == false, unsortedPlayers.first(where: { $0.licenceId == p.license }) != nil {
|
|
Text("Déjà inscrit !").foregroundStyle(.logoRed).bold()
|
|
}
|
|
if tournament.isPlayerAgeInadequate(player: p) {
|
|
Text("Âge invalide !").foregroundStyle(.logoRed).bold()
|
|
}
|
|
if tournament.isPlayerRankInadequate(player: p) {
|
|
Text("Trop bien classé !").foregroundStyle(.logoRed).bold()
|
|
}
|
|
ImportedPlayerView(player: p).tag(p.license!)
|
|
}
|
|
}
|
|
}
|
|
if editedTeam == nil {
|
|
if createdPlayerIds.isEmpty {
|
|
RowButtonView("Bloquer une place") {
|
|
_createTeam(checkDuplicates: false, checkHomonym: false)
|
|
}
|
|
} else {
|
|
RowButtonView("Ajouter l'équipe") {
|
|
_createTeam(checkDuplicates: true, checkHomonym: true)
|
|
}
|
|
}
|
|
} else {
|
|
RowButtonView("Confirmer") {
|
|
_updateTeam(checkDuplicates: false)
|
|
dismiss()
|
|
}
|
|
}
|
|
} header: {
|
|
let _currentSelection = _currentSelection()
|
|
let selectedSortedTeams = tournament.selectedSortedTeams()
|
|
let rank = _currentSelection.map {
|
|
$0.computedRank
|
|
}.reduce(0, +)
|
|
let teamIndex = selectedSortedTeams.firstIndex(where: { $0.weight >= rank }) ?? selectedSortedTeams.count
|
|
if _currentSelection.isEmpty == false, tournament.hideWeight() == false, rank > 0 {
|
|
HStack(spacing: 16.0) {
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
Text("Rang").font(.caption)
|
|
Text("#" + (teamIndex + 1).formatted())
|
|
}
|
|
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
Text("Poids").font(.caption)
|
|
Text(rank.formatted())
|
|
}
|
|
Spacer()
|
|
VStack(alignment: .trailing, spacing: 0) {
|
|
Text("").font(.caption)
|
|
Text(tournament.cutLabel(index: teamIndex, teamCount: selectedSortedTeams.count))
|
|
}
|
|
}
|
|
// } else {
|
|
// Text("Préparation de l'équipe")
|
|
}
|
|
}
|
|
|
|
|
|
if let pasteString, pasteString.isEmpty == false {
|
|
let sortedPlayers = _searchFilteredPlayers()
|
|
|
|
if sortedPlayers.isEmpty {
|
|
ContentUnavailableView {
|
|
Label("Aucun résultat", systemImage: "person.2.slash")
|
|
} description: {
|
|
Text("Aucun joueur classé n'a été trouvé dans ce message. Attention, si un joueur n'a pas joué de tournoi dans les 12 derniers, Padel Club ne pourra pas le trouver.")
|
|
} actions: {
|
|
RowButtonView("Créer un joueur non classé") {
|
|
selectionSearchField = pasteString
|
|
}
|
|
|
|
RowButtonView("Chercher dans la base") {
|
|
presentPlayerSearch = true
|
|
}
|
|
|
|
RowButtonView("Effacer cette recherche") {
|
|
self.pasteString = nil
|
|
self.editableTextField = ""
|
|
}
|
|
}
|
|
|
|
} else {
|
|
_listOfPlayers(searchFilteredPlayers: sortedPlayers, pasteString: pasteString)
|
|
}
|
|
} else {
|
|
_managementView()
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
func hitForSearch(_ ip: ImportedPlayer, _ pasteString: String?) -> Int {
|
|
guard let pasteString else { return 0 }
|
|
let _searchForHit = pasteString.hashValue
|
|
|
|
if searchForHit != _searchForHit {
|
|
DispatchQueue.main.async {
|
|
searchForHit = _searchForHit
|
|
hitsForSearch = [:]
|
|
}
|
|
}
|
|
|
|
let value = hitsForSearch[ip.id.hashValue]
|
|
if let value {
|
|
return value
|
|
} else {
|
|
let hit = ip.hitForSearch(pasteString)
|
|
DispatchQueue.main.async {
|
|
hitsForSearch[ip.id.hashValue] = hit
|
|
}
|
|
return hit
|
|
}
|
|
}
|
|
|
|
private var count: Int {
|
|
return fetchPlayers.filter { hitForSearch($0, pasteString) >= hitTarget }.count
|
|
}
|
|
|
|
private var hitTarget: Int {
|
|
if (pasteString?.matches(of: /[1-9][0-9]{5,7}/).count ?? 0) > 1 {
|
|
if fetchPlayers.filter({ hitForSearch($0, pasteString) == 100 }).count == 2 { return 100 }
|
|
} else {
|
|
return 2
|
|
}
|
|
return 1
|
|
}
|
|
|
|
@MainActor
|
|
private func handlePasteString(_ first: String) {
|
|
if first.isEmpty == false {
|
|
DispatchQueue.main.async {
|
|
fetchPlayers.nsPredicate = SearchViewModel.pastePredicate(pasteField: first, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable, filterOption: _filterOption())
|
|
fetchPlayers.nsSortDescriptors = [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)]
|
|
autoSelect = true
|
|
}
|
|
}
|
|
pasteString = first
|
|
editableTextField = first
|
|
textHeight = Self._calculateHeight(text: first)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func _listOfPlayers(searchFilteredPlayers: [ImportedPlayer], pasteString: String) -> some View {
|
|
let sortedPlayers = _sortedPlayers(searchFilteredPlayers: searchFilteredPlayers, pasteString: pasteString)
|
|
|
|
Section {
|
|
ForEach(sortedPlayers) { player in
|
|
ImportedPlayerView(player: player).tag(player.license!)
|
|
//Text(player.getLastName() + " " + player.getFirstName()).tag(player.license!)
|
|
}
|
|
} header: {
|
|
Text(sortedPlayers.count.formatted() + " résultat" + sortedPlayers.count.pluralSuffix)
|
|
}
|
|
}
|
|
|
|
|
|
private func _searchFilteredPlayers() -> [ImportedPlayer] {
|
|
if searchField.isEmpty {
|
|
return Array(fetchPlayers)
|
|
} else {
|
|
return fetchPlayers.filter({ $0.contains(searchField) })
|
|
}
|
|
}
|
|
|
|
private func _sortedPlayers(searchFilteredPlayers: [ImportedPlayer], pasteString: String) -> [ImportedPlayer] {
|
|
return searchFilteredPlayers.sorted(by: { hitForSearch($0, pasteString) > hitForSearch($1, pasteString) })
|
|
}
|
|
}
|
|
|
|
let testMessages = [
|
|
"Anthony dovetta ( 3620578 K )et christophe capeau ( 4666443v)",
|
|
"""
|
|
ok merci, il s'agit de :
|
|
Olivier Seguin - licence 5033439
|
|
JPascal Bondierlange - licence :
|
|
6508359 С
|
|
Cordialement
|
|
""",
|
|
"""
|
|
Bonsoir Lise, peux tu nous inscrire pour le 250 hommes du 15 au 17 novembre ?
|
|
Paires DESCHAMPS/PARDO. En te remerciant. Bonne soirée
|
|
Franck
|
|
""",
|
|
"""
|
|
Coucou inscription pour le tournoi du 11 /
|
|
12 octobre
|
|
Dumoutier/ Liagre Charlotte
|
|
Merci de ta confirmation"
|
|
""",
|
|
"""
|
|
Anthony Contet 6081758f
|
|
Tullou Benjamin 8990867f
|
|
""",
|
|
"""
|
|
Sms Julien La Croix +33622886688
|
|
Salut Raz, c'est ! Ju Lacroix J'espère que tu vas bien depuis le temps! Est-ce que tu peux nous inscrire au 1000 de Bandol avec Derek Gerson stp?
|
|
"""
|
|
]
|
|
|