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.
 
 
PadelClub/PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView....

385 lines
16 KiB

//
// TournamentSubscriptionView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 09/09/2024.
//
import SwiftUI
import EventKit
import PadelClubData
struct TournamentSubscriptionView: View {
@EnvironmentObject var networkMonitor: NetworkMonitor
let federalTournament: FederalTournament
let build: any TournamentBuildHolder
let user: CustomUser
@State private var selectedPlayers: [ImportedPlayer]
@State private var contactType: ContactType? = nil
@State private var sentError: ContactManagerError? = nil
@State private var didSendMessage: Bool = false
@State private var didSaveInCalendar: Bool = false
@State private var phoneNumber: String? = nil
@State private var errorWhenGatheringPhone: Bool = false
init(federalTournament: FederalTournament, build: any TournamentBuildHolder, user: CustomUser) {
self.federalTournament = federalTournament
self.build = build
self.user = user
_selectedPlayers = .init(wrappedValue: [user.currentPlayerData()].compactMap({ $0 }))
}
func hasPartner() -> Bool {
selectedPlayers.count == 2
}
func inscriptionSent() -> Bool {
didSendMessage
}
func addEvent() {
let eventStore = EKEventStore()
eventStore.requestWriteOnlyAccessToEvents { (granted, error) in
if granted && error == nil {
print("Access granted")
let startDate = federalTournament.startDate
let endDate = federalTournament.dateFin ?? federalTournament.startDate.endOfDay()
addEventToCalendar(title: messageSubject, startDate: startDate, endDate: endDate)
didSaveInCalendar = true
} else {
print("Access denied or error occurred: \(String(describing: error?.localizedDescription))")
sentError = .calendarAccessDenied
}
}
}
func addEventToCalendar(title: String, startDate: Date, endDate: Date) {
let eventStore = EKEventStore()
if eventStore.defaultCalendarForNewEvents == nil {
sentError = .noCalendarAvailable
return
}
eventStore.requestWriteOnlyAccessToEvents { (granted, error) in
if granted && error == nil {
let event = EKEvent(eventStore: eventStore)
event.title = title
event.isAllDay = true
event.startDate = startDate
event.endDate = endDate
event.calendar = eventStore.defaultCalendarForNewEvents
event.notes = noteCalendar
event.location = federalTournament.clubLabel()
do {
try eventStore.save(event, span: .thisEvent)
didSaveInCalendar = true
print("Event saved")
} catch let error {
print("Failed to save event: \(error.localizedDescription)")
sentError = .calendarEventSaveFailed
}
} else {
print("Access denied or error occurred: \(String(describing: error?.localizedDescription))")
sentError = .calendarAccessDenied
}
}
}
var body: some View {
List {
Section {
LabeledContent("Tournoi") {
Text(federalTournament.libelle ?? "Tournoi")
}
LabeledContent("Club") {
Text(federalTournament.clubLabel())
}
LabeledContent("Épreuve") {
Text(build.buildHolderTitle(.wide))
}
LabeledContent("JAP") {
Text(federalTournament.umpireLabel())
}
LabeledContent("Mail") {
Text(federalTournament.mailLabel())
}
LabeledContent("Téléphone Club") {
Text(federalTournament.phoneLabel())
}
LabeledContent("Téléphone JAP") {
if let phoneNumber {
Text(phoneNumber)
} else if errorWhenGatheringPhone == false {
ProgressView()
} else {
Image(systemName: "exclamationmark.triangle")
}
}
} 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: {
if selectedPlayers.isEmpty == false {
HStack {
Text("Poids de l'équipe")
Spacer()
Text(selectedPlayers.map { $0.rank }.reduce(0, +).formatted())
}
}
}
Section {
Text(messageBody)
} header: {
Text("Message preparé par Padel Club")
} footer: {
CopyPasteButtonView(pasteValue: messageBody)
}
}
.ifAvailableiOS26 { view in
view.toolbar(.hidden, for: .tabBar)
}
.task {
do {
self.phoneNumber = try await NetworkFederalService.shared.getUmpireData(idTournament: federalTournament.id).phone
} catch {
self.errorWhenGatheringPhone = true
}
}
.toolbarBackground(.visible, for: .bottomBar)
.toolbarBackground(.visible, for: .navigationBar)
.overlay(alignment: .bottom) {
if didSaveInCalendar {
Label("Ajouté dans votre calendrier par défaut", systemImage: "checkmark")
.toastFormatted()
.deferredRendering(for: .seconds(3))
}
}
.toolbar(content: {
if #available(iOS 26.0, *) {
ToolbarSpacer(placement: .bottomBar)
}
ToolbarItem(placement: .bottomBar) {
Menu {
Menu {
if let courrielEngagement = federalTournament.courrielEngagement {
Button("Email", systemImage: "envelope") {
contactType = .mail(date: nil, recipients: [courrielEngagement], bccRecipients: nil, body: messageBody, subject: messageSubject, tournamentBuild: build as? TournamentBuild)
}
}
if let telephone = phoneNumber {
if telephone.isMobileNumber() {
Button("Message", systemImage: "message") {
contactType = .message(date: nil, recipients: [telephone], body: messageBodyShort, tournamentBuild: build as? TournamentBuild)
}
}
let number = telephone.replacingOccurrences(of: " ", with: "")
if let url = URL(string: "tel:\(number)") {
Link(destination: url) {
Label("Appeler le JAP", systemImage: "phone")
}
}
}
} label: {
Label("Inscription", systemImage: "pencil.and.list.clipboard")
}
Menu {
if let installation = federalTournament.installation, let telephone = installation.telephone {
Button("Email", systemImage: "envelope") {
contactType = .message(date: nil, recipients: [telephone], body: messageBodyShort, tournamentBuild: build as? TournamentBuild)
}
let number = telephone.replacingOccurrences(of: " ", with: "")
if let url = URL(string: "tel:\(number)") {
Link(destination: url) {
Label("Appeler", systemImage: "phone")
}
}
}
} label: {
Label("Contacter le club", systemImage: "house.and.flag")
}
} label: {
Text("S'inscrire")
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
}
.menuStyle(.button)
.buttonStyle(.borderedProminent)
}
if #available(iOS 26.0, *) {
ToolbarSpacer(placement: .bottomBar)
}
ToolbarItem(placement: .topBarTrailing) {
Menu {
ShareLink(item: federalTournament.sharePartnerMessage) {
Label("Prévenir votre partenaire", systemImage: "person.2")
}
Button("Ajouter à votre agenda", systemImage: "calendar") {
addEvent()
}
Link(destination: URL(string:"https://tenup.fft.fr/tournoi/\(federalTournament.id)")!) {
Label("Voir sur Tenup", systemImage: "tennisball")
}
ShareLink(item: federalTournament.shareMessage) {
Label("Partager les infos", systemImage: "info")
}
} label: {
LabelOptions()
}
}
})
.alert("Un problème est survenu", isPresented: messageSentFailed) {
Button("OK") {
}
if case .calendarAccessDenied = sentError {
Button("Voir vos réglages") {
openAppSettings()
}
}
if case .noCalendarAvailable = sentError{
Button("Voir vos réglages") {
openAppSettings()
}
}
} 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
} else {
self.didSendMessage = true
}
@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
} else {
self.didSendMessage = true
}
@unknown default:
break
}
}
}
}
.tint(.master)
}
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Détail du tournoi")
}
var teamsString: String {
selectedPlayers.map { $0.pasteData(withRank: true) }.joined(separator: "\n")
}
var messageBody: String {
let bonjourOuBonsoir = Date().timeOfDay.hello
let bonneSoireeOuBonneJournee = Date().timeOfDay.goodbye
let body = [["\(bonjourOuBonsoir),\n\nJe souhaiterais inscrire mon équipe au tournoi : ", build.buildHolderTitle(.wide), "du", federalTournament.computedStartDate, "au", federalTournament.clubLabel() + ".\n"].compacted().joined(separator: " "), teamsString, "\nCordialement,\n", user.fullName() ?? bonneSoireeOuBonneJournee, "----------------------------------\nCe message a été préparé grâce à l'application Padel Club !\n\(URLs.appStore.rawValue)"].compactMap { $0 }.joined(separator: "\n") + "\n"
return body
}
var messageBodyShort: String {
let bonjourOuBonsoir = Date().timeOfDay.hello
let bonneSoireeOuBonneJournee = Date().timeOfDay.goodbye
let body = [["\(bonjourOuBonsoir),\n\nJe souhaiterais inscrire mon équipe au tournoi : ", build.buildHolderTitle(.wide), "du", federalTournament.computedStartDate, "au", federalTournament.clubLabel() + ".\n"].compacted().joined(separator: " "), teamsString, "\nCordialement,\n", user.fullName() ?? bonneSoireeOuBonneJournee, "----------------------------------\nCe message a été préparé grâce à l'application Padel Club !\n\(URLs.appStore.rawValue)"].compactMap { $0 }.joined(separator: "\n") + "\n"
return body
}
var noteCalendar: String {
let body = [[build.buildHolderTitle(.wide), "du", federalTournament.computedStartDate, "au", federalTournament.clubLabel() + ".\n"].compacted().joined(separator: " "), teamsString, federalTournament.calendarNoteMessage()].compactMap { $0 }.joined(separator: "\n") + "\n"
return body
}
var messageSubject: String {
let subject = [build.buildHolderTitle(.wide), federalTournament.clubLabel()].compacted().joined(separator: " ")
return subject
}
var messageSentFailed: Binding<Bool> {
Binding {
sentError != nil
} set: { newValue in
if newValue == false {
sentError = nil
}
}
}
private var _networkErrorMessage: String {
ContactManagerError.getNetworkErrorMessage(sentError: sentError, networkMonitorConnected: networkMonitor.connected)
}
func openAppSettings() {
if let appSettings = URL(string: UIApplication.openSettingsURLString) {
if UIApplication.shared.canOpenURL(appSettings) {
UIApplication.shared.open(appSettings, options: [:], completionHandler: nil)
}
}
}
}