mirror of
https://github.com/kiwix/kiwix-apple.git
synced 2025-09-14 14:49:50 -04:00
Onboarding (#393)
* button setup * LibraryCategoryView message * refactor * deprecation * ActionCell * onboarding * LibraryLanguageView sort order * preload favicons * refresh when on screen * dependency * schema
This commit is contained in:
parent
c8cdf00a67
commit
05331031c5
@ -15,8 +15,8 @@
|
||||
"repositoryURL": "https://github.com/realm/realm-cocoa",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "83a07f6a508c3427058d9e2c466208d0b6a960fa",
|
||||
"version": "10.12.0"
|
||||
"revision": "e7e7f072a1571435049683ca43c51501de2612fd",
|
||||
"version": "10.14.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -24,8 +24,8 @@
|
||||
"repositoryURL": "https://github.com/realm/realm-core",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "e72b3078bfc5c3f69a0b18f7a220be27e28c463f",
|
||||
"version": "11.2.0"
|
||||
"revision": "fdb2157346dcdf0c2677b3608d9a4c30315fa7f0",
|
||||
"version": "11.3.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -28,11 +28,23 @@ class OPDSRefreshOperation: Operation {
|
||||
let parser = OPDSStreamParser()
|
||||
try parser.parse(data: data)
|
||||
try processData(parser: parser)
|
||||
|
||||
// if library has never been refreshed before, preload wikipedia favicons
|
||||
if Defaults[.libraryLastRefresh] == nil, let languageCode = Locale.current.languageCode {
|
||||
(try? Realm())?.objects(ZimFile.self)
|
||||
.filter(NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
NSPredicate(format: "categoryRaw = %@", ZimFile.Category.wikipedia.rawValue),
|
||||
NSPredicate(format: "languageCode = %@", languageCode),
|
||||
NSPredicate(format: "faviconData = nil"),
|
||||
NSPredicate(format: "faviconURL != nil"),
|
||||
]))
|
||||
.forEach { FaviconDownloadService.shared.download(zimFile: $0) }
|
||||
}
|
||||
|
||||
DispatchQueue.main.sync {
|
||||
// apply language filter if library has never been refreshed
|
||||
if Defaults[.libraryLastRefresh] == nil, let code = Locale.current.languageCode {
|
||||
Defaults[.libraryLanguageCodes] = [code]
|
||||
// if library has never been refreshed before, apply initial language filter
|
||||
if Defaults[.libraryLastRefresh] == nil, let languageCode = Locale.current.languageCode {
|
||||
Defaults[.libraryLanguageCodes] = [languageCode]
|
||||
}
|
||||
|
||||
// update last library refresh time
|
||||
|
@ -24,12 +24,12 @@ class ZimFile: Object, ObjectKeyIdentifiable {
|
||||
@Persisted(indexed: true) var languageCode: String = ""
|
||||
@Persisted(indexed: true) var creationDate: Date = Date()
|
||||
@Persisted(indexed: true) var size: Int64 = 0
|
||||
@Persisted var articleCount: Int64 = 0
|
||||
@Persisted var mediaCount: Int64 = 0
|
||||
@Persisted(indexed: true) var articleCount: Int64 = 0
|
||||
@Persisted(indexed: true) var mediaCount: Int64 = 0
|
||||
@Persisted(indexed: true) var categoryRaw: String = Category.other.rawValue
|
||||
@Persisted(indexed: true) var stateRaw: String = State.remote.rawValue
|
||||
@Persisted var creator: String = ""
|
||||
@Persisted var publisher: String = ""
|
||||
@Persisted var categoryRaw: String = Category.other.rawValue
|
||||
@Persisted var stateRaw: String = State.remote.rawValue
|
||||
|
||||
// MARK: - bool properties
|
||||
|
||||
|
@ -10,15 +10,12 @@ import os
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
#endif
|
||||
import Combine
|
||||
import Defaults
|
||||
import RealmSwift
|
||||
|
||||
class LibraryService {
|
||||
static let shared = LibraryService()
|
||||
|
||||
private var faviconDownloadPipeline: Any?
|
||||
|
||||
func isFileInDocumentDirectory(zimFileID: String) -> Bool {
|
||||
if let fileName = ZimFileService.shared.getFileURL(zimFileID: zimFileID)?.lastPathComponent,
|
||||
let documentDirectoryURL = try? FileManager.default.url(
|
||||
@ -60,6 +57,11 @@ class LibraryService {
|
||||
|
||||
#if canImport(UIKit)
|
||||
static let autoUpdateInterval: TimeInterval = 3600.0 * 6
|
||||
static var isOutdated: Bool {
|
||||
guard let lastRefresh = Defaults[.libraryLastRefresh] else { return true }
|
||||
return Date().timeIntervalSince(lastRefresh) > LibraryService.autoUpdateInterval
|
||||
}
|
||||
|
||||
func applyAutoUpdateSetting() {
|
||||
UIApplication.shared.setMinimumBackgroundFetchInterval(
|
||||
Defaults[.libraryAutoRefresh] ? LibraryService.autoUpdateInterval : UIApplication.backgroundFetchIntervalNever
|
||||
|
@ -24,9 +24,10 @@ enum ExternalLinkLoadingPolicy: String, CaseIterable, CustomStringConvertible, I
|
||||
}
|
||||
}
|
||||
|
||||
enum LibraryLanguageSortingMode: String, Codable, CustomStringConvertible, Defaults.Serializable {
|
||||
enum LibraryLanguageSortingMode: String, CaseIterable, Codable, CustomStringConvertible, Identifiable, Defaults.Serializable {
|
||||
case alphabetically, byCount
|
||||
|
||||
var id: String { self.rawValue }
|
||||
var description: String {
|
||||
switch self {
|
||||
case .alphabetically:
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import Defaults
|
||||
import RealmSwift
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
@ -67,6 +68,11 @@ class LibraryViewController: UISplitViewController, UISplitViewControllerDelegat
|
||||
searchResultsController.rootView.zimFileSelected = {
|
||||
[unowned self] zimFileID, title in self.showZimFile(zimFileID, title)
|
||||
}
|
||||
|
||||
// refresh library when library is opened, but only when library has been previously refreshed
|
||||
if Defaults[.libraryLastRefresh] != nil, Defaults[.libraryAutoRefresh], LibraryService.isOutdated {
|
||||
LibraryOperationQueue.shared.addOperation(OPDSRefreshOperation())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Delegates
|
||||
|
@ -13,22 +13,28 @@ import WebKit
|
||||
struct ActionCell: View {
|
||||
let title: String
|
||||
let isDestructive: Bool
|
||||
let alignment: HorizontalAlignment
|
||||
let action: (() -> Void)
|
||||
|
||||
init(title: String, isDestructive: Bool = false, action: @escaping (() -> Void) = {}) {
|
||||
init(title: String,
|
||||
isDestructive: Bool = false,
|
||||
alignment: HorizontalAlignment = .center,
|
||||
action: @escaping (() -> Void) = {}
|
||||
) {
|
||||
self.title = title
|
||||
self.isDestructive = isDestructive
|
||||
self.alignment = alignment
|
||||
self.action = action
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button(action: action, label: {
|
||||
HStack {
|
||||
Spacer()
|
||||
if alignment != .leading { Spacer() }
|
||||
Text(title)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(isDestructive ? .red : nil)
|
||||
Spacer()
|
||||
if alignment != .trailing { Spacer() }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import RealmSwift
|
||||
@available(iOS 13.0, *)
|
||||
struct LibraryCategoryView: View {
|
||||
@ObservedObject private var viewModel: ViewModel
|
||||
@Default(.libraryLastRefresh) private var libraryLastRefresh
|
||||
|
||||
let category: ZimFile.Category
|
||||
var zimFileTapped: (String, String) -> Void = { _, _ in }
|
||||
@ -25,19 +26,7 @@ struct LibraryCategoryView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if let languages = viewModel.languages, languages.isEmpty {
|
||||
InfoView(
|
||||
imageSystemName: {
|
||||
if #available(iOS 14.0, *) {
|
||||
return "text.book.closed"
|
||||
} else {
|
||||
return "book"
|
||||
}
|
||||
}(),
|
||||
title: "No Zim Files",
|
||||
help: "Enable some other languages to see zim files under this category."
|
||||
)
|
||||
} else if let languages = viewModel.languages {
|
||||
if let languages = viewModel.languages, !languages.isEmpty {
|
||||
List {
|
||||
ForEach(languages) { language in
|
||||
Section(header: languages.count > 1 ? Text(language.name) : nil) {
|
||||
@ -50,6 +39,27 @@ struct LibraryCategoryView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let languages = viewModel.languages, languages.isEmpty {
|
||||
InfoView(
|
||||
imageSystemName: {
|
||||
if #available(iOS 14.0, *) {
|
||||
return "text.book.closed"
|
||||
} else {
|
||||
return "book"
|
||||
}
|
||||
}(),
|
||||
title: "No Zim Files",
|
||||
help: {
|
||||
if libraryLastRefresh == nil {
|
||||
return "Download online catalog to see zim files under this category."
|
||||
} else {
|
||||
return "Enable some other languages to see zim files under this category."
|
||||
}
|
||||
}()
|
||||
)
|
||||
} else {
|
||||
// show nothing when catagory hasn't been fully loaded
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,17 +21,19 @@ struct LibraryLanguageView: View {
|
||||
list.navigationTitle("Languages").toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Picker(selection: $sortingMode, label: Image(systemName: "arrow.up.arrow.down")) {
|
||||
Text("Alphabetically").tag(LibraryLanguageSortingMode.alphabetically)
|
||||
Text("By Count").tag(LibraryLanguageSortingMode.byCount)
|
||||
ForEach(LibraryLanguageSortingMode.allCases) { sortingMode in
|
||||
Text(sortingMode.description).tag(sortingMode)
|
||||
}
|
||||
}.pickerStyle(MenuPickerStyle())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
list.navigationBarItems(trailing: HStack {
|
||||
Picker("Language Sorting Mode", selection: $sortingMode, content: {
|
||||
Text("A-Z").tag(LibraryLanguageSortingMode.alphabetically)
|
||||
Text("By Count").tag(LibraryLanguageSortingMode.byCount)
|
||||
}).pickerStyle(SegmentedPickerStyle())
|
||||
Picker(selection: $sortingMode, label: Image(systemName: "arrow.up.arrow.down")) {
|
||||
ForEach(LibraryLanguageSortingMode.allCases) { sortingMode in
|
||||
Text(sortingMode.description).tag(sortingMode)
|
||||
}
|
||||
}.pickerStyle(SegmentedPickerStyle())
|
||||
Spacer(minLength: 60)
|
||||
})
|
||||
}
|
||||
|
@ -7,11 +7,13 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
import RealmSwift
|
||||
|
||||
/// A list of all on device & downloading zim files and all zim file categories.
|
||||
@available(iOS 13.0, *)
|
||||
struct LibraryPrimaryView: View {
|
||||
@Default(.libraryLastRefresh) private var libraryLastRefresh
|
||||
@ObservedResults(
|
||||
ZimFile.self,
|
||||
configuration: Realm.defaultConfig,
|
||||
@ -27,11 +29,20 @@ struct LibraryPrimaryView: View {
|
||||
),
|
||||
sortDescriptor: SortDescriptor(keyPath: "size", ascending: false)
|
||||
) private var download
|
||||
@ObservedObject private var viewModel = ViewModel()
|
||||
var zimFileSelected: (String, String) -> Void = { _, _ in }
|
||||
var categorySelected: (ZimFile.Category) -> Void = { _ in }
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
if onDevice.count == 0, libraryLastRefresh == nil {
|
||||
Section(header: Text("Get Started")) {
|
||||
ActionCell(
|
||||
title: viewModel.isRefreshing ? "Refreshing..." : "Download Online Catalog",
|
||||
alignment: .leading
|
||||
) { viewModel.refresh() }.disabled(viewModel.isRefreshing)
|
||||
}
|
||||
}
|
||||
if onDevice.count > 0 {
|
||||
Section(header: Text("On Device")) {
|
||||
ForEach(onDevice) { zimFile in
|
||||
@ -72,4 +83,20 @@ struct LibraryPrimaryView: View {
|
||||
}
|
||||
}.listStyle(GroupedListStyle())
|
||||
}
|
||||
|
||||
class ViewModel: ObservableObject {
|
||||
@Published private(set) var isRefreshing = false
|
||||
|
||||
init() {
|
||||
if let operation = LibraryOperationQueue.shared.currentOPDSRefreshOperation {
|
||||
isRefreshing = !operation.isFinished
|
||||
}
|
||||
}
|
||||
|
||||
func refresh() {
|
||||
guard LibraryOperationQueue.shared.currentOPDSRefreshOperation == nil else { return }
|
||||
LibraryOperationQueue.shared.addOperation(OPDSRefreshOperation())
|
||||
isRefreshing = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ struct LibrarySettingsView: View {
|
||||
}
|
||||
|
||||
private class ViewModel: ObservableObject {
|
||||
@Published var isRefreshing = false
|
||||
@Published private(set) var isRefreshing = false
|
||||
|
||||
private var refreshObserver: NSKeyValueObservation?
|
||||
private let autoRefreshObserver = Defaults.observe(.libraryAutoRefresh) { _ in
|
||||
|
Loading…
x
Reference in New Issue
Block a user