Various fixes

multistore
Laurent 1 year ago
parent 64b6ad40ec
commit 246541a9ec
  1. 8
      PadelClub/Views/Calling/CallView.swift
  2. 99
      PadelClub/Views/Calling/Components/MenuWarningView.swift
  3. 278
      PadelClub/Views/Match/MatchDetailView.swift
  4. 10
      PadelClub/Views/Subscription/Guard.swift
  5. 63
      PadelClub/Views/Subscription/SubscriptionView.swift
  6. 2
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  7. 38
      PadelClub/Views/User/LoginView.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)
}
}
}
})

@ -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 {

@ -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<Bool> {
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..<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)
// }
// }
// }
}
}
var body: some View {
List {
if match.hasWalkoutTeam() == false {
@ -130,7 +87,7 @@ struct MatchDetailView: View {
showDetails = true
}
Spacer()
MenuWarningView(teams: match.teams(), message: match.matchWarningMessage(), umpireMail: dataStore.user.email, subject: match.matchWarningSubject(), contactType: $contactType)
MenuWarningView(tournament: self.match.currentTournament()!, teams: match.teams(), message: match.matchWarningMessage(), umpireMail: dataStore.user.email, subject: match.matchWarningSubject(), contactType: $contactType)
.buttonStyle(.borderless)
}
}
@ -139,23 +96,14 @@ struct MatchDetailView: View {
if match.isReady() {
Section {
RowButtonView("Saisir les résultats", systemImage: "list.clipboard") {
do {
if let tournament = self.match.currentTournament() {
try tournament.payIfNecessary()
scoreType = .edition
} else {
self.showSubscriptionView = true
}
} catch {
self.showSubscriptionView = true
}
self._editScores()
}
}
}
let players = match.teams().flatMap { $0.players() }
let players = self.match.teams().flatMap { $0.players() }
let unpaid = players.filter({ $0.hasPaid() == false })
if unpaid.isEmpty == false {
Section {
DisclosureGroup {
@ -186,6 +134,14 @@ struct MatchDetailView: View {
SubscriptionView(isPresented: self.$showSubscriptionView, showLackOfPlanMessage: true)
}
})
.sheet(isPresented: self.$showUserCreationView, content: {
NavigationStack {
LoginView(reason: LoginReason.loginRequiredForFeature) { _ in
self.showUserCreationView = false
self._editScores()
}
}
})
.sheet(item: $scoreType, onDismiss: {
if match.hasEnded() {
dismiss()
@ -194,38 +150,38 @@ struct MatchDetailView: View {
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)
// }
// }
// 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) {
@ -239,47 +195,35 @@ struct MatchDetailView: View {
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
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
}
}
} else {
NavigationStack {
SubscriptionView(isPresented: self.$showSubscriptionView, showLackOfPlanMessage: true)
@unknown default:
break
}
}
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:
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 = .mailFailed
case .sent:
if networkMonitor.connected == false {
self.contactType = nil
self.sentError = .mailNotSent
}
@unknown default:
break
self.sentError = .mailNotSent
}
}
} else {
NavigationStack {
SubscriptionView(isPresented: self.$showSubscriptionView, showLackOfPlanMessage: true)
@unknown default:
break
}
}
}
@ -305,7 +249,7 @@ struct MatchDetailView: View {
} label: {
Text("Supprimer l'horaire")
}
Button(role: .destructive) {
match.resetScores()
match.resetMatch()
@ -314,7 +258,7 @@ struct MatchDetailView: View {
} label: {
Text("Supprimer les scores")
}
} label: {
LabelOptions()
}
@ -326,6 +270,52 @@ struct MatchDetailView: View {
}
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
@ -442,6 +432,34 @@ struct MatchDetailView: View {
}
}
fileprivate func _editScores() {
self._verifyUser {
self._payTournamentAndExecute {
self.scoreType = .edition
}
}
}
fileprivate func _verifyUser(_ handler: () -> ()) {
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)

@ -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
}

@ -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<Bool>, 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)

@ -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

@ -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)

Loading…
Cancel
Save