From 15dbd05e571639badcbc336d09ff6a2732f1b4ff Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Wed, 1 May 2024 17:05:07 +0200 Subject: [PATCH] fix stuff --- PadelClub/Data/Club.swift | 4 + PadelClub/Data/Tournament.swift | 12 ++ PadelClub/Utils/SourceFileManager.swift | 1 - PadelClub/Utils/URLs.swift | 2 + PadelClub/ViewModel/AgendaDestination.swift | 2 +- .../GroupStage/GroupStageSettingsView.swift | 6 +- .../Views/GroupStage/GroupStagesView.swift | 26 ++-- .../Navigation/Agenda/ActivityView.swift | 4 +- .../Views/Tournament/FileImportView.swift | 2 +- .../Tournament/Screen/BroadcastView.swift | 118 ++++++++++++++++++ .../Components/InscriptionInfoView.swift | 2 +- .../TournamentMatchFormatsSettingsView.swift | 4 +- .../Components/TournamentStatusView.swift | 51 +++++--- .../Screen/InscriptionManagerView.swift | 6 +- .../Views/Tournament/TournamentView.swift | 62 +++++---- 15 files changed, 241 insertions(+), 61 deletions(-) diff --git a/PadelClub/Data/Club.swift b/PadelClub/Data/Club.swift index f0f7e4f..2d23616 100644 --- a/PadelClub/Data/Club.swift +++ b/PadelClub/Data/Club.swift @@ -54,6 +54,10 @@ class Club : ModelObject, Storable, Hashable { return acronym } } + + func shareURL() -> URL? { + return URLs.main.url.appending(path: "?club=\(id)") + } var courts: [Court] { Store.main.filter { $0.club == self.id }.sorted(by: \.index) diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 89dd04e..62cc32d 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -98,8 +98,17 @@ class Tournament : ModelObject, Storable { enum State { case initial case build + case canceled } + func shareURL() -> URL? { + return URLs.main.url.appending(path: "tournament/\(id)") + } + + func broadcastURL() -> URL? { + return URLs.main.url.appending(path: "tournament/\(id)/broadcast") + } + func courtUsed() -> [Int] { let runningMatches : [Match] = Store.main.filter(isIncluded: { $0.isRunning() }).filter({ $0.tournamentId() == self.id }) return Set(runningMatches.compactMap { $0.courtIndex }).sorted() @@ -141,6 +150,9 @@ class Tournament : ModelObject, Storable { } func state() -> Tournament.State { + if isCanceled { + return .canceled + } if (groupStageCount > 0 && groupStages().isEmpty == false) || rounds().isEmpty == false { return .build diff --git a/PadelClub/Utils/SourceFileManager.swift b/PadelClub/Utils/SourceFileManager.swift index 018a341..fab3def 100644 --- a/PadelClub/Utils/SourceFileManager.swift +++ b/PadelClub/Utils/SourceFileManager.swift @@ -9,7 +9,6 @@ import Foundation class SourceFileManager { static let shared = SourceFileManager() - static let beachPadel = URL(string: "https://beach-padel.app.fft.fr/beachja/index/")! var lastDataSource: String? { DataStore.shared.appSettings.lastDataSource diff --git a/PadelClub/Utils/URLs.swift b/PadelClub/Utils/URLs.swift index 3170d66..51d4b4f 100644 --- a/PadelClub/Utils/URLs.swift +++ b/PadelClub/Utils/URLs.swift @@ -9,6 +9,8 @@ import Foundation enum URLs: String, Identifiable { case subscriptions = "https://apple.co/2Th4vqI" + case main = "https://xlr.alwaysdata.net/" + case beachPadel = "https://beach-padel.app.fft.fr/beachja/index/" var id: String { return self.rawValue } diff --git a/PadelClub/ViewModel/AgendaDestination.swift b/PadelClub/ViewModel/AgendaDestination.swift index 1531906..8f30534 100644 --- a/PadelClub/ViewModel/AgendaDestination.swift +++ b/PadelClub/ViewModel/AgendaDestination.swift @@ -48,7 +48,7 @@ enum AgendaDestination: CaseIterable, Identifiable, Selectable { func badgeValue() -> Int? { switch self { case .activity: - DataStore.shared.tournaments.filter { $0.endDate == nil && FederalDataViewModel.shared.isTournamentValidForFilters($0) }.count + DataStore.shared.tournaments.filter { $0.endDate == nil && $0.isDeleted == false && FederalDataViewModel.shared.isTournamentValidForFilters($0) }.count case .history: DataStore.shared.tournaments.filter { $0.endDate != nil && FederalDataViewModel.shared.isTournamentValidForFilters($0) }.count case .tenup: diff --git a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift index 5f3cbb5..5b2d65d 100644 --- a/PadelClub/Views/GroupStage/GroupStageSettingsView.swift +++ b/PadelClub/Views/GroupStage/GroupStageSettingsView.swift @@ -26,8 +26,10 @@ struct GroupStageSettingsView: View { var body: some View { List { - Section { - menuBuildAllGroupStages + if tournament.unsortedTeams().filter({ $0.groupStagePosition != nil }).isEmpty == false { + Section { + menuBuildAllGroupStages + } } Section { diff --git a/PadelClub/Views/GroupStage/GroupStagesView.swift b/PadelClub/Views/GroupStage/GroupStagesView.swift index 3628cb4..340ad0e 100644 --- a/PadelClub/Views/GroupStage/GroupStagesView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesView.swift @@ -49,9 +49,13 @@ struct GroupStagesView: View { init(tournament: Tournament) { self.tournament = tournament - let gs = tournament.getActiveGroupStage() - if let gs { - _selectedDestination = State(wrappedValue: .groupStage(gs)) + if tournament.unsortedTeams().filter({ $0.groupStagePosition != nil }).isEmpty { + _selectedDestination = State(wrappedValue: nil) + } else { + let gs = tournament.getActiveGroupStage() + if let gs { + _selectedDestination = State(wrappedValue: .groupStage(gs)) + } } } @@ -67,17 +71,23 @@ struct GroupStagesView: View { GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations(), nilDestinationIsValid: true) switch selectedDestination { case .all: + let allGroupStages = tournament.groupStages() + let availableToStart = allGroupStages.flatMap({ $0.availableToStart() }) + let runningMatches = allGroupStages.flatMap({ $0.runningMatches() }) + let readyMatches = allGroupStages.flatMap({ $0.readyMatches() }) + let finishedMatches = allGroupStages.flatMap({ $0.finishedMatches() }) + List { - let allGroupStages = tournament.groupStages() - let availableToStart = allGroupStages.flatMap({ $0.availableToStart() }) - let runningMatches = allGroupStages.flatMap({ $0.runningMatches() }) - let readyMatches = allGroupStages.flatMap({ $0.readyMatches() }) - let finishedMatches = allGroupStages.flatMap({ $0.finishedMatches() }) MatchListView(section: "disponible", matches: availableToStart, matchViewStyle: .standardStyle, isExpanded: false) MatchListView(section: "en cours", matches: runningMatches, matchViewStyle: .standardStyle, isExpanded: false) MatchListView(section: "à lancer", matches: readyMatches, matchViewStyle: .standardStyle, isExpanded: false) MatchListView(section: "terminés", matches: finishedMatches, matchViewStyle: .standardStyle, isExpanded: false) } + .overlay { + if availableToStart.isEmpty && runningMatches.isEmpty && readyMatches.isEmpty && finishedMatches.isEmpty { + ContentUnavailableView("Aucun match à afficher", systemImage: "tennisball") + } + } .navigationTitle("Toutes les poules") case .groupStage(let groupStage): GroupStageView(groupStage: groupStage) diff --git a/PadelClub/Views/Navigation/Agenda/ActivityView.swift b/PadelClub/Views/Navigation/Agenda/ActivityView.swift index 17d9ab8..90c2cee 100644 --- a/PadelClub/Views/Navigation/Agenda/ActivityView.swift +++ b/PadelClub/Views/Navigation/Agenda/ActivityView.swift @@ -22,12 +22,12 @@ struct ActivityView: View { @State private var uuid: UUID = UUID() var runningTournaments: [FederalTournamentHolder] { - dataStore.tournaments.filter({ $0.endDate == nil && $0.isDeleted == false }) + dataStore.tournaments.filter({ $0.endDate == nil }) .filter({ federalDataViewModel.isTournamentValidForFilters($0) }) } var endedTournaments: [Tournament] { - dataStore.tournaments.filter({ $0.endDate != nil && $0.isDeleted == false }) + dataStore.tournaments.filter({ $0.endDate != nil }) .filter({ federalDataViewModel.isTournamentValidForFilters($0) }) .sorted(using: SortDescriptor(\.startDate, order: .reverse)) } diff --git a/PadelClub/Views/Tournament/FileImportView.swift b/PadelClub/Views/Tournament/FileImportView.swift index 727b9da..9feacf1 100644 --- a/PadelClub/Views/Tournament/FileImportView.swift +++ b/PadelClub/Views/Tournament/FileImportView.swift @@ -37,7 +37,7 @@ struct FileImportView: View { if teams.isEmpty { Section { - Link(destination: SourceFileManager.beachPadel) { + Link(destination: URLs.beachPadel.url) { Label("beach-padel.app.fft.fr", systemImage: "tennisball") } diff --git a/PadelClub/Views/Tournament/Screen/BroadcastView.swift b/PadelClub/Views/Tournament/Screen/BroadcastView.swift index 2d6ace4..5b89cf0 100644 --- a/PadelClub/Views/Tournament/Screen/BroadcastView.swift +++ b/PadelClub/Views/Tournament/Screen/BroadcastView.swift @@ -6,15 +6,133 @@ // import SwiftUI +import CoreImage.CIFilterBuiltins +import LeStorage + +extension String : Identifiable { + public var id: String { self } +} struct BroadcastView: View { + @EnvironmentObject var dataStore: DataStore + @Environment(Tournament.self) var tournament: Tournament + let context = CIContext() + let filter = CIFilter.qrCodeGenerator() + @State private var urlToShow: String? + @State private var tvMode: Bool = false + var body: some View { + @Bindable var tournament = tournament List { + Section { + Toggle(isOn: $tournament.isPrivate) { + Text("Tournoi privée") + } + } footer: { + Text("Le tournoi sera masqué sur le site \(URLs.main.rawValue)") + } + + Section { + LabeledContent { + actionForURL(URLs.main.url) + } label: { + Text("Lien Padel Club") + } + + if let club = tournament.club(), let clubURL = club.shareURL() { + LabeledContent { + actionForURL(clubURL) + } label: { + Text("Lien du club") + } + } + + if let url = tournament.shareURL() { + LabeledContent { + actionForURL(url) + } label: { + Text("Lien du tournoi") + } + } + + if let url = tournament.broadcastURL() { + LabeledContent { + actionForURL(url) + } label: { + Text("Lien TV") + } + } + + } header: { + Text("Liens à partager") + .textCase(nil) + } + } .navigationTitle("Diffusion") .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.visible, for: .navigationBar) + .sheet(item: $urlToShow) { urlToShow in + Image(uiImage: generateQRCode(from: urlToShow)) + .interpolation(.none) + .resizable() + .scaledToFit() + .frame(width: 300, height: 300) + .onAppear { + UIPasteboard.general.string = urlToShow + } + } + .onChange(of: tournament.isPrivate) { + _save() + } + } + + private func _save() { + do { + try dataStore.tournaments.addOrUpdate(instance: tournament) + } catch { + Logger.error(error) + } + } + + private func generateQRCode(from string: String) -> UIImage { + filter.message = Data(string.utf8) + + if let outputImage = filter.outputImage { + if let cgimg = context.createCGImage(outputImage, from: outputImage.extent) { + return UIImage(cgImage: cgimg) + } + } + + return UIImage(systemName: "xmark.circle") ?? UIImage() } + + @ViewBuilder + func actionForURL(_ url: URL, removeSource: Bool = false) -> some View { + Menu { + Button { + UIApplication.shared.open(url) + } label: { + Label("Voir", systemImage: "safari") + } + + Button { + urlToShow = url.absoluteString + } label: { + Label("QRCode", systemImage: "qrcode") + } + + ShareLink(item: url) { + Label("Partager le lien", systemImage: "link") + } + } label: { + Text("lien") + .underline() + } + .buttonStyle(.borderless) + } + + } #Preview { diff --git a/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift b/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift index 5c3ab54..8b95ae2 100644 --- a/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift @@ -33,7 +33,7 @@ struct InscriptionInfoView: View { Text(entriesFromBeachPadel.count.formatted()) } label: { Text("Paires importées") - Text(SourceFileManager.beachPadel.absoluteString) + Text(URLs.beachPadel.url.absoluteString) } .listRowView(color: .indigo) diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentMatchFormatsSettingsView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentMatchFormatsSettingsView.swift index c4fae1c..ede4593 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentMatchFormatsSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentMatchFormatsSettingsView.swift @@ -69,10 +69,10 @@ struct TournamentMatchFormatsSettingsView: View { private func _confirmOrSave() { switch tournament.state() { - case .initial: - break case .build: confirmUpdate = true + default: + break } } diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift index e405692..632b254 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift @@ -9,37 +9,55 @@ import SwiftUI import LeStorage struct TournamentStatusView: View { + @Environment(\.dismiss) private var dismiss @Environment(Tournament.self) private var tournament: Tournament + @Environment(NavigationViewModel.self) private var navigation: NavigationViewModel @EnvironmentObject var dataStore: DataStore var body: some View { @Bindable var tournament = tournament Form { + RowButtonView("debug: Un-delete le tournoi") { + tournament.endDate = nil + tournament.isDeleted.toggle() + } + Section { - RowButtonView("Supprimer le tournoi", role: .destructive) { - tournament.isDeleted.toggle() + if tournament.endDate == nil { + RowButtonView("Terminer le tournoi", role: .destructive) { + tournament.endDate = Date() + } + } else { + RowButtonView("Ré-ouvrir le tournoi", role: .destructive) { + tournament.endDate = nil + } } } footer: { Text("todo: expliquer ce que ca fait") } + Section { - if tournament.hasEnded() == false { - if tournament.currentCanceled == false { - RowButtonView("Annuler le tournoi", role: .destructive) { - tournament.setCanceled(true) - self._save() - } - } else { - RowButtonView("Reprendre le tournoi", role: .destructive) { - tournament.setCanceled(false) - self._save() - } - } + RowButtonView("Supprimer le tournoi", role: .destructive) { + tournament.endDate = Date() + tournament.isDeleted.toggle() + tournament.navigationPath.removeAll() + navigation.path = NavigationPath() } } footer: { Text("todo: expliquer ce que ca fait") } + if tournament.hasEnded() == false && tournament.isCanceled == false { + Section { + RowButtonView("Annuler le tournoi", role: .destructive) { + tournament.isCanceled.toggle() + dismiss() + } + } footer: { + Text("todo: expliquer ce que ca fait") + } + } + Section { Toggle(isOn: $tournament.isPrivate) { Text("Tournoi privée") @@ -49,7 +67,10 @@ struct TournamentStatusView: View { } } .toolbarBackground(.visible, for: .navigationBar) - .onChange(of: [tournament.isDeleted, tournament.isPrivate]) { + .onChange(of: tournament.endDate) { + _save() + } + .onChange(of: [tournament.isDeleted, tournament.isCanceled, tournament.isPrivate]) { _save() } } diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index ea46289..f145131 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -171,7 +171,7 @@ struct InscriptionManagerView: View { } label: { Label("Importer beach-padel", systemImage: "square.and.arrow.down") } - Link(destination: SourceFileManager.beachPadel) { + Link(destination: URLs.beachPadel.url) { Label("beach-padel.app.fft.fr", systemImage: "safari") } } else { @@ -362,7 +362,7 @@ struct InscriptionManagerView: View { TipView(fileTip) { action in if action.id == "website" { - UIApplication.shared.open(SourceFileManager.beachPadel) + UIApplication.shared.open(URLs.beachPadel.url) } else if action.id == "add-team-file" { presentImportView = true } @@ -451,7 +451,7 @@ struct InscriptionManagerView: View { isLearningMore = true } if action.id == "padel-beach" { - UIApplication.shared.open(SourceFileManager.beachPadel) + UIApplication.shared.open(URLs.beachPadel.url) } } .tipStyle(tint: nil) diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index ac9806a..4e7fa1d 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -26,38 +26,50 @@ struct TournamentView: View { VStack(spacing: 0.0) { List { SubscriptionInfoView() - Section { - NavigationLink(value: Screen.inscription) { - LabeledContent { - Text(tournament.unsortedTeams().count.formatted() + "/" + tournament.teamCount.formatted()) - } label: { - Text("Gestion des inscriptions") - if let closedRegistrationDate = tournament.closedRegistrationDate { - Text("clôturé le " + closedRegistrationDate.formatted(date: .abbreviated, time: .shortened)) + + if tournament.state() != .canceled { + Section { + NavigationLink(value: Screen.inscription) { + LabeledContent { + Text(tournament.unsortedTeams().count.formatted() + "/" + tournament.teamCount.formatted()) + } label: { + Text("Gestion des inscriptions") + if let closedRegistrationDate = tournament.closedRegistrationDate { + Text("clôturé le " + closedRegistrationDate.formatted(date: .abbreviated, time: .shortened)) + } } } - } - if let endOfInscriptionDate = tournament.mandatoryRegistrationCloseDate(), tournament.inscriptionClosed() == false && tournament.hasStarted() == false { - LabeledContent { - Text(endOfInscriptionDate.formatted(date: .abbreviated, time: .shortened)) - } label: { - Text("Date limite") + if let endOfInscriptionDate = tournament.mandatoryRegistrationCloseDate(), tournament.inscriptionClosed() == false && tournament.hasStarted() == false { + LabeledContent { + Text(endOfInscriptionDate.formatted(date: .abbreviated, time: .shortened)) + } label: { + Text("Date limite") + } } - } - } footer: { - if tournament.inscriptionClosed() == false && tournament.state() == .build && tournament.unsortedTeams().isEmpty == false && tournament.hasStarted() == false { - Button { - tournament.lockRegistration() - _save() - } label: { - Text("clôturer les inscriptions") - .underline() + } footer: { + if tournament.inscriptionClosed() == false && tournament.state() == .build && tournament.unsortedTeams().isEmpty == false && tournament.hasStarted() == false { + Button { + tournament.lockRegistration() + _save() + } label: { + Text("clôturer les inscriptions") + .underline() + } + .buttonStyle(.borderless) } - .buttonStyle(.borderless) } } - + switch tournament.state() { + case .canceled: + Section { + RowButtonView("Reprendre le tournoi", role: .destructive) { + tournament.isCanceled.toggle() + _save() + } + } footer: { + Text("todo expliquer cet état") + } case .initial: TournamentInitView()