From 6b6ff7ce1cefe2bc376a0befa873ace2f4e8fadc Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 3 Jul 2020 20:31:48 +0800 Subject: [PATCH 1/8] Work in progress for #2184 --- Multiplatform/Shared/Add/AddFeedView.swift | 223 +++++++++++++++++++ Multiplatform/Shared/MainApp.swift | 8 +- Multiplatform/Shared/String+URLChecker.swift | 17 ++ NetNewsWire.xcodeproj/project.pbxproj | 42 +++- 4 files changed, 277 insertions(+), 13 deletions(-) create mode 100644 Multiplatform/Shared/Add/AddFeedView.swift create mode 100644 Multiplatform/Shared/String+URLChecker.swift diff --git a/Multiplatform/Shared/Add/AddFeedView.swift b/Multiplatform/Shared/Add/AddFeedView.swift new file mode 100644 index 000000000..f2fd1b0bd --- /dev/null +++ b/Multiplatform/Shared/Add/AddFeedView.swift @@ -0,0 +1,223 @@ +// +// 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.. 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() + } +} diff --git a/Multiplatform/Shared/MainApp.swift b/Multiplatform/Shared/MainApp.swift index 634dea81c..7f6f24fa7 100644 --- a/Multiplatform/Shared/MainApp.swift +++ b/Multiplatform/Shared/MainApp.swift @@ -20,6 +20,7 @@ struct MainApp: App { @StateObject private var sceneModel = SceneModel() @StateObject private var defaults = AppDefaults.shared + @State private var showSheet = false @SceneBuilder var body: some Scene { #if os(macOS) @@ -28,12 +29,15 @@ struct MainApp: App { .frame(minWidth: 600, idealWidth: 1000, maxWidth: .infinity, minHeight: 600, idealHeight: 700, maxHeight: .infinity) .environmentObject(sceneModel) .environmentObject(defaults) + .sheet(isPresented: $showSheet, onDismiss: { showSheet = false }) { + AddFeedView() + } .toolbar { ToolbarItem { - Button(action: {}, label: { + Button(action: { showSheet = true }, label: { Image(systemName: "plus").foregroundColor(.secondary) - }).help("New Feed") + }).help("Add Feed") } ToolbarItem { diff --git a/Multiplatform/Shared/String+URLChecker.swift b/Multiplatform/Shared/String+URLChecker.swift new file mode 100644 index 000000000..dd482b238 --- /dev/null +++ b/Multiplatform/Shared/String+URLChecker.swift @@ -0,0 +1,17 @@ +// +// String+URLChecker.swift +// NetNewsWire +// +// Created by Stuart Breckenridge on 3/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation + +extension String { + var isValidURL: Bool { + let regEx = "^(http|https|feed)\\://([a-zA-Z0-9\\.\\-]+(\\:[a-zA-Z0-9\\.&%\\$\\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\\-]+\\.)*[a-zA-Z0-9\\-]+\\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(\\:[0-9]+)*(/($|[a-zA-Z0-9\\.\\,\\?\\'\\\\\\+&%\\$#\\=~_\\-]+))*$" + let predicate = NSPredicate(format:"SELF MATCHES %@", argumentArray:[regEx]) + return predicate.evaluate(with: self) + } +} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index eabc431a0..9e14e7dc1 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -21,6 +21,10 @@ 175942AB24AD533200585066 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; 1776E88E24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; }; 1776E88F24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; }; + 17925E1724AF41D000D3A4F6 /* String+URLChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17925E1624AF41D000D3A4F6 /* String+URLChecker.swift */; }; + 17925E1824AF41D000D3A4F6 /* String+URLChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17925E1624AF41D000D3A4F6 /* String+URLChecker.swift */; }; + 17930ED424AF10EE00A9BA52 /* AddFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17930ED324AF10EE00A9BA52 /* AddFeedView.swift */; }; + 17930ED524AF10EE00A9BA52 /* AddFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17930ED324AF10EE00A9BA52 /* AddFeedView.swift */; }; 179DB1DFBCF9177104B12E0F /* AccountsNewsBlurWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */; }; 179DB3CE822BFCC2D774D9F4 /* AccountsNewsBlurWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */; }; 3B3A32A5238B820900314204 /* FeedWranglerAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */; }; @@ -1717,6 +1721,8 @@ 1729529A24AA1FD200D65E66 /* MacSearchField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacSearchField.swift; sourceTree = ""; }; 172952AF24AA287100D65E66 /* CompactSidebarContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompactSidebarContainerView.swift; sourceTree = ""; }; 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDefaults.swift; sourceTree = ""; }; + 17925E1624AF41D000D3A4F6 /* String+URLChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+URLChecker.swift"; sourceTree = ""; }; + 17930ED324AF10EE00A9BA52 /* AddFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFeedView.swift; sourceTree = ""; }; 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsNewsBlurWindowController.swift; sourceTree = ""; }; 17B223DB24AC24D2001E4592 /* TimelineLayoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineLayoutView.swift; sourceTree = ""; }; 3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedWranglerAccountViewController.swift; sourceTree = ""; }; @@ -2394,6 +2400,14 @@ path = View; sourceTree = ""; }; + 17930ED224AF10CD00A9BA52 /* Add */ = { + isa = PBXGroup; + children = ( + 17930ED324AF10EE00A9BA52 /* AddFeedView.swift */, + ); + path = Add; + sourceTree = ""; + }; 17B223B924AC24A8001E4592 /* Submenus */ = { isa = PBXGroup; children = ( @@ -2748,12 +2762,14 @@ 51E499D724A912C200B667CB /* SceneModel.swift */, 51E49A0224A91FF600B667CB /* SceneNavigationView.swift */, 51C0513824A77DF800194D5E /* Assets.xcassets */, + 17930ED224AF10CD00A9BA52 /* Add */, 51A576B924AE617B00078888 /* Article */, 51919FB124AAB95300541E64 /* Images */, 514E6BFD24AD252400AC6F6E /* Previews */, 51E499FB24A9135A00B667CB /* Sidebar */, 514E6C0424AD2B0400AC6F6E /* SwiftUI Extensions */, 51919FCB24AB855000541E64 /* Timeline */, + 17925E1624AF41D000D3A4F6 /* String+URLChecker.swift */, ); path = Shared; sourceTree = ""; @@ -3945,46 +3961,46 @@ TargetAttributes = { 51314636235A7BBE00387FDC = { CreatedOnToolsVersion = 11.2; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; LastSwiftMigration = 1120; ProvisioningStyle = Automatic; }; 513C5CE5232571C2003D4054 = { CreatedOnToolsVersion = 11.0; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; }; 518B2ED12351B3DD00400001 = { CreatedOnToolsVersion = 11.2; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; TestTargetID = 840D617B2029031C009BC708; }; 51C0513C24A77DF800194D5E = { CreatedOnToolsVersion = 12.0; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; }; 51C0514324A77DF800194D5E = { CreatedOnToolsVersion = 12.0; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; }; 6581C73220CED60000F4AD34 = { - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; }; 65ED3FA2235DEF6C0081F399 = { - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; }; 65ED4090235DEF770081F399 = { - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; }; 840D617B2029031C009BC708 = { CreatedOnToolsVersion = 9.3; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { @@ -3994,7 +4010,7 @@ }; 849C645F1ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.HardenedRuntime = { @@ -4004,7 +4020,7 @@ }; 849C64701ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = FQLBNX3GP7; ProvisioningStyle = Automatic; TestTargetID = 849C645F1ED37A5D003D8FC0; }; @@ -4828,6 +4844,7 @@ 514E6C0624AD2B5F00AC6F6E /* Image-Extensions.swift in Sources */, 51E4995624A8734D00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */, 5125E6CA24AE461D002A7562 /* TimelineLayoutView.swift in Sources */, + 17925E1724AF41D000D3A4F6 /* String+URLChecker.swift in Sources */, 51E4996824A8760C00B667CB /* ArticleStyle.swift in Sources */, 51E4990024A808BB00B667CB /* FaviconGenerator.swift in Sources */, 51E4997124A8764C00B667CB /* ActivityType.swift in Sources */, @@ -4839,6 +4856,7 @@ 51E4993C24A8709900B667CB /* AppDelegate.swift in Sources */, 51E498F924A8085D00B667CB /* SmartFeed.swift in Sources */, 51A576BB24AE621800078888 /* ArticleModel.swift in Sources */, + 17930ED424AF10EE00A9BA52 /* AddFeedView.swift in Sources */, 51E4995124A8734D00B667CB /* ExtensionPointManager.swift in Sources */, 51E4990C24A808C500B667CB /* AuthorAvatarDownloader.swift in Sources */, 51E4992124A8095000B667CB /* RSImage-Extensions.swift in Sources */, @@ -4865,6 +4883,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 17930ED524AF10EE00A9BA52 /* AddFeedView.swift in Sources */, 51E4993A24A8708800B667CB /* AppDelegate.swift in Sources */, 51E498CE24A8085D00B667CB /* UnreadFeed.swift in Sources */, 51E498C724A8085D00B667CB /* StarredFeedDelegate.swift in Sources */, @@ -4901,6 +4920,7 @@ 51E4993424A867E700B667CB /* UserInfoKey.swift in Sources */, 1776E88F24AC5F8A00E78166 /* AppDefaults.swift in Sources */, 1729529724AA1CD000D65E66 /* MacPreferencesView.swift in Sources */, + 17925E1824AF41D000D3A4F6 /* String+URLChecker.swift in Sources */, 51E4994C24A8734C00B667CB /* RedditFeedProvider-Extensions.swift in Sources */, 1729529324AA1CAA00D65E66 /* AccountsPreferencesView.swift in Sources */, 51919FAD24AA8CCA00541E64 /* UnreadCountView.swift in Sources */, From 6fb5af81af06724599312a8c5a266c91958ed263 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 3 Jul 2020 20:41:47 +0800 Subject: [PATCH 2/8] Removes Combine import. --- Multiplatform/Shared/Add/AddFeedView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Multiplatform/Shared/Add/AddFeedView.swift b/Multiplatform/Shared/Add/AddFeedView.swift index f2fd1b0bd..826f7f221 100644 --- a/Multiplatform/Shared/Add/AddFeedView.swift +++ b/Multiplatform/Shared/Add/AddFeedView.swift @@ -9,7 +9,6 @@ import SwiftUI import Account import RSCore -import Combine fileprivate enum AddFeedError: LocalizedError { From 467c16465ff1520f117e6c5925ac3b9799a520d8 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 3 Jul 2020 21:43:55 +0800 Subject: [PATCH 3/8] add feed changes sorts alignment issues with monospaced fonts. --- Multiplatform/Shared/Add/AddFeedView.swift | 27 +++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/Multiplatform/Shared/Add/AddFeedView.swift b/Multiplatform/Shared/Add/AddFeedView.swift index 826f7f221..0481844be 100644 --- a/Multiplatform/Shared/Add/AddFeedView.swift +++ b/Multiplatform/Shared/Add/AddFeedView.swift @@ -77,15 +77,25 @@ struct AddFeedView: View { #if os(macOS) var macForm: some View { VStack(alignment: .leading) { + HStack { + Spacer() + Image(systemName: "globe").foregroundColor(.accentColor).font(.title) + Text("Add a Web Feed") + .font(.title) + Spacer() + } urlTextField .textFieldStyle(RoundedBorderTextFieldStyle()) + .help("The URL of the feed you want to add.") providedNameTextField .textFieldStyle(RoundedBorderTextFieldStyle()) + .help("The name of the feed. (Optional.)") folderPicker + .help("Pick the folder you want to add the feed to.") buttonStack } .padding() - .frame(minWidth: 400) + .frame(minWidth: 450) } #endif @@ -101,20 +111,20 @@ struct AddFeedView: View { var urlTextField: some View { HStack { - Text("Feed URL:") + Text("Feed: ").font(.system(.body, design: .monospaced)) TextField("URL", text: $viewModel.providedURL) } } var providedNameTextField: some View { HStack(alignment: .lastTextBaseline) { - Text("Name:") + Text("Name: ").font(.system(.body, design: .monospaced)) TextField("Optional", text: $viewModel.providedName) } } var folderPicker: some View { - Picker("Folder", selection: $viewModel.selectedFolderIndex, content: { + Picker("Folder:", selection: $viewModel.selectedFolderIndex, content: { ForEach(0.. Date: Fri, 3 Jul 2020 21:53:43 +0800 Subject: [PATCH 4/8] Credits for url parser Work in progress for #2184 --- Multiplatform/Shared/String+URLChecker.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Multiplatform/Shared/String+URLChecker.swift b/Multiplatform/Shared/String+URLChecker.swift index dd482b238..c2cc1e1db 100644 --- a/Multiplatform/Shared/String+URLChecker.swift +++ b/Multiplatform/Shared/String+URLChecker.swift @@ -9,6 +9,8 @@ import Foundation extension String { + + /// Reference: [StackOverflow](https://stackoverflow.com/questions/161738/what-is-the-best-regular-expression-to-check-if-a-string-is-a-valid-url) var isValidURL: Bool { let regEx = "^(http|https|feed)\\://([a-zA-Z0-9\\.\\-]+(\\:[a-zA-Z0-9\\.&%\\$\\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\\-]+\\.)*[a-zA-Z0-9\\-]+\\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(\\:[0-9]+)*(/($|[a-zA-Z0-9\\.\\,\\?\\'\\\\\\+&%\\$#\\=~_\\-]+))*$" let predicate = NSPredicate(format:"SELF MATCHES %@", argumentArray:[regEx]) From a4f53bc1674bdb9ab8e038f017c8925388111c8f Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 3 Jul 2020 22:58:39 +0800 Subject: [PATCH 5/8] Using Form and monospace to keep alignment Work in progress for #2184 --- Multiplatform/Shared/Add/AddFeedView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Multiplatform/Shared/Add/AddFeedView.swift b/Multiplatform/Shared/Add/AddFeedView.swift index 0481844be..698dd7d14 100644 --- a/Multiplatform/Shared/Add/AddFeedView.swift +++ b/Multiplatform/Shared/Add/AddFeedView.swift @@ -76,7 +76,7 @@ struct AddFeedView: View { #if os(macOS) var macForm: some View { - VStack(alignment: .leading) { + Form { HStack { Spacer() Image(systemName: "globe").foregroundColor(.accentColor).font(.title) @@ -124,7 +124,7 @@ struct AddFeedView: View { } var folderPicker: some View { - Picker("Folder:", selection: $viewModel.selectedFolderIndex, content: { + Picker(" ", selection: $viewModel.selectedFolderIndex, content: { ForEach(0.. Date: Fri, 3 Jul 2020 23:43:20 +0800 Subject: [PATCH 6/8] AddWebFeed Fixes #2184 Adding Web Feeds is working for macOS and iOS. There are some alignment is. --- ...AddFeedView.swift => AddWebFeedView.swift} | 60 +++++++++++++------ Multiplatform/Shared/MainApp.swift | 2 +- .../Shared/Sidebar/SidebarToolbar.swift | 31 +++++++--- NetNewsWire.xcodeproj/project.pbxproj | 12 ++-- 4 files changed, 73 insertions(+), 32 deletions(-) rename Multiplatform/Shared/Add/{AddFeedView.swift => AddWebFeedView.swift} (82%) diff --git a/Multiplatform/Shared/Add/AddFeedView.swift b/Multiplatform/Shared/Add/AddWebFeedView.swift similarity index 82% rename from Multiplatform/Shared/Add/AddFeedView.swift rename to Multiplatform/Shared/Add/AddWebFeedView.swift index 698dd7d14..d7ec81f2c 100644 --- a/Multiplatform/Shared/Add/AddFeedView.swift +++ b/Multiplatform/Shared/Add/AddWebFeedView.swift @@ -1,5 +1,5 @@ // -// AddFeedView.swift +// AddWebFeedView.swift // NetNewsWire // // Created by Stuart Breckenridge on 3/7/20. @@ -10,7 +10,7 @@ import SwiftUI import Account import RSCore -fileprivate enum AddFeedError: LocalizedError { +fileprivate enum AddWebFeedError: LocalizedError { case none, alreadySubscribed, initialDownload, noFeeds @@ -29,12 +29,12 @@ fileprivate enum AddFeedError: LocalizedError { } -fileprivate class AddFeedViewModel: ObservableObject { +fileprivate class AddWebFeedViewModel: ObservableObject { @Published var providedURL: String = "" @Published var providedName: String = "" @Published var selectedFolderIndex: Int = 0 - @Published var addFeedError: AddFeedError? { + @Published var addFeedError: AddWebFeedError? { didSet { addFeedError != .none ? (showError = true) : (showError = false) } @@ -54,10 +54,10 @@ fileprivate class AddFeedViewModel: ObservableObject { } -struct AddFeedView: View { +struct AddWebFeedView: View { @Environment(\.presentationMode) private var presentationMode - @ObservedObject private var viewModel = AddFeedViewModel() + @ObservedObject private var viewModel = AddWebFeedViewModel() @ViewBuilder var body: some View { #if os(iOS) @@ -100,31 +100,48 @@ struct AddFeedView: View { #endif #if os(iOS) - var iosForm: some View { - NavigationLink { - List { - Text("PLACEHOLDER") - }.listStyle(InsetGroupedListStyle()) + @ViewBuilder var iosForm: some View { + NavigationView { + Form { + urlTextField + providedNameTextField + folderPicker + } + .listStyle(InsetGroupedListStyle()) + .navigationTitle("Add Web Feed") + .navigationBarTitleDisplayMode(.inline) + .navigationBarItems(leading: + Button("Cancel", action: { + presentationMode.wrappedValue.dismiss() + }) + .help("Cancel Add Feed") + , trailing: + Button("Add", action: { + addWebFeed() + }) + .disabled(!viewModel.providedURL.isValidURL) + .help("Add Feed") + ) } } #endif var urlTextField: some View { HStack { - Text("Feed: ").font(.system(.body, design: .monospaced)) + Text("Feed:") TextField("URL", text: $viewModel.providedURL) } } var providedNameTextField: some View { HStack(alignment: .lastTextBaseline) { - Text("Name: ").font(.system(.body, design: .monospaced)) + Text("Name:") TextField("Optional", text: $viewModel.providedName) } } var folderPicker: some View { - Picker(" ", selection: $viewModel.selectedFolderIndex, content: { + Picker("Folder:", selection: $viewModel.selectedFolderIndex, content: { ForEach(0.. Date: Sat, 4 Jul 2020 00:06:43 +0800 Subject: [PATCH 7/8] Merges fixes from colorPalette work --- .../Shared/Sidebar/SidebarToolbar.swift | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/Multiplatform/Shared/Sidebar/SidebarToolbar.swift b/Multiplatform/Shared/Sidebar/SidebarToolbar.swift index 48705d4bb..490620443 100644 --- a/Multiplatform/Shared/Sidebar/SidebarToolbar.swift +++ b/Multiplatform/Shared/Sidebar/SidebarToolbar.swift @@ -8,26 +8,28 @@ import SwiftUI -struct SidebarToolbar: View { -<<<<<<< HEAD - - enum ToolbarSheets { - case none, web, twitter, reddit, folder, settings - } +fileprivate enum ToolbarSheets { + case none, web, twitter, reddit, folder, settings +} + +fileprivate class SidebarViewModel: ObservableObject { - @State private var showSheet: Bool = false - @State private var sheetToShow: ToolbarSheets = .none { + @Published var showSheet: Bool = false + @Published var sheetToShow: ToolbarSheets = .none { didSet { sheetToShow != .none ? (showSheet = true) : (showSheet = false) } } - @State private var showActionSheet: Bool = false -======= + @Published var showActionSheet: Bool = false + @Published var showAddSheet: Bool = false +} + +struct SidebarToolbar: View { + @EnvironmentObject private var appSettings: AppDefaults - @State private var showSettings: Bool = false - @State private var showAddSheet: Bool = false ->>>>>>> pr/7 + @StateObject private var viewModel = SidebarViewModel() + var addActionSheetButtons = [ Button(action: {}, label: { Text("Add Feed") }) @@ -38,53 +40,51 @@ struct SidebarToolbar: View { Divider() HStack(alignment: .center) { Button(action: { - sheetToShow = .settings + viewModel.sheetToShow = .settings }, label: { Image(systemName: "gear") .font(.title3) .foregroundColor(.accentColor) }).help("Settings") + Spacer() + Text("Last updated") .font(.caption) .foregroundColor(.secondary) + Spacer() + Button(action: { - showActionSheet = true + viewModel.showActionSheet = true }, label: { Image(systemName: "plus") .font(.title3) .foregroundColor(.accentColor) }) .help("Add") - .actionSheet(isPresented: $showActionSheet) { + .actionSheet(isPresented: $viewModel.showActionSheet) { ActionSheet(title: Text("Add"), buttons: [ .cancel(), - .default(Text("Add Web Feed"), action: { sheetToShow = .web }), + .default(Text("Add Web Feed"), action: { viewModel.sheetToShow = .web }), .default(Text("Add Twitter Feed")), .default(Text("Add Reddit Feed")), .default(Text("Add Folder")) ]) } - } .padding(.horizontal, 16) .padding(.bottom, 12) .padding(.top, 4) } .background(VisualEffectBlur(blurStyle: .systemChromeMaterial).edgesIgnoringSafeArea(.bottom)) -<<<<<<< HEAD - .sheet(isPresented: $showSheet, onDismiss: { sheetToShow = .none }) { - switch sheetToShow { - case .web: + .sheet(isPresented: $viewModel.showSheet, onDismiss: { viewModel.sheetToShow = .none }) { + if viewModel.sheetToShow == .web { AddWebFeedView() - default: - EmptyView() } -======= - .sheet(isPresented: $showSettings, onDismiss: { showSettings = false }) { - SettingsView().modifier(PreferredColorSchemeModifier(preferredColorScheme: appSettings.userInterfaceColorPalette)) ->>>>>>> pr/7 + if viewModel.sheetToShow == .settings { + SettingsView().modifier(PreferredColorSchemeModifier(preferredColorScheme: appSettings.userInterfaceColorPalette)) + } } } From 74438de343279ca80fca76da097dfc68eaafdf2c Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sat, 4 Jul 2020 00:10:47 +0800 Subject: [PATCH 8/8] Renames viewmodel --- Multiplatform/Shared/Sidebar/SidebarToolbar.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Multiplatform/Shared/Sidebar/SidebarToolbar.swift b/Multiplatform/Shared/Sidebar/SidebarToolbar.swift index 490620443..9aec8c5c8 100644 --- a/Multiplatform/Shared/Sidebar/SidebarToolbar.swift +++ b/Multiplatform/Shared/Sidebar/SidebarToolbar.swift @@ -12,7 +12,7 @@ fileprivate enum ToolbarSheets { case none, web, twitter, reddit, folder, settings } -fileprivate class SidebarViewModel: ObservableObject { +fileprivate class SidebarToolbarViewModel: ObservableObject { @Published var showSheet: Bool = false @Published var sheetToShow: ToolbarSheets = .none { @@ -28,7 +28,7 @@ fileprivate class SidebarViewModel: ObservableObject { struct SidebarToolbar: View { @EnvironmentObject private var appSettings: AppDefaults - @StateObject private var viewModel = SidebarViewModel() + @StateObject private var viewModel = SidebarToolbarViewModel() var addActionSheetButtons = [