From 470aa619d623a4fb9b19009b68967cff96787e01 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Mon, 9 Sep 2024 20:40:39 +0200 Subject: [PATCH] add tournament subscribe --- PadelClub.xcodeproj/project.pbxproj | 4 + .../Coredata/ImportedPlayer+Extensions.swift | 6 +- .../Data/Federal/FederalTournament.swift | 12 ++ PadelClub/Utils/PadelRule.swift | 6 +- .../Agenda/TournamentSubscriptionView.swift | 194 ++++++++++++++++++ .../Views/Shared/TournamentFilterView.swift | 4 +- .../Shared/TournamentCellView.swift | 12 +- 7 files changed, 231 insertions(+), 7 deletions(-) create mode 100644 PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 8fcbf80..8c9f213 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -162,6 +162,7 @@ FF70916A2B90F95E00AB08DA /* DateBoxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7091692B90F95E00AB08DA /* DateBoxView.swift */; }; FF70916C2B91005400AB08DA /* TournamentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF70916B2B91005400AB08DA /* TournamentView.swift */; }; FF70916E2B9108C600AB08DA /* InscriptionManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF70916D2B9108C600AB08DA /* InscriptionManagerView.swift */; }; + FF8044AC2C8F676D00A49A52 /* TournamentSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8044AB2C8F676D00A49A52 /* TournamentSubscriptionView.swift */; }; FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */; }; FF82CFC92B9132AF00B0CAF2 /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */; }; FF8E1CE62C006E0200184680 /* Alphabet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8E1CE52C006E0200184680 /* Alphabet.swift */; }; @@ -509,6 +510,7 @@ FF7091692B90F95E00AB08DA /* DateBoxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateBoxView.swift; sourceTree = ""; }; FF70916B2B91005400AB08DA /* TournamentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentView.swift; sourceTree = ""; }; FF70916D2B9108C600AB08DA /* InscriptionManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InscriptionManagerView.swift; sourceTree = ""; }; + FF8044AB2C8F676D00A49A52 /* TournamentSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentSubscriptionView.swift; sourceTree = ""; }; FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrganizedTournamentView.swift; sourceTree = ""; }; FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = ""; }; FF8E1CE52C006E0200184680 /* Alphabet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alphabet.swift; sourceTree = ""; }; @@ -1294,6 +1296,7 @@ FF59FFB22B90EFAC0061EFF9 /* EventListView.swift */, FF5D0D8A2BB4D1E3005CB568 /* CalendarView.swift */, FFD655D72C8DE27400E5B35E /* TournamentLookUpView.swift */, + FF8044AB2C8F676D00A49A52 /* TournamentSubscriptionView.swift */, ); path = Agenda; sourceTree = ""; @@ -1594,6 +1597,7 @@ FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */, FF0EC5202BB16F680056B6D1 /* SwiftParser.swift in Sources */, C4A47DA92B85F82100ADC637 /* ChangePasswordView.swift in Sources */, + FF8044AC2C8F676D00A49A52 /* TournamentSubscriptionView.swift in Sources */, FFC83D512BB8087E00750834 /* RoundView.swift in Sources */, FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */, FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */, diff --git a/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift b/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift index 6dc4216..12ba149 100644 --- a/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift +++ b/PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift @@ -57,7 +57,11 @@ extension ImportedPlayer: PlayerHolder { func isMalePlayer() -> Bool { male } - + + func pasteData() -> String { + return [firstName?.capitalized, lastName?.capitalized, license].compactMap({ $0 }).joined(separator: " ") + } + func isNotFromCurrentDate() -> Bool { if let importDate, importDate != SourceFileManager.shared.lastDataSourceDate() { return true diff --git a/PadelClub/Data/Federal/FederalTournament.swift b/PadelClub/Data/Federal/FederalTournament.swift index 0cce922..a535417 100644 --- a/PadelClub/Data/Federal/FederalTournament.swift +++ b/PadelClub/Data/Federal/FederalTournament.swift @@ -155,6 +155,18 @@ struct FederalTournament: Identifiable, Codable { [nomClub, jugeArbitre?.nom, jugeArbitre?.prenom, courrielEngagement, installation?.telephone].compactMap({$0}).joined(separator: ";") } + func umpireLabel() -> String { + [jugeArbitre?.nom, jugeArbitre?.prenom].compactMap({$0}).joined(separator: " ") + } + + func phoneLabel() -> String { + [installation?.telephone].compactMap({$0}).joined(separator: " ") + } + + func mailLabel() -> String { + [courrielEngagement].compactMap({$0}).joined(separator: " ") + } + func validForSearch(_ searchText: String, scope: FederalTournamentSearchScope) -> Bool { var trimmedSearchText = searchText.lowercased().trimmingCharacters(in: .whitespaces).folding(options: .diacriticInsensitive, locale: .current) trimmedSearchText = trimmedSearchText.replaceCharactersFromSet(characterSet: .punctuationCharacters, replacementString: " ") diff --git a/PadelClub/Utils/PadelRule.swift b/PadelClub/Utils/PadelRule.swift index d5b13b4..a30fd68 100644 --- a/PadelClub/Utils/PadelRule.swift +++ b/PadelClub/Utils/PadelRule.swift @@ -6,6 +6,7 @@ // import Foundation +import LeStorage enum RankSource: Hashable { case national @@ -33,7 +34,8 @@ protocol TournamentBuildHolder: Identifiable { } struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable { - var id: String { identifier } + var uniqueId: String = Store.randomId() + var id: String { uniqueId } let category: TournamentCategory let level: TournamentLevel let age: FederalTournamentAge @@ -42,7 +44,7 @@ struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable { // var japLastName: String? = nil func buildHolderTitle() -> String { - localizedLabel() + computedLabel } var identifier: String { diff --git a/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift b/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift new file mode 100644 index 0000000..b7afe67 --- /dev/null +++ b/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift @@ -0,0 +1,194 @@ +// +// TournamentSubscriptionView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 09/09/2024. +// + +import SwiftUI + +struct TournamentSubscriptionView: View { + @EnvironmentObject var networkMonitor: NetworkMonitor + + let federalTournament: FederalTournament + let build: any TournamentBuildHolder + + @State private var selectedPlayers: [ImportedPlayer] + @State private var contactType: ContactType? = nil + @State private var sentError: ContactManagerError? = nil + + init(federalTournament: FederalTournament, build: any TournamentBuildHolder, user: User) { + self.federalTournament = federalTournament + self.build = build + _selectedPlayers = .init(wrappedValue: [user.currentPlayerData()].compactMap({ $0 })) + } + + var body: some View { + List { + Section { + LabeledContent("Tournoi") { + Text(federalTournament.libelle ?? "Tournoi") + } + LabeledContent("Club") { + Text(federalTournament.clubLabel()) + } + LabeledContent("Épreuve") { + Text(build.buildHolderTitle()) + } + } header: { + Text("Informations") + } + + Section { + ForEach(selectedPlayers) { teamPlayer in + NavigationLink { + SelectablePlayerListView(allowSelection: 1, playerSelectionAction: { players in + if let player = players.first { + selectedPlayers.remove(elements: [teamPlayer]) + selectedPlayers.append(player) + } + }) + } label: { + ImportedPlayerView(player: teamPlayer) + } + } + + if selectedPlayers.count < 2 { + NavigationLink { + SelectablePlayerListView(allowSelection: 1, playerSelectionAction: { players in + if let player = players.first { + selectedPlayers.append(player) + } + }) + } label: { + Text("Choisir un partenaire") + } + } + } header: { + HStack { + Text("Poids") + Spacer() + Text(selectedPlayers.map { $0.rank }.reduce(0, +).formatted()) + } + } + + Section { + LabeledContent("JAP") { + Text(federalTournament.umpireLabel()) + } + LabeledContent("Mail") { + Text(federalTournament.mailLabel()) + } + LabeledContent("Téléphone") { + Text(federalTournament.phoneLabel()) + } + } + + let teams = selectedPlayers.map { $0.pasteData() }.joined(separator: "\n") + let body = [[build.buildHolderTitle(), federalTournament.computedStartDate].compacted().joined(separator: " "), teams].compactMap { $0 }.joined(separator: "\n") + "\n" + let subject = [build.buildHolderTitle(), federalTournament.nomClub].compacted().joined(separator: " ") + if let courrielEngagement = federalTournament.courrielEngagement { + Section { + RowButtonView("Contacter par email") { + contactType = .mail(date: nil, recipients: [courrielEngagement], bccRecipients: nil, body: body, subject: subject, tournamentBuild: build as? TournamentBuild) + } + } + } + + if let installation = federalTournament.installation, let telephone = installation.telephone { + if telephone.isMobileNumber() { + let body = [[build.buildHolderTitle(), federalTournament.nomClub].compacted().joined(separator: " "), federalTournament.computedStartDate, teams].compacted().joined(separator: "\n") + "\n" + Section { + RowButtonView("Contacter par message") { + contactType = .message(date: nil, recipients: [telephone], body: body, tournamentBuild: build as? TournamentBuild) + } + } + } else { + let number = telephone.replacingOccurrences(of: " ", with: "") + if let url = URL(string: "tel:\(number)") { + Link(destination: url) { + Label("Appeler", systemImage: "phone") + } + } + } + } + + } + .alert("Un problème est survenu", isPresented: messageSentFailed) { + Button("OK") { + } + } message: { + Text(_networkErrorMessage) + } + .sheet(item: $contactType) { contactType in + Group { + switch contactType { + case .message(_, let recipients, let body, _): + MessageComposeView(recipients: recipients, body: body) { result in + switch result { + case .cancelled: + break + case .failed: + self.sentError = .messageFailed + case .sent: + if networkMonitor.connected == false { + self.sentError = .messageNotSent + } + @unknown default: + break + } + } + case .mail(_, let recipients, let bccRecipients, let body, let subject, _): + MailComposeView(recipients: recipients, bccRecipients: bccRecipients, body: body, subject: subject) { result in + switch result { + case .cancelled, .saved: + self.contactType = nil + case .failed: + self.contactType = nil + self.sentError = .mailFailed + case .sent: + if networkMonitor.connected == false { + self.contactType = nil + self.sentError = .mailNotSent + } + @unknown default: + break + } + } + } + } + .tint(.master) + } + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + .navigationTitle("Détail du tournoi") + } + + var messageSentFailed: Binding { + Binding { + sentError != nil + } set: { newValue in + if newValue == false { + sentError = nil + } + } + } + + private var _networkErrorMessage: String { + var errors: [String] = [] + + if networkMonitor.connected == false { + errors.append("L'appareil n'est pas connecté à internet.") + } + if sentError == .mailNotSent { + errors.append("Le mail est dans la boîte d'envoi de l'app Mail. Vérifiez son état dans l'app Mail avant d'essayer de le renvoyer.") + } + if (sentError == .messageFailed || sentError == .messageNotSent) { + errors.append("Le SMS n'a pas été envoyé") + } + if sentError == .mailFailed { + errors.append("Le mail n'a pas été envoyé") + } + return errors.joined(separator: "\n") + } +} diff --git a/PadelClub/Views/Shared/TournamentFilterView.swift b/PadelClub/Views/Shared/TournamentFilterView.swift index a312db0..9d0a2c3 100644 --- a/PadelClub/Views/Shared/TournamentFilterView.swift +++ b/PadelClub/Views/Shared/TournamentFilterView.swift @@ -71,7 +71,7 @@ struct TournamentFilterView: View { } Section { - ForEach(TournamentCategory.allCases) { category in + ForEach([TournamentCategory.men, TournamentCategory.women, TournamentCategory.mix]) { category in LabeledContent { Button { if categories.contains(category) { @@ -85,7 +85,7 @@ struct TournamentFilterView: View { } } } label: { - Text(category.localizedLabel(.title)) + Text(category.localizedLabel(.wide)) } } } header: { diff --git a/PadelClub/Views/Tournament/Shared/TournamentCellView.swift b/PadelClub/Views/Tournament/Shared/TournamentCellView.swift index 5014e02..1e9ab62 100644 --- a/PadelClub/Views/Tournament/Shared/TournamentCellView.swift +++ b/PadelClub/Views/Tournament/Shared/TournamentCellView.swift @@ -23,7 +23,15 @@ struct TournamentCellView: View { var body: some View { ForEach(tournament.tournaments, id: \.id) { build in - _buildView(build, existingTournament: event?.existingBuild(build)) + if navigation.agendaDestination == .around, let federalTournament = tournament as? FederalTournament { + NavigationLink { + TournamentSubscriptionView(federalTournament: federalTournament, build: build, user: dataStore.user) + } label: { + _buildView(build, existingTournament: event?.existingBuild(build)) + } + } else { + _buildView(build, existingTournament: event?.existingBuild(build)) + } } } @@ -98,7 +106,7 @@ struct TournamentCellView: View { } else if let teamCount { Text(teamCount.formatted()) } - } else if let federalTournament = tournament as? FederalTournament { + } else if let federalTournament = tournament as? FederalTournament, navigation.agendaDestination != .around { Button { _createOrShow(federalTournament: federalTournament, existingTournament: existingTournament, build: build) } label: {