diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 7cc5bbe..612c14b 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ C45BAE442BCA753E002EEC8A /* Purchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45BAE432BCA753E002EEC8A /* Purchase.swift */; }; C49EF0192BD694290077B5AA /* PurchaseListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0182BD694290077B5AA /* PurchaseListView.swift */; }; C49EF01B2BD6A1E80077B5AA /* URLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF01A2BD6A1E80077B5AA /* URLs.swift */; }; + C49EF0262BD80AE80077B5AA /* OffersHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0252BD80AE80077B5AA /* OffersHeaderView.swift */; }; C4A47D5A2B6D383C00ADC637 /* Tournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D592B6D383C00ADC637 /* Tournament.swift */; }; C4A47D5E2B6D38EC00ADC637 /* DataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */; }; C4A47D632B6D3D6500ADC637 /* Club.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D622B6D3D6500ADC637 /* Club.swift */; }; @@ -310,6 +311,7 @@ C45BAE432BCA753E002EEC8A /* Purchase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Purchase.swift; sourceTree = ""; }; C49EF0182BD694290077B5AA /* PurchaseListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseListView.swift; sourceTree = ""; }; C49EF01A2BD6A1E80077B5AA /* URLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLs.swift; sourceTree = ""; }; + C49EF0252BD80AE80077B5AA /* OffersHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OffersHeaderView.swift; sourceTree = ""; }; C4A47D592B6D383C00ADC637 /* Tournament.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tournament.swift; sourceTree = ""; }; C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStore.swift; sourceTree = ""; }; C4A47D622B6D3D6500ADC637 /* Club.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Club.swift; sourceTree = ""; }; @@ -729,6 +731,7 @@ C4A47D8D2B7BBBEC00ADC637 /* StoreManager.swift */, C4A47D8F2B7BBBEC00ADC637 /* StoreItem.swift */, C49EF0182BD694290077B5AA /* PurchaseListView.swift */, + C49EF0252BD80AE80077B5AA /* OffersHeaderView.swift */, ); path = Subscription; sourceTree = ""; @@ -1440,6 +1443,7 @@ FFCFC00E2BBC3D4600B82851 /* PointSelectionView.swift in Sources */, FF089EB62BB00A3800F0AEC7 /* TeamRowView.swift in Sources */, FF92680B2BCEE3E10080F940 /* ContactManager.swift in Sources */, + C49EF0262BD80AE80077B5AA /* OffersHeaderView.swift in Sources */, FFCFC00C2BBC3D1E00B82851 /* EditScoreView.swift in Sources */, FF7091622B90F04300AB08DA /* TournamentOrganizerView.swift in Sources */, FF92680D2BCEE5EA0080F940 /* NetworkMonitor.swift in Sources */, diff --git a/PadelClub/Views/Calling/CallView.swift b/PadelClub/Views/Calling/CallView.swift index 84282d9..0ac7364 100644 --- a/PadelClub/Views/Calling/CallView.swift +++ b/PadelClub/Views/Calling/CallView.swift @@ -84,7 +84,7 @@ struct CallView: View { var body: some View { let callWord = teams.allSatisfy({ $0.called() }) ? "Reconvoquer" : "Convoquer" - HStack { + HStack(spacing: 0.0) { if teams.count == 1 { if let previousCallDate = teams.first?.callDate, Calendar.current.compare(previousCallDate, to: callDate, toGranularity: .minute) != .orderedSame { Text("Reconvoquer " + callDate.localizedDate() + " par") @@ -120,42 +120,53 @@ struct CallView: View { .sheet(item: $contactType) { contactType in switch contactType { case .message(_, let recipients, let body, _): - MessageComposeView(recipients: recipients, body: body) { result in - switch result { - case .cancelled: - _called(true) - break - case .failed: - self.sentError = .messageFailed - case .sent: - if networkMonitor.connected == false { - self.sentError = .messageNotSent - } else { + + if Guard.main.paymentForNewTournament() != nil { + MessageComposeView(recipients: recipients, body: body) { result in + switch result { + case .cancelled: _called(true) + break + case .failed: + self.sentError = .messageFailed + case .sent: + if networkMonitor.connected == false { + self.sentError = .messageNotSent + } else { + _called(true) + } + @unknown default: + break } - @unknown default: - break } + } else { + SubscriptionView(showLackOfPlanMessage: true) } + 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 - _called(true) - case .failed: - self.contactType = nil - self.sentError = .mailFailed - case .sent: - if networkMonitor.connected == false { + if Guard.main.paymentForNewTournament() != nil { + + MailComposeView(recipients: recipients, bccRecipients: bccRecipients, body: body, subject: subject) { result in + switch result { + case .cancelled, .saved: self.contactType = nil - self.sentError = .mailNotSent - } else { _called(true) + case .failed: + self.contactType = nil + self.sentError = .mailFailed + case .sent: + if networkMonitor.connected == false { + self.contactType = nil + self.sentError = .mailNotSent + } else { + _called(true) + } + @unknown default: + break } - @unknown default: - break } + } else { + SubscriptionView(showLackOfPlanMessage: true) } } } diff --git a/PadelClub/Views/Navigation/Agenda/ActivityView.swift b/PadelClub/Views/Navigation/Agenda/ActivityView.swift index 450ec45..dc31d45 100644 --- a/PadelClub/Views/Navigation/Agenda/ActivityView.swift +++ b/PadelClub/Views/Navigation/Agenda/ActivityView.swift @@ -55,6 +55,7 @@ struct ActivityView: View { NavigationStack(path: $navigation.path) { VStack(spacing: 0) { GenericDestinationPickerView(selectedDestination: $navigation.agendaDestination, destinations: AgendaDestination.allCases, nilDestinationIsValid: false) + List { switch navigation.agendaDestination! { case .activity: diff --git a/PadelClub/Views/Navigation/Umpire/UmpireView.swift b/PadelClub/Views/Navigation/Umpire/UmpireView.swift index a71c0fd..77306ad 100644 --- a/PadelClub/Views/Navigation/Umpire/UmpireView.swift +++ b/PadelClub/Views/Navigation/Umpire/UmpireView.swift @@ -17,16 +17,19 @@ struct UmpireView: View { PurchaseListView() + Section { + NavigationLink { + SubscriptionView() + } label: { + Label("Les offres", systemImage: "bookmark.circle.fill") + } + } + NavigationLink { MainUserView() } label: { Label("Mon compte", systemImage: "person.circle.fill") } - NavigationLink { - SubscriptionView() - } label: { - Label("Abonnement", systemImage: "tennisball.circle.fill") - } if let user = dataStore.user { let currentPlayerData = user.currentPlayerData() diff --git a/PadelClub/Views/Planning/PlanningSettingsView.swift b/PadelClub/Views/Planning/PlanningSettingsView.swift index 3d16793..c2806e6 100644 --- a/PadelClub/Views/Planning/PlanningSettingsView.swift +++ b/PadelClub/Views/Planning/PlanningSettingsView.swift @@ -37,7 +37,9 @@ struct PlanningSettingsView: View { var body: some View { @Bindable var tournament = tournament + OffersHeaderView() List { + Section { DatePicker(tournament.startDate.formatted(.dateTime.weekday()), selection: $tournament.startDate) LabeledContent { diff --git a/PadelClub/Views/Subscription/Guard.swift b/PadelClub/Views/Subscription/Guard.swift index 9391cdc..e3e4c85 100644 --- a/PadelClub/Views/Subscription/Guard.swift +++ b/PadelClub/Views/Subscription/Guard.swift @@ -141,8 +141,14 @@ import LeStorage func userFilteredPurchases() -> [StoreKit.Transaction] { - return self.purchasedTransactions.filter { transaction in - return Store.main.currentUserUUID() == transaction.appAccountToken + let userTransactions = self.purchasedTransactions.filter { Store.main.currentUserUUID() == $0.appAccountToken } + + return userTransactions.filter { transaction in + if let expirationDate = transaction.expirationDate { + return expirationDate > Date() + } else { + return true + } } // return self.purchasedTransactions.filter { transaction in @@ -201,35 +207,12 @@ import LeStorage } var remainingTournaments: Int { - let subscriptionPayed = DataStore.shared.tournaments.filter { $0.payment?.isSubscription == true } - let unitlyPayed = DataStore.shared.tournaments.count - subscriptionPayed.count + let unitlyPayed = DataStore.shared.tournaments.filter { $0.payment == Tournament.TournamentPayment.unit }.count let tournamentCreditCount = self._purchasedTournamentCount() + Logger.log("total count = \(DataStore.shared.tournaments.count), unitlyPayed = \(unitlyPayed), purchased = \(tournamentCreditCount) ") return tournamentCreditCount - unitlyPayed } -// func purchaseRows(products: [Product]) -> [PurchaseRow] { -// -// var rows: [PurchaseRow] = [] -// let userPurchases = self.userFilteredPurchases() -// for userPurchase in userPurchases { -// -// if let item = StoreItem(rawValue: userPurchase.productID), -// let product = products.first(where: { $0.id == item.rawValue } ) { -// switch item { -// case .unit: -// let remainingTournaments = self.remainingTournaments -// if remainingTournaments > 0 { -// rows.append(PurchaseRow(name: product.displayName, item: item, quantity: remainingTournaments)) -// } -// default: -// rows.append(PurchaseRow(name: product.displayName, item: item)) -// } -// } -// } -// -// return rows -// } - } struct PurchaseRow: Identifiable { diff --git a/PadelClub/Views/Subscription/OffersHeaderView.swift b/PadelClub/Views/Subscription/OffersHeaderView.swift index d6273a2..1e0be93 100644 --- a/PadelClub/Views/Subscription/OffersHeaderView.swift +++ b/PadelClub/Views/Subscription/OffersHeaderView.swift @@ -8,9 +8,49 @@ import SwiftUI struct OffersHeaderView: View { + + let payment: Tournament.TournamentPayment? = .free + var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + VStack { + if let text = self.text { + Text(text) + .multilineTextAlignment(.center) + .font(.callout) + .padding() + .foregroundColor(self.foregroundColor) + .frame(maxWidth: .infinity) + .background(self.backgroundColor) + } + + } + } + + var foregroundColor: Color { + switch self.payment { + case .free: return .blue + default: return .red + } + } + + var backgroundColor: Color { + switch self.payment { + case .free: return Color(red: 0.9, green: 0.9, blue: 1.0) + default: return Color(red: 1.0, green: 0.9, blue: 0.9) + } + } + + var text: String? { + switch self.payment { + case .free: + return "Nous vous offrons votre premier tournoi ! Convoquez les équipes, créez les poules, le tableau comme vous le souhaitez. \nEnregistrez les résultats de chaque équipes et diffusez les scores en temps réel sur les écrans de votre club !" + case nil: + return "Vous ne disposez pas d'une offre vous permettant de convoquer les joueurs ou de rentrer les résultats des matchs. Vous pouvez consulter les offres dans l'onglet JA." + default: + return nil + } } + } #Preview { diff --git a/PadelClub/Views/Subscription/PurchaseListView.swift b/PadelClub/Views/Subscription/PurchaseListView.swift index 7396f5b..073de88 100644 --- a/PadelClub/Views/Subscription/PurchaseListView.swift +++ b/PadelClub/Views/Subscription/PurchaseListView.swift @@ -25,7 +25,7 @@ class PurchaseManager: ObservableObject { Task { do { self._products = try await Product.products(for: identifiers) - self._buildRows() + self._buildRowsOnMainThread() } catch { Logger.error(error) } @@ -38,33 +38,46 @@ class PurchaseManager: ObservableObject { } self._purchases.removeAll() self._purchases.append(contentsOf: collection) - self._buildRows() + self._buildRowsOnMainThread() } - fileprivate func _buildRows() { + fileprivate func _buildRowsOnMainThread() { DispatchQueue.main.async { + self._buildRows() + } + + } + + fileprivate func _buildRows() { + + var rows: [PurchaseRow] = [] + let userPurchases: [StoreKit.Transaction] = Guard.main.userFilteredPurchases() + + // Subscriptions + for userPurchase in userPurchases { - var rows: [PurchaseRow] = [] - let userPurchases: [StoreKit.Transaction] = Guard.main.userFilteredPurchases() - for userPurchase in userPurchases { - - if let item = StoreItem(rawValue: userPurchase.productID), - let product = self._products.first(where: { $0.id == item.rawValue } ) { - switch item { - case .unit: - let remainingTournaments = Guard.main.remainingTournaments - if remainingTournaments > 0 { - rows.append(PurchaseRow(name: product.displayName, item: item, quantity: remainingTournaments)) - } - default: - rows.append(PurchaseRow(name: product.displayName, item: item)) - } + if let item = StoreItem(rawValue: userPurchase.productID), + let product = self._products.first(where: { $0.id == item.rawValue } ) { + switch item { + case .fivePerMonth, .monthlyUnlimited: + rows.append(PurchaseRow(name: product.displayName, item: item)) + case .unit: + break } } - self.purchaseRows = rows } + // Units + let remainingTournaments = Guard.main.remainingTournaments + if remainingTournaments > 0 { + let unitItem: StoreItem = StoreItem.unit + if let product = self._products.first(where: { $0.id == unitItem.rawValue } ) { + rows.append(PurchaseRow(name: product.displayName, item: unitItem, quantity: remainingTournaments)) + } + } + + self.purchaseRows = rows } } @@ -104,7 +117,7 @@ struct PurchaseView: View { Spacer() if let quantity = purchaseRow.quantity { let remaining = Guard.main.remainingTournaments - Text("\(remaining) / \(quantity.formatted())") + Text("\(remaining)") } } } diff --git a/PadelClub/Views/Subscription/SubscriptionView.swift b/PadelClub/Views/Subscription/SubscriptionView.swift index f52e550..f6457dc 100644 --- a/PadelClub/Views/Subscription/SubscriptionView.swift +++ b/PadelClub/Views/Subscription/SubscriptionView.swift @@ -103,7 +103,8 @@ class SubscriptionModel: ObservableObject, StoreDelegate { struct SubscriptionView: View { @ObservedObject var model: SubscriptionModel = SubscriptionModel() - + + var showLackOfPlanMessage: Bool = false @State var isRestoring: Bool = false @State var showLoginView: Bool = false @@ -118,6 +119,11 @@ struct SubscriptionView: View { } else { Form { + + if self.showLackOfPlanMessage { + Text("Vous ne disposez malheureusement pas d'offre pour continuer votre tournoi. Voici ce que nous proposons:") + } + if self.model.products.count > 0 { Section { diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index 731ee8f..d88e46d 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -22,51 +22,58 @@ struct TournamentView: View { } var body: some View { - List { + + VStack(spacing: 0.0) { -// if tournament.missingUnrankedValue() { -// Button("update NC") { -// tournament.femaleUnrankedValue = SourceFileManager.shared.getUnrankValue(forMale: false, rankSourceDate: tournament.rankSourceDate) -// tournament.maleUnrankedValue = SourceFileManager.shared.getUnrankValue(forMale: true, rankSourceDate: tournament.rankSourceDate) -// try? dataStore.tournaments.addOrUpdate(instance: tournament) -// } -// } -// -// - Section { - NavigationLink(value: Screen.inscription) { - LabeledContent { - Text(tournament.unsortedTeams().count.formatted() + "/" + tournament.teamCount.formatted()) - .foregroundStyle(.master) - } label: { - Text("Gestion des inscriptions") - if let closedRegistrationDate = tournament.closedRegistrationDate { - Text("clôturé le " + closedRegistrationDate.formatted(date: .abbreviated, time: .shortened)) + OffersHeaderView() + + List { + + + // if tournament.missingUnrankedValue() { + // Button("update NC") { + // tournament.femaleUnrankedValue = SourceFileManager.shared.getUnrankValue(forMale: false, rankSourceDate: tournament.rankSourceDate) + // tournament.maleUnrankedValue = SourceFileManager.shared.getUnrankValue(forMale: true, rankSourceDate: tournament.rankSourceDate) + // try? dataStore.tournaments.addOrUpdate(instance: tournament) + // } + // } + // + // + Section { + NavigationLink(value: Screen.inscription) { + LabeledContent { + Text(tournament.unsortedTeams().count.formatted() + "/" + tournament.teamCount.formatted()) + .foregroundStyle(.master) + } 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)) - .foregroundStyle(.master) - } label: { - Text("Date limite") - } - - if endOfInscriptionDate < Date() { - RowButtonView("Clôturer les inscriptions") { - tournament.lockRegistration() - _save() + if let endOfInscriptionDate = tournament.mandatoryRegistrationCloseDate(), tournament.inscriptionClosed() == false && tournament.hasStarted() == false { + LabeledContent { + Text(endOfInscriptionDate.formatted(date: .abbreviated, time: .shortened)) + .foregroundStyle(.master) + } label: { + Text("Date limite") + } + + if endOfInscriptionDate < Date() { + RowButtonView("Clôturer les inscriptions") { + tournament.lockRegistration() + _save() + } } } } - } - switch tournament.state() { - case .initial: - TournamentInitView() - case .build: - TournamentRunningView(tournament: tournament) + switch tournament.state() { + case .initial: + TournamentInitView() + case .build: + TournamentRunningView(tournament: tournament) + } } } .toolbarBackground(.visible, for: .navigationBar)