13 Commits

Author SHA1 Message Date
relikd
b3940f103a Workaround for issue #7 2020-11-27 17:51:27 +01:00
relikd
239527908f Fix colorUnreadIndicator not being used 2020-11-27 17:42:13 +01:00
relikd
7df70a7936 update changelog 2020-08-31 21:26:44 +02:00
relikd
e8c4c06d33 Fix an issue where indices weren't updated, related to #6 2020-08-31 21:14:13 +02:00
relikd
a9c0e64689 Fix error log 'extractOptions unknown hint identifier' 2020-08-31 20:44:41 +02:00
relikd
51dd688801 Tint with accent color 2020-01-17 22:46:07 +01:00
relikd
352428679d Bump v1.1.0 + QLOPML v1.3 2020-01-17 15:21:47 +01:00
relikd
9b20262207 Menu bar icon tint (10.14+) 2020-01-17 14:07:14 +01:00
relikd
47a0e76cb3 NS_ASSUME_NONNULL 2020-01-17 12:19:08 +01:00
relikd
4c9362b42e Fix text color in About tab 2019-10-25 19:48:01 +02:00
relikd
9af191834e Fix menu flickering on macOS 10.15 2019-10-25 15:57:24 +02:00
relikd
8d2e4e4383 Fix preferences on macOS 10.15 2019-10-24 12:40:12 +02:00
relikd
473d4b6057 Update changelog 2019-10-04 12:52:22 +02:00
54 changed files with 440 additions and 129 deletions

View File

@@ -7,7 +7,40 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
## [Unreleased] ## [Unreleased]
## [1.0.0] - 2019-10-03
## [1.1.2] 2020-11-27
### Fixed
- Fixes hidden color option for marking unread entries. Unread menu entries did use `colorStatusIconTint` instead of `colorUnreadIndicator` (thanks @tchek)
- Workaround for not displaying status bar highlight color in macOS 11.0 (issue #7)
## [1.1.1] 2020-08-31
### Fixed
- Feed indices weren't updated properly which resulted in empty feed menus (issue: #6)
## [1.1.0] 2020-01-17
### Added
- *QuickLook*: Thumbnail previews for OPML files (QLOPML v1.3)
- *Status Bar Menu*: Tint menu bar icon with Accent color (macOS 10.14+)
### Fixed
- Resolved Xcode warnings in Xcode 11
## [1.0.2] 2019-10-25
### Fixed
- *Status Bar Menu*: Preferences could not be opened on macOS 10.15
- *Status Bar Menu*: Menu flickering resulting in a hang on macOS 10.15
- *UI*: Text color in `About` tab
## [1.0.1] 2019-10-04
### Fixed
- Crash on macOS 10.14 due to a `CGColorRef` null pointer
## [1.0.0] 2019-10-03
### Added ### Added
- App Signing - App Signing
- Sandboxing & hardened runtime environment - Sandboxing & hardened runtime environment
@@ -64,7 +97,7 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
- *UI:* Mark unread articles with blue dot, instead of tick mark - *UI:* Mark unread articles with blue dot, instead of tick mark
## [0.9.4] - 2019-04-02 ## [0.9.4] 2019-04-02
### Fixed ### Fixed
- Article order got mixed up for some feeds (issue: #4) - Article order got mixed up for some feeds (issue: #4)
- If multiple consecutive items reappear in the middle of the feed mark them read - If multiple consecutive items reappear in the middle of the feed mark them read
@@ -73,7 +106,7 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
- *UI:* Removed checkbox `Start on login`. Use Preferences > Users > Login Items instead. - *UI:* Removed checkbox `Start on login`. Use Preferences > Users > Login Items instead.
## [0.9.3] - 2019-03-14 ## [0.9.3] 2019-03-14
### Added ### Added
- Changelog - Changelog
- *UI:* Show body tag in article tooltip if abstract tag is empty - *UI:* Show body tag in article tooltip if abstract tag is empty
@@ -83,7 +116,7 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
- Fixed update for feeds where all article URLs point to the same resource (issue: #3) - Fixed update for feeds where all article URLs point to the same resource (issue: #3)
## [0.9.2] - 2019-03-07 ## [0.9.2] 2019-03-07
### Added ### Added
- Limit number of articles that are displayed in feed menu (issue: #2) - Limit number of articles that are displayed in feed menu (issue: #2)
@@ -93,7 +126,7 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
- libxml2 will ignore lower ascii characters (`0x00``0x1F`) - libxml2 will ignore lower ascii characters (`0x00``0x1F`)
## [0.9.1] - 2019-02-14 ## [0.9.1] 2019-02-14
### Added ### Added
- Mark single article as un/read (hold down option key and click on article) - Mark single article as un/read (hold down option key and click on article)
@@ -111,11 +144,16 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
- Remove html tags from abstract on save (not on display) - Remove html tags from abstract on save (not on display)
## [0.9] - 2019-02-11 ## [0.9] 2019-02-11
Initial release Initial release
[Unreleased]: https://github.com/relikd/baRSS/compare/v1.0.0...HEAD [Unreleased]: https://github.com/relikd/baRSS/compare/v1.1.2...HEAD
[1.1.2]: https://github.com/relikd/baRSS/compare/v1.1.1...v1.1.2
[1.1.1]: https://github.com/relikd/baRSS/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/relikd/baRSS/compare/v1.0.2...v1.1.0
[1.0.2]: https://github.com/relikd/baRSS/compare/v1.0.1...v1.0.2
[1.0.1]: https://github.com/relikd/baRSS/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/relikd/baRSS/compare/v0.9.4...v1.0.0 [1.0.0]: https://github.com/relikd/baRSS/compare/v0.9.4...v1.0.0
[0.9.4]: https://github.com/relikd/baRSS/compare/v0.9.3...v0.9.4 [0.9.4]: https://github.com/relikd/baRSS/compare/v0.9.3...v0.9.4
[0.9.3]: https://github.com/relikd/baRSS/compare/v0.9.2...v0.9.3 [0.9.3]: https://github.com/relikd/baRSS/compare/v0.9.2...v0.9.3

View File

@@ -1 +1 @@
github "relikd/RSXML2" "v2.0.0" github "relikd/RSXML2" "f1a02fabbdece48e3f7835725c65a7589d98782f"

View File

@@ -1 +1 @@
github "relikd/RSXML2" "v2.0.0" github "relikd/RSXML2" "f1a02fabbdece48e3f7835725c65a7589d98782f"

View File

@@ -31,39 +31,6 @@ But it will reuse `ETag` and `Last-Modified` headers to avoid unnecessary transm
Further, tuning the update frequently will decrease the traffic even more. Further, tuning the update frequently will decrease the traffic even more.
### Why create something that already existed?
First, open source is awesome!
Secondly, RSS Menu made some design decisions I didn't like.
For example, the new integrated browser window.
One thing I liked most, was the fact that feeds were opened in the default browser.
Not like 99% of the other feed readers on the market that show a separate HTML viewer window.
No rendering issues, no broken links, no content that is different from the actual news article.
I know, the whole purpose of RSS is to deliver content without the need of opening a webpage.
But for me RSS is more about being informed whenever a blog or news feed has some updated content.
E.g, subscribing to video channels without having to have an account.
### Why is this project not written in Swift?!
Actually, I started this project with Swift.
Even without adding much functionality, the app was exceeding the 10 Mb file size.
The working alpha version, written in Objective-C, had only 500 Kb.
The reason being that Swift frameworks are always packed into the final application.
Sadly, this was before Swift 5 and ABI stability.
Had I only started the project a year later…
But on the other hand, now it is macOS 10.12 compatible.
### 3rd Party Libraries
This project uses a modified version of Brent Simmons [RSXML](https://github.com/brentsimmons/RSXML) for feed parsing.
RSXML is licensed under a MIT license (same as this project).
Download & Install Download & Install
------------------ ------------------
@@ -72,18 +39,18 @@ Requires macOS Sierra (10.12) or higher.
### Easy way ### Easy way
Go to [releases](https://github.com/relikd/baRSS/releases) and downloaded the latest version. Go to [releases](https://github.com/relikd/baRSS/releases) and downloaded the latest version.
Searching for the App Store release? Read this [notice](#app-store-notice).
### Build from source ### Build from source
You'll need Xcode and [Carthage](https://github.com/Carthage/Carthage#installing-carthage). You'll need Xcode and [Carthage](https://github.com/Carthage/Carthage#installing-carthage).
The latter is optional, you can build the [RSXML2](https://github.com/relikd/RSXML2) library from source instead. The latter is optional, you can build the [RSXML2] library from source instead.
Carthage just makes it more convenient. Carthage just makes it more convenient.
Download and unzip this project, navigate to the root folder and run `carthage bootstrap --platform macOS`. Download and unzip this project, navigate to the root folder and run `carthage bootstrap --platform macOS`.
Next, you need to clone [QLOPML](https://github.com/relikd/QLOPML) in the same folder where this project is. Next, you need to clone [QLOPML](https://github.com/relikd/QLOPML) in the same folder where this project is.
Alternatively, you can simply delete the `QLOPML` project reference without much harm. Alternatively, you can simply delete the `QLOPML` project reference without much harm.
`QLOPML` is a Quick Look plugin for `.opml` files. `QLOPML` is a Quick Look plugin for `.opml` files.
It will display the file contents whenever you hit space. It will display the file contents whenever you hit spacebar.
That's it. That's it.
Open Xcode and build the project. Open Xcode and build the project.
@@ -141,7 +108,7 @@ ToDo
The following list is not exhaustive but rather a collection of nice things that will be added eventually. The following list is not exhaustive but rather a collection of nice things that will be added eventually.
I may postpone some until demand increases … I may postpone some until demand increases …
- [ ] Localizations - [ ] Localizations
- [ ] Feed generator for websites without feeds - [ ] Feed generator for websites without feeds
- [ ] Automatically choose best update interval (e.g., avg) - [ ] Automatically choose best update interval (e.g., avg)
- [ ] Sync with online services - [ ] Sync with online services
@@ -160,9 +127,61 @@ I may postpone some until demand increases …
FAQ / Q&A
---------
### App Store Notice
In the last couple of months I prepared baRSS to be released on the App Store.
With sandboxing enabled and hardened runtime environment, etc.
But, for the time being, I decided to not publish this app for political reasons.
I was not happy about some decisions made in the last weeks.
Decisions that were evaluated on monetary aspects and not on ethical considerations.
I won't support this conduct with my own money.
If you find this app somewhere on the App Store, you can be sure that it is a counterfeit.
As long as you can read this very notice, I am not responsible for the publication.
Further, I can't guarantee the App Store version wasn't modified by a malicious actor to spy on you.
### Why create something that already existed?
First, open source is awesome!
Secondly, RSS Menu made some design decisions I didn't like.
For example, the new integrated browser window.
One thing I liked most, was the fact that feeds were opened in the default browser.
Not like 99% of the other feed readers on the market that show a separate HTML viewer window.
No rendering issues, no broken links, no content that is different from the actual news article.
I know, the whole purpose of RSS is to deliver content without the need of opening a webpage.
But for me RSS is more about being informed whenever a blog or news feed has some updated content.
E.g, subscribing to video channels without having to have an account.
### Why is this project not written in Swift?!
Actually, I started this project with Swift.
Even without adding much functionality, the app was exceeding the 10 Mb file size.
The working alpha version, written in Objective-C, had only 500 Kb.
The reason being that Swift frameworks are always packed into the final application.
Sadly, this was before Swift 5 and ABI stability.
Had I only started the project a year later…
But on the other hand, now it is macOS 10.12 compatible.
### 3rd Party Libraries
This project uses a modified version of Brent Simmons' [RSXML](https://github.com/brentsimmons/RSXML) for feed parsing.
[RSXML2] is licensed under a MIT license (same as this project).
##### Trivia ##### Trivia
- Start of project: __July 19, 2018__ - Start of project: __July 19, 2018__
- Estimated development time: __1940h+__ - Estimated development time: __1963h+__
- First prototype used __feedparser python__ library - First prototype used __feedparser python__ library
[RSXML2]: https://github.com/relikd/RSXML2

View File

@@ -47,6 +47,7 @@
54BF444A22D0F4F300660096 /* AppIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 54BF444922D0F4F300660096 /* AppIcon.icns */; }; 54BF444A22D0F4F300660096 /* AppIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 54BF444922D0F4F300660096 /* AppIcon.icns */; };
54D55D7322E624CD00057B98 /* SettingsFeeds+DragDrop.m in Sources */ = {isa = PBXBuildFile; fileRef = 54D55D7222E624CD00057B98 /* SettingsFeeds+DragDrop.m */; }; 54D55D7322E624CD00057B98 /* SettingsFeeds+DragDrop.m in Sources */ = {isa = PBXBuildFile; fileRef = 54D55D7222E624CD00057B98 /* SettingsFeeds+DragDrop.m */; };
54D857CE227C5785001BA1C8 /* RefreshStatisticsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 54D857CD227C5785001BA1C8 /* RefreshStatisticsView.m */; }; 54D857CE227C5785001BA1C8 /* RefreshStatisticsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 54D857CD227C5785001BA1C8 /* RefreshStatisticsView.m */; };
54DD9F1323D1D6B000B1EAA6 /* NSColor+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54DD9F1223D1D6B000B1EAA6 /* NSColor+Ext.m */; };
54E3C02122EE076D006E2E24 /* opml-icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 54E3C02022EE076D006E2E24 /* opml-icon.icns */; }; 54E3C02122EE076D006E2E24 /* opml-icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 54E3C02022EE076D006E2E24 /* opml-icon.icns */; };
54E4446C2329AE0600BBF481 /* NSError+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E4446B2329AE0600BBF481 /* NSError+Ext.m */; }; 54E4446C2329AE0600BBF481 /* NSError+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E4446B2329AE0600BBF481 /* NSError+Ext.m */; };
54E88320211B509D00064188 /* ModalFeedEdit.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E8831E211B509D00064188 /* ModalFeedEdit.m */; }; 54E88320211B509D00064188 /* ModalFeedEdit.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E8831E211B509D00064188 /* ModalFeedEdit.m */; };
@@ -181,6 +182,8 @@
54D857CD227C5785001BA1C8 /* RefreshStatisticsView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RefreshStatisticsView.m; sourceTree = "<group>"; }; 54D857CD227C5785001BA1C8 /* RefreshStatisticsView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RefreshStatisticsView.m; sourceTree = "<group>"; };
54D857D022802309001BA1C8 /* SettingsGeneralView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SettingsGeneralView.h; sourceTree = "<group>"; }; 54D857D022802309001BA1C8 /* SettingsGeneralView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SettingsGeneralView.h; sourceTree = "<group>"; };
54D857D122802309001BA1C8 /* SettingsGeneralView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SettingsGeneralView.m; sourceTree = "<group>"; }; 54D857D122802309001BA1C8 /* SettingsGeneralView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SettingsGeneralView.m; sourceTree = "<group>"; };
54DD9F1123D1D6B000B1EAA6 /* NSColor+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSColor+Ext.h"; sourceTree = "<group>"; };
54DD9F1223D1D6B000B1EAA6 /* NSColor+Ext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSColor+Ext.m"; sourceTree = "<group>"; };
54E3C02022EE076D006E2E24 /* opml-icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = "opml-icon.icns"; sourceTree = "<group>"; }; 54E3C02022EE076D006E2E24 /* opml-icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = "opml-icon.icns"; sourceTree = "<group>"; };
54E4446A2329AE0600BBF481 /* NSError+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSError+Ext.h"; sourceTree = "<group>"; }; 54E4446A2329AE0600BBF481 /* NSError+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSError+Ext.h"; sourceTree = "<group>"; };
54E4446B2329AE0600BBF481 /* NSError+Ext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSError+Ext.m"; sourceTree = "<group>"; }; 54E4446B2329AE0600BBF481 /* NSError+Ext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSError+Ext.m"; sourceTree = "<group>"; };
@@ -226,10 +229,8 @@
544936F721F1E51E00DEE9AA /* NSCategories */ = { 544936F721F1E51E00DEE9AA /* NSCategories */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
54B517052270E8C6006C1B29 /* NSView+Ext.h */, 54DD9F1123D1D6B000B1EAA6 /* NSColor+Ext.h */,
54B517062270E92A006C1B29 /* NSView+Ext.m */, 54DD9F1223D1D6B000B1EAA6 /* NSColor+Ext.m */,
54AD4E0A2301853D000AE386 /* NSString+Ext.h */,
54AD4E0B2301853D000AE386 /* NSString+Ext.m */,
54BB048721FD2AB500C303A5 /* NSDate+Ext.h */, 54BB048721FD2AB500C303A5 /* NSDate+Ext.h */,
54BB048821FD2AB500C303A5 /* NSDate+Ext.m */, 54BB048821FD2AB500C303A5 /* NSDate+Ext.m */,
54E4446A2329AE0600BBF481 /* NSError+Ext.h */, 54E4446A2329AE0600BBF481 /* NSError+Ext.h */,
@@ -238,6 +239,10 @@
548C6D09230C33DE003A1AAF /* NSURL+Ext.m */, 548C6D09230C33DE003A1AAF /* NSURL+Ext.m */,
54B6F14C23155E1A002C94C9 /* NSURLRequest+Ext.h */, 54B6F14C23155E1A002C94C9 /* NSURLRequest+Ext.h */,
54B6F14D23155E1A002C94C9 /* NSURLRequest+Ext.m */, 54B6F14D23155E1A002C94C9 /* NSURLRequest+Ext.m */,
54AD4E0A2301853D000AE386 /* NSString+Ext.h */,
54AD4E0B2301853D000AE386 /* NSString+Ext.m */,
54B517052270E8C6006C1B29 /* NSView+Ext.h */,
54B517062270E92A006C1B29 /* NSView+Ext.m */,
); );
path = NSCategories; path = NSCategories;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -457,7 +462,7 @@
54ACC27421061B3B0020715F /* Project object */ = { 54ACC27421061B3B0020715F /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 0940; LastUpgradeCheck = 1200;
ORGANIZATIONNAME = relikd; ORGANIZATIONNAME = relikd;
TargetAttributes = { TargetAttributes = {
54ACC27B21061B3B0020715F = { 54ACC27B21061B3B0020715F = {
@@ -577,6 +582,7 @@
544B011D2114EE9100386E5C /* AppHook.m in Sources */, 544B011D2114EE9100386E5C /* AppHook.m in Sources */,
546FC44321189975007CC3A3 /* SettingsGeneral.m in Sources */, 546FC44321189975007CC3A3 /* SettingsGeneral.m in Sources */,
546A6A2922C583390034E806 /* SettingsGeneralView.m in Sources */, 546A6A2922C583390034E806 /* SettingsGeneralView.m in Sources */,
54DD9F1323D1D6B000B1EAA6 /* NSColor+Ext.m in Sources */,
54ACC29521061E270020715F /* UpdateScheduler.m in Sources */, 54ACC29521061E270020715F /* UpdateScheduler.m in Sources */,
54BB048921FD2AB500C303A5 /* NSDate+Ext.m in Sources */, 54BB048921FD2AB500C303A5 /* NSDate+Ext.m in Sources */,
5477D34E21233C62002BA27F /* FeedGroup+Ext.m in Sources */, 5477D34E21233C62002BA27F /* FeedGroup+Ext.m in Sources */,
@@ -641,6 +647,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -696,12 +703,14 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1000" LastUpgradeVersion = "1200"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
@@ -38,8 +36,8 @@
ReferencedContainer = "container:baRSS.xcodeproj"> ReferencedContainer = "container:baRSS.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<AdditionalOptions> <Testables>
</AdditionalOptions> </Testables>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
@@ -49,7 +47,7 @@
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
stopOnEveryMainThreadCheckerIssue = "YES" migratedStopOnEveryIssue = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
allowLocationSimulation = "YES"> allowLocationSimulation = "YES">
<BuildableProductRunnable <BuildableProductRunnable
@@ -83,8 +81,6 @@
isEnabled = "NO"> isEnabled = "NO">
</EnvironmentVariable> </EnvironmentVariable>
</EnvironmentVariables> </EnvironmentVariables>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Release" buildConfiguration = "Release"

View File

@@ -24,6 +24,8 @@
#import "Feed+CoreDataClass.h" #import "Feed+CoreDataClass.h"
@class RSParsedFeed; @class RSParsedFeed;
NS_ASSUME_NONNULL_BEGIN
@interface Feed (Ext) @interface Feed (Ext)
@property (readonly) BOOL hasIcon; @property (readonly) BOOL hasIcon;
@property (nonnull, readonly) NSImage* iconImage16; @property (nonnull, readonly) NSImage* iconImage16;
@@ -38,3 +40,5 @@
// Article properties // Article properties
- (NSArray<FeedArticle*>*)sortedArticles; - (NSArray<FeedArticle*>*)sortedArticles;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -196,7 +196,8 @@
if (self.articles.count == 0) { if (self.articles.count == 0) {
img = [NSImage imageNamed:NSImageNameCaution]; img = [NSImage imageNamed:NSImageNameCaution];
} else if (self.hasIcon) { } else if (self.hasIcon) {
img = [[NSImage alloc] initByReferencingURL:[self iconPath]]; NSData* data = [[NSData alloc] initWithContentsOfURL:[self iconPath]];
img = [[NSImage alloc] initWithData:data];
} else { } else {
img = [NSImage imageNamed:RSSImageDefaultRSSIcon]; img = [NSImage imageNamed:RSSImageDefaultRSSIcon];
} }

View File

@@ -24,7 +24,11 @@
#import "FeedArticle+CoreDataClass.h" #import "FeedArticle+CoreDataClass.h"
@class RSParsedArticle; @class RSParsedArticle;
NS_ASSUME_NONNULL_BEGIN
@interface FeedArticle (Ext) @interface FeedArticle (Ext)
+ (instancetype)newArticle:(RSParsedArticle*)entry inContext:(NSManagedObjectContext*)moc; + (instancetype)newArticle:(RSParsedArticle*)entry inContext:(NSManagedObjectContext*)moc;
- (NSMenuItem*)newMenuItem; - (NSMenuItem*)newMenuItem;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -29,6 +29,7 @@ typedef NS_ENUM(int16_t, FeedGroupType) {
GROUP = 0, FEED = 1, SEPARATOR = 2 GROUP = 0, FEED = 1, SEPARATOR = 2
}; };
NS_ASSUME_NONNULL_BEGIN
@interface FeedGroup (Ext) @interface FeedGroup (Ext)
/// Overwrites @c type attribute with enum. Use one of: @c GROUP, @c FEED, @c SEPARATOR. /// Overwrites @c type attribute with enum. Use one of: @c GROUP, @c FEED, @c SEPARATOR.
@@ -39,7 +40,7 @@ typedef NS_ENUM(int16_t, FeedGroupType) {
+ (instancetype)newGroup:(FeedGroupType)type inContext:(NSManagedObjectContext*)context; + (instancetype)newGroup:(FeedGroupType)type inContext:(NSManagedObjectContext*)context;
+ (instancetype)appendToRoot:(FeedGroupType)type inContext:(NSManagedObjectContext*)moc; + (instancetype)appendToRoot:(FeedGroupType)type inContext:(NSManagedObjectContext*)moc;
- (void)setParent:(FeedGroup *)parent andSortIndex:(int32_t)sortIndex; - (void)setParent:(nullable FeedGroup *)parent andSortIndex:(int32_t)sortIndex;
- (void)setSortIndexIfChanged:(int32_t)sortIndex; - (void)setSortIndexIfChanged:(int32_t)sortIndex;
- (void)setNameIfChanged:(nullable NSString*)name; - (void)setNameIfChanged:(nullable NSString*)name;
- (NSMenuItem*)newMenuItem; - (NSMenuItem*)newMenuItem;
@@ -50,3 +51,5 @@ typedef NS_ENUM(int16_t, FeedGroupType) {
// Printing // Printing
- (NSString*)readableDescription; - (NSString*)readableDescription;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -78,7 +78,7 @@
} }
/// Set @c parent and @c sortIndex. Also if type is @c FEED calculate and set @c indexPath string. /// Set @c parent and @c sortIndex. Also if type is @c FEED calculate and set @c indexPath string.
- (void)setParent:(FeedGroup *)parent andSortIndex:(int32_t)sortIndex { - (void)setParent:(nullable FeedGroup *)parent andSortIndex:(int32_t)sortIndex {
self.parent = parent; self.parent = parent;
self.sortIndex = sortIndex; self.sortIndex = sortIndex;
if (self.type == FEED) if (self.type == FEED)
@@ -89,10 +89,11 @@
- (void)setSortIndexIfChanged:(int32_t)sortIndex { - (void)setSortIndexIfChanged:(int32_t)sortIndex {
if (self.sortIndex != sortIndex) { if (self.sortIndex != sortIndex) {
self.sortIndex = sortIndex; self.sortIndex = sortIndex;
[self iterateSorted:NO overDescendantFeeds:^(Feed *feed, BOOL *cancel) {
[feed calculateAndSetIndexPathString];
}];
} }
// Otherwise move from 0.0 -> 0 will not trigger index path update
[self iterateSorted:NO overDescendantFeeds:^(Feed *feed, BOOL *cancel) {
[feed calculateAndSetIndexPathString];
}];
} }
/// Set @c name attribute but only if value differs. /// Set @c name attribute but only if value differs.

View File

@@ -25,6 +25,8 @@
static int32_t const kDefaultFeedRefreshInterval = 30 * 60; static int32_t const kDefaultFeedRefreshInterval = 30 * 60;
NS_ASSUME_NONNULL_BEGIN
@interface FeedMeta (Ext) @interface FeedMeta (Ext)
+ (instancetype)newMetaInContext:(NSManagedObjectContext*)moc; + (instancetype)newMetaInContext:(NSManagedObjectContext*)moc;
// HTTP response // HTTP response
@@ -35,3 +37,5 @@ static int32_t const kDefaultFeedRefreshInterval = 30 * 60;
- (void)setRefreshIfChanged:(int32_t)refresh; - (void)setRefreshIfChanged:(int32_t)refresh;
- (void)scheduleNow:(NSTimeInterval)future; - (void)scheduleNow:(NSTimeInterval)future;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -22,6 +22,8 @@
@import Cocoa; @import Cocoa;
NS_ASSUME_NONNULL_BEGIN
@interface NSFetchRequest<ResultType> (Ext) @interface NSFetchRequest<ResultType> (Ext)
// Perform core data request and fetch data // Perform core data request and fetch data
- (NSArray<ResultType>*)fetchAllRows:(NSManagedObjectContext*)moc; - (NSArray<ResultType>*)fetchAllRows:(NSManagedObjectContext*)moc;
@@ -37,3 +39,5 @@
- (instancetype)sortDESC:(NSString*)key; // add .sortDescriptors -> ascending:NO - (instancetype)sortDESC:(NSString*)key; // add .sortDescriptors -> ascending:NO
- (instancetype)addFunctionExpression:(NSString*)fn onKeyPath:(NSString*)keyPath name:(NSString*)name type:(NSAttributeType)type; // add .propertiesToFetch -> (expressionForFunction:@[expressionForKeyPath:]) - (instancetype)addFunctionExpression:(NSString*)fn onKeyPath:(NSString*)keyPath name:(NSString*)name type:(NSAttributeType)type; // add .propertiesToFetch -> (expressionForFunction:@[expressionForKeyPath:])
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -23,6 +23,8 @@
@import Cocoa; @import Cocoa;
#import "DBv1+CoreDataModel.h" #import "DBv1+CoreDataModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface StoreCoordinator : NSObject @interface StoreCoordinator : NSObject
// Managing contexts // Managing contexts
+ (NSManagedObjectContext*)getMainContext; + (NSManagedObjectContext*)getMainContext;
@@ -44,7 +46,7 @@
+ (NSArray<NSDictionary*>*)countAggregatedUnread; + (NSArray<NSDictionary*>*)countAggregatedUnread;
// Get List Of Elements // Get List Of Elements
+ (NSArray<FeedGroup*>*)sortedFeedGroupsWithParent:(id)parent inContext:(nullable NSManagedObjectContext*)moc; + (NSArray<FeedGroup*>*)sortedFeedGroupsWithParent:(nullable id)parent inContext:(nullable NSManagedObjectContext*)moc;
+ (Feed*)feedWithIndexPath:(nonnull NSString*)path inContext:(nullable NSManagedObjectContext*)moc; + (Feed*)feedWithIndexPath:(nonnull NSString*)path inContext:(nullable NSManagedObjectContext*)moc;
+ (NSString*)urlForFeedWithIndexPath:(nonnull NSString*)path; + (NSString*)urlForFeedWithIndexPath:(nonnull NSString*)path;
@@ -55,3 +57,5 @@
+ (void)cleanupAndShowAlert:(BOOL)flag; + (void)cleanupAndShowAlert:(BOOL)flag;
+ (NSUInteger)cleanupFavicons; + (NSUInteger)cleanupFavicons;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -144,7 +144,7 @@
@param moc If @c nil perform requests on main context (ok for reading). @param moc If @c nil perform requests on main context (ok for reading).
@return Sorted list of @c FeedGroup items where @c FeedGroup.parent @c = @c parent. @return Sorted list of @c FeedGroup items where @c FeedGroup.parent @c = @c parent.
*/ */
+ (NSArray<FeedGroup*>*)sortedFeedGroupsWithParent:(id)parent inContext:(nullable NSManagedObjectContext*)moc { + (NSArray<FeedGroup*>*)sortedFeedGroupsWithParent:(nullable id)parent inContext:(nullable NSManagedObjectContext*)moc {
return [[[[FeedGroup fetchRequest] where:@"parent = %@", parent] sortASC:@"sortIndex"] fetchAllRows:moc ? moc : [self getMainContext]]; return [[[[FeedGroup fetchRequest] where:@"parent = %@", parent] sortASC:@"sortIndex"] fetchAllRows:moc ? moc : [self getMainContext]];
} }

View File

@@ -24,9 +24,13 @@
#define ENV_LOG_YOUTUBE 1 #define ENV_LOG_YOUTUBE 1
NS_ASSUME_NONNULL_BEGIN
// TODO: Make plugins extensible? community extensions. // TODO: Make plugins extensible? community extensions.
@interface YouTubePlugin : NSObject @interface YouTubePlugin : NSObject
+ (NSString*)feedURL:(NSURL*)url; + (NSString*)feedURL:(NSURL*)url;
+ (NSString*)videoImage:(NSString*)videoid; + (NSString*)videoImage:(NSString*)videoid;
+ (NSString*)videoImageHQ:(NSString*)videoid; + (NSString*)videoImageHQ:(NSString*)videoid;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -80,5 +80,10 @@
return [NSString stringWithFormat:@"http://i.ytimg.com/vi/%@/hqdefault.jpg", videoid]; return [NSString stringWithFormat:@"http://i.ytimg.com/vi/%@/hqdefault.jpg", videoid];
} }
/// @return @c http://i.ytimg.com/vi/<videoid>/maxresdefault.jpg
+ (NSString*)videoImage4k:(NSString*)videoid {
return [NSString stringWithFormat:@"http://i.ytimg.com/vi/%@/maxresdefault.jpg", videoid];
}
@end @end

View File

@@ -24,6 +24,8 @@
@class Feed, RSHTMLMetadata, FeedDownload; @class Feed, RSHTMLMetadata, FeedDownload;
@protocol FaviconDownloadDelegate; @protocol FaviconDownloadDelegate;
NS_ASSUME_NONNULL_BEGIN
@interface FaviconDownload : NSObject @interface FaviconDownload : NSObject
/// @c img and @c path are @c nil if image is not valid or couldn't be downloaded. /// @c img and @c path are @c nil if image is not valid or couldn't be downloaded.
typedef void(^FaviconDownloadBlock)(NSImage * _Nullable img, NSURL * _Nullable path); typedef void(^FaviconDownloadBlock)(NSImage * _Nullable img, NSURL * _Nullable path);
@@ -45,3 +47,5 @@ typedef void(^FaviconDownloadBlock)(NSImage * _Nullable img, NSURL * _Nullable p
/// Called after image download. Called on error, but not if download is cancled. /// Called after image download. Called on error, but not if download is cancled.
- (void)faviconDownload:(FaviconDownload*)sender didFinish:(nullable NSURL*)path; - (void)faviconDownload:(FaviconDownload*)sender didFinish:(nullable NSURL*)path;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -147,7 +147,13 @@
return; return;
self.currentDownload = [[NSURLRequest requestWithURL:self.remoteURL] downloadTask:^(NSURL * _Nullable path, NSError * _Nullable error) { self.currentDownload = [[NSURLRequest requestWithURL:self.remoteURL] downloadTask:^(NSURL * _Nullable path, NSError * _Nullable error) {
if (error) path = nil; // will also nullify img if (error) path = nil; // will also nullify img
NSImage *img = path ? [[NSImage alloc] initByReferencingURL:path] : nil; NSImage *img;
if (path) {
NSData* data = [[NSData alloc] initWithContentsOfURL:path];
img = [[NSImage alloc] initWithData:data];
} else {
img = nil;
}
if (img.valid) { if (img.valid) {
// move image to temporary destination, otherwise dataTask: will delete it. // move image to temporary destination, otherwise dataTask: will delete it.
NSString *tmpFile = NSProcessInfo.processInfo.globallyUniqueString; NSString *tmpFile = NSProcessInfo.processInfo.globallyUniqueString;
@@ -166,7 +172,8 @@
if (self.canceled) if (self.canceled)
return; return;
NSURL *path = self.fileURL; NSURL *path = self.fileURL;
NSImage *img = [[NSImage alloc] initByReferencingURL:path]; NSData* data = [[NSData alloc] initWithContentsOfURL:path];
NSImage* img = [[NSImage alloc] initWithData:data];
if (!img.valid) { path = nil; img = nil; } if (!img.valid) { path = nil; img = nil; }
#if DEBUG && ENV_LOG_DOWNLOAD #if DEBUG && ENV_LOG_DOWNLOAD
printf("ICON %1.0fx%1.0f %s\n", img.size.width, img.size.height, self.remoteURL.absoluteString.UTF8String); printf("ICON %1.0fx%1.0f %s\n", img.size.width, img.size.height, self.remoteURL.absoluteString.UTF8String);

View File

@@ -24,6 +24,8 @@
@class RSParsedFeed, RSHTMLMetadataFeedLink, Feed, FaviconDownload; @class RSParsedFeed, RSHTMLMetadataFeedLink, Feed, FaviconDownload;
@protocol FeedDownloadDelegate; @protocol FeedDownloadDelegate;
NS_ASSUME_NONNULL_BEGIN
/** /**
All properties will be parsed and stored in local variables. All properties will be parsed and stored in local variables.
This will avoid unnecessary core data operations if user decides to cancel the edit. This will avoid unnecessary core data operations if user decides to cancel the edit.
@@ -61,3 +63,5 @@ typedef void (^FeedDownloadBlock)(FeedDownload *sender);
/// Called after xml data is loaded and parsed. Called on error, but not if download is cancled. /// Called after xml data is loaded and parsed. Called on error, but not if download is cancled.
- (void)feedDownloadDidFinish:(FeedDownload*)sender; - (void)feedDownloadDidFinish:(FeedDownload*)sender;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -28,6 +28,8 @@ typedef NS_OPTIONS(NSUInteger, OpmlFileExportOptions) {
OpmlFileExportOptionFullBackup = 1 << 2, OpmlFileExportOptionFullBackup = 1 << 2,
}; };
NS_ASSUME_NONNULL_BEGIN
#pragma mark - Protocols #pragma mark - Protocols
@protocol OpmlFileImportDelegate <NSObject> @protocol OpmlFileImportDelegate <NSObject>
@@ -61,3 +63,5 @@ typedef NS_OPTIONS(NSUInteger, OpmlFileExportOptions) {
- (void)showExportDialog:(NSWindow*)window; - (void)showExportDialog:(NSWindow*)window;
- (nullable NSError*)writeOPMLFile:(NSURL*)url withOptions:(OpmlFileExportOptions)opt; - (nullable NSError*)writeOPMLFile:(NSURL*)url withOptions:(OpmlFileExportOptions)opt;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -24,6 +24,8 @@
@class Feed; @class Feed;
NS_ASSUME_NONNULL_BEGIN
@interface UpdateScheduler : NSObject @interface UpdateScheduler : NSObject
@property (class, readonly) NSUInteger feedsInQueue; @property (class, readonly) NSUInteger feedsInQueue;
@property (class, readonly) NSDate *dateScheduled; @property (class, readonly) NSDate *dateScheduled;
@@ -46,3 +48,5 @@
+ (void)registerNetworkChangeNotification; + (void)registerNetworkChangeNotification;
+ (void)unregisterNetworkChangeNotification; + (void)unregisterNetworkChangeNotification;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -22,7 +22,7 @@
#import "DrawImage.h" #import "DrawImage.h"
#import "Constants.h" #import "Constants.h"
#import "UserPrefs.h" #import "NSColor+Ext.h"
@implementation DrawSeparator @implementation DrawSeparator
@@ -312,15 +312,11 @@ static void Register(CGFloat size, NSImageName name, NSString *description, BOOL
/// Register all icons that require custom drawing in @c ImageNamed cache /// Register all icons that require custom drawing in @c ImageNamed cache
void RegisterImageViewNames(void) { void RegisterImageViewNames(void) {
NSColor *orange = [NSColor colorWithCalibratedRed:251/255.f green:163/255.f blue:58/255.f alpha:1.f]; // #FBA33A Register(16, RSSImageDefaultRSSIcon, NSLocalizedString(@"RSS icon", nil), ^(NSRect r) { DrawRSSGradientIcon(r, [NSColor rssOrange]); return YES; });
NSColor *c1 = UserPrefsColor(Pref_colorStatusIconTint, orange);
NSColor *c2 = UserPrefsColor(Pref_colorUnreadIndicator, [NSColor systemBlueColor]);
Register(16, RSSImageDefaultRSSIcon, NSLocalizedString(@"RSS icon", nil), ^(NSRect r) { DrawRSSGradientIcon(r, orange); return YES; });
Register(16, RSSImageSettingsGlobal, NSLocalizedString(@"Global settings", nil), ^(NSRect r) { DrawGlobalIcon(r, [NSColor controlTextColor].CGColor, NO); return YES; }); Register(16, RSSImageSettingsGlobal, NSLocalizedString(@"Global settings", nil), ^(NSRect r) { DrawGlobalIcon(r, [NSColor controlTextColor].CGColor, NO); return YES; });
Register(16, RSSImageSettingsGroup, NSLocalizedString(@"Group settings", nil), ^(NSRect r) { DrawGroupIcon(r, [NSColor controlTextColor].CGColor, NO); return YES; }); Register(16, RSSImageSettingsGroup, NSLocalizedString(@"Group settings", nil), ^(NSRect r) { DrawGroupIcon(r, [NSColor controlTextColor].CGColor, NO); return YES; });
Register(16, RSSImageSettingsFeed, NSLocalizedString(@"Feed settings", nil), ^(NSRect r) { DrawRSSIcon(r, [NSColor controlTextColor].CGColor, NO, YES); return YES; }); Register(16, RSSImageSettingsFeed, NSLocalizedString(@"Feed settings", nil), ^(NSRect r) { DrawRSSIcon(r, [NSColor controlTextColor].CGColor, NO, YES); return YES; });
Register(16, RSSImageMenuBarIconActive, NSLocalizedString(@"RSS menu bar icon", nil), ^(NSRect r) { DrawRSSIcon(r, c1.CGColor, YES, YES); return YES; }); Register(16, RSSImageMenuBarIconActive, NSLocalizedString(@"RSS menu bar icon", nil), ^(NSRect r) { DrawRSSIcon(r, [NSColor menuBarIconColor].CGColor, YES, YES); return YES; });
Register(16, RSSImageMenuBarIconPaused, NSLocalizedString(@"RSS menu bar icon, paused", nil), ^(NSRect r) { DrawRSSIcon(r, c1.CGColor, YES, NO); return YES; }); Register(16, RSSImageMenuBarIconPaused, NSLocalizedString(@"RSS menu bar icon, paused", nil), ^(NSRect r) { DrawRSSIcon(r, [NSColor menuBarIconColor].CGColor, YES, NO); return YES; });
Register(14, RSSImageMenuItemUnread, NSLocalizedString(@"Unread icon", nil), ^(NSRect r) { DrawUnreadIcon(r, c2); return YES; }); Register(14, RSSImageMenuItemUnread, NSLocalizedString(@"Unread icon", nil), ^(NSRect r) { DrawUnreadIcon(r, [NSColor unreadIndicatorColor]); return YES; });
} }

View File

@@ -45,7 +45,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0.1</string> <string>1.1.2</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
<array> <array>
<dict> <dict>
@@ -70,7 +70,7 @@
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>14405</string> <string>14633</string>
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>
<string>public.app-category.news</string> <string>public.app-category.news</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>

View File

@@ -0,0 +1,36 @@
//
// The MIT License (MIT)
// Copyright (c) 2020 Oleg Geier
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
@import Cocoa;
NS_ASSUME_NONNULL_BEGIN
@interface NSColor (Ext)
/** @return @c RGB(251,163,58) @c (#FBA33A) */
+ (instancetype)rssOrange;
/** @return User preferred color; default: @c rssOrange */
+ (instancetype)menuBarIconColor;
/** @return User preferred color; default: @c systemBlueColor */
+ (instancetype)unreadIndicatorColor;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,63 @@
//
// The MIT License (MIT)
// Copyright (c) 2020 Oleg Geier
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#import "NSColor+Ext.h"
#import "UserPrefs.h"
@implementation NSColor (Ext)
+ (instancetype)rssOrange {
static NSColor *color;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
color = [NSColor colorWithCalibratedRed:251/255.f green:163/255.f blue:58/255.f alpha:1.f]; // #FBA33A
});
return color;
}
+ (instancetype)menuBarIconColor {
static NSColor *color;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (@available(macOS 10.14, *)) {
color = UserPrefsColor(Pref_colorStatusIconTint, [NSColor controlAccentColor]);
} else {
color = UserPrefsColor(Pref_colorStatusIconTint, [self rssOrange]);
}
});
return color;
}
+ (instancetype)unreadIndicatorColor {
static NSColor *color;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (@available(macOS 10.14, *)) {
color = UserPrefsColor(Pref_colorUnreadIndicator, [NSColor controlAccentColor]);
} else {
color = UserPrefsColor(Pref_colorUnreadIndicator, [NSColor systemBlueColor]);
}
});
return color;
}
@end

View File

@@ -32,6 +32,8 @@ typedef NS_ENUM(int32_t, TimeUnitType) {
TimeUnitYears = 365 * 24 * 60 * 60 TimeUnitYears = 365 * 24 * 60 * 60
}; };
NS_ASSUME_NONNULL_BEGIN
@interface NSDate (Ext) @interface NSDate (Ext)
+ (NSString*)timeStringISO8601; + (NSString*)timeStringISO8601;
+ (NSString*)dayStringISO8601; + (NSString*)dayStringISO8601;
@@ -57,3 +59,5 @@ typedef NS_ENUM(int32_t, TimeUnitType) {
@interface NSDate (Statistics) @interface NSDate (Statistics)
+ (NSDictionary*)refreshIntervalStatistics:(NSArray<NSDate*> *)list; + (NSDictionary*)refreshIntervalStatistics:(NSArray<NSDate*> *)list;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -25,6 +25,8 @@
/// Log error message and prepend calling class and calling method. /// Log error message and prepend calling class and calling method.
#define NSLogCaller(desc) { NSLog(@"%@:%@ %@", [self class], NSStringFromSelector(_cmd), desc); } #define NSLogCaller(desc) { NSLog(@"%@:%@ %@", [self class], NSStringFromSelector(_cmd), desc); }
NS_ASSUME_NONNULL_BEGIN
@interface NSError (Ext) @interface NSError (Ext)
// Generators // Generators
+ (instancetype)statusCode:(NSInteger)code reason:(nullable NSString*)reason; + (instancetype)statusCode:(NSInteger)code reason:(nullable NSString*)reason;
@@ -35,3 +37,5 @@
- (BOOL)inCaseLog:(nullable const char*)title; - (BOOL)inCaseLog:(nullable const char*)title;
- (BOOL)inCasePresent:(NSApplication*)app; - (BOOL)inCasePresent:(NSApplication*)app;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -22,6 +22,8 @@
@import Cocoa; @import Cocoa;
NS_ASSUME_NONNULL_BEGIN
@interface NSString (PlainHTML) @interface NSString (PlainHTML)
+ (NSString*)plainTextFromHTMLData:(NSData*)data; + (NSString*)plainTextFromHTMLData:(NSData*)data;
- (nonnull NSString*)htmlToPlainText; - (nonnull NSString*)htmlToPlainText;
@@ -30,3 +32,5 @@
@interface NSString (HexColor) @interface NSString (HexColor)
- (nullable NSColor*)hexColor; - (nullable NSColor*)hexColor;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -24,6 +24,8 @@
#define ENV_LOG_FILES 0 #define ENV_LOG_FILES 0
NS_ASSUME_NONNULL_BEGIN
@interface NSURL (Ext) @interface NSURL (Ext)
// Generators // Generators
+ (NSURL*)applicationSupportURL; + (NSURL*)applicationSupportURL;
@@ -38,3 +40,5 @@
- (void)remove; - (void)remove;
- (void)moveTo:(NSURL*)destination; - (void)moveTo:(NSURL*)destination;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -24,8 +24,12 @@
#define ENV_LOG_DOWNLOAD 1 #define ENV_LOG_DOWNLOAD 1
NS_ASSUME_NONNULL_BEGIN
@interface NSURLRequest (Ext) @interface NSURLRequest (Ext)
+ (instancetype)withURL:(NSString*)urlStr; + (instancetype)withURL:(NSString*)urlStr;
- (NSURLSessionDataTask*)dataTask:(nonnull void(^)(NSData * _Nullable data, NSError * _Nullable error, NSHTTPURLResponse *response))block; - (NSURLSessionDataTask*)dataTask:(nonnull void(^)(NSData * _Nullable data, NSError * _Nullable error, NSHTTPURLResponse *response))block;
- (NSURLSessionDownloadTask*)downloadTask:(void(^)(NSURL * _Nullable path, NSError * _Nullable error))block; - (NSURLSessionDownloadTask*)downloadTask:(void(^)(NSURL * _Nullable path, NSError * _Nullable error))block;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -40,6 +40,8 @@
/// Static variable to calculate origin center coordinate in its @c superview. The value of this var isn't used. /// Static variable to calculate origin center coordinate in its @c superview. The value of this var isn't used.
static CGFloat const CENTER = -0.015625; static CGFloat const CENTER = -0.015625;
NS_ASSUME_NONNULL_BEGIN
/// Calculate @c origin.y going down from the top border of its @c superview /// Calculate @c origin.y going down from the top border of its @c superview
static inline CGFloat YFromTop(NSView *view) { return NSHeight(view.superview.frame) - NSMinY(view.frame) - view.alignmentRectInsets.bottom; } static inline CGFloat YFromTop(NSView *view) { return NSHeight(view.superview.frame) - NSMinY(view.frame) - view.alignmentRectInsets.bottom; }
/// @c MAX() /// @c MAX()
@@ -65,7 +67,7 @@ static inline CGFloat NSMaxWidth(NSView *a, NSView *b) { return Max(NSWidth(a.fr
+ (NSButton*)inlineButton:(NSString*)text; + (NSButton*)inlineButton:(NSString*)text;
+ (NSPopUpButton*)popupButton:(CGFloat)w; + (NSPopUpButton*)popupButton:(CGFloat)w;
// UI: Others // UI: Others
+ (NSImageView*)imageView:(NSImageName)name size:(CGFloat)size; + (NSImageView*)imageView:(nullable NSImageName)name size:(CGFloat)size;
+ (NSButton*)checkbox:(BOOL)flag; + (NSButton*)checkbox:(BOOL)flag;
+ (NSProgressIndicator*)activitySpinner; + (NSProgressIndicator*)activitySpinner;
+ (NSView*)radioGroup:(NSArray<NSString*>*)entries target:(id)target action:(nonnull SEL)action; + (NSView*)radioGroup:(NSArray<NSString*>*)entries target:(id)target action:(nonnull SEL)action;
@@ -91,7 +93,7 @@ static inline CGFloat NSMaxWidth(NSView *a, NSView *b) { return Max(NSWidth(a.fr
@interface NSControl (Ext) @interface NSControl (Ext)
- (instancetype)action:(SEL)selector target:(id)target; - (instancetype)action:(SEL)selector target:(nullable id)target;
- (instancetype)large; - (instancetype)large;
- (instancetype)small; - (instancetype)small;
- (instancetype)tiny; - (instancetype)tiny;
@@ -106,3 +108,5 @@ static inline CGFloat NSMaxWidth(NSView *a, NSView *b) { return Max(NSWidth(a.fr
- (instancetype)selectable; - (instancetype)selectable;
- (instancetype)multiline:(NSSize)size; - (instancetype)multiline:(NSSize)size;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -122,7 +122,7 @@
/// Create @c ImageView with square @c size /// Create @c ImageView with square @c size
+ (NSImageView*)imageView:(NSImageName)name size:(CGFloat)size { + (NSImageView*)imageView:(nullable NSImageName)name size:(CGFloat)size {
NSImageView *imgView = [[NSImageView alloc] initWithFrame: NSMakeRect(0, 0, size, size)]; NSImageView *imgView = [[NSImageView alloc] initWithFrame: NSMakeRect(0, 0, size, size)];
if (name) imgView.image = [NSImage imageNamed:name]; if (name) imgView.image = [NSImage imageNamed:name];
return imgView; return imgView;
@@ -328,7 +328,7 @@ static inline void SetFrameWidth(NSView *view, CGFloat w) {
@implementation NSControl (Ext) @implementation NSControl (Ext)
/// Set @c target and @c action simultaneously /// Set @c target and @c action simultaneously
- (instancetype)action:(SEL)selector target:(id)target { - (instancetype)action:(SEL)selector target:(nullable id)target {
self.action = selector; self.action = selector;
self.target = target; self.target = target;
return self; return self;

View File

@@ -74,7 +74,8 @@
/// Helper method to insert attributed (bold) text /// Helper method to insert attributed (bold) text
- (void)str:(NSMutableAttributedString*)parent add:(NSString*)text bold:(BOOL)flag { - (void)str:(NSMutableAttributedString*)parent add:(NSString*)text bold:(BOOL)flag {
NSFont *font = [NSFont systemFontOfSize:NSFont.systemFontSize weight:(flag ? NSFontWeightMedium : NSFontWeightLight)]; NSFont *font = [NSFont systemFontOfSize:NSFont.systemFontSize weight:(flag ? NSFontWeightMedium : NSFontWeightLight)];
[parent appendAttributedString:[[NSAttributedString alloc] initWithString:NonLocalized(text) attributes:@{ NSFontAttributeName : font }]]; NSDictionary *style = @{ NSFontAttributeName: font, NSForegroundColorAttributeName: [NSColor controlTextColor] };
[parent appendAttributedString:[[NSAttributedString alloc] initWithString:NonLocalized(text) attributes:style]];
} }
/// Helper method to insert attributed hyperlink text /// Helper method to insert attributed hyperlink text

View File

@@ -22,6 +22,10 @@
@import Cocoa; @import Cocoa;
NS_ASSUME_NONNULL_BEGIN
@interface SettingsAppearance : NSViewController @interface SettingsAppearance : NSViewController
- (void)didSelectCheckbox:(NSButton*)sender; - (void)didSelectCheckbox:(NSButton*)sender;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -24,6 +24,8 @@
#import "ModalSheet.h" #import "ModalSheet.h"
@class FeedGroup, ModalSheet; @class FeedGroup, ModalSheet;
NS_ASSUME_NONNULL_BEGIN
@interface ModalEditDialog : NSViewController @interface ModalEditDialog : NSViewController
+ (instancetype)modalWith:(FeedGroup*)group; + (instancetype)modalWith:(FeedGroup*)group;
- (ModalSheet*)getModalSheet; - (ModalSheet*)getModalSheet;
@@ -38,3 +40,4 @@
@interface ModalGroupEdit : ModalEditDialog @interface ModalGroupEdit : ModalEditDialog
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -216,7 +216,13 @@
*/ */
- (void)faviconDownload:(FaviconDownload*)sender didFinish:(nullable NSURL*)path { - (void)faviconDownload:(FaviconDownload*)sender didFinish:(nullable NSURL*)path {
// Create image from favicon temporary file location or default icon if no favicon exists. // Create image from favicon temporary file location or default icon if no favicon exists.
NSImage *img = path ? [[NSImage alloc] initByReferencingURL:path] : [NSImage imageNamed:RSSImageDefaultRSSIcon]; NSImage *img;
if (path) {
NSData* data = [[NSData alloc] initWithContentsOfURL:path];
img = [[NSImage alloc] initWithData:data];
} else {
img = [NSImage imageNamed:RSSImageDefaultRSSIcon];
}
self.view.favicon.image = img; self.view.favicon.image = img;
self.faviconFile = path; self.faviconFile = path;
[self downloadComplete]; [self downloadComplete];

View File

@@ -23,23 +23,27 @@
@import Cocoa; @import Cocoa;
@class ModalFeedEdit; @class ModalFeedEdit;
NS_ASSUME_NONNULL_BEGIN
@interface ModalFeedEditView : NSView @interface ModalFeedEditView : NSView
@property (weak) IBOutlet NSTextField *url; @property (strong) IBOutlet NSTextField *url;
@property (weak) IBOutlet NSProgressIndicator *spinnerURL; @property (strong) IBOutlet NSProgressIndicator *spinnerURL;
@property (weak) IBOutlet NSImageView *favicon; @property (strong) IBOutlet NSImageView *favicon;
@property (weak) IBOutlet NSTextField *name; @property (strong) IBOutlet NSTextField *name;
@property (weak) IBOutlet NSProgressIndicator *spinnerName; @property (strong) IBOutlet NSProgressIndicator *spinnerName;
@property (weak) IBOutlet NSTextField *refreshNum; @property (strong) IBOutlet NSTextField *refreshNum;
@property (weak) IBOutlet NSPopUpButton *refreshUnit; @property (strong) IBOutlet NSPopUpButton *refreshUnit;
@property (weak) IBOutlet NSButton *warningButton; @property (strong) IBOutlet NSButton *warningButton;
@property NSPopover *warningPopover; @property NSPopover *warningPopover;
@property (weak) IBOutlet NSTextField *warningText; @property (strong) IBOutlet NSTextField *warningText;
@property (weak) IBOutlet NSButton *warningReload; @property (strong) IBOutlet NSButton *warningReload;
- (instancetype)initWithController:(ModalFeedEdit*)controller NS_DESIGNATED_INITIALIZER; - (instancetype)initWithController:(ModalFeedEdit*)controller NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithFrame:(NSRect)frameRect NS_UNAVAILABLE; - (instancetype)initWithFrame:(NSRect)frameRect NS_UNAVAILABLE;
- (nullable instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE; - (nullable instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -22,6 +22,8 @@
@import Cocoa; @import Cocoa;
NS_ASSUME_NONNULL_BEGIN
@protocol RefreshIntervalButtonDelegate <NSObject> @protocol RefreshIntervalButtonDelegate <NSObject>
@required @required
/// @c sender.tag is refresh interval in seconds /// @c sender.tag is refresh interval in seconds
@@ -34,3 +36,5 @@
- (instancetype)initWithFrame:(NSRect)frameRect NS_UNAVAILABLE; - (instancetype)initWithFrame:(NSRect)frameRect NS_UNAVAILABLE;
- (nullable instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE; - (nullable instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -23,7 +23,11 @@
#import "SettingsFeeds.h" #import "SettingsFeeds.h"
#import "OpmlFile.h" #import "OpmlFile.h"
NS_ASSUME_NONNULL_BEGIN
@interface SettingsFeeds (DragDrop) <NSOutlineViewDataSource, NSFilePromiseProviderDelegate, NSPasteboardTypeOwner, OpmlFileImportDelegate, OpmlFileExportDelegate> @interface SettingsFeeds (DragDrop) <NSOutlineViewDataSource, NSFilePromiseProviderDelegate, NSPasteboardTypeOwner, OpmlFileImportDelegate, OpmlFileExportDelegate>
- (void)prepareOutlineViewForDragDrop:(NSOutlineView*)outline; - (void)prepareOutlineViewForDragDrop:(NSOutlineView*)outline;
- (void)importOpmlFiles:(NSArray<NSURL*>*)files; - (void)importOpmlFiles:(NSArray<NSURL*>*)files;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -22,10 +22,12 @@
@import Cocoa; @import Cocoa;
NS_ASSUME_NONNULL_BEGIN
/** Manages the NSOutlineView and Feed creation and editing */ /** Manages the NSOutlineView and Feed creation and editing */
@interface SettingsFeeds : NSViewController <NSOutlineViewDelegate> @interface SettingsFeeds : NSViewController <NSOutlineViewDelegate>
@property (strong) NSTreeController *dataStore; @property (strong) NSTreeController *dataStore;
@property (strong) NSArray<NSTreeNode*> *currentlyDraggedNodes; @property (strong, nullable) NSArray<NSTreeNode*> *currentlyDraggedNodes;
- (void)editSelectedItem; - (void)editSelectedItem;
- (void)doubleClickOutlineView:(NSOutlineView*)sender; - (void)doubleClickOutlineView:(NSOutlineView*)sender;
@@ -40,3 +42,5 @@
- (BOOL)endCoreDataChangeUndoEmpty:(BOOL)undoEmpty forceUndo:(BOOL)force; - (BOOL)endCoreDataChangeUndoEmpty:(BOOL)undoEmpty forceUndo:(BOOL)force;
- (void)restoreOrderingAndIndexPathStr:(NSArray<NSTreeNode*>*)parentsList; - (void)restoreOrderingAndIndexPathStr:(NSArray<NSTreeNode*>*)parentsList;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -23,10 +23,12 @@
@import Cocoa; @import Cocoa;
@class SettingsFeeds; @class SettingsFeeds;
NS_ASSUME_NONNULL_BEGIN
@interface SettingsFeedsView : NSView @interface SettingsFeedsView : NSView
@property (weak) IBOutlet NSOutlineView *outline; @property (strong) IBOutlet NSOutlineView *outline;
@property (weak) IBOutlet NSTextField *status; @property (strong) IBOutlet NSTextField *status;
@property (weak) IBOutlet NSProgressIndicator *spinner; @property (strong) IBOutlet NSProgressIndicator *spinner;
- (instancetype)initWithController:(SettingsFeeds*)delegate NS_DESIGNATED_INITIALIZER; - (instancetype)initWithController:(SettingsFeeds*)delegate NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithFrame:(NSRect)frameRect NS_UNAVAILABLE; - (instancetype)initWithFrame:(NSRect)frameRect NS_UNAVAILABLE;
@@ -45,3 +47,5 @@ extern NSUserInterfaceItemIdentifier const CustomCellRefresh;
@interface SeparatorColumnCell : NSTableCellView @interface SeparatorColumnCell : NSTableCellView
extern NSUserInterfaceItemIdentifier const CustomCellSeparator; extern NSUserInterfaceItemIdentifier const CustomCellSeparator;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -22,7 +22,11 @@
@import Cocoa; @import Cocoa;
NS_ASSUME_NONNULL_BEGIN
@interface SettingsGeneral : NSViewController @interface SettingsGeneral : NSViewController
- (void)changeHttpApplication:(NSPopUpButton *)sender; - (void)changeHttpApplication:(NSPopUpButton *)sender;
- (void)clickHowToDefaults:(NSButton *)sender; - (void)clickHowToDefaults:(NSButton *)sender;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -23,12 +23,15 @@
@import Cocoa; @import Cocoa;
@class SettingsGeneral; @class SettingsGeneral;
NS_ASSUME_NONNULL_BEGIN
@interface SettingsGeneralView : NSView @interface SettingsGeneralView : NSView
@property (weak) IBOutlet NSPopUpButton* popupHttpApplication; @property (strong) IBOutlet NSPopUpButton* popupHttpApplication;
@property (weak) IBOutlet NSTextField *defaultReader; @property (strong) IBOutlet NSTextField *defaultReader;
- (instancetype)initWithController:(SettingsGeneral*)controller NS_DESIGNATED_INITIALIZER; - (instancetype)initWithController:(SettingsGeneral*)controller NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithFrame:(NSRect)frameRect NS_UNAVAILABLE; - (instancetype)initWithFrame:(NSRect)frameRect NS_UNAVAILABLE;
- (nullable instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE; - (nullable instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -22,6 +22,8 @@
@import Cocoa; @import Cocoa;
NS_ASSUME_NONNULL_BEGIN
@interface ModalSheet : NSPanel @interface ModalSheet : NSPanel
@property (readonly) BOOL didTapCancel; @property (readonly) BOOL didTapCancel;
@@ -31,3 +33,5 @@
- (void)setDoneEnabled:(BOOL)accept; - (void)setDoneEnabled:(BOOL)accept;
- (void)extendContentViewBy:(CGFloat)dy; - (void)extendContentViewBy:(CGFloat)dy;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -44,7 +44,7 @@
[content setFrameSize: NSMakeSize(w, h)]; [content setFrameSize: NSMakeSize(w, h)];
// after content size, increase to window size // after content size, increase to window size
w += 2 * PAD_WIN; w += 2 * (NSInteger)PAD_WIN;
h += PAD_WIN + contentOffsetY; // the second PAD_WIN is already in contentOffsetY h += PAD_WIN + contentOffsetY; // the second PAD_WIN is already in contentOffsetY
NSWindowStyleMask style = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskFullSizeContentView; NSWindowStyleMask style = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskFullSizeContentView;

View File

@@ -23,7 +23,11 @@
@import Cocoa; @import Cocoa;
@class SettingsFeeds; @class SettingsFeeds;
NS_ASSUME_NONNULL_BEGIN
@interface Preferences : NSWindow <NSWindowDelegate> @interface Preferences : NSWindow <NSWindowDelegate>
+ (instancetype)window; + (instancetype)window;
- (__kindof NSViewController*)selectTab:(NSUInteger)index; - (__kindof NSViewController*)selectTab:(NSUInteger)index;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -23,7 +23,11 @@
@import Cocoa; @import Cocoa;
@class BarStatusItem; @class BarStatusItem;
NS_ASSUME_NONNULL_BEGIN
@interface BarMenu : NSObject <NSMenuDelegate> @interface BarMenu : NSObject <NSMenuDelegate>
- (instancetype)init NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithStatusItem:(BarStatusItem*)statusItem NS_DESIGNATED_INITIALIZER; - (instancetype)initWithStatusItem:(BarStatusItem*)statusItem NS_DESIGNATED_INITIALIZER;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -82,11 +82,6 @@
} }
} }
/// Get rid of everything that is not needed.
- (void)menuDidClose:(NSMenu*)menu {
[menu cleanup];
}
/// Generate items for @c FeedGroup menu. /// Generate items for @c FeedGroup menu.
- (void)setFeedGroups:(NSArray<FeedGroup*>*)sortedList forMenu:(NSMenu*)menu { - (void)setFeedGroups:(NSArray<FeedGroup*>*)sortedList forMenu:(NSMenu*)menu {
[menu insertDefaultHeader]; [menu insertDefaultHeader];
@@ -125,17 +120,21 @@
Fetch @c Feed from core data and find deepest visible @c NSMenuItem. Fetch @c Feed from core data and find deepest visible @c NSMenuItem.
@warning @c item and @c feed will often mismatch. @warning @c item and @c feed will often mismatch.
*/ */
- (void)updateFeedMenuItem:(NSManagedObjectID*)oid withBlock:(void(^)(Feed *feed, NSMenuItem *item))block { - (BOOL)findDeepest:(NSManagedObjectID*)oid feed:(Feed*__autoreleasing*)feed menuItem:(NSMenuItem*__autoreleasing*)item {
Feed *feed = [[StoreCoordinator getMainContext] objectWithID:oid]; Feed *f = [[StoreCoordinator getMainContext] objectWithID:oid];
if ([feed isKindOfClass:[Feed class]]) { if (![f isKindOfClass:[Feed class]]) return NO;
NSMenuItem *item = [self.statusItem.mainMenu deepestItemWithPath:feed.indexPath]; NSMenuItem *mi = [self.statusItem.mainMenu deepestItemWithPath:f.indexPath];
if (item) block(feed, item); if (!mi) return NO;
} *feed = f;
*item = mi;
return YES;
} }
/// Callback method fired when feed has been updated in the background. /// Callback method fired when feed has been updated in the background.
- (void)articlesUpdated:(NSNotification*)notify { - (void)articlesUpdated:(NSNotification*)notify {
[self updateFeedMenuItem:notify.object withBlock:^(Feed *feed, NSMenuItem *item) { Feed *feed;
NSMenuItem *item;
if ([self findDeepest:notify.object feed:&feed menuItem:&item]) {
// 1. update in-memory unread count // 1. update in-memory unread count
UnreadTotal *updated = [UnreadTotal new]; UnreadTotal *updated = [UnreadTotal new];
updated.total = feed.articles.count; updated.total = feed.articles.count;
@@ -160,15 +159,17 @@
[item setTitleCount:uct.unread]; [item setTitleCount:uct.unread];
item = item.parentItem; item = item.parentItem;
} }
}]; }
} }
/// Callback method fired when feed icon has changed. /// Callback method fired when feed icon has changed.
- (void)feedIconUpdated:(NSNotification*)notify { - (void)feedIconUpdated:(NSNotification*)notify {
[self updateFeedMenuItem:notify.object withBlock:^(Feed *feed, NSMenuItem *item) { Feed *feed;
NSMenuItem *item;
if ([self findDeepest:notify.object feed:&feed menuItem:&item]) {
if (item.submenu.isFeedMenu) if (item.submenu.isFeedMenu)
item.image = [feed iconImage16]; item.image = [feed iconImage16];
}]; }
} }
@end @end

View File

@@ -22,6 +22,8 @@
@import Cocoa; @import Cocoa;
NS_ASSUME_NONNULL_BEGIN
@interface BarStatusItem : NSObject @interface BarStatusItem : NSObject
@property (weak, readonly) NSMenu *mainMenu; @property (weak, readonly) NSMenu *mainMenu;
@@ -32,3 +34,4 @@
- (void)showWelcomeMessage; - (void)showWelcomeMessage;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -28,6 +28,7 @@
#import "BarMenu.h" #import "BarMenu.h"
#import "AppHook.h" #import "AppHook.h"
#import "NSView+Ext.h" #import "NSView+Ext.h"
#import "NSColor+Ext.h"
@interface BarStatusItem() @interface BarStatusItem()
@property (strong) BarMenu *barMenu; @property (strong) BarMenu *barMenu;
@@ -119,8 +120,22 @@
BOOL hasNet = [UpdateScheduler allowNetworkConnection]; BOOL hasNet = [UpdateScheduler allowNetworkConnection];
BOOL tint = (self.unreadCountTotal > 0 && hasNet && UserPrefsBool(Pref_globalTintMenuIcon)); BOOL tint = (self.unreadCountTotal > 0 && hasNet && UserPrefsBool(Pref_globalTintMenuIcon));
self.statusItem.button.image = [NSImage imageNamed:(hasNet ? RSSImageMenuBarIconActive : RSSImageMenuBarIconPaused)]; self.statusItem.button.image = [NSImage imageNamed:(hasNet ? RSSImageMenuBarIconActive : RSSImageMenuBarIconPaused)];
self.statusItem.button.image.template = !tint;
// TODO: use macOS 10.14 contentTintColor, if (@available(macOS 10.14, *)) {} else {} if (@available(macOS 11, *)) {
self.statusItem.button.image.template = !tint;
} else if (@available(macOS 10.14, *)) {
// There is no proper way to display tinted icon WITHOUT tinted text!
// - using alternate image instead of tint:
// icon & text stays black on highlight (but only in light mode)
// - using tint and attributed titles:
// with controlTextColor the tint is applied regardless
// with controlColor the color doesnt match (either normal or on highlight)
// also, setting attributed title kills tint on icon
self.statusItem.button.image.template = YES;
self.statusItem.button.contentTintColor = tint ? [NSColor menuBarIconColor] : nil;
} else {
self.statusItem.button.image.template = !tint;
}
BOOL showCount = (self.unreadCountTotal > 0 && UserPrefsBool(Pref_globalUnreadCount)); BOOL showCount = (self.unreadCountTotal > 0 && UserPrefsBool(Pref_globalUnreadCount));
self.statusItem.button.title = (showCount ? [NSString stringWithFormat:@"%ld", self.unreadCountTotal] : @""); self.statusItem.button.title = (showCount ? [NSString stringWithFormat:@"%ld", self.unreadCountTotal] : @"");

View File

@@ -22,6 +22,8 @@
@import Cocoa; @import Cocoa;
NS_ASSUME_NONNULL_BEGIN
@interface UnreadTotal : NSObject @interface UnreadTotal : NSObject
@property (nonatomic, assign) NSUInteger unread; @property (nonatomic, assign) NSUInteger unread;
@property (nonatomic, assign) NSUInteger total; @property (nonatomic, assign) NSUInteger total;
@@ -39,3 +41,5 @@
- (UnreadTotal*)objectForKeyedSubscript:(NSString*)key; - (UnreadTotal*)objectForKeyedSubscript:(NSString*)key;
- (void)setObject:(UnreadTotal*)obj forKeyedSubscript:(NSString*)key; - (void)setObject:(UnreadTotal*)obj forKeyedSubscript:(NSString*)key;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -23,6 +23,8 @@
@import Cocoa; @import Cocoa;
@class FeedGroup; @class FeedGroup;
NS_ASSUME_NONNULL_BEGIN
@interface NSMenu (Ext) @interface NSMenu (Ext)
@property (nonnull, copy, readonly) NSString *titleIndexPath; @property (nonnull, copy, readonly) NSString *titleIndexPath;
@property (nullable, readonly) NSMenuItem* parentItem; @property (nullable, readonly) NSMenuItem* parentItem;
@@ -33,7 +35,6 @@
- (NSMenuItem*)insertFeedGroupItem:(FeedGroup*)fg; - (NSMenuItem*)insertFeedGroupItem:(FeedGroup*)fg;
- (void)insertDefaultHeader; - (void)insertDefaultHeader;
// Update menu // Update menu
- (void)cleanup;
- (void)setHeaderHasUnread:(BOOL)hasUnread hasRead:(BOOL)hasRead; - (void)setHeaderHasUnread:(BOOL)hasUnread hasRead:(BOOL)hasRead;
- (NSMenuItem*)deepestItemWithPath:(nonnull NSString*)path; - (NSMenuItem*)deepestItemWithPath:(nonnull NSString*)path;
@end @end
@@ -43,3 +44,5 @@
- (instancetype)alternateWithTitle:(NSString*)title; - (instancetype)alternateWithTitle:(NSString*)title;
- (void)setTitleCount:(NSUInteger)count; - (void)setTitleCount:(NSUInteger)count;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -104,12 +104,6 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
#pragma mark - Update Menu #pragma mark - Update Menu
/// Replace this menu with a clean @c NSMenu. Copy old @c title and @c delegate to new menu. @b Won't work without supermenu!
- (void)cleanup {
NSMenu *m = [[NSMenu alloc] initWithTitle:self.title];
m.delegate = self.delegate;
self.parentItem.submenu = m;
}
/// Loop over default header and enable 'OpenAllUnread' and 'TagMarkAllRead' based on unread count. /// Loop over default header and enable 'OpenAllUnread' and 'TagMarkAllRead' based on unread count.
- (void)setHeaderHasUnread:(BOOL)hasUnread hasRead:(BOOL)hasRead { - (void)setHeaderHasUnread:(BOOL)hasUnread hasRead:(BOOL)hasRead {