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.
369 lines
14 KiB
369 lines
14 KiB
//
|
|
// MainView.swift
|
|
// PadelClub
|
|
//
|
|
// Created by Razmig Sarkissian on 29/02/2024.
|
|
//
|
|
|
|
import SwiftUI
|
|
import LeStorage
|
|
import StoreKit
|
|
import PadelClubData
|
|
|
|
struct MainView: View {
|
|
@EnvironmentObject var dataStore: DataStore
|
|
//TODO: IOS BUG
|
|
//@Environment(\.requestReview) var requestReview
|
|
|
|
@Environment(NavigationViewModel.self) private var navigation: NavigationViewModel
|
|
@Environment(ImportObserver.self) private var importObserver: ImportObserver
|
|
@State private var federalDataViewModel: FederalDataViewModel = FederalDataViewModel.shared
|
|
|
|
@State private var mainViewId: UUID = UUID()
|
|
@State private var presentOnboarding: Bool = false
|
|
@State private var canPresentOnboarding: Bool = false
|
|
@State private var presentFilterView: Bool = false
|
|
@State private var displaySearchView: Bool = false
|
|
|
|
@AppStorage("didSeeOnboarding") private var didSeeOnboarding: Bool = false
|
|
|
|
var lastDataSource: String? {
|
|
dataStore.appSettings.lastDataSource
|
|
}
|
|
|
|
var selectedTabHandler: Binding<TabDestination?> { Binding(
|
|
get: { navigation.selectedTab },
|
|
set: {
|
|
if $0 == navigation.selectedTab {
|
|
// switch navigation.selectedTab {
|
|
// case .activity:
|
|
// navigation.path.removeLast()
|
|
// case .toolbox:
|
|
// navigation.toolboxPath = NavigationPath()
|
|
// case .umpire:
|
|
// navigation.umpirePath = NavigationPath()
|
|
// case .ongoing:
|
|
// navigation.ongoingPath = NavigationPath()
|
|
// case .tournamentOrganizer:
|
|
// break
|
|
// case .none:
|
|
// break
|
|
// }
|
|
} else {
|
|
navigation.selectedTab = $0
|
|
}
|
|
}
|
|
)}
|
|
|
|
var badgeText: Text? {
|
|
return (dataStore.appSettings.didCreateAccount && StoreCenter.main.isAuthenticated == false) ? Text("!").font(.headline) : nil
|
|
}
|
|
|
|
var body: some View {
|
|
TabView(selection: selectedTabHandler) {
|
|
ActivityView()
|
|
.tabItem(for: .activity)
|
|
.onAppear {
|
|
print("on appear main view")
|
|
if importObserver.willCheckDataIntegrity == false {
|
|
importObserver.willCheckDataIntegrity = true
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
|
_checkingDataIntegrity()
|
|
importObserver.willCheckDataIntegrity = false
|
|
}
|
|
}
|
|
|
|
#if DEBUG
|
|
#else
|
|
//_requestReviewIfAppropriated()
|
|
#endif
|
|
|
|
}
|
|
.toolbarBackground(.visible, for: .tabBar)
|
|
OngoingContainerView()
|
|
.tabItem(for: .ongoing)
|
|
.badge(self.dataStore.runningMatches().count)
|
|
.toolbarBackground(.visible, for: .tabBar)
|
|
UmpireOptionsView()
|
|
.tabItem(for: .umpire)
|
|
.toolbarBackground(.visible, for: .tabBar)
|
|
// TournamentOrganizerView()
|
|
// .tabItem(for: .tournamentOrganizer)
|
|
// .toolbarBackground(.visible, for: .tabBar)
|
|
ToolboxView()
|
|
.tabItem(for: .toolbox)
|
|
.toolbarBackground(.visible, for: .tabBar)
|
|
MyAccountView()
|
|
.tabItem(for: .myAccount)
|
|
.toolbarBackground(.visible, for: .tabBar)
|
|
.badge(badgeText)
|
|
// PadelClubView()
|
|
// .tabItem(for: .padelClub)
|
|
}
|
|
.applyTabViewBottomAccessory(isVisible: (navigation.selectedTab == .activity || navigation.selectedTab == nil) && _shouldDisplaySearchStatus(), content: {
|
|
_searchBoxView()
|
|
})
|
|
.sheet(isPresented: $presentFilterView) {
|
|
TournamentFilterView(federalDataViewModel: federalDataViewModel)
|
|
.environment(navigation)
|
|
.tint(.master)
|
|
}
|
|
.sheet(isPresented: $displaySearchView) {
|
|
NavigationStack {
|
|
TournamentLookUpView()
|
|
.environment(federalDataViewModel)
|
|
.environment(navigation)
|
|
}
|
|
}
|
|
.onAppear {
|
|
if canPresentOnboarding || StoreCenter.main.userId != nil {
|
|
if didSeeOnboarding == false {
|
|
presentOnboarding = true
|
|
}
|
|
}
|
|
}
|
|
.sheet(isPresented: $presentOnboarding, content: {
|
|
OnboardingView()
|
|
.environmentObject(dataStore)
|
|
})
|
|
.id(mainViewId)
|
|
.onChange(of: dataStore.user.id) {
|
|
print("dataStore.user.id = ", dataStore.user.id)
|
|
print("StoreCenter.main.userId = ", StoreCenter.main.userId ?? "")
|
|
if StoreCenter.main.userId == nil { // user disconnected
|
|
navigation.path.removeLast(navigation.path.count)
|
|
mainViewId = UUID()
|
|
}
|
|
|
|
canPresentOnboarding = true
|
|
}
|
|
.environmentObject(dataStore)
|
|
.task {
|
|
//await self._checkSourceFileAvailability()
|
|
if StoreCenter.main.isAuthenticated {
|
|
do {
|
|
dataStore.clubs.reset()
|
|
try await dataStore.clubs.loadDataFromServerIfAllowed()
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
|
|
|
|
let ids = dataStore.user.clubs
|
|
var save = false
|
|
ids.forEach { clubId in
|
|
if dataStore.clubs.findById(clubId) == nil {
|
|
dataStore.user.clubs.removeAll(where: { $0 == clubId })
|
|
save = true
|
|
}
|
|
}
|
|
|
|
if save {
|
|
dataStore.saveUser()
|
|
}
|
|
}
|
|
}
|
|
// .refreshable {
|
|
// Task {
|
|
// await self._checkSourceFileAvailability()
|
|
// }
|
|
// }
|
|
.overlay(alignment: .bottom) {
|
|
if importObserver.isImportingFile() {
|
|
_activityStatusBoxView()
|
|
} else {
|
|
_activityStatusBoxView()
|
|
.deferredRendering(for: .seconds(3))
|
|
}
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
private func _requestReviewIfAppropriated() {
|
|
let isConnected = StoreCenter.main.userId != nil
|
|
let numberOfSignificantTournaments = dataStore.tournaments.filter({ $0.isDeleted == false && $0.endDate != nil }).count
|
|
if isConnected || numberOfSignificantTournaments > 0 {
|
|
//requestReview()
|
|
}
|
|
}
|
|
|
|
func _activityStatusBoxView() -> some View {
|
|
return _activityStatus()
|
|
.toastFormatted()
|
|
}
|
|
|
|
@ViewBuilder
|
|
func _activityStatus() -> some View {
|
|
if importObserver.isImportingFile() {
|
|
HStack(spacing: 20) {
|
|
ProgressView()
|
|
.progressViewStyle(CircularProgressViewStyle(tint: Color.black))
|
|
Text(importObserver.currentlyImportingLabel())
|
|
}
|
|
} else if let mostRecentDateAvailable = SourceFileManager.shared.mostRecentDateAvailable, let lastDataSourceDate = SourceFileManager.shared.lastDataSourceDate() {
|
|
if mostRecentDateAvailable > lastDataSourceDate {
|
|
Label(mostRecentDateAvailable.monthYearFormatted + " disponible", systemImage: "exclamationmark.triangle")
|
|
.labelStyle(.titleAndIcon)
|
|
} else {
|
|
Label(mostRecentDateAvailable.monthYearFormatted, systemImage: "checkmark")
|
|
.labelStyle(.titleAndIcon)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func _checkSourceFileAvailability() async {
|
|
print("dataStore.appSettings.lastDataSource :", dataStore.appSettings.lastDataSource ?? "none")
|
|
print("check internet")
|
|
print("check files on internet")
|
|
print("check if any files on internet are more recent than here")
|
|
importObserver.checkingFiles = true
|
|
await SourceFileManager.shared.fetchData()
|
|
importObserver.checkingFilesAttempt += 1
|
|
importObserver.checkingFiles = false
|
|
|
|
if let mostRecentDateAvailable = SourceFileManager.shared.mostRecentDateAvailable, mostRecentDateAvailable > SourceFileManager.shared.lastDataSourceDate() ?? .distantPast {
|
|
|
|
print("importing \(mostRecentDateAvailable)")
|
|
await _startImporting(importingDate: mostRecentDateAvailable)
|
|
}
|
|
}
|
|
|
|
private func _startImporting(importingDate: Date) async {
|
|
await MainActor.run {
|
|
importObserver.currentImportDate = importingDate
|
|
}
|
|
let lastDataSource = await FileImportManager.shared.importDataFromFFT(importingDate: importingDate)
|
|
dataStore.appSettings.lastDataSource = lastDataSource
|
|
dataStore.appSettingsStorage.write()
|
|
await _calculateMonthData(dataSource: lastDataSource)
|
|
await MainActor.run {
|
|
importObserver.currentImportDate = nil
|
|
|
|
}
|
|
await _downloadPreviousDate()
|
|
}
|
|
|
|
private func _calculateMonthData(dataSource: String?) async {
|
|
if let dataSource, let mostRecentDate = URL.importDateFormatter.date(from: dataSource) {
|
|
await MonthData.calculateCurrentUnrankedValues(fromDate: mostRecentDate)
|
|
}
|
|
}
|
|
|
|
private func _downloadPreviousDate() async {
|
|
await SourceFileManager.shared.getAllFiles(initialDate: "05-2024")
|
|
}
|
|
|
|
private func _checkingDataIntegrity() {
|
|
guard importObserver.checkingFiles == false, importObserver.isImportingFile() == false else {
|
|
return
|
|
}
|
|
if lastDataSource == nil {
|
|
Task {
|
|
await self._checkSourceFileAvailability()
|
|
}
|
|
} else if let lastDataSource, lastDataSource != URL.importDateFormatter.string(from: Date()) {
|
|
Task {
|
|
await self._checkSourceFileAvailability()
|
|
}
|
|
} else if let lastDataSource, let mostRecentDateImported = URL.importDateFormatter.date(from: lastDataSource) {
|
|
|
|
let monthData = dataStore.monthData.sorted(by: \.creationDate)
|
|
|
|
if let current = monthData.last {
|
|
Task {
|
|
let updated = await SourceFileManager.shared.fetchData(fromDate: mostRecentDateImported)
|
|
let fileURL = SourceFileManager.shared.allFiles(true).first(where: { $0.dateFromPath == mostRecentDateImported && $0.index == 0 })
|
|
print("file updated", updated)
|
|
if let updated, updated == 1 {
|
|
await _startImporting(importingDate: mostRecentDateImported)
|
|
} else if current.dataModelIdentifier != PersistenceController.getModelVersion() && current.fileModelIdentifier != fileURL?.fileModelIdentifier() {
|
|
await _startImporting(importingDate: mostRecentDateImported)
|
|
} else if updated == 0 {
|
|
await _calculateMonthData(dataSource: current.monthKey)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func _searchStatus() -> String {
|
|
var searchStatus : [String] = []
|
|
if navigation.agendaDestination == .around, federalDataViewModel.searchedFederalTournaments.isEmpty == false {
|
|
let filteredSearchedFederalTournaments = federalDataViewModel.filteredSearchedFederalTournaments
|
|
|
|
let status : String = filteredSearchedFederalTournaments.count.formatted() + " tournoi" + filteredSearchedFederalTournaments.count.pluralSuffix
|
|
searchStatus.append(status)
|
|
}
|
|
|
|
if federalDataViewModel.areFiltersEnabled() {
|
|
searchStatus.append(federalDataViewModel.filterStatus())
|
|
}
|
|
|
|
return searchStatus.joined(separator: " ")
|
|
}
|
|
|
|
|
|
private func _shouldDisplaySearchStatus() -> Bool {
|
|
guard navigation.path.count == 0 else { return false }
|
|
return federalDataViewModel.areFiltersEnabled() || (navigation.agendaDestination == .around && federalDataViewModel.searchedFederalTournaments.isEmpty == false)
|
|
}
|
|
|
|
private func _searchBoxView() -> some View {
|
|
VStack(spacing: 0) {
|
|
let searchStatus = _searchStatus()
|
|
if searchStatus.isEmpty == false {
|
|
Text(_searchStatus())
|
|
.font(.footnote)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
HStack {
|
|
if navigation.agendaDestination == .around {
|
|
FooterButtonView("modifier votre recherche") {
|
|
displaySearchView = true
|
|
}
|
|
|
|
if federalDataViewModel.areFiltersEnabled() {
|
|
Text("ou")
|
|
}
|
|
}
|
|
|
|
if federalDataViewModel.areFiltersEnabled() {
|
|
FooterButtonView(_filterButtonTitle()) {
|
|
presentFilterView = true
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func _filterButtonTitle() -> String {
|
|
var prefix = "modifier "
|
|
if navigation.agendaDestination == .around, federalDataViewModel.searchedFederalTournaments.isEmpty == false {
|
|
prefix = ""
|
|
}
|
|
return prefix + "vos filtres"
|
|
}
|
|
|
|
|
|
}
|
|
|
|
//#Preview {
|
|
// MainView()
|
|
//}
|
|
|
|
fileprivate extension View {
|
|
@ViewBuilder
|
|
func applyTabViewBottomAccessory<Content: View>(isVisible: Bool,
|
|
@ViewBuilder content: () -> Content
|
|
) -> some View {
|
|
if #available(iOS 26.0, *), isVisible {
|
|
self.tabViewBottomAccessory {
|
|
content()
|
|
}
|
|
} else {
|
|
self
|
|
}
|
|
}
|
|
}
|
|
|