From 30ccd58d4a6e979d24584b3cdad386a9f897406c Mon Sep 17 00:00:00 2001 From: Davide Date: Mon, 11 Nov 2024 12:08:22 +0100 Subject: [PATCH] Wait for initial profiles (#847) Show progress view until initial local/remote profiles are fetched. May visually improve later. --- .../Views/App/ProfileContainerView.swift | 4 +- .../Business/ProfileManager.swift | 49 +++++++++++++++---- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileContainerView.swift b/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileContainerView.swift index ca862045..88f169c7 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileContainerView.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileContainerView.swift @@ -114,11 +114,13 @@ private struct ContainerModifier: ViewModifier { func body(content: Content) -> some View { debugChanges() return content - .themeEmptyContent(if: !profileManager.hasProfiles, message: Strings.Views.Profiles.Folders.noProfiles) + .themeProgress(if: !profileManager.isReady) + .themeEmptyContent(if: profileManager.isReady && !profileManager.hasProfiles, message: Strings.Views.Profiles.Folders.noProfiles) .searchable(text: $search) .onChange(of: search) { profileManager.search(byName: $0) } + .themeAnimation(on: profileManager.isReady, category: .profiles) .themeAnimation(on: profileManager.headers, category: .profiles) } } diff --git a/Passepartout/Library/Sources/CommonLibrary/Business/ProfileManager.swift b/Passepartout/Library/Sources/CommonLibrary/Business/ProfileManager.swift index f64f5ec4..fa8c43dd 100644 --- a/Passepartout/Library/Sources/CommonLibrary/Business/ProfileManager.swift +++ b/Passepartout/Library/Sources/CommonLibrary/Business/ProfileManager.swift @@ -29,12 +29,20 @@ import PassepartoutKit @MainActor public final class ProfileManager: ObservableObject { + private enum Observer: CaseIterable { + case local + + case remote + } + public enum Event { case save(Profile) case remove([Profile.ID]) } + // MARK: Dependencies + private let repository: ProfileRepository private let backupRepository: ProfileRepository? @@ -47,6 +55,8 @@ public final class ProfileManager: ObservableObject { private let processor: ProfileProcessor? + // MARK: State + @Published private var profiles: [Profile] @@ -56,10 +66,19 @@ public final class ProfileManager: ObservableObject { } } + private var allRemoteProfiles: [Profile.ID: Profile] + @Published public private(set) var isRemoteImportingEnabled: Bool - private var allRemoteProfiles: [Profile.ID: Profile] + public var isReady: Bool { + waitingObservers.isEmpty + } + + @Published + private var waitingObservers: Set + + // MARK: Publishers public let didChange: PassthroughSubject @@ -78,15 +97,17 @@ public final class ProfileManager: ObservableObject { } mirrorsRemoteRepository = false processor = nil + self.profiles = [] allProfiles = profiles.reduce(into: [:]) { $0[$1.id] = $1 } allRemoteProfiles = [:] + isRemoteImportingEnabled = false + waitingObservers = [] didChange = PassthroughSubject() searchSubject = CurrentValueSubject("") - isRemoteImportingEnabled = false subscriptions = [] remoteSubscriptions = [] } @@ -104,13 +125,19 @@ public final class ProfileManager: ObservableObject { self.remoteRepositoryBlock = remoteRepositoryBlock self.mirrorsRemoteRepository = mirrorsRemoteRepository self.processor = processor + profiles = [] allProfiles = [:] allRemoteProfiles = [:] + isRemoteImportingEnabled = false + if remoteRepositoryBlock != nil { + waitingObservers = [.local, .remote] + } else { + waitingObservers = [.local] + } didChange = PassthroughSubject() searchSubject = CurrentValueSubject("") - isRemoteImportingEnabled = false subscriptions = [] remoteSubscriptions = [] } @@ -323,10 +350,10 @@ extension ProfileManager { self.isRemoteImportingEnabled = isRemoteImportingEnabled remoteSubscriptions.removeAll() - remoteRepository = remoteRepositoryBlock(isRemoteImportingEnabled) - if let initialProfiles = try await remoteRepository?.fetchProfiles() { - reloadRemoteProfiles(initialProfiles, importing: false) - } + let newRepository = remoteRepositoryBlock(isRemoteImportingEnabled) + let initialProfiles = try await newRepository.fetchProfiles() + reloadRemoteProfiles(initialProfiles, importing: false) + remoteRepository = newRepository remoteRepository? .profilesPublisher @@ -345,7 +372,9 @@ private extension ProfileManager { allProfiles = result.reduce(into: [:]) { $0[$1.id] = $1 } - // objectWillChange implicit from updating profiles in didSet + if waitingObservers.contains(.local) { + waitingObservers.remove(.local) + } // should not be imported at all, but you never know if let processor { @@ -369,7 +398,9 @@ private extension ProfileManager { allRemoteProfiles = result.reduce(into: [:]) { $0[$1.id] = $1 } - objectWillChange.send() + if waitingObservers.contains(.remote) { + waitingObservers.remove(.remote) + } guard importing else { return