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.
385 lines
16 KiB
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|