// // AddFeedView.swift // NetNewsWire // // Created by Stuart Breckenridge on 3/7/20. // Copyright © 2020 Ranchero Software. All rights reserved. // import SwiftUI import Account import RSCore import Combine fileprivate enum AddFeedError: LocalizedError { case none, alreadySubscribed, initialDownload, noFeeds var errorDescription: String? { switch self { case .alreadySubscribed: return NSLocalizedString("Can’t add this feed because you’ve already subscribed to it.", comment: "Feed finder") case .initialDownload: return NSLocalizedString("Can’t add this feed because of a download error.", comment: "Feed finder") case .noFeeds: return NSLocalizedString("Can’t add a feed because no feed was found.", comment: "Feed finder") default: return nil } } } fileprivate class AddFeedViewModel: ObservableObject { @Published var providedURL: String = "" @Published var providedName: String = "" @Published var selectedFolderIndex: Int = 0 @Published var addFeedError: AddFeedError? { didSet { addFeedError != .none ? (showError = true) : (showError = false) } } @Published var showError: Bool = false @Published var containers: [Container] = [] @Published var showProgressIndicator: Bool = false init() { for account in AccountManager.shared.sortedActiveAccounts { containers.append(account) if let sortedFolders = account.sortedFolders { containers.append(contentsOf: sortedFolders) } } } } struct AddFeedView: View { @Environment(\.presentationMode) private var presentationMode @ObservedObject private var viewModel = AddFeedViewModel() @ViewBuilder var body: some View { #if os(iOS) iosForm #else macForm .onAppear { pasteUrlFromPasteboard() }.alert(isPresented: $viewModel.showError) { Alert(title: Text("Oops"), message: Text(viewModel.addFeedError!.localizedDescription), dismissButton: Alert.Button.cancel({ viewModel.addFeedError = .none })) } #endif } #if os(macOS) var macForm: some View { VStack(alignment: .leading) { urlTextField .textFieldStyle(RoundedBorderTextFieldStyle()) providedNameTextField .textFieldStyle(RoundedBorderTextFieldStyle()) folderPicker buttonStack } .padding() .frame(minWidth: 400) } #endif #if os(iOS) var iosForm: some View { NavigationLink { List { Text("PLACEHOLDER") }.listStyle(InsetGroupedListStyle()) } } #endif var urlTextField: some View { HStack { Text("Feed URL:") TextField("URL", text: $viewModel.providedURL) } } var providedNameTextField: some View { HStack(alignment: .lastTextBaseline) { Text("Name:") TextField("Optional", text: $viewModel.providedName) } } var folderPicker: some View { Picker("Folder", selection: $viewModel.selectedFolderIndex, content: { ForEach(0..<viewModel.containers.count, id: \.self, content: { index in if let containerName = (viewModel.containers[index] as? DisplayNameProvider)?.nameForDisplay { if viewModel.containers[index] is Folder { Text("\(viewModel.containers[index].account?.nameForDisplay ?? "") / \(containerName)").tag(index) } else { Text(containerName).tag(index) } } }) }) } var buttonStack: some View { HStack { if viewModel.showProgressIndicator == true { ProgressView() .frame(width: 25, height: 25) } Spacer() Button("Cancel", action: { presentationMode.wrappedValue.dismiss() }) Button("Add", action: { addWebFeed() }).disabled(!viewModel.providedURL.isValidURL) } } #if os(macOS) func pasteUrlFromPasteboard() { guard let stringFromPasteboard = urlStringFromPasteboard, stringFromPasteboard.isValidURL else { return } viewModel.providedURL = stringFromPasteboard } #endif } private extension AddFeedView { var urlStringFromPasteboard: String? { if let urlString = NSPasteboard.urlString(from: NSPasteboard.general) { return urlString.normalizedURL } return nil } struct AccountAndFolderSpecifier { let account: Account let folder: Folder? } func accountAndFolderFromContainer(_ container: Container) -> AccountAndFolderSpecifier? { if let account = container as? Account { return AccountAndFolderSpecifier(account: account, folder: nil) } if let folder = container as? Folder, let account = folder.account { return AccountAndFolderSpecifier(account: account, folder: folder) } return nil } func addWebFeed() { if let account = accountAndFolderFromContainer(viewModel.containers[viewModel.selectedFolderIndex])?.account { viewModel.showProgressIndicator = true let container = viewModel.containers[viewModel.selectedFolderIndex] if account.hasWebFeed(withURL: viewModel.providedURL) { viewModel.addFeedError = .alreadySubscribed viewModel.showProgressIndicator = false return } account.createWebFeed(url: viewModel.providedURL, name: viewModel.providedName, container: container, completion: { result in viewModel.showProgressIndicator = false switch result { case .success(let feed): NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.webFeed: feed]) presentationMode.wrappedValue.dismiss() case .failure(let error): switch error { case AccountError.createErrorAlreadySubscribed: self.viewModel.addFeedError = .alreadySubscribed return case AccountError.createErrorNotFound: self.viewModel.addFeedError = .noFeeds return default: print("Error") } } }) } } } struct AddFeedView_Previews: PreviewProvider { static var previews: some View { AddFeedView() } }