mirror of
https://github.com/kiwix/kiwix-apple.git
synced 2025-08-03 12:37:15 -04:00
202 lines
7.7 KiB
Swift
202 lines
7.7 KiB
Swift
// This file is part of Kiwix for iOS & macOS.
|
|
//
|
|
// Kiwix is free software; you can redistribute it and/or modify it
|
|
// under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 3 of the License, or
|
|
// any later version.
|
|
//
|
|
// Kiwix is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Kiwix; If not, see https://www.gnu.org/licenses/.
|
|
|
|
import CoreData
|
|
import SwiftUI
|
|
|
|
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
|
|
Toggle("", isOn: Binding {
|
|
selected.contains(language.code)
|
|
} set: { isSelected in
|
|
if isSelected {
|
|
selected.insert(language.code)
|
|
} else if selected.count > 1 {
|
|
selected.remove(language.code)
|
|
}
|
|
})
|
|
}.width(14)
|
|
TableColumn("language_selector.name.title".localized, value: \.name)
|
|
TableColumn("language_selector.count.table.title".localized, value: \.count) { language in
|
|
Text(language.count.formatted())
|
|
}
|
|
}
|
|
.opacity( library.state == .complete ? 1.0 : 0.3)
|
|
.tableStyle(.bordered(alternatesRowBackgrounds: true))
|
|
.onChange(of: sortOrder) { languages.sort(using: $0) }
|
|
.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)
|
|
}
|
|
}
|
|
}
|
|
#elseif os(iOS)
|
|
struct LanguageSelector: View {
|
|
@Default(.libraryLanguageSortingMode) private var sortingMode
|
|
@State private var showing = [Language]()
|
|
@State private var hiding = [Language]()
|
|
|
|
var body: some View {
|
|
List {
|
|
Section {
|
|
if showing.isEmpty {
|
|
Text("language_selector.no_language.title".localized).foregroundColor(.secondary)
|
|
} else {
|
|
ForEach(showing) { language in
|
|
Button { hide(language) } label: { LanguageLabel(language: language) }
|
|
}
|
|
}
|
|
} header: { Text("language_selector.showing.header".localized) }
|
|
Section {
|
|
ForEach(hiding) { language in
|
|
Button { show(language) } label: { LanguageLabel(language: language) }
|
|
}
|
|
} header: { Text("language_selector.hiding.header".localized) }
|
|
}
|
|
.listStyle(.insetGrouped)
|
|
.navigationTitle("language_selector.navitation.title".localized)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
Picker(selection: $sortingMode) {
|
|
ForEach(LibraryLanguageSortingMode.allCases) { sortingMode in
|
|
Text(sortingMode.name).tag(sortingMode)
|
|
}
|
|
} label: {
|
|
Label("language_selector.toolbar.sorting".localized, systemImage: "arrow.up.arrow.down")
|
|
}.pickerStyle(.menu)
|
|
}
|
|
.onAppear {
|
|
Task {
|
|
var languages = await Languages.fetch()
|
|
languages.sort(by: Languages.compare(lhs:rhs:))
|
|
showing = languages.filter { Defaults[.libraryLanguageCodes].contains($0.code) }
|
|
hiding = languages.filter { !Defaults[.libraryLanguageCodes].contains($0.code) }
|
|
}
|
|
}
|
|
.onChange(of: sortingMode) { _ in
|
|
showing.sort(by: Languages.compare(lhs:rhs:))
|
|
hiding.sort(by: Languages.compare(lhs:rhs:))
|
|
}
|
|
}
|
|
|
|
private func show(_ language: Language) {
|
|
Defaults[.libraryLanguageCodes].insert(language.code)
|
|
withAnimation {
|
|
hiding.removeAll { $0.code == language.code }
|
|
showing.append(language)
|
|
showing.sort(by: Languages.compare(lhs:rhs:))
|
|
}
|
|
}
|
|
|
|
private func hide(_ language: Language) {
|
|
guard Defaults[.libraryLanguageCodes].count > 1 else {
|
|
// we should not remove all languages, it will produce empty results
|
|
return
|
|
}
|
|
Defaults[.libraryLanguageCodes].remove(language.code)
|
|
withAnimation {
|
|
showing.removeAll { $0.code == language.code }
|
|
hiding.append(language)
|
|
hiding.sort(by: Languages.compare(lhs:rhs:))
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct LanguageLabel: View {
|
|
let language: Language
|
|
|
|
var body: some View {
|
|
HStack {
|
|
Text(language.name).foregroundColor(.primary)
|
|
Spacer()
|
|
Text("\(language.count)").foregroundColor(.secondary)
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
class Languages {
|
|
/// Retrieve a list of languages.
|
|
/// - Returns: languages with count of zim files in each language
|
|
static func fetch() async -> [Language] {
|
|
let count = NSExpressionDescription()
|
|
count.name = "count"
|
|
count.expression = NSExpression(forFunction: "count:", arguments: [NSExpression(forKeyPath: "languageCode")])
|
|
count.expressionResultType = .integer16AttributeType
|
|
|
|
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ZimFile")
|
|
// exclude the already downloaded files, they might have invalid language set
|
|
// but we are mainly interested in fetched content
|
|
fetchRequest.predicate = ZimFile.Predicate.notDownloaded
|
|
fetchRequest.propertiesToFetch = ["languageCode", count]
|
|
fetchRequest.propertiesToGroupBy = ["languageCode"]
|
|
fetchRequest.resultType = .dictionaryResultType
|
|
|
|
let languages: [Language] = await withCheckedContinuation { continuation in
|
|
Database.shared.performBackgroundTask { context in
|
|
guard let results = try? context.fetch(fetchRequest) else {
|
|
continuation.resume(returning: [])
|
|
return
|
|
}
|
|
let collector = LanguageCollector()
|
|
for result in results {
|
|
if let result = result as? NSDictionary,
|
|
let languageCodes = result["languageCode"] as? String,
|
|
let count = result["count"] as? Int {
|
|
collector.addLanguages(codes: languageCodes, count: count)
|
|
}
|
|
}
|
|
continuation.resume(returning: collector.languages())
|
|
}
|
|
}
|
|
return languages
|
|
}
|
|
|
|
/// Compare two languages based on library language sorting order.
|
|
/// Can be removed once support for iOS 14 drops.
|
|
/// - Parameters:
|
|
/// - lhs: one language to compare
|
|
/// - rhs: another language to compare
|
|
/// - Returns: if one language should appear before or after another
|
|
static func compare(lhs: Language, rhs: Language) -> Bool {
|
|
switch Defaults[.libraryLanguageSortingMode] {
|
|
case .alphabetically:
|
|
return lhs.name.caseInsensitiveCompare(rhs.name) == .orderedAscending
|
|
case .byCounts:
|
|
return lhs.count > rhs.count
|
|
}
|
|
}
|
|
}
|