You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
277 lines
9.8 KiB
277 lines
9.8 KiB
//
|
|
// PadelClubApp.swift
|
|
// PadelClub
|
|
//
|
|
// Created by Laurent Morvillier on 02/02/2024.
|
|
//
|
|
|
|
import SwiftUI
|
|
import LeStorage
|
|
import TipKit
|
|
import PadelClubData
|
|
|
|
@main
|
|
struct PadelClubApp: App {
|
|
let persistenceController = PersistenceController.shared
|
|
@State private var navigationViewModel = NavigationViewModel()
|
|
@StateObject var networkMonitor: NetworkMonitor = NetworkMonitor()
|
|
@StateObject var dataStore = DataStore.shared
|
|
@State private var registrationError: RegistrationError? = nil
|
|
@State private var importObserverViewModel = ImportObserver()
|
|
@State private var showDisconnectionAlert: Bool = false
|
|
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
|
|
|
@State var requiredVersion: String? = nil
|
|
|
|
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
|
|
|
var presentError: Binding<Bool> {
|
|
Binding {
|
|
registrationError != nil
|
|
} set: { value in
|
|
if value == false {
|
|
registrationError = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
enum RegistrationError: LocalizedError {
|
|
case badActivationLink
|
|
case activationFailed
|
|
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .badActivationLink:
|
|
return "Le lien d'activation n'a pas fonctionné"
|
|
case .activationFailed:
|
|
return "L'activation de votre compte n'a pas fonctionné"
|
|
}
|
|
}
|
|
}
|
|
|
|
static var appVersion: String {
|
|
let dictionary = Bundle.main.infoDictionary!
|
|
let version = dictionary["CFBundleShortVersionString"] as! String
|
|
let build = dictionary["CFBundleVersion"] as! String
|
|
#if DEBUG
|
|
return "\(version) (\(build)) Debug"
|
|
#elseif TESTFLIGHT
|
|
return "\(version) (\(build)) TestFlight"
|
|
#elseif PRODTEST
|
|
return "\(version) (\(build)) ProdTest"
|
|
#else
|
|
return "\(version) (\(build))"
|
|
#endif
|
|
}
|
|
|
|
var body: some Scene {
|
|
WindowGroup {
|
|
|
|
if let requiredVersion {
|
|
DownloadNewVersionView(version: requiredVersion)
|
|
} else {
|
|
MainView()
|
|
.environment(\.horizontalSizeClass, .compact)
|
|
.alert(isPresented: presentError, error: registrationError) {
|
|
Button("Contactez-nous") {
|
|
_openMail()
|
|
}
|
|
Button("Annuler", role: .cancel) {
|
|
registrationError = nil
|
|
}
|
|
}
|
|
.onOpenURL { url in
|
|
#if targetEnvironment(simulator)
|
|
#else
|
|
_handleIncomingURL(url)
|
|
#endif
|
|
}
|
|
.environmentObject(networkMonitor)
|
|
.environmentObject(dataStore)
|
|
.environment(importObserverViewModel)
|
|
.environment(navigationViewModel)
|
|
.accentColor(.master)
|
|
.onAppear {
|
|
self._checkVersion()
|
|
|
|
if ManualPatcher.patchIfPossible(.disconnect) == true {
|
|
self.showDisconnectionAlert = true
|
|
}
|
|
#if DEBUG
|
|
print("Running in Debug mode")
|
|
#elseif TESTFLIGHT
|
|
print("Running in TestFlight mode")
|
|
#elseif PRODTEST
|
|
print("Running in ProdTest mode")
|
|
#else
|
|
print("Running in Release mode")
|
|
#endif
|
|
print(URLs.main.url)
|
|
networkMonitor.checkConnection()
|
|
self._onAppear()
|
|
print(PersistenceController.getModelVersion())
|
|
}
|
|
.alert(isPresented: self.$showDisconnectionAlert, content: {
|
|
Alert(title: Text("Vous avez été déconnecté. Veuillez vous reconnecter pour récupérer vos données."))
|
|
})
|
|
.task {
|
|
|
|
// try? Tips.resetDatastore()
|
|
|
|
try? Tips.configure([
|
|
.displayFrequency(.immediate),
|
|
.datastoreLocation(.applicationDefault)
|
|
])
|
|
}
|
|
.environment(\.managedObjectContext, persistenceController.localContainer.viewContext)
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func _checkVersion() {
|
|
Task.detached(priority: .high) {
|
|
if let requiredVersion = await self._retrieveRequiredVersion() {
|
|
let cleanedRequired = requiredVersion.replacingOccurrences(of: "\n", with: "")
|
|
Logger.log(">>> REQUIRED VERSION = \(requiredVersion)")
|
|
if let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
|
|
await MainActor.run {
|
|
if VersionComparator.compare(cleanedRequired, currentVersion) == 1 {
|
|
self.requiredVersion = cleanedRequired
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func _retrieveRequiredVersion() async -> String? {
|
|
let requiredVersionURL = URLs.main.extend(path: "static/misc/required-version.txt")
|
|
|
|
do {
|
|
let (data, _) = try await URLSession.shared.data(from: requiredVersionURL)
|
|
return String(data: data, encoding: .utf8)
|
|
} catch {
|
|
Logger.log("Error fetching required version: \(error)")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private func _handleIncomingURL(_ url: URL) {
|
|
// Parse the URL
|
|
let pathComponents = url.pathComponents
|
|
if url.scheme == "padelclub" && pathComponents.count > 3 && pathComponents[1] == "activate" {
|
|
let uidb64 = pathComponents[2]
|
|
let token = pathComponents[3]
|
|
_activateUser(uidb64: uidb64, token: token)
|
|
print(uidb64, token)
|
|
} else {
|
|
// Handle invalid URL case
|
|
print("Handle invalid URL case")
|
|
registrationError = .badActivationLink
|
|
}
|
|
}
|
|
|
|
private func _activateUser(uidb64: String, token: String) {
|
|
guard let url = URL(string: "https://\(URLs.activationHost.rawValue)/activate/\(uidb64)/\(token)/") else {
|
|
registrationError = .activationFailed
|
|
return
|
|
}
|
|
|
|
URLSession.shared.dataTask(with: url) { (data, response, error) in
|
|
guard let data = data, let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
|
|
print("activation error")
|
|
print(error)
|
|
registrationError = .activationFailed
|
|
return
|
|
}
|
|
|
|
DispatchQueue.main.async {
|
|
dataStore.appSettings.didRegisterAccount = true
|
|
dataStore.appSettingsStorage.write()
|
|
|
|
if navigationViewModel.selectedTab != .umpire {
|
|
navigationViewModel.selectedTab = .umpire
|
|
}
|
|
|
|
if navigationViewModel.accountPath.isEmpty {
|
|
navigationViewModel.accountPath.append(MyAccountView.AccountScreen.login)
|
|
} else if navigationViewModel.accountPath.last! != .login {
|
|
navigationViewModel.accountPath.removeAll()
|
|
navigationViewModel.accountPath.append(MyAccountView.AccountScreen.login)
|
|
}
|
|
}
|
|
}.resume()
|
|
}
|
|
|
|
fileprivate func _onAppear() {
|
|
let docURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
Logger.log("doc dir = \(docURL.absoluteString)")
|
|
UserDefaults.standard.set(false, forKey: "_UIConstraintBasedLayoutLogUnsatisfiable")
|
|
|
|
// AppDelegate.askPermissions()
|
|
|
|
}
|
|
|
|
|
|
private func _openMail(emailTo: String = "support@padelclub.app", subject: String = "Support Padel Club") {
|
|
if let url = URL(string: "mailto:\(emailTo)?subject=\(subject)"), UIApplication.shared.canOpenURL(url) {
|
|
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
struct DownloadNewVersionView: View {
|
|
|
|
var version: String
|
|
|
|
var body: some View {
|
|
|
|
VStack {
|
|
// AngledStripesBackground()
|
|
Spacer()
|
|
VStack(spacing: 20.0) {
|
|
Text("Veuillez télécharger la nouvelle version de Padel Club pour continuer à vous servir de l'app !")
|
|
.fontWeight(.semibold)
|
|
.foregroundStyle(.white)
|
|
.padding()
|
|
.background(.logoRed)
|
|
.clipShape(.buttonBorder)
|
|
// .padding(32.0)
|
|
|
|
VStack(alignment: .center, spacing: 0.0
|
|
) {
|
|
Text("Version \(self.version)")
|
|
.fontWeight(.bold)
|
|
|
|
Image(systemName: "square.and.arrow.down").font(.title)
|
|
}.padding().background(.logoYellow)
|
|
.clipShape(.buttonBorder)
|
|
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.foregroundStyle(.logoBackground)
|
|
.fontWeight(.medium)
|
|
.multilineTextAlignment(.center)
|
|
.padding(.horizontal, 36.0)
|
|
Image("logo").padding(.vertical, 50.0)
|
|
Spacer()
|
|
}
|
|
.background(.logoBackground)
|
|
.onTapGesture {
|
|
UIApplication.shared.open(URLs.appStore.url)
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
struct DisconnectionAlertView: View {
|
|
var body: some View {
|
|
Text("Vous avez été déconnecté. Veuillez vous reconnecter pour récupérer vos données.").multilineTextAlignment(.center).padding()
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
DownloadNewVersionView(version: "1.2")
|
|
}
|
|
|