mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-02-02 03:56:55 +01:00
Merge branch 'ios-release'
This commit is contained in:
commit
532db0b640
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
matrix:
|
||||
run-config:
|
||||
- { scheme: 'NetNewsWire', destination: 'platform=macOS'}
|
||||
- { scheme: 'NetNewsWire-iOS', destination: 'platform=iOS Simulator,OS=13.0,name=iPhone 11' }
|
||||
- { scheme: 'NetNewsWire-iOS', destination: 'platform=iOS Simulator,OS=13.4,name=iPhone 11' }
|
||||
|
||||
steps:
|
||||
- name: Checkout Project
|
||||
@ -25,8 +25,11 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: List Available Applications
|
||||
run: ls /Applications
|
||||
|
||||
- name: Switch to Xcode 11
|
||||
run: sudo xcode-select -s /Applications/Xcode_11.app
|
||||
run: sudo xcode-select -s /Applications/Xcode_11.4.app
|
||||
|
||||
- name: Show Build Version
|
||||
run: xcodebuild -version
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16086"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
@ -57,7 +57,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Email" textAlignment="natural" adjustsFontForContentSizeCategory="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="o06-fe-i3S">
|
||||
<rect key="frame" x="20" y="11.5" width="334" height="21"/>
|
||||
<rect key="frame" x="20" y="11" width="334" height="22"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<textInputTraits key="textInputTraits" spellCheckingType="no" keyboardType="emailAddress" textContentType="username"/>
|
||||
</textField>
|
||||
@ -206,7 +206,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Name" textAlignment="natural" adjustsFontForContentSizeCategory="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="Yl1-R6-xZi">
|
||||
<rect key="frame" x="20" y="11.5" width="334" height="21"/>
|
||||
<rect key="frame" x="20" y="11" width="334" height="22"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="words"/>
|
||||
</textField>
|
||||
@ -291,7 +291,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Email" textAlignment="natural" adjustsFontForContentSizeCategory="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="vJa-NN-yjR">
|
||||
<rect key="frame" x="20" y="11.5" width="334" height="21"/>
|
||||
<rect key="frame" x="20" y="11" width="334" height="22"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<textInputTraits key="textInputTraits" spellCheckingType="no" keyboardType="emailAddress" textContentType="username"/>
|
||||
</textField>
|
||||
@ -315,7 +315,7 @@
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<textInputTraits key="textInputTraits" secureTextEntry="YES" textContentType="password"/>
|
||||
</textField>
|
||||
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="TfW-wf-V06">
|
||||
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" pointerInteraction="YES" translatesAutoresizingMaskIntoConstraints="NO" id="TfW-wf-V06">
|
||||
<rect key="frame" x="311" y="5.5" width="43" height="33"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<state key="normal" title="Show"/>
|
||||
|
@ -12,7 +12,7 @@ import Account
|
||||
import RSTree
|
||||
|
||||
protocol MasterFeedTableViewCellDelegate: class {
|
||||
func disclosureSelected(_ sender: MasterFeedTableViewCell, expanding: Bool)
|
||||
func masterFeedTableViewCellDisclosureDidToggle(_ sender: MasterFeedTableViewCell, expanding: Bool)
|
||||
}
|
||||
|
||||
class MasterFeedTableViewCell : VibrantTableViewCell {
|
||||
@ -149,7 +149,7 @@ class MasterFeedTableViewCell : VibrantTableViewCell {
|
||||
@objc func buttonPressed(_ sender: UIButton) {
|
||||
if isDisclosureAvailable {
|
||||
setDisclosure(isExpanded: !isDisclosureExpanded, animated: true)
|
||||
delegate?.disclosureSelected(self, expanding: isDisclosureExpanded)
|
||||
delegate?.masterFeedTableViewCellDisclosureDidToggle(self, expanding: isDisclosureExpanded)
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,6 +181,9 @@ private extension MasterFeedTableViewCell {
|
||||
disclosureButton?.tintColor = AppAssets.controlBackgroundColor
|
||||
disclosureButton?.imageView?.contentMode = .center
|
||||
disclosureButton?.imageView?.clipsToBounds = false
|
||||
if #available(iOS 13.4, *) {
|
||||
disclosureButton?.addInteraction(UIPointerInteraction())
|
||||
}
|
||||
addSubviewAtInit(disclosureButton!)
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,14 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol MasterFeedTableViewSectionHeaderDelegate {
|
||||
func masterFeedTableViewSectionHeaderDisclosureDidToggle(_ sender: MasterFeedTableViewSectionHeader)
|
||||
}
|
||||
|
||||
class MasterFeedTableViewSectionHeader: UITableViewHeaderFooterView {
|
||||
|
||||
var delegate: MasterFeedTableViewSectionHeaderDelegate?
|
||||
|
||||
override var accessibilityLabel: String? {
|
||||
set {}
|
||||
get {
|
||||
@ -66,12 +72,16 @@ class MasterFeedTableViewSectionHeader: UITableViewHeaderFooterView {
|
||||
}()
|
||||
|
||||
private let unreadCountView = MasterFeedUnreadCountView(frame: CGRect.zero)
|
||||
private var disclosureView: UIImageView = {
|
||||
let iView = NonIntrinsicImageView()
|
||||
iView.tintColor = UIColor.tertiaryLabel
|
||||
iView.image = AppAssets.disclosureImage
|
||||
iView.contentMode = .center
|
||||
return iView
|
||||
private lazy var disclosureButton: UIButton = {
|
||||
let button = NonIntrinsicButton()
|
||||
button.tintColor = UIColor.tertiaryLabel
|
||||
button.setImage(AppAssets.disclosureImage, for: .normal)
|
||||
button.contentMode = .center
|
||||
if #available(iOS 13.4, *) {
|
||||
button.addInteraction(UIPointerInteraction())
|
||||
}
|
||||
button.addTarget(self, action: #selector(toggleDisclosure), for: .touchUpInside)
|
||||
return button
|
||||
}()
|
||||
|
||||
private let topSeparatorView: UIView = {
|
||||
@ -115,10 +125,14 @@ class MasterFeedTableViewSectionHeader: UITableViewHeaderFooterView {
|
||||
|
||||
private extension MasterFeedTableViewSectionHeader {
|
||||
|
||||
@objc func toggleDisclosure() {
|
||||
delegate?.masterFeedTableViewSectionHeaderDisclosureDidToggle(self)
|
||||
}
|
||||
|
||||
func commonInit() {
|
||||
addSubviewAtInit(unreadCountView)
|
||||
addSubviewAtInit(titleView)
|
||||
addSubviewAtInit(disclosureView)
|
||||
addSubviewAtInit(disclosureButton)
|
||||
updateExpandedState(animate: false)
|
||||
addBackgroundView()
|
||||
addSubviewAtInit(topSeparatorView)
|
||||
@ -136,9 +150,9 @@ private extension MasterFeedTableViewSectionHeader {
|
||||
withDuration: duration,
|
||||
animations: {
|
||||
if self.disclosureExpanded {
|
||||
self.disclosureView.transform = CGAffineTransform(rotationAngle: 1.570796)
|
||||
self.disclosureButton.transform = CGAffineTransform(rotationAngle: 1.570796)
|
||||
} else {
|
||||
self.disclosureView.transform = CGAffineTransform(rotationAngle: 0)
|
||||
self.disclosureButton.transform = CGAffineTransform(rotationAngle: 0)
|
||||
}
|
||||
}, completion: { _ in
|
||||
if !self.isLastSection && !self.disclosureExpanded {
|
||||
@ -167,7 +181,7 @@ private extension MasterFeedTableViewSectionHeader {
|
||||
func layoutWith(_ layout: MasterFeedTableViewSectionHeaderLayout) {
|
||||
titleView.setFrameIfNotEqual(layout.titleRect)
|
||||
unreadCountView.setFrameIfNotEqual(layout.unreadCountRect)
|
||||
disclosureView.setFrameIfNotEqual(layout.disclosureButtonRect)
|
||||
disclosureButton.setFrameIfNotEqual(layout.disclosureButtonRect)
|
||||
|
||||
let top = CGRect(x: safeAreaInsets.left, y: 0, width: frame.width - safeAreaInsets.right - safeAreaInsets.left, height: 0.33)
|
||||
topSeparatorView.setFrameIfNotEqual(top)
|
||||
|
@ -158,6 +158,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||
}
|
||||
|
||||
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! MasterFeedTableViewSectionHeader
|
||||
headerView.delegate = self
|
||||
headerView.name = nameProvider.nameForDisplay
|
||||
|
||||
guard let sectionNode = coordinator.rootNode.childAtIndex(section) else {
|
||||
@ -386,24 +387,10 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||
}
|
||||
|
||||
@objc func toggleSectionHeader(_ sender: UITapGestureRecognizer) {
|
||||
|
||||
guard let sectionIndex = sender.view?.tag,
|
||||
let sectionNode = coordinator.rootNode.childAtIndex(sectionIndex),
|
||||
let headerView = sender.view as? MasterFeedTableViewSectionHeader
|
||||
else {
|
||||
return
|
||||
guard let headerView = sender.view as? MasterFeedTableViewSectionHeader else {
|
||||
return
|
||||
}
|
||||
|
||||
if coordinator.isExpanded(sectionNode) {
|
||||
headerView.disclosureExpanded = false
|
||||
coordinator.collapse(sectionNode)
|
||||
self.applyChanges(animated: true)
|
||||
} else {
|
||||
headerView.disclosureExpanded = true
|
||||
coordinator.expand(sectionNode)
|
||||
self.applyChanges(animated: true)
|
||||
}
|
||||
|
||||
toggle(headerView)
|
||||
}
|
||||
|
||||
@objc func refreshAccounts(_ sender: Any) {
|
||||
@ -556,11 +543,21 @@ extension MasterFeedViewController: UIContextMenuInteractionDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: MasterFeedTableViewSectionHeaderDelegate
|
||||
|
||||
extension MasterFeedViewController: MasterFeedTableViewSectionHeaderDelegate {
|
||||
|
||||
func masterFeedTableViewSectionHeaderDisclosureDidToggle(_ sender: MasterFeedTableViewSectionHeader) {
|
||||
toggle(sender)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: MasterTableViewCellDelegate
|
||||
|
||||
extension MasterFeedViewController: MasterFeedTableViewCellDelegate {
|
||||
|
||||
func disclosureSelected(_ sender: MasterFeedTableViewCell, expanding: Bool) {
|
||||
func masterFeedTableViewCellDisclosureDidToggle(_ sender: MasterFeedTableViewCell, expanding: Bool) {
|
||||
if expanding {
|
||||
expand(sender)
|
||||
} else {
|
||||
@ -753,6 +750,22 @@ private extension MasterFeedViewController {
|
||||
return nil
|
||||
}
|
||||
|
||||
func toggle(_ headerView: MasterFeedTableViewSectionHeader) {
|
||||
guard let sectionNode = coordinator.rootNode.childAtIndex(headerView.tag) else {
|
||||
return
|
||||
}
|
||||
|
||||
if coordinator.isExpanded(sectionNode) {
|
||||
headerView.disclosureExpanded = false
|
||||
coordinator.collapse(sectionNode)
|
||||
self.applyChanges(animated: true)
|
||||
} else {
|
||||
headerView.disclosureExpanded = true
|
||||
coordinator.expand(sectionNode)
|
||||
self.applyChanges(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
func expand(_ cell: MasterFeedTableViewCell) {
|
||||
guard let indexPath = tableView.indexPath(for: cell), let node = dataSource.itemIdentifier(for: indexPath) else {
|
||||
return
|
||||
|
@ -13,5 +13,38 @@ class MasterTimelineTitleView: UIView {
|
||||
@IBOutlet weak var iconView: IconView!
|
||||
@IBOutlet weak var label: UILabel!
|
||||
@IBOutlet weak var unreadCountView: MasterTimelineUnreadCountView!
|
||||
|
||||
@available(iOS 13.4, *)
|
||||
private lazy var pointerInteraction: UIPointerInteraction = {
|
||||
UIPointerInteraction(delegate: self)
|
||||
}()
|
||||
|
||||
func buttonize() {
|
||||
heightAnchor.constraint(equalToConstant: 40.0).isActive = true
|
||||
accessibilityTraits = .button
|
||||
if #available(iOS 13.4, *) {
|
||||
addInteraction(pointerInteraction)
|
||||
}
|
||||
}
|
||||
|
||||
func debuttonize() {
|
||||
accessibilityTraits.remove(.button)
|
||||
if #available(iOS 13.4, *) {
|
||||
removeInteraction(pointerInteraction)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension MasterTimelineTitleView: UIPointerInteractionDelegate {
|
||||
|
||||
@available(iOS 13.4, *)
|
||||
func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? {
|
||||
var rect = self.frame
|
||||
rect.origin.x = rect.origin.x - 10
|
||||
rect.size.width = rect.width + 20
|
||||
|
||||
return UIPointerStyle(effect: .automatic(UITargetedPreview(view: self)), shape: .roundedRect(rect))
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -564,12 +564,11 @@ private extension MasterTimelineViewController {
|
||||
updateTitleUnreadCount()
|
||||
|
||||
if coordinator.timelineFeed is WebFeed {
|
||||
titleView.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
|
||||
titleView.buttonize()
|
||||
titleView.addGestureRecognizer(feedTapGestureRecognizer)
|
||||
titleView.accessibilityTraits = .button
|
||||
} else {
|
||||
titleView.debuttonize()
|
||||
titleView.removeGestureRecognizer(feedTapGestureRecognizer)
|
||||
titleView.accessibilityTraits.remove(.button)
|
||||
}
|
||||
|
||||
navigationItem.titleView = titleView
|
||||
|
@ -61,7 +61,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
private var wasRootSplitViewControllerCollapsed = false
|
||||
|
||||
private let fetchAndMergeArticlesQueue = CoalescingQueue(name: "Fetch and Merge Articles", interval: 0.5)
|
||||
private let rebuildBackingStoresWithMergeQueue = CoalescingQueue(name: "Rebuild The Backing Stores by Merging", interval: 1.0)
|
||||
private let rebuildBackingStoresQueue = CoalescingQueue(name: "Rebuild The Backing Stores", interval: 1.0)
|
||||
private var fetchSerialNumber = 0
|
||||
private let fetchRequestQueue = FetchRequestQueue()
|
||||
|
||||
@ -441,12 +441,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
return
|
||||
}
|
||||
|
||||
// If we are filtering reads, the new unread count is greater than 1, and the feed isn't shown then continue
|
||||
guard let feed = note.object as? Feed, isReadFeedsFiltered, feed.unreadCount > 0, !shadowTableContains(feed) else {
|
||||
return
|
||||
}
|
||||
|
||||
rebuildBackingStoresWithMergeQueue.add(self, #selector(rebuildBackingStoresWithMerge))
|
||||
queueRebuildBackingStores()
|
||||
}
|
||||
|
||||
@objc func statusesDidChange(_ note: Notification) {
|
||||
@ -465,7 +460,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
}
|
||||
|
||||
@objc func batchUpdateDidPerform(_ notification: Notification) {
|
||||
rebuildBackingStoresWithMerge()
|
||||
rebuildBackingStores()
|
||||
}
|
||||
|
||||
@objc func displayNameDidChange(_ note: Notification) {
|
||||
@ -541,7 +536,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
}
|
||||
|
||||
@objc func downloadArticlesDidUpdateUnreadCounts(_ note: Notification) {
|
||||
rebuildBackingStoresWithMerge()
|
||||
rebuildBackingStores()
|
||||
}
|
||||
|
||||
@objc func accountDidDownloadArticles(_ note: Notification) {
|
||||
@ -568,7 +563,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
|
||||
func suspend() {
|
||||
fetchAndMergeArticlesQueue.performCallsImmediately()
|
||||
rebuildBackingStoresWithMergeQueue.performCallsImmediately()
|
||||
rebuildBackingStoresQueue.performCallsImmediately()
|
||||
fetchRequestQueue.cancelAllRequests()
|
||||
}
|
||||
|
||||
@ -745,6 +740,9 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
} else {
|
||||
|
||||
setTimelineFeed(nil, animated: false) {
|
||||
if self.isReadFeedsFiltered {
|
||||
self.queueRebuildBackingStores()
|
||||
}
|
||||
self.activityManager.invalidateSelecting()
|
||||
if self.rootSplitViewController.isCollapsed && self.navControllerForTimeline().viewControllers.last is MasterTimelineViewController {
|
||||
self.navControllerForTimeline().popViewController(animated: animations.contains(.navigation))
|
||||
@ -1360,7 +1358,15 @@ private extension SceneCoordinator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func queueRebuildBackingStores() {
|
||||
rebuildBackingStoresQueue.add(self, #selector(rebuildBackingStoresWithDefaults))
|
||||
}
|
||||
|
||||
@objc func rebuildBackingStoresWithDefaults() {
|
||||
rebuildBackingStores()
|
||||
}
|
||||
|
||||
func rebuildBackingStores(initialLoad: Bool = false, updateExpandedNodes: (() -> Void)? = nil, completion: (() -> Void)? = nil) {
|
||||
if !animatingChanges && !BatchUpdate.shared.isPerforming {
|
||||
|
||||
@ -1375,11 +1381,6 @@ private extension SceneCoordinator {
|
||||
}
|
||||
}
|
||||
|
||||
@objc func rebuildBackingStoresWithMerge() {
|
||||
addShadowTableToFilterExceptions()
|
||||
rebuildBackingStores()
|
||||
}
|
||||
|
||||
func rebuildShadowTable() {
|
||||
shadowTable = [[Node]]()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user