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.
408 lines
14 KiB
408 lines
14 KiB
//
|
|
// CallView.swift
|
|
// PadelClub
|
|
//
|
|
// Created by Razmig Sarkissian on 16/04/2024.
|
|
//
|
|
|
|
import SwiftUI
|
|
import LeStorage
|
|
|
|
struct CallView: View {
|
|
|
|
struct CallStatusView: View {
|
|
let count: Int
|
|
let total: Int
|
|
let startDate: Date?
|
|
var title: String = "convoquées au bon horaire"
|
|
|
|
var body: some View {
|
|
VStack(spacing: 0) {
|
|
HStack {
|
|
if let startDate {
|
|
Text(startDate.formattedAsHourMinute())
|
|
} else {
|
|
Text("Aucun horaire").font(.body)
|
|
}
|
|
Spacer()
|
|
Text("\(count.formatted())/\(total.formatted())")
|
|
}
|
|
.font(.largeTitle)
|
|
HStack {
|
|
if let startDate {
|
|
Text(startDate.formatted(.dateTime.weekday().day(.twoDigits).month().year()))
|
|
}
|
|
Spacer()
|
|
Text(title)
|
|
}
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
}
|
|
|
|
@EnvironmentObject var dataStore: DataStore
|
|
|
|
@EnvironmentObject var networkMonitor: NetworkMonitor
|
|
@Environment(Tournament.self) var tournament: Tournament
|
|
|
|
var teams: [TeamRegistration]
|
|
let callDate: Date
|
|
let matchFormat: MatchFormat
|
|
let roundLabel: String
|
|
let displayContext: SummoningDisplayContext
|
|
|
|
@State private var contactType: ContactType? = nil
|
|
@State private var sentError: ContactManagerError? = nil
|
|
|
|
@State var showSubscriptionView: Bool = false
|
|
@State var showUserCreationView: Bool = false
|
|
|
|
@State var summonParamByMessage: Bool = false
|
|
@State var summonParamReSummon: Bool = false
|
|
|
|
let simpleMode : Bool
|
|
|
|
init(teams: [TeamRegistration], callDate: Date, matchFormat: MatchFormat, roundLabel: String) {
|
|
self.teams = teams
|
|
self.callDate = callDate
|
|
self.matchFormat = matchFormat
|
|
self.roundLabel = roundLabel
|
|
self.simpleMode = false
|
|
self.displayContext = .footer
|
|
}
|
|
|
|
init(teams: [TeamRegistration]) {
|
|
self.teams = teams
|
|
self.callDate = Date()
|
|
self.matchFormat = MatchFormat.nineGames
|
|
self.roundLabel = ""
|
|
self.simpleMode = true
|
|
self.displayContext = .footer
|
|
}
|
|
|
|
init(team: TeamRegistration, displayContext: SummoningDisplayContext) {
|
|
self.teams = [team]
|
|
let expectedSummonDate = team.expectedSummonDate()
|
|
self.displayContext = displayContext
|
|
|
|
if let expectedSummonDate, let initialMatch = team.initialMatch() {
|
|
self.callDate = expectedSummonDate
|
|
self.matchFormat = initialMatch.matchFormat
|
|
self.roundLabel = initialMatch.roundTitle() ?? "tableau"
|
|
self.simpleMode = false
|
|
} else if let expectedSummonDate, let initialGroupStage = team.groupStageObject() {
|
|
self.callDate = expectedSummonDate
|
|
self.matchFormat = initialGroupStage.matchFormat
|
|
self.roundLabel = "poule"
|
|
self.simpleMode = false
|
|
} else {
|
|
self.callDate = Date()
|
|
self.matchFormat = MatchFormat.nineGames
|
|
self.roundLabel = ""
|
|
self.simpleMode = true
|
|
}
|
|
}
|
|
|
|
var tournamentStore: TournamentStore {
|
|
return self.tournament.tournamentStore
|
|
}
|
|
|
|
var messageSentFailed: Binding<Bool> {
|
|
Binding {
|
|
sentError != nil
|
|
} set: { newValue in
|
|
if newValue == false {
|
|
sentError = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
private func _called(_ calledTeams: [TeamRegistration], _ success: Bool) {
|
|
if simpleMode {
|
|
return
|
|
}
|
|
if success {
|
|
calledTeams.forEach { team in
|
|
team.callDate = callDate
|
|
if reSummon {
|
|
team.confirmationDate = nil
|
|
}
|
|
}
|
|
do {
|
|
try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: calledTeams)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
func finalMessage(reSummon: Bool) -> String {
|
|
if simpleMode {
|
|
let signature = dataStore.user.summonsMessageSignature ?? dataStore.user.defaultSignature()
|
|
return "\n\n\n\n" + signature
|
|
}
|
|
|
|
return ContactType.callingMessage(tournament: tournament, startDate: callDate, roundLabel: roundLabel, matchFormat: matchFormat, reSummon: reSummon)
|
|
}
|
|
|
|
var reSummon: Bool {
|
|
if simpleMode {
|
|
return false
|
|
}
|
|
return self.teams.allSatisfy({ $0.called() })
|
|
}
|
|
|
|
var mainWord: String {
|
|
if simpleMode {
|
|
return "Contacter"
|
|
} else {
|
|
return "Convoquer"
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
Group {
|
|
switch displayContext {
|
|
case .footer:
|
|
_footerStyleView()
|
|
case .menu:
|
|
_menuStyleView()
|
|
}
|
|
}
|
|
.alert("Un problème est survenu", isPresented: messageSentFailed) {
|
|
Button("OK") {
|
|
}
|
|
|
|
if case .uncalledTeams(let uncalledTeams) = sentError {
|
|
NavigationLink("Voir les équipes non contactées") {
|
|
TeamsCallingView(teams: uncalledTeams)
|
|
.environment(tournament)
|
|
}
|
|
}
|
|
|
|
} 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:
|
|
let calledTeams = teams.filter { $0.getPhoneNumbers().isEmpty == false }
|
|
let uncalledTeams = teams.filter { $0.getPhoneNumbers().isEmpty }
|
|
|
|
if networkMonitor.connected == false {
|
|
if uncalledTeams.isEmpty == false {
|
|
self.sentError = .uncalledTeams(uncalledTeams)
|
|
} else {
|
|
self.sentError = .messageNotSent
|
|
}
|
|
} else {
|
|
if uncalledTeams.isEmpty == false {
|
|
self.sentError = .uncalledTeams(uncalledTeams)
|
|
}
|
|
self._called(calledTeams, 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:
|
|
let calledTeams = teams.filter { $0.getMail().isEmpty == false }
|
|
let uncalledTeams = teams.filter { $0.getMail().isEmpty }
|
|
|
|
if networkMonitor.connected == false {
|
|
self.contactType = nil
|
|
if uncalledTeams.isEmpty == false {
|
|
self.sentError = .uncalledTeams(uncalledTeams)
|
|
} else {
|
|
self.sentError = .mailNotSent
|
|
}
|
|
} else {
|
|
if uncalledTeams.isEmpty == false {
|
|
self.sentError = .uncalledTeams(uncalledTeams)
|
|
}
|
|
self._called(calledTeams, true)
|
|
}
|
|
@unknown default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.tint(.master)
|
|
}
|
|
.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._payTournamentAndExecute {
|
|
self._summon(byMessage: self.summonParamByMessage,
|
|
reSummon: self.summonParamByMessage)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
private func _footerStyleView() -> some View {
|
|
HStack {
|
|
let callWord : String = (reSummon ? "Reconvoquer" : mainWord)
|
|
if self.teams.count == 1 {
|
|
if simpleMode {
|
|
Text("\(callWord) cette paire par")
|
|
} else {
|
|
if let previousCallDate = teams.first?.callDate, Calendar.current.compare(previousCallDate, to: callDate, toGranularity: .minute) != .orderedSame {
|
|
Text("Reconvoquer \(self.callDate.localizedDate()) par")
|
|
} else {
|
|
Text("\(callWord) cette paire par")
|
|
}
|
|
}
|
|
} else {
|
|
Text("\(callWord) ces \(self.teams.count) paires par")
|
|
}
|
|
|
|
self._summonMenu(byMessage: true)
|
|
Text("ou")
|
|
self._summonMenu(byMessage: false)
|
|
}
|
|
.font(.subheadline)
|
|
.buttonStyle(.borderless)
|
|
}
|
|
|
|
private func _menuStyleView() -> some View {
|
|
Menu {
|
|
self._summonMenu(byMessage: true)
|
|
self._summonMenu(byMessage: false)
|
|
} label: {
|
|
let callWord : String = (reSummon ? "Reconvoquer" : mainWord)
|
|
if self.teams.count == 1 {
|
|
if simpleMode {
|
|
Text("\(callWord) cette paire")
|
|
} else {
|
|
if let previousCallDate = teams.first?.callDate, Calendar.current.compare(previousCallDate, to: callDate, toGranularity: .minute) != .orderedSame {
|
|
Text("Reconvoquer \(self.callDate.localizedDate())")
|
|
} else {
|
|
Text("\(callWord) cette paire")
|
|
}
|
|
}
|
|
} else {
|
|
Text("\(callWord) ces \(self.teams.count) paires")
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@ViewBuilder
|
|
private func _summonMenu(byMessage: Bool) -> some View {
|
|
if self.reSummon {
|
|
Menu {
|
|
Button(mainWord) {
|
|
self._summon(byMessage: byMessage, reSummon: false)
|
|
}
|
|
|
|
Button("Re-convoquer") {
|
|
self._summon(byMessage: byMessage, reSummon: true)
|
|
}
|
|
|
|
} label: {
|
|
Text(byMessage ? "sms" : "mail")
|
|
.underline()
|
|
}
|
|
} else {
|
|
FooterButtonView(byMessage ? "sms" : "mail") {
|
|
self._summon(byMessage: byMessage, reSummon: false)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func _summon(byMessage: Bool, reSummon: Bool) {
|
|
self.summonParamByMessage = byMessage
|
|
self.summonParamReSummon = reSummon
|
|
self._verifyUser {
|
|
self._payTournamentAndExecute {
|
|
if byMessage {
|
|
self._contactByMessage(reSummon: reSummon)
|
|
} else {
|
|
self._contactByMail(reSummon: reSummon)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func _verifyUser(_ handler: () -> ()) {
|
|
if StoreCenter.main.userId != nil {
|
|
handler()
|
|
} else {
|
|
self.showUserCreationView = true
|
|
}
|
|
}
|
|
|
|
fileprivate func _payTournamentAndExecute(_ handler: () -> ()) {
|
|
do {
|
|
try self.tournament.payIfNecessary()
|
|
handler()
|
|
} catch {
|
|
self.showSubscriptionView = true
|
|
}
|
|
}
|
|
|
|
fileprivate func _contactByMessage(reSummon: Bool) {
|
|
self.contactType = .message(date: callDate,
|
|
recipients: teams.flatMap { $0.getPhoneNumbers() },
|
|
body: finalMessage(reSummon: reSummon),
|
|
tournamentBuild: nil)
|
|
}
|
|
|
|
fileprivate func _contactByMail(reSummon: Bool) {
|
|
self.contactType = .mail(date: callDate,
|
|
recipients: tournament.umpireMail(),
|
|
bccRecipients: teams.flatMap { $0.getMail() },
|
|
body: finalMessage(reSummon: reSummon),
|
|
subject: tournament.tournamentTitle(),
|
|
tournamentBuild: nil)
|
|
}
|
|
|
|
private var _networkErrorMessage: String {
|
|
ContactManagerError.getNetworkErrorMessage(sentError: sentError, networkMonitorConnected: networkMonitor.connected)
|
|
}
|
|
|
|
}
|
|
|
|
struct TeamCallView: View {
|
|
@Environment(Tournament.self) var tournament: Tournament
|
|
let team: TeamRegistration
|
|
var action: (() -> Void)?
|
|
|
|
var body: some View {
|
|
NavigationLink {
|
|
CallMenuOptionsView(team: team, action: action)
|
|
.environment(tournament)
|
|
} label: {
|
|
TeamRowView(team: team, displayCallDate: true)
|
|
}
|
|
.buttonStyle(.plain)
|
|
.listRowView(isActive: team.confirmed(), color: .green, hideColorVariation: true)
|
|
}
|
|
}
|
|
|