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.
553 lines
20 KiB
553 lines
20 KiB
//
|
|
// MatchDetailView.swift
|
|
// PadelClub
|
|
//
|
|
// Created by Razmig Sarkissian on 23/03/2024.
|
|
//
|
|
|
|
import SwiftUI
|
|
import LeStorage
|
|
|
|
struct MatchDetailView: View {
|
|
|
|
@EnvironmentObject var dataStore: DataStore
|
|
|
|
@EnvironmentObject var networkMonitor: NetworkMonitor
|
|
@Environment(\.dismiss) var dismiss
|
|
let matchViewStyle: MatchViewStyle
|
|
|
|
@State private var showLiveScore: Bool = false
|
|
@State private var editScore: Bool = false
|
|
@State private var scoreType: ScoreType?
|
|
@State private var shareStat: Bool = false
|
|
@State private var startDateSetup: MatchDateSetup = .now
|
|
@State private var fieldSetup: MatchFieldSetup = .random
|
|
@State private var broadcasted: Bool = false
|
|
@State private var startDate: Date = Date()
|
|
@State private var endDate: Date = Date()
|
|
@State private var isEditing: Bool = false
|
|
@State private var showDetails: Bool = false
|
|
@State private var contactType: ContactType? = nil
|
|
@State private var sentError: ContactManagerError? = nil
|
|
// @State private var showSubscriptionView: Bool = false
|
|
|
|
@State var showSubscriptionView: Bool = false
|
|
@State var showUserCreationView: Bool = false
|
|
|
|
var tournamentStore: TournamentStore {
|
|
return match.tournamentStore
|
|
}
|
|
|
|
var messageSentFailed: Binding<Bool> {
|
|
Binding {
|
|
sentError != nil
|
|
} set: { newValue in
|
|
if newValue == false {
|
|
sentError = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
var match: Match
|
|
|
|
init(match: Match, matchViewStyle: MatchViewStyle = .standardStyle) {
|
|
self.match = match
|
|
self.matchViewStyle = matchViewStyle
|
|
|
|
if match.hasStarted() == false && (match.startDate == nil || match.courtIndex == nil) {
|
|
_isEditing = State(wrappedValue: true)
|
|
}
|
|
|
|
if let startDate = match.startDate {
|
|
_startDateSetup = State(wrappedValue: .customDate)
|
|
_startDate = State(wrappedValue: startDate)
|
|
} else if match.isReady() == false {
|
|
_startDateSetup = State(wrappedValue: .customDate)
|
|
}
|
|
|
|
if let endDate = match.endDate {
|
|
_endDate = State(wrappedValue: endDate)
|
|
}
|
|
|
|
if let courtIndex = match.courtIndex {
|
|
_fieldSetup = State(wrappedValue: .field(courtIndex))
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
List {
|
|
if match.hasWalkoutTeam() == false {
|
|
if match.hasStarted() {
|
|
quickLookHeader
|
|
} else {
|
|
startingOptionView
|
|
}
|
|
}
|
|
|
|
Section {
|
|
MatchSummaryView(match: match, matchViewStyle: .plainStyle)
|
|
} footer: {
|
|
if match.isEmpty() == false {
|
|
HStack {
|
|
FooterButtonView("Détails des joueurs") {
|
|
showDetails = true
|
|
}
|
|
Spacer()
|
|
if let tournament = match.currentTournament() {
|
|
MenuWarningView(tournament: tournament, teams: match.teams(), message: match.matchWarningMessage(), umpireMail: dataStore.user.email, subject: match.matchWarningSubject(), contactType: $contactType)
|
|
.buttonStyle(.borderless)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Section {
|
|
RowButtonView("Saisir les résultats", systemImage: "list.clipboard") {
|
|
self._editScores()
|
|
}
|
|
}
|
|
|
|
let players = self.match.teams().flatMap { $0.players() }
|
|
let unpaid = players.filter({ $0.hasPaid() == false })
|
|
|
|
if unpaid.isEmpty == false {
|
|
Section {
|
|
DisclosureGroup {
|
|
ForEach(unpaid) { player in
|
|
LabeledContent {
|
|
PlayerPayView(player: player)
|
|
.environmentObject(tournamentStore)
|
|
} label: {
|
|
Text(player.playerLabel())
|
|
}
|
|
}
|
|
} label: {
|
|
LabeledContent {
|
|
Text(unpaid.count.formatted() + " / " + players.count.formatted())
|
|
} label: {
|
|
Text("Encaissement manquant")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
menuView
|
|
}
|
|
.sheet(isPresented: $showDetails) {
|
|
MatchTeamDetailView(match: match).tint(.master)
|
|
.environmentObject(tournamentStore)
|
|
}
|
|
.sheet(isPresented: self.$showSubscriptionView, content: {
|
|
NavigationStack {
|
|
SubscriptionView(isPresented: self.$showSubscriptionView, showLackOfPlanMessage: true)
|
|
.environment(\.colorScheme, .light)
|
|
}
|
|
})
|
|
.sheet(isPresented: self.$showUserCreationView, content: {
|
|
NavigationStack {
|
|
LoginView(reason: LoginReason.loginRequiredForFeature) { _ in
|
|
self.showUserCreationView = false
|
|
self._editScores()
|
|
}
|
|
}
|
|
})
|
|
.sheet(item: $scoreType, onDismiss: {
|
|
if match.hasEnded() {
|
|
dismiss()
|
|
}
|
|
}) { scoreType in
|
|
let matchDescriptor = MatchDescriptor(match: match)
|
|
EditScoreView(matchDescriptor: matchDescriptor)
|
|
.tint(.master)
|
|
|
|
// switch scoreType {
|
|
// case .edition:
|
|
// let matchDescriptor = MatchDescriptor(match: match)
|
|
// EditScoreView(matchDescriptor: matchDescriptor)
|
|
// case .live:
|
|
// if let score = match.score {
|
|
// if score.sets.isEmpty {
|
|
// SplashView(score: score)
|
|
// } else {
|
|
// NewLiveScoringView(score: score)
|
|
// }
|
|
// }
|
|
// case .prepare:
|
|
// if match.freeMatchTeams.isEmpty == false {
|
|
// EditFreeMatchView(match: match)
|
|
// } else {
|
|
// PrepareMatchView(match: match)
|
|
// }
|
|
// case .stat:
|
|
// if let score = match.score {
|
|
// MatchStatView()
|
|
// .environmentObject(score)
|
|
// }
|
|
// case .health:
|
|
// HealthKitView(match: match)
|
|
// .presentationDetents([.medium])
|
|
// case .feeling:
|
|
// if let feedbackData = match.feedbackData {
|
|
// FeedbackView(feedbackData: feedbackData)
|
|
// }
|
|
// }
|
|
|
|
}
|
|
.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)
|
|
}
|
|
.toolbar {
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Menu {
|
|
// Button("Créer les scores") {
|
|
// let teamsScores = match.getOrCreateTeamScores()
|
|
// do {
|
|
// try dataStore.teamScores.addOrUpdate(contentOfs: teamsScores)
|
|
// } catch {
|
|
// Logger.error(error)
|
|
// }
|
|
// }
|
|
|
|
Toggle(isOn: .init(get: {
|
|
return match.confirmed
|
|
}, set: { value in
|
|
match.confirmed = value
|
|
save()
|
|
})) {
|
|
Text(match.confirmed ? "Confirmé" : "Non confirmé")
|
|
}
|
|
|
|
Divider()
|
|
|
|
if match.courtIndex != nil {
|
|
Button(role: .destructive) {
|
|
match.removeCourt()
|
|
save()
|
|
} label: {
|
|
Text("Supprimer le terrain")
|
|
}
|
|
}
|
|
Button(role: .destructive) {
|
|
match.startDate = nil
|
|
match.endDate = nil
|
|
match.confirmed = false
|
|
save()
|
|
} label: {
|
|
Text("Supprimer l'horaire")
|
|
}
|
|
|
|
Button(role: .destructive) {
|
|
match.resetScores()
|
|
match.resetMatch()
|
|
match.confirmed = false
|
|
save()
|
|
} label: {
|
|
Text("Supprimer les scores")
|
|
}
|
|
Divider()
|
|
Button(role: .destructive) {
|
|
match.resetTeamScores(outsideOf: [])
|
|
match.resetMatch()
|
|
match.confirmed = false
|
|
save()
|
|
} label: {
|
|
Text("Remise-à-zéro")
|
|
}
|
|
|
|
} label: {
|
|
LabelOptions()
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle(match.matchTitle())
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbarBackground(.visible, for: .navigationBar)
|
|
|
|
}
|
|
|
|
var quickLookHeader: some View {
|
|
Section {
|
|
HStack {
|
|
Menu {
|
|
Button("Non défini") {
|
|
match.removeCourt()
|
|
save()
|
|
}
|
|
if let tournament = match.currentTournament() {
|
|
ForEach(0..<tournament.courtCount, id: \.self) { courtIndex in
|
|
Button(tournament.courtName(atIndex: courtIndex)) {
|
|
match.setCourt(courtIndex)
|
|
save()
|
|
}
|
|
}
|
|
}
|
|
} label: {
|
|
VStack(alignment: .leading) {
|
|
Text("terrain").font(.footnote).foregroundStyle(.secondary)
|
|
if let courtName = match.courtName() {
|
|
Text(courtName)
|
|
.foregroundStyle(Color.master)
|
|
.underline()
|
|
} else {
|
|
Text("Choisir")
|
|
.foregroundStyle(Color.master)
|
|
.underline()
|
|
}
|
|
}
|
|
}
|
|
Spacer()
|
|
MatchDateView(match: match, showPrefix: true)
|
|
}
|
|
.font(.title)
|
|
.buttonStyle(.plain)
|
|
} footer: {
|
|
// if match.hasWalkoutTeam() == false {
|
|
// if let weatherData = match.weatherData {
|
|
// HStack {
|
|
// WeatherView(weatherData: weatherData)
|
|
// }
|
|
// }
|
|
// }
|
|
}
|
|
}
|
|
|
|
enum ScoreType: Int, Identifiable, Hashable {
|
|
var id: Int {
|
|
self.rawValue
|
|
}
|
|
case edition = 0
|
|
case live = 1
|
|
case prepare = 2
|
|
case stat = 3
|
|
case feeling = 4
|
|
case health = 5
|
|
}
|
|
|
|
@ViewBuilder
|
|
var menuView: some View {
|
|
if match.hasStarted() {
|
|
Section {
|
|
editionView
|
|
}
|
|
}
|
|
|
|
NavigationLink {
|
|
EditSharingView(match: match)
|
|
} label: {
|
|
Text("Partage sur les réseaux sociaux")
|
|
}
|
|
|
|
// if let followUpMatch = match.followUpMatch {
|
|
// Section {
|
|
// MatchRowView(match: followUpMatch)
|
|
// } header: {
|
|
// Text("à suivre terrain \(match.fieldIndex)")
|
|
// }
|
|
// }
|
|
}
|
|
|
|
var editionView: some View {
|
|
DisclosureGroup(isExpanded: $isEditing) {
|
|
startingOptionView
|
|
} label: {
|
|
Text("Modifier l'horaire et le terrain")
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
var startingOptionView: some View {
|
|
if match.hasEnded() == false {
|
|
let rotationDuration = match.getDuration()
|
|
Picker(selection: $startDateSetup) {
|
|
if match.isReady() {
|
|
Text("Dans 5 minutes").tag(MatchDateSetup.inMinutes(5))
|
|
Text("Dans 15 minutes").tag(MatchDateSetup.inMinutes(15))
|
|
Text("Tout de suite").tag(MatchDateSetup.now)
|
|
}
|
|
Text("Précédente rotation").tag(MatchDateSetup.inMinutes(-rotationDuration))
|
|
Text("Prochaine rotation").tag(MatchDateSetup.inMinutes(rotationDuration))
|
|
Text("À").tag(MatchDateSetup.customDate)
|
|
} label: {
|
|
Text("Horaire")
|
|
}
|
|
.onChange(of: startDateSetup) {
|
|
switch startDateSetup {
|
|
case .customDate:
|
|
break
|
|
case .now:
|
|
startDate = Date()
|
|
case .inMinutes(let minutes):
|
|
startDate = Date().addingTimeInterval(Double(minutes) * 60)
|
|
}
|
|
}
|
|
}
|
|
|
|
if match.startDate != nil || startDateSetup == .customDate {
|
|
DatePicker(selection: $startDate) {
|
|
Label("Début", systemImage: "calendar").labelStyle(.titleOnly)
|
|
}
|
|
.datePickerStyle(.compact)
|
|
}
|
|
|
|
if match.endDate != nil {
|
|
DatePicker(selection: $endDate) {
|
|
Label("Fin", systemImage: "calendar").labelStyle(.titleOnly)
|
|
}
|
|
.datePickerStyle(.compact)
|
|
}
|
|
|
|
|
|
Picker(selection: $fieldSetup) {
|
|
Text("Au hasard parmi les libres").tag(MatchFieldSetup.random)
|
|
Text("Au hasard").tag(MatchFieldSetup.fullRandom)
|
|
//Text("Premier disponible").tag(MatchFieldSetup.firstAvailable)
|
|
if let club = match.currentTournament()?.club() {
|
|
ForEach(0..<club.courtCount, id: \.self) { courtIndex in
|
|
Text(club.courtName(atIndex: courtIndex)) .tag(MatchFieldSetup.field(courtIndex))
|
|
}
|
|
} else if let tournament = match.currentTournament() {
|
|
ForEach(0..<tournament.courtCount, id: \.self) { courtIndex in
|
|
Text(tournament.courtName(atIndex: courtIndex)) .tag(MatchFieldSetup.field(courtIndex))
|
|
}
|
|
} else {
|
|
ForEach(0..<20, id: \.self) { courtIndex in
|
|
Text(Court.courtIndexedTitle(atIndex: courtIndex)) .tag(MatchFieldSetup.field(courtIndex))
|
|
}
|
|
}
|
|
} label: {
|
|
Text("Terrain")
|
|
}
|
|
|
|
RowButtonView("Valider") {
|
|
match.validateMatch(fromStartDate: startDateSetup == .now ? Date() : startDate, toEndDate: endDate, fieldSetup: fieldSetup)
|
|
|
|
save()
|
|
|
|
isEditing.toggle()
|
|
|
|
if match.hasStarted() == false {
|
|
dismiss()
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func _editScores() {
|
|
if match.isReady() == false && match.teams().count == 2 {
|
|
let teamsScores = match.getOrCreateTeamScores()
|
|
do {
|
|
try tournamentStore.teamScores.addOrUpdate(contentOfs: teamsScores)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
}
|
|
|
|
self._verifyUser {
|
|
self._payTournamentAndExecute {
|
|
self.scoreType = .edition
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func _verifyUser(_ handler: () -> ()) {
|
|
if StoreCenter.main.userId != nil {
|
|
handler()
|
|
} else {
|
|
self.showUserCreationView = true
|
|
}
|
|
}
|
|
|
|
fileprivate func _payTournamentAndExecute(_ handler: () -> ()) {
|
|
guard let tournament = match.currentTournament() else { fatalError("missing tournament") }
|
|
|
|
do {
|
|
try tournament.payIfNecessary()
|
|
handler()
|
|
} catch {
|
|
self.showSubscriptionView = true
|
|
}
|
|
}
|
|
|
|
private func save() {
|
|
if let startDate = match.startDate, let tournament = match.currentTournament() {
|
|
if startDate < tournament.startDate {
|
|
tournament.startDate = startDate
|
|
}
|
|
do {
|
|
try dataStore.tournaments.addOrUpdate(instance: tournament)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
}
|
|
|
|
do {
|
|
try tournamentStore.matches.addOrUpdate(instance: match)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
}
|
|
|
|
//#Preview {
|
|
// MatchDetailView(match: Match.mock(), matchViewStyle: .standardStyle)
|
|
//}
|
|
|