232 lines
6.7 KiB
Swift
232 lines
6.7 KiB
Swift
import Foundation
|
|
import Models
|
|
import Services
|
|
import SwiftUI
|
|
import Utils
|
|
|
|
@MainActor
|
|
public class LibraryAddFeedViewModel: NSObject, ObservableObject {
|
|
let dataService: DataService
|
|
let feedURL: String
|
|
let prefetchContent: Bool
|
|
let folder: String
|
|
let selectedLabels: [LinkedItemLabel]
|
|
let toastOperationHandler: ToastOperationHandler?
|
|
|
|
@Published var isLoading = true
|
|
@Published var errorMessage: String = ""
|
|
@Published var showErrorMessage: Bool = false
|
|
|
|
@Published var feeds: [Feed] = []
|
|
@Published var selected: [String] = []
|
|
|
|
init(dataService: DataService, feedURL: String, prefetchContent: Bool, folder: String, selectedLabels: [LinkedItemLabel], toastOperationHandler: ToastOperationHandler?) {
|
|
self.dataService = dataService
|
|
self.feedURL = feedURL
|
|
self.prefetchContent = prefetchContent
|
|
self.folder = folder
|
|
self.selectedLabels = selectedLabels
|
|
self.toastOperationHandler = toastOperationHandler
|
|
}
|
|
|
|
func scanFeed() async {
|
|
isLoading = true
|
|
if let feedURL = URL(string: feedURL) {
|
|
let result = try? await dataService.scanFeed(feedURL: feedURL)
|
|
if let feeds = result {
|
|
self.feeds = feeds
|
|
selected = feeds.map(\.url)
|
|
} else {
|
|
feeds = []
|
|
error("Error adding feed")
|
|
}
|
|
} else {
|
|
error("invalid URL")
|
|
}
|
|
isLoading = false
|
|
}
|
|
|
|
func addFeeds() async {
|
|
if let toastOperationHandler = toastOperationHandler {
|
|
toastOperationHandler.update(OperationStatus.isPerforming, "Subscribing...")
|
|
|
|
let selected = self.selected
|
|
|
|
let addTask = Task.detached(priority: .background) {
|
|
_ = await withTaskGroup(of: Bool.self) { group in
|
|
for feedURL in selected {
|
|
group.addTask {
|
|
(try? await self.dataService.subscribeToFeed(feedURL: feedURL, folder: self.folder, fetchContent: self.prefetchContent)) ?? false
|
|
}
|
|
}
|
|
|
|
var successCount = 0
|
|
var failureCount = 0
|
|
for await value in group {
|
|
if value {
|
|
successCount += 1
|
|
} else {
|
|
failureCount += 1
|
|
}
|
|
}
|
|
|
|
let hasFailures = failureCount
|
|
DispatchQueue.main.async {
|
|
if hasFailures > 0 {
|
|
toastOperationHandler.update(OperationStatus.failure, "Failed to subscribe to \(hasFailures) feeds")
|
|
} else {
|
|
toastOperationHandler.update(OperationStatus.success, "Subscribed")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
toastOperationHandler.performOperation(addTask)
|
|
} else {
|
|
_ = await withTaskGroup(of: Bool.self) { group in
|
|
for feedURL in selected {
|
|
group.addTask {
|
|
(try? await self.dataService.subscribeToFeed(feedURL: feedURL, folder: self.folder, fetchContent: self.prefetchContent)) ?? false
|
|
}
|
|
}
|
|
|
|
var successCount = 0
|
|
var failureCount = 0
|
|
for await value in group {
|
|
if value {
|
|
successCount += 1
|
|
} else {
|
|
failureCount += 1
|
|
}
|
|
}
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(4000)) {
|
|
if failureCount > 0 {
|
|
showInLibrarySnackbar("Failed to add \(failureCount) feeds")
|
|
} else {
|
|
showInLibrarySnackbar("Added \(successCount) feed\(successCount == 0 ? "" : "s")")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func setLabelsRule(dataService _: DataService, existingRule _: Rule?, ruleName _: String, filter _: String, labelIDs _: [String]) async {
|
|
// Task {
|
|
// operationMessage = "Creating label rule..."
|
|
// operationStatus = .isPerforming
|
|
// do {
|
|
// // Make sure the labels have been created
|
|
// await loadLabels(dataService: dataService)
|
|
// let existingLabelIDs = labels?.map(\.unwrappedID) ?? []
|
|
// if labelIDs.first(where: { !existingLabelIDs.contains($0) }) != nil {
|
|
// throw BasicError.message(messageText: "Label not created")
|
|
// }
|
|
//
|
|
// _ = try await dataService.createOrUpdateAddLabelsRule(
|
|
// existingID: existingRule?.id,
|
|
// name: ruleName,
|
|
// filter: filter,
|
|
// labelIDs: labelIDs
|
|
// )
|
|
// if let newRules = try? await dataService.rules() {
|
|
// if !newRules.contains(where: { $0.name == ruleName }) {
|
|
// throw BasicError.message(messageText: "Rule not created")
|
|
// }
|
|
// rules = newRules
|
|
// }
|
|
// operationMessage = "Rule created"
|
|
// operationStatus = .success
|
|
// } catch {
|
|
// operationMessage = "Failed to create label rule"
|
|
// operationStatus = .failure
|
|
// }
|
|
// }
|
|
}
|
|
|
|
func error(_ msg: String) {
|
|
errorMessage = msg
|
|
showErrorMessage = true
|
|
isLoading = false
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
public struct LibraryScanFeedView: View {
|
|
let dismiss: () -> Void
|
|
@StateObject var viewModel: LibraryAddFeedViewModel
|
|
|
|
func isSelected(_ url: String) -> Bool {
|
|
viewModel.selected.contains(url)
|
|
}
|
|
|
|
var innerBody: some View {
|
|
if viewModel.isLoading {
|
|
AnyView(ProgressView().frame(maxWidth: .infinity, alignment: .center))
|
|
} else if viewModel.feeds.count == 0 {
|
|
AnyView(Text("No feeds found for URL"))
|
|
} else {
|
|
AnyView(List {
|
|
Section("Choose the feeds to add") {
|
|
ForEach(viewModel.feeds, id: \.title) { feed in
|
|
Button(action: {
|
|
if !isSelected(feed.url) {
|
|
viewModel.selected.append(feed.url)
|
|
} else {
|
|
if let idx = viewModel.selected.firstIndex(of: feed.url) {
|
|
viewModel.selected.remove(at: idx)
|
|
}
|
|
}
|
|
}, label: {
|
|
HStack {
|
|
Text(feed.title)
|
|
Spacer()
|
|
if isSelected(feed.url) {
|
|
Image(systemName: "checkmark")
|
|
}
|
|
}
|
|
.contentShape(Rectangle())
|
|
})
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
public var body: some View {
|
|
Group {
|
|
#if os(iOS)
|
|
Form {
|
|
innerBody
|
|
.navigationTitle("Select Feeds")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
}.task {
|
|
await viewModel.scanFeed()
|
|
}
|
|
#else
|
|
innerBody
|
|
#endif
|
|
}
|
|
.toolbar {
|
|
ToolbarItem(placement: .barTrailing) {
|
|
if viewModel.selected.count > 0 {
|
|
Button(action: {
|
|
dismiss()
|
|
showInLibrarySnackbar("Adding feeds...")
|
|
Task {
|
|
await viewModel.addFeeds()
|
|
}
|
|
}, label: {
|
|
Text("Add").bold().disabled(viewModel.selected.count < 1)
|
|
})
|
|
} else {
|
|
Button(action: {
|
|
dismiss()
|
|
}, label: {
|
|
Text("Done").bold()
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|