Merge branch 'upstream/master'
|
@ -0,0 +1,58 @@
|
|||
# iOS CircleCI 2.0 configuration file
|
||||
#
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
|
||||
# Specify the Xcode version to use
|
||||
macos:
|
||||
xcode: "10.2.1"
|
||||
# https://circleci.com/docs/2.0/configuration-reference/
|
||||
|
||||
# Mac/IOS specific examples and docs under the following links:
|
||||
# https://circleci.com/docs/2.0/hello-world-macos/
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- run: git submodule sync
|
||||
- run: git submodule update --init
|
||||
# Commands will execute in macOS container
|
||||
# with Xcode 10.2.1 installed
|
||||
- run: xcodebuild -version
|
||||
#- run:
|
||||
# name: get xcodebuild build options
|
||||
# command: xcodebuild -help
|
||||
- run:
|
||||
name: get xcodebuild build settings
|
||||
command: xcodebuild -showBuildSettings
|
||||
|
||||
- run:
|
||||
name: force wipe of any pre-existing derived data in CI
|
||||
command: rm -rf /Users/distiller/Library/Developer/Xcode/DerivedData/NetNewsWire-*
|
||||
|
||||
# Build the app and run tests
|
||||
- run:
|
||||
name: Build Mac
|
||||
command: xcodebuild -workspace NetNewsWire.xcworkspace -scheme NetNewsWire -configuration Debug -showBuildTimingSummary
|
||||
# NOTE(heckj):
|
||||
# the -configuration Release build invokes a shell script specifically
|
||||
# codesigning the Sparkle pieces with the developer 'Brent Simmons',
|
||||
# so we don't try and invoke that in CI
|
||||
#
|
||||
|
||||
# the stuff below is from example that was using fastlane
|
||||
# (and we're not using that...) so it's placeholder tidbits
|
||||
# to clue me in to where I can get things for test log output
|
||||
# for the CircleCI UI exposure...
|
||||
|
||||
# Collect XML test results data to show in the UI,
|
||||
# and save the same XML files under test-results folder
|
||||
# in the Artifacts tab
|
||||
#- store_test_results:
|
||||
# path: test_output/report.xml
|
||||
#- store_artifacts:
|
||||
# path: /tmp/test-results
|
||||
# destination: scan-test-results
|
||||
#- store_artifacts:
|
||||
# path: ~/Library/Logs/scan
|
||||
# destination: scan-logs
|
|
@ -6,6 +6,58 @@
|
|||
<description>Most recent NetNewsWire changes with links to updates.</description>
|
||||
<language>en</language>
|
||||
|
||||
<item>
|
||||
<title>NetNewsWire 5.0a3</title>
|
||||
<description><![CDATA[
|
||||
<p>Fixed crash happening only on macOS 10.15 beta. We owe Apple a bug report for this one.</p>
|
||||
<p>Fixed a crash that could happen when finding a feed.</p>
|
||||
<p>Skip showing error dialogs on automatic refreshes.</p>
|
||||
<p>Immediately show the refresh progress bar when an OPML import to Feedbin starts.</p>
|
||||
<p>Add ellipsis to Import from OPML and Export to OPML buttons.</p>
|
||||
]]></description>
|
||||
<pubDate>Mon, 10 Jun 2019 21:45:00 -0700</pubDate>
|
||||
<enclosure url="https://ranchero.com/downloads/NetNewsWire5.0a3.zip" sparkle:version="2223" sparkle:shortVersionString="5.0a3" length="4689888" type="application/zip" />
|
||||
<sparkle:minimumSystemVersion>10.14.4</sparkle:minimumSystemVersion>
|
||||
</item>
|
||||
|
||||
|
||||
<item>
|
||||
<title>NetNewsWire 5.0a2</title>
|
||||
<description><![CDATA[
|
||||
<p>Escape HTML in the title in the article view — if there’s HTML in the title, the tags should actually be displayed.</p>
|
||||
<p>The Mark as Read command in the Article menu now turns into Mark as Unread at the appropriate times.</p>
|
||||
<p>Feedbin syncing: send locally changed statuses before downloading statuses from the server.</p>
|
||||
<p>Feedbin syncing: fix bug renaming a folder that has no feeds.</p>
|
||||
<p>Feedbin syncing: fixed a bunch of accuracy and reliability issues, and a crashing bug.</p>
|
||||
<p>Fixed issue where local account feed finder could lock UI in the case of an error.</p>
|
||||
]]></description>
|
||||
<pubDate>Sat, 08 Jun 2019 16:00:00 -0700</pubDate>
|
||||
<enclosure url="https://ranchero.com/downloads/NetNewsWire5.0a2.zip" sparkle:version="2209" sparkle:shortVersionString="5.0a2" length="4691481" type="application/zip" />
|
||||
<sparkle:minimumSystemVersion>10.14.4</sparkle:minimumSystemVersion>
|
||||
</item>
|
||||
|
||||
|
||||
<item>
|
||||
<title>NetNewsWire 5.0a1</title>
|
||||
<description><![CDATA[
|
||||
<p>NetNewsWire 5.0 has reached alpha stage! This means it has no known bugs. It surely <i>does</i> have bugs, though. Now it’s time for testing. (And writing the Help book. And making the website better.)</p>
|
||||
<p>Fixed a crashing bug with parsing a response from Feedbin. (Totally our fault, not Feedbin’s fault.)</p>
|
||||
<p>Show avatars from Micro.blog feeds with multiple authors (such as your personal timeline feed).</p>
|
||||
<p>Made OPML import to the On My Mac account way faster.</p>
|
||||
<p>The Today smart feed now updates when the day changes.</p>
|
||||
<p>You can now drag and drop in the sidebar between accounts.</p>
|
||||
<p>Made the default file name for OPML exports “Subscriptions-[accountName].opml”</p>
|
||||
<p>Add explanation text to Account preferences for the Name field. (It’s just a display name and doesn’t affect authentication.)</p>
|
||||
<p>Fixed several bugs with Feedbin syncing — it’s now more reliable. (We know of no remaining sync bugs, though of course there might be some.)</p>
|
||||
<p>Added a placeholder web page for the Help book.</p>
|
||||
<p>New app icon! But it might take a while for your Mac to notice and put in the Dock. (I wish we could speed that up, but it’s out of our control.)</p>
|
||||
]]></description>
|
||||
<pubDate>Fri, 31 May 2019 20:30:00 -0700</pubDate>
|
||||
<enclosure url="https://ranchero.com/downloads/NetNewsWire5.0a1.zip" sparkle:version="2185" sparkle:shortVersionString="5.0a1" length="4686634" type="application/zip" />
|
||||
<sparkle:minimumSystemVersion>10.14.4</sparkle:minimumSystemVersion>
|
||||
</item>
|
||||
|
||||
|
||||
<item>
|
||||
<title>NetNewsWire 5.0d17</title>
|
||||
<description><![CDATA[
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
# Contributing
|
||||
|
||||
We welcome contributions!
|
||||
|
||||
If you’d like to contribute:
|
||||
|
||||
1. File a ticket describing the bug you want to fix or feature you want to add. Or find an existing ticket.
|
||||
2. On the Slack group, bring it up on the #work channel for discussion (which may or may not include implementation discussion).
|
||||
3. Once approved, then go for it. Write the code, then do a pull request. We’ll either have comments or we’ll merge it. (We might revise it afterward, of course.)
|
||||
|
||||
## Notes
|
||||
|
||||
It’s important that the pull request merge cleanly with master.
|
||||
|
||||
You should have read the [coding guidelines](Technotes/CodingGuidelines.md) first. If your code doesn’t follow the guidelines, we will likely suggest revising it.
|
||||
|
||||
Patience may be required at times. Brent has a day job, and sometimes everything happens at once. :)
|
||||
|
||||
Our code of conduct is below.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
### Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
### Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
### Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
### Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
### Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting Brent Simmons at brent@ranchero.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
### Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
|
@ -263,6 +263,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
switch credentials {
|
||||
case .basic(let username, _):
|
||||
self.username = username
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
try CredentialsManager.storeCredentials(credentials, server: server)
|
||||
|
|
|
@ -18,6 +18,10 @@ public enum AccountError: LocalizedError {
|
|||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .createErrorNotFound:
|
||||
return NSLocalizedString("The feed couldn't be found and can't be added.", comment: "Not found")
|
||||
case .createErrorAlreadySubscribed:
|
||||
return NSLocalizedString("You are already subscribed to this feed and can't add it again.", comment: "Already subscribed")
|
||||
case .opmlImportInProgress:
|
||||
return NSLocalizedString("An OPML import for this account is already running.", comment: "Import running")
|
||||
case .wrappedError(let error, let account):
|
||||
|
@ -32,13 +36,15 @@ public enum AccountError: LocalizedError {
|
|||
default:
|
||||
return unknownError(error, account)
|
||||
}
|
||||
default:
|
||||
return NSLocalizedString("An unknown error occurred.", comment: "Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
public var recoverySuggestion: String? {
|
||||
switch self {
|
||||
case .createErrorNotFound:
|
||||
return nil
|
||||
case .createErrorAlreadySubscribed:
|
||||
return nil
|
||||
case .wrappedError(let error, _):
|
||||
switch error {
|
||||
case TransportError.httpError(let status):
|
||||
|
|
|
@ -64,7 +64,7 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha
|
|||
set {
|
||||
let oldNameForDisplay = nameForDisplay
|
||||
metadata.name = newValue
|
||||
if oldNameForDisplay != nameForDisplay {
|
||||
if oldNameForDisplay != newValue {
|
||||
postDisplayNameDidChangeNotification()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ struct FeedSpecifier: Hashable {
|
|||
return feedSpecifiers.anyObject()
|
||||
}
|
||||
|
||||
var currentHighScore = 0
|
||||
var currentHighScore = Int.min
|
||||
var currentBestFeed: FeedSpecifier? = nil
|
||||
|
||||
for oneFeedSpecifier in feedSpecifiers {
|
||||
|
|
|
@ -532,11 +532,11 @@ extension FeedbinAPICaller {
|
|||
|
||||
if let lowerBound = link.range(of: "page=")?.upperBound {
|
||||
let partialLink = link[lowerBound..<link.endIndex]
|
||||
if let upperBound = partialLink.range(of: "&")?.lowerBound {
|
||||
return Int(link[lowerBound..<upperBound])
|
||||
if let upperBound = partialLink.firstIndex(of: "&") {
|
||||
return Int(partialLink[partialLink.startIndex..<upperBound])
|
||||
}
|
||||
if let upperBound = partialLink.range(of: ">")?.lowerBound {
|
||||
return Int(link[lowerBound..<upperBound])
|
||||
if let upperBound = partialLink.firstIndex(of: ">") {
|
||||
return Int(partialLink[partialLink.startIndex..<upperBound])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -88,11 +88,13 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
|||
case .success():
|
||||
|
||||
self.refreshArticles(account) {
|
||||
self.refreshArticleStatus(for: account) {
|
||||
self.refreshMissingArticles(account) {
|
||||
self.refreshProgress.clear()
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(()))
|
||||
self.sendArticleStatus(for: account) {
|
||||
self.refreshArticleStatus(for: account) {
|
||||
self.refreshMissingArticles(account) {
|
||||
self.refreshProgress.clear()
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -206,12 +208,14 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
|||
|
||||
os_log(.debug, log: log, "Begin importing OPML...")
|
||||
opmlImportInProgress = true
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
|
||||
caller.importOPML(opmlData: opmlData) { result in
|
||||
switch result {
|
||||
case .success(let importResult):
|
||||
if importResult.complete {
|
||||
os_log(.debug, log: self.log, "Import OPML done.")
|
||||
self.refreshProgress.completeTask()
|
||||
self.opmlImportInProgress = false
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(()))
|
||||
|
@ -221,6 +225,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
|||
}
|
||||
case .failure(let error):
|
||||
os_log(.debug, log: self.log, "Import OPML failed.")
|
||||
self.refreshProgress.completeTask()
|
||||
self.opmlImportInProgress = false
|
||||
DispatchQueue.main.async {
|
||||
let wrappedError = AccountError.wrappedError(error: error, account: account)
|
||||
|
@ -241,10 +246,16 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
|||
|
||||
func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
guard folder.hasAtLeastOneFeed() else {
|
||||
folder.name = name
|
||||
return
|
||||
}
|
||||
|
||||
caller.renameTag(oldName: folder.name ?? "", newName: name) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
self.renameFolderRelationship(for: account, fromName: folder.name ?? "", toName: name)
|
||||
folder.name = name
|
||||
completion(.success(()))
|
||||
}
|
||||
|
@ -269,16 +280,44 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
|||
let group = DispatchGroup()
|
||||
|
||||
for feed in folder.topLevelFeeds {
|
||||
group.enter()
|
||||
removeFeed(for: account, with: feed, from: folder) { result in
|
||||
group.leave()
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription)
|
||||
|
||||
if feed.folderRelationship?.count ?? 0 > 1 {
|
||||
|
||||
if let feedTaggingID = feed.folderRelationship?[folder.name ?? ""] {
|
||||
group.enter()
|
||||
caller.deleteTagging(taggingID: feedTaggingID) { result in
|
||||
group.leave()
|
||||
switch result {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if let subscriptionID = feed.subscriptionID {
|
||||
group.enter()
|
||||
caller.deleteSubscription(subscriptionID: subscriptionID) { result in
|
||||
group.leave()
|
||||
switch result {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
account.clearFeedMetadata(feed)
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
|
@ -347,7 +386,6 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
|||
if feed.folderRelationship?.count ?? 0 > 1 {
|
||||
deleteTagging(for: account, with: feed, from: container, completion: completion)
|
||||
} else {
|
||||
account.clearFeedMetadata(feed)
|
||||
deleteSubscription(for: account, with: feed, from: container, completion: completion)
|
||||
}
|
||||
}
|
||||
|
@ -399,12 +437,23 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
|||
|
||||
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
createFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
if let existingFeed = account.existingFeed(withURL: feed.url) {
|
||||
account.addFeed(existingFeed, to: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
createFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -412,22 +461,27 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
|||
|
||||
func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
account.addFolder(folder)
|
||||
let group = DispatchGroup()
|
||||
|
||||
for feed in folder.topLevelFeeds {
|
||||
|
||||
folder.topLevelFeeds.remove(feed)
|
||||
|
||||
group.enter()
|
||||
addFeed(for: account, with: feed, to: folder) { result in
|
||||
if account.topLevelFeeds.contains(feed) {
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
restoreFeed(for: account, feed: feed, container: folder) { result in
|
||||
group.leave()
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Restore folder feed error: %@.", error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
account.addFolder(folder)
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
|
@ -502,6 +556,7 @@ private extension FeedbinAccountDelegate {
|
|||
if let result = importResult, result.complete {
|
||||
os_log(.debug, log: self.log, "Checking status of OPML import successfully completed.")
|
||||
timer.invalidate()
|
||||
self.refreshProgress.completeTask()
|
||||
self.opmlImportInProgress = false
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(()))
|
||||
|
@ -510,6 +565,7 @@ private extension FeedbinAccountDelegate {
|
|||
case .failure(let error):
|
||||
os_log(.debug, log: self.log, "Import OPML check failed.")
|
||||
timer.invalidate()
|
||||
self.refreshProgress.completeTask()
|
||||
self.opmlImportInProgress = false
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(error))
|
||||
|
@ -647,7 +703,10 @@ private extension FeedbinAccountDelegate {
|
|||
DispatchQueue.main.sync {
|
||||
if let feed = account.idToFeedDictionary[subFeedId] {
|
||||
feed.name = subscription.name
|
||||
// If the name has been changed on the server remove the locally edited name
|
||||
feed.editedName = nil
|
||||
feed.homePageURL = subscription.homePageURL
|
||||
feed.subscriptionID = String(subscription.subscriptionID)
|
||||
} else {
|
||||
let feed = account.createFeed(with: subscription.name, url: subscription.url, feedID: subFeedId, homePageURL: subscription.homePageURL)
|
||||
feed.subscriptionID = String(subscription.subscriptionID)
|
||||
|
@ -792,6 +851,17 @@ private extension FeedbinAccountDelegate {
|
|||
|
||||
}
|
||||
|
||||
func renameFolderRelationship(for account: Account, fromName: String, toName: String) {
|
||||
for feed in account.flattenedFeeds() {
|
||||
if var folderRelationship = feed.folderRelationship {
|
||||
let relationship = folderRelationship[fromName]
|
||||
folderRelationship[fromName] = nil
|
||||
folderRelationship[toName] = relationship
|
||||
feed.folderRelationship = folderRelationship
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func clearFolderRelationship(for feed: Feed, withFolderName folderName: String) {
|
||||
if var folderRelationship = feed.folderRelationship {
|
||||
folderRelationship[folderName] = nil
|
||||
|
@ -1158,6 +1228,7 @@ private extension FeedbinAccountDelegate {
|
|||
switch result {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
account.clearFeedMetadata(feed)
|
||||
account.removeFeed(feed)
|
||||
if let folders = account.folders {
|
||||
for folder in folders {
|
||||
|
|
|
@ -124,12 +124,12 @@ final class LocalAccountDelegate: AccountDelegate {
|
|||
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
case .failure:
|
||||
completion(.failure(AccountError.createErrorNotFound))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
|
|
@ -413,7 +413,7 @@
|
|||
<items>
|
||||
<menuItem title="Mark as Read" keyEquivalent="U" id="Fc9-c7-2AY">
|
||||
<connections>
|
||||
<action selector="markRead:" target="Ady-hI-5gd" id="RQv-jl-2Nv"/>
|
||||
<action selector="toggleRead:" target="Ady-hI-5gd" id="jLQ-ZF-xye"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Mark All as Read" keyEquivalent="k" id="HdN-Ks-cwh">
|
||||
|
|
|
@ -8,11 +8,18 @@
|
|||
|
||||
import AppKit
|
||||
import Account
|
||||
import os.log
|
||||
|
||||
struct ErrorHandler {
|
||||
|
||||
private static var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Account")
|
||||
|
||||
public static func present(_ error: Error) {
|
||||
NSApplication.shared.presentError(error)
|
||||
}
|
||||
|
||||
public static func log(_ error: Error) {
|
||||
os_log(.error, log: self.log, "%@", error.localizedDescription)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -62,7 +62,10 @@ class AddFeedController: AddFeedWindowControllerDelegate {
|
|||
|
||||
account.createFeed(url: url.absoluteString, name: title, container: container) { result in
|
||||
|
||||
self.endShowingProgress()
|
||||
DispatchQueue.main.async {
|
||||
self.endShowingProgress()
|
||||
}
|
||||
|
||||
BatchUpdate.shared.end()
|
||||
|
||||
switch result {
|
||||
|
|
|
@ -18,12 +18,12 @@
|
|||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="355" height="180"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
|
||||
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="371" height="171"/>
|
||||
<view key="contentView" wantsLayer="YES" misplaced="YES" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="391" height="171"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="mvx-54-DH0">
|
||||
<rect key="frame" x="18" y="100" width="335" height="51"/>
|
||||
<rect key="frame" x="18" y="100" width="355" height="51"/>
|
||||
<textFieldCell key="cell" selectable="YES" id="7Ap-KG-Lc7">
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="title">Choose the account with the subscriptions you’d like to export. Subscriptions are exported in the standard OPML format, which most RSS readers can import.</string>
|
||||
|
@ -40,7 +40,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="bbC-2g-e3k" userLabel="Account Popup">
|
||||
<rect key="frame" x="85" y="58" width="269" height="25"/>
|
||||
<rect key="frame" x="85" y="58" width="289" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Item 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="MJb-Bf-UJG" id="xZd-AP-nuM">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
|
@ -54,8 +54,8 @@
|
|||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<button horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eZ4-Ej-Hks">
|
||||
<rect key="frame" x="219" y="13" width="138" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Export as OPML" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="bRz-cx-bmm">
|
||||
<rect key="frame" x="229" y="13" width="148" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Export as OPML…" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="bRz-cx-bmm">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
|
@ -67,7 +67,7 @@ DQ
|
|||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="PPB-R8-A9a">
|
||||
<rect key="frame" x="81" y="13" width="138" height="32"/>
|
||||
<rect key="frame" x="81" y="13" width="148" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6lK-bV-Vwd">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
|
|
|
@ -18,12 +18,12 @@
|
|||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="401" height="183"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
|
||||
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="401" height="154"/>
|
||||
<view key="contentView" wantsLayer="YES" misplaced="YES" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="421" height="154"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="vE6-sv-BA0">
|
||||
<rect key="frame" x="18" y="100" width="365" height="34"/>
|
||||
<rect key="frame" x="18" y="100" width="385" height="34"/>
|
||||
<textFieldCell key="cell" selectable="YES" title="Choose the account to get the imported subscriptions. This requires an OPML file, which most RSS readers can create." id="1Vu-Te-PGl">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -39,7 +39,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sEU-ot-DE2" userLabel="Account Popup">
|
||||
<rect key="frame" x="85" y="58" width="299" height="25"/>
|
||||
<rect key="frame" x="85" y="58" width="319" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Item 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="xsd-12-2yb" id="NuO-Hk-nk3">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
|
@ -53,7 +53,7 @@
|
|||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ceu-mM-EKm">
|
||||
<rect key="frame" x="81" y="13" width="153" height="32"/>
|
||||
<rect key="frame" x="81" y="13" width="163" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="9ab-cB-hex">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
|
@ -66,8 +66,8 @@ Gw
|
|||
</connections>
|
||||
</button>
|
||||
<button horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="et6-I1-6wB">
|
||||
<rect key="frame" x="234" y="13" width="153" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Import from OPML" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="dhV-on-ayM">
|
||||
<rect key="frame" x="244" y="13" width="163" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Import from OPML…" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="dhV-on-ayM">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
|
|
|
@ -18,15 +18,18 @@ final class SingleLineTextFieldSizer {
|
|||
private let textField: NSTextField
|
||||
private var cache = [String: NSSize]()
|
||||
|
||||
init(font: NSFont) {
|
||||
/// Get the NSTextField size for text, given a font.
|
||||
static func size(for text: String, font: NSFont) -> NSSize {
|
||||
return sizer(for: font).size(for: text)
|
||||
}
|
||||
|
||||
init(font: NSFont) {
|
||||
self.textField = NSTextField(labelWithString: "")
|
||||
self.textField.font = font
|
||||
self.font = font
|
||||
}
|
||||
|
||||
func size(for text: String) -> NSSize {
|
||||
|
||||
if let cachedSize = cache[text] {
|
||||
return cachedSize
|
||||
}
|
||||
|
@ -40,29 +43,23 @@ final class SingleLineTextFieldSizer {
|
|||
return calculatedSize
|
||||
}
|
||||
|
||||
static private var sizers = [NSFont: SingleLineTextFieldSizer]()
|
||||
static private var sizers = [SingleLineTextFieldSizer]()
|
||||
|
||||
static func sizer(for font: NSFont) -> SingleLineTextFieldSizer {
|
||||
|
||||
if let cachedSizer = sizers[font] {
|
||||
static private func sizer(for font: NSFont) -> SingleLineTextFieldSizer {
|
||||
// We used to use an [NSFont: SingleLineTextFieldSizer] dictionary —
|
||||
// until, in 10.14.5, we started getting crashes with the message:
|
||||
// Fatal error: Duplicate keys of type 'NSFont' were found in a Dictionary.
|
||||
// This usually means either that the type violates Hashable's requirements, or
|
||||
// that members of such a dictionary were mutated after insertion.
|
||||
// We use just an array of sizers now — which is totally fine,
|
||||
// because there’s only going to be like three of them.
|
||||
if let cachedSizer = sizers.firstElementPassingTest({ $0.font == font }) {
|
||||
return cachedSizer
|
||||
}
|
||||
|
||||
let newSizer = SingleLineTextFieldSizer(font: font)
|
||||
sizers[font] = newSizer
|
||||
sizers.append(newSizer)
|
||||
|
||||
return newSizer
|
||||
}
|
||||
|
||||
// Use this call. It’s easiest.
|
||||
|
||||
static func size(for text: String, font: NSFont) -> NSSize {
|
||||
|
||||
return sizer(for: font).size(for: text)
|
||||
}
|
||||
|
||||
static func emptyCache() {
|
||||
|
||||
sizers = [NSFont: SingleLineTextFieldSizer]()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "16x16",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_16x16.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "16x16",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_16x16@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "32x32",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_32x32.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "32x32",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_32x32@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "128x128",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_128x128.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "128x128",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_128x128@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "256x256",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_256x256.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "256x256",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_256x256@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "512x512",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_512x512.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "512x512",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_512x512@2x.png",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 371 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 208 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 208 KiB |
Before Width: | Height: | Size: 337 KiB After Width: | Height: | Size: 639 KiB |
|
@ -17,7 +17,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.0d17</string>
|
||||
<string>5.0a3</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
510D707422B028E1004E8F65 /* SettingsAddAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510D707322B028E1004E8F65 /* SettingsAddAccountView.swift */; };
|
||||
510D707E22B02A4B004E8F65 /* SettingsLocalAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510D707D22B02A4B004E8F65 /* SettingsLocalAccountView.swift */; };
|
||||
510D708022B02A5F004E8F65 /* SettingsFeedbinAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510D707F22B02A5F004E8F65 /* SettingsFeedbinAccountView.swift */; };
|
||||
510D708222B041CC004E8F65 /* SettingsAccountLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510D708122B041CC004E8F65 /* SettingsAccountLabelView.swift */; };
|
||||
51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; };
|
||||
5115CAF42266301400B21BCE /* AddContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */; };
|
||||
5126EE97226CB48A00C22AFC /* NavigationStateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5126EE96226CB48A00C22AFC /* NavigationStateController.swift */; };
|
||||
|
@ -134,6 +138,8 @@
|
|||
51EF0F8E2279C9260050506E /* AccountsAdd.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51EF0F8D2279C9260050506E /* AccountsAdd.xib */; };
|
||||
51EF0F902279C9500050506E /* AccountsAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F8F2279C9500050506E /* AccountsAddViewController.swift */; };
|
||||
51EF0F922279CA620050506E /* AccountsAddTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F912279CA620050506E /* AccountsAddTableCellView.swift */; };
|
||||
51F35D0922AFD4760003CE1B /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F35D0822AFD4760003CE1B /* SettingsView.swift */; };
|
||||
51F772F622B279570087D9D1 /* SettingsDetailAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F772EC22B2789B0087D9D1 /* SettingsDetailAccountView.swift */; };
|
||||
51F85BE5227217D000C787DC /* RefreshIntervalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BDB2272162F00C787DC /* RefreshIntervalViewController.swift */; };
|
||||
51F85BE7227245FC00C787DC /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BE6227245FC00C787DC /* AboutViewController.swift */; };
|
||||
51F85BEB22724CB600C787DC /* About.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 51F85BEA22724CB600C787DC /* About.rtf */; };
|
||||
|
@ -159,7 +165,6 @@
|
|||
840958632201629A002C1579 /* Subscribe to Feed.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840BEE4021D70E64009BBAFA /* CrashReportWindowController.swift */; };
|
||||
840D617F2029031C009BC708 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D617E2029031C009BC708 /* AppDelegate.swift */; };
|
||||
840D61962029031D009BC708 /* NetNewsWire_iOSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D61952029031D009BC708 /* NetNewsWire_iOSTests.swift */; };
|
||||
84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84162A142038C12C00035290 /* MarkCommandValidationStatus.swift */; };
|
||||
841ABA4E20145E7300980E11 /* NothingInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA4D20145E7300980E11 /* NothingInspectorViewController.swift */; };
|
||||
841ABA5E20145E9200980E11 /* FolderInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */; };
|
||||
|
@ -456,13 +461,6 @@
|
|||
remoteGlobalIDString = 844BEE401F0AB3AB004AB7CD;
|
||||
remoteInfo = ArticlesDatabaseTests;
|
||||
};
|
||||
840D61922029031D009BC708 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 849C64581ED37A5D003D8FC0 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 840D617B2029031C009BC708;
|
||||
remoteInfo = "NetNewsWire-iOS";
|
||||
};
|
||||
849C64721ED37A5D003D8FC0 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 849C64581ED37A5D003D8FC0 /* Project object */;
|
||||
|
@ -661,6 +659,10 @@
|
|||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
510D707322B028E1004E8F65 /* SettingsAddAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAddAccountView.swift; sourceTree = "<group>"; };
|
||||
510D707D22B02A4B004E8F65 /* SettingsLocalAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLocalAccountView.swift; sourceTree = "<group>"; };
|
||||
510D707F22B02A5F004E8F65 /* SettingsFeedbinAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFeedbinAccountView.swift; sourceTree = "<group>"; };
|
||||
510D708122B041CC004E8F65 /* SettingsAccountLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccountLabelView.swift; sourceTree = "<group>"; };
|
||||
51121AA12265430A00BC0EC1 /* NetNewsWire_iOS_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOS_target.xcconfig; sourceTree = "<group>"; };
|
||||
51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContainerViewController.swift; sourceTree = "<group>"; };
|
||||
51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSImage-Extensions.swift"; sourceTree = "<group>"; };
|
||||
|
@ -727,6 +729,8 @@
|
|||
51EF0F8D2279C9260050506E /* AccountsAdd.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsAdd.xib; sourceTree = "<group>"; };
|
||||
51EF0F8F2279C9500050506E /* AccountsAddViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddViewController.swift; sourceTree = "<group>"; };
|
||||
51EF0F912279CA620050506E /* AccountsAddTableCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddTableCellView.swift; sourceTree = "<group>"; };
|
||||
51F35D0822AFD4760003CE1B /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||
51F772EC22B2789B0087D9D1 /* SettingsDetailAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDetailAccountView.swift; sourceTree = "<group>"; };
|
||||
51F85BDB2272162F00C787DC /* RefreshIntervalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshIntervalViewController.swift; sourceTree = "<group>"; };
|
||||
51F85BE6227245FC00C787DC /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = "<group>"; };
|
||||
51F85BEA22724CB600C787DC /* About.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = About.rtf; sourceTree = "<group>"; };
|
||||
|
@ -756,7 +760,6 @@
|
|||
840BEE4021D70E64009BBAFA /* CrashReportWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportWindowController.swift; sourceTree = "<group>"; };
|
||||
840D617C2029031C009BC708 /* NetNewsWire.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NetNewsWire.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
840D617E2029031C009BC708 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
840D61912029031D009BC708 /* NetNewsWire-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "NetNewsWire-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
840D61952029031D009BC708 /* NetNewsWire_iOSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetNewsWire_iOSTests.swift; sourceTree = "<group>"; };
|
||||
840D61972029031D009BC708 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
84162A142038C12C00035290 /* MarkCommandValidationStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkCommandValidationStatus.swift; sourceTree = "<group>"; };
|
||||
|
@ -873,6 +876,7 @@
|
|||
84C9FCA32262A1B800D921D6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
84CBDDAE1FD3674C005A61AA /* Technotes */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Technotes; sourceTree = "<group>"; };
|
||||
84CC88171FE59CBF00644329 /* SmartFeedsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartFeedsController.swift; sourceTree = "<group>"; };
|
||||
84D2200922B0BC4B0019E085 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = "<group>"; };
|
||||
84D52E941FE588BB00D14F5B /* DetailStatusBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailStatusBarView.swift; sourceTree = "<group>"; };
|
||||
84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleLineTextFieldSizer.swift; sourceTree = "<group>"; };
|
||||
84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineTextFieldSizer.swift; sourceTree = "<group>"; };
|
||||
|
@ -949,13 +953,6 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
840D618E2029031D009BC708 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
849C645D1ED37A5D003D8FC0 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -1043,16 +1040,13 @@
|
|||
5183CCEB227117C70010922C /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5183CCEC22711DCE0010922C /* Settings.storyboard */,
|
||||
51E595AA228DF94C00FCC42B /* SettingsTableViewCell.xib */,
|
||||
5183CCEE227125970010922C /* SettingsViewController.swift */,
|
||||
51E595AC228E1C2100FCC42B /* AddAccountViewController.swift */,
|
||||
515436892291FED9005E1CDF /* FeedbinAccountViewController.swift */,
|
||||
515436872291D75D005E1CDF /* AddLocalAccountViewController.swift */,
|
||||
51F85BE6227245FC00C787DC /* AboutViewController.swift */,
|
||||
51543684228F6753005E1CDF /* DetailAccountViewController.swift */,
|
||||
51F85BDB2272162F00C787DC /* RefreshIntervalViewController.swift */,
|
||||
51EF0F7B2277919E0050506E /* TimelineNumberOfLinesViewController.swift */,
|
||||
510D708122B041CC004E8F65 /* SettingsAccountLabelView.swift */,
|
||||
510D707322B028E1004E8F65 /* SettingsAddAccountView.swift */,
|
||||
51F772EC22B2789B0087D9D1 /* SettingsDetailAccountView.swift */,
|
||||
510D707F22B02A5F004E8F65 /* SettingsFeedbinAccountView.swift */,
|
||||
510D707D22B02A4B004E8F65 /* SettingsLocalAccountView.swift */,
|
||||
51F35D0822AFD4760003CE1B /* SettingsView.swift */,
|
||||
51F35CFD22AFD0350003CE1B /* UIKit */,
|
||||
);
|
||||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1163,6 +1157,23 @@
|
|||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
51F35CFD22AFD0350003CE1B /* UIKit */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5183CCEC22711DCE0010922C /* Settings.storyboard */,
|
||||
51E595AA228DF94C00FCC42B /* SettingsTableViewCell.xib */,
|
||||
5183CCEE227125970010922C /* SettingsViewController.swift */,
|
||||
51E595AC228E1C2100FCC42B /* AddAccountViewController.swift */,
|
||||
515436892291FED9005E1CDF /* FeedbinAccountViewController.swift */,
|
||||
515436872291D75D005E1CDF /* AddLocalAccountViewController.swift */,
|
||||
51F85BE6227245FC00C787DC /* AboutViewController.swift */,
|
||||
51543684228F6753005E1CDF /* DetailAccountViewController.swift */,
|
||||
51F85BDB2272162F00C787DC /* RefreshIntervalViewController.swift */,
|
||||
51EF0F7B2277919E0050506E /* TimelineNumberOfLinesViewController.swift */,
|
||||
);
|
||||
path = UIKit;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6581C73620CED60100F4AD34 /* SafariExtension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1447,6 +1458,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
845B14A51FC2299E0013CF92 /* README.md */,
|
||||
84D2200922B0BC4B0019E085 /* CONTRIBUTING.md */,
|
||||
84CBDDAE1FD3674C005A61AA /* Technotes */,
|
||||
84C9FC6522629B3900D921D6 /* Mac */,
|
||||
84C9FC922262A0E600D921D6 /* iOS */,
|
||||
|
@ -1470,7 +1482,6 @@
|
|||
849C64601ED37A5D003D8FC0 /* NetNewsWire.app */,
|
||||
849C64711ED37A5D003D8FC0 /* NetNewsWireTests.xctest */,
|
||||
840D617C2029031C009BC708 /* NetNewsWire.app */,
|
||||
840D61912029031D009BC708 /* NetNewsWire-iOSTests.xctest */,
|
||||
6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */,
|
||||
);
|
||||
name = Products;
|
||||
|
@ -1864,24 +1875,6 @@
|
|||
productReference = 840D617C2029031C009BC708 /* NetNewsWire.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
840D61902029031D009BC708 /* NetNewsWire-iOSTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 840D61A62029031E009BC708 /* Build configuration list for PBXNativeTarget "NetNewsWire-iOSTests" */;
|
||||
buildPhases = (
|
||||
840D618D2029031D009BC708 /* Sources */,
|
||||
840D618E2029031D009BC708 /* Frameworks */,
|
||||
840D618F2029031D009BC708 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
840D61932029031D009BC708 /* PBXTargetDependency */,
|
||||
);
|
||||
name = "NetNewsWire-iOSTests";
|
||||
productName = "NetNewsWire-iOSTests";
|
||||
productReference = 840D61912029031D009BC708 /* NetNewsWire-iOSTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
849C645F1ED37A5D003D8FC0 /* NetNewsWire */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 849C647A1ED37A5D003D8FC0 /* Build configuration list for PBXNativeTarget "NetNewsWire" */;
|
||||
|
@ -1955,12 +1948,6 @@
|
|||
};
|
||||
};
|
||||
};
|
||||
840D61902029031D009BC708 = {
|
||||
CreatedOnToolsVersion = 9.3;
|
||||
DevelopmentTeam = 9C84TZ7Q6Z;
|
||||
ProvisioningStyle = Automatic;
|
||||
TestTargetID = 840D617B2029031C009BC708;
|
||||
};
|
||||
849C645F1ED37A5D003D8FC0 = {
|
||||
CreatedOnToolsVersion = 8.2.1;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
|
@ -2034,7 +2021,6 @@
|
|||
849C645F1ED37A5D003D8FC0 /* NetNewsWire */,
|
||||
849C64701ED37A5D003D8FC0 /* NetNewsWireTests */,
|
||||
840D617B2029031C009BC708 /* NetNewsWire-iOS */,
|
||||
840D61902029031D009BC708 /* NetNewsWire-iOSTests */,
|
||||
6581C73220CED60000F4AD34 /* Subscribe to Feed */,
|
||||
);
|
||||
};
|
||||
|
@ -2215,13 +2201,6 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
840D618F2029031D009BC708 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
849C645E1ED37A5D003D8FC0 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -2342,11 +2321,15 @@
|
|||
51F85BF52273625800C787DC /* Bundle-Extensions.swift in Sources */,
|
||||
51C452A622650A3500C03939 /* Node-Extensions.swift in Sources */,
|
||||
5183CCDF226F1FCC0010922C /* UINavigationController+Progress.swift in Sources */,
|
||||
510D707422B028E1004E8F65 /* SettingsAddAccountView.swift in Sources */,
|
||||
51C45294226509C800C03939 /* SearchFeedDelegate.swift in Sources */,
|
||||
512E09352268B25900BDCFDD /* UISplitViewController-Extensions.swift in Sources */,
|
||||
51F772F622B279570087D9D1 /* SettingsDetailAccountView.swift in Sources */,
|
||||
510D707E22B02A4B004E8F65 /* SettingsLocalAccountView.swift in Sources */,
|
||||
51C452A022650A1900C03939 /* FeedIconDownloader.swift in Sources */,
|
||||
51F85BE7227245FC00C787DC /* AboutViewController.swift in Sources */,
|
||||
5154368A2291FED9005E1CDF /* FeedbinAccountViewController.swift in Sources */,
|
||||
510D708222B041CC004E8F65 /* SettingsAccountLabelView.swift in Sources */,
|
||||
51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */,
|
||||
51C45292226509C800C03939 /* TodayFeedDelegate.swift in Sources */,
|
||||
51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */,
|
||||
|
@ -2361,6 +2344,7 @@
|
|||
51C4526A226508F600C03939 /* MasterFeedTableViewCellLayout.swift in Sources */,
|
||||
51C452AE2265104D00C03939 /* TimelineStringFormatter.swift in Sources */,
|
||||
512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */,
|
||||
51F35D0922AFD4760003CE1B /* SettingsView.swift in Sources */,
|
||||
51543685228F6753005E1CDF /* DetailAccountViewController.swift in Sources */,
|
||||
51C4529922650A0000C03939 /* ArticleStylesManager.swift in Sources */,
|
||||
51EF0F802277A8330050506E /* MasterTimelineCellLayout.swift in Sources */,
|
||||
|
@ -2393,14 +2377,7 @@
|
|||
51C452782265091600C03939 /* MasterTimelineCellData.swift in Sources */,
|
||||
51C45259226508D300C03939 /* AppDefaults.swift in Sources */,
|
||||
51C45293226509C800C03939 /* StarredFeedDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
840D618D2029031D009BC708 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
840D61962029031D009BC708 /* NetNewsWire_iOSTests.swift in Sources */,
|
||||
510D708022B02A5F004E8F65 /* SettingsFeedbinAccountView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -2624,11 +2601,6 @@
|
|||
name = Account;
|
||||
targetProxy = 51C451FA2264C83E00C03939 /* PBXContainerItemProxy */;
|
||||
};
|
||||
840D61932029031D009BC708 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 840D617B2029031C009BC708 /* NetNewsWire-iOS */;
|
||||
targetProxy = 840D61922029031D009BC708 /* PBXContainerItemProxy */;
|
||||
};
|
||||
849C64731ED37A5D003D8FC0 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 849C645F1ED37A5D003D8FC0 /* NetNewsWire */;
|
||||
|
@ -2823,7 +2795,7 @@
|
|||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = iOS/Resources/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
|
@ -2886,7 +2858,7 @@
|
|||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = iOS/Resources/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ranchero.NetNewsWire-Evergreen.iOS";
|
||||
|
@ -2898,137 +2870,6 @@
|
|||
};
|
||||
name = Release;
|
||||
};
|
||||
840D61A72029031E009BC708 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = "NetNewsWire-iOSTests/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.3;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ranchero.NetNewsWire-Evergreen.iOSTests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NetNewsWire-iOS.app/NetNewsWire-iOS";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
840D61A82029031E009BC708 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = "NetNewsWire-iOSTests/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.3;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ranchero.NetNewsWire-Evergreen.iOSTests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NetNewsWire-iOS.app/NetNewsWire-iOS";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
849C64781ED37A5D003D8FC0 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D5907CDD2002F0BE005947E5 /* NetNewsWire_project_debug.xcconfig */;
|
||||
|
@ -3113,15 +2954,6 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
840D61A62029031E009BC708 /* Build configuration list for PBXNativeTarget "NetNewsWire-iOSTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
840D61A72029031E009BC708 /* Debug */,
|
||||
840D61A82029031E009BC708 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
849C645B1ED37A5D003D8FC0 /* Build configuration list for PBXProject "NetNewsWire" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
|
|
@ -27,6 +27,15 @@
|
|||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "849C645F1ED37A5D003D8FC0"
|
||||
BuildableName = "NetNewsWire.app"
|
||||
BlueprintName = "NetNewsWire"
|
||||
ReferencedContainer = "container:NetNewsWire.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
|
@ -39,17 +48,6 @@
|
|||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "849C645F1ED37A5D003D8FC0"
|
||||
BuildableName = "NetNewsWire.app"
|
||||
BlueprintName = "NetNewsWire"
|
||||
ReferencedContainer = "container:NetNewsWire.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
|
@ -63,6 +61,7 @@
|
|||
stopOnEveryThreadSanitizerIssue = "YES"
|
||||
stopOnEveryUBSanitizerIssue = "YES"
|
||||
stopOnEveryMainThreadCheckerIssue = "YES"
|
||||
migratedStopOnEveryIssue = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
|
@ -75,8 +74,6 @@
|
|||
ReferencedContainer = "container:NetNewsWire.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
|
18
README.md
|
@ -1,8 +1,10 @@
|
|||
# NetNewsWire
|
||||
# ![Icon](Technotes/Images/icon.png) NetNewsWire
|
||||
|
||||
[![CircleCI](https://circleci.com/gh/brentsimmons/NetNewsWire.svg?style=svg)](https://circleci.com/gh/brentsimmons/NetNewsWire)
|
||||
|
||||
It’s a free and open source feed reader for macOS.
|
||||
|
||||
It’s not in beta yet. Not even alpha! While NetNewsWire 5.0 is feature-complete as of May 25, 2019, it has known bugs — and, surely, plenty of unknown bugs.
|
||||
It’s not in beta just yet. Getting close! While NetNewsWire 5.0 is feature-complete as of May 25, 2019, it has known bugs — and, surely, plenty of unknown bugs.
|
||||
|
||||
It supports [RSS](http://cyber.harvard.edu/rss/rss.html), [Atom](https://tools.ietf.org/html/rfc4287), [JSON Feed](https://jsonfeed.org/), and [RSS-in-JSON](https://github.com/scripting/Scripting-News/blob/master/rss-in-json/README.md) formats.
|
||||
|
||||
|
@ -12,19 +14,17 @@ Also see the [Technotes](Technotes/) and the [Roadmap](Technotes/Roadmap.md).
|
|||
|
||||
Note: NetNewsWire’s Help menu has a bunch of these links, so you don’t have to remember to come back to this page.
|
||||
|
||||
Here’s [How to Support NetNewsWire](Technotes/HowToSupportNetNewsWire.markdown). Spoiler: don’t send money. :)
|
||||
|
||||
#### Community
|
||||
|
||||
[Join the Slack group](https://join.slack.com/t/netnewswire/shared_invite/enQtNjM4MDA1MjQzMDkzLTNlNjBhOWVhYzdhYjA4ZWFhMzQ1MTUxYjU0NTE5ZGY0YzYwZWJhNjYwNTNmNTg2NjIwYWY4YzhlYzk5NmU3ZTc) to talk with other NetNewsWire users — and to help out, if you’d like to, by testing, coding, writing, providing feedback, or just helping us think things through. Everybody is welcome and encouraged to join.
|
||||
|
||||
#### On accepting pull requests
|
||||
Every community member is expected to abide by the code of conduct which is included in the [Contributing](CONTRIBUTING.md) page.
|
||||
|
||||
It’s pretty early still, and we have strong opinions about how we want to do things, so we’re not seeking help just yet.
|
||||
#### Pull Requests
|
||||
|
||||
That said, we will seriously consider any pull requests we do get. Just note that we may not accept them, or we may accept them and do a bunch of revision.
|
||||
|
||||
It’s probably a good idea to let us know first what you’d like to do. The best place for that is definitely the [Slack group](https://join.slack.com/t/netnewswire/shared_invite/enQtNjM4MDA1MjQzMDkzLTNlNjBhOWVhYzdhYjA4ZWFhMzQ1MTUxYjU0NTE5ZGY0YzYwZWJhNjYwNTNmNTg2NjIwYWY4YzhlYzk5NmU3ZTc).
|
||||
|
||||
We do plan to add more and more contributors over time. Totally. But we’re taking it slow as we learn how to manage an open source project.
|
||||
See the [Contributing](Contributing.md) page for our process. It’s pretty straightforward.
|
||||
|
||||
#### Building
|
||||
|
||||
|
|
|
@ -86,10 +86,11 @@ private extension ArticleRenderer {
|
|||
}
|
||||
|
||||
func titleOrTitleLink() -> String {
|
||||
let escapedTitle = title.escapeHTML()
|
||||
if let link = article?.preferredLink {
|
||||
return title.htmlByAddingLink(link)
|
||||
return escapedTitle.htmlByAddingLink(link)
|
||||
}
|
||||
return title
|
||||
return escapedTitle
|
||||
}
|
||||
|
||||
func substitutions() -> [String: String] {
|
||||
|
|
|
@ -46,12 +46,30 @@ final class DeleteCommand: UndoableCommand {
|
|||
func perform() {
|
||||
|
||||
BatchUpdate.shared.perform {
|
||||
itemSpecifiers.forEach { $0.delete() }
|
||||
itemSpecifiers.forEach { $0.delete() {} }
|
||||
treeController.rebuild()
|
||||
}
|
||||
registerUndo()
|
||||
}
|
||||
|
||||
func perform(completion: @escaping () -> Void) {
|
||||
|
||||
let group = DispatchGroup()
|
||||
group.enter()
|
||||
itemSpecifiers.forEach {
|
||||
$0.delete() {
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
treeController.rebuild()
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
self.registerUndo()
|
||||
completion()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func undo() {
|
||||
|
||||
BatchUpdate.shared.perform {
|
||||
|
@ -132,18 +150,20 @@ private struct SidebarItemSpecifier {
|
|||
self.path = ContainerPath(account: account!, folders: node.containingFolders())
|
||||
}
|
||||
|
||||
func delete() {
|
||||
func delete(completion: @escaping () -> Void) {
|
||||
|
||||
if let feed = feed {
|
||||
BatchUpdate.shared.start()
|
||||
account?.removeFeed(feed, from: path.resolveContainer()) { result in
|
||||
BatchUpdate.shared.end()
|
||||
completion()
|
||||
self.checkResult(result)
|
||||
}
|
||||
} else if let folder = folder {
|
||||
BatchUpdate.shared.start()
|
||||
account?.removeFolder(folder) { result in
|
||||
BatchUpdate.shared.end()
|
||||
completion()
|
||||
self.checkResult(result)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ class AccountRefreshTimer {
|
|||
lastTimedRefresh = Date()
|
||||
update()
|
||||
|
||||
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.present)
|
||||
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
enum RefreshInterval: Int {
|
||||
enum RefreshInterval: Int, CaseIterable {
|
||||
case manually = 1
|
||||
case every10Minutes = 2
|
||||
case every30Minutes = 3
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
# NetNewsWire Branching Strategy
|
||||
|
||||
The main repository for NetNewsWire utilizes a [Trunk Based Development](https://trunkbaseddevelopment.com) branching strategy. This branching strategy is a variant of [Three-Flow](https://www.nomachetejuggling.com/2017/04/09/a-different-branching-strategy/).
|
||||
|
||||
## Three-Flow
|
||||
|
||||
Three-Flow uses 3 branches to facilitate development, stabilize a release, and manage production hotfixes. Development happens on Master and moves to a branch called Candidate when it is ready to be stabilized. New feature development continues on Master and bug fixes to the release candidate happen on Candidate. When the product is released, it is pushed to the Release branch. Hotfixes can happen on the Release branch. Candidate is now free to be reused to stabilize the next release. All bugs found and fixed are back merged to Candidate and then Master respectively.
|
||||
|
||||
![Branching](Images/Branching.png)
|
||||
|
||||
All arrows going up are promotions (pushes) to the next environment. All arrows going down are back ports of bugfixes.
|
||||
|
||||
That is Three-Flow applied to NetNewsWire. It would be that simple, but we have two products we are going to deliver from the same repository. The iOS and the macOS variants of NetNewsWire. To stabilize and manage both variants, each will need to be given their own Candidate and Release branches.
|
||||
|
||||
![Branching Full](Images/Branching-Full.png)
|
||||
|
||||
Today (6/12/2019) we have 2 branches, master and macOS Candidate, in the main repository which will eventually grow to be 5 branches.
|
||||
|
||||
There will also be a number of repository forks that NetNewWire developers will create to do bug fixes and implement new features (not shown here). Typically contributers will fork the Master branch to thier own repository. They would then create a feature/bugfix branch on their repository. Once work on thier forked branch is complete, they will submit a pull request to be merged back into the main repository master.
|
||||
|
||||
## Tagging
|
||||
|
||||
Each release should be tagged using [Semantic Versioning](https://semver.org/). Candidates will continue to be tagged using the current convention which denotes the difference between developer, alpha and beta releases. Additionally, we will need to use a convention to avoid tag name collisions between iOS and macOS products. macOS will use even minor release numbers and iOS will use odd minor release numbers. (See the above diagram for examples.)
|
||||
|
||||
## Submodules
|
||||
|
||||
NetNewsWire uses Git submodules to manage project dependencies. All the submodules are under the same project umbrella as NetNewWire and there are no third party dependencies to manage. These submodules are mostly stable at this point. For simplicity sake, all development on the submodules will continue on their repository Master branch. These submodules won’t be managed as separate projects with separate releases/tags at this time.
|
||||
|
||||
## Summary
|
||||
|
||||
There are 3 types of branches: Master, Candidate, and Release. All feature development happens on Master. Stabilization happens on Candidate. Hotfixes happen on Release. Each product gets its own Candidate and Release branches. All candidates and releases get tagged.
|
|
@ -0,0 +1,30 @@
|
|||
# NetNewsWire Continuous Integration
|
||||
|
||||
CI for NetNewsWire is enabled through CircleCI, hosted at
|
||||
<https://circleci.com/gh/brentsimmons/NetNewsWire>. The CI configuration (hosted in
|
||||
[`.circleci/config.yml`](https://github.com/brentsimmons/NetNewsWire/blob/master/.circleci/config.yml)
|
||||
uses `xcodebuild` to build the project after syncing the repository and
|
||||
the various submodules.
|
||||
|
||||
As of June 2019, CircleCI offered Xcode 10.2.1, so IOS 13 and Catalina support are not available
|
||||
via CI as yet.
|
||||
|
||||
The build itself focuses on the scheme NetNewsWire and leverages the
|
||||
`NetNewsWire.xcworkspace` configuration.
|
||||
|
||||
Each submodule also has it's own CI configuration, which are set up and built from
|
||||
their own repositories. The submodule CI systems are entirely independent so that
|
||||
those libraries can grow and change, getting CI verification, indepdent of NetNewsWire.
|
||||
|
||||
The submodule CI are typically set to run a build and any available tests. Refer to the
|
||||
project repository for the current and complete list of submodules, but for quick reference:
|
||||
|
||||
- [RSCore](https://github.com/brentsimmons/RSCore) [![CircleCI](https://circleci.com/gh/brentsimmons/RSCore.svg?style=svg)](https://circleci.com/gh/brentsimmons/RSCore)
|
||||
|
||||
- [RSWeb](https://github.com/brentsimmons/RSWeb) [![CircleCI](https://circleci.com/gh/brentsimmons/RSWeb.svg?style=svg)](https://circleci.com/gh/brentsimmons/RSWeb)
|
||||
|
||||
- [RSParser](https://github.com/brentsimmons/RSParser) [![CircleCI](https://circleci.com/gh/brentsimmons/RSParser.svg?style=svg)](https://circleci.com/gh/brentsimmons/RSParser)
|
||||
|
||||
- [RSTree](https://github.com/brentsimmons/RSTree) [![CircleCI](https://circleci.com/gh/brentsimmons/RSTree.svg?style=svg)](https://circleci.com/gh/brentsimmons/RSTree)
|
||||
|
||||
- [RSDatabase](https://github.com/brentsimmons/RSDatabase) [![CircleCI](https://circleci.com/gh/brentsimmons/RSDatabase.svg?style=svg)](https://circleci.com/gh/brentsimmons/RSDatabase)
|
|
@ -0,0 +1,43 @@
|
|||
# How to Support NetNewsWire
|
||||
|
||||
First thing: don’t send money. This app is [written for love](https://inessential.com/2015/06/30/love), not money. :)
|
||||
|
||||
NetNewsWire is all about three things:
|
||||
|
||||
* The open web
|
||||
* High-quality open source Mac and iOS apps
|
||||
* The community that loves both of the above
|
||||
|
||||
Supporting all these things takes *work*.
|
||||
|
||||
### Here are some things you can do
|
||||
|
||||
In no particular order…
|
||||
|
||||
Write a blog instead of posting to Twitter or Facebook. (You can always re-post to those places if you want to extend your reach.) [Micro.blog](https://micro.blog/) is one good place to get going, but it’s not the only one.
|
||||
|
||||
Use an RSS reader even if it’s not NetNewsWire. (There are a bunch of good ones!)
|
||||
|
||||
Teach other people to use RSS readers. Blog about RSS readers. And about other open web technologies and apps.
|
||||
|
||||
Suggest apps for [macopenweb.com](https://macopenweb.com/).
|
||||
|
||||
Write Mac and iOS apps that promote use of the open web.
|
||||
|
||||
Donate to charities that promote literacy.
|
||||
|
||||
Tell other people about cool blogs and feeds you’ve found.
|
||||
|
||||
Support indie podcast apps.
|
||||
|
||||
Vote for candidates whose policies are not cruel.
|
||||
|
||||
Support your local library.
|
||||
|
||||
Be bold and do your best work.
|
||||
|
||||
Support indie developers — pay for apps that cost money. Even though NetNewsWire is free, apps are most definitely *not* free to make, and it costs money to keep improving them. It’s worth it.
|
||||
|
||||
Finally: report bugs and make feature requests on our Issues tracker. You can also join the Slack group — it’s not just for coders. We also need testers, writers, and, especially, people who are willing to talk things over. Most of software development is just making decisions, and we appreciate all the help we can get!
|
||||
|
||||
Or: skip helping us, and, instead, help people who need help more than we do. Those people should not be hard to find.
|
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 2.1 KiB |
|
@ -16,4 +16,8 @@
|
|||
|
||||
## Contributing
|
||||
|
||||
[Contributing](../CONTRIBUTING.md)
|
||||
|
||||
[Coding Guidelines](CodingGuidelines.md)
|
||||
|
||||
[Branching Strategy](BranchingStrategy.md)
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# Why Reruns Happen
|
||||
|
||||
Sometimes you might see a new article in a feed that you’d swear you’ve already read. And maybe you can even see, in NetNewsWire, what looks like another copy of that same exact article, with no changes.
|
||||
|
||||
Here’s the thing to know: if the article really was the exact same in every respect, NetNewsWire would see that. It’s super-easy for a computer to tell that some data is the exact same as some other data.
|
||||
|
||||
When it’s not really the exact same, that’s where the problem comes in.
|
||||
|
||||
Here are some reasons this situation can happen:
|
||||
|
||||
## A blog changes its blog engine
|
||||
|
||||
If someone switches from (for instance) Ghost to WordPress, then the code that creates that feeds will be different. And that code will make a different choice for the unique ID for each article in the feed.
|
||||
|
||||
Those unique IDs are critical: they’re how NetNewsWire identifies an article. If an article appears with a new unique ID, then NetNewsWire treats it like a new article.
|
||||
|
||||
In this situation, you’ll often see that you get a bunch of reruns for a given feed all at once. You’ll get 10 or 20 or whatever.
|
||||
|
||||
This is by far the most common cause of reruns.
|
||||
|
||||
## A feed that lacks unique IDs does something weird
|
||||
|
||||
This is quite a bit less common. There are some feeds that don’t have unique IDs, which means NetNewsWire has to use some combination of other article metadata to identify articles.
|
||||
|
||||
That metadata could change just enough to throw NetNewsWire off. This is rare, but it can happen.
|
||||
|
||||
## A feed just has terrible bugs
|
||||
|
||||
We’ve seen feeds that create a different unique ID for each article every time you fetch the feed, which results in reruns every single time. We’ve seen feeds that use the same unique ID for every article in the feed, even — which goes against the very idea of unique IDs!
|
||||
|
||||
Some feeds just have bugs, and weird, unpredictable things happen.
|
||||
|
||||
NetNewsWire is designed to be resistant to that, and it does a good job — but we haven’t anticipated every odd case.
|
||||
|
||||
However, this is the most rare cause of reruns. The most common cause is, by far, the first one: the feed is now being generated by different software.
|
||||
|
||||
## Reporting Bugs
|
||||
|
||||
If you have a feed that keeps showing reruns (as opposed to once, when a blog changes its blogging system), please do report a bug, either on our [Issues Tracker](https://github.com/brentsimmons/NetNewsWire/issues) or on the [Slack group](https://join.slack.com/t/netnewswire/shared_invite/enQtNjM4MDA1MjQzMDkzLTNlNjBhOWVhYzdhYjA4ZWFhMzQ1MTUxYjU0NTE5ZGY0YzYwZWJhNjYwNTNmNTg2NjIwYWY4YzhlYzk5NmU3ZTc).
|
|
@ -11,6 +11,7 @@ import Account
|
|||
import Articles
|
||||
import RSCore
|
||||
import RSTree
|
||||
import SwiftUI
|
||||
|
||||
class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunner {
|
||||
|
||||
|
@ -393,13 +394,8 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn
|
|||
|
||||
@IBAction func settings(_ sender: UIBarButtonItem) {
|
||||
|
||||
let settingsNavViewController = UIStoryboard.settings.instantiateInitialViewController() as! UINavigationController
|
||||
settingsNavViewController.modalPresentationStyle = .formSheet
|
||||
|
||||
let settingsViewController = settingsNavViewController.topViewController as! SettingsViewController
|
||||
settingsViewController.presentingParentController = self
|
||||
|
||||
self.present(settingsNavViewController, animated: true)
|
||||
let settings = UIHostingController(rootView: SettingsView(viewModel: SettingsView.ViewModel()))
|
||||
self.present(settings, animated: true)
|
||||
|
||||
}
|
||||
|
||||
|
@ -526,14 +522,23 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn
|
|||
else {
|
||||
return
|
||||
}
|
||||
|
||||
navState.beginUpdates()
|
||||
|
||||
runCommand(deleteCommand)
|
||||
navState.rebuildShadowTable()
|
||||
tableView.deleteRows(at: [indexPath], with: .automatic)
|
||||
var deleteIndexPaths = [indexPath]
|
||||
if navState.isExpanded(deleteNode) {
|
||||
for i in 0..<deleteNode.numberOfChildNodes {
|
||||
deleteIndexPaths.append(IndexPath(row: indexPath.row + 1 + i, section: indexPath.section))
|
||||
}
|
||||
}
|
||||
|
||||
navState.endUpdates()
|
||||
pushUndoableCommand(deleteCommand)
|
||||
|
||||
navState.beginUpdates()
|
||||
deleteCommand.perform {
|
||||
self.navState.treeController.rebuild()
|
||||
self.navState.rebuildShadowTable()
|
||||
self.tableView.deleteRows(at: deleteIndexPaths, with: .automatic)
|
||||
self.navState.endUpdates()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ extension MasterTimelineCellLayout {
|
|||
var r = CGRect.zero
|
||||
r.size = CGSize(width: MasterTimelineDefaultCellLayout.unreadCircleDimension, height: MasterTimelineDefaultCellLayout.unreadCircleDimension)
|
||||
r.origin.x = point.x
|
||||
r.origin.y = point.y + 8
|
||||
r.origin.y = point.y + 4
|
||||
return r
|
||||
}
|
||||
|
||||
|
@ -38,14 +38,15 @@ extension MasterTimelineCellLayout {
|
|||
r.size.width = MasterTimelineDefaultCellLayout.starDimension
|
||||
r.size.height = MasterTimelineDefaultCellLayout.starDimension
|
||||
r.origin.x = floor(point.x - ((MasterTimelineDefaultCellLayout.starDimension - MasterTimelineDefaultCellLayout.unreadCircleDimension) / 2.0))
|
||||
r.origin.y = point.y + 5
|
||||
r.origin.y = point.y + 2
|
||||
return r
|
||||
}
|
||||
|
||||
static func rectForAvatar(_ point: CGPoint) -> CGRect {
|
||||
var r = CGRect.zero
|
||||
r.size = MasterTimelineDefaultCellLayout.avatarSize
|
||||
r.origin = point
|
||||
r.origin.x = point.x
|
||||
r.origin.y = point.y + 4
|
||||
return r
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ struct MasterTimelineDefaultCellLayout: MasterTimelineCellLayout {
|
|||
static let unreadCircleDimension = CGFloat(integerLiteral: 12)
|
||||
static let unreadCircleMarginRight = CGFloat(integerLiteral: 8)
|
||||
|
||||
static let starDimension = CGFloat(integerLiteral: 13)
|
||||
static let starDimension = CGFloat(integerLiteral: 16)
|
||||
|
||||
static let avatarSize = CGSize(width: 48.0, height: 48.0)
|
||||
static let avatarMarginRight = CGFloat(integerLiteral: 8)
|
||||
|
|
|
@ -17,8 +17,6 @@ class MasterTimelineTableViewCell: UITableViewCell {
|
|||
private let dateView = MasterTimelineTableViewCell.singleLineUILabel()
|
||||
private let feedNameView = MasterTimelineTableViewCell.singleLineUILabel()
|
||||
|
||||
private var layout: MasterTimelineCellLayout?
|
||||
|
||||
private lazy var avatarImageView: UIImageView = {
|
||||
let imageView = NonIntrinsicImageView(image: AppAssets.feedImage)
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
|
@ -47,29 +45,25 @@ class MasterTimelineTableViewCell: UITableViewCell {
|
|||
}
|
||||
|
||||
override func sizeThatFits(_ size: CGSize) -> CGSize {
|
||||
if layout == nil {
|
||||
layout = updatedLayout()
|
||||
}
|
||||
return CGSize(width: bounds.width, height: layout!.height)
|
||||
let layout = updatedLayout(width: size.width)
|
||||
return CGSize(width: size.width, height: layout.height)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
|
||||
super.layoutSubviews()
|
||||
|
||||
if layout == nil {
|
||||
layout = updatedLayout()
|
||||
}
|
||||
let layout = updatedLayout(width: bounds.width)
|
||||
|
||||
unreadIndicatorView.setFrameIfNotEqual(layout!.unreadIndicatorRect)
|
||||
starView.setFrameIfNotEqual(layout!.starRect)
|
||||
avatarImageView.setFrameIfNotEqual(layout!.avatarImageRect)
|
||||
setFrame(for: titleView, rect: layout!.titleRect)
|
||||
setFrame(for: summaryView, rect: layout!.summaryRect)
|
||||
feedNameView.setFrameIfNotEqual(layout!.feedNameRect)
|
||||
dateView.setFrameIfNotEqual(layout!.dateRect)
|
||||
unreadIndicatorView.setFrameIfNotEqual(layout.unreadIndicatorRect)
|
||||
starView.setFrameIfNotEqual(layout.starRect)
|
||||
avatarImageView.setFrameIfNotEqual(layout.avatarImageRect)
|
||||
setFrame(for: titleView, rect: layout.titleRect)
|
||||
setFrame(for: summaryView, rect: layout.summaryRect)
|
||||
feedNameView.setFrameIfNotEqual(layout.feedNameRect)
|
||||
dateView.setFrameIfNotEqual(layout.dateRect)
|
||||
|
||||
separatorInset = layout!.separatorInsets
|
||||
separatorInset = layout.separatorInsets
|
||||
|
||||
}
|
||||
|
||||
|
@ -137,11 +131,11 @@ private extension MasterTimelineTableViewCell {
|
|||
accessoryView = UIImageView(image: AppAssets.chevronRightImage)
|
||||
}
|
||||
|
||||
func updatedLayout() -> MasterTimelineCellLayout {
|
||||
func updatedLayout(width: CGFloat) -> MasterTimelineCellLayout {
|
||||
if UIApplication.shared.preferredContentSizeCategory.isAccessibilityCategory {
|
||||
return MasterTimelineAccessibilityCellLayout(width: bounds.width, insets: safeAreaInsets, cellData: cellData)
|
||||
return MasterTimelineAccessibilityCellLayout(width: width, insets: safeAreaInsets, cellData: cellData)
|
||||
} else {
|
||||
return MasterTimelineDefaultCellLayout(width: bounds.width, insets: safeAreaInsets, cellData: cellData)
|
||||
return MasterTimelineDefaultCellLayout(width: width, insets: safeAreaInsets, cellData: cellData)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -234,7 +228,6 @@ private extension MasterTimelineTableViewCell {
|
|||
}
|
||||
|
||||
func updateSubviews() {
|
||||
layout = nil
|
||||
updateTitleView()
|
||||
updateSummaryView()
|
||||
updateDateView()
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"author" : "xcode"
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
"template-rendering-intent" : "template",
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@
|
|||
"author" : "xcode"
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
"template-rendering-intent" : "template",
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// SettingsAccountLabelView.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Maurice Parker on 6/11/19.
|
||||
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsAccountLabelView : View {
|
||||
let accountImage: String
|
||||
let accountLabel: String
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Spacer()
|
||||
HStack {
|
||||
Image(accountImage)
|
||||
.resizable()
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.frame(height: 32)
|
||||
Text(verbatim: accountLabel).font(.title)
|
||||
|
||||
}
|
||||
.layoutPriority(1)
|
||||
Spacer()
|
||||
}
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
struct SettingsAccountLabelView_Previews : PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsAccountLabelView(accountImage: "accountLocal", accountLabel: "On My Device")
|
||||
.previewLayout(.fixed(width: 300, height: 44))
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// SettingsAddAccountView.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Maurice Parker on 6/11/19.
|
||||
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Account
|
||||
|
||||
struct SettingsAddAccountView : View {
|
||||
var body: some View {
|
||||
List {
|
||||
PresentationButton(SettingsAccountLabelView(accountImage: "accountLocal", accountLabel: Account.defaultLocalAccountName),
|
||||
destination: SettingsLocalAccountView(name: "")).padding(.all, 4)
|
||||
PresentationButton(SettingsAccountLabelView(accountImage: "accountFeedbin", accountLabel: "Feedbin"),
|
||||
destination: SettingsFeedbinAccountView(viewModel: SettingsFeedbinAccountView.ViewModel())).padding(.all, 4)
|
||||
}
|
||||
.listStyle(.grouped)
|
||||
.navigationBarTitle(Text("Add Account"), displayMode: .inline)
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
struct AddAccountView_Previews : PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsAddAccountView()
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,114 @@
|
|||
//
|
||||
// SettingsDetailAccountView.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Maurice Parker on 6/13/19.
|
||||
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
import Account
|
||||
|
||||
struct SettingsDetailAccountView : View {
|
||||
@ObjectBinding var viewModel: ViewModel
|
||||
@State private var verifyDelete = false
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section {
|
||||
HStack {
|
||||
Text("Name")
|
||||
Divider()
|
||||
TextField($viewModel.name, placeholder: Text("(Optional)"))
|
||||
}
|
||||
Toggle(isOn: $viewModel.isActive) {
|
||||
Text("Active")
|
||||
}
|
||||
}
|
||||
Section {
|
||||
HStack {
|
||||
Spacer()
|
||||
Button(action: {
|
||||
|
||||
}) {
|
||||
Text("Credentials")
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
if viewModel.isDeletable {
|
||||
Section {
|
||||
HStack {
|
||||
Spacer()
|
||||
Button(action: {
|
||||
self.verifyDelete = true
|
||||
}) {
|
||||
Text("Delete Account")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
.presentation($verifyDelete) {
|
||||
Alert(title: Text("Are you sure you want to delete \"\(viewModel.nameForDisplay)\"?"),
|
||||
primaryButton: Alert.Button.default(Text("Delete"), onTrigger: { self.viewModel.delete() }),
|
||||
secondaryButton: Alert.Button.cancel())
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.grouped)
|
||||
.navigationBarTitle(Text(verbatim: viewModel.nameForDisplay), displayMode: .inline)
|
||||
|
||||
}
|
||||
|
||||
class ViewModel: BindableObject {
|
||||
let didChange = PassthroughSubject<ViewModel, Never>()
|
||||
let account: Account
|
||||
|
||||
init(_ account: Account) {
|
||||
self.account = account
|
||||
}
|
||||
|
||||
var nameForDisplay: String {
|
||||
account.nameForDisplay
|
||||
}
|
||||
|
||||
var name: String {
|
||||
get {
|
||||
account.name ?? ""
|
||||
}
|
||||
set {
|
||||
account.name = newValue.isEmpty ? nil : newValue
|
||||
didChange.send(self)
|
||||
}
|
||||
}
|
||||
|
||||
var isActive: Bool {
|
||||
get {
|
||||
account.isActive
|
||||
}
|
||||
set {
|
||||
account.isActive = newValue
|
||||
didChange.send(self)
|
||||
}
|
||||
}
|
||||
|
||||
var isDeletable: Bool {
|
||||
return AccountManager.shared.defaultAccount != account
|
||||
}
|
||||
|
||||
func delete() {
|
||||
AccountManager.shared.deleteAccount(account)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
struct SettingsDetailAccountView_Previews : PreviewProvider {
|
||||
static var previews: some View {
|
||||
let viewModel = SettingsDetailAccountView.ViewModel(AccountManager.shared.defaultAccount)
|
||||
return SettingsDetailAccountView(viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,150 @@
|
|||
//
|
||||
// SettingsFeedbinAccountView.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Maurice Parker on 6/11/19.
|
||||
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
import Account
|
||||
import RSWeb
|
||||
|
||||
struct SettingsFeedbinAccountView : View {
|
||||
@Environment(\.isPresented) private var isPresented
|
||||
@ObjectBinding var viewModel: ViewModel
|
||||
@State var busy: Bool = false
|
||||
@State var error: Text = Text("")
|
||||
var account: Account? = nil
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
Section(header:
|
||||
SettingsAccountLabelView(accountImage: "accountFeedbin", accountLabel: "Feedbin").padding()
|
||||
) {
|
||||
HStack {
|
||||
Spacer()
|
||||
TextField($viewModel.email, placeholder: Text("Email"))
|
||||
.textContentType(.username)
|
||||
Spacer()
|
||||
}
|
||||
HStack {
|
||||
Spacer()
|
||||
SecureField($viewModel.password, placeholder: Text("Password"))
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
Section(footer:
|
||||
HStack {
|
||||
Spacer()
|
||||
error.color(.red)
|
||||
Spacer()
|
||||
}
|
||||
) {
|
||||
HStack {
|
||||
Spacer()
|
||||
Button(action: { self.addAccount() }) {
|
||||
Text("Add Account")
|
||||
}
|
||||
.disabled(!viewModel.isValid)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabled(busy)
|
||||
.listStyle(.grouped)
|
||||
.navigationBarTitle(Text(""), displayMode: .inline)
|
||||
.navigationBarItems(leading:
|
||||
Button(action: { self.dismiss() }) { Text("Cancel") }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func addAccount() {
|
||||
|
||||
busy = true
|
||||
|
||||
let emailAddress = viewModel.email.trimmingCharacters(in: .whitespaces)
|
||||
let credentials = Credentials.basic(username: emailAddress, password: viewModel.password)
|
||||
|
||||
Account.validateCredentials(type: .feedbin, credentials: credentials) { result in
|
||||
|
||||
self.busy = false
|
||||
|
||||
switch result {
|
||||
case .success(let authenticated):
|
||||
|
||||
if authenticated {
|
||||
|
||||
var newAccount = false
|
||||
let workAccount: Account
|
||||
if self.account == nil {
|
||||
workAccount = AccountManager.shared.createAccount(type: .feedbin)
|
||||
newAccount = true
|
||||
} else {
|
||||
workAccount = self.account!
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
do {
|
||||
try workAccount.removeBasicCredentials()
|
||||
} catch {}
|
||||
try workAccount.storeCredentials(credentials)
|
||||
|
||||
if newAccount {
|
||||
workAccount.refreshAll() { result in }
|
||||
}
|
||||
|
||||
self.dismiss()
|
||||
|
||||
} catch {
|
||||
self.error = Text("Keychain error while storing credentials.")
|
||||
}
|
||||
|
||||
} else {
|
||||
self.error = Text("Invalid email/password combination.")
|
||||
}
|
||||
|
||||
case .failure:
|
||||
self.error = Text("Network error. Try again later.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func dismiss() {
|
||||
isPresented?.value = false
|
||||
}
|
||||
|
||||
class ViewModel: BindableObject {
|
||||
let didChange = PassthroughSubject<ViewModel, Never>()
|
||||
|
||||
var email: String = "" {
|
||||
didSet {
|
||||
didChange.send(self)
|
||||
}
|
||||
}
|
||||
var password: String = "" {
|
||||
didSet {
|
||||
didChange.send(self)
|
||||
}
|
||||
}
|
||||
|
||||
var isValid: Bool {
|
||||
return !email.isEmpty && !password.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
struct SettingsFeedbinAccountView_Previews : PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsFeedbinAccountView(viewModel: SettingsFeedbinAccountView.ViewModel())
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// SettingsLocalAccountView.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Maurice Parker on 6/11/19.
|
||||
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Account
|
||||
|
||||
struct SettingsLocalAccountView : View {
|
||||
@Environment(\.isPresented) private var isPresented
|
||||
@State var name: String
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
Section(header:
|
||||
SettingsAccountLabelView(accountImage: "accountLocal", accountLabel: Account.defaultLocalAccountName).padding()
|
||||
) {
|
||||
HStack {
|
||||
Spacer()
|
||||
TextField($name, placeholder: Text("Name (Optional)"))
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
Section {
|
||||
HStack {
|
||||
Spacer()
|
||||
Button(action: { self.addAccount() }) {
|
||||
Text("Add Account")
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.grouped)
|
||||
.navigationBarTitle(Text(""), displayMode: .inline)
|
||||
.navigationBarItems(leading: Button(action: { self.dismiss() }) { Text("Cancel") } )
|
||||
}
|
||||
}
|
||||
|
||||
private func addAccount() {
|
||||
let account = AccountManager.shared.createAccount(type: .onMyMac)
|
||||
account.name = name
|
||||
dismiss()
|
||||
}
|
||||
|
||||
private func dismiss() {
|
||||
isPresented?.value = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
struct SettingsLocalAccountView_Previews : PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsLocalAccountView(name: "")
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,159 @@
|
|||
//
|
||||
// SettingsView.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Maurice Parker on 6/11/19.
|
||||
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
import Account
|
||||
|
||||
struct SettingsView : View {
|
||||
@ObjectBinding var viewModel: ViewModel
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
|
||||
Section(header: Text("ACCOUNTS")) {
|
||||
ForEach(viewModel.accounts.identified(by: \.self)) { account in
|
||||
NavigationButton(destination: SettingsDetailAccountView(viewModel: SettingsDetailAccountView.ViewModel(account)), isDetail: false) {
|
||||
Text(verbatim: account.nameForDisplay)
|
||||
}
|
||||
}
|
||||
NavigationButton(destination: SettingsAddAccountView(), isDetail: false) {
|
||||
Text("Add Account")
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("ABOUT")) {
|
||||
|
||||
Text("About NetNewsWire")
|
||||
|
||||
Button(action: {
|
||||
UIApplication.shared.open(URL(string: "https://ranchero.com/netnewswire/")!, options: [:])
|
||||
}) {
|
||||
Text("Website")
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
UIApplication.shared.open(URL(string: "https://github.com/brentsimmons/NetNewsWire")!, options: [:])
|
||||
}) {
|
||||
Text("Github Repository")
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
UIApplication.shared.open(URL(string: "https://github.com/brentsimmons/NetNewsWire/issues")!, options: [:])
|
||||
}) {
|
||||
Text("Bug Tracker")
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
UIApplication.shared.open(URL(string: "https://github.com/brentsimmons/NetNewsWire/tree/master/Technotes")!, options: [:])
|
||||
}) {
|
||||
Text("Technotes")
|
||||
}
|
||||
|
||||
Text("Add NetNewsWire News Feed")
|
||||
|
||||
}
|
||||
.foregroundColor(.primary)
|
||||
|
||||
Section(header: Text("TIMELINE")) {
|
||||
Toggle(isOn: $viewModel.sortOldestToNewest) {
|
||||
Text("Sort Oldest to Newest")
|
||||
}
|
||||
Stepper(value: $viewModel.timelineNumberOfLines, in: 2...6) {
|
||||
Text("Number of Text Lines: \(viewModel.timelineNumberOfLines)")
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("DATABASE")) {
|
||||
Picker(selection: $viewModel.refreshInterval, label: Text("Refresh Interval")) {
|
||||
ForEach(RefreshInterval.allCases.identified(by: \.self)) { interval in
|
||||
Text(interval.description()).tag(interval)
|
||||
}
|
||||
}
|
||||
Text("Import Subscriptions...")
|
||||
Text("Export Subscriptions...")
|
||||
}
|
||||
|
||||
}
|
||||
.listStyle(.grouped)
|
||||
.navigationBarTitle(Text("Settings"), displayMode: .inline)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class ViewModel: BindableObject {
|
||||
|
||||
let didChange = PassthroughSubject<ViewModel, Never>()
|
||||
|
||||
init() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .AccountsDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil)
|
||||
}
|
||||
|
||||
var accounts: [Account] {
|
||||
get {
|
||||
return AccountManager.shared.sortedAccounts
|
||||
}
|
||||
set {
|
||||
}
|
||||
}
|
||||
|
||||
var sortOldestToNewest: Bool {
|
||||
get {
|
||||
return AppDefaults.timelineSortDirection == .orderedDescending
|
||||
}
|
||||
set {
|
||||
if newValue == true {
|
||||
AppDefaults.timelineSortDirection = .orderedDescending
|
||||
} else {
|
||||
AppDefaults.timelineSortDirection = .orderedAscending
|
||||
}
|
||||
didChange.send(self)
|
||||
}
|
||||
}
|
||||
|
||||
var timelineNumberOfLines: Int {
|
||||
get {
|
||||
return AppDefaults.timelineNumberOfLines
|
||||
}
|
||||
set {
|
||||
AppDefaults.timelineNumberOfLines = newValue
|
||||
didChange.send(self)
|
||||
}
|
||||
}
|
||||
|
||||
var refreshInterval: RefreshInterval {
|
||||
get {
|
||||
return AppDefaults.refreshInterval
|
||||
}
|
||||
set {
|
||||
AppDefaults.refreshInterval = newValue
|
||||
didChange.send(self)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func accountsDidChange(_ notification: Notification) {
|
||||
didChange.send(self)
|
||||
}
|
||||
|
||||
@objc func displayNameDidChange(_ notification: Notification) {
|
||||
didChange.send(self)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
struct SettingsView_Previews : PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsView(viewModel: SettingsView.ViewModel())
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -10,7 +10,7 @@ import Account
|
|||
import UIKit
|
||||
|
||||
protocol AddAccountDismissDelegate: UIViewController {
|
||||
func dismiss(_ viewController: UIViewController)
|
||||
func dismiss()
|
||||
}
|
||||
|
||||
class AddAccountViewController: UITableViewController, AddAccountDismissDelegate {
|
||||
|
@ -27,11 +27,13 @@ class AddAccountViewController: UITableViewController, AddAccountDismissDelegate
|
|||
switch indexPath.row {
|
||||
case 0:
|
||||
let navController = UIStoryboard.settings.instantiateViewController(withIdentifier: "AddLocalAccountNavigationViewController") as! UINavigationController
|
||||
navController.modalPresentationStyle = .currentContext
|
||||
let addViewController = navController.topViewController as! AddLocalAccountViewController
|
||||
addViewController.delegate = self
|
||||
present(navController, animated: true)
|
||||
case 1:
|
||||
let navController = UIStoryboard.settings.instantiateViewController(withIdentifier: "FeedbinAccountNavigationViewController") as! UINavigationController
|
||||
navController.modalPresentationStyle = .currentContext
|
||||
let addViewController = navController.topViewController as! FeedbinAccountViewController
|
||||
addViewController.delegate = self
|
||||
present(navController, animated: true)
|
||||
|
@ -40,8 +42,8 @@ class AddAccountViewController: UITableViewController, AddAccountDismissDelegate
|
|||
}
|
||||
}
|
||||
|
||||
func dismiss(_ viewController: UIViewController) {
|
||||
viewController.dismiss(animated: true, completion: nil)
|
||||
func dismiss() {
|
||||
navigationController?.popViewController(animated: false)
|
||||
}
|
||||
|
||||
}
|
|
@ -25,13 +25,15 @@ class AddLocalAccountViewController: UIViewController {
|
|||
}
|
||||
|
||||
@IBAction func cancel(_ sender: Any) {
|
||||
delegate?.dismiss(self)
|
||||
dismiss(animated: true, completion: nil)
|
||||
delegate?.dismiss()
|
||||
}
|
||||
|
||||
@IBAction func addAccountTapped(_ sender: Any) {
|
||||
let account = AccountManager.shared.createAccount(type: .onMyMac)
|
||||
account.name = nameTextField.text
|
||||
delegate?.dismiss(self)
|
||||
dismiss(animated: true, completion: nil)
|
||||
delegate?.dismiss()
|
||||
}
|
||||
|
||||
}
|
|
@ -37,15 +37,26 @@ class DetailAccountViewController: UITableViewController {
|
|||
extension DetailAccountViewController {
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
|
||||
guard let account = account else { return 0 }
|
||||
|
||||
if account == AccountManager.shared.defaultAccount {
|
||||
return 1
|
||||
} else if account.type == .onMyMac {
|
||||
return 2
|
||||
} else {
|
||||
return super.numberOfSections(in: tableView)
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = super.tableView(tableView, cellForRowAt: indexPath)
|
||||
|
||||
let cell: UITableViewCell
|
||||
if indexPath.section == 1, let account = account, account.type == .onMyMac {
|
||||
cell = super.tableView(tableView, cellForRowAt: IndexPath(row: 0, section: 2))
|
||||
} else {
|
||||
cell = super.tableView(tableView, cellForRowAt: indexPath)
|
||||
}
|
||||
|
||||
let bgView = UIView()
|
||||
bgView.backgroundColor = AppAssets.selectionBackgroundColor
|
||||
|
@ -54,7 +65,7 @@ extension DetailAccountViewController {
|
|||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
|
||||
if indexPath.section == 1 {
|
||||
if indexPath.section > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -62,8 +73,19 @@ extension DetailAccountViewController {
|
|||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
if indexPath.section == 1 {
|
||||
deleteAccount()
|
||||
if let account = account, account.type == .onMyMac {
|
||||
if indexPath.section == 1 {
|
||||
deleteAccount()
|
||||
}
|
||||
} else {
|
||||
switch indexPath.section {
|
||||
case 1:
|
||||
credentials()
|
||||
case 2:
|
||||
deleteAccount()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
|
||||
|
@ -73,6 +95,19 @@ extension DetailAccountViewController {
|
|||
|
||||
private extension DetailAccountViewController {
|
||||
|
||||
func credentials() {
|
||||
guard let account = account else { return }
|
||||
switch account.type {
|
||||
case .feedbin:
|
||||
let navController = UIStoryboard.settings.instantiateViewController(withIdentifier: "FeedbinAccountNavigationViewController") as! UINavigationController
|
||||
let addViewController = navController.topViewController as! FeedbinAccountViewController
|
||||
addViewController.account = account
|
||||
present(navController, animated: true)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func deleteAccount() {
|
||||
let title = NSLocalizedString("Delete Account", comment: "Delete Account")
|
||||
let message = NSLocalizedString("Are you sure you want to delete this account? This can not be undone.", comment: "Delete Account")
|
|
@ -16,7 +16,7 @@ class FeedbinAccountViewController: UIViewController {
|
|||
@IBOutlet weak var cancelBarButtonItem: UIBarButtonItem!
|
||||
@IBOutlet weak var emailTextField: UITextField!
|
||||
@IBOutlet weak var passwordTextField: UITextField!
|
||||
@IBOutlet weak var addAccountButton: UIButton!
|
||||
@IBOutlet weak var actionButton: UIButton!
|
||||
|
||||
@IBOutlet weak var errorMessageLabel: UILabel!
|
||||
|
||||
|
@ -31,18 +31,22 @@ class FeedbinAccountViewController: UIViewController {
|
|||
passwordTextField.delegate = self
|
||||
|
||||
if let account = account, let credentials = try? account.retrieveBasicCredentials() {
|
||||
actionButton.setTitle(NSLocalizedString("Update Credentials", comment: "Update Credentials"), for: .normal)
|
||||
if case .basic(let username, let password) = credentials {
|
||||
emailTextField.text = username
|
||||
passwordTextField.text = password
|
||||
}
|
||||
} else {
|
||||
actionButton.setTitle(NSLocalizedString("Add Account", comment: "Update Credentials"), for: .normal)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func cancel(_ sender: Any) {
|
||||
delegate?.dismiss(self)
|
||||
dismiss(animated: true, completion: nil)
|
||||
delegate?.dismiss()
|
||||
}
|
||||
|
||||
@IBAction func addAccountTapped(_ sender: Any) {
|
||||
@IBAction func action(_ sender: Any) {
|
||||
self.errorMessageLabel.text = nil
|
||||
|
||||
guard emailTextField.text != nil && passwordTextField.text != nil else {
|
||||
|
@ -56,8 +60,7 @@ class FeedbinAccountViewController: UIViewController {
|
|||
// When you fill in the email address via auto-complete it adds extra whitespace
|
||||
let emailAddress = emailTextField.text?.trimmingCharacters(in: .whitespaces)
|
||||
let credentials = Credentials.basic(username: emailAddress ?? "", password: passwordTextField.text ?? "")
|
||||
Account.validateCredentials(type: .feedbin, credentials: credentials) { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
Account.validateCredentials(type: .feedbin, credentials: credentials) { result in
|
||||
|
||||
self.stopAnimtatingActivityIndicator()
|
||||
self.enableNavigation()
|
||||
|
@ -73,7 +76,9 @@ class FeedbinAccountViewController: UIViewController {
|
|||
|
||||
do {
|
||||
|
||||
try self.account?.removeBasicCredentials()
|
||||
do {
|
||||
try self.account?.removeBasicCredentials()
|
||||
} catch {}
|
||||
try self.account?.storeCredentials(credentials)
|
||||
|
||||
if newAccount {
|
||||
|
@ -87,7 +92,8 @@ class FeedbinAccountViewController: UIViewController {
|
|||
}
|
||||
}
|
||||
|
||||
self.delegate?.dismiss(self)
|
||||
self.dismiss(animated: true, completion: nil)
|
||||
self.delegate?.dismiss()
|
||||
} catch {
|
||||
self.errorMessageLabel.text = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error")
|
||||
}
|
||||
|
@ -103,12 +109,12 @@ class FeedbinAccountViewController: UIViewController {
|
|||
|
||||
private func enableNavigation() {
|
||||
self.cancelBarButtonItem.isEnabled = true
|
||||
self.addAccountButton.isEnabled = true
|
||||
self.actionButton.isEnabled = true
|
||||
}
|
||||
|
||||
private func disableNavigation() {
|
||||
cancelBarButtonItem.isEnabled = false
|
||||
addAccountButton.isEnabled = false
|
||||
actionButton.isEnabled = false
|
||||
}
|
||||
|
||||
private func startAnimatingActivityIndicator() {
|
|
@ -1,13 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="9cW-lu-HoC">
|
||||
<device id="retina6_1" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14810.11" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="9cW-lu-HoC">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14766.13"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
<capability name="iOS 13.0 system colors" minToolsVersion="11.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Settings-->
|
||||
|
@ -25,11 +23,11 @@
|
|||
<rect key="frame" x="0.0" y="55.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="XHc-rQ-7FK" id="nmL-EM-Bsi">
|
||||
<rect key="frame" x="0.0" y="0.0" width="376" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="382.5" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Add Account" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="6sn-wY-hHH">
|
||||
<rect key="frame" x="20" y="0.0" width="356" height="43.5"/>
|
||||
<rect key="frame" x="20" y="0.0" width="354.5" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
|
@ -46,11 +44,11 @@
|
|||
<rect key="frame" x="0.0" y="155.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="376" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="382.5" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="About NetNewsWire" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="2o6-8W-nyK">
|
||||
<rect key="frame" x="20" y="0.0" width="356" height="43.5"/>
|
||||
<rect key="frame" x="20" y="0.0" width="354.5" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
|
@ -63,11 +61,11 @@
|
|||
<rect key="frame" x="0.0" y="199.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="43.5"/>
|
||||
<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="Website" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="lOk-Dh-GfZ">
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="43.5"/>
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
|
@ -80,11 +78,11 @@
|
|||
<rect key="frame" x="0.0" y="243.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="43.5"/>
|
||||
<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="Github Repository" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="TEA-EG-V6d">
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="43.5"/>
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
|
@ -97,11 +95,11 @@
|
|||
<rect key="frame" x="0.0" y="287.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="43.5"/>
|
||||
<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="Bug Tracker" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="Q9a-Pi-uCc">
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="43.5"/>
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
|
@ -114,11 +112,11 @@
|
|||
<rect key="frame" x="0.0" y="331.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="43.5"/>
|
||||
<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="Technotes" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="dWz-1o-EpJ">
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="43.5"/>
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
|
@ -131,11 +129,11 @@
|
|||
<rect key="frame" x="0.0" y="375.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="43.5"/>
|
||||
<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="43.5"/>
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
|
@ -152,7 +150,7 @@
|
|||
<rect key="frame" x="0.0" y="475.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="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Sort Oldest to Newest" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c9W-IF-u6i">
|
||||
|
@ -182,7 +180,7 @@
|
|||
<rect key="frame" x="0.0" y="519.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="air-4O-D7p" id="76S-R5-xwM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="376" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="382.5" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Number of Text Lines" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="B5l-Qp-6Gm">
|
||||
|
@ -193,7 +191,7 @@
|
|||
<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="u7B-tj-cw8">
|
||||
<rect key="frame" x="332" y="12" width="44" height="20.5"/>
|
||||
<rect key="frame" x="330.5" y="12" width="44" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
|
@ -210,7 +208,7 @@
|
|||
<rect key="frame" x="0.0" y="619.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="376" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="382.5" 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">
|
||||
|
@ -221,7 +219,7 @@
|
|||
<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">
|
||||
<rect key="frame" x="332" y="12" width="44" height="20.5"/>
|
||||
<rect key="frame" x="330.5" y="12" width="44" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
|
@ -234,11 +232,11 @@
|
|||
<rect key="frame" x="0.0" y="663.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="43.5"/>
|
||||
<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="43.5"/>
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
|
@ -251,11 +249,11 @@
|
|||
<rect key="frame" x="0.0" y="707.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="43.5"/>
|
||||
<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="43.5"/>
|
||||
<rect key="frame" x="20" y="0.0" width="374" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
|
@ -301,7 +299,7 @@
|
|||
<tableViewSection id="Zeb-b0-lsx">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="5DR-M4-NFv">
|
||||
<rect key="frame" x="0.0" y="35" width="414" height="44"/>
|
||||
<rect key="frame" x="0.0" y="18" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="5DR-M4-NFv" id="edh-bL-MIR">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
|
||||
|
@ -333,10 +331,10 @@
|
|||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="4n9-sW-i8D">
|
||||
<rect key="frame" x="0.0" y="79" width="414" height="44"/>
|
||||
<rect key="frame" x="0.0" y="61.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="4n9-sW-i8D" id="h3v-g9-biw">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Active" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pvF-Ge-m4M">
|
||||
|
@ -362,13 +360,37 @@
|
|||
<tableViewSection id="yP0-5C-1Dy">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="oJc-7j-G4v">
|
||||
<rect key="frame" x="0.0" y="159" width="414" height="44"/>
|
||||
<rect key="frame" x="0.0" y="141.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="oJc-7j-G4v" id="mV2-iL-ltS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Delete Account" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OKd-Ps-a1K">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Credentials" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OKd-Ps-a1K">
|
||||
<rect key="frame" x="163.5" y="11.5" width="87" height="21"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="OKd-Ps-a1K" firstAttribute="centerY" secondItem="mV2-iL-ltS" secondAttribute="centerY" id="ix2-SF-I7U"/>
|
||||
<constraint firstItem="OKd-Ps-a1K" firstAttribute="centerX" secondItem="mV2-iL-ltS" secondAttribute="centerX" id="nZf-Eh-VXu"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection id="sVG-jo-N8H">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="5r9-pq-th4">
|
||||
<rect key="frame" x="0.0" y="221.5" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="5r9-pq-th4" id="Z5N-KD-L3U">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Delete Account" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="a8v-SL-W9q">
|
||||
<rect key="frame" x="148" y="11.5" width="118" height="21"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
|
@ -376,8 +398,8 @@
|
|||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="OKd-Ps-a1K" firstAttribute="centerY" secondItem="mV2-iL-ltS" secondAttribute="centerY" id="ix2-SF-I7U"/>
|
||||
<constraint firstItem="OKd-Ps-a1K" firstAttribute="centerX" secondItem="mV2-iL-ltS" secondAttribute="centerX" id="nZf-Eh-VXu"/>
|
||||
<constraint firstItem="a8v-SL-W9q" firstAttribute="centerX" secondItem="Z5N-KD-L3U" secondAttribute="centerX" id="F0E-Pt-dt3"/>
|
||||
<constraint firstItem="a8v-SL-W9q" firstAttribute="centerY" secondItem="Z5N-KD-L3U" secondAttribute="centerY" id="TIC-BM-f0j"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
|
@ -410,10 +432,10 @@
|
|||
<tableViewSection id="m3P-em-PgI">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="55" id="UFl-6I-ucw">
|
||||
<rect key="frame" x="0.0" y="35" width="414" height="55"/>
|
||||
<rect key="frame" x="0.0" y="18" width="414" height="55"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="UFl-6I-ucw" id="99i-Ge-guB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="54.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="55"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="iTt-HT-Ane">
|
||||
|
@ -444,10 +466,10 @@
|
|||
<inset key="separatorInset" minX="50" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="56" id="te1-L9-osf">
|
||||
<rect key="frame" x="0.0" y="90" width="414" height="56"/>
|
||||
<rect key="frame" x="0.0" y="73" width="414" height="56"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="te1-L9-osf" id="DgY-u7-DRO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="55.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="56"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="7dy-NH-2zV">
|
||||
|
@ -658,10 +680,9 @@
|
|||
<constraint firstAttribute="height" constant="48" id="8Vt-l1-eL1"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<state key="normal" title="Add Account"/>
|
||||
<state key="normal" title="Action"/>
|
||||
<connections>
|
||||
<action selector="addAccountTapped:" destination="byh-sg-6p5" eventType="touchUpInside" id="BFR-MI-1qW"/>
|
||||
<action selector="addAccountTapped:" destination="lkT-rF-XV3" eventType="touchUpInside" id="YKl-dE-pVK"/>
|
||||
<action selector="action:" destination="byh-sg-6p5" eventType="touchUpInside" id="ZQy-9g-TeU"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9QD-Wz-fqW">
|
||||
|
@ -699,7 +720,7 @@
|
|||
</barButtonItem>
|
||||
<barButtonItem key="rightBarButtonItem" style="plain" id="L90-ti-E7I">
|
||||
<view key="customView" contentMode="scaleToFill" id="xpt-lr-f2h">
|
||||
<rect key="frame" x="374" y="12" width="20" height="20"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="20" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="Pl1-lc-sIl">
|
||||
|
@ -712,8 +733,8 @@
|
|||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<outlet property="actionButton" destination="pv5-O6-P6Z" id="6Fm-3l-zj1"/>
|
||||
<outlet property="activityIndicator" destination="Pl1-lc-sIl" id="hqg-mX-Yns"/>
|
||||
<outlet property="addAccountButton" destination="pv5-O6-P6Z" id="DEh-oq-rnD"/>
|
||||
<outlet property="cancelBarButtonItem" destination="xVt-VC-XFV" id="yBm-px-sgt"/>
|
||||
<outlet property="emailTextField" destination="UiV-th-dQb" id="fCb-hg-AXa"/>
|
||||
<outlet property="errorMessageLabel" destination="9QD-Wz-fqW" id="Kjo-73-Pgh"/>
|
||||
|
@ -734,7 +755,7 @@
|
|||
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="Ebv-d0-yty">
|
||||
<rect key="frame" x="0.0" y="55.5" width="414" height="44"/>
|
||||
<rect key="frame" x="0.0" y="55.5" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Ebv-d0-yty" id="OVc-0q-4qT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
|
||||
|
@ -763,7 +784,7 @@
|
|||
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="91W-kj-0Dw">
|
||||
<rect key="frame" x="0.0" y="55.5" width="414" height="44"/>
|
||||
<rect key="frame" x="0.0" y="55.5" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="91W-kj-0Dw" id="AXy-Ti-xiS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
|
||||
|
@ -795,10 +816,10 @@
|
|||
<tableViewSection id="apW-l0-gWz">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="62" id="zbQ-3A-f3f">
|
||||
<rect key="frame" x="0.0" y="35" width="414" height="62"/>
|
||||
<rect key="frame" x="0.0" y="18" width="414" height="62"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="zbQ-3A-f3f" id="5Al-LU-dRg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="61.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="62"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="NetNewsWire 5 for iOS" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UgA-s6-Vvg">
|
||||
|
@ -817,14 +838,14 @@
|
|||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="62" id="2DV-bO-vyT">
|
||||
<rect key="frame" x="0.0" y="97" width="414" height="62"/>
|
||||
<rect key="frame" x="0.0" y="80" width="414" height="62"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="2DV-bO-vyT" id="YUn-eb-xyx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="61.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="62"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" usesAttributedText="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5fQ-qz-qbW">
|
||||
<rect key="frame" x="16" y="0.0" width="382" height="61.5"/>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" usesAttributedText="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5fQ-qz-qbW">
|
||||
<rect key="frame" x="16" y="0.0" width="382" height="62"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<attributedString key="attributedText"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
|
@ -843,14 +864,14 @@
|
|||
<tableViewSection headerTitle="CREDITS" id="O1X-Iq-ibE">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="62" id="ZY4-id-Iia">
|
||||
<rect key="frame" x="0.0" y="215" width="414" height="62"/>
|
||||
<rect key="frame" x="0.0" y="198" width="414" height="62"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ZY4-id-Iia" id="IPw-QQ-LYI">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="61.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="62"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" usesAttributedText="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LiZ-Tv-tqb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="61.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="62"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<attributedString key="attributedText"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
|
@ -869,14 +890,14 @@
|
|||
<tableViewSection headerTitle="ACKNOWLEDGMENTS" id="0Jq-ba-ylz">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="62" id="0Ge-wc-h3h">
|
||||
<rect key="frame" x="0.0" y="333" width="414" height="62"/>
|
||||
<rect key="frame" x="0.0" y="316" width="414" height="62"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="0Ge-wc-h3h" id="tFb-3V-qIg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="61.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="62"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" usesAttributedText="YES" translatesAutoresizingMaskIntoConstraints="NO" id="YLf-rp-9nE">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="61.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="62"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<attributedString key="attributedText"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
|
@ -895,14 +916,14 @@
|
|||
<tableViewSection headerTitle="THANKS" id="Sgx-f1-hsT">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="62" id="bLz-mu-psL">
|
||||
<rect key="frame" x="0.0" y="451" width="414" height="62"/>
|
||||
<rect key="frame" x="0.0" y="434" width="414" height="62"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="bLz-mu-psL" id="cxy-IZ-zFZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="61.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="62"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" usesAttributedText="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wTL-xl-1rK">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="61.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="62"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<attributedString key="attributedText"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
|
@ -921,14 +942,14 @@
|
|||
<tableViewSection headerTitle="DEDICATION" id="nbm-Bs-te6">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="62" id="aab-HD-ce6">
|
||||
<rect key="frame" x="0.0" y="569" width="414" height="62"/>
|
||||
<rect key="frame" x="0.0" y="552" width="414" height="62"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="aab-HD-ce6" id="6pH-5O-3V8">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="61.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="62"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" usesAttributedText="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DIp-6a-oPH">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="61.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="62"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<attributedString key="attributedText"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
|
@ -1 +1 @@
|
|||
Subproject commit 97dc785d171c5ffa151d7df73135641da8f5bc17
|
||||
Subproject commit aa7107080e90d5be11ae54fd41ee4dd192468e30
|
|
@ -1 +1 @@
|
|||
Subproject commit c38a779de5f935c4d041e89066a0d15d490f3776
|
||||
Subproject commit 7b44768308dc6970ee78470d0ea1e5287badc2bc
|
|
@ -1 +1 @@
|
|||
Subproject commit 93b481897d84849345daa965bd8e11860c9422e7
|
||||
Subproject commit 032edf89b64ccbbfb6c05887b239a4bf81329b92
|
|
@ -1 +1 @@
|
|||
Subproject commit b6cd62f04c90922dbc58f2907a8db6c33b96b50e
|
||||
Subproject commit 350762104423aef963ca945d4388ca9d47f991ce
|
|
@ -1 +1 @@
|
|||
Subproject commit 59685e50640cd4629294bf2c0d63193ffa4ccc74
|
||||
Subproject commit 5d648e4050b700bb20fc7ae3303f087edcb3228f
|