multistore
Laurent 1 year ago
commit effaef46b3
  1. 25
      PadelClub.xcodeproj/project.pbxproj
  2. 4
      PadelClub/Info.plist
  3. 1
      PadelClub/PadelClubApp.swift
  4. 10
      PadelClub/Utils/ContactManager.swift
  5. 2
      PadelClub/Views/Calling/CallView.swift
  6. 5
      PadelClub/Views/GroupStage/GroupStagesView.swift
  7. 4
      PadelClub/Views/Navigation/Agenda/ActivityView.swift
  8. 10
      PadelClub/Views/Navigation/Toolbox/ToolboxView.swift
  9. 72
      PadelClub/Views/Planning/PlanningSettingsView.swift
  10. 100
      PadelClub/Views/Shared/SupportButtonView.swift
  11. 3
      PadelClub/Views/Subscription/Guard.swift

@ -170,6 +170,7 @@
FF8F264F2BAE0B9600650388 /* MatchTypeSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F264E2BAE0B9600650388 /* MatchTypeSelectionView.swift */; };
FF8F26512BAE0BAD00650388 /* MatchFormatPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26502BAE0BAD00650388 /* MatchFormatPickerView.swift */; };
FF8F26542BAE1E4400650388 /* TableStructureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26532BAE1E4400650388 /* TableStructureView.swift */; };
FF92660D2C241CE0002361A4 /* Zip in Frameworks */ = {isa = PBXBuildFile; productRef = FF92660C2C241CE0002361A4 /* Zip */; };
FF9267F82BCE78C70080F940 /* CashierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9267F72BCE78C70080F940 /* CashierView.swift */; };
FF9267FA2BCE78EC0080F940 /* CashierDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9267F92BCE78EB0080F940 /* CashierDetailView.swift */; };
FF9267FC2BCE84870080F940 /* PlayerPayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9267FB2BCE84870080F940 /* PlayerPayView.swift */; };
@ -237,6 +238,7 @@
FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */; };
FFE2D2D52C216B5000D0C7BE /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = FFE2D2D42C216B5000D0C7BE /* FirebaseCrashlytics */; };
FFE2D2D82C216D4800D0C7BE /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = FFE2D2D72C216D4800D0C7BE /* GoogleService-Info.plist */; };
FFE2D2E22C231BEE00D0C7BE /* SupportButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */; };
FFEF7F4E2BDE69130033D0F0 /* MenuWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */; };
FFF0241E2BF48B15001F14B4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFF0241D2BF48B15001F14B4 /* Localizable.strings */; };
FFF03C942BD91D0C00B516FC /* ButtonValidateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF03C932BD91D0C00B516FC /* ButtonValidateView.swift */; };
@ -562,6 +564,7 @@
FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MySortDescriptor.swift; sourceTree = "<group>"; };
FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredViewModifier.swift; sourceTree = "<group>"; };
FFE2D2D72C216D4800D0C7BE /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportButtonView.swift; sourceTree = "<group>"; };
FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuWarningView.swift; sourceTree = "<group>"; };
FFF0241C2BF48B15001F14B4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
FFF0241F2BF48B1A001F14B4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -589,6 +592,7 @@
files = (
FFE2D2D52C216B5000D0C7BE /* FirebaseCrashlytics in Frameworks */,
FFCFBFFE2BBBE86600B82851 /* Algorithms in Frameworks */,
FF92660D2C241CE0002361A4 /* Zip in Frameworks */,
C49EF0392BDFF4600077B5AA /* LeStorage.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1060,6 +1064,7 @@
FF5D0D6F2BB3EFA5005CB568 /* LearnMoreSheetView.swift */,
FFCFC0192BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift */,
FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */,
FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */,
);
path = Shared;
sourceTree = "<group>";
@ -1337,6 +1342,7 @@
packageProductDependencies = (
FFCFBFFD2BBBE86600B82851 /* Algorithms */,
FFE2D2D42C216B5000D0C7BE /* FirebaseCrashlytics */,
FF92660C2C241CE0002361A4 /* Zip */,
);
productName = PadelClub;
productReference = C425D3FD2B6D249D002A7B48 /* PadelClub.app */;
@ -1414,6 +1420,7 @@
packageReferences = (
FF4C7F052BBBE6B90031B6A3 /* XCRemoteSwiftPackageReference "swift-algorithms" */,
FFE2D2D32C216B5000D0C7BE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
FF92660B2C241CE0002361A4 /* XCRemoteSwiftPackageReference "Zip" */,
);
productRefGroup = C425D3FE2B6D249D002A7B48 /* Products */;
projectDirPath = "";
@ -1520,6 +1527,7 @@
FF025AE12BD0EB9000A86CF8 /* TournamentClubSettingsView.swift in Sources */,
FFBF065C2BBD2657009D6715 /* GroupStageTeamView.swift in Sources */,
FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */,
FFE2D2E22C231BEE00D0C7BE /* SupportButtonView.swift in Sources */,
FFB1C98B2C10255100B154A7 /* TournamentBroadcastRowView.swift in Sources */,
FF025ADF2BD0CE0A00A86CF8 /* TeamWeightView.swift in Sources */,
FF9268012BCE94920080F940 /* SeedsCallingView.swift in Sources */,
@ -1893,7 +1901,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 57;
CURRENT_PROJECT_VERSION = 58;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
@ -1933,7 +1941,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 57;
CURRENT_PROJECT_VERSION = 58;
DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6;
@ -2092,6 +2100,14 @@
minimumVersion = 1.2.0;
};
};
FF92660B2C241CE0002361A4 /* XCRemoteSwiftPackageReference "Zip" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/marmelroy/Zip";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.1.2;
};
};
FFE2D2D32C216B5000D0C7BE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git";
@ -2103,6 +2119,11 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
FF92660C2C241CE0002361A4 /* Zip */ = {
isa = XCSwiftPackageProductDependency;
package = FF92660B2C241CE0002361A4 /* XCRemoteSwiftPackageReference "Zip" */;
productName = Zip;
};
FFCFBFFD2BBBE86600B82851 /* Algorithms */ = {
isa = XCSwiftPackageProductDependency;
package = FF4C7F052BBBE6B90031B6A3 /* XCRemoteSwiftPackageReference "swift-algorithms" */;

@ -2,8 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIFileSharingEnabled</key>
<true/>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
@ -22,5 +20,7 @@
</array>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>UIFileSharingEnabled</key>
<true/>
</dict>
</plist>

@ -52,7 +52,6 @@ struct PadelClubApp: App {
Logger.log("doc dir = \(docURL.absoluteString)")
UserDefaults.standard.set(false, forKey: "_UIConstraintBasedLayoutLogUnsatisfiable")
}
}
class AppDelegate: NSObject, UIApplicationDelegate {

@ -151,6 +151,7 @@ struct MailComposeView: UIViewControllerRepresentable {
let bccRecipients: [String]?
let body: String?
let subject: String?
var attachmentURL: URL?
let completion: Completion?
func makeUIViewController(context: Context) -> UIViewController {
@ -163,6 +164,15 @@ struct MailComposeView: UIViewControllerRepresentable {
controller.mailComposeDelegate = context.coordinator
controller.setToRecipients(recipients)
controller.setBccRecipients(bccRecipients)
if let attachmentURL {
do {
let attachmentData = try Data(contentsOf: attachmentURL)
controller.addAttachmentData(attachmentData, mimeType: "application/zip", fileName: "backup.zip")
} catch {
print("Could not attach file: \(error)")
}
}
if let body {
controller.setMessageBody(body, isHTML: false)
}

@ -206,7 +206,7 @@ struct CallView: View {
.underline()
}
} else {
Button(byMessage ? "sms" : "mail") {
FooterButtonView(byMessage ? "sms" : "mail") {
self._summon(byMessage: byMessage, reSummon: false)
}
}

@ -107,11 +107,6 @@ struct GroupStagesView: View {
availableToStart = await tournament.availableToStart(allMatches, in: runningMatches ?? [])
readyMatches = await tournament.readyMatches(allMatches)
}
.overlay {
if availableToStart?.isEmpty == true && runningMatches?.isEmpty == true && readyMatches?.isEmpty == true && finishedMatches.isEmpty == true {
ContentUnavailableView("Aucun match à afficher", systemImage: "tennisball")
}
}
.navigationTitle("Toutes les poules")
case .groupStage(let groupStage):
GroupStageView(groupStage: groupStage).id(groupStage.id)

@ -309,7 +309,7 @@ struct ActivityView: View {
Image(.padelClubLogoFondclairTransparent)
.resizable()
.scaledToFit()
.frame(width: 128)
.frame(width: 100)
}
} description: {
Text("Aucun événement en cours ou à venir dans votre agenda.")
@ -326,6 +326,8 @@ struct ActivityView: View {
navigation.agendaDestination = .tenup
}
}
SupportButtonView(contentIsUnavailable: true)
}
}

@ -16,6 +16,12 @@ struct ToolboxView: View {
@Bindable var navigation = navigation
NavigationStack(path: $navigation.toolboxPath) {
List {
Section {
Text("Version de l'application").badge(PadelClubApp.appVersion)
SupportButtonView(contentIsUnavailable: false)
}
#if DEBUG
Section {
@ -113,10 +119,6 @@ struct ToolboxView: View {
Section {
Link("Accéder au guide de la compétition", destination: URLs.padelRules.url)
}
Section {
Text("Version de l'application").badge(PadelClubApp.appVersion)
}
}
.navigationTitle(TabDestination.toolbox.title)
}

@ -18,18 +18,20 @@ struct PlanningSettingsView: View {
@State private var schedulingDone: Bool = false
@State private var showOptions: Bool = false
@State private var issueFound: Bool = false
@State private var parallelType: Bool = false
init(tournament: Tournament) {
self.tournament = tournament
if let matchScheduler = tournament.matchScheduler() {
if matchScheduler.groupStageChunkCount == nil {
matchScheduler.groupStageChunkCount = tournament.getGroupStageChunkValue()
}
self.matchScheduler = matchScheduler
self._groupStageChunkCount = State(wrappedValue: matchScheduler.groupStageChunkCount ?? tournament.getGroupStageChunkValue())
if matchScheduler.groupStageChunkCount != nil {
_parallelType = .init(wrappedValue: true)
_groupStageChunkCount = State(wrappedValue: matchScheduler.groupStageChunkCount!)
} else {
_groupStageChunkCount = State(wrappedValue: tournament.getGroupStageChunkValue())
}
} else {
self.matchScheduler = MatchScheduler(tournament: tournament.id, groupStageChunkCount: tournament.getGroupStageChunkValue())
self.matchScheduler = MatchScheduler(tournament: tournament.id)
self._groupStageChunkCount = State(wrappedValue: tournament.getGroupStageChunkValue())
}
}
@ -93,28 +95,11 @@ struct PlanningSettingsView: View {
.foregroundStyle(.logoRed)
}
if tournament.groupStageCount > 0 {
Section {
TournamentFieldsManagerView(localizedStringKey: "Poule en parallèle", count: $groupStageChunkCount, max: tournament.groupStageCount)
.onChange(of: groupStageChunkCount) {
matchScheduler.groupStageChunkCount = groupStageChunkCount
}
} footer: {
NavigationLink {
_optionsView()
} label: {
Text("Voir les options avancées")
.underline()
.foregroundStyle(.master)
}
}
} else {
Section {
NavigationLink {
_optionsView()
} label: {
Text("Voir les options avancées")
}
Section {
NavigationLink {
_optionsView()
} label: {
Text("Voir les options avancées")
}
}
@ -219,6 +204,37 @@ struct PlanningSettingsView: View {
@ViewBuilder
private func _optionsView() -> some View {
List {
if tournament.groupStageCount > 0 {
Section {
Picker(selection: $parallelType) {
Text("Auto.").tag(false)
Text("Manuel").tag(true)
} label: {
Text("Poules en parallèle")
let value = tournament.getGroupStageChunkValue()
if parallelType == false, value > 1 {
Text("\(value.formatted()) poules commenceront en parallèle")
}
}
.onChange(of: parallelType) {
if parallelType {
groupStageChunkCount = tournament.getGroupStageChunkValue()
} else {
matchScheduler.groupStageChunkCount = nil
}
}
if parallelType {
TournamentFieldsManagerView(localizedStringKey: "Poule en parallèle", count: $groupStageChunkCount, max: tournament.groupStageCount)
.onChange(of: groupStageChunkCount) {
matchScheduler.groupStageChunkCount = groupStageChunkCount
}
}
} footer: {
Text("Vous pouvez indiquer le nombre de poule démarrant en même temps.")
}
}
Section {
Toggle(isOn: $matchScheduler.overrideCourtsUnavailability) {
Text("Ne pas tenir compte des autres épreuves")

@ -0,0 +1,100 @@
//
// SupportButtonView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 19/06/2024.
//
import SwiftUI
import LeStorage
import Zip
extension URL: Identifiable {
public var id: String {
return self.absoluteString
}
}
struct SupportButtonView: View {
let contentIsUnavailable: Bool
@State private var sentError: ContactManagerError? = nil
@State private var zipFilePath: URL?
@EnvironmentObject var networkMonitor: NetworkMonitor
var messageSentFailed: Binding<Bool> {
Binding {
sentError != nil
} set: { newValue in
if newValue == false {
sentError = nil
}
}
}
var body: some View {
Group {
if contentIsUnavailable {
FooterButtonView("Besoin d'aide ? Un problème ? Contactez-nous !") {
_zip()
}
} else {
Button("Signaler un problème") {
_zip()
}
}
}
.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: $zipFilePath) { zipFilePath in
MailComposeView(recipients: ["support@padelclub.app"], bccRecipients: nil, body: nil, subject: _getSubject(), attachmentURL: zipFilePath) { result in
switch result {
case .cancelled, .saved:
self.zipFilePath = nil
case .failed:
self.zipFilePath = nil
self.sentError = .mailFailed
case .sent:
if networkMonitor.connected == false {
self.zipFilePath = nil
self.sentError = .mailNotSent
}
@unknown default:
break
}
}
.tint(.master)
}
}
private func _getSubject() -> String {
let device = UIDevice.current
let iOSVersion = device.systemVersion
return "[\(PadelClubApp.appVersion), \(iOSVersion), \(_getDeviceIdentifier())] Support Padel Club"
}
private func _getDeviceIdentifier() -> String {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
let identifier = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
return identifier
}
private func _zip() {
do {
let filePath = try Club.storageDirectoryPath()
self.zipFilePath = try Zip.quickZipFiles([filePath], fileName: "backup") // Zip
} catch {
Logger.error(error)
}
}
}

@ -140,8 +140,9 @@ import LeStorage
}
var currentPlan: StoreItem? {
// #if DEBUG
return .monthlyUnlimited
// #if DEBUG
// return .monthlyUnlimited
// #else
// if let currentBestPlan = self.currentBestPlan, let plan = StoreItem(rawValue: currentBestPlan.productID) {
// return plan

Loading…
Cancel
Save