diff --git a/PadelClub/Views/Calling/CallView.swift b/PadelClub/Views/Calling/CallView.swift index 167dcfb..8df33dc 100644 --- a/PadelClub/Views/Calling/CallView.swift +++ b/PadelClub/Views/Calling/CallView.swift @@ -172,10 +172,12 @@ struct CallView: View { }) .sheet(isPresented: self.$showUserCreationView, content: { NavigationStack { - LoginView { _ in + LoginView(reason: LoginReason.loginRequiredForFeature) { _ in self.showUserCreationView = false - self._summon(byMessage: self.summonParamByMessage, - reSummon: self.summonParamByMessage) + self._payTournamentAndExecute { + self._summon(byMessage: self.summonParamByMessage, + reSummon: self.summonParamByMessage) + } } } }) diff --git a/PadelClub/Views/Calling/Components/MenuWarningView.swift b/PadelClub/Views/Calling/Components/MenuWarningView.swift index 3451ac7..fb16e2a 100644 --- a/PadelClub/Views/Calling/Components/MenuWarningView.swift +++ b/PadelClub/Views/Calling/Components/MenuWarningView.swift @@ -6,8 +6,10 @@ // import SwiftUI +import LeStorage struct MenuWarningView: View { + let tournament: Tournament let teams: [TeamRegistration] var date: Date? var message: String? @@ -16,6 +18,11 @@ struct MenuWarningView: View { @Binding var contactType: ContactType? + @State var showSubscriptionView: Bool = false + @State var showUserCreationView: Bool = false + + @State var savedContactType: ContactType? = nil + private func _getUmpireMail() -> [String]? { if let umpireMail { return [umpireMail] @@ -23,7 +30,40 @@ struct MenuWarningView: View { return nil } - // TODO: Guard + var body: some View { + + Menu { + if let team = teams.first, teams.count == 1 { + _teamActionView(team) + } else { + 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() + } + .sheet(isPresented: self.$showSubscriptionView, content: { + NavigationStack { + SubscriptionView(isPresented: self.$showSubscriptionView, showLackOfPlanMessage: true) + } + }) + .sheet(isPresented: self.$showUserCreationView, content: { + NavigationStack { + LoginView(reason: LoginReason.loginRequiredForFeature) { _ in + self.showUserCreationView = false + self._tryToContact() + } + } + }) + } + @ViewBuilder private func _actionView(players: [PlayerRegistration], privateMode: Bool = false) -> some View { if players.count == 1, let player = players.first, let number = player.phoneNumber?.replacingOccurrences(of: " ", with: ""), let url = URL(string: "tel:\(number)") { @@ -47,32 +87,21 @@ struct MenuWarningView: View { } Button("Message") { - contactType = .message(date: date, recipients: players.compactMap({ $0.phoneNumber }), body: message, tournamentBuild: nil) + self._contactByMessage(players: players, privateMode: privateMode) } 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) + self._contactByMail(players: players, privateMode: privateMode) } } - var body: some View { - - Menu { - if let team = teams.first, teams.count == 1 { - _teamActionView(team) - } else { - 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() - } + fileprivate func _contactByMessage(players: [PlayerRegistration], privateMode: Bool) { + self.savedContactType = .message(date: date, recipients: players.compactMap({ $0.phoneNumber }), body: message, tournamentBuild: nil) + self._tryToContact() + } + + fileprivate func _contactByMail(players: [PlayerRegistration], privateMode: Bool) { + self.savedContactType = .mail(date: date, recipients: privateMode ? _getUmpireMail() : players.compactMap({ $0.email }), bccRecipients: privateMode ? players.compactMap({ $0.email }) : nil, body: message, subject: subject, tournamentBuild: nil) + self._tryToContact() } func _playerView(_ player: PlayerRegistration) -> some View { @@ -103,6 +132,32 @@ struct MenuWarningView: View { _playerView(player) } } + + fileprivate func _tryToContact() { + self._verifyUser { + self._payTournamentAndExecute { + self.contactType = self.savedContactType + } + } + } + + fileprivate func _verifyUser(_ handler: () -> ()) { + if Store.main.userId != nil { + handler() + } else { + self.showUserCreationView = true + } + } + + fileprivate func _payTournamentAndExecute(_ handler: () -> ()) { + do { + try tournament.payIfNecessary() + handler() + } catch { + self.showSubscriptionView = true + } + } + } //#Preview { diff --git a/PadelClub/Views/Match/MatchDetailView.swift b/PadelClub/Views/Match/MatchDetailView.swift index 79e1cc7..1107f1b 100644 --- a/PadelClub/Views/Match/MatchDetailView.swift +++ b/PadelClub/Views/Match/MatchDetailView.swift @@ -27,8 +27,11 @@ struct MatchDetailView: View { @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 private var showSubscriptionView: Bool = false + @State var showSubscriptionView: Bool = false + @State var showUserCreationView: Bool = false + var messageSentFailed: Binding { Binding { sentError != nil @@ -65,52 +68,6 @@ struct MatchDetailView: View { } } - var quickLookHeader: some View { - Section { - HStack { - Menu { - Button("Non défini") { - match.removeCourt() - save() - } - if let tournament = match.currentTournament() { - ForEach(0.. ()) { + if Store.main.userId != nil { + handler() + } else { + self.showUserCreationView = true + } + } + + fileprivate func _payTournamentAndExecute(_ handler: () -> ()) { + guard let tournament = self.match.currentTournament() else { fatalError("missing tournament") } + + do { + try tournament.payIfNecessary() + handler() + } catch { + self.showSubscriptionView = true + } + } + private func save() { do { try dataStore.matches.addOrUpdate(instance: match) diff --git a/PadelClub/Views/Subscription/Guard.swift b/PadelClub/Views/Subscription/Guard.swift index c7fc264..0d1fa2e 100644 --- a/PadelClub/Views/Subscription/Guard.swift +++ b/PadelClub/Views/Subscription/Guard.swift @@ -140,14 +140,14 @@ import LeStorage } var currentPlan: StoreItem? { - return .monthlyUnlimited +// return .monthlyUnlimited // #if DEBUG // return .monthlyUnlimited // #else -// if let currentBestPlan = self.currentBestPlan, let plan = StoreItem(rawValue: currentBestPlan.productID) { -// return plan -// } -// return nil + if let currentBestPlan = self.currentBestPlan, let plan = StoreItem(rawValue: currentBestPlan.productID) { + return plan + } + return nil // #endif } diff --git a/PadelClub/Views/Subscription/SubscriptionView.swift b/PadelClub/Views/Subscription/SubscriptionView.swift index 23f2b67..982dc65 100644 --- a/PadelClub/Views/Subscription/SubscriptionView.swift +++ b/PadelClub/Views/Subscription/SubscriptionView.swift @@ -46,7 +46,7 @@ class SubscriptionModel: ObservableObject, StoreDelegate { @Published var quantity: Int = 1 @Published var products: [Product] = [] @Published var totalPrice: String = "" - + init() { self.load() } @@ -72,7 +72,7 @@ class SubscriptionModel: ObservableObject, StoreDelegate { self.isLoading = false self.error = error } - + func purchase() async throws -> Bool { Logger.log("start purchase...") guard let product: Product = self.selectedProduct, let storeManager = self.storeManager else { @@ -110,7 +110,7 @@ class SubscriptionModel: ObservableObject, StoreDelegate { struct SubscriptionView: View { @ObservedObject var model: SubscriptionModel = SubscriptionModel() - + @Binding var isPresented: Bool var showLackOfPlanMessage: Bool = false @@ -118,7 +118,7 @@ struct SubscriptionView: View { @State var showLoginView: Bool = false @State var isPurchasing: Bool = false @State var showSuccessfulPurchaseView: Bool = false - + init(isPresented: Binding, showLackOfPlanMessage: Bool = false) { self._isPresented = isPresented self.showLackOfPlanMessage = showLackOfPlanMessage @@ -126,7 +126,13 @@ struct SubscriptionView: View { var body: some View { - VStack { + VStack(alignment: .leading) { + Text("Abonnements") + .font(.system(size: 36.0)) + .fontWeight(.bold) + .padding(.horizontal) + .foregroundStyle(.white) + if self.showLoginView { LoginView { _ in self.showLoginView = false @@ -156,7 +162,7 @@ struct SubscriptionView: View { .buttonStyle(.borderedProminent) .tint(.orange) .listRowBackground(Color.clear) - + } footer: { if product.item.isConsumable == false { SubscriptionFooterView() @@ -172,18 +178,27 @@ struct SubscriptionView: View { } .listStyle(.grouped) .scrollContentBackground(.hidden) - .background(.logoBackground) - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button("Restaurer") { - self._restore() - }.isLoading(self.isRestoring) - } - } } } - .preferredColorScheme(.dark) - .navigationTitle("Abonnements") + .background(.logoBackground) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button("Restaurer") { + self._restore() + }.isLoading(self.isRestoring) + } + } + + // .toolbar { + // ToolbarItem(placement: .principal) { + // VStack(spacing: -4.0) { + // Text("Abonnements").font(.headline).foregroundStyle(.white) + // } + // } + // + // }.navigationBarTitleDisplayMode(.inline) + // .preferredColorScheme(.dark) + // .navigationTitle("Abonnements") } @@ -198,7 +213,7 @@ struct SubscriptionView: View { fileprivate func _purchase() { self.isPurchasing = true - + Task { do { let success = try await self.model.purchase() @@ -248,9 +263,9 @@ fileprivate struct ProductsSectionView: View { } } } header: { - Text("Sélectionnez une offre") + Text("Sélectionnez une offre").foregroundStyle(Color(white: 0.8)) } - + } } @@ -295,8 +310,8 @@ struct ProductView: View { @ObservedObject var model: SubscriptionModel -// @Binding var quantity: Int -// var selected: Bool + // @Binding var quantity: Int + // var selected: Bool var body: some View { HStack { @@ -325,7 +340,7 @@ struct ProductView: View { .tint(Color.master) .listRowBackground(Color.clear) } - + fileprivate var _item: StoreItem? { return StoreItem(rawValue: self.product.id) } @@ -368,7 +383,7 @@ fileprivate struct SubscriptionNoProductView: View { }) } } - + } } @@ -384,7 +399,7 @@ fileprivate struct SubscriptionDetailView: View { var body: some View { HStack { Image(systemName: "exclamationmark.bubble.fill") - //.foregroundStyle(Color.accentColor) + //.foregroundStyle(Color.accentColor) .font(.title) Text("Vous êtes arrivé à limite de votre offre actuelle. Voici ce que nous proposons pour poursuivre votre tournoi:") .fontWeight(.semibold) diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index 7c8172e..9795143 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -1118,7 +1118,7 @@ struct InscriptionManagerView: View { private func _teamMenuOptionView(_ team: TeamRegistration) -> some View { Menu { Section { - MenuWarningView(teams: [team], contactType: $contactType) + MenuWarningView(tournament: team.tournamentObject()!, teams: [team], contactType: $contactType) //Divider() Button("Copier") { let pasteboard = UIPasteboard.general diff --git a/PadelClub/Views/User/LoginView.swift b/PadelClub/Views/User/LoginView.swift index 348f8ad..db79e0f 100644 --- a/PadelClub/Views/User/LoginView.swift +++ b/PadelClub/Views/User/LoginView.swift @@ -8,6 +8,32 @@ import SwiftUI import LeStorage +enum LoginReason { + case loginRequiredForFeature + + var title: String { + switch self { + case .loginRequiredForFeature: + return "Compte requis" + } + } + + var systemImage: String { + switch self { + case .loginRequiredForFeature: + return "person.crop.circle.fill" + } + } + + var description: String { + switch self { + case .loginRequiredForFeature: + return "Pour accéder à cette fonctionnalité, veuillez-vous connecter ou créer un compte" + } + } + +} + typealias Credentials = (username: String, password: String) struct LoginView: View { @@ -27,6 +53,8 @@ struct LoginView: View { @State var errorText: String? = nil @FocusState var focusedField: UserLogin? + var reason: LoginReason? = nil + var showEmailValidationMessage: Bool { credentials != nil } @@ -64,6 +92,16 @@ struct LoginView: View { } } + if let reason { + Section { + ContentUnavailableView { + Label(reason.title, systemImage: reason.systemImage) + } description: { + Text(reason.description) + } + } + } + Section { TextField("Nom d'utilisateur", text: self.$username) .autocorrectionDisabled(true)