diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index e18569f2b..bc10ad7ee 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -29,6 +29,7 @@ public extension Notification.Name { static let AccountDidDownloadArticles = Notification.Name(rawValue: "AccountDidDownloadArticles") static let AccountStateDidChange = Notification.Name(rawValue: "AccountStateDidChange") static let StatusesDidChange = Notification.Name(rawValue: "StatusesDidChange") + static let FeedMetadataDidChange = Notification.Name(rawValue: "FeedMetadataDidChange") } public enum AccountType: Int { @@ -401,6 +402,13 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, } + public func resetAllFeedMetadata() { + for feed in flattenedFeeds() { + feed.metadata = feedMetadata(feedURL: feed.url, feedID: feed.feedID) + } + NotificationCenter.default.post(name: .FeedMetadataDidChange, object: self, userInfo: nil) + } + public func markArticles(_ articles: Set
, statusKey: ArticleStatus.Key, flag: Bool) -> Set
? { return delegate.markArticles(for: self, articles: articles, statusKey: statusKey, flag: flag) } diff --git a/Frameworks/Account/AccountManager.swift b/Frameworks/Account/AccountManager.swift index 08b27bd97..21edfcbe5 100644 --- a/Frameworks/Account/AccountManager.swift +++ b/Frameworks/Account/AccountManager.swift @@ -72,6 +72,14 @@ public final class AccountManager: UnreadCountProvider { return CombinedRefreshProgress(downloadProgressArray: downloadProgressArray) } + public convenience init() { + let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String + let accountsURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) + let accountsFolder = accountsURL!.appendingPathComponent("Accounts").absoluteString + let accountsFolderPath = accountsFolder.suffix(from: accountsFolder.index(accountsFolder.startIndex, offsetBy: 7)) + self.init(accountsFolder: String(accountsFolderPath)) + } + public init(accountsFolder: String) { self.accountsFolder = accountsFolder diff --git a/Frameworks/Account/AccountMetadataFile.swift b/Frameworks/Account/AccountMetadataFile.swift index e27455c07..14457dc45 100644 --- a/Frameworks/Account/AccountMetadataFile.swift +++ b/Frameworks/Account/AccountMetadataFile.swift @@ -48,6 +48,7 @@ private extension AccountMetadataFile { if let fileData = try? Data(contentsOf: readURL) { let decoder = PropertyListDecoder() account.metadata = (try? decoder.decode(AccountMetadata.self, from: fileData)) ?? AccountMetadata() + account.metadata.delegate = account } }) @@ -55,7 +56,6 @@ private extension AccountMetadataFile { os_log(.error, log: log, "Read from disk coordination failed: %@.", error.localizedDescription) } - account.metadata.delegate = account } func saveCallback() { diff --git a/Frameworks/Account/Credentials/CredentialsManager.swift b/Frameworks/Account/Credentials/CredentialsManager.swift index 6ac291cca..b52b6c30c 100644 --- a/Frameworks/Account/Credentials/CredentialsManager.swift +++ b/Frameworks/Account/Credentials/CredentialsManager.swift @@ -10,6 +10,15 @@ import Foundation public struct CredentialsManager { + private static var keychainGroup: String? = { + guard let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as? String else { + return nil + } + let appIdentifierPrefix = Bundle.main.object(forInfoDictionaryKey: "AppIdentifierPrefix") as! String + let appGroupSuffix = appGroup.suffix(appGroup.count - 6) + return "\(appIdentifierPrefix)\(appGroupSuffix)" + }() + public static func storeCredentials(_ credentials: Credentials, server: String) throws { var query: [String: Any] = [kSecClass as String: kSecClassInternetPassword, @@ -20,6 +29,10 @@ public struct CredentialsManager { query[kSecAttrSecurityDomain as String] = credentials.type.rawValue } + if let securityGroup = keychainGroup { + query[kSecAttrAccessGroup as String] = securityGroup + } + let secretData = credentials.secret.data(using: String.Encoding.utf8)! let attributes: [String: Any] = [kSecValueData as String: secretData] let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary) @@ -59,6 +72,10 @@ public struct CredentialsManager { query[kSecAttrSecurityDomain as String] = type.rawValue } + if let securityGroup = keychainGroup { + query[kSecAttrAccessGroup as String] = securityGroup + } + var item: CFTypeRef? let status = SecItemCopyMatching(query as CFDictionary, &item) @@ -93,6 +110,10 @@ public struct CredentialsManager { query[kSecAttrSecurityDomain as String] = type.rawValue } + if let securityGroup = keychainGroup { + query[kSecAttrAccessGroup as String] = securityGroup + } + let status = SecItemDelete(query as CFDictionary) guard status == errSecSuccess || status == errSecItemNotFound else { throw CredentialsError.unhandledError(status: status) diff --git a/Frameworks/Account/Feed.swift b/Frameworks/Account/Feed.swift index f7c994409..5a7a4c3ec 100644 --- a/Frameworks/Account/Feed.swift +++ b/Frameworks/Account/Feed.swift @@ -196,10 +196,11 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha } } + var metadata: FeedMetadata + // MARK: - Private private let accountID: String // Used for hashing and equality; account may turn nil - private let metadata: FeedMetadata // MARK: - Init diff --git a/Frameworks/Account/FeedMetadataFile.swift b/Frameworks/Account/FeedMetadataFile.swift index 739518232..e9314a4f0 100644 --- a/Frameworks/Account/FeedMetadataFile.swift +++ b/Frameworks/Account/FeedMetadataFile.swift @@ -48,6 +48,8 @@ private extension FeedMetadataFile { if let fileData = try? Data(contentsOf: readURL) { let decoder = PropertyListDecoder() account.feedMetadata = (try? decoder.decode(Account.FeedMetadataDictionary.self, from: fileData)) ?? Account.FeedMetadataDictionary() + account.feedMetadata.values.forEach { $0.delegate = account } + account.resetAllFeedMetadata() } }) @@ -55,7 +57,6 @@ private extension FeedMetadataFile { os_log(.error, log: log, "Read from disk coordination failed: %@.", error.localizedDescription) } - account.feedMetadata.values.forEach { $0.delegate = account } } diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index d57ac1872..12332219a 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -63,6 +63,8 @@ 5144EA43227A380F00D19003 /* ExportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA42227A380F00D19003 /* ExportOPMLWindowController.swift */; }; 5144EA51227B8E4500D19003 /* AccountsFeedbinWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */; }; 5144EA52227B8E4500D19003 /* AccountsFeedbin.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */; }; + 5148F44B2336DB4700F8CD8B /* MasterTimelineTitleView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5148F44A2336DB4700F8CD8B /* MasterTimelineTitleView.xib */; }; + 5148F4552336DB7000F8CD8B /* MasterTimelineTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5148F4542336DB7000F8CD8B /* MasterTimelineTitleView.swift */; }; 514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */; }; 514B7D1F23219F3C00BAC947 /* AddControllerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7D1E23219F3C00BAC947 /* AddControllerType.swift */; }; 5152E0F923248F6200E5C7AD /* SettingsLocalAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510D707D22B02A4B004E8F65 /* SettingsLocalAccountView.swift */; }; @@ -808,6 +810,8 @@ 5144EA42227A380F00D19003 /* ExportOPMLWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportOPMLWindowController.swift; sourceTree = ""; }; 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsFeedbinWindowController.swift; sourceTree = ""; }; 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsFeedbin.xib; sourceTree = ""; }; + 5148F44A2336DB4700F8CD8B /* MasterTimelineTitleView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MasterTimelineTitleView.xib; sourceTree = ""; }; + 5148F4542336DB7000F8CD8B /* MasterTimelineTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineTitleView.swift; sourceTree = ""; }; 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootSplitViewController.swift; sourceTree = ""; }; 514B7D1E23219F3C00BAC947 /* AddControllerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddControllerType.swift; sourceTree = ""; }; 51554BFC228B6EB50055115A /* SyncDatabase.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SyncDatabase.xcodeproj; path = Frameworks/SyncDatabase/SyncDatabase.xcodeproj; sourceTree = SOURCE_ROOT; }; @@ -1350,6 +1354,8 @@ children = ( 51C4526E2265091600C03939 /* MasterTimelineViewController.swift */, 51D6A5BB23199C85001C27D8 /* MasterTimelineDataSource.swift */, + 5148F4542336DB7000F8CD8B /* MasterTimelineTitleView.swift */, + 5148F44A2336DB4700F8CD8B /* MasterTimelineTitleView.xib */, 51C4526F2265091600C03939 /* Cell */, ); path = MasterTimeline; @@ -2510,6 +2516,7 @@ buildActionMask = 2147483647; files = ( 517630052336215100E15FFF /* main.js in Resources */, + 5148F44B2336DB4700F8CD8B /* MasterTimelineTitleView.xib in Resources */, 511D43D0231FA62500FB1562 /* TimelineKeyboardShortcuts.plist in Resources */, 51C452862265093600C03939 /* Add.storyboard in Resources */, 511D43EF231FBDE900FB1562 /* LaunchScreenPad.storyboard in Resources */, @@ -2800,6 +2807,7 @@ 51C4528D2265095F00C03939 /* AddFolderViewController.swift in Sources */, 51934CD023108953006127BE /* ActivityID.swift in Sources */, 51C452782265091600C03939 /* MasterTimelineCellData.swift in Sources */, + 5148F4552336DB7000F8CD8B /* MasterTimelineTitleView.swift in Sources */, 513228FC233037630033D4ED /* Reachability.swift in Sources */, 51C45259226508D300C03939 /* AppDefaults.swift in Sources */, 519D73FB2323FF35008BB345 /* SettingsView.swift in Sources */, diff --git a/NetNewsWire.xcodeproj/xcshareddata/xcschemes/NetNewsWire iOS Share Extension.xcscheme b/NetNewsWire.xcodeproj/xcshareddata/xcschemes/NetNewsWire iOS Share Extension.xcscheme new file mode 100644 index 000000000..e255a7c7d --- /dev/null +++ b/NetNewsWire.xcodeproj/xcshareddata/xcschemes/NetNewsWire iOS Share Extension.xcscheme @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Shared/Article Rendering/main.js b/Shared/Article Rendering/main.js index e33d725da..f2fd4d24e 100644 --- a/Shared/Article Rendering/main.js +++ b/Shared/Article Rendering/main.js @@ -1,11 +1,20 @@ +// These mouse functions are used by NetNewsWire for Mac to display link previews function mouseDidEnterLink(anchor) { window.webkit.messageHandlers.mouseDidEnter.postMessage(anchor.href); } - function mouseDidExitLink(anchor) { window.webkit.messageHandlers.mouseDidExit.postMessage(anchor.href); } +// Add the mouse listeners for the above functions +function linkHover() { + document.querySelectorAll("a").forEach(element => { + element.addEventListener("mouseenter", function() { mouseDidEnterLink(this) }); + element.addEventListener("mouseleave", function() { mouseDidExitLink(this) }); + }); +} + +// Here we are making iframes responsive. Particularly useful for inline Youtube videos. function wrapFrames() { document.querySelectorAll("iframe").forEach(element => { var wrapper = document.createElement("div"); @@ -15,17 +24,19 @@ function wrapFrames() { }); } +// Strip out all styling so that we have better control over layout function stripStyles() { document.getElementsByTagName("body")[0].querySelectorAll("style, link[rel=stylesheet]").forEach(element => element.remove()); document.getElementsByTagName("body")[0].querySelectorAll("[style]").forEach(element => element.removeAttribute("style")); } -function linkHover() { - var anchors = document.getElementsByTagName("a"); - for (var i = 0; i < anchors.length; i++) { - anchors[i].addEventListener("mouseenter", function() { mouseDidEnterLink(this) }); - anchors[i].addEventListener("mouseleave", function() { mouseDidExitLink(this) }); - } +// Add the playsinline attribute to any HTML5 videos that don't have it. +// Without this attribute videos may autoplay and take over the whole screen +// on an iphone when viewing an article. +function inlineVideos() { + document.querySelectorAll("video").forEach(element => { + element.setAttribute("playsinline", true) + }); } function error() { @@ -35,9 +46,11 @@ function error() { function render(data) { document.getElementsByTagName("style")[0].innerHTML = data.style; document.body.innerHTML = data.body; + window.scrollTo(0, 0); wrapFrames() stripStyles() linkHover() + inlineVideos() } diff --git a/iOS/AppDefaults.swift b/iOS/AppDefaults.swift index be4de2a08..3ef8572f5 100644 --- a/iOS/AppDefaults.swift +++ b/iOS/AppDefaults.swift @@ -10,6 +10,8 @@ import UIKit struct AppDefaults { + static var shared = UserDefaults.standard + struct Key { static let lastImageCacheFlushDate = "lastImageCacheFlushDate" static let firstRunDate = "firstRunDate" @@ -21,7 +23,7 @@ struct AppDefaults { } static let isFirstRun: Bool = { - if let _ = UserDefaults.standard.object(forKey: Key.firstRunDate) as? Date { + if let _ = AppDefaults.shared.object(forKey: Key.firstRunDate) as? Date { return false } firstRunDate = Date() @@ -39,11 +41,11 @@ struct AppDefaults { static var refreshInterval: RefreshInterval { get { - let rawValue = UserDefaults.standard.integer(forKey: Key.refreshInterval) + let rawValue = AppDefaults.shared.integer(forKey: Key.refreshInterval) return RefreshInterval(rawValue: rawValue) ?? RefreshInterval.everyHour } set { - UserDefaults.standard.set(newValue.rawValue, forKey: Key.refreshInterval) + AppDefaults.shared.set(newValue.rawValue, forKey: Key.refreshInterval) } } @@ -89,7 +91,7 @@ struct AppDefaults { Key.timelineGroupByFeed: false, Key.timelineNumberOfLines: 3, Key.timelineSortDirection: ComparisonResult.orderedDescending.rawValue] - UserDefaults.standard.register(defaults: defaults) + AppDefaults.shared.register(defaults: defaults) } } @@ -106,27 +108,27 @@ private extension AppDefaults { } static func bool(for key: String) -> Bool { - return UserDefaults.standard.bool(forKey: key) + return AppDefaults.shared.bool(forKey: key) } static func setBool(for key: String, _ flag: Bool) { - UserDefaults.standard.set(flag, forKey: key) + AppDefaults.shared.set(flag, forKey: key) } static func int(for key: String) -> Int { - return UserDefaults.standard.integer(forKey: key) + return AppDefaults.shared.integer(forKey: key) } static func setInt(for key: String, _ x: Int) { - UserDefaults.standard.set(x, forKey: key) + AppDefaults.shared.set(x, forKey: key) } static func date(for key: String) -> Date? { - return UserDefaults.standard.object(forKey: key) as? Date + return AppDefaults.shared.object(forKey: key) as? Date } static func setDate(for key: String, _ date: Date?) { - UserDefaults.standard.set(date, forKey: key) + AppDefaults.shared.set(date, forKey: key) } static func sortDirection(for key:String) -> ComparisonResult { diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift index af2cca088..2bce61ac6 100644 --- a/iOS/AppDelegate.swift +++ b/iOS/AppDelegate.swift @@ -55,8 +55,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele // Force lazy initialization of the web view provider so that it can warm up the queue of prepared web views let _ = DetailViewControllerWebViewProvider.shared - - AccountManager.shared = AccountManager(accountsFolder: RSDataSubfolder(nil, "Accounts")!) + AccountManager.shared = AccountManager() + AppDefaults.shared = UserDefaults.init(suiteName: "group.\(Bundle.main.bundleIdentifier!)")! registerBackgroundTasks() diff --git a/iOS/Detail/DetailViewControllerWebViewProvider.swift b/iOS/Detail/DetailViewControllerWebViewProvider.swift index 35bf79b04..58c1d7fdf 100644 --- a/iOS/Detail/DetailViewControllerWebViewProvider.swift +++ b/iOS/Detail/DetailViewControllerWebViewProvider.swift @@ -63,7 +63,16 @@ class DetailViewControllerWebViewProvider: NSObject, WKNavigationDelegate { private func replenishQueueIfNeeded() { while queue.count < minimumQueueDepth { - let webView = WKWebView(frame: .zero) + let preferences = WKPreferences() + preferences.javaScriptCanOpenWindowsAutomatically = false + preferences.javaScriptEnabled = true + + let configuration = WKWebViewConfiguration() + configuration.preferences = preferences + configuration.allowsInlineMediaPlayback = true + configuration.mediaTypesRequiringUserActionForPlayback = .video + + let webView = WKWebView(frame: .zero, configuration: configuration) enqueueWebView(webView) } } diff --git a/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift b/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift index 80c30cb82..47645738b 100644 --- a/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift +++ b/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift @@ -93,7 +93,10 @@ class MasterFeedTableViewCell : NNWTableViewCell { }() private let faviconImageView: UIImageView = { - return NonIntrinsicImageView(image: AppAssets.faviconTemplateImage) + let imageView = NonIntrinsicImageView(image: AppAssets.faviconTemplateImage) + imageView.layer.cornerRadius = MasterFeedTableViewCellLayout.faviconCornerRadius + imageView.clipsToBounds = true + return imageView }() private var unreadCountView = MasterFeedUnreadCountView(frame: CGRect.zero) diff --git a/iOS/MasterFeed/Cell/MasterFeedTableViewCellLayout.swift b/iOS/MasterFeed/Cell/MasterFeedTableViewCellLayout.swift index 6da4a0345..0417cc1f4 100644 --- a/iOS/MasterFeed/Cell/MasterFeedTableViewCellLayout.swift +++ b/iOS/MasterFeed/Cell/MasterFeedTableViewCellLayout.swift @@ -21,6 +21,8 @@ struct MasterFeedTableViewCellLayout { private static let minRowHeight = CGFloat(integerLiteral: 44) + static let faviconCornerRadius = CGFloat(integerLiteral: 2) + let faviconRect: CGRect let titleRect: CGRect let unreadCountRect: CGRect diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 61a9ce8d0..10006583b 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -53,6 +53,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(feedMetadataDidChange(_:)), name: .FeedMetadataDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userDidAddFeed(_:)), name: .UserDidAddFeed, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil) @@ -123,6 +124,10 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } } + @objc func feedMetadataDidChange(_ note: Notification) { + reloadAllVisibleCells() + } + @objc func userDidAddFeed(_ notification: Notification) { guard let feed = notification.userInfo?[UserInfoKey.feed] as? Feed else { return diff --git a/iOS/MasterTimeline/MasterTimelineTitleView.swift b/iOS/MasterTimeline/MasterTimelineTitleView.swift new file mode 100644 index 000000000..2861ce634 --- /dev/null +++ b/iOS/MasterTimeline/MasterTimelineTitleView.swift @@ -0,0 +1,24 @@ +// +// MasterFeedTitleView.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 9/21/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import UIKit + +class MasterTimelineTitleView: UIView { + + @IBOutlet weak var imageView: UIImageView! { + didSet { + if let imageView = imageView { + imageView.layer.cornerRadius = 2 + imageView.clipsToBounds = true + } + } + } + + @IBOutlet weak var label: UILabel! + +} diff --git a/iOS/MasterTimeline/MasterTimelineTitleView.xib b/iOS/MasterTimeline/MasterTimelineTitleView.xib new file mode 100644 index 000000000..169cf3eb5 --- /dev/null +++ b/iOS/MasterTimeline/MasterTimelineTitleView.xib @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 6f476ce7c..a7bbe6645 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -454,7 +454,12 @@ private extension MasterTimelineViewController { func resetUI() { title = coordinator.timelineName - navigationController?.title = coordinator.timelineName + if let titleView = Bundle.main.loadNibNamed("MasterTimelineTitleView", owner: self, options: nil)?[0] as? MasterTimelineTitleView { + titleView.imageView.image = coordinator.timelineFavicon + titleView.label.text = coordinator.timelineName + navigationItem.titleView = titleView + } + tableView.selectRow(at: nil, animated: false, scrollPosition: .top) if dataSource.snapshot().itemIdentifiers(inSection: 0).count > 0 { diff --git a/iOS/Resources/Info.plist b/iOS/Resources/Info.plist index cee7acd46..c5fa02fdd 100644 --- a/iOS/Resources/Info.plist +++ b/iOS/Resources/Info.plist @@ -2,6 +2,10 @@ + AppGroup + group.$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS + AppIdentifierPrefix + $(AppIdentifierPrefix) BGTaskSchedulerPermittedIdentifiers com.ranchero.NetNewsWire.FeedRefresh diff --git a/iOS/Resources/NetNewsWire.entitlements b/iOS/Resources/NetNewsWire.entitlements index d6b369115..05d04e805 100644 --- a/iOS/Resources/NetNewsWire.entitlements +++ b/iOS/Resources/NetNewsWire.entitlements @@ -6,5 +6,9 @@ group.$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS + keychain-access-groups + + $(AppIdentifierPrefix)$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS + diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 7d48eb653..ae8e43b85 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -101,6 +101,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { private(set) var currentFeedIndexPath: IndexPath? + var timelineFavicon: RSImage? { + return (timelineFetcher as? SmallIconProvider)?.smallIcon + } + var timelineName: String? { return (timelineFetcher as? DisplayNameProvider)?.nameForDisplay } diff --git a/iOS/Settings/SettingsView.swift b/iOS/Settings/SettingsView.swift index 54066bba4..c853d60c4 100644 --- a/iOS/Settings/SettingsView.swift +++ b/iOS/Settings/SettingsView.swift @@ -46,7 +46,7 @@ struct SettingsView : View { func buildAccountsSection() -> some View { Section(header: Text("ACCOUNTS").padding(.top, 22.0)) { - ForEach(0.. + AppGroup + group.$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS + AppIdentifierPrefix + $(AppIdentifierPrefix) CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName diff --git a/iOS/ShareExtension/NetNewsWire_iOS_ShareExtension.entitlements b/iOS/ShareExtension/NetNewsWire_iOS_ShareExtension.entitlements index d6b369115..05d04e805 100644 --- a/iOS/ShareExtension/NetNewsWire_iOS_ShareExtension.entitlements +++ b/iOS/ShareExtension/NetNewsWire_iOS_ShareExtension.entitlements @@ -6,5 +6,9 @@ group.$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS + keychain-access-groups + + $(AppIdentifierPrefix)$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS + diff --git a/iOS/ShareExtension/ShareFolderPickerController.swift b/iOS/ShareExtension/ShareFolderPickerController.swift index 0385159c6..d20b3e59d 100644 --- a/iOS/ShareExtension/ShareFolderPickerController.swift +++ b/iOS/ShareExtension/ShareFolderPickerController.swift @@ -10,7 +10,7 @@ import UIKit import Account protocol ShareFolderPickerControllerDelegate: class { - func shareFolderPickerDidSelect(_ container: Container) + func shareFolderPickerDidSelect(_ container: Container, _ selectionName: String) } class ShareFolderPickerController: UITableViewController { @@ -37,14 +37,15 @@ class ShareFolderPickerController: UITableViewController { cell.textLabel?.text = pickerData?.containerNames[indexPath.row] ?? "" if pickerData?.containers[indexPath.row] === selectedContainer { cell.accessoryType = .checkmark + } else { + cell.accessoryType = .none } return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let pickerData = pickerData else { return } - delegate?.shareFolderPickerDidSelect(pickerData.containers[indexPath.row]) - navigationController?.popViewController(animated: true) + delegate?.shareFolderPickerDidSelect(pickerData.containers[indexPath.row], pickerData.containerNames[indexPath.row]) } } diff --git a/iOS/ShareExtension/ShareViewController.swift b/iOS/ShareExtension/ShareViewController.swift index 09a9aacec..19d42cb4e 100644 --- a/iOS/ShareExtension/ShareViewController.swift +++ b/iOS/ShareExtension/ShareViewController.swift @@ -20,10 +20,12 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont private var url: URL? private var container: Container? + private var folderItem: SLComposeSheetConfigurationItem! override func viewDidLoad() { - AccountManager.shared = AccountManager(accountsFolder: RSDataSubfolder(nil, "Accounts")!) + AccountManager.shared = AccountManager() + pickerData = FlattenedAccountFolderPickerData() if pickerData?.containers.count ?? 0 > 0 { @@ -99,7 +101,6 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont } override func didSelectPost() { - var account: Account? if let containerAccount = container as? Account { account = containerAccount @@ -126,16 +127,12 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont } } - - - - // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments. - - // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context. } - func shareFolderPickerDidSelect(_ container: Container) { + func shareFolderPickerDidSelect(_ container: Container, _ selectionName: String) { self.container = container + self.folderItem.value = selectionName + self.popConfigurationViewController() } override func configurationItems() -> [Any]! { @@ -145,7 +142,7 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont urlItem.title = "URL" urlItem.value = url?.absoluteString ?? "" - guard let folderItem = SLComposeSheetConfigurationItem() else { return nil } + folderItem = SLComposeSheetConfigurationItem() folderItem.title = "Folder" if let nameProvider = container as? DisplayNameProvider { @@ -165,7 +162,7 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont } - return [folderItem, urlItem] + return [folderItem!, urlItem] } diff --git a/submodules/RSCore b/submodules/RSCore index 960eaf603..ced48ad15 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit 960eaf60336f592306fb1bf6f5a62800a9c5050f +Subproject commit ced48ad15ebc762fea50beb618eed3cf10721148