Wait for initial profiles (#847)

Show progress view until initial local/remote profiles are fetched. May
visually improve later.
This commit is contained in:
Davide 2024-11-11 12:08:22 +01:00 committed by GitHub
parent 49d22e6e67
commit 30ccd58d4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 43 additions and 10 deletions

View File

@ -114,11 +114,13 @@ private struct ContainerModifier: ViewModifier {
func body(content: Content) -> some View { func body(content: Content) -> some View {
debugChanges() debugChanges()
return content 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) .searchable(text: $search)
.onChange(of: search) { .onChange(of: search) {
profileManager.search(byName: $0) profileManager.search(byName: $0)
} }
.themeAnimation(on: profileManager.isReady, category: .profiles)
.themeAnimation(on: profileManager.headers, category: .profiles) .themeAnimation(on: profileManager.headers, category: .profiles)
} }
} }

View File

@ -29,12 +29,20 @@ import PassepartoutKit
@MainActor @MainActor
public final class ProfileManager: ObservableObject { public final class ProfileManager: ObservableObject {
private enum Observer: CaseIterable {
case local
case remote
}
public enum Event { public enum Event {
case save(Profile) case save(Profile)
case remove([Profile.ID]) case remove([Profile.ID])
} }
// MARK: Dependencies
private let repository: ProfileRepository private let repository: ProfileRepository
private let backupRepository: ProfileRepository? private let backupRepository: ProfileRepository?
@ -47,6 +55,8 @@ public final class ProfileManager: ObservableObject {
private let processor: ProfileProcessor? private let processor: ProfileProcessor?
// MARK: State
@Published @Published
private var profiles: [Profile] private var profiles: [Profile]
@ -56,10 +66,19 @@ public final class ProfileManager: ObservableObject {
} }
} }
private var allRemoteProfiles: [Profile.ID: Profile]
@Published @Published
public private(set) var isRemoteImportingEnabled: Bool public private(set) var isRemoteImportingEnabled: Bool
private var allRemoteProfiles: [Profile.ID: Profile] public var isReady: Bool {
waitingObservers.isEmpty
}
@Published
private var waitingObservers: Set<Observer>
// MARK: Publishers
public let didChange: PassthroughSubject<Event, Never> public let didChange: PassthroughSubject<Event, Never>
@ -78,15 +97,17 @@ public final class ProfileManager: ObservableObject {
} }
mirrorsRemoteRepository = false mirrorsRemoteRepository = false
processor = nil processor = nil
self.profiles = [] self.profiles = []
allProfiles = profiles.reduce(into: [:]) { allProfiles = profiles.reduce(into: [:]) {
$0[$1.id] = $1 $0[$1.id] = $1
} }
allRemoteProfiles = [:] allRemoteProfiles = [:]
isRemoteImportingEnabled = false
waitingObservers = []
didChange = PassthroughSubject() didChange = PassthroughSubject()
searchSubject = CurrentValueSubject("") searchSubject = CurrentValueSubject("")
isRemoteImportingEnabled = false
subscriptions = [] subscriptions = []
remoteSubscriptions = [] remoteSubscriptions = []
} }
@ -104,13 +125,19 @@ public final class ProfileManager: ObservableObject {
self.remoteRepositoryBlock = remoteRepositoryBlock self.remoteRepositoryBlock = remoteRepositoryBlock
self.mirrorsRemoteRepository = mirrorsRemoteRepository self.mirrorsRemoteRepository = mirrorsRemoteRepository
self.processor = processor self.processor = processor
profiles = [] profiles = []
allProfiles = [:] allProfiles = [:]
allRemoteProfiles = [:] allRemoteProfiles = [:]
isRemoteImportingEnabled = false
if remoteRepositoryBlock != nil {
waitingObservers = [.local, .remote]
} else {
waitingObservers = [.local]
}
didChange = PassthroughSubject() didChange = PassthroughSubject()
searchSubject = CurrentValueSubject("") searchSubject = CurrentValueSubject("")
isRemoteImportingEnabled = false
subscriptions = [] subscriptions = []
remoteSubscriptions = [] remoteSubscriptions = []
} }
@ -323,10 +350,10 @@ extension ProfileManager {
self.isRemoteImportingEnabled = isRemoteImportingEnabled self.isRemoteImportingEnabled = isRemoteImportingEnabled
remoteSubscriptions.removeAll() remoteSubscriptions.removeAll()
remoteRepository = remoteRepositoryBlock(isRemoteImportingEnabled) let newRepository = remoteRepositoryBlock(isRemoteImportingEnabled)
if let initialProfiles = try await remoteRepository?.fetchProfiles() { let initialProfiles = try await newRepository.fetchProfiles()
reloadRemoteProfiles(initialProfiles, importing: false) reloadRemoteProfiles(initialProfiles, importing: false)
} remoteRepository = newRepository
remoteRepository? remoteRepository?
.profilesPublisher .profilesPublisher
@ -345,7 +372,9 @@ private extension ProfileManager {
allProfiles = result.reduce(into: [:]) { allProfiles = result.reduce(into: [:]) {
$0[$1.id] = $1 $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 // should not be imported at all, but you never know
if let processor { if let processor {
@ -369,7 +398,9 @@ private extension ProfileManager {
allRemoteProfiles = result.reduce(into: [:]) { allRemoteProfiles = result.reduce(into: [:]) {
$0[$1.id] = $1 $0[$1.id] = $1
} }
objectWillChange.send() if waitingObservers.contains(.remote) {
waitingObservers.remove(.remote)
}
guard importing else { guard importing else {
return return