From 959eef6a1ac6e1520bbf7f680d75778cc39025b0 Mon Sep 17 00:00:00 2001 From: Rizwan Mohamed Ibrahim Date: Thu, 2 Jul 2020 21:01:56 +0530 Subject: [PATCH 01/24] Add color palette implementation for Appearance settings --- Multiplatform/iOS/AppDelegate.swift | 29 +++++++++ Multiplatform/iOS/Settings/SettingsView.swift | 6 +- .../Submenus/ColorPaletteContainerView.swift | 60 +++++++++++++++++++ NetNewsWire.xcodeproj/project.pbxproj | 7 ++- 4 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 Multiplatform/iOS/Settings/Submenus/ColorPaletteContainerView.swift diff --git a/Multiplatform/iOS/AppDelegate.swift b/Multiplatform/iOS/AppDelegate.swift index 3df75f60d..a3539f5da 100644 --- a/Multiplatform/iOS/AppDelegate.swift +++ b/Multiplatform/iOS/AppDelegate.swift @@ -103,6 +103,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD UNUserNotificationCenter.current().delegate = self userNotificationManager = UserNotificationManager() + NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange), name: UserDefaults.didChangeNotification, object: nil) + + // extensionContainersFile = ExtensionContainersFile() // extensionFeedAddRequestFile = ExtensionFeedAddRequestFile() @@ -388,3 +391,29 @@ private extension AppDelegate { } } + +private extension AppDelegate { + @objc func userDefaultsDidChange() { + updateUserInterfaceStyle() + } + + var window: UIWindow? { + guard let scene = UIApplication.shared.connectedScenes.first, + let windowSceneDelegate = scene.delegate as? UIWindowSceneDelegate, + let window = windowSceneDelegate.window else { + return nil + } + return window + } + + func updateUserInterfaceStyle() { + switch AppDefaults.shared.userInterfaceColorPalette { + case .automatic: + window?.overrideUserInterfaceStyle = .unspecified + case .light: + window?.overrideUserInterfaceStyle = .light + case .dark: + window?.overrideUserInterfaceStyle = .dark + } + } +} diff --git a/Multiplatform/iOS/Settings/SettingsView.swift b/Multiplatform/iOS/Settings/SettingsView.swift index b08cd249b..9aca56100 100644 --- a/Multiplatform/iOS/Settings/SettingsView.swift +++ b/Multiplatform/iOS/Settings/SettingsView.swift @@ -155,12 +155,12 @@ struct SettingsView: View { var appearance: some View { Section(header: Text("Appearance"), content: { NavigationLink( - destination: EmptyView(), + destination: ColorPaletteContainerView().environmentObject(settings), label: { HStack { - Text("Color Pallete") + Text("Color Palette") Spacer() - Text("Automatic") + Text(settings.userInterfaceColorPalette.description) .foregroundColor(.secondary) } }) diff --git a/Multiplatform/iOS/Settings/Submenus/ColorPaletteContainerView.swift b/Multiplatform/iOS/Settings/Submenus/ColorPaletteContainerView.swift new file mode 100644 index 000000000..79190c06e --- /dev/null +++ b/Multiplatform/iOS/Settings/Submenus/ColorPaletteContainerView.swift @@ -0,0 +1,60 @@ +// +// ColorPaletteContainerView.swift +// Multiplatform iOS +// +// Created by Rizwan on 02/07/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import SwiftUI + +struct ColorPaletteContainerView: View { + private let colorPalettes = UserInterfaceColorPalette.allCases + @EnvironmentObject private var appSettings: AppDefaults + @Environment(\.presentationMode) var presentationMode + + var body: some View { + List { + ForEach.init(0 ..< colorPalettes.count) { index in + Button(action: { + onTapColorPalette(at:index) + }) { + ColorPaletteView(colorPalette: colorPalettes[index]) + } + } + } + .listStyle(InsetGroupedListStyle()) + .navigationBarTitle("Color Palette", displayMode: .inline) + } + + func onTapColorPalette(at index: Int) { + if let colorPalette = UserInterfaceColorPalette(rawValue: index) { + appSettings.userInterfaceColorPalette = colorPalette + } + presentationMode.wrappedValue.dismiss() + } +} + +struct ColorPaletteView: View { + var colorPalette: UserInterfaceColorPalette + @EnvironmentObject private var appSettings: AppDefaults + + var body: some View { + HStack { + Text(colorPalette.description).foregroundColor(.primary) + Spacer() + if colorPalette == appSettings.userInterfaceColorPalette { + Image(systemName: "checkmark") + .foregroundColor(.blue) + } + } + } +} + +struct ColorPaletteContainerView_Previews: PreviewProvider { + static var previews: some View { + NavigationView { + ColorPaletteContainerView() + } + } +} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 32ea67edc..e39067881 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -23,7 +23,6 @@ 1776E88F24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; }; 179DB1DFBCF9177104B12E0F /* AccountsNewsBlurWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */; }; 179DB3CE822BFCC2D774D9F4 /* AccountsNewsBlurWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */; }; - 17B223DC24AC24D2001E4592 /* TimelineLayoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B223DB24AC24D2001E4592 /* TimelineLayoutView.swift */; }; 3B3A32A5238B820900314204 /* FeedWranglerAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */; }; 3B826DCB2385C84800FC1ADB /* AccountsFeedWrangler.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */; }; 3B826DCC2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */; }; @@ -621,6 +620,8 @@ 6581C73D20CED60100F4AD34 /* SafariExtensionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */; }; 6581C74020CED60100F4AD34 /* netnewswire-subscribe-to-feed.js in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73F20CED60100F4AD34 /* netnewswire-subscribe-to-feed.js */; }; 6581C74220CED60100F4AD34 /* ToolbarItemIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */; }; + 65CBAD3624AE02D50006DD91 /* TimelineLayoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B223DB24AC24D2001E4592 /* TimelineLayoutView.swift */; }; + 65CBAD5A24AE03C20006DD91 /* ColorPaletteContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65CBAD5924AE03C20006DD91 /* ColorPaletteContainerView.swift */; }; 65ED3FB7235DEF6C0081F399 /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; }; 65ED3FB8235DEF6C0081F399 /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848B937121C8C5540038DC0D /* CrashReporter.swift */; }; 65ED3FB9235DEF6C0081F399 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* IconView.swift */; }; @@ -1985,6 +1986,7 @@ 6581C73F20CED60100F4AD34 /* netnewswire-subscribe-to-feed.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "netnewswire-subscribe-to-feed.js"; sourceTree = ""; }; 6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = ToolbarItemIcon.pdf; sourceTree = ""; }; 6581C74320CED60100F4AD34 /* Subscribe_to_Feed.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Subscribe_to_Feed.entitlements; sourceTree = ""; }; + 65CBAD5924AE03C20006DD91 /* ColorPaletteContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPaletteContainerView.swift; sourceTree = ""; }; 65ED4083235DEF6C0081F399 /* NetNewsWire.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NetNewsWire.app; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED409D235DEF770081F399 /* Subscribe to Feed.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Subscribe to Feed.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED409F235DEFF00081F399 /* container-migration.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "container-migration.plist"; sourceTree = ""; }; @@ -2389,6 +2391,7 @@ isa = PBXGroup; children = ( 17B223DB24AC24D2001E4592 /* TimelineLayoutView.swift */, + 65CBAD5924AE03C20006DD91 /* ColorPaletteContainerView.swift */, ); path = Submenus; sourceTree = ""; @@ -4774,6 +4777,7 @@ 51E498F524A8085D00B667CB /* TodayFeedDelegate.swift in Sources */, 172952B024AA287100D65E66 /* CompactSidebarContainerView.swift in Sources */, 172199F124AB716900A31D04 /* SidebarToolbar.swift in Sources */, + 65CBAD5A24AE03C20006DD91 /* ColorPaletteContainerView.swift in Sources */, 51E4990B24A808C500B667CB /* ImageDownloader.swift in Sources */, 51E498F424A8085D00B667CB /* SmartFeedDelegate.swift in Sources */, 514E6BFF24AD255D00AC6F6E /* PreviewArticles.swift in Sources */, @@ -4805,6 +4809,7 @@ 51E4992624A80AAB00B667CB /* AppAssets.swift in Sources */, 514E6C0624AD2B5F00AC6F6E /* Image-Extensions.swift in Sources */, 51E4995624A8734D00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */, + 65CBAD3624AE02D50006DD91 /* TimelineLayoutView.swift in Sources */, 51E4996824A8760C00B667CB /* ArticleStyle.swift in Sources */, 51E4990024A808BB00B667CB /* FaviconGenerator.swift in Sources */, 51E4997124A8764C00B667CB /* ActivityType.swift in Sources */, From fc548ac913938c0f092121e89f5c8f9ba8de4167 Mon Sep 17 00:00:00 2001 From: Rizwan Mohamed Ibrahim Date: Fri, 3 Jul 2020 12:35:12 +0530 Subject: [PATCH 02/24] Try conditional modifier for preferredColorScheme --- Multiplatform/Shared/MainApp.swift | 21 ++++++++++++++++++- .../Shared/Sidebar/SidebarToolbar.swift | 5 +++-- Multiplatform/iOS/AppDelegate.swift | 16 +++++++------- .../Submenus/ColorPaletteContainerView.swift | 2 +- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/Multiplatform/Shared/MainApp.swift b/Multiplatform/Shared/MainApp.swift index 634dea81c..79c0a7669 100644 --- a/Multiplatform/Shared/MainApp.swift +++ b/Multiplatform/Shared/MainApp.swift @@ -146,7 +146,9 @@ struct MainApp: App { SceneNavigationView() .environmentObject(sceneModel) .environmentObject(defaults) - }.commands { + .modifier(PreferredColorSchemeModifier(preferredColorScheme: defaults.userInterfaceColorPalette)) + } + .commands { CommandGroup(after: .newItem, addition: { Button("New Feed", action: {}) .keyboardShortcut("N") @@ -193,3 +195,20 @@ struct MainApp: App { #endif } } + +struct PreferredColorSchemeModifier: ViewModifier { + + var preferredColorScheme: UserInterfaceColorPalette + + @ViewBuilder + func body(content: Content) -> some View { + switch preferredColorScheme { + case .automatic: + content + case .dark: + content.preferredColorScheme(.dark) + case .light: + content.preferredColorScheme(.light) + } + } +} diff --git a/Multiplatform/Shared/Sidebar/SidebarToolbar.swift b/Multiplatform/Shared/Sidebar/SidebarToolbar.swift index e7df534f6..dafe70564 100644 --- a/Multiplatform/Shared/Sidebar/SidebarToolbar.swift +++ b/Multiplatform/Shared/Sidebar/SidebarToolbar.swift @@ -9,7 +9,8 @@ import SwiftUI struct SidebarToolbar: View { - + + @EnvironmentObject private var appSettings: AppDefaults @State private var showSettings: Bool = false var body: some View { @@ -40,7 +41,7 @@ struct SidebarToolbar: View { } .background(VisualEffectBlur(blurStyle: .systemChromeMaterial).edgesIgnoringSafeArea(.bottom)) .sheet(isPresented: $showSettings, onDismiss: { showSettings = false }) { - SettingsView() + SettingsView().modifier(PreferredColorSchemeModifier(preferredColorScheme: appSettings.userInterfaceColorPalette)) } } diff --git a/Multiplatform/iOS/AppDelegate.swift b/Multiplatform/iOS/AppDelegate.swift index a3539f5da..717065ff6 100644 --- a/Multiplatform/iOS/AppDelegate.swift +++ b/Multiplatform/iOS/AppDelegate.swift @@ -407,13 +407,13 @@ private extension AppDelegate { } func updateUserInterfaceStyle() { - switch AppDefaults.shared.userInterfaceColorPalette { - case .automatic: - window?.overrideUserInterfaceStyle = .unspecified - case .light: - window?.overrideUserInterfaceStyle = .light - case .dark: - window?.overrideUserInterfaceStyle = .dark - } +// switch AppDefaults.shared.userInterfaceColorPalette { +// case .automatic: +// window?.overrideUserInterfaceStyle = .unspecified +// case .light: +// window?.overrideUserInterfaceStyle = .light +// case .dark: +// window?.overrideUserInterfaceStyle = .dark +// } } } diff --git a/Multiplatform/iOS/Settings/Submenus/ColorPaletteContainerView.swift b/Multiplatform/iOS/Settings/Submenus/ColorPaletteContainerView.swift index 79190c06e..3cf4ee25a 100644 --- a/Multiplatform/iOS/Settings/Submenus/ColorPaletteContainerView.swift +++ b/Multiplatform/iOS/Settings/Submenus/ColorPaletteContainerView.swift @@ -31,7 +31,7 @@ struct ColorPaletteContainerView: View { if let colorPalette = UserInterfaceColorPalette(rawValue: index) { appSettings.userInterfaceColorPalette = colorPalette } - presentationMode.wrappedValue.dismiss() + self.presentationMode.wrappedValue.dismiss() } } From 6b6ff7ce1cefe2bc376a0befa873ace2f4e8fadc Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 3 Jul 2020 20:31:48 +0800 Subject: [PATCH 03/24] 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 04/24] 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 05/24] 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 06/24] 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 e621e5e0f720462acd3b1f8cce6a2c4251d0768a Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 3 Jul 2020 09:48:14 -0500 Subject: [PATCH 07/24] Add prebuild script to generate Secrets for new multiplaform targets --- .../xcschemes/Multiplatform iOS.xcscheme | 20 +++- .../xcschemes/Multiplatform macOS.xcscheme | 96 +++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 NetNewsWire.xcodeproj/xcshareddata/xcschemes/Multiplatform macOS.xcscheme diff --git a/NetNewsWire.xcodeproj/xcshareddata/xcschemes/Multiplatform iOS.xcscheme b/NetNewsWire.xcodeproj/xcshareddata/xcschemes/Multiplatform iOS.xcscheme index eafbe066c..e06266bca 100644 --- a/NetNewsWire.xcodeproj/xcshareddata/xcschemes/Multiplatform iOS.xcscheme +++ b/NetNewsWire.xcodeproj/xcshareddata/xcschemes/Multiplatform iOS.xcscheme @@ -1,10 +1,28 @@ + version = "1.7"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From a4f53bc1674bdb9ab8e038f017c8925388111c8f Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 3 Jul 2020 22:58:39 +0800 Subject: [PATCH 08/24] 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 10:43:05 -0500 Subject: [PATCH 09/24] Change to set preferredColorScheme to nil which should make it automatic again (but doesn't) --- Multiplatform/Shared/MainApp.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Multiplatform/Shared/MainApp.swift b/Multiplatform/Shared/MainApp.swift index 79c0a7669..67b94e866 100644 --- a/Multiplatform/Shared/MainApp.swift +++ b/Multiplatform/Shared/MainApp.swift @@ -204,7 +204,7 @@ struct PreferredColorSchemeModifier: ViewModifier { func body(content: Content) -> some View { switch preferredColorScheme { case .automatic: - content + content.preferredColorScheme(nil) case .dark: content.preferredColorScheme(.dark) case .light: From d8914510f72a7c0bd6030acd19d36afb4bf46588 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 3 Jul 2020 23:43:20 +0800 Subject: [PATCH 10/24] 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: Fri, 3 Jul 2020 10:45:12 -0500 Subject: [PATCH 11/24] Refactored PreferredColorSchemeModifier out into its own swift file --- Multiplatform/Shared/MainApp.swift | 17 ------------ .../PreferredColorSchemeModifier.swift | 27 +++++++++++++++++++ NetNewsWire.xcodeproj/project.pbxproj | 7 ++++- 3 files changed, 33 insertions(+), 18 deletions(-) create mode 100644 Multiplatform/Shared/SwiftUI Extensions/PreferredColorSchemeModifier.swift diff --git a/Multiplatform/Shared/MainApp.swift b/Multiplatform/Shared/MainApp.swift index 67b94e866..eea77ae4c 100644 --- a/Multiplatform/Shared/MainApp.swift +++ b/Multiplatform/Shared/MainApp.swift @@ -195,20 +195,3 @@ struct MainApp: App { #endif } } - -struct PreferredColorSchemeModifier: ViewModifier { - - var preferredColorScheme: UserInterfaceColorPalette - - @ViewBuilder - func body(content: Content) -> some View { - switch preferredColorScheme { - case .automatic: - content.preferredColorScheme(nil) - case .dark: - content.preferredColorScheme(.dark) - case .light: - content.preferredColorScheme(.light) - } - } -} diff --git a/Multiplatform/Shared/SwiftUI Extensions/PreferredColorSchemeModifier.swift b/Multiplatform/Shared/SwiftUI Extensions/PreferredColorSchemeModifier.swift new file mode 100644 index 000000000..ba9a04d36 --- /dev/null +++ b/Multiplatform/Shared/SwiftUI Extensions/PreferredColorSchemeModifier.swift @@ -0,0 +1,27 @@ +// +// PreferredColorSchemeModifier.swift +// NetNewsWire +// +// Created by Maurice Parker on 7/3/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import SwiftUI + +struct PreferredColorSchemeModifier: ViewModifier { + + var preferredColorScheme: UserInterfaceColorPalette + + @ViewBuilder + func body(content: Content) -> some View { + switch preferredColorScheme { + case .automatic: + content.preferredColorScheme(nil) + case .dark: + content.preferredColorScheme(.dark) + case .light: + content.preferredColorScheme(.light) + } + } + +} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 541f202df..6931dca1d 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -214,6 +214,8 @@ 517A757A24451C0700B553B9 /* OAuthSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 517A755524451BD500B553B9 /* OAuthSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 517A757B24451C1500B553B9 /* OAuthSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 517A755324451BD500B553B9 /* OAuthSwift.framework */; }; 517A757C24451C1500B553B9 /* OAuthSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 517A755324451BD500B553B9 /* OAuthSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 5181C5AD24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5181C5AC24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift */; }; + 5181C5AE24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5181C5AC24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift */; }; 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */; }; 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */; }; 5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; @@ -627,7 +629,6 @@ 6581C73D20CED60100F4AD34 /* SafariExtensionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */; }; 6581C74020CED60100F4AD34 /* netnewswire-subscribe-to-feed.js in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73F20CED60100F4AD34 /* netnewswire-subscribe-to-feed.js */; }; 6581C74220CED60100F4AD34 /* ToolbarItemIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */; }; - 65CBAD3624AE02D50006DD91 /* TimelineLayoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B223DB24AC24D2001E4592 /* TimelineLayoutView.swift */; }; 65CBAD5A24AE03C20006DD91 /* ColorPaletteContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65CBAD5924AE03C20006DD91 /* ColorPaletteContainerView.swift */; }; 65ED3FB7235DEF6C0081F399 /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; }; 65ED3FB8235DEF6C0081F399 /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848B937121C8C5540038DC0D /* CrashReporter.swift */; }; @@ -1837,6 +1838,7 @@ 517630222336657E00E15FFF /* WebViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewProvider.swift; sourceTree = ""; }; 517A745A2443665000B553B9 /* UIPageViewController-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIPageViewController-Extensions.swift"; sourceTree = ""; }; 517A754424451BD500B553B9 /* OAuthSwift.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = OAuthSwift.xcodeproj; path = submodules/OAuthSwift/OAuthSwift.xcodeproj; sourceTree = ""; }; + 5181C5AC24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferredColorSchemeModifier.swift; sourceTree = ""; }; 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicLabel.swift; sourceTree = ""; }; 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicImageView.swift; sourceTree = ""; }; 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshInterval.swift; sourceTree = ""; }; @@ -2561,6 +2563,7 @@ isa = PBXGroup; children = ( 514E6C0524AD2B5F00AC6F6E /* Image-Extensions.swift */, + 5181C5AC24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift */, ); path = "SwiftUI Extensions"; sourceTree = ""; @@ -4813,6 +4816,7 @@ 51E4996924A8760C00B667CB /* ArticleStylesManager.swift in Sources */, 51E498F324A8085D00B667CB /* PseudoFeed.swift in Sources */, 51A5769624AE617200078888 /* ArticleContainerView.swift in Sources */, + 5181C5AD24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift in Sources */, 51E4996B24A8762D00B667CB /* ArticleExtractor.swift in Sources */, 51E49A0324A91FF600B667CB /* SceneNavigationView.swift in Sources */, 51E4990124A808BB00B667CB /* FaviconURLFinder.swift in Sources */, @@ -4897,6 +4901,7 @@ 51E4994F24A8734C00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */, 51E498CA24A8085D00B667CB /* SmartFeedDelegate.swift in Sources */, 51E4990524A808C300B667CB /* FeaturedImageDownloader.swift in Sources */, + 5181C5AE24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift in Sources */, 51E4991624A8090300B667CB /* ArticleUtilities.swift in Sources */, 51919FF224AB864A00541E64 /* TimelineModel.swift in Sources */, 51E4991A24A8090F00B667CB /* IconImage.swift in Sources */, From 8840bf535ffdeeafdaa2b84d0d7475b760e80d5c Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sat, 4 Jul 2020 00:06:43 +0800 Subject: [PATCH 12/24] 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 13/24] 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 = [ From c761a083b5b64d44cf342de3590723d50cc30fec Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 3 Jul 2020 12:21:08 -0500 Subject: [PATCH 14/24] Add Timeline vertical padding for macOS --- Multiplatform/Shared/Timeline/TimelineItemView.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Multiplatform/Shared/Timeline/TimelineItemView.swift b/Multiplatform/Shared/Timeline/TimelineItemView.swift index 1e1a25ae8..252ef97ac 100644 --- a/Multiplatform/Shared/Timeline/TimelineItemView.swift +++ b/Multiplatform/Shared/Timeline/TimelineItemView.swift @@ -14,7 +14,14 @@ struct TimelineItemView: View { @StateObject var articleIconImageLoader = ArticleIconImageLoader() var timelineItem: TimelineItem - + + #if os(macOS) + var verticalPadding: CGFloat = 10 + #endif + #if os(iOS) + var verticalPadding: CGFloat = 0 + #endif + var body: some View { VStack { HStack(alignment: .top) { @@ -46,6 +53,7 @@ struct TimelineItemView: View { } } } + .padding(.vertical, verticalPadding) .onAppear { articleIconImageLoader.loadImage(for: timelineItem.article) } From dd9dca57defc677a8ecfbb9b721a2767d9a82ecf Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 3 Jul 2020 19:19:40 -0500 Subject: [PATCH 15/24] Update design to more closely match the existing iOS design and Big Sur look and feel --- Multiplatform/Shared/AppAssets.swift | 136 +++++++++++++----- .../AccentColor.colorset/Contents.json | 6 +- .../MarkAllAsReadPDF.imageset/Contents.json | 16 +++ .../MarkAllAsReadPDF.pdf | Bin 0 -> 4179 bytes .../StarColor.colorset/Contents.json | 20 +++ .../markAllAsRead.symbolset/markAllAsRead.svg | 9 +- Multiplatform/Shared/MainApp.swift | 45 +++--- .../Shared/Sidebar/SidebarToolbar.swift | 11 +- .../Timeline/TimelineItemStatusView.swift | 2 - .../Contents.json | 28 ++-- .../starColor.colorset/Contents.json | 20 +-- 11 files changed, 199 insertions(+), 94 deletions(-) create mode 100644 Multiplatform/Shared/Assets.xcassets/MarkAllAsReadPDF.imageset/Contents.json create mode 100644 Multiplatform/Shared/Assets.xcassets/MarkAllAsReadPDF.imageset/MarkAllAsReadPDF.pdf create mode 100644 Multiplatform/Shared/Assets.xcassets/StarColor.colorset/Contents.json diff --git a/Multiplatform/Shared/AppAssets.swift b/Multiplatform/Shared/AppAssets.swift index b6930bb34..b31a55a7a 100644 --- a/Multiplatform/Shared/AppAssets.swift +++ b/Multiplatform/Shared/AppAssets.swift @@ -13,70 +13,102 @@ import Account struct AppAssets { static var accountLocalMacImage: RSImage! = { - return RSImage(named: "accountLocalMac") + return RSImage(named: "AccountLocalMac") }() static var accountLocalPadImage: RSImage = { - return RSImage(named: "accountLocalPad")! + return RSImage(named: "AccountLocalPad")! }() static var accountLocalPhoneImage: RSImage = { - return RSImage(named: "accountLocalPhone")! + return RSImage(named: "AccountLocalPhone")! }() static var accountCloudKitImage: RSImage = { - return RSImage(named: "accountCloudKit")! + return RSImage(named: "AccountCloudKit")! }() static var accountFeedbinImage: RSImage = { - return RSImage(named: "accountFeedbin")! + return RSImage(named: "AccountFeedbin")! }() static var accountFeedlyImage: RSImage = { - return RSImage(named: "accountFeedly")! + return RSImage(named: "AccountFeedly")! }() static var accountFeedWranglerImage: RSImage = { - return RSImage(named: "accountFeedWrangler")! + return RSImage(named: "AccountFeedWrangler")! }() static var accountFreshRSSImage: RSImage = { - return RSImage(named: "accountFreshRSS")! + return RSImage(named: "AccountFreshRSS")! }() static var accountNewsBlurImage: RSImage = { - return RSImage(named: "accountNewsBlur")! + return RSImage(named: "AccountNewsBlur")! }() + static var addMenuImage: Image = { + return Image(systemName: "plus") + }() + static var extensionPointMarsEdit: RSImage = { - return RSImage(named: "extensionPointMarsEdit")! + return RSImage(named: "ExtensionPointMarsEdit")! }() static var extensionPointMicroblog: RSImage = { - return RSImage(named: "extensionPointMicroblog")! + return RSImage(named: "ExtensionPointMicroblog")! }() static var extensionPointReddit: RSImage = { - return RSImage(named: "extensionPointReddit")! + return RSImage(named: "ExtensionPointReddit")! }() static var extensionPointTwitter: RSImage = { - return RSImage(named: "extensionPointTwitter")! + return RSImage(named: "ExtensionPointTwitter")! }() static var faviconTemplateImage: RSImage = { - return RSImage(named: "faviconTemplateImage")! + return RSImage(named: "FaviconTemplateImage")! }() - static var masterFolderImage: IconImage = { + static var settingsImage: Image = { + return Image(systemName: "gear") + }() + + static var masterFolderImage: IconImage { #if os(macOS) - return IconImage(NSImage(systemSymbolName: "folder.fill", accessibilityDescription: nil)!) + let image = NSImage(systemSymbolName: "folder.fill", accessibilityDescription: nil)! + let coloredImage = image.tinted(with: NSColor(named: "AccentColor")!) + return IconImage(coloredImage) #endif #if os(iOS) - return IconImage(UIImage(systemName: "folder.fill")!) + let image = UIImage(systemName: "folder.fill")! + let coloredImage = image.tinted(color: UIColor(named: "AccentColor")!)! + return IconImage(coloredImage) #endif + } + + static var markAllAsReadImage: Image = { + return Image("MarkAllAsRead") }() + static var markAllAsReadImagePDF: Image = { + return Image("MarkAllAsReadPDF") + }() + + static var nextUnreadArticleImage: Image = { + return Image(systemName: "chevron.down.circle") + }() + + static var openInBrowserImage: Image = { + return Image(systemName: "safari") + }() + + static var refreshImage: Image = { + return Image(systemName: "arrow.clockwise") + }() + static var searchFeedImage: IconImage = { #if os(macOS) return IconImage(NSImage(systemSymbolName: "magnifyingglass", accessibilityDescription: nil)!) @@ -86,6 +118,10 @@ struct AppAssets { #endif }() + static var shareImage: Image = { + Image(systemName: "square.and.arrow.up") + }() + static var smartFeedImage: RSImage = { #if os(macOS) return NSImage(systemSymbolName: "gear", accessibilityDescription: nil)! @@ -97,40 +133,76 @@ struct AppAssets { static var starredFeedImage: IconImage = { #if os(macOS) - return IconImage(NSImage(systemSymbolName: "star.fill", accessibilityDescription: nil)!) + let image = NSImage(systemSymbolName: "star.fill", accessibilityDescription: nil)! + let coloredImage = image.tinted(with: NSColor(named: "StarColor")!) + return IconImage(coloredImage) #endif #if os(iOS) - return IconImage(UIImage(systemName: "star.fill")!) + let image = UIImage(systemName: "star.fill")! + let coloredImage = image.tinted(color: UIColor(named: "StarColor")!)! + return IconImage(coloredImage) #endif }() static var timelineStarred: Image = { - return Image(systemName: "star.fill") - + #if os(macOS) + let image = NSImage(systemSymbolName: "star.fill", accessibilityDescription: nil)! + let coloredImage = image.tinted(with: NSColor(named: "StarColor")!) + return Image(nsImage: coloredImage) + #endif + #if os(iOS) + let image = UIImage(systemName: "star.fill")! + let coloredImage = image.tinted(color: UIColor(named: "StarColor")!)! + return Image(uiImage: coloredImage) + #endif }() - static var timelineUnread: Image = { - return Image(systemName: "circle.fill") - - }() + static var timelineUnread: Image { + #if os(macOS) + let image = NSImage(systemSymbolName: "circle.fill", accessibilityDescription: nil)! + let coloredImage = image.tinted(with: NSColor(named: "AccentColor")!) + return Image(nsImage: coloredImage) + #endif + #if os(iOS) + let image = UIImage(systemName: "circle.fill")! + let coloredImage = image.tinted(color: UIColor(named: "AccentColor")!)! + return Image(uiImage: coloredImage) + #endif + } static var todayFeedImage: IconImage = { #if os(macOS) - return IconImage(NSImage(systemSymbolName: "sun.max.fill", accessibilityDescription: nil)!) + let image = NSImage(systemSymbolName: "sun.max.fill", accessibilityDescription: nil)! + let coloredImage = image.tinted(with: .orange) + return IconImage(coloredImage) #endif #if os(iOS) - return IconImage(UIImage(systemName: "sun.max.fill")!) + let image = UIImage(systemName: "sun.max.fill")! + let coloredImage = image.tinted(color: .orange)! + return IconImage(coloredImage) #endif }() - - static var unreadFeedImage: IconImage = { + + static var toggleStarred: Image = { + return Image(systemName: "star.fill") + }() + + static var toggleRead: Image = { + return Image(systemName: "largecircle.fill.circle") + }() + + static var unreadFeedImage: IconImage { #if os(macOS) - return IconImage(NSImage(systemSymbolName: "largecircle.fill.circle", accessibilityDescription: nil)!) + let image = NSImage(systemSymbolName: "largecircle.fill.circle", accessibilityDescription: nil)! + let coloredImage = image.tinted(with: NSColor(named: "AccentColor")!) + return IconImage(coloredImage) #endif #if os(iOS) - return IconImage(UIImage(systemName: "largecircle.fill.circle")!) + let image = UIImage(systemName: "largecircle.fill.circle")! + let coloredImage = image.tinted(color: UIColor(named: "AccentColor")!)! + return IconImage(coloredImage) #endif - }() + } static func image(for accountType: AccountType) -> RSImage? { switch accountType { diff --git a/Multiplatform/Shared/Assets.xcassets/AccentColor.colorset/Contents.json b/Multiplatform/Shared/Assets.xcassets/AccentColor.colorset/Contents.json index bf3d625ad..4f1440c52 100644 --- a/Multiplatform/Shared/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/Multiplatform/Shared/Assets.xcassets/AccentColor.colorset/Contents.json @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.945", - "green" : "0.502", - "red" : "0.176" + "blue" : "0.957", + "green" : "0.620", + "red" : "0.369" } }, "idiom" : "universal" diff --git a/Multiplatform/Shared/Assets.xcassets/MarkAllAsReadPDF.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/MarkAllAsReadPDF.imageset/Contents.json new file mode 100644 index 000000000..f23296bcc --- /dev/null +++ b/Multiplatform/Shared/Assets.xcassets/MarkAllAsReadPDF.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "MarkAllAsReadPDF.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/Multiplatform/Shared/Assets.xcassets/MarkAllAsReadPDF.imageset/MarkAllAsReadPDF.pdf b/Multiplatform/Shared/Assets.xcassets/MarkAllAsReadPDF.imageset/MarkAllAsReadPDF.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e2a044d563efa11c16626ef66db1589a2f15e443 GIT binary patch literal 4179 zcmai%2{@GP_s1bi%P=#RM3zCK>@xNl24fpBmXs`6vL#!XLiR07Q3*r# z7Ox>&6dG&xCE4Xadf$F;zyJIGum5vB+jZ`9pXYw=^F7z~Il=}AElC+EC|J04YHe!1 z@Z00=)^;!yfB-nO3;4nXKw1myhR1kOo^B{S27$51IbZ;3J&cDF-WhFMr@v^lZJrB43lfR7@xhP=3$Tx7JdDClip|{zV8ULw3}(E4yV8@ zZbx%0FO$C1XWXg-A`sV5wAmTAYmFzhvlJ&U43~DB@HnlOU@ec+n9MZLqgVO$KSv^J zM~BUuIjOU&g|FAzIGwle)vA}BI66_jxi7t;_*Gbm)Sqqd^XiZ zU_-|ppZCt0G*ebdAND|Hwu4mIy@6;M|MvN7ZbBvh{=sli=%o3uJyw8lCGT1H8S?W_ zY+^sGXuVmJkIUrd%19|q=@N2JTR|AmSaD+Z9Sh#O4bGS?xKTlvrCYG)Xz`sLtX+(O zk*-)_UJCytXFlwV01{KA8D|W2e;^W?wts?TPop#tquE;&6mxV=ZUU#bf;c^5oed{r(=#c zkR?>zQMr4{;#85#@e`^Bcn`K-^V5wd>e%;?dg#IjB2T%U+3MEX#yYMtW6&0%B-ETj zYdOb@YCBX1VM9sc?9%i4=72`mvV5hYh;8%Fw`>V%>5uVBi@V)-L z4@#FfK!#B|!3H!DVIsZ=IxTiKF*65~*VNjPg7%v1Om{-jgN$pe2JKCT2+gUHSXaj7 zGWfSq!hQ#GY-r}?;E>aD;9xD4!Cok+8E;M<49hlWUz%^SI)37P>n6=fR%#Y=>epmP zN09awf~IeesGOc&bg8{+V@WF5q3)AJqk)L`z(G)gpuC3A5h}1|zubk}B3x56$;MS} z+_DFtck3x)vJo<}oY{;yPXs4gE8I2;r_a^BO-mJ-%;rIMao58#W9tk?3Bu&uSWvcA zn(!B=+*|Y*qKOWHYHIUrZ|~M4Jt_!$x^3r-NVZ#)tR_46Qq&p^6+?2XV!anj%(kL) zCY67I(_}O#N(WuQHkgK;`ufIkXSK?B8!dA#x#bG^uD>2>y_)$Rge$lL_dt_c80ntw zoUmBNx44KLn0DT792(=lZM?7iCScceRb+vbsTP}w9CKTEB2DC^+7PmMc&e%)#gwYr z{fO!m`HB8AK($6kRXzIX8-IkGO=9afvnW%{!h)MOb(X8txv%oOS)sUhjS-5Q>F<|z z&6EnbE{2@Z;ecW^rwR%SbP=7M%q*!Shy6{~gNfIQ*gxL3NIc4V&9xf-)zt87GGkbj zb6xm_5cB&|4&bn$esa)BIZ=A<+ZnFx&OWwOf#He(O^|U&(u^tQi0WEK2rPMaq*<;3km23T@978cr3Km(5&c-W4i|aNFi22GutVv^`qp47{rd_50>R)NH3+69n^UiZu!M zS@L*a#a&9cK1q8dG8>h4lCzzAA`%%(Mw_7q(3c_oY4&MmdR)_Tj;zp-?a+L5|I-3< z(I*#u8Rgg>#!0rC*~QygR;E6s{fJsn;$!J}%{|WNdAsJMm8=K69Q5+oU~FCRe9KX? z$f|$6_$d2w=&>g674)ss4d5WYApWsW>qH1~83~D{SBIGBxCmN{D2otJC%rxIpzU=M zscNHNE|dk>3Li_nXpnqXgkAqKpQifIRmID%RBx!>(8xDBTIA7ZUXoUuVWCe%1YQo5 z)=q+^eoLKrTb5Ls;F>a@dQ(_Gc`(&4)mAD?)b@1Hi?Ss?QH&h9Rf4 z<@0uXlV;o21-=s388N~b%c-+dd{aDARqu?BG(+sRphn#Ju}QIYvGLzv)2~Pqq)&1c zSet`-v6O9b5AjzE)G6^LdZxI3_Wq0#_Y(a+h>M5|%4Nu9w_mC>j#NnU%Oqvk z!VV_)BIhb=qXKGnT)wx=DbFSG5O^ARxOq%@Qeo|IZh{BlEn$i<1M8@EwqSY=vY4`% zd2RzqNnh$TaTKnKQOqtYUo4dDQOO%ARW7qHsxo)Vy;{|E!a7mPQeN$))k|YPb$wlF zb7|WO!{uxh!>qEbyhiOt%b<6!yMbZ57`2B@McL8Wji^jiF4_J}NkVClc&l~EZwi8^s_uNZ&q8#S@3B~_D{U8kKzV5RW7j-1NRNaPc(fyat17v9{; zz437S9Fua4c}xc92&b*YqJ**Jyo3N!3`uA1UOiX2(=yyjaJ9A%?1*rgu&?SW7@2-v zRxCN@+qf90M6j+L;jFt_N7`W75Zo2nm7!OT^p7O6Y<|T1o~;WD>}Q+*O0MZzjh&zu zy(#*%SEq0+1KB0kb+jv&iJ5DKt55QzSI>2F4WCznl!H{Pt-oh>pXsNR35)ri&MSj) zGrq0vts@_mhxTiB&mGe}mY^NekzANG!$V@Sxtm&XE#X?l%fwCevdeOz%6XM2l{l3X z4ZaPffzn&3eVTpWosn;YD>FNTyZRsn&=bb5;5?8P$c$cvNtW?Aoe!v`iMpvSR6zQJ zt&mk8Cyn}^hHv-YF{wh4J%oS#?V)EMhQ)QRTeDZTP_+gE;5puwcN$lb)5_H#3FtJYhX zy#!Ed*t4)6L~5&bl)X;54u{SJjlSCRFFwy~*Fq+XlkX?zib0EeivPfdRc}@Istco{ zE6-U&l|_ltEwEgOM(sOn|B~s69Mzr#y|dm($GWt8KlHv|iWPb&d( z&ajhhdVP{G>GA&5+i!1oZ^Iqoem+hOzCR-Nn&3;WEM?&X3*78=Y@me=i$VdEX*t7VIx5ieJ`X!jS+<;{ig#1p28b2ncuW(;?H8f+YM59+0k zqiv&C;b-To4|=m{;992U-iG>2e$qmk&zY-jbPBQ!LM&cdO(1E9Yv*?~$NTgv^$p4w z$tTM-$0fEuow(kC+yC-i)8;ktc_}sdsa1Hn=zARM#oViw_TOt;cSp6RO@h>5>wUJ`RMzQ)C!x`S6;=Rh`W2HsijWkiq=Yj8G*#bi2b+${<_Qu zwhOnhv(J~^iL;(n`wu3{?lsT_&g@w4N{?>T22R~O$l#@oIkK}P)o}B`=Ckr_;OKr2 zeep4yi=6=q`_B9J3llk_V14n-l8a^g{ab^M#l-c6$AfmHs=H3{5j7gp2PxYX`Ym*% zV$wOSYOOi#GHp`qcI-&N)AhEM_pzzH6XA_wKVEF6^ps}Z_S+>oq2IQyxc1DaU%Wh8 z)S$RCu;D)A@O84+54&7Y7(H6raB(AKKYQ1Hy(3|EL4Qj>B`fZt-z~ONRE2S66029s4(^tBQL41G{u_yB;y z>pv0SA1_A1bV{pFbQgyQ;PJ}_g~KSn_un=MM3%Dc|H~#1{g=5i z|FA(6Ae0^d@3~Mp=)c$$D0%zkhxbBZ-7sE1dv#-M00vN`v`9D{9yrVurCrkTaKr(J zX*hgGP_lXf - - Untitled + + markAllAsRead Created with Sketch. @@ -136,6 +136,11 @@ + + + + + diff --git a/Multiplatform/Shared/MainApp.swift b/Multiplatform/Shared/MainApp.swift index 547b3f8a9..e011aeca0 100644 --- a/Multiplatform/Shared/MainApp.swift +++ b/Multiplatform/Shared/MainApp.swift @@ -34,61 +34,60 @@ struct MainApp: App { } .toolbar { - ToolbarItem { + ToolbarItem() { Button(action: { showSheet = true }, label: { - Image(systemName: "plus").foregroundColor(.secondary) + AppAssets.addMenuImage }).help("Add Feed") } ToolbarItem { Button(action: {}, label: { - Image(systemName: "folder.fill.badge.plus").foregroundColor(.pink) - }).help("New Folder") - } - - ToolbarItem { - Button(action: {}, label: { - Image(systemName: "arrow.clockwise").foregroundColor(.secondary) + AppAssets.refreshImage }).help("Refresh").padding(.trailing, 40) } - + ToolbarItem { Button(action: {}, label: { - Image(systemName: "circle.dashed").foregroundColor(.orange) + AppAssets.markAllAsReadImagePDF + .resizable() + .scaledToFit() + .frame(width: 20, height: 20, alignment: .center) }).help("Mark All as Read") } ToolbarItem { - Button(action: {}, label: { - Image(systemName: "arrow.triangle.turn.up.right.circle.fill").foregroundColor(.purple) - }).help("Go to Next Unread") + MacSearchField() + .frame(width: 200) } - + ToolbarItem { Button(action: {}, label: { - Image(systemName: "star.fill").foregroundColor(.yellow) + AppAssets.nextUnreadArticleImage + }).help("Go to Next Unread").padding(.trailing, 40) + } + + ToolbarItem { + Button(action: {}, label: { + AppAssets.toggleStarred }).help("Mark as Starred") } ToolbarItem { Button(action: {}, label: { - Image(systemName: "checkmark.circle.fill").foregroundColor(.green) + AppAssets.toggleRead }).help("Mark as Unread") } ToolbarItem { Button(action: {}, label: { - Image(systemName: "safari").foregroundColor(.blue) + AppAssets.openInBrowserImage }).help("Open in Browser") } ToolbarItem { Button(action: {}, label: { - Image(systemName: "square.and.arrow.up") + AppAssets.shareImage }).help("Share") } - ToolbarItem { - MacSearchField() - .frame(width: 300) - } + } } .commands { diff --git a/Multiplatform/Shared/Sidebar/SidebarToolbar.swift b/Multiplatform/Shared/Sidebar/SidebarToolbar.swift index 9aec8c5c8..90d4a7e39 100644 --- a/Multiplatform/Shared/Sidebar/SidebarToolbar.swift +++ b/Multiplatform/Shared/Sidebar/SidebarToolbar.swift @@ -22,19 +22,14 @@ fileprivate class SidebarToolbarViewModel: ObservableObject { } @Published var showActionSheet: Bool = false @Published var showAddSheet: Bool = false + } - struct SidebarToolbar: View { @EnvironmentObject private var appSettings: AppDefaults @StateObject private var viewModel = SidebarToolbarViewModel() - - var addActionSheetButtons = [ - Button(action: {}, label: { Text("Add Feed") }) - ] - var body: some View { VStack { Divider() @@ -42,7 +37,7 @@ struct SidebarToolbar: View { Button(action: { viewModel.sheetToShow = .settings }, label: { - Image(systemName: "gear") + AppAssets.settingsImage .font(.title3) .foregroundColor(.accentColor) }).help("Settings") @@ -58,7 +53,7 @@ struct SidebarToolbar: View { Button(action: { viewModel.showActionSheet = true }, label: { - Image(systemName: "plus") + AppAssets.addMenuImage .font(.title3) .foregroundColor(.accentColor) }) diff --git a/Multiplatform/Shared/Timeline/TimelineItemStatusView.swift b/Multiplatform/Shared/Timeline/TimelineItemStatusView.swift index bef9ff233..0db428879 100644 --- a/Multiplatform/Shared/Timeline/TimelineItemStatusView.swift +++ b/Multiplatform/Shared/Timeline/TimelineItemStatusView.swift @@ -19,12 +19,10 @@ struct TimelineItemStatusView: View { .resizable() .frame(width: 8, height: 8, alignment: .center) .padding(.all, 2) - .foregroundColor(.accentColor) case .showStar: AppAssets.timelineStarred .resizable() .frame(width: 10, height: 10, alignment: .center) - .foregroundColor(.yellow) case .showNone: AppAssets.timelineUnread .resizable() diff --git a/iOS/Resources/Assets.xcassets/secondaryAccentColor.colorset/Contents.json b/iOS/Resources/Assets.xcassets/secondaryAccentColor.colorset/Contents.json index 750f4b573..b94873d3d 100644 --- a/iOS/Resources/Assets.xcassets/secondaryAccentColor.colorset/Contents.json +++ b/iOS/Resources/Assets.xcassets/secondaryAccentColor.colorset/Contents.json @@ -1,23 +1,18 @@ { - "info" : { - "version" : 1, - "author" : "xcode" - }, "colors" : [ { - "idiom" : "universal", "color" : { "color-space" : "srgb", "components" : { - "red" : "0x08", "alpha" : "1.000", "blue" : "0xEE", - "green" : "0x6A" + "green" : "0x6A", + "red" : "0x08" } - } + }, + "idiom" : "universal" }, { - "idiom" : "universal", "appearances" : [ { "appearance" : "luminosity", @@ -27,12 +22,17 @@ "color" : { "color-space" : "srgb", "components" : { - "red" : "0x5E", "alpha" : "1.000", "blue" : "0xF4", - "green" : "0x9E" + "green" : "0x9E", + "red" : "0x5E" } - } + }, + "idiom" : "universal" } - ] -} \ No newline at end of file + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/Resources/Assets.xcassets/starColor.colorset/Contents.json b/iOS/Resources/Assets.xcassets/starColor.colorset/Contents.json index b1d4071d7..4811237e1 100644 --- a/iOS/Resources/Assets.xcassets/starColor.colorset/Contents.json +++ b/iOS/Resources/Assets.xcassets/starColor.colorset/Contents.json @@ -1,20 +1,20 @@ { - "info" : { - "version" : 1, - "author" : "xcode" - }, "colors" : [ { - "idiom" : "universal", "color" : { "color-space" : "srgb", "components" : { - "red" : "249", "alpha" : "1.000", "blue" : "52", - "green" : "198" + "green" : "198", + "red" : "249" } - } + }, + "idiom" : "universal" } - ] -} \ No newline at end of file + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} From 217d1ac88080ccdcddee0be47ccbb374f8723eac Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 3 Jul 2020 19:29:25 -0500 Subject: [PATCH 16/24] Changed add button to be a menu. --- Multiplatform/Shared/MainApp.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Multiplatform/Shared/MainApp.swift b/Multiplatform/Shared/MainApp.swift index e011aeca0..1be5b3720 100644 --- a/Multiplatform/Shared/MainApp.swift +++ b/Multiplatform/Shared/MainApp.swift @@ -35,9 +35,14 @@ struct MainApp: App { .toolbar { ToolbarItem() { - Button(action: { showSheet = true }, label: { + Menu { + Button("Add Web Feed", action: { showSheet = true }) + Button("Add Reddit Feed", action: { }) + Button("Add Twitter Feed", action: { }) + Button("Add Folder", action: { }) + } label : { AppAssets.addMenuImage - }).help("Add Feed") + } } ToolbarItem { From 4393e54a5cceb1014c94ae0315ccc10d7f63efc5 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 3 Jul 2020 19:41:38 -0500 Subject: [PATCH 17/24] Fix unread indicator color --- Multiplatform/Shared/AppAssets.swift | 8 ++++++++ .../Contents.json | 20 +++++++++++++++++++ .../Contents.json | 20 +++++++++++++++++++ .../Shared/Sidebar/UnreadCountView.swift | 3 ++- .../NSAttributedString+NetNewsWire.swift | 1 - 5 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 Multiplatform/Shared/Assets.xcassets/SidebarUnreadCountBackground.colorset/Contents.json create mode 100644 Multiplatform/Shared/Assets.xcassets/SidebarUnreadCountForeground.colorset/Contents.json diff --git a/Multiplatform/Shared/AppAssets.swift b/Multiplatform/Shared/AppAssets.swift index b31a55a7a..3ca181288 100644 --- a/Multiplatform/Shared/AppAssets.swift +++ b/Multiplatform/Shared/AppAssets.swift @@ -118,6 +118,14 @@ struct AppAssets { #endif }() + static var sidebarUnreadCountBackground: Color = { + return Color("SidebarUnreadCountBackground") + }() + + static var sidebarUnreadCountForeground: Color = { + return Color("SidebarUnreadCountForeground") + }() + static var shareImage: Image = { Image(systemName: "square.and.arrow.up") }() diff --git a/Multiplatform/Shared/Assets.xcassets/SidebarUnreadCountBackground.colorset/Contents.json b/Multiplatform/Shared/Assets.xcassets/SidebarUnreadCountBackground.colorset/Contents.json new file mode 100644 index 000000000..ceb10be63 --- /dev/null +++ b/Multiplatform/Shared/Assets.xcassets/SidebarUnreadCountBackground.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.500", + "blue" : "0.000", + "green" : "0.000", + "red" : "0.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Multiplatform/Shared/Assets.xcassets/SidebarUnreadCountForeground.colorset/Contents.json b/Multiplatform/Shared/Assets.xcassets/SidebarUnreadCountForeground.colorset/Contents.json new file mode 100644 index 000000000..91ae93460 --- /dev/null +++ b/Multiplatform/Shared/Assets.xcassets/SidebarUnreadCountForeground.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.900", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Multiplatform/Shared/Sidebar/UnreadCountView.swift b/Multiplatform/Shared/Sidebar/UnreadCountView.swift index f1c760bfd..c46bc88f5 100644 --- a/Multiplatform/Shared/Sidebar/UnreadCountView.swift +++ b/Multiplatform/Shared/Sidebar/UnreadCountView.swift @@ -17,7 +17,8 @@ struct UnreadCountView: View { .font(.footnote) .padding(.horizontal, 7) .padding(.vertical, 1) - .background(SwiftUI.Color.gray.opacity(0.5)) + .background(AppAssets.sidebarUnreadCountBackground) + .foregroundColor(AppAssets.sidebarUnreadCountForeground) .cornerRadius(8) } } diff --git a/Shared/Extensions/NSAttributedString+NetNewsWire.swift b/Shared/Extensions/NSAttributedString+NetNewsWire.swift index 4037b2489..0d028a283 100644 --- a/Shared/Extensions/NSAttributedString+NetNewsWire.swift +++ b/Shared/Extensions/NSAttributedString+NetNewsWire.swift @@ -12,7 +12,6 @@ import RSParser import AppKit typealias Font = NSFont typealias FontDescriptor = NSFontDescriptor -typealias Color = NSColor private let boldTrait = NSFontDescriptor.SymbolicTraits.bold private let italicTrait = NSFontDescriptor.SymbolicTraits.italic From 6b45a72707648d472c18cdb6d7da26ebf80e295a Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 3 Jul 2020 20:03:36 -0500 Subject: [PATCH 18/24] Change unread indicator font and weight to match existing iOS parameters --- Multiplatform/Shared/Sidebar/UnreadCountView.swift | 3 ++- Shared/Extensions/NSAttributedString+NetNewsWire.swift | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Multiplatform/Shared/Sidebar/UnreadCountView.swift b/Multiplatform/Shared/Sidebar/UnreadCountView.swift index c46bc88f5..72cf9d5ec 100644 --- a/Multiplatform/Shared/Sidebar/UnreadCountView.swift +++ b/Multiplatform/Shared/Sidebar/UnreadCountView.swift @@ -14,7 +14,8 @@ struct UnreadCountView: View { var body: some View { Text(verbatim: String(count)) - .font(.footnote) + .font(.caption) + .fontWeight(.bold) .padding(.horizontal, 7) .padding(.vertical, 1) .background(AppAssets.sidebarUnreadCountBackground) diff --git a/Shared/Extensions/NSAttributedString+NetNewsWire.swift b/Shared/Extensions/NSAttributedString+NetNewsWire.swift index 0d028a283..f778dfb75 100644 --- a/Shared/Extensions/NSAttributedString+NetNewsWire.swift +++ b/Shared/Extensions/NSAttributedString+NetNewsWire.swift @@ -20,7 +20,6 @@ private let monoSpaceTrait = NSFontDescriptor.SymbolicTraits.monoSpace import UIKit typealias Font = UIFont typealias FontDescriptor = UIFontDescriptor -typealias Color = UIColor private let boldTrait = UIFontDescriptor.SymbolicTraits.traitBold private let italicTrait = UIFontDescriptor.SymbolicTraits.traitItalic From e99ff1346be5d2f9c27fc81b45ade1a9bf9d1f3e Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sat, 4 Jul 2020 09:07:55 +0800 Subject: [PATCH 19/24] Work on #2196 Fixes #2196 All logic has moved to `AddWebFeedViewModel`. --- Multiplatform/Shared/Add/AddWebFeedView.swift | 145 +++--------------- .../Shared/Add/AddWebFeedViewModel.swift | 132 ++++++++++++++++ NetNewsWire.xcodeproj/project.pbxproj | 7 +- 3 files changed, 157 insertions(+), 127 deletions(-) create mode 100644 Multiplatform/Shared/Add/AddWebFeedViewModel.swift diff --git a/Multiplatform/Shared/Add/AddWebFeedView.swift b/Multiplatform/Shared/Add/AddWebFeedView.swift index d7ec81f2c..692484e4f 100644 --- a/Multiplatform/Shared/Add/AddWebFeedView.swift +++ b/Multiplatform/Shared/Add/AddWebFeedView.swift @@ -10,49 +10,6 @@ import SwiftUI import Account import RSCore -fileprivate enum AddWebFeedError: 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 AddWebFeedViewModel: ObservableObject { - - @Published var providedURL: String = "" - @Published var providedName: String = "" - @Published var selectedFolderIndex: Int = 0 - @Published var addFeedError: AddWebFeedError? { - 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 AddWebFeedView: View { @@ -62,15 +19,28 @@ struct AddWebFeedView: View { @ViewBuilder var body: some View { #if os(iOS) iosForm + .onAppear { + viewModel.pasteUrlFromPasteboard() + } + .onReceive(viewModel.$shouldDismiss, perform: { dismiss in + if dismiss == true { + presentationMode.wrappedValue.dismiss() + } + }) + #else macForm .onAppear { - pasteUrlFromPasteboard() + viewModel.pasteUrlFromPasteboard() }.alert(isPresented: $viewModel.showError) { Alert(title: Text("Oops"), message: Text(viewModel.addFeedError!.localizedDescription), dismissButton: Alert.Button.cancel({ - viewModel.addFeedError = .none + viewModel.addFeedError = AddWebFeedError.none })) - } + }.onReceive(viewModel.$shouldDismiss, perform: { dismiss in + if dismiss == true { + presentationMode.wrappedValue.dismiss() + } + }) #endif } @@ -117,7 +87,7 @@ struct AddWebFeedView: View { .help("Cancel Add Feed") , trailing: Button("Add", action: { - addWebFeed() + viewModel.addWebFeed() }) .disabled(!viewModel.providedURL.isValidURL) .help("Add Feed") @@ -168,94 +138,17 @@ struct AddWebFeedView: View { .help("Cancel Add Feed") Button("Add", action: { - addWebFeed() + viewModel.addWebFeed() }) .disabled(!viewModel.providedURL.isValidURL) .help("Add Feed") } } - #if os(macOS) - func pasteUrlFromPasteboard() { - guard let stringFromPasteboard = urlStringFromPasteboard, stringFromPasteboard.isValidURL else { - return - } - viewModel.providedURL = stringFromPasteboard - } - #endif + } -private extension AddWebFeedView { - - #if os(macOS) - var urlStringFromPasteboard: String? { - if let urlString = NSPasteboard.urlString(from: NSPasteboard.general) { - return urlString.normalizedURL - } - return nil - } - #else - var urlStringFromPasteboard: String? { - if let urlString = UIPasteboard.general.url?.absoluteString { - return urlString.normalizedURL - } - return nil - } - #endif - - 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 { AddWebFeedView() diff --git a/Multiplatform/Shared/Add/AddWebFeedViewModel.swift b/Multiplatform/Shared/Add/AddWebFeedViewModel.swift new file mode 100644 index 000000000..a8f7a5280 --- /dev/null +++ b/Multiplatform/Shared/Add/AddWebFeedViewModel.swift @@ -0,0 +1,132 @@ +// +// AddWebFeedViewModel.swift +// NetNewsWire +// +// Created by Stuart Breckenridge on 4/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation +import Account +import RSCore +import SwiftUI + +enum AddWebFeedError: 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 + } + } + +} + +class AddWebFeedViewModel: ObservableObject { + + @Published var shouldDismiss: Bool = false + @Published var providedURL: String = "" + @Published var providedName: String = "" + @Published var selectedFolderIndex: Int = 0 + @Published var addFeedError: AddWebFeedError? { + didSet { + addFeedError != AddWebFeedError.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) + } + } + } + + func pasteUrlFromPasteboard() { + guard let stringFromPasteboard = urlStringFromPasteboard, stringFromPasteboard.isValidURL else { + return + } + providedURL = stringFromPasteboard + } + + #if os(macOS) + var urlStringFromPasteboard: String? { + if let urlString = NSPasteboard.urlString(from: NSPasteboard.general) { + return urlString.normalizedURL + } + return nil + } + #else + var urlStringFromPasteboard: String? { + if let urlString = UIPasteboard.general.url?.absoluteString { + return urlString.normalizedURL + } + return nil + } + #endif + + 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(containers[selectedFolderIndex])?.account { + + showProgressIndicator = true + + let container = containers[selectedFolderIndex] + + if account.hasWebFeed(withURL: providedURL) { + addFeedError = .alreadySubscribed + showProgressIndicator = false + return + } + + account.createWebFeed(url: providedURL, name: providedName, container: container, completion: { [weak self] result in + self?.showProgressIndicator = false + switch result { + case .success(let feed): + NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.webFeed: feed]) + self?.shouldDismiss = true + case .failure(let error): + switch error { + case AccountError.createErrorAlreadySubscribed: + self?.addFeedError = .alreadySubscribed + return + case AccountError.createErrorNotFound: + self?.addFeedError = .noFeeds + return + default: + print("Error") + } + } + }) + } + } + + + +} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index afea53483..cdeb0d3eb 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ 17930ED524AF10EE00A9BA52 /* AddWebFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */; }; 179DB1DFBCF9177104B12E0F /* AccountsNewsBlurWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */; }; 179DB3CE822BFCC2D774D9F4 /* AccountsNewsBlurWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */; }; + 17D232A824AFF10A0005F075 /* AddWebFeedViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D232A724AFF10A0005F075 /* AddWebFeedViewModel.swift */; }; + 17D232A924AFF10A0005F075 /* AddWebFeedViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D232A724AFF10A0005F075 /* AddWebFeedViewModel.swift */; }; 3B3A32A5238B820900314204 /* FeedWranglerAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */; }; 3B826DCB2385C84800FC1ADB /* AccountsFeedWrangler.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */; }; 3B826DCC2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */; }; @@ -631,7 +633,6 @@ 6581C73D20CED60100F4AD34 /* SafariExtensionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */; }; 6581C74020CED60100F4AD34 /* netnewswire-subscribe-to-feed.js in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73F20CED60100F4AD34 /* netnewswire-subscribe-to-feed.js */; }; 6581C74220CED60100F4AD34 /* ToolbarItemIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */; }; - 65CBAD3624AE02D50006DD91 /* TimelineLayoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B223DB24AC24D2001E4592 /* TimelineLayoutView.swift */; }; 65CBAD5A24AE03C20006DD91 /* ColorPaletteContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65CBAD5924AE03C20006DD91 /* ColorPaletteContainerView.swift */; }; 65ED3FB7235DEF6C0081F399 /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; }; 65ED3FB8235DEF6C0081F399 /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848B937121C8C5540038DC0D /* CrashReporter.swift */; }; @@ -1727,6 +1728,7 @@ 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedView.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 = ""; }; + 17D232A724AFF10A0005F075 /* AddWebFeedViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedViewModel.swift; sourceTree = ""; }; 3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedWranglerAccountViewController.swift; sourceTree = ""; }; 3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsFeedWrangler.xib; sourceTree = ""; }; 3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsFeedWranglerWindowController.swift; sourceTree = ""; }; @@ -2406,6 +2408,7 @@ 17930ED224AF10CD00A9BA52 /* Add */ = { isa = PBXGroup; children = ( + 17D232A724AFF10A0005F075 /* AddWebFeedViewModel.swift */, 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */, ); path = Add; @@ -4837,6 +4840,7 @@ 51A576BE24AE637400078888 /* ArticleView.swift in Sources */, 51E4995324A8734D00B667CB /* RedditFeedProvider-Extensions.swift in Sources */, 172199C924AB228900A31D04 /* SettingsView.swift in Sources */, + 17D232A824AFF10A0005F075 /* AddWebFeedViewModel.swift in Sources */, 51E4994224A8713C00B667CB /* ArticleStatusSyncTimer.swift in Sources */, 51E498F624A8085D00B667CB /* SearchFeedDelegate.swift in Sources */, 51E498F224A8085D00B667CB /* SmartFeedsController.swift in Sources */, @@ -4911,6 +4915,7 @@ 51E4992224A8095600B667CB /* URL-Extensions.swift in Sources */, 51E4990424A808C300B667CB /* WebFeedIconDownloader.swift in Sources */, 51E498CB24A8085D00B667CB /* TodayFeedDelegate.swift in Sources */, + 17D232A924AFF10A0005F075 /* AddWebFeedViewModel.swift in Sources */, 51E4993324A867E700B667CB /* AppNotifications.swift in Sources */, 51E4990624A808C300B667CB /* ImageDownloader.swift in Sources */, 51E4994F24A8734C00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */, From b28fc913af86ff0b36e882f0d4752ef4acad42c8 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 3 Jul 2020 20:23:41 -0500 Subject: [PATCH 20/24] Renamed because we just call out view models Models --- ...dViewModel.swift => AddWebFeedModel.swift} | 4 +-- Multiplatform/Shared/Add/AddWebFeedView.swift | 2 +- NetNewsWire.xcodeproj/project.pbxproj | 34 +++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) rename Multiplatform/Shared/Add/{AddWebFeedViewModel.swift => AddWebFeedModel.swift} (97%) diff --git a/Multiplatform/Shared/Add/AddWebFeedViewModel.swift b/Multiplatform/Shared/Add/AddWebFeedModel.swift similarity index 97% rename from Multiplatform/Shared/Add/AddWebFeedViewModel.swift rename to Multiplatform/Shared/Add/AddWebFeedModel.swift index a8f7a5280..1520d312e 100644 --- a/Multiplatform/Shared/Add/AddWebFeedViewModel.swift +++ b/Multiplatform/Shared/Add/AddWebFeedModel.swift @@ -1,5 +1,5 @@ // -// AddWebFeedViewModel.swift +// AddWebFeedModel.swift // NetNewsWire // // Created by Stuart Breckenridge on 4/7/20. @@ -30,7 +30,7 @@ enum AddWebFeedError: LocalizedError { } -class AddWebFeedViewModel: ObservableObject { +class AddWebFeedModel: ObservableObject { @Published var shouldDismiss: Bool = false @Published var providedURL: String = "" diff --git a/Multiplatform/Shared/Add/AddWebFeedView.swift b/Multiplatform/Shared/Add/AddWebFeedView.swift index 692484e4f..fa9fb33b6 100644 --- a/Multiplatform/Shared/Add/AddWebFeedView.swift +++ b/Multiplatform/Shared/Add/AddWebFeedView.swift @@ -14,7 +14,7 @@ import RSCore struct AddWebFeedView: View { @Environment(\.presentationMode) private var presentationMode - @ObservedObject private var viewModel = AddWebFeedViewModel() + @ObservedObject private var viewModel = AddWebFeedModel() @ViewBuilder var body: some View { #if os(iOS) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 56f3cdc60..dfe9076e6 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -27,8 +27,8 @@ 17930ED524AF10EE00A9BA52 /* AddWebFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */; }; 179DB1DFBCF9177104B12E0F /* AccountsNewsBlurWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */; }; 179DB3CE822BFCC2D774D9F4 /* AccountsNewsBlurWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */; }; - 17D232A824AFF10A0005F075 /* AddWebFeedViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D232A724AFF10A0005F075 /* AddWebFeedViewModel.swift */; }; - 17D232A924AFF10A0005F075 /* AddWebFeedViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D232A724AFF10A0005F075 /* AddWebFeedViewModel.swift */; }; + 17D232A824AFF10A0005F075 /* AddWebFeedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D232A724AFF10A0005F075 /* AddWebFeedModel.swift */; }; + 17D232A924AFF10A0005F075 /* AddWebFeedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D232A724AFF10A0005F075 /* AddWebFeedModel.swift */; }; 3B3A32A5238B820900314204 /* FeedWranglerAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */; }; 3B826DCB2385C84800FC1ADB /* AccountsFeedWrangler.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */; }; 3B826DCC2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */; }; @@ -1730,7 +1730,7 @@ 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedView.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 = ""; }; - 17D232A724AFF10A0005F075 /* AddWebFeedViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedViewModel.swift; sourceTree = ""; }; + 17D232A724AFF10A0005F075 /* AddWebFeedModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedModel.swift; sourceTree = ""; }; 3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedWranglerAccountViewController.swift; sourceTree = ""; }; 3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsFeedWrangler.xib; sourceTree = ""; }; 3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsFeedWranglerWindowController.swift; sourceTree = ""; }; @@ -2411,7 +2411,7 @@ 17930ED224AF10CD00A9BA52 /* Add */ = { isa = PBXGroup; children = ( - 17D232A724AFF10A0005F075 /* AddWebFeedViewModel.swift */, + 17D232A724AFF10A0005F075 /* AddWebFeedModel.swift */, 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */, ); path = Add; @@ -3972,46 +3972,46 @@ TargetAttributes = { 51314636235A7BBE00387FDC = { CreatedOnToolsVersion = 11.2; - DevelopmentTeam = FQLBNX3GP7; + DevelopmentTeam = SHJK2V3AJG; LastSwiftMigration = 1120; ProvisioningStyle = Automatic; }; 513C5CE5232571C2003D4054 = { CreatedOnToolsVersion = 11.0; - DevelopmentTeam = FQLBNX3GP7; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; }; 518B2ED12351B3DD00400001 = { CreatedOnToolsVersion = 11.2; - DevelopmentTeam = FQLBNX3GP7; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; TestTargetID = 840D617B2029031C009BC708; }; 51C0513C24A77DF800194D5E = { CreatedOnToolsVersion = 12.0; - DevelopmentTeam = FQLBNX3GP7; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; }; 51C0514324A77DF800194D5E = { CreatedOnToolsVersion = 12.0; - DevelopmentTeam = FQLBNX3GP7; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; }; 6581C73220CED60000F4AD34 = { - DevelopmentTeam = FQLBNX3GP7; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; }; 65ED3FA2235DEF6C0081F399 = { - DevelopmentTeam = FQLBNX3GP7; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; }; 65ED4090235DEF770081F399 = { - DevelopmentTeam = FQLBNX3GP7; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; }; 840D617B2029031C009BC708 = { CreatedOnToolsVersion = 9.3; - DevelopmentTeam = FQLBNX3GP7; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { @@ -4021,7 +4021,7 @@ }; 849C645F1ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = FQLBNX3GP7; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.HardenedRuntime = { @@ -4031,7 +4031,7 @@ }; 849C64701ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = FQLBNX3GP7; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; TestTargetID = 849C645F1ED37A5D003D8FC0; }; @@ -4845,7 +4845,7 @@ 51A576BE24AE637400078888 /* ArticleView.swift in Sources */, 51E4995324A8734D00B667CB /* RedditFeedProvider-Extensions.swift in Sources */, 172199C924AB228900A31D04 /* SettingsView.swift in Sources */, - 17D232A824AFF10A0005F075 /* AddWebFeedViewModel.swift in Sources */, + 17D232A824AFF10A0005F075 /* AddWebFeedModel.swift in Sources */, 51E4994224A8713C00B667CB /* ArticleStatusSyncTimer.swift in Sources */, 51E498F624A8085D00B667CB /* SearchFeedDelegate.swift in Sources */, 51E498F224A8085D00B667CB /* SmartFeedsController.swift in Sources */, @@ -4920,7 +4920,7 @@ 51E4992224A8095600B667CB /* URL-Extensions.swift in Sources */, 51E4990424A808C300B667CB /* WebFeedIconDownloader.swift in Sources */, 51E498CB24A8085D00B667CB /* TodayFeedDelegate.swift in Sources */, - 17D232A924AFF10A0005F075 /* AddWebFeedViewModel.swift in Sources */, + 17D232A924AFF10A0005F075 /* AddWebFeedModel.swift in Sources */, 51E4993324A867E700B667CB /* AppNotifications.swift in Sources */, 51E4990624A808C300B667CB /* ImageDownloader.swift in Sources */, 51E4994F24A8734C00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */, From 9491855fb8c3e8a2152b90659694cad767eccacf Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sat, 4 Jul 2020 09:31:58 +0800 Subject: [PATCH 21/24] 2199 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On iOS: • Autocorrection and auto capitalisation is disabled on URL field On macOS: • Autocorrection is disabled. --- Multiplatform/Shared/Add/AddWebFeedView.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Multiplatform/Shared/Add/AddWebFeedView.swift b/Multiplatform/Shared/Add/AddWebFeedView.swift index 692484e4f..9e205ae8c 100644 --- a/Multiplatform/Shared/Add/AddWebFeedView.swift +++ b/Multiplatform/Shared/Add/AddWebFeedView.swift @@ -78,7 +78,7 @@ struct AddWebFeedView: View { folderPicker } .listStyle(InsetGroupedListStyle()) - .navigationTitle("Add Web Feed") + .navigationBarTitle("Add Web Feed") .navigationBarTitleDisplayMode(.inline) .navigationBarItems(leading: Button("Cancel", action: { @@ -99,7 +99,14 @@ struct AddWebFeedView: View { var urlTextField: some View { HStack { Text("Feed:") + #if os(iOS) TextField("URL", text: $viewModel.providedURL) + .disableAutocorrection(true) + .autocapitalization(UITextAutocapitalizationType.none) + #else + TextField("URL", text: $viewModel.providedURL) + .disableAutocorrection(true) + #endif } } From c66c386df75c6c3fb543c52e3c959509c004e501 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sat, 4 Jul 2020 09:40:38 +0800 Subject: [PATCH 22/24] 2199 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #2199 • Removes scheme from url checker. --- Multiplatform/Shared/String+URLChecker.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Multiplatform/Shared/String+URLChecker.swift b/Multiplatform/Shared/String+URLChecker.swift index c2cc1e1db..c1994b1b1 100644 --- a/Multiplatform/Shared/String+URLChecker.swift +++ b/Multiplatform/Shared/String+URLChecker.swift @@ -12,7 +12,7 @@ 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 regEx = "^([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) } From 4486d2bd09fc15bf9c2d3e307b28ee2bf59ea3a2 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 3 Jul 2020 21:55:27 -0500 Subject: [PATCH 23/24] Add TimeilneToolbar. Issue #2195 --- .../Shared/Article/ArticleContainerView.swift | 18 +++--- .../Timeline/TimelineContainerView.swift | 5 ++ .../Shared/Timeline/TimelineToolbar.swift | 56 +++++++++++++++++++ NetNewsWire.xcodeproj/project.pbxproj | 8 ++- 4 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 Multiplatform/Shared/Timeline/TimelineToolbar.swift diff --git a/Multiplatform/Shared/Article/ArticleContainerView.swift b/Multiplatform/Shared/Article/ArticleContainerView.swift index 021c39d62..8db0c6318 100644 --- a/Multiplatform/Shared/Article/ArticleContainerView.swift +++ b/Multiplatform/Shared/Article/ArticleContainerView.swift @@ -13,19 +13,15 @@ struct ArticleContainerView: View { @EnvironmentObject private var sceneModel: SceneModel @StateObject private var articleModel = ArticleModel() - var article: Article? = nil + var article: Article @ViewBuilder var body: some View { - if let article = article { - ArticleView() - .environmentObject(articleModel) - .onAppear { - sceneModel.articleModel = articleModel - articleModel.delegate = sceneModel - } - } else { - EmptyView() - } + ArticleView() + .environmentObject(articleModel) + .onAppear { + sceneModel.articleModel = articleModel + articleModel.delegate = sceneModel + } } } diff --git a/Multiplatform/Shared/Timeline/TimelineContainerView.swift b/Multiplatform/Shared/Timeline/TimelineContainerView.swift index 008e28d42..306119ac1 100644 --- a/Multiplatform/Shared/Timeline/TimelineContainerView.swift +++ b/Multiplatform/Shared/Timeline/TimelineContainerView.swift @@ -24,6 +24,11 @@ struct TimelineContainerView: View { timelineModel.delegate = sceneModel timelineModel.rebuildTimelineItems(feed) } + .overlay(Group { + #if os(iOS) + TimelineToolbar() + #endif + },alignment: .bottom) } else { EmptyView() } diff --git a/Multiplatform/Shared/Timeline/TimelineToolbar.swift b/Multiplatform/Shared/Timeline/TimelineToolbar.swift new file mode 100644 index 000000000..5344fbd02 --- /dev/null +++ b/Multiplatform/Shared/Timeline/TimelineToolbar.swift @@ -0,0 +1,56 @@ +// +// TimelineToolbar.swift +// NetNewsWire +// +// Created by Maurice Parker on 7/3/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import SwiftUI + +struct TimelineToolbar: View { + + var body: some View { + VStack { + Divider() + HStack(alignment: .center) { + Button(action: { + }, label: { + AppAssets.markAllAsReadImage + .resizable() + .scaledToFit() + .frame(width: 24, height: 24, alignment: .center) + .foregroundColor(.accentColor) + }).help("Mark All As Read") + + Spacer() + + Text("Last updated") + .font(.caption) + .foregroundColor(.secondary) + + Spacer() + + Button(action: { + }, label: { + AppAssets.nextUnreadArticleImage + .font(.title3) + .foregroundColor(.accentColor) + }) + .help("Next Unread") + } + .padding(.horizontal, 16) + .padding(.bottom, 12) + .padding(.top, 4) + } + .background(VisualEffectBlur(blurStyle: .systemChromeMaterial).edgesIgnoringSafeArea(.bottom)) + + } +} + + +struct TimelineToolbar_Previews: PreviewProvider { + static var previews: some View { + TimelineToolbar() + } +} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index dfe9076e6..59c2857e6 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -222,6 +222,8 @@ 517A757C24451C1500B553B9 /* OAuthSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 517A755324451BD500B553B9 /* OAuthSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5181C5AD24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5181C5AC24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift */; }; 5181C5AE24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5181C5AC24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift */; }; + 5181C64C24B024B4002E0F70 /* TimelineToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5181C64B24B024B4002E0F70 /* TimelineToolbar.swift */; }; + 5181C64D24B024B4002E0F70 /* TimelineToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5181C64B24B024B4002E0F70 /* TimelineToolbar.swift */; }; 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */; }; 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */; }; 5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; @@ -1848,6 +1850,7 @@ 517A745A2443665000B553B9 /* UIPageViewController-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIPageViewController-Extensions.swift"; sourceTree = ""; }; 517A754424451BD500B553B9 /* OAuthSwift.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = OAuthSwift.xcodeproj; path = submodules/OAuthSwift/OAuthSwift.xcodeproj; sourceTree = ""; }; 5181C5AC24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferredColorSchemeModifier.swift; sourceTree = ""; }; + 5181C64B24B024B4002E0F70 /* TimelineToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineToolbar.swift; sourceTree = ""; }; 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicLabel.swift; sourceTree = ""; }; 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicImageView.swift; sourceTree = ""; }; 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshInterval.swift; sourceTree = ""; }; @@ -2688,10 +2691,11 @@ children = ( 51919FED24AB85E400541E64 /* TimelineContainerView.swift */, 51919FF324AB869C00541E64 /* TimelineItem.swift */, + 514E6C0124AD29A300AC6F6E /* TimelineItemStatusView.swift */, 514E6BD924ACEA0400AC6F6E /* TimelineItemView.swift */, 51919FF024AB864A00541E64 /* TimelineModel.swift */, + 5181C64B24B024B4002E0F70 /* TimelineToolbar.swift */, 51919FF624AB8B7700541E64 /* TimelineView.swift */, - 514E6C0124AD29A300AC6F6E /* TimelineItemStatusView.swift */, ); path = Timeline; sourceTree = ""; @@ -4811,6 +4815,7 @@ 514E6BDA24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */, 51E4990D24A808C500B667CB /* RSHTMLMetadata+Extension.swift in Sources */, 51919FF424AB869C00541E64 /* TimelineItem.swift in Sources */, + 5181C64C24B024B4002E0F70 /* TimelineToolbar.swift in Sources */, 514E6C0224AD29A300AC6F6E /* TimelineItemStatusView.swift in Sources */, 51E49A0024A91FC100B667CB /* RegularSidebarContainerView.swift in Sources */, 51E4995C24A875F300B667CB /* ArticleRenderer.swift in Sources */, @@ -4923,6 +4928,7 @@ 17D232A924AFF10A0005F075 /* AddWebFeedModel.swift in Sources */, 51E4993324A867E700B667CB /* AppNotifications.swift in Sources */, 51E4990624A808C300B667CB /* ImageDownloader.swift in Sources */, + 5181C64D24B024B4002E0F70 /* TimelineToolbar.swift in Sources */, 51E4994F24A8734C00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */, 51E498CA24A8085D00B667CB /* SmartFeedDelegate.swift in Sources */, 51E4990524A808C300B667CB /* FeaturedImageDownloader.swift in Sources */, From ba0093e07c338acdd9b66f1e2d1d4519f863c40f Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 3 Jul 2020 22:14:36 -0500 Subject: [PATCH 24/24] Implement timeline toolbar using toolbar modifier --- .../Timeline/TimelineContainerView.swift | 32 ++++++++++- .../Shared/Timeline/TimelineToolbar.swift | 56 ------------------- NetNewsWire.xcodeproj/project.pbxproj | 6 -- 3 files changed, 29 insertions(+), 65 deletions(-) delete mode 100644 Multiplatform/Shared/Timeline/TimelineToolbar.swift diff --git a/Multiplatform/Shared/Timeline/TimelineContainerView.swift b/Multiplatform/Shared/Timeline/TimelineContainerView.swift index 306119ac1..a258638b5 100644 --- a/Multiplatform/Shared/Timeline/TimelineContainerView.swift +++ b/Multiplatform/Shared/Timeline/TimelineContainerView.swift @@ -24,11 +24,37 @@ struct TimelineContainerView: View { timelineModel.delegate = sceneModel timelineModel.rebuildTimelineItems(feed) } - .overlay(Group { + .toolbar { #if os(iOS) - TimelineToolbar() + ToolbarItem { + Button(action: { + }, label: { + AppAssets.markAllAsReadImage + .foregroundColor(.accentColor) + }).help("Mark All As Read") + } + ToolbarItem { + Spacer() + } + ToolbarItem { + Text("Last updated") + .font(.caption) + .foregroundColor(.secondary) + } + ToolbarItem { + Spacer() + } + ToolbarItem { + Button(action: { + }, label: { + AppAssets.nextUnreadArticleImage + .resizable() + .scaledToFit() + .frame(width: 22, height: 22, alignment: .center) + }) + } #endif - },alignment: .bottom) + } } else { EmptyView() } diff --git a/Multiplatform/Shared/Timeline/TimelineToolbar.swift b/Multiplatform/Shared/Timeline/TimelineToolbar.swift deleted file mode 100644 index 5344fbd02..000000000 --- a/Multiplatform/Shared/Timeline/TimelineToolbar.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// TimelineToolbar.swift -// NetNewsWire -// -// Created by Maurice Parker on 7/3/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct TimelineToolbar: View { - - var body: some View { - VStack { - Divider() - HStack(alignment: .center) { - Button(action: { - }, label: { - AppAssets.markAllAsReadImage - .resizable() - .scaledToFit() - .frame(width: 24, height: 24, alignment: .center) - .foregroundColor(.accentColor) - }).help("Mark All As Read") - - Spacer() - - Text("Last updated") - .font(.caption) - .foregroundColor(.secondary) - - Spacer() - - Button(action: { - }, label: { - AppAssets.nextUnreadArticleImage - .font(.title3) - .foregroundColor(.accentColor) - }) - .help("Next Unread") - } - .padding(.horizontal, 16) - .padding(.bottom, 12) - .padding(.top, 4) - } - .background(VisualEffectBlur(blurStyle: .systemChromeMaterial).edgesIgnoringSafeArea(.bottom)) - - } -} - - -struct TimelineToolbar_Previews: PreviewProvider { - static var previews: some View { - TimelineToolbar() - } -} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 59c2857e6..bdb2dc841 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -222,8 +222,6 @@ 517A757C24451C1500B553B9 /* OAuthSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 517A755324451BD500B553B9 /* OAuthSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5181C5AD24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5181C5AC24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift */; }; 5181C5AE24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5181C5AC24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift */; }; - 5181C64C24B024B4002E0F70 /* TimelineToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5181C64B24B024B4002E0F70 /* TimelineToolbar.swift */; }; - 5181C64D24B024B4002E0F70 /* TimelineToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5181C64B24B024B4002E0F70 /* TimelineToolbar.swift */; }; 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */; }; 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */; }; 5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; @@ -1850,7 +1848,6 @@ 517A745A2443665000B553B9 /* UIPageViewController-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIPageViewController-Extensions.swift"; sourceTree = ""; }; 517A754424451BD500B553B9 /* OAuthSwift.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = OAuthSwift.xcodeproj; path = submodules/OAuthSwift/OAuthSwift.xcodeproj; sourceTree = ""; }; 5181C5AC24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferredColorSchemeModifier.swift; sourceTree = ""; }; - 5181C64B24B024B4002E0F70 /* TimelineToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineToolbar.swift; sourceTree = ""; }; 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicLabel.swift; sourceTree = ""; }; 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicImageView.swift; sourceTree = ""; }; 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshInterval.swift; sourceTree = ""; }; @@ -2694,7 +2691,6 @@ 514E6C0124AD29A300AC6F6E /* TimelineItemStatusView.swift */, 514E6BD924ACEA0400AC6F6E /* TimelineItemView.swift */, 51919FF024AB864A00541E64 /* TimelineModel.swift */, - 5181C64B24B024B4002E0F70 /* TimelineToolbar.swift */, 51919FF624AB8B7700541E64 /* TimelineView.swift */, ); path = Timeline; @@ -4815,7 +4811,6 @@ 514E6BDA24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */, 51E4990D24A808C500B667CB /* RSHTMLMetadata+Extension.swift in Sources */, 51919FF424AB869C00541E64 /* TimelineItem.swift in Sources */, - 5181C64C24B024B4002E0F70 /* TimelineToolbar.swift in Sources */, 514E6C0224AD29A300AC6F6E /* TimelineItemStatusView.swift in Sources */, 51E49A0024A91FC100B667CB /* RegularSidebarContainerView.swift in Sources */, 51E4995C24A875F300B667CB /* ArticleRenderer.swift in Sources */, @@ -4928,7 +4923,6 @@ 17D232A924AFF10A0005F075 /* AddWebFeedModel.swift in Sources */, 51E4993324A867E700B667CB /* AppNotifications.swift in Sources */, 51E4990624A808C300B667CB /* ImageDownloader.swift in Sources */, - 5181C64D24B024B4002E0F70 /* TimelineToolbar.swift in Sources */, 51E4994F24A8734C00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */, 51E498CA24A8085D00B667CB /* SmartFeedDelegate.swift in Sources */, 51E4990524A808C300B667CB /* FeaturedImageDownloader.swift in Sources */,