diff --git a/PadelClub/Data/DataStore.swift b/PadelClub/Data/DataStore.swift index b83f56d..3823e92 100644 --- a/PadelClub/Data/DataStore.swift +++ b/PadelClub/Data/DataStore.swift @@ -119,10 +119,6 @@ class DataStore: ObservableObject { @objc func collectionDidLoad(notification: Notification) { - DispatchQueue.main.async { - self.objectWillChange.send() - } - if let userSingleton: StoredSingleton = notification.object as? StoredSingleton { self.user = userSingleton.item() ?? self._temporaryLocalUser.item ?? User.placeHolder() } else if let clubsCollection: StoredCollection = notification.object as? StoredCollection { diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 190d79f..0df302d 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -72,8 +72,14 @@ final class Tournament : ModelObject, Storable { //local variable var refreshInProgress: Bool = false + var lastTeamRefresh: Date? var refreshRanking: Bool = false + func shouldRefreshTeams() -> Bool { + guard let lastTeamRefresh else { return true } + return lastTeamRefresh.timeIntervalSinceNow < -60 + } + @ObservationIgnored var navigationPath: [Screen] = [] @@ -2621,7 +2627,7 @@ defer { // MARK: - Status - func shouldTournamentBeOver() -> Bool { + func shouldTournamentBeOver() async -> Bool { #if _DEBUGING_TIME //DEBUGING TIME let start = Date() defer { @@ -2661,13 +2667,27 @@ defer { onlineTeams().isEmpty == false && hasEnded() == false && hasStarted() == false } - func refreshTeamList() async throws { - guard enableOnlineRegistration, refreshInProgress == false, hasEnded() == false else { return } - refreshInProgress = true - try await self.tournamentStore.playerRegistrations.loadDataFromServerIfAllowed(clear: true) - try await self.tournamentStore.teamScores.loadDataFromServerIfAllowed(clear: true) - try await self.tournamentStore.teamRegistrations.loadDataFromServerIfAllowed(clear: true) - refreshInProgress = false + func refreshTeamList() async { + guard StoreCenter.main.hasToken() else { return } + guard shouldRefreshTeams(), refreshInProgress == false, enableOnlineRegistration, hasEnded() == false else { return } + await MainActor.run { + refreshInProgress = true + } + do { + try await self.tournamentStore.playerRegistrations.loadDataFromServerIfAllowed(clear: true) + try await self.tournamentStore.teamScores.loadDataFromServerIfAllowed(clear: true) + try await self.tournamentStore.teamRegistrations.loadDataFromServerIfAllowed(clear: true) + await MainActor.run { + refreshInProgress = false + lastTeamRefresh = Date() + } + } catch { + Logger.error(error) + await MainActor.run { + refreshInProgress = false + lastTeamRefresh = Date() + } + } } // MARK: - diff --git a/PadelClub/Extensions/Sequence+Extensions.swift b/PadelClub/Extensions/Sequence+Extensions.swift index 02001b2..e95ba3a 100644 --- a/PadelClub/Extensions/Sequence+Extensions.swift +++ b/PadelClub/Extensions/Sequence+Extensions.swift @@ -45,6 +45,24 @@ extension Sequence { } } } + + func concurrentForEach( + _ operation: @escaping (Element) async -> Void + ) async { + // A task group automatically waits for all of its + // sub-tasks to complete, while also performing those + // tasks in parallel: + await withTaskGroup(of: Void.self) { group in + for element in self { + group.addTask { + await operation(element) + } + + for await _ in group {} + } + } + } + } enum SortOrder { diff --git a/PadelClub/Views/Navigation/Agenda/ActivityView.swift b/PadelClub/Views/Navigation/Agenda/ActivityView.swift index 48bdf11..9c222ce 100644 --- a/PadelClub/Views/Navigation/Agenda/ActivityView.swift +++ b/PadelClub/Views/Navigation/Agenda/ActivityView.swift @@ -178,6 +178,12 @@ struct ActivityView: View { federalDataViewModel.federalTournaments.removeAll() NetworkFederalService.shared.formId = "" _gatherFederalTournaments() + } else if navigation.agendaDestination == .activity { + runningTournaments.forEach { t in + if let tournament = t as? Tournament { + tournament.lastTeamRefresh = nil + } + } } } .task { diff --git a/PadelClub/Views/Navigation/Agenda/EventListView.swift b/PadelClub/Views/Navigation/Agenda/EventListView.swift index f7de342..1218d7d 100644 --- a/PadelClub/Views/Navigation/Agenda/EventListView.swift +++ b/PadelClub/Views/Navigation/Agenda/EventListView.swift @@ -196,12 +196,8 @@ struct EventListView: View { Button { Task { - do { - try await pcTournaments.concurrentForEach { tournament in - try await tournament.refreshTeamList() - } - } catch { - Logger.error(error) + await pcTournaments.concurrentForEach { tournament in + await tournament.refreshTeamList() } } } label: { @@ -262,13 +258,10 @@ struct EventListView: View { private func _tournamentView(_ tournament: Tournament) -> some View { NavigationLink(value: tournament) { - TournamentCellView(tournament: tournament, shouldTournamentBeOver: tournament.shouldTournamentBeOver()) - .task { - do { - try await tournament.refreshTeamList() - } catch { - Logger.error(error) - } + TournamentCellView(tournament: tournament) + .id(tournament.lastTeamRefresh) + .task(priority: .background) { + await tournament.refreshTeamList() } } .listRowView(isActive: tournament.enableOnlineRegistration, color: .green, hideColorVariation: true) diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index f6b0387..12d9a63 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -258,7 +258,7 @@ struct InscriptionManagerView: View { } } } - .task { + .task(priority: .background) { await _refreshList(forced: false) } .refreshable { @@ -556,19 +556,20 @@ struct InscriptionManagerView: View { refreshStatus = nil do { - try await self.tournament.refreshTeamList() + await self.tournament.refreshTeamList() _setHash() - - self.refreshResult = "La synchronization a réussi" + if let lastTeamRefresh = self.tournament.lastTeamRefresh?.formatted(date: .abbreviated, time: .shortened) { + self.refreshResult = "Dernière m-à-j : \(lastTeamRefresh)" + } else { + self.refreshResult = "La synchronization a réussi" + } self.refreshStatus = true } catch { Logger.error(error) - self.refreshResult = "La synchronization a échoué" self.refreshStatus = false - tournament.refreshInProgress = false } } diff --git a/PadelClub/Views/Tournament/Shared/TournamentCellView.swift b/PadelClub/Views/Tournament/Shared/TournamentCellView.swift index 589ac6e..2c9d62d 100644 --- a/PadelClub/Views/Tournament/Shared/TournamentCellView.swift +++ b/PadelClub/Views/Tournament/Shared/TournamentCellView.swift @@ -15,7 +15,7 @@ struct TournamentCellView: View { let tournament: FederalTournamentHolder // let color: Color = .black var displayStyle: DisplayStyle = .wide - var shouldTournamentBeOver: Bool = false + @State var shouldTournamentBeOver: Bool = false var event: Event? { guard let federalTournament = tournament as? FederalTournament else { return nil } @@ -159,6 +159,9 @@ struct TournamentCellView: View { let hasStarted = tournament.inscriptionClosed() || tournament.hasStarted() let word = hasStarted ? "équipe" : "inscription" Text(word + teamCount.pluralSuffix) + .task(priority: .background) { + self.shouldTournamentBeOver = await tournament.shouldTournamentBeOver() + } } } } diff --git a/PadelClub/Views/Tournament/TournamentBuildView.swift b/PadelClub/Views/Tournament/TournamentBuildView.swift index d44e5aa..62b72c9 100644 --- a/PadelClub/Views/Tournament/TournamentBuildView.swift +++ b/PadelClub/Views/Tournament/TournamentBuildView.swift @@ -54,7 +54,7 @@ struct TournamentBuildView: View { } } } - .task { + .task(priority: .background) { groupStageStatus = await tournament.groupStageStatus() } } @@ -105,7 +105,7 @@ struct TournamentBuildView: View { } } } - .task { + .task(priority: .background) { bracketStatus = await tournament.bracketStatus() } } @@ -157,7 +157,7 @@ struct TournamentBuildView: View { } } } - .task { + .task(priority: .background) { scheduleStatus = await tournament.scheduleStatus() } @@ -178,7 +178,7 @@ struct TournamentBuildView: View { } } } - .task { + .task(priority: .background) { callStatus = await tournament.callStatus() } @@ -199,7 +199,7 @@ struct TournamentBuildView: View { } } } - .task { + .task(priority: .background) { cashierStatus = await tournament.cashierStatus() } } diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index 92fea53..e9f9b5d 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -15,7 +15,8 @@ struct TournamentView: View { @Environment(NavigationViewModel.self) var navigation: NavigationViewModel @State var tournament: Tournament @State private var showMoreInfos: Bool = false - + @State var shouldTournamentBeOver: Bool = false + var presentationContext: PresentationContext = .agenda let tournamentSelectionTip: TournamentSelectionTip = TournamentSelectionTip() @@ -106,7 +107,7 @@ struct TournamentView: View { TournamentBuildView(tournament: tournament) TournamentInitView(tournament: tournament) case .running: - if tournament.shouldTournamentBeOver() { + if shouldTournamentBeOver { Section { TipView(shouldTournamentBeOverTip) { actions in navigation.path.append(Screen.stateSettings) @@ -125,6 +126,9 @@ struct TournamentView: View { RegistrationInfoSheetView() } } + .task(priority: .background) { + self.shouldTournamentBeOver = await tournament.shouldTournamentBeOver() + } .environment(tournament) .id(tournament.id) .toolbarBackground(.visible, for: .navigationBar)