diff --git a/SwiftUI/Model/DefaultKeys.swift b/SwiftUI/Model/DefaultKeys.swift index eeb04b9d..1c02bf50 100644 --- a/SwiftUI/Model/DefaultKeys.swift +++ b/SwiftUI/Model/DefaultKeys.swift @@ -34,7 +34,6 @@ extension Defaults.Keys { static let libraryAutoRefresh = Key("libraryAutoRefresh", default: true) static let libraryUsingOldISOLangCodes = Key("libraryUsingOldISOLangCodes", default: true) static let libraryLastRefresh = Key("libraryLastRefresh") - static let libraryLastRefreshTime = Key("libraryLastRefreshTime") static let isFirstLaunch = Key("isFirstLaunch", default: true) static let downloadUsingCellular = Key("downloadUsingCellular", default: false) diff --git a/ViewModel/LibraryViewModel.swift b/ViewModel/LibraryViewModel.swift index 96f8416d..a8a3c72f 100644 --- a/ViewModel/LibraryViewModel.swift +++ b/ViewModel/LibraryViewModel.swift @@ -11,16 +11,23 @@ import os import Defaults +public enum LibraryState { + case initial + case inProgress + case complete +} + public class LibraryViewModel: ObservableObject { @Published var selectedZimFile: ZimFile? @MainActor @Published public private(set) var error: Error? - @MainActor @Published public private(set) var isInProgress = false - + @MainActor @Published public private(set) var state: LibraryState + private let urlSession: URLSession private let context: NSManagedObjectContext private var insertionCount = 0 private var deletionCount = 0 + @MainActor public init(urlSession: URLSession? = nil) { self.urlSession = urlSession ?? URLSession.shared @@ -28,6 +35,11 @@ public class LibraryViewModel: ObservableObject { context.persistentStoreCoordinator = Database.shared.container.persistentStoreCoordinator context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump context.undoManager = nil + if Defaults[.libraryLastRefresh] == nil { + state = .initial + } else { + state = .complete + } } public func start(isUserInitiated: Bool) { @@ -36,19 +48,22 @@ public class LibraryViewModel: ObservableObject { @MainActor public func start(isUserInitiated: Bool) async { + guard state != .inProgress else { return } + let oldState = state do { - guard !isInProgress else { return } - isInProgress = true - defer { isInProgress = false } - // decide if refresh should proceed let lastRefresh: Date? = Defaults[.libraryLastRefresh] let hasAutoRefresh: Bool = Defaults[.libraryAutoRefresh] let isStale = (lastRefresh?.timeIntervalSinceNow ?? -3600) <= -3600 guard isUserInitiated || (hasAutoRefresh && isStale) else { return } + state = .inProgress + // refresh library - guard let data = try await fetchData() else { return } + guard let data = try await fetchData() else { + state = oldState + return + } let parser = try await parse(data: data) // delete all old ISO Lang Code entries if needed, by passing in an empty parser if Defaults[.libraryUsingOldISOLangCodes] { @@ -68,12 +83,14 @@ public class LibraryViewModel: ObservableObject { // reset error error = nil - + state = .complete + // logging os_log("Refresh finished -- addition: %d, deletion: %d, total: %d", log: Log.OPDS, type: .default, insertionCount, deletionCount, parser.zimFileIDs.count) } catch { self.error = error + state = oldState } } diff --git a/Views/Library/ZimFilesNew.swift b/Views/Library/ZimFilesNew.swift index 43b27df5..0b646e77 100644 --- a/Views/Library/ZimFilesNew.swift +++ b/Views/Library/ZimFilesNew.swift @@ -69,7 +69,7 @@ struct ZimFilesNew: View { } #endif ToolbarItem { - if viewModel.isInProgress { + if viewModel.state == .inProgress { ProgressView() #if os(macOS) .scaleEffect(0.5) diff --git a/Views/Settings/LanguageSelector.swift b/Views/Settings/LanguageSelector.swift index 0a6f7893..e3310ebf 100644 --- a/Views/Settings/LanguageSelector.swift +++ b/Views/Settings/LanguageSelector.swift @@ -14,9 +14,10 @@ import Defaults #if os(macOS) struct LanguageSelector: View { @Default(.libraryLanguageCodes) private var selected + @EnvironmentObject private var library: LibraryViewModel @State private var languages = [Language]() @State private var sortOrder = [KeyPathComparator(\Language.count, order: .reverse)] - + var body: some View { Table(languages, sortOrder: $sortOrder) { TableColumn("") { language in @@ -35,9 +36,20 @@ struct LanguageSelector: View { Text(language.count.formatted()) } } + .opacity( library.state == .complete ? 1.0 : 0.3) .tableStyle(.bordered(alternatesRowBackgrounds: true)) .onChange(of: sortOrder) { languages.sort(using: $0) } - .task { + .onChange(of: library.state) { state in + guard state != .inProgress else { return } + reloadLanguages() + } + .onAppear { + reloadLanguages() + } + } + + private func reloadLanguages() { + Task { languages = await Languages.fetch() languages.sort(using: sortOrder) } @@ -48,7 +60,7 @@ struct LanguageSelector: View { @Default(.libraryLanguageSortingMode) private var sortingMode @State private var showing = [Language]() @State private var hiding = [Language]() - + var body: some View { List { Section { diff --git a/Views/Settings/Settings.swift b/Views/Settings/Settings.swift index 036a2dd8..ea58a329 100644 --- a/Views/Settings/Settings.swift +++ b/Views/Settings/Settings.swift @@ -57,12 +57,12 @@ struct LibrarySettings: View { var body: some View { VStack(spacing: 16) { - SettingSection(name: "library_settings.catalog.title".localized) { + SettingSection(name: "library_settings.catalog.title".localized, alignment: .top) { HStack(spacing: 6) { Button("library_settings.button.refresh_now".localized) { library.start(isUserInitiated: true) - }.disabled(library.isInProgress) - if library.isInProgress { + }.disabled(library.state == .inProgress) + if library.state == .inProgress { ProgressView().progressViewStyle(.circular).scaleEffect(0.5).frame(height: 1) } Spacer() @@ -173,7 +173,7 @@ struct Settings: View { LanguageSelector() } label: { SelectedLanaguageLabel() - } + }.disabled(library.state != .complete) Toggle("library_settings.toggle.cellular".localized, isOn: $downloadUsingCellular) } header: { Text("library_settings.tab.library.title".localized) @@ -189,7 +189,7 @@ struct Settings: View { Spacer() LibraryLastRefreshTime().foregroundColor(.secondary) } - if library.isInProgress { + if library.state == .inProgress { HStack { Text("catalog_settings.refreshing.text".localized).foregroundColor(.secondary) Spacer() diff --git a/Views/Welcome.swift b/Views/Welcome.swift index b58622da..10a0d278 100644 --- a/Views/Welcome.swift +++ b/Views/Welcome.swift @@ -44,8 +44,8 @@ struct Welcome: View { } .padding() .ignoresSafeArea() - .onChange(of: library.isInProgress) { isInProgress in - guard !isInProgress else { return } + .onChange(of: library.state) { state in + guard state != .inProgress else { return } #if os(macOS) navigation.currentItem = .categories #elseif os(iOS) @@ -70,7 +70,8 @@ struct Welcome: View { GridSection(title: "welcome.main_page.title".localized) { ForEach(zimFiles) { zimFile in Button { - guard let url = ZimFileService.shared.getMainPageURL(zimFileID: zimFile.fileID) else { return } + guard let url = ZimFileService.shared + .getMainPageURL(zimFileID: zimFile.fileID) else { return } browser.load(url: url) } label: { ZimFileCell(zimFile, prominent: .name) @@ -121,7 +122,7 @@ struct Welcome: View { } label: { HStack { Spacer() - if library.isInProgress { + if library.state == .inProgress { #if os(macOS) Text("welcome.button.status.fetching.text".localized) #elseif os(iOS) @@ -135,7 +136,7 @@ struct Welcome: View { } Spacer() }.padding(6) - }.disabled(library.isInProgress) + }.disabled(library.state == .inProgress) } .font(.subheadline) .buttonStyle(.bordered)