Merge branch 'master' of https://github.com/brentsimmons/NetNewsWire
This commit is contained in:
commit
e7337ed5ec
|
@ -14,7 +14,7 @@ extension RSImage {
|
|||
static let avatarSize = 48
|
||||
|
||||
static func scaledForAvatar(_ data: Data, imageResultBlock: @escaping (RSImage?) -> Void) {
|
||||
DispatchQueue.global().async {
|
||||
DispatchQueue.global(qos: .default).async {
|
||||
let image = RSImage.scaledForAvatar(data)
|
||||
DispatchQueue.main.async {
|
||||
imageResultBlock(image)
|
||||
|
|
|
@ -18,7 +18,9 @@ import AppKit
|
|||
|
||||
public class ColorHash {
|
||||
|
||||
public static let defaultLS = [CGFloat(0.45), CGFloat(0.6), CGFloat(0.75)]
|
||||
public static let defaultSaturation = [CGFloat(0.35), CGFloat(0.5), CGFloat(0.65)]
|
||||
public static let defaultBrightness = [CGFloat(0.5), CGFloat(0.65), CGFloat(0.80)]
|
||||
|
||||
let seed = CGFloat(131.0)
|
||||
let seed2 = CGFloat(137.0)
|
||||
let maxSafeInteger = 9007199254740991.0 / CGFloat(137.0)
|
||||
|
@ -28,7 +30,7 @@ public class ColorHash {
|
|||
public private(set) var brightness: [CGFloat]
|
||||
public private(set) var saturation: [CGFloat]
|
||||
|
||||
public init(_ str: String, _ saturation: [CGFloat] = defaultLS, _ brightness: [CGFloat] = defaultLS) {
|
||||
public init(_ str: String, _ saturation: [CGFloat] = defaultSaturation, _ brightness: [CGFloat] = defaultBrightness) {
|
||||
self.str = str
|
||||
self.saturation = saturation
|
||||
self.brightness = brightness
|
||||
|
|
|
@ -18,11 +18,28 @@ extension Notification.Name {
|
|||
|
||||
final class FaviconDownloader {
|
||||
|
||||
private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0)
|
||||
|
||||
private let folder: String
|
||||
private let diskCache: BinaryDiskCache
|
||||
private var singleFaviconDownloaderCache = [String: SingleFaviconDownloader]() // faviconURL: SingleFaviconDownloader
|
||||
|
||||
private var homePageToFaviconURLCache = [String: String]() //homePageURL: faviconURL
|
||||
private var homePageURLsWithNoFaviconURL = Set<String>()
|
||||
private var homePageToFaviconURLCachePath: String
|
||||
private var homePageToFaviconURLCacheDirty = false {
|
||||
didSet {
|
||||
queueSaveHomePageToFaviconURLCacheIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
private var homePageURLsWithNoFaviconURLCache = Set<String>()
|
||||
private var homePageURLsWithNoFaviconURLCachePath: String
|
||||
private var homePageURLsWithNoFaviconURLCacheDirty = false {
|
||||
didSet {
|
||||
queueSaveHomePageURLsWithNoFaviconURLCacheIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
private let queue: DispatchQueue
|
||||
private var cache = [Feed: RSImage]() // faviconURL: RSImage
|
||||
|
||||
|
@ -36,6 +53,11 @@ final class FaviconDownloader {
|
|||
self.diskCache = BinaryDiskCache(folder: folder)
|
||||
self.queue = DispatchQueue(label: "FaviconDownloader serial queue - \(folder)")
|
||||
|
||||
self.homePageToFaviconURLCachePath = (folder as NSString).appendingPathComponent("HomePageToFaviconURLCache.plist")
|
||||
self.homePageURLsWithNoFaviconURLCachePath = (folder as NSString).appendingPathComponent("HomePageURLsWithNoFaviconURLCache.plist")
|
||||
loadHomePageToFaviconURLCache()
|
||||
loadHomePageURLsWithNoFaviconURLCache()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(didLoadFavicon(_:)), name: .DidLoadFavicon, object: nil)
|
||||
}
|
||||
|
||||
|
@ -92,7 +114,7 @@ final class FaviconDownloader {
|
|||
func favicon(withHomePageURL homePageURL: String) -> RSImage? {
|
||||
|
||||
let url = homePageURL.rs_normalizedURL()
|
||||
if homePageURLsWithNoFaviconURL.contains(url) {
|
||||
if homePageURLsWithNoFaviconURLCache.contains(url) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -103,10 +125,12 @@ final class FaviconDownloader {
|
|||
findFaviconURL(with: url) { (faviconURL) in
|
||||
if let faviconURL = faviconURL {
|
||||
self.homePageToFaviconURLCache[url] = faviconURL
|
||||
self.homePageToFaviconURLCacheDirty = true
|
||||
let _ = self.favicon(with: faviconURL)
|
||||
}
|
||||
else {
|
||||
self.homePageURLsWithNoFaviconURL.insert(url)
|
||||
self.homePageURLsWithNoFaviconURLCache.insert(url)
|
||||
self.homePageURLsWithNoFaviconURLCacheDirty = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,6 +150,18 @@ final class FaviconDownloader {
|
|||
|
||||
postFaviconDidBecomeAvailableNotification(singleFaviconDownloader.faviconURL)
|
||||
}
|
||||
|
||||
@objc func saveHomePageToFaviconURLCacheIfNeeded() {
|
||||
if homePageToFaviconURLCacheDirty {
|
||||
saveHomePageToFaviconURLCache()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func saveHomePageURLsWithNoFaviconURLCacheIfNeeded() {
|
||||
if homePageURLsWithNoFaviconURLCacheDirty {
|
||||
saveHomePageURLsWithNoFaviconURLCache()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension FaviconDownloader {
|
||||
|
@ -175,4 +211,60 @@ private extension FaviconDownloader {
|
|||
NotificationCenter.default.post(name: .FaviconDidBecomeAvailable, object: self, userInfo: userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
func loadHomePageToFaviconURLCache() {
|
||||
let url = URL(fileURLWithPath: homePageToFaviconURLCachePath)
|
||||
guard let data = try? Data(contentsOf: url) else {
|
||||
return
|
||||
}
|
||||
let decoder = PropertyListDecoder()
|
||||
homePageToFaviconURLCache = (try? decoder.decode([String: String].self, from: data)) ?? [String: String]()
|
||||
}
|
||||
|
||||
func loadHomePageURLsWithNoFaviconURLCache() {
|
||||
let url = URL(fileURLWithPath: homePageURLsWithNoFaviconURLCachePath)
|
||||
guard let data = try? Data(contentsOf: url) else {
|
||||
return
|
||||
}
|
||||
let decoder = PropertyListDecoder()
|
||||
let decoded = (try? decoder.decode([String].self, from: data)) ?? [String]()
|
||||
homePageURLsWithNoFaviconURLCache = Set(decoded)
|
||||
}
|
||||
|
||||
func queueSaveHomePageToFaviconURLCacheIfNeeded() {
|
||||
FaviconDownloader.saveQueue.add(self, #selector(saveHomePageToFaviconURLCacheIfNeeded))
|
||||
}
|
||||
|
||||
func queueSaveHomePageURLsWithNoFaviconURLCacheIfNeeded() {
|
||||
FaviconDownloader.saveQueue.add(self, #selector(saveHomePageURLsWithNoFaviconURLCacheIfNeeded))
|
||||
}
|
||||
|
||||
func saveHomePageToFaviconURLCache() {
|
||||
homePageToFaviconURLCacheDirty = false
|
||||
|
||||
let encoder = PropertyListEncoder()
|
||||
encoder.outputFormat = .binary
|
||||
let url = URL(fileURLWithPath: homePageToFaviconURLCachePath)
|
||||
do {
|
||||
let data = try encoder.encode(homePageToFaviconURLCache)
|
||||
try data.write(to: url)
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func saveHomePageURLsWithNoFaviconURLCache() {
|
||||
homePageURLsWithNoFaviconURLCacheDirty = false
|
||||
|
||||
let encoder = PropertyListEncoder()
|
||||
encoder.outputFormat = .binary
|
||||
let url = URL(fileURLWithPath: homePageURLsWithNoFaviconURLCachePath)
|
||||
do {
|
||||
let data = try encoder.encode(Array(homePageURLsWithNoFaviconURLCache))
|
||||
try data.write(to: url)
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,7 +32,14 @@ public final class FeedIconDownloader {
|
|||
}
|
||||
}
|
||||
|
||||
private var homePagesWithNoIconURL = Set<String>()
|
||||
private var homePagesWithNoIconURLCache = Set<String>()
|
||||
private var homePagesWithNoIconURLCachePath: String
|
||||
private var homePagesWithNoIconURLCacheDirty = false {
|
||||
didSet {
|
||||
queueHomePagesWithNoIconURLCacheIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
private var urlsInProgress = Set<String>()
|
||||
private var cache = [Feed: RSImage]()
|
||||
private var waitingForFeedURLs = [String: Feed]()
|
||||
|
@ -40,7 +47,9 @@ public final class FeedIconDownloader {
|
|||
init(imageDownloader: ImageDownloader, folder: String) {
|
||||
self.imageDownloader = imageDownloader
|
||||
self.homePageToIconURLCachePath = (folder as NSString).appendingPathComponent("HomePageToIconURLCache.plist")
|
||||
self.homePagesWithNoIconURLCachePath = (folder as NSString).appendingPathComponent("HomePagesWithNoIconURLCache.plist")
|
||||
loadHomePageToIconURLCache()
|
||||
loadHomePagesWithNoIconURLCache()
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .ImageDidBecomeAvailable, object: imageDownloader)
|
||||
}
|
||||
|
||||
|
@ -99,13 +108,19 @@ public final class FeedIconDownloader {
|
|||
}
|
||||
}
|
||||
|
||||
@objc func saveHomePagesWithNoIconURLCacheIfNeeded() {
|
||||
if homePagesWithNoIconURLCacheDirty {
|
||||
saveHomePagesWithNoIconURLCache()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension FeedIconDownloader {
|
||||
|
||||
func icon(forHomePageURL homePageURL: String, feed: Feed, _ imageResultBlock: @escaping (RSImage?) -> Void) {
|
||||
|
||||
if homePagesWithNoIconURL.contains(homePageURL) {
|
||||
if homePagesWithNoIconURLCache.contains(homePageURL) {
|
||||
imageResultBlock(nil)
|
||||
return
|
||||
}
|
||||
|
@ -141,7 +156,8 @@ private extension FeedIconDownloader {
|
|||
}
|
||||
|
||||
func cacheIconURL(for homePageURL: String, _ iconURL: String) {
|
||||
homePagesWithNoIconURL.remove(homePageURL)
|
||||
homePagesWithNoIconURLCache.remove(homePageURL)
|
||||
homePagesWithNoIconURLCacheDirty = true
|
||||
homePageToIconURLCache[homePageURL] = iconURL
|
||||
homePageToIconURLCacheDirty = true
|
||||
}
|
||||
|
@ -172,7 +188,8 @@ private extension FeedIconDownloader {
|
|||
return
|
||||
}
|
||||
|
||||
homePagesWithNoIconURL.insert(homePageURL)
|
||||
homePagesWithNoIconURLCache.insert(homePageURL)
|
||||
homePagesWithNoIconURLCacheDirty = true
|
||||
}
|
||||
|
||||
func loadHomePageToIconURLCache() {
|
||||
|
@ -184,10 +201,24 @@ private extension FeedIconDownloader {
|
|||
homePageToIconURLCache = (try? decoder.decode([String: String].self, from: data)) ?? [String: String]()
|
||||
}
|
||||
|
||||
func loadHomePagesWithNoIconURLCache() {
|
||||
let url = URL(fileURLWithPath: homePagesWithNoIconURLCachePath)
|
||||
guard let data = try? Data(contentsOf: url) else {
|
||||
return
|
||||
}
|
||||
let decoder = PropertyListDecoder()
|
||||
let decoded = (try? decoder.decode([String].self, from: data)) ?? [String]()
|
||||
homePagesWithNoIconURLCache = Set(decoded)
|
||||
}
|
||||
|
||||
func queueSaveHomePageToIconURLCacheIfNeeded() {
|
||||
FeedIconDownloader.saveQueue.add(self, #selector(saveHomePageToIconURLCacheIfNeeded))
|
||||
}
|
||||
|
||||
func queueHomePagesWithNoIconURLCacheIfNeeded() {
|
||||
FeedIconDownloader.saveQueue.add(self, #selector(saveHomePagesWithNoIconURLCacheIfNeeded))
|
||||
}
|
||||
|
||||
func saveHomePageToIconURLCache() {
|
||||
homePageToIconURLCacheDirty = false
|
||||
|
||||
|
@ -202,4 +233,18 @@ private extension FeedIconDownloader {
|
|||
}
|
||||
}
|
||||
|
||||
func saveHomePagesWithNoIconURLCache() {
|
||||
homePagesWithNoIconURLCacheDirty = false
|
||||
|
||||
let encoder = PropertyListEncoder()
|
||||
encoder.outputFormat = .binary
|
||||
let url = URL(fileURLWithPath: homePagesWithNoIconURLCachePath)
|
||||
do {
|
||||
let data = try encoder.encode(Array(homePagesWithNoIconURLCache))
|
||||
try data.write(to: url)
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -109,6 +109,7 @@ private extension AddContainerViewController {
|
|||
return
|
||||
}
|
||||
|
||||
navigationItem.title = NSLocalizedString("Add Feed", comment: "Add Feed")
|
||||
resetUI()
|
||||
hideCurrentController()
|
||||
|
||||
|
@ -126,6 +127,7 @@ private extension AddContainerViewController {
|
|||
return
|
||||
}
|
||||
|
||||
navigationItem.title = NSLocalizedString("Add Folder", comment: "Add Folder")
|
||||
resetUI()
|
||||
hideCurrentController()
|
||||
displayContentController(UIStoryboard.add.instantiateController(ofType: AddFolderViewController.self))
|
||||
|
|
|
@ -44,7 +44,6 @@ class AddFeedViewController: UITableViewController, AddContainerViewControllerCh
|
|||
urlTextField.autocapitalizationType = .none
|
||||
urlTextField.text = initialFeed
|
||||
urlTextField.delegate = self
|
||||
urlTextField.becomeFirstResponder()
|
||||
|
||||
if initialFeed != nil {
|
||||
delegate?.readyToAdd(state: true)
|
||||
|
|
|
@ -31,7 +31,6 @@ class AddFolderViewController: UITableViewController, AddContainerViewController
|
|||
accounts = AccountManager.shared.sortedActiveAccounts
|
||||
|
||||
nameTextField.delegate = self
|
||||
nameTextField.becomeFirstResponder()
|
||||
|
||||
accountLabel.text = (accounts[0] as DisplayNameProvider).nameForDisplay
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ var appDelegate: AppDelegate!
|
|||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, UnreadCountProvider {
|
||||
|
||||
private var waitBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
|
||||
private var syncBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
|
||||
|
||||
var syncTimer: ArticleStatusSyncTimer?
|
||||
|
@ -130,28 +131,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|||
|
||||
func prepareAccountsForBackground() {
|
||||
syncTimer?.invalidate()
|
||||
|
||||
// Schedule background app refresh
|
||||
scheduleBackgroundFeedRefresh()
|
||||
|
||||
// Sync article status
|
||||
let completeProcessing = { [unowned self] in
|
||||
UIApplication.shared.endBackgroundTask(self.syncBackgroundUpdateTask)
|
||||
self.syncBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
|
||||
}
|
||||
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
self.syncBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask {
|
||||
completeProcessing()
|
||||
os_log("Accounts sync processing terminated for running too long.", log: self.log, type: .info)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
AccountManager.shared.syncArticleStatusAll() {
|
||||
completeProcessing()
|
||||
}
|
||||
}
|
||||
}
|
||||
waitForProgressToFinish()
|
||||
syncArticleStatus()
|
||||
}
|
||||
|
||||
func prepareAccountsForForeground() {
|
||||
|
@ -200,12 +182,15 @@ private extension AppDelegate {
|
|||
let faviconsFolderURL = tempDir.appendingPathComponent("Favicons")
|
||||
let imagesFolderURL = tempDir.appendingPathComponent("Images")
|
||||
let homePageToIconURL = tempDir.appendingPathComponent("HomePageToIconURLCache.plist")
|
||||
let homePagesWithNoIconURL = tempDir.appendingPathComponent("HomePagesWithNoIconURLCache.plist")
|
||||
let homePageToFaviconURL = tempDir.appendingPathComponent("HomePageToFaviconURLCache.plist")
|
||||
let homePageURLsWithNoFaviconURL = tempDir.appendingPathComponent("HomePageURLsWithNoFaviconURLCache.plist")
|
||||
|
||||
// If the image disk cache hasn't been flushed for 3 days and the network is available, delete it
|
||||
if let flushDate = AppDefaults.lastImageCacheFlushDate, flushDate.addingTimeInterval(3600*24*3) < Date() {
|
||||
if let reachability = try? Reachability(hostname: "apple.com") {
|
||||
if reachability.connection != .unavailable {
|
||||
for tempItem in [faviconsFolderURL, imagesFolderURL, homePageToIconURL] {
|
||||
for tempItem in [faviconsFolderURL, imagesFolderURL, homePageToIconURL, homePagesWithNoIconURL, homePageToFaviconURL, homePageURLsWithNoFaviconURL] {
|
||||
do {
|
||||
os_log(.info, log: self.log, "Removing cache file: %@", tempItem.absoluteString)
|
||||
try FileManager.default.removeItem(at: tempItem)
|
||||
|
@ -253,6 +238,68 @@ private extension AppDelegate {
|
|||
|
||||
}
|
||||
|
||||
// MARK: Go To Background
|
||||
private extension AppDelegate {
|
||||
|
||||
func waitForProgressToFinish() {
|
||||
let completeProcessing = { [unowned self] in
|
||||
AccountManager.shared.saveAll()
|
||||
UIApplication.shared.endBackgroundTask(self.waitBackgroundUpdateTask)
|
||||
self.waitBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
|
||||
}
|
||||
|
||||
self.waitBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask {
|
||||
completeProcessing()
|
||||
os_log("Accounts wait for progress terminated for running too long.", log: self.log, type: .info)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.waitToComplete() {
|
||||
completeProcessing()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func waitToComplete(completion: @escaping () -> Void) {
|
||||
guard UIApplication.shared.applicationState != .active else {
|
||||
os_log("App came back to forground, no longer waiting.", log: self.log, type: .info)
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
if AccountManager.shared.refreshInProgress {
|
||||
os_log("Waiting for refresh progress to finish...", log: self.log, type: .info)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
|
||||
self?.waitToComplete() {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
os_log("Refresh progress complete.", log: self.log, type: .info)
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
func syncArticleStatus() {
|
||||
let completeProcessing = { [unowned self] in
|
||||
UIApplication.shared.endBackgroundTask(self.syncBackgroundUpdateTask)
|
||||
self.syncBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
|
||||
}
|
||||
|
||||
self.syncBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask {
|
||||
completeProcessing()
|
||||
os_log("Accounts sync processing terminated for running too long.", log: self.log, type: .info)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
AccountManager.shared.syncArticleStatusAll() {
|
||||
completeProcessing()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: Background Tasks
|
||||
|
||||
private extension AppDelegate {
|
||||
|
|
|
@ -85,6 +85,9 @@ class ArticleViewController: UIViewController {
|
|||
|
||||
deinit {
|
||||
if webView != nil {
|
||||
webView?.evaluateJavaScript("cancelImageLoad();")
|
||||
webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasClicked)
|
||||
webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasShown)
|
||||
webView.removeFromSuperview()
|
||||
ArticleViewControllerWebViewProvider.shared.enqueueWebView(webView)
|
||||
webView = nil
|
||||
|
@ -109,8 +112,6 @@ class ArticleViewController: UIViewController {
|
|||
webView.navigationDelegate = self
|
||||
webView.uiDelegate = self
|
||||
|
||||
webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasClicked)
|
||||
webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasShown)
|
||||
webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasClicked)
|
||||
webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasShown)
|
||||
|
||||
|
@ -118,8 +119,6 @@ class ArticleViewController: UIViewController {
|
|||
// to work around this bug: http://www.openradar.me/22855188
|
||||
let url = Bundle.main.url(forResource: "page", withExtension: "html")!
|
||||
webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent())
|
||||
// let request = URLRequest(url: url)
|
||||
// webView.load(request)
|
||||
|
||||
}
|
||||
|
||||
|
@ -184,6 +183,7 @@ class ArticleViewController: UIViewController {
|
|||
render = "render(\(json));"
|
||||
}
|
||||
|
||||
webView?.scrollView.setZoomScale(1.0, animated: false)
|
||||
webView?.evaluateJavaScript(render)
|
||||
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ final class AvatarView: UIView {
|
|||
imageView.image = image
|
||||
|
||||
if self.traitCollection.userInterfaceStyle == .dark {
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
DispatchQueue.global(qos: .default).async {
|
||||
if self.image?.isDark() ?? false {
|
||||
DispatchQueue.main.async {
|
||||
self.isDisconcernable = false
|
||||
|
@ -74,7 +74,7 @@ final class AvatarView: UIView {
|
|||
|
||||
override func layoutSubviews() {
|
||||
imageView.setFrameIfNotEqual(rectForImageView())
|
||||
if (isVerticalBackgroundExposed && !isSymbolImage) || !isDisconcernable {
|
||||
if (image != nil && isVerticalBackgroundExposed && !isSymbolImage) || !isDisconcernable {
|
||||
backgroundColor = AppAssets.avatarBackgroundColor
|
||||
} else {
|
||||
backgroundColor = nil
|
||||
|
|
|
@ -69,7 +69,6 @@ struct FeedInspectorView : View {
|
|||
.onDisappear { self.viewModel.save() }
|
||||
.navigationBarTitle(Text(verbatim: self.viewModel.nameForDisplay), displayMode: .inline)
|
||||
.navigationBarItems(leading: Button(action: {
|
||||
self.viewModel.save()
|
||||
self.viewController?.dismiss(animated: true)
|
||||
}) { Text("Done") } )
|
||||
}
|
||||
|
@ -137,7 +136,8 @@ struct FeedInspectorView : View {
|
|||
|
||||
func save() {
|
||||
if name != nameForDisplay {
|
||||
feed.editedName = name.isEmpty ? nil : name
|
||||
let newName = name.isEmpty ? (feed.name ?? NSLocalizedString("Untitled", comment: "Feed name")) : name
|
||||
feed.rename(to: newName) { _ in }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -87,6 +87,7 @@ class MasterFeedTableViewCell : VibrantTableViewCell {
|
|||
label.numberOfLines = 0
|
||||
label.allowsDefaultTighteningForTruncation = false
|
||||
label.adjustsFontForContentSizeCategory = true
|
||||
label.lineBreakMode = .byTruncatingTail
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
return label
|
||||
}()
|
||||
|
|
|
@ -37,10 +37,6 @@ struct MasterFeedTableViewCellLayout {
|
|||
if indent {
|
||||
initialIndent += MasterFeedTableViewCellLayout.disclosureButtonSize.width
|
||||
}
|
||||
if showingEditingControl {
|
||||
initialIndent += MasterFeedTableViewCellLayout.editingControlIndent
|
||||
}
|
||||
|
||||
let bounds = CGRect(x: initialIndent, y: 0.0, width: floor(cellWidth - initialIndent - insets.right), height: 0.0)
|
||||
|
||||
// Disclosure Button
|
||||
|
@ -68,18 +64,12 @@ struct MasterFeedTableViewCellLayout {
|
|||
if !unreadCountIsHidden {
|
||||
rUnread.size = unreadCountSize
|
||||
rUnread.origin.x = bounds.maxX - (MasterFeedTableViewCellLayout.unreadCountMarginRight + unreadCountSize.width)
|
||||
if showingEditingControl {
|
||||
rUnread.origin.x = rUnread.origin.x - MasterFeedTableViewCellLayout.editingControlIndent
|
||||
}
|
||||
}
|
||||
|
||||
// Title
|
||||
|
||||
var rLabelx = CGFloat.zero
|
||||
if shouldShowDisclosure {
|
||||
rLabelx = bounds.minX + MasterFeedTableViewCellLayout.disclosureButtonSize.width
|
||||
} else {
|
||||
rLabelx = rFavicon.maxX + MasterFeedTableViewCellLayout.imageMarginRight
|
||||
var rLabelx = insets.left + MasterFeedTableViewCellLayout.disclosureButtonSize.width
|
||||
if !shouldShowDisclosure {
|
||||
rLabelx = rLabelx + MasterFeedTableViewCellLayout.imageSize.width + MasterFeedTableViewCellLayout.imageMarginRight
|
||||
}
|
||||
let rLabely = UIFontMetrics.default.scaledValue(for: MasterFeedTableViewCellLayout.verticalPadding)
|
||||
|
||||
|
@ -91,7 +81,23 @@ struct MasterFeedTableViewCellLayout {
|
|||
}
|
||||
|
||||
let labelSizeInfo = MultilineUILabelSizer.size(for: label.text ?? "", font: label.font, numberOfLines: 0, width: Int(floor(labelWidth)))
|
||||
let rLabel = CGRect(x: rLabelx, y: rLabely, width: labelSizeInfo.size.width, height: labelSizeInfo.size.height)
|
||||
|
||||
// Now that we've got everything (especially the label) computed without the editing controls, update for them.
|
||||
// We do this because we don't want the row height to change when the editing controls are brought out. We will
|
||||
// handle the missing space, but removing it from the label and truncating.
|
||||
if showingEditingControl {
|
||||
rDisclosure.origin.x += MasterFeedTableViewCellLayout.editingControlIndent
|
||||
rFavicon.origin.x += MasterFeedTableViewCellLayout.editingControlIndent
|
||||
rLabelx += MasterFeedTableViewCellLayout.editingControlIndent
|
||||
if !unreadCountIsHidden {
|
||||
rUnread.origin.x -= MasterFeedTableViewCellLayout.editingControlIndent
|
||||
labelWidth = cellWidth - (rLabelx + MasterFeedTableViewCellLayout.labelMarginRight + (cellWidth - rUnread.minX))
|
||||
} else {
|
||||
labelWidth = cellWidth - (rLabelx + MasterFeedTableViewCellLayout.labelMarginRight + MasterFeedTableViewCellLayout.editingControlIndent)
|
||||
}
|
||||
}
|
||||
|
||||
let rLabel = CGRect(x: rLabelx, y: rLabely, width: labelWidth, height: labelSizeInfo.size.height)
|
||||
|
||||
// Determine cell height
|
||||
let paddedLabelHeight = rLabel.maxY + UIFontMetrics.default.scaledValue(for: MasterFeedTableViewCellLayout.verticalPadding)
|
||||
|
|
|
@ -14,7 +14,7 @@ import RSTree
|
|||
|
||||
class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||
|
||||
private var refreshProgressView: RefreshProgressView!
|
||||
private var refreshProgressView: RefreshProgressView?
|
||||
private var addNewItemButton: UIBarButtonItem!
|
||||
|
||||
private lazy var dataSource = makeDataSource()
|
||||
|
@ -99,10 +99,8 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
node = coordinator.rootNode.descendantNodeRepresentingObject(representedObject as AnyObject)
|
||||
}
|
||||
|
||||
if let node = node, let indexPath = dataSource.indexPath(for: node), let unreadCountProvider = node.representedObject as? UnreadCountProvider {
|
||||
if let cell = tableView.cellForRow(at: indexPath) as? MasterFeedTableViewCell {
|
||||
cell.unreadCount = unreadCountProvider.unreadCount
|
||||
}
|
||||
if let node = node, dataSource.indexPath(for: node) != nil {
|
||||
reloadNode(node)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,7 +129,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
guard let feed = notification.userInfo?[UserInfoKey.feed] as? Feed else {
|
||||
return
|
||||
}
|
||||
discloseFeed(feed)
|
||||
discloseFeed(feed, animated: true)
|
||||
}
|
||||
|
||||
@objc func contentSizeCategoryDidChange(_ note: Notification) {
|
||||
|
@ -483,7 +481,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
}
|
||||
}
|
||||
|
||||
func discloseFeed(_ feed: Feed, completion: (() -> Void)? = nil) {
|
||||
func discloseFeed(_ feed: Feed, animated: Bool, completion: (() -> Void)? = nil) {
|
||||
|
||||
guard let node = coordinator.rootNode.descendantNodeRepresentingObject(feed as AnyObject) else {
|
||||
completion?()
|
||||
|
@ -492,7 +490,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
|
||||
if let indexPath = dataSource.indexPath(for: node) {
|
||||
tableView.selectRowAndScrollIfNotVisible(at: indexPath, animated: true)
|
||||
coordinator.selectFeed(indexPath)
|
||||
coordinator.selectFeed(indexPath, animated: animated)
|
||||
completion?()
|
||||
return
|
||||
}
|
||||
|
@ -507,7 +505,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
|
||||
self.applyChanges(animate: true, adjustScroll: true) { [weak self] in
|
||||
if let indexPath = self?.dataSource.indexPath(for: node) {
|
||||
self?.coordinator.selectFeed(indexPath)
|
||||
self?.coordinator.selectFeed(indexPath, animated: animated)
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
@ -572,8 +570,8 @@ private extension MasterFeedViewController {
|
|||
}
|
||||
|
||||
func updateUI() {
|
||||
refreshProgressView.updateRefreshLabel()
|
||||
addNewItemButton.isEnabled = !AccountManager.shared.activeAccounts.isEmpty
|
||||
refreshProgressView?.updateRefreshLabel()
|
||||
addNewItemButton?.isEnabled = !AccountManager.shared.activeAccounts.isEmpty
|
||||
}
|
||||
|
||||
func reloadNode(_ node: Node) {
|
||||
|
@ -656,7 +654,7 @@ private extension MasterFeedViewController {
|
|||
return feedIconImage
|
||||
}
|
||||
|
||||
if let faviconImage = appDelegate.faviconDownloader.faviconAsAvatar(for: feed) {
|
||||
if let faviconImage = appDelegate.faviconDownloader.favicon(for: feed) {
|
||||
return faviconImage
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,6 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
|||
NSLocalizedString("Here", comment: "Here"),
|
||||
NSLocalizedString("All Articles", comment: "All Articles")
|
||||
]
|
||||
navigationItem.searchController = searchController
|
||||
definesPresentationContext = true
|
||||
|
||||
// Configure the table
|
||||
|
@ -74,6 +73,13 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
|||
super.viewWillAppear(animated)
|
||||
}
|
||||
|
||||
override func viewWillLayoutSubviews() {
|
||||
super.viewWillLayoutSubviews()
|
||||
// You have to assign the search controller here to avoid showing it by default
|
||||
// https://stackoverflow.com/questions/57581557/how-to-initally-hide-searchbar-in-navigation-controller-on-ios-13
|
||||
navigationItem.searchController = searchController
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
|
||||
@IBAction func markAllAsRead(_ sender: Any) {
|
||||
|
@ -580,7 +586,8 @@ private extension MasterTimelineViewController {
|
|||
|
||||
let title = NSLocalizedString("Select Feed", comment: "Select Feed")
|
||||
let action = UIAction(title: title, image: AppAssets.openInSidebarImage) { [weak self] action in
|
||||
self?.coordinator.discloseFeed(feed)
|
||||
self?.coordinator.selectFeed(nil, animated: true)
|
||||
self?.coordinator.discloseFeed(feed, animated: true)
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
@ -590,7 +597,8 @@ private extension MasterTimelineViewController {
|
|||
|
||||
let title = NSLocalizedString("Select Feed", comment: "Select Feed")
|
||||
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
||||
self?.coordinator.discloseFeed(feed)
|
||||
self?.coordinator.selectFeed(nil, animated: true)
|
||||
self?.coordinator.discloseFeed(feed, animated: true)
|
||||
completionHandler(true)
|
||||
}
|
||||
return action
|
||||
|
|
|
@ -1,23 +1,37 @@
|
|||
var imageIsLoading = false;
|
||||
var controller = new AbortController()
|
||||
|
||||
// Cancel any pending image loads (there might be none) and reset the controller
|
||||
function cancelImageLoad() {
|
||||
controller.abort();
|
||||
controller = new AbortController();
|
||||
}
|
||||
|
||||
// Used to pop a resizable image view
|
||||
async function imageWasClicked(img) {
|
||||
img.classList.add("nnwClicked");
|
||||
cancelImageLoad();
|
||||
showNetworkLoading(img);
|
||||
|
||||
try {
|
||||
showNetworkLoading(img);
|
||||
const response = await fetch(img.src);
|
||||
|
||||
const signal = controller.signal;
|
||||
const response = await fetch(img.src, { signal: signal });
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok.');
|
||||
}
|
||||
|
||||
const imgBlob = await response.blob();
|
||||
if (signal.aborted) {
|
||||
throw new Error('Network response was aborted.');
|
||||
}
|
||||
|
||||
hideNetworkLoading(img);
|
||||
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.readAsDataURL(imgBlob);
|
||||
reader.onloadend = function() {
|
||||
|
||||
img.classList.add("nnwClicked");
|
||||
|
||||
const rect = img.getBoundingClientRect();
|
||||
var message = {
|
||||
x: rect.x,
|
||||
|
@ -29,8 +43,9 @@ async function imageWasClicked(img) {
|
|||
|
||||
var jsonMessage = JSON.stringify(message);
|
||||
window.webkit.messageHandlers.imageWasClicked.postMessage(jsonMessage);
|
||||
|
||||
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
hideNetworkLoading(img);
|
||||
console.log('There has been a problem with your fetch operation: ', error.message);
|
||||
|
@ -39,7 +54,6 @@ async function imageWasClicked(img) {
|
|||
}
|
||||
|
||||
function showNetworkLoading(img) {
|
||||
imageIsLoading = true;
|
||||
|
||||
var wrapper = document.createElement("div");
|
||||
wrapper.classList.add("activityIndicatorWrap");
|
||||
|
@ -64,8 +78,6 @@ function hideNetworkLoading(img) {
|
|||
var wrapperParent = wrapper.parentNode;
|
||||
wrapperParent.insertBefore(img, wrapper);
|
||||
wrapperParent.removeChild(wrapper);
|
||||
|
||||
imageIsLoading = false;
|
||||
}
|
||||
|
||||
// Used to animate the transition to a fullscreen image
|
||||
|
@ -85,7 +97,7 @@ function showClickedImage() {
|
|||
// Add the click listener for images
|
||||
function imageClicks() {
|
||||
window.onclick = function(event) {
|
||||
if (event.target.matches('img') && !imageIsLoading) {
|
||||
if (event.target.matches('img')) {
|
||||
imageWasClicked(event.target);
|
||||
}
|
||||
}
|
||||
|
@ -101,8 +113,9 @@ function inlineVideos() {
|
|||
}
|
||||
|
||||
function postRenderProcessing() {
|
||||
imageClicks()
|
||||
inlineVideos()
|
||||
cancelImageLoad();
|
||||
imageClicks();
|
||||
inlineVideos();
|
||||
}
|
||||
|
||||
const activityIndicator = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjAiIHdpZHRoPSI2NHB4IiBoZWlnaHQ9IjY0cHgiIHZpZXdCb3g9IjAgMCAxMjggMTI4IiB4bWw6c3BhY2U9InByZXNlcnZlIj48Zz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiMwMDAwMDAiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiNjY2NjY2MiIHRyYW5zZm9ybT0icm90YXRlKDMwIDY0IDY0KSIvPjxwYXRoIGQ9Ik01OS42IDBoOHY0MGgtOFYweiIgZmlsbD0iI2NjY2NjYyIgdHJhbnNmb3JtPSJyb3RhdGUoNjAgNjQgNjQpIi8+PHBhdGggZD0iTTU5LjYgMGg4djQwaC04VjB6IiBmaWxsPSIjY2NjY2NjIiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiNjY2NjY2MiIHRyYW5zZm9ybT0icm90YXRlKDEyMCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiNiMmIyYjIiIHRyYW5zZm9ybT0icm90YXRlKDE1MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM5OTk5OTkiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM3ZjdmN2YiIHRyYW5zZm9ybT0icm90YXRlKDIxMCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM2NjY2NjYiIHRyYW5zZm9ybT0icm90YXRlKDI0MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM0YzRjNGMiIHRyYW5zZm9ybT0icm90YXRlKDI3MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiMzMzMzMzMiIHRyYW5zZm9ybT0icm90YXRlKDMwMCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiMxOTE5MTkiIHRyYW5zZm9ybT0icm90YXRlKDMzMCA2NCA2NCkiLz48YW5pbWF0ZVRyYW5zZm9ybSBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIHR5cGU9InJvdGF0ZSIgdmFsdWVzPSIwIDY0IDY0OzMwIDY0IDY0OzYwIDY0IDY0OzkwIDY0IDY0OzEyMCA2NCA2NDsxNTAgNjQgNjQ7MTgwIDY0IDY0OzIxMCA2NCA2NDsyNDAgNjQgNjQ7MjcwIDY0IDY0OzMwMCA2NCA2NDszMzAgNjQgNjQiIGNhbGNNb2RlPSJkaXNjcmV0ZSIgZHVyPSIxMDgwbXMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIj48L2FuaW1hdGVUcmFuc2Zvcm0+PC9nPjwvc3ZnPg==";
|
||||
|
|
|
@ -14,10 +14,10 @@ class RootSplitViewController: UISplitViewController {
|
|||
var coordinator: SceneCoordinator!
|
||||
|
||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
if UIApplication.shared.applicationState != .background {
|
||||
self.coordinator.configureThreePanelMode(for: size)
|
||||
}
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
coordinator.animate(alongsideTransition: { [weak self] context in
|
||||
self?.coordinator.configureThreePanelMode(for: size)
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: Keyboard Shortcuts
|
||||
|
|
|
@ -65,6 +65,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
private var lastSearchScope: SearchScope? = nil
|
||||
private var isSearching: Bool = false
|
||||
private var searchArticleIds: Set<String>? = nil
|
||||
private var isTimelineViewControllerPending = false
|
||||
private var isArticleViewControllerPending = false
|
||||
|
||||
private(set) var sortDirection = AppDefaults.timelineSortDirection {
|
||||
|
@ -782,8 +783,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
markArticlesWithUndo([article], statusKey: .starred, flag: !article.status.starred)
|
||||
}
|
||||
|
||||
func discloseFeed(_ feed: Feed, completion: (() -> Void)? = nil) {
|
||||
masterFeedViewController.discloseFeed(feed) {
|
||||
func discloseFeed(_ feed: Feed, animated: Bool, completion: (() -> Void)? = nil) {
|
||||
masterFeedViewController.discloseFeed(feed, animated: animated) {
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
@ -952,11 +953,15 @@ extension SceneCoordinator: UINavigationControllerDelegate {
|
|||
}
|
||||
|
||||
// If we are showing the Feeds and only the feeds start clearing stuff
|
||||
if viewController === masterFeedViewController && !isThreePanelMode {
|
||||
if viewController === masterFeedViewController && !isThreePanelMode && !isTimelineViewControllerPending {
|
||||
activityManager.invalidateCurrentActivities()
|
||||
selectFeed(nil)
|
||||
return
|
||||
}
|
||||
|
||||
if viewController is MasterTimelineViewController {
|
||||
isTimelineViewControllerPending = false
|
||||
}
|
||||
|
||||
// If we are using a phone and navigate away from the detail, clear up the article resources (including activity).
|
||||
// Don't clear it if we have pushed an ArticleViewController, but don't yet see it on the navigation stack.
|
||||
|
@ -1473,6 +1478,9 @@ private extension SceneCoordinator {
|
|||
// MARK: Double Split
|
||||
|
||||
func installTimelineControllerIfNecessary(animated: Bool) {
|
||||
|
||||
isTimelineViewControllerPending = true
|
||||
|
||||
if navControllerForTimeline().viewControllers.filter({ $0 is MasterTimelineViewController }).count < 1 {
|
||||
masterTimelineViewController = UIStoryboard.main.instantiateController(ofType: MasterTimelineViewController.self)
|
||||
masterTimelineViewController!.coordinator = self
|
||||
|
@ -1654,7 +1662,7 @@ private extension SceneCoordinator {
|
|||
return
|
||||
}
|
||||
if let feed = feedNode.representedObject as? Feed {
|
||||
discloseFeed(feed)
|
||||
discloseFeed(feed, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1663,7 +1671,7 @@ private extension SceneCoordinator {
|
|||
return
|
||||
}
|
||||
|
||||
discloseFeed(feedNode.representedObject as! Feed) {
|
||||
discloseFeed(feedNode.representedObject as! Feed, animated: false) {
|
||||
|
||||
guard let articleID = userInfo?[DeepLinkKey.articleID.rawValue] as? String else { return }
|
||||
if let article = self.articles.first(where: { $0.articleID == articleID }) {
|
||||
|
|
|
@ -33,8 +33,11 @@ class AboutViewController: UITableViewController {
|
|||
buildLabel.numberOfLines = 0
|
||||
buildLabel.sizeToFit()
|
||||
buildLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
tableView.tableFooterView = buildLabel
|
||||
|
||||
let wrapperView = UIView(frame: CGRect(x: 0, y: 0, width: buildLabel.frame.width, height: buildLabel.frame.height + 10.0))
|
||||
wrapperView.translatesAutoresizingMaskIntoConstraints = false
|
||||
wrapperView.addSubview(buildLabel)
|
||||
tableView.tableFooterView = wrapperView
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
|
|
|
@ -57,10 +57,89 @@
|
|||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="Feeds" id="hAC-uA-RbS">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="qur-cL-wrM" detailTextLabel="qIl-N6-6wQ" style="IBUITableViewCellStyleValue1" id="z1J-VF-St0" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="255.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="z1J-VF-St0" id="Y8U-Ka-GeZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="383" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Refresh Interval" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="qur-cL-wrM">
|
||||
<rect key="frame" x="20" y="12" width="119.5" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="qIl-N6-6wQ" customClass="VibrantLabel" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="331" y="12" width="44" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="4Hg-B3-zAE" style="IBUITableViewCellStyleDefault" id="glf-Pg-s3P" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="299.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="glf-Pg-s3P" id="bPA-43-Oqh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Import Subscriptions" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="4Hg-B3-zAE">
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="25J-iX-3at" style="IBUITableViewCellStyleDefault" id="qke-Ha-PXl" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="343.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="qke-Ha-PXl" id="pZi-ck-RV5">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Export Subscriptions" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="25J-iX-3at">
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="dXN-Mw-yf2" style="IBUITableViewCellStyleDefault" id="F0L-Ut-reX" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="387.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="F0L-Ut-reX" id="5SX-M2-2jR">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Add NetNewsWire News Feed" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="dXN-Mw-yf2">
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="Timeline" id="9Pk-Y8-JVJ">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="MpA-w1-Wwh" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="255.5" width="414" height="44"/>
|
||||
<rect key="frame" x="0.0" y="487.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="MpA-w1-Wwh" id="GhU-ib-Mz8">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
|
@ -90,7 +169,7 @@
|
|||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="f7r-AZ-aDn" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="299.5" width="414" height="44"/>
|
||||
<rect key="frame" x="0.0" y="531.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="f7r-AZ-aDn" id="KHC-cc-tOC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
|
@ -120,7 +199,7 @@
|
|||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="5wo-fM-0l6" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="343.5" width="414" height="44"/>
|
||||
<rect key="frame" x="0.0" y="575.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="5wo-fM-0l6" id="XAn-lK-LoN">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
|
@ -151,72 +230,10 @@
|
|||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="Database" id="hAC-uA-RbS">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="qur-cL-wrM" detailTextLabel="qIl-N6-6wQ" style="IBUITableViewCellStyleValue1" id="z1J-VF-St0" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="443.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="z1J-VF-St0" id="Y8U-Ka-GeZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="383" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Refresh Interval" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="qur-cL-wrM">
|
||||
<rect key="frame" x="20" y="12" width="119.5" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="qIl-N6-6wQ" customClass="VibrantLabel" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="331" y="12" width="44" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="4Hg-B3-zAE" style="IBUITableViewCellStyleDefault" id="glf-Pg-s3P" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="487.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="glf-Pg-s3P" id="bPA-43-Oqh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Import OPML" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="4Hg-B3-zAE">
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="25J-iX-3at" style="IBUITableViewCellStyleDefault" id="qke-Ha-PXl" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="531.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="qke-Ha-PXl" id="pZi-ck-RV5">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Export OPML" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="25J-iX-3at">
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="About" id="TkH-4v-yhk">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="2o6-8W-nyK" style="IBUITableViewCellStyleDefault" id="he9-Ql-yfa" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="631.5" width="414" height="44"/>
|
||||
<rect key="frame" x="0.0" y="675.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="he9-Ql-yfa" id="q6L-C8-H9a">
|
||||
<rect key="frame" x="0.0" y="0.0" width="383" height="44"/>
|
||||
|
@ -233,7 +250,7 @@
|
|||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="lOk-Dh-GfZ" style="IBUITableViewCellStyleDefault" id="GWZ-jk-qU6" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="675.5" width="414" height="44"/>
|
||||
<rect key="frame" x="0.0" y="719.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="GWZ-jk-qU6" id="ZgS-bo-xDl">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
|
@ -250,7 +267,7 @@
|
|||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="Pm8-6D-fdE" style="IBUITableViewCellStyleDefault" id="3cU-BG-6kK" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="719.5" width="414" height="44"/>
|
||||
<rect key="frame" x="0.0" y="763.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="3cU-BG-6kK" id="Qm0-SY-0vx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
|
@ -267,7 +284,7 @@
|
|||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="TEA-EG-V6d" style="IBUITableViewCellStyleDefault" id="4yc-ig-I61" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="763.5" width="414" height="44"/>
|
||||
<rect key="frame" x="0.0" y="807.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="4yc-ig-I61" id="uQl-VP-9p9">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
|
@ -284,7 +301,7 @@
|
|||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="Q9a-Pi-uCc" style="IBUITableViewCellStyleDefault" id="mSW-A7-8lf" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="807.5" width="414" height="44"/>
|
||||
<rect key="frame" x="0.0" y="851.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="mSW-A7-8lf" id="shF-ro-Zpx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
|
@ -301,7 +318,7 @@
|
|||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="dWz-1o-EpJ" style="IBUITableViewCellStyleDefault" id="2MG-qn-idJ" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="851.5" width="414" height="44"/>
|
||||
<rect key="frame" x="0.0" y="895.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="2MG-qn-idJ" id="gP9-ry-keC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
|
@ -317,23 +334,6 @@
|
|||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="dXN-Mw-yf2" style="IBUITableViewCellStyleDefault" id="F0L-Ut-reX" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="895.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="F0L-Ut-reX" id="5SX-M2-2jR">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Add NetNewsWire News Feed" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="dXN-Mw-yf2">
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
</sections>
|
||||
|
|
|
@ -66,7 +66,11 @@ class SettingsViewController: UITableViewController {
|
|||
buildLabel.text = "\(Bundle.main.appName) v \(Bundle.main.versionNumber) (Build \(Bundle.main.buildNumber))"
|
||||
buildLabel.sizeToFit()
|
||||
buildLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
tableView.tableFooterView = buildLabel
|
||||
|
||||
let wrapperView = UIView(frame: CGRect(x: 0, y: 0, width: buildLabel.frame.width, height: buildLabel.frame.height + 10.0))
|
||||
wrapperView.translatesAutoresizingMaskIntoConstraints = false
|
||||
wrapperView.addSubview(buildLabel)
|
||||
tableView.tableFooterView = wrapperView
|
||||
|
||||
}
|
||||
|
||||
|
@ -81,7 +85,7 @@ class SettingsViewController: UITableViewController {
|
|||
switch section {
|
||||
case 1:
|
||||
return AccountManager.shared.accounts.count + 1
|
||||
case 4:
|
||||
case 2:
|
||||
let defaultNumberOfRows = super.tableView(tableView, numberOfRowsInSection: section)
|
||||
if AccountManager.shared.activeAccounts.isEmpty || AccountManager.shared.anyAccountHasFeedWithURL(appNewsURLString) {
|
||||
return defaultNumberOfRows - 1
|
||||
|
@ -135,7 +139,7 @@ class SettingsViewController: UITableViewController {
|
|||
controller.account = sortedAccounts[indexPath.row]
|
||||
self.navigationController?.pushViewController(controller, animated: true)
|
||||
}
|
||||
case 3:
|
||||
case 2:
|
||||
switch indexPath.row {
|
||||
case 0:
|
||||
let timeline = UIStoryboard.settings.instantiateController(ofType: RefreshIntervalViewController.self)
|
||||
|
@ -152,6 +156,9 @@ class SettingsViewController: UITableViewController {
|
|||
let sourceRect = tableView.rectForRow(at: indexPath)
|
||||
exportOPML(sourceView: sourceView, sourceRect: sourceRect)
|
||||
}
|
||||
case 3:
|
||||
addFeed()
|
||||
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -175,9 +182,6 @@ class SettingsViewController: UITableViewController {
|
|||
case 5:
|
||||
openURL("https://github.com/brentsimmons/NetNewsWire/tree/master/Technotes")
|
||||
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
|
||||
case 6:
|
||||
addFeed()
|
||||
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -264,7 +268,15 @@ extension SettingsViewController: UIDocumentPickerDelegate {
|
|||
|
||||
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
||||
for url in urls {
|
||||
opmlAccount?.importOPML(url) { result in}
|
||||
opmlAccount?.importOPML(url) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
let title = NSLocalizedString("Import Failed", comment: "Import Failed")
|
||||
self.presentError(title: title, message: error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue