Fix memory issue when going in federal players search

sync2
Laurent 8 months ago
parent 2b856fcde4
commit 6334648efd
  1. 456
      PadelClub/Views/Tournament/Screen/AddTeamView.swift

@ -10,7 +10,7 @@ import LeStorage
import CoreData
struct AddTeamView: View {
@Environment(\.dismiss) var dismiss
private var fetchRequest: FetchRequest<ImportedPlayer>
@ -45,7 +45,7 @@ struct AddTeamView: View {
@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
}
@ -61,11 +61,11 @@ struct AddTeamView: View {
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
@ -77,7 +77,7 @@ struct AddTeamView: View {
_textHeight = .init(wrappedValue: Self._calculateHeight(text: pasteString))
cancelShouldDismiss = true
}
fetchRequest = FetchRequest(fetchRequest: request, animation: .default)
}
@ -97,7 +97,7 @@ struct AddTeamView: View {
computedBody
}
}
var computedBody: some View {
List(selection: $createdPlayerIds) {
_buildingTeamView()
@ -127,7 +127,7 @@ struct AddTeamView: View {
Button("Créer l'équipe quand même") {
_createTeam(checkDuplicates: false, checkHomonym: false)
}
Button("Annuler", role: .cancel) {
confirmHomonym = false
}
@ -140,7 +140,7 @@ struct AddTeamView: View {
Button("Créer l'équipe quand même") {
_createTeam(checkDuplicates: false, checkHomonym: true)
}
Button("Annuler", role: .cancel) {
confirmDuplicate = false
}
@ -221,11 +221,11 @@ struct AddTeamView: View {
.buttonBorderShape(.capsule)
}
}
ToolbarItem(placement: .topBarTrailing) {
Button {
let generalString = UIPasteboard.general.string ?? ""
#if targetEnvironment(simulator)
let s = testMessages[testMessageIndex % testMessages.count]
handlePasteString(s)
@ -249,15 +249,15 @@ struct AddTeamView: View {
.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 {
@ -269,7 +269,7 @@ struct AddTeamView: View {
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") {
@ -279,7 +279,7 @@ struct AddTeamView: View {
Text("Crée une équipe par joueur sélectionné")
}
}
Section {
RowButtonView("Créer un non classé / non licencié") {
if let pasteString, pasteString.isEmpty == false {
@ -292,7 +292,7 @@ struct AddTeamView: View {
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:
@ -304,11 +304,11 @@ struct AddTeamView: View {
}
}
private func _filterOption() -> PlayerFilterOption {
return tournament.tournamentCategory.playerFilterOption
}
private func _currentSelection() -> Set<PlayerRegistration> {
var currentSelection = Set<PlayerRegistration>()
createdPlayerIds.compactMap { id in
@ -318,7 +318,7 @@ struct AddTeamView: View {
player.setComputedRank(in: tournament)
currentSelection.insert(player)
}
createdPlayerIds.compactMap { id in
createdPlayers.first(where: { id == $0.id })
}.forEach {
@ -334,7 +334,7 @@ struct AddTeamView: View {
}.forEach { player in
currentSelection.append(player.license)
}
createdPlayerIds.compactMap { id in
createdPlayers.first(where: { id == $0.id })
}.forEach {
@ -342,7 +342,7 @@ struct AddTeamView: View {
}
return currentSelection
}
private func _isDuplicate() -> Bool {
if tournament.isAnimation() { return false }
let ids : [String?] = _currentSelectionIds()
@ -351,15 +351,15 @@ struct AddTeamView: View {
}
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 {
@ -367,27 +367,27 @@ struct AddTeamView: View {
return
}
}
let team = tournament.addTeam(players)
self.tournamentStore?.teamRegistrations.addOrUpdate(instance: team)
self.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players)
pasteString = nil
editableTextField = ""
createdPlayers.removeAll()
createdPlayerIds.removeAll()
if team.players().count > 1 {
dismiss()
} else {
editedTeam = team
team.unsortedPlayers().forEach { player in
createdPlayers.insert(player)
createdPlayerIds.insert(player.id)
}
}
pasteString = nil
editableTextField = ""
createdPlayers.removeAll()
createdPlayerIds.removeAll()
if team.players().count > 1 {
dismiss()
} else {
editedTeam = team
team.unsortedPlayers().forEach { player in
createdPlayers.insert(player)
createdPlayerIds.insert(player.id)
}
}
}
private func _updateTeam(checkDuplicates: Bool) {
guard let editedTeam else { return }
if checkDuplicates && _isDuplicate() {
@ -399,7 +399,7 @@ struct AddTeamView: View {
editedTeam.updatePlayers(players, inTournamentCategory: tournament.tournamentCategory)
self.tournamentStore?.teamRegistrations.addOrUpdate(instance: editedTeam)
self.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players)
pasteString = nil
editableTextField = ""
@ -407,7 +407,7 @@ struct AddTeamView: View {
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)
@ -420,23 +420,15 @@ struct AddTeamView: View {
)
return max(boundingRect.height + 20, 40) // Add some padding and set a minimum height
}
struct PasteStringSection: View {
let pasteString: String?
@Binding var editableTextField: String
@Binding var textHeight: CGFloat
@FocusState var focusedField: AddTeamView.FocusField?
var handlePasteString: (String) -> Void
@Binding var displayWarningNotEnoughCharacter: Bool
var body: some View {
@ViewBuilder
private func _buildingTeamView() -> some View {
if let pasteString {
Section {
TextEditor(text: $editableTextField)
.frame(height: textHeight)
.onChange(of: editableTextField) {
textHeight = AddTeamView._calculateHeight(text: pasteString)
textHeight = Self._calculateHeight(text: pasteString)
}
.focused($focusedField, equals: .pasteField)
.toolbar {
@ -468,104 +460,121 @@ struct AddTeamView: View {
FooterButtonView("effacer le texte") {
self.focusedField = nil
self.editableTextField = ""
self.handlePasteString("")
self.pasteString = nil
}
}
}
}
}
}
@ViewBuilder
private func _buildingTeamView() -> some View {
PasteStringSection(
pasteString: pasteString,
editableTextField: $editableTextField,
textHeight: $textHeight,
focusedField: _focusedField,
handlePasteString: handlePasteString,
displayWarningNotEnoughCharacter: $displayWarningNotEnoughCharacter
)
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())
}
TeamSelectionSection(
createdPlayerIds: createdPlayerIds,
createdPlayers: createdPlayers,
unsortedPlayers: unsortedPlayers,
fetchPlayers: fetchPlayers,
editedTeam: editedTeam,
pasteString: pasteString,
tournament: tournament,
_createTeam: _createTeam,
_updateTeam: _updateTeam,
dismiss: dismiss,
_currentSelection: _currentSelection
)
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
}
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("Chercher dans la base") {
presentPlayerSearch = true
RowButtonView("Effacer cette recherche") {
self.pasteString = nil
self.editableTextField = ""
}
}
RowButtonView("Effacer cette recherche") {
self.pasteString = nil
self.editableTextField = ""
}
} else {
_listOfPlayers(searchFilteredPlayers: sortedPlayers, pasteString: pasteString)
}
} else {
_listOfPlayers(searchFilteredPlayers: sortedPlayers, pasteString: pasteString)
_managementView()
}
} else {
_managementView()
}
}
//
// 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 }
@ -602,7 +611,7 @@ struct AddTeamView: View {
}
return 1
}
private func handlePasteString(_ first: String) {
if first.isEmpty == false {
fetchPlayers.nsPredicate = SearchViewModel.pastePredicate(pasteField: first, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable, filterOption: _filterOption())
@ -617,7 +626,7 @@ struct AddTeamView: View {
@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!)
@ -628,7 +637,7 @@ struct AddTeamView: View {
}
}
private func _searchFilteredPlayers() -> [ImportedPlayer] {
if searchField.isEmpty {
return Array(fetchPlayers)
@ -636,171 +645,12 @@ struct AddTeamView: View {
return fetchPlayers.filter({ $0.contains(searchField) })
}
}
private func _sortedPlayers(searchFilteredPlayers: [ImportedPlayer], pasteString: String) -> [ImportedPlayer] {
return searchFilteredPlayers.sorted(by: { hitForSearch($0, pasteString) > hitForSearch($1, pasteString) })
}
}
struct TeamSelectionSection: View {
let createdPlayerIds: Set<String>
let createdPlayers: Set<PlayerRegistration>
let unsortedPlayers: [PlayerRegistration]
let fetchPlayers: FetchedResults<ImportedPlayer>
let editedTeam: TeamRegistration?
let pasteString: String?
let tournament: Tournament
let _createTeam: (Bool, Bool) -> Void
let _updateTeam: (Bool) -> Void
let dismiss: DismissAction
let _currentSelection: () -> Set<PlayerRegistration>
var body: some View {
Section {
PlayerListView(createdPlayerIds: createdPlayerIds,
createdPlayers: createdPlayers,
unsortedPlayers: unsortedPlayers,
fetchPlayers: fetchPlayers,
editedTeam: editedTeam,
pasteString: pasteString,
tournament: tournament)
ActionButton(editedTeam: editedTeam,
createdPlayerIds: createdPlayerIds,
_createTeam: _createTeam,
_updateTeam: _updateTeam,
dismiss: dismiss)
} header: {
TeamHeader(tournament: tournament,
_currentSelection: _currentSelection)
}
}
}
struct PlayerListView: View {
let createdPlayerIds: Set<String>
let createdPlayers: Set<PlayerRegistration>
let unsortedPlayers: [PlayerRegistration]
let fetchPlayers: FetchedResults<ImportedPlayer>
let editedTeam: TeamRegistration?
let pasteString: String?
let tournament: Tournament
var body: some View {
ForEach(createdPlayerIds.sorted(), id: \.self) { id in
if let p = createdPlayers.first(where: { $0.id == id }) {
CreatedPlayerView(player: p, unsortedPlayers: unsortedPlayers, editedTeam: editedTeam, tournament: tournament)
}
if let p = fetchPlayers.first(where: { $0.license == id }) {
FetchedPlayerView(player: p, unsortedPlayers: unsortedPlayers, pasteString: pasteString, tournament: tournament)
}
}
}
}
struct CreatedPlayerView: View {
let player: PlayerRegistration
let unsortedPlayers: [PlayerRegistration]
let editedTeam: TeamRegistration?
let tournament: Tournament
var body: some View {
VStack(alignment: .leading, spacing: 0) {
if let existingPlayer = unsortedPlayers.first(where: { ($0.licenceId == player.licenceId && $0.licenceId != nil) }), editedTeam?.includes(player: existingPlayer) == false {
Text("Déjà inscrit !").foregroundStyle(.logoRed).bold()
}
if tournament.isPlayerAgeInadequate(player: player) {
Text("Âge invalide !").foregroundStyle(.logoRed).bold()
}
if tournament.isPlayerRankInadequate(player: player) {
Text("Trop bien classé !").foregroundStyle(.logoRed).bold()
}
PlayerView(player: player).tag(player.id)
.environment(tournament)
}
}
}
struct FetchedPlayerView: View {
let player: ImportedPlayer
let unsortedPlayers: [PlayerRegistration]
let pasteString: String?
let tournament: Tournament
var body: some View {
VStack(alignment: .leading, spacing: 0) {
if let pasteString, pasteString.isEmpty == false, unsortedPlayers.first(where: { $0.licenceId == player.license }) != nil {
Text("Déjà inscrit !").foregroundStyle(.logoRed).bold()
}
if tournament.isPlayerAgeInadequate(player: player) {
Text("Âge invalide !").foregroundStyle(.logoRed).bold()
}
if tournament.isPlayerRankInadequate(player: player) {
Text("Trop bien classé !").foregroundStyle(.logoRed).bold()
}
ImportedPlayerView(player: player).tag(player.license!)
}
}
}
struct ActionButton: View {
let editedTeam: TeamRegistration?
let createdPlayerIds: Set<String>
let _createTeam: (Bool, Bool) -> Void
let _updateTeam: (Bool) -> Void
let dismiss: DismissAction
var body: some View {
if editedTeam == nil {
if createdPlayerIds.isEmpty {
RowButtonView("Bloquer une place") {
_createTeam(false, false)
}
} else {
RowButtonView("Ajouter l'équipe") {
_createTeam(true, true)
}
}
} else {
RowButtonView("Confirmer") {
_updateTeam(false)
dismiss()
}
}
}
}
struct TeamHeader: View {
let tournament: Tournament
let _currentSelection: () -> Set<PlayerRegistration>
var body: some View {
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, !tournament.hideWeight(), 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))
}
}
}
}
}
let testMessages = [
"Anthony dovetta ( 3620578 K )et christophe capeau ( 4666443v)",
"""
@ -827,6 +677,6 @@ 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?
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?
"""
]

Loading…
Cancel
Save