From 16c750f95011cd34c5311ba546378cf9afb22b2e Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Sun, 28 Apr 2024 12:35:22 +0200 Subject: [PATCH 1/4] add start date to groupstage --- PadelClub.xcodeproj/project.pbxproj | 4 + PadelClub/Data/PlayerRegistration.swift | 3 + PadelClub/Data/Round.swift | 18 +++++ PadelClub/Data/Tournament.swift | 4 +- PadelClub/ViewModel/SeedInterval.swift | 6 ++ .../Views/Calling/CallSettingsView.swift | 7 +- PadelClub/Views/Calling/CallView.swift | 2 +- .../Views/Calling/GroupStageCallingView.swift | 13 ++++ .../Views/Calling/SeedsCallingView.swift | 13 ++++ PadelClub/Views/Calling/SendToAllView.swift | 74 +++++++++++++++++++ .../Components/DateUpdateManagerView.swift | 8 ++ PadelClub/Views/Round/LoserRoundView.swift | 12 ++- PadelClub/Views/Round/RoundView.swift | 19 ++++- 13 files changed, 175 insertions(+), 8 deletions(-) create mode 100644 PadelClub/Views/Calling/SendToAllView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 0cf439a..9f0af5b 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -122,6 +122,7 @@ FF1DF49B2BD8D23900822FA0 /* BarButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DF49A2BD8D23900822FA0 /* BarButtonView.swift */; }; FF2BE4872B85E27400592328 /* LeStorage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C425D4542B6D24E2002A7B48 /* LeStorage.framework */; }; FF2BE4882B85E27400592328 /* LeStorage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C425D4542B6D24E2002A7B48 /* LeStorage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */; }; FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */; }; FF3795662B9399AA004EA093 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3795652B9399AA004EA093 /* Persistence.swift */; }; FF3B60A32BC49BBC008C2E66 /* MatchScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */; }; @@ -422,6 +423,7 @@ FF1DC5582BAB767000FD8220 /* Tips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tips.swift; sourceTree = ""; }; FF1DC55A2BAB80C400FD8220 /* DisplayContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayContext.swift; sourceTree = ""; }; FF1DF49A2BD8D23900822FA0 /* BarButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarButtonView.swift; sourceTree = ""; }; + FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToAllView.swift; sourceTree = ""; }; FF3795612B9396D0004EA093 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; FF3795652B9399AA004EA093 /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchScheduler.swift; sourceTree = ""; }; @@ -1084,6 +1086,7 @@ FF9268022BCE94A30080F940 /* GroupStageCallingView.swift */, FF9268082BCEDC2C0080F940 /* CallView.swift */, FF1162792BCF8109000C4809 /* CallMessageCustomizationView.swift */, + FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */, ); path = Calling; sourceTree = ""; @@ -1467,6 +1470,7 @@ C4A47DA92B85F82100ADC637 /* ChangePasswordView.swift in Sources */, FFC83D512BB8087E00750834 /* RoundView.swift in Sources */, FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */, + FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */, FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */, FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */, FFF964552BC266CF00EEF017 /* SchedulerView.swift in Sources */, diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index 5f06bf3..a3db02b 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -305,6 +305,7 @@ class PlayerRegistration: ModelObject, Storable { case bankTransfer = 5 case clubHouse = 6 case creditCard = 7 + case forfeit = 8 func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch self { @@ -322,6 +323,8 @@ class PlayerRegistration: ModelObject, Storable { return "Clubhouse" case .creditCard: return "CB" + case .forfeit: + return "Forfait" case .gift: return "Offert" } diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index 10b5b0d..2038e8f 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -122,6 +122,10 @@ class Round: ModelObject, Storable { _matches().compactMap { $0.losingTeamId }.compactMap { Store.main.findById($0) } } + func teams() -> [TeamRegistration] { + playedMatches().flatMap({ $0.teams() }) + } + func roundProjectedTeam(_ team: TeamPosition, inMatch match: Match) -> TeamRegistration? { if isLoserBracket() == false, let seed = seed(team, inMatchIndex: match.index) { return seed @@ -199,6 +203,16 @@ class Round: ModelObject, Storable { Store.main.filter { $0.round == self.id && $0.disabled == false } } + func displayableMatches() -> [Match] { + if index == 0 && isUpperBracket() { + var matches : [Match?] = [playedMatches().first] + matches.append(loserRounds().first?.playedMatches().first) + return matches.compactMap({ $0 }) + } else { + return playedMatches() + } + } + func playedMatches() -> [Match] { if parent == nil { enabledMatches() @@ -370,6 +384,10 @@ class Round: ModelObject, Storable { return loserRounds + loserRounds.flatMap({ $0.loserRoundsAndChildren() }) } + func isUpperBracket() -> Bool { + parent == nil + } + func isLoserBracket() -> Bool { parent != nil } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 3ea6e3b..5506196 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -562,13 +562,13 @@ class Tournament : ModelObject, Storable { } func isStartDateIsDifferentThanCallDate(_ team: TeamRegistration) -> Bool { - guard let callDate = team.callDate else { return false } + guard let callDate = team.callDate else { return true } if let groupStageStartDate = team.groupStageObject()?.startDate { return Calendar.current.compare(callDate, to: groupStageStartDate, toGranularity: .minute) != ComparisonResult.orderedSame } else if let roundMatchStartDate = team.initialMatch()?.startDate { return Calendar.current.compare(callDate, to: roundMatchStartDate, toGranularity: .minute) != ComparisonResult.orderedSame } - return false + return true } func availableToStart(_ allMatches: [Match]) -> [Match] { diff --git a/PadelClub/ViewModel/SeedInterval.swift b/PadelClub/ViewModel/SeedInterval.swift index a4d1af6..93e778e 100644 --- a/PadelClub/ViewModel/SeedInterval.swift +++ b/PadelClub/ViewModel/SeedInterval.swift @@ -12,6 +12,12 @@ struct SeedInterval: Hashable, Comparable { let last: Int var reduce: Int = 0 + func pointsRange(tournamentLevel: TournamentLevel, teamsCount: Int) -> String { + let range = [tournamentLevel.points(for: last - 1 - reduce, count: teamsCount), + tournamentLevel.points(for: first - 1 - reduce, count: teamsCount)] + return range.map { $0.formatted(.number.sign(strategy: .always())) }.joined(separator: " / ") + " pts" + } + static func <(lhs: SeedInterval, rhs: SeedInterval) -> Bool { return lhs.first < rhs.first } diff --git a/PadelClub/Views/Calling/CallSettingsView.swift b/PadelClub/Views/Calling/CallSettingsView.swift index b718ef6..2811cd9 100644 --- a/PadelClub/Views/Calling/CallSettingsView.swift +++ b/PadelClub/Views/Calling/CallSettingsView.swift @@ -10,6 +10,7 @@ import SwiftUI struct CallSettingsView: View { @EnvironmentObject var dataStore: DataStore @Environment(Tournament.self) var tournament: Tournament + @State private var showSendToAllView: Bool = false var body: some View { List { @@ -24,7 +25,7 @@ struct CallSettingsView: View { Section { RowButtonView("Envoyer un message à tout le monde") { - + showSendToAllView = true } } @@ -48,6 +49,10 @@ struct CallSettingsView: View { } } } + .sheet(isPresented: $showSendToAllView) { + SendToAllView() + .tint(.master) + } } } diff --git a/PadelClub/Views/Calling/CallView.swift b/PadelClub/Views/Calling/CallView.swift index 1126f31..2b85926 100644 --- a/PadelClub/Views/Calling/CallView.swift +++ b/PadelClub/Views/Calling/CallView.swift @@ -20,7 +20,7 @@ struct CallView: View { if let startDate { Text(startDate.formattedAsHourMinute()) } else { - Text("Aucun horaire") + Text("Aucun horaire").font(.body) } Spacer() Text(count.formatted() + "/" + total.formatted()) diff --git a/PadelClub/Views/Calling/GroupStageCallingView.swift b/PadelClub/Views/Calling/GroupStageCallingView.swift index 1f8de11..6e74ce4 100644 --- a/PadelClub/Views/Calling/GroupStageCallingView.swift +++ b/PadelClub/Views/Calling/GroupStageCallingView.swift @@ -83,6 +83,19 @@ struct GroupStageCallingView: View { } } + .overlay { + if groupStage.startDate == nil { + ContentUnavailableView { + Label("Aucun horaire défini", systemImage: "clock.badge.questionmark") + } description: { + Text("Vous n'avez pas encore défini d'horaire pour les différentes phases du tournoi") + } actions: { +// RowButtonView("Horaire intelligent") { +// selectedScheduleDestination = nil +// } + } + } + } .headerProminence(.increased) .navigationTitle(groupStage.groupStageTitle()) .navigationBarTitleDisplayMode(.inline) diff --git a/PadelClub/Views/Calling/SeedsCallingView.swift b/PadelClub/Views/Calling/SeedsCallingView.swift index d3b0400..4ce94e5 100644 --- a/PadelClub/Views/Calling/SeedsCallingView.swift +++ b/PadelClub/Views/Calling/SeedsCallingView.swift @@ -85,6 +85,19 @@ struct SeedsCallingView: View { } } } + .overlay { + if (keys.isEmpty && displayByMatch == false) || (displayByMatch && roundMatches.isEmpty) { + ContentUnavailableView { + Label("Aucun horaire défini", systemImage: "clock.badge.questionmark") + } description: { + Text("Vous n'avez pas encore défini d'horaire pour les différentes phases du tournoi") + } actions: { +// RowButtonView("Horaire intelligent") { +// selectedScheduleDestination = nil +// } + } + } + } .headerProminence(.increased) .navigationTitle(round.roundTitle()) .navigationBarTitleDisplayMode(.inline) diff --git a/PadelClub/Views/Calling/SendToAllView.swift b/PadelClub/Views/Calling/SendToAllView.swift new file mode 100644 index 0000000..6dc1b05 --- /dev/null +++ b/PadelClub/Views/Calling/SendToAllView.swift @@ -0,0 +1,74 @@ +// +// SendToAllView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 28/04/2024. +// + +import SwiftUI +import LeStorage + +struct SendToAllView: View { + @Environment(Tournament.self) var tournament: Tournament + @State private var contactMethod: Int = 1 + @State private var contactRecipients: Set = Set() + + + var body: some View { + NavigationStack { + List(selection: $contactRecipients) { + Section { + Picker(selection: $contactMethod) { + Text("par sms").tag(0) + Text("par mail").tag(1) + } label: { + Text("méthode") + } + .labelsHidden() + .pickerStyle(.inline) + + } + + Section { + ForEach(tournament.rounds()) { round in + LabeledContent { + let seeds = round.seeds() + Text(round.seeds().count + " tête" + seeds.count.pluralSuffix + " de série") + } label: { + Text(round.roundTitle()) + } + .tag(round.id) + } + } + + Section { + RowButtonView("Contacter \(totalString)") { + + } + } + } + .environment(\.editMode, Binding.constant(EditMode.active)) + .headerProminence(.increased) + .navigationTitle("Réglages") + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + } + } + + func _teams() -> [TeamRegistration] { + let rounds : [Round] = contactRecipients.map { Store.main.findById($0) } + return rounds.flatMap({ $0.teams() }) + } + + func _totalString() -> String { + if contactRecipients.isEmpty { + return "toutes les équipes" + } else { + return + } + } +} + +#Preview { + SendToAllView() +} diff --git a/PadelClub/Views/Planning/Components/DateUpdateManagerView.swift b/PadelClub/Views/Planning/Components/DateUpdateManagerView.swift index 51e8c29..45253be 100644 --- a/PadelClub/Views/Planning/Components/DateUpdateManagerView.swift +++ b/PadelClub/Views/Planning/Components/DateUpdateManagerView.swift @@ -41,6 +41,14 @@ struct DatePickingView: View { } else { HStack { Menu { + Button("de 30 minutes") { + startDate = startDate.addingTimeInterval(1800) + } + + Button("d'une heure") { + startDate = startDate.addingTimeInterval(3600) + } + Button("à 9h") { startDate = startDate.atNine() } diff --git a/PadelClub/Views/Round/LoserRoundView.swift b/PadelClub/Views/Round/LoserRoundView.swift index e050d79..1ed9c82 100644 --- a/PadelClub/Views/Round/LoserRoundView.swift +++ b/PadelClub/Views/Round/LoserRoundView.swift @@ -9,6 +9,8 @@ import SwiftUI struct LoserRoundView: View { @EnvironmentObject var dataStore: DataStore + @Environment(Tournament.self) var tournament: Tournament + let loserRounds: [Round] @State private var isEditingTournamentSeed: Bool = false @@ -39,7 +41,15 @@ struct LoserRoundView: View { .disabled(match.disabled) } } header: { - Text(loserRound.roundTitle(.wide)) + HStack { + Text(loserRound.roundTitle(.wide)) + let tournamentTeamCount = tournament.teamCount + if let seedIntervalPointRange = loserRound.seedInterval()?.pointsRange(tournamentLevel: tournament.tournamentLevel, teamsCount: tournamentTeamCount) { + Spacer() + Text(seedIntervalPointRange) + .font(.caption) + } + } } } } diff --git a/PadelClub/Views/Round/RoundView.swift b/PadelClub/Views/Round/RoundView.swift index 52186b3..f00ea1f 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -17,8 +17,9 @@ struct RoundView: View { var body: some View { List { + let loserRounds = round.loserRounds() + if isEditingTournamentSeed.wrappedValue == false { - let loserRounds = round.loserRounds() //(where: { $0.isDisabled() == false || isEditingTournamentSeed.wrappedValue }) if loserRounds.isEmpty == false, let first = loserRounds.first { let correspondingLoserRoundTitle = round.correspondingLoserRoundTitle() @@ -61,11 +62,23 @@ struct RoundView: View { } } - ForEach(round.playedMatches()) { match in + ForEach(round.displayableMatches()) { match in Section { MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle) } header: { - Text(round.roundTitle(.wide) + " " + match.matchTitle(.short)) + HStack { + Text(round.roundTitle(.wide)) + if round.index > 0 { + Text(match.matchTitle(.short)) + } else { + let tournamentTeamCount = tournament.teamCount + if let seedIntervalPointRange = loserRounds.first?.seedInterval()?.pointsRange(tournamentLevel: tournament.tournamentLevel, teamsCount: tournamentTeamCount) { + Spacer() + Text(seedIntervalPointRange) + .font(.caption) + } + } + } } } } From 78ae6071ec78513927f07949b6989952b021f4b7 Mon Sep 17 00:00:00 2001 From: Raz Date: Sun, 28 Apr 2024 13:18:07 +0200 Subject: [PATCH 2/4] contact all --- PadelClub/Views/Calling/SendToAllView.swift | 43 +++++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/PadelClub/Views/Calling/SendToAllView.swift b/PadelClub/Views/Calling/SendToAllView.swift index 6dc1b05..cc9f9b8 100644 --- a/PadelClub/Views/Calling/SendToAllView.swift +++ b/PadelClub/Views/Calling/SendToAllView.swift @@ -19,30 +19,43 @@ struct SendToAllView: View { List(selection: $contactRecipients) { Section { Picker(selection: $contactMethod) { - Text("par sms").tag(0) - Text("par mail").tag(1) + Text("Contacter par sms").tag(0) + Text("Contacter par mail").tag(1) } label: { Text("méthode") } .labelsHidden() .pickerStyle(.inline) - } Section { + ForEach(tournament.groupStages()) { groupStage in + let teams = groupStage.teams() + if teams.isEmpty == false { + LabeledContent { + Text(teams.count.formatted() + " équipe" + teams.count.pluralSuffix) + } label: { + Text(groupStage.groupStageTitle()) + } + .tag(groupStage.id) + } + + } ForEach(tournament.rounds()) { round in - LabeledContent { - let seeds = round.seeds() - Text(round.seeds().count + " tête" + seeds.count.pluralSuffix + " de série") - } label: { - Text(round.roundTitle()) + let teams = round.teams() + if teams.isEmpty == false { + LabeledContent { + Text(teams.count.formatted() + " équipe" + teams.count.pluralSuffix) + } label: { + Text(round.roundTitle()) + } + .tag(round.id) } - .tag(round.id) } } Section { - RowButtonView("Contacter \(totalString)") { + RowButtonView("Contacter \(_totalString())") { } } @@ -56,15 +69,21 @@ struct SendToAllView: View { } func _teams() -> [TeamRegistration] { - let rounds : [Round] = contactRecipients.map { Store.main.findById($0) } + let rounds : [Round] = contactRecipients.compactMap { Store.main.findById($0) } return rounds.flatMap({ $0.teams() }) } + + func _groupStagesTeams() -> [TeamRegistration] { + let groupStages : [GroupStage] = contactRecipients.compactMap { Store.main.findById($0) } + return groupStages.flatMap({ $0.teams() }) + } func _totalString() -> String { if contactRecipients.isEmpty { return "toutes les équipes" } else { - return + let teams = _teams() + _groupStagesTeams() + return teams.count.formatted() + " équipe" + teams.count.pluralSuffix } } } From 44d88770b571a2b8bac98db5ad6768f1a9c0d524 Mon Sep 17 00:00:00 2001 From: Raz Date: Mon, 29 Apr 2024 08:26:26 +0200 Subject: [PATCH 3/4] wip --- PadelClub.xcodeproj/project.pbxproj | 12 +++ .../Calling/Components/MenuWarningView.swift | 80 +++++++++++++++++++ PadelClub/Views/Calling/SendToAllView.swift | 1 - PadelClub/Views/Match/MatchDetailView.swift | 12 +-- 4 files changed, 95 insertions(+), 10 deletions(-) create mode 100644 PadelClub/Views/Calling/Components/MenuWarningView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 9f0af5b..a52daa1 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -243,6 +243,7 @@ FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */; }; FFDB1C732BB2CFE900F1E467 /* MySortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */; }; FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */; }; + FFEF7F4E2BDE69130033D0F0 /* MenuWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */; }; FFF03C942BD91D0C00B516FC /* ButtonValidateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF03C932BD91D0C00B516FC /* ButtonValidateView.swift */; }; FFF116E12BD2A9B600A33B06 /* DateInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF116E02BD2A9B600A33B06 /* DateInterval.swift */; }; FFF116E32BD2AF4800A33B06 /* CourtAvailabilitySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF116E22BD2AF4800A33B06 /* CourtAvailabilitySettingsView.swift */; }; @@ -545,6 +546,7 @@ FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MySortDescriptor.swift; sourceTree = ""; }; FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredViewModifier.swift; sourceTree = ""; }; + FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuWarningView.swift; sourceTree = ""; }; FFF03C932BD91D0C00B516FC /* ButtonValidateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonValidateView.swift; sourceTree = ""; }; FFF116E02BD2A9B600A33B06 /* DateInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateInterval.swift; sourceTree = ""; }; FFF116E22BD2AF4800A33B06 /* CourtAvailabilitySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourtAvailabilitySettingsView.swift; sourceTree = ""; }; @@ -1087,6 +1089,7 @@ FF9268082BCEDC2C0080F940 /* CallView.swift */, FF1162792BCF8109000C4809 /* CallMessageCustomizationView.swift */, FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */, + FFEF7F4C2BDE68F80033D0F0 /* Components */, ); path = Calling; sourceTree = ""; @@ -1172,6 +1175,14 @@ path = ViewModifiers; sourceTree = ""; }; + FFEF7F4C2BDE68F80033D0F0 /* Components */ = { + isa = PBXGroup; + children = ( + FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */, + ); + path = Components; + sourceTree = ""; + }; FFF8ACD02B9238A2008466FA /* Manager */ = { isa = PBXGroup; children = ( @@ -1564,6 +1575,7 @@ FF11628A2BD05247000C4809 /* DateUpdateManagerView.swift in Sources */, FFCFC01A2BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift in Sources */, FF025AE92BD1307F00A86CF8 /* MonthData.swift in Sources */, + FFEF7F4E2BDE69130033D0F0 /* MenuWarningView.swift in Sources */, FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */, FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */, FF1DC55B2BAB80C400FD8220 /* DisplayContext.swift in Sources */, diff --git a/PadelClub/Views/Calling/Components/MenuWarningView.swift b/PadelClub/Views/Calling/Components/MenuWarningView.swift new file mode 100644 index 0000000..e8e5218 --- /dev/null +++ b/PadelClub/Views/Calling/Components/MenuWarningView.swift @@ -0,0 +1,80 @@ +// +// MenuWarningView.swift +// PadelClub +// +// Created by razmig on 28/04/2024. +// + +import SwiftUI + +struct MenuWarningView: View { + let teams: [TeamRegistration] + var date: Date? + var message: String? + var umpireMail: String? + var subject: String? + + @State private var contactType: ContactType? + + func getUmpireMail() -> [String]? { + if let umpireMail { + return [umpireMail] + } + return nil + } + + @ViewBuilder + private func _actionView(players: [PlayerRegistration], privateMode: Bool = false) -> some View { + Button("Message") { + contactType = .message(date: date, recipients: players.compactMap({ $0.phoneNumber }), body: message, tournamentBuild: nil) + } + Button("Mail") { + contactType = .mail(date: date, recipients: privateMode ? getUmpireMail() : players.compactMap({ $0.email }), bccRecipients: privateMode ? players.compactMap({ $0.email }) : nil, body: message, subject: subject, tournamentBuild: nil) + } + } + + var body: some View { + + Menu { + Menu("Tout le monde") { + let players = teams.flatMap({ $0.players() }) + _actionView(players: players, privateMode: true) + } + Divider() + ForEach(teams) { team in + _teamView(team) + } + } label: { + Text("Prévenir") + .underline() + } + } + + func _playerView(_ player: PlayerRegistration) -> some View { + Menu { + let players = [player] + _actionView(players: players) + } label: { + Text(player.playerLabel(.short)) + } + } + + func _teamView(_ team: TeamRegistration) -> some View { + Menu { + Menu("Toute l'équipe") { + let players = team.players() + _actionView(players: players) + } + Divider() + ForEach(team.players()) { player in + _playerView(player) + } + } label: { + Text(team.teamLabel(.short)) + } + } +} + +#Preview { + MenuWarningView(teams: []) +} diff --git a/PadelClub/Views/Calling/SendToAllView.swift b/PadelClub/Views/Calling/SendToAllView.swift index cc9f9b8..c577015 100644 --- a/PadelClub/Views/Calling/SendToAllView.swift +++ b/PadelClub/Views/Calling/SendToAllView.swift @@ -39,7 +39,6 @@ struct SendToAllView: View { } .tag(groupStage.id) } - } ForEach(tournament.rounds()) { round in let teams = round.teams() diff --git a/PadelClub/Views/Match/MatchDetailView.swift b/PadelClub/Views/Match/MatchDetailView.swift index 4285752..0e59ae9 100644 --- a/PadelClub/Views/Match/MatchDetailView.swift +++ b/PadelClub/Views/Match/MatchDetailView.swift @@ -110,18 +110,12 @@ struct MatchDetailView: View { } footer: { if match.isEmpty() == false { HStack { - Button { + FooterButtonView("Détails des joueurs") { showDetails = true - } label: { - Text("Détails des joueurs") } Spacer() - Menu { - //MenuWarnView(warningSender: match) - } label: { - Text("Prévenir") - } - .buttonStyle(.borderless) + MenuWarningView(teams: match.teams()) + .buttonStyle(.borderless) } } } From 1ee399aa2e12240b306e66bad0e1a3aafbeb520b Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Mon, 29 Apr 2024 10:14:36 +0200 Subject: [PATCH 4/4] fix warning button action and contact all feature --- PadelClub/Data/Match.swift | 8 ++ .../Calling/Components/MenuWarningView.swift | 10 +-- PadelClub/Views/Calling/SendToAllView.swift | 81 ++++++++++++++++++- PadelClub/Views/Match/MatchDetailView.swift | 71 +++++++++++++++- 4 files changed, 161 insertions(+), 9 deletions(-) diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index 05e2ed6..cf19e34 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -55,6 +55,14 @@ class Match: ModelObject, Storable { return RoundRule.matchIndexWithinRound(fromMatchIndex: index) } + func matchWarningSubject() -> String { + [roundTitle(), matchTitle()].compacted().joined(separator: " ") + } + + func matchWarningMessage() -> String { + [roundTitle(), matchTitle(), startDate?.localizedDate(), courtName()].compacted().joined(separator: "\n") + } + func matchTitle(_ displayStyle: DisplayStyle = .wide) -> String { if let groupStageObject { return groupStageObject.localizedMatchUpLabel(for: index) diff --git a/PadelClub/Views/Calling/Components/MenuWarningView.swift b/PadelClub/Views/Calling/Components/MenuWarningView.swift index e8e5218..31c74ed 100644 --- a/PadelClub/Views/Calling/Components/MenuWarningView.swift +++ b/PadelClub/Views/Calling/Components/MenuWarningView.swift @@ -14,9 +14,9 @@ struct MenuWarningView: View { var umpireMail: String? var subject: String? - @State private var contactType: ContactType? - - func getUmpireMail() -> [String]? { + @Binding var contactType: ContactType? + + private func _getUmpireMail() -> [String]? { if let umpireMail { return [umpireMail] } @@ -29,7 +29,7 @@ struct MenuWarningView: View { contactType = .message(date: date, recipients: players.compactMap({ $0.phoneNumber }), body: message, tournamentBuild: nil) } Button("Mail") { - contactType = .mail(date: date, recipients: privateMode ? getUmpireMail() : players.compactMap({ $0.email }), bccRecipients: privateMode ? players.compactMap({ $0.email }) : nil, body: message, subject: subject, tournamentBuild: nil) + contactType = .mail(date: date, recipients: privateMode ? _getUmpireMail() : players.compactMap({ $0.email }), bccRecipients: privateMode ? players.compactMap({ $0.email }) : nil, body: message, subject: subject, tournamentBuild: nil) } } @@ -76,5 +76,5 @@ struct MenuWarningView: View { } #Preview { - MenuWarningView(teams: []) + MenuWarningView(teams: [], contactType: .constant(nil)) } diff --git a/PadelClub/Views/Calling/SendToAllView.swift b/PadelClub/Views/Calling/SendToAllView.swift index c577015..891cbf5 100644 --- a/PadelClub/Views/Calling/SendToAllView.swift +++ b/PadelClub/Views/Calling/SendToAllView.swift @@ -10,10 +10,23 @@ import LeStorage struct SendToAllView: View { @Environment(Tournament.self) var tournament: Tournament + @EnvironmentObject var networkMonitor: NetworkMonitor + + @State private var contactType: ContactType? = nil @State private var contactMethod: Int = 1 @State private var contactRecipients: Set = Set() + @State private var sentError: ContactManagerError? = nil + + var messageSentFailed: Binding { + Binding { + sentError != nil + } set: { newValue in + if newValue == false { + sentError = nil + } + } + } - var body: some View { NavigationStack { List(selection: $contactRecipients) { @@ -55,7 +68,11 @@ struct SendToAllView: View { Section { RowButtonView("Contacter \(_totalString())") { - + if contactMethod == 0 { + contactType = .message(date: nil, recipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.phoneNumber }, body: tournament.tournamentTitle(), tournamentBuild: nil) + } else { + contactType = .mail(date: nil, recipients: nil, bccRecipients: _teams().flatMap { $0.unsortedPlayers() }.compactMap { $0.email }, body: nil, subject: tournament.tournamentTitle(), tournamentBuild: nil) + } } } } @@ -64,10 +81,68 @@ struct SendToAllView: View { .navigationTitle("Réglages") .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.visible, for: .navigationBar) + .alert("Un problème est survenu", isPresented: messageSentFailed) { + Button("OK") { + } + } message: { + let message = [networkMonitor.connected == false ? "L'appareil n'est pas connecté à internet." as String? : nil, sentError == .mailNotSent ? "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." as String? : nil, (sentError == .messageFailed || sentError == .messageNotSent) ? "Le SMS n'a pas été envoyé" as String? : nil, sentError == .mailFailed ? "Le mail n'a pas été envoyé" as String? : nil].compacted().joined(separator: "\n") + Text(message) + } + .sheet(item: $contactType) { contactType in + Group { + switch contactType { + case .message(_, let recipients, let body, _): + if Guard.main.paymentForNewTournament() != nil { + 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 + } + } + } else { + SubscriptionView(showLackOfPlanMessage: true) + } + case .mail(_, let recipients, let bccRecipients, let body, let subject, _): + if Guard.main.paymentForNewTournament() != nil { + 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 + } + } + } else { + SubscriptionView(showLackOfPlanMessage: true) + } + } + } + .tint(.master) + } } } func _teams() -> [TeamRegistration] { + _roundTeams() + _groupStagesTeams() + } + + func _roundTeams() -> [TeamRegistration] { let rounds : [Round] = contactRecipients.compactMap { Store.main.findById($0) } return rounds.flatMap({ $0.teams() }) } @@ -81,7 +156,7 @@ struct SendToAllView: View { if contactRecipients.isEmpty { return "toutes les équipes" } else { - let teams = _teams() + _groupStagesTeams() + let teams = _teams() return teams.count.formatted() + " équipe" + teams.count.pluralSuffix } } diff --git a/PadelClub/Views/Match/MatchDetailView.swift b/PadelClub/Views/Match/MatchDetailView.swift index 0e59ae9..9815eb1 100644 --- a/PadelClub/Views/Match/MatchDetailView.swift +++ b/PadelClub/Views/Match/MatchDetailView.swift @@ -9,6 +9,7 @@ import SwiftUI struct MatchDetailView: View { @EnvironmentObject var dataStore: DataStore + @EnvironmentObject var networkMonitor: NetworkMonitor @Environment(\.dismiss) var dismiss let matchViewStyle: MatchViewStyle @@ -23,6 +24,19 @@ struct MatchDetailView: View { @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 + + var messageSentFailed: Binding { + Binding { + sentError != nil + } set: { newValue in + if newValue == false { + sentError = nil + } + } + } + var match: Match init(match: Match, matchViewStyle: MatchViewStyle = .standardStyle) { @@ -114,7 +128,7 @@ struct MatchDetailView: View { showDetails = true } Spacer() - MenuWarningView(teams: match.teams()) + MenuWarningView(teams: match.teams(), message: match.matchWarningMessage(), umpireMail: dataStore.user?.email, subject: match.matchWarningSubject(), contactType: $contactType) .buttonStyle(.borderless) } } @@ -191,6 +205,60 @@ struct MatchDetailView: View { // } } + .alert("Un problème est survenu", isPresented: messageSentFailed) { + Button("OK") { + } + } message: { + let message = [networkMonitor.connected == false ? "L'appareil n'est pas connecté à internet." as String? : nil, sentError == .mailNotSent ? "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." as String? : nil, (sentError == .messageFailed || sentError == .messageNotSent) ? "Le SMS n'a pas été envoyé" as String? : nil, sentError == .mailFailed ? "Le mail n'a pas été envoyé" as String? : nil].compacted().joined(separator: "\n") + Text(message) + } + .sheet(item: $contactType) { contactType in + Group { + switch contactType { + case .message(_, let recipients, let body, _): + if Guard.main.paymentForNewTournament() != nil { + 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 + } + } + } else { + SubscriptionView(showLackOfPlanMessage: true) + } + case .mail(_, let recipients, let bccRecipients, let body, let subject, _): + if Guard.main.paymentForNewTournament() != nil { + 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 + } + } + } else { + SubscriptionView(showLackOfPlanMessage: true) + } + } + } + .tint(.master) + } // .refreshable { // if match.isBroadcasted() { @@ -258,6 +326,7 @@ struct MatchDetailView: View { .navigationTitle(match.matchTitle()) .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.visible, for: .navigationBar) + } enum ScoreType: Int, Identifiable, Hashable {