2019-04-15 22:03:05 +02:00
//
// M a s t e r V i e w C o n t r o l l e r . s w i f t
// N e t N e w s W i r e
//
// C r e a t e d b y M a u r i c e P a r k e r o n 4 / 8 / 1 9 .
// C o p y r i g h t © 2 0 1 9 R a n c h e r o S o f t w a r e . A l l r i g h t s r e s e r v e d .
//
import UIKit
import Account
2019-04-17 01:25:55 +02:00
import Articles
2019-04-15 22:03:05 +02:00
import RSCore
import RSTree
2020-05-15 09:09:33 +02:00
import SafariServices
2019-04-15 22:03:05 +02:00
2019-07-27 21:49:07 +02:00
class MasterFeedViewController : UITableViewController , UndoableCommandRunner {
2019-04-15 22:03:05 +02:00
2020-01-08 01:39:45 +01:00
@IBOutlet weak var filterButton : UIBarButtonItem !
2019-10-30 10:04:13 +01:00
private var refreshProgressView : RefreshProgressView ?
2021-01-31 15:37:47 +01:00
@IBOutlet weak var addNewItemButton : UIBarButtonItem ! {
didSet {
if #available ( iOS 14 , * ) {
addNewItemButton . primaryAction = nil
} else {
addNewItemButton . action = #selector ( MasterFeedViewController . add ( _ : ) )
}
}
}
2020-02-23 19:57:20 +01:00
2019-04-17 01:25:55 +02:00
var undoableCommands = [ UndoableCommand ] ( )
2019-09-01 19:43:07 +02:00
weak var coordinator : SceneCoordinator !
2019-08-29 01:06:27 +02:00
2019-09-05 21:37:07 +02:00
private let keyboardManager = KeyboardManager ( type : . sidebar )
2019-09-04 23:24:16 +02:00
override var keyCommands : [ UIKeyCommand ] ? {
2021-06-23 09:44:34 +02:00
// I f t h e f i r s t r e s p o n d e r i s t h e W K W e b V i e w ( P r e l o a d e d W e b V i e w ) w e d o n ' t w a n t t o s u p p l y a n y k e y b o a r d
// c o m m a n d s t h a t t h e s y s t e m i s l o o k i n g f o r b y g o i n g u p t h e r e s p o n d e r c h a i n . T h e y w i l l i n t e r f e r e w i t h
// t h e W K W e b V i e w s b u i l t i n h a r d w a r e k e y b o a r d s h o r t c u t s , s p e c i f i c a l l y t h e u p a n d d o w n a r r o w k e y s .
guard let current = UIResponder . currentFirstResponder , ! ( current is PreloadedWebView ) else { return nil }
2019-09-04 23:24:16 +02:00
return keyboardManager . keyCommands
}
2019-04-17 01:25:55 +02:00
override var canBecomeFirstResponder : Bool {
return true
}
2021-05-01 21:29:18 +02:00
2019-04-15 22:03:05 +02:00
override func viewDidLoad ( ) {
super . viewDidLoad ( )
2019-08-23 19:27:45 +02:00
if traitCollection . userInterfaceIdiom = = . phone {
navigationController ? . navigationBar . prefersLargeTitles = true
}
2019-09-15 02:51:23 +02:00
// I f y o u d o n ' t h a v e a n e m p t y t a b l e h e a d e r , U I K i t t r i e s t o h e l p o u t b y p u t t i n g o n e i n f o r y o u
// t h a t m a k e s a g a p b e t w e e n t h e f i r s t s e c t i o n h e a d e r a n d t h e n a v i g a t i o n b a r
var frame = CGRect . zero
frame . size . height = . leastNormalMagnitude
tableView . tableHeaderView = UIView ( frame : frame )
2019-04-23 15:00:27 +02:00
tableView . register ( MasterFeedTableViewSectionHeader . self , forHeaderFooterViewReuseIdentifier : " SectionHeader " )
2019-11-21 03:28:24 +01:00
tableView . dragDelegate = self
tableView . dropDelegate = self
tableView . dragInteractionEnabled = true
2019-10-29 01:52:50 +01:00
resetEstimatedRowHeight ( )
tableView . separatorStyle = . none
2019-04-15 22:03:05 +02:00
NotificationCenter . default . addObserver ( self , selector : #selector ( unreadCountDidChange ( _ : ) ) , name : . UnreadCountDidChange , object : nil )
NotificationCenter . default . addObserver ( self , selector : #selector ( faviconDidBecomeAvailable ( _ : ) ) , name : . FaviconDidBecomeAvailable , object : nil )
2019-11-15 03:11:41 +01:00
NotificationCenter . default . addObserver ( self , selector : #selector ( webFeedIconDidBecomeAvailable ( _ : ) ) , name : . WebFeedIconDidBecomeAvailable , object : nil )
NotificationCenter . default . addObserver ( self , selector : #selector ( webFeedSettingDidChange ( _ : ) ) , name : . WebFeedSettingDidChange , object : nil )
2019-04-28 17:31:35 +02:00
NotificationCenter . default . addObserver ( self , selector : #selector ( contentSizeCategoryDidChange ) , name : UIContentSizeCategory . didChangeNotification , object : nil )
2019-11-13 20:43:02 +01:00
NotificationCenter . default . addObserver ( self , selector : #selector ( willEnterForeground ( _ : ) ) , name : UIApplication . willEnterForegroundNotification , object : nil )
2021-02-01 01:33:37 +01:00
NotificationCenter . default . addObserver ( self , selector : #selector ( configureContextMenu ( _ : ) ) , name : . ActiveExtensionPointsDidChange , object : nil )
2023-02-05 01:46:16 +01:00
2019-04-22 23:25:16 +02:00
2020-01-03 16:42:43 +01:00
refreshControl = UIRefreshControl ( )
refreshControl ! . addTarget ( self , action : #selector ( refreshAccounts ( _ : ) ) , for : . valueChanged )
2019-10-25 20:34:59 +02:00
configureToolbar ( )
2019-09-06 17:52:21 +02:00
becomeFirstResponder ( )
2019-04-15 22:03:05 +02:00
}
override func viewWillAppear ( _ animated : Bool ) {
2019-10-26 00:07:40 +02:00
updateUI ( )
2019-04-15 22:03:05 +02:00
super . viewWillAppear ( animated )
}
2019-11-30 00:36:22 +01:00
2023-02-05 01:46:16 +01:00
override func viewDidAppear ( _ animated : Bool ) {
super . viewDidAppear ( animated )
if ( isBeingPresented || isMovingToParent ) {
// O n l y s h o w t h e T w i t t e r a l e r t t h e f i r s t t i m e
// t h e v i e w i s p r e s e n t e d .
presentTwitterDeprecationAlertIfRequired ( )
}
}
2021-05-01 21:29:18 +02:00
override func traitCollectionDidChange ( _ previousTraitCollection : UITraitCollection ? ) {
2021-10-21 16:34:33 +02:00
super . traitCollectionDidChange ( previousTraitCollection )
2021-10-21 16:33:29 +02:00
if traitCollection . preferredContentSizeCategory != previousTraitCollection ? . preferredContentSizeCategory {
IconImageCache . shared . emptyCache ( )
reloadAllVisibleCells ( )
}
2021-05-01 21:29:18 +02:00
}
2019-04-17 20:35:16 +02:00
// MARK: N o t i f i c a t i o n s
2019-04-17 01:25:55 +02:00
2019-04-15 22:03:05 +02:00
@objc func unreadCountDidChange ( _ note : Notification ) {
2019-04-28 19:37:53 +02:00
updateUI ( )
2019-08-29 21:35:18 +02:00
2021-11-13 18:06:17 +01:00
guard let unreadCountProvider = note . object as ? UnreadCountProvider else {
2019-08-29 21:35:18 +02:00
return
}
2021-11-13 18:06:17 +01:00
if let account = unreadCountProvider as ? Account {
2019-08-29 21:35:18 +02:00
if let node = coordinator . rootNode . childNodeRepresentingObject ( account ) {
let sectionIndex = coordinator . rootNode . indexOfChild ( node ) !
if let headerView = tableView . headerView ( forSection : sectionIndex ) as ? MasterFeedTableViewSectionHeader {
headerView . unreadCount = account . unreadCount
}
}
return
}
2021-11-13 17:33:07 +01:00
var node : Node ? = nil
2021-11-13 18:06:17 +01:00
if let coordinator = unreadCountProvider as ? SceneCoordinator , let feed = coordinator . timelineFeed {
2021-11-13 17:33:07 +01:00
node = coordinator . rootNode . descendantNodeRepresentingObject ( feed as AnyObject )
} else {
2021-11-13 18:06:17 +01:00
node = coordinator . rootNode . descendantNodeRepresentingObject ( unreadCountProvider as AnyObject )
2021-11-13 17:33:07 +01:00
}
guard let unreadCountNode = node , let indexPath = coordinator . indexPathFor ( unreadCountNode ) else { return }
2021-11-13 18:06:17 +01:00
if let cell = tableView . cellForRow ( at : indexPath ) as ? MasterFeedTableViewCell {
cell . unreadCount = unreadCountProvider . unreadCount
}
2019-04-15 22:03:05 +02:00
}
@objc func faviconDidBecomeAvailable ( _ note : Notification ) {
2019-11-06 01:05:57 +01:00
applyToAvailableCells ( configureIcon )
2019-10-29 02:57:26 +01:00
}
2019-11-15 03:11:41 +01:00
@objc func webFeedIconDidBecomeAvailable ( _ note : Notification ) {
guard let webFeed = note . userInfo ? [ UserInfoKey . webFeed ] as ? WebFeed else {
2019-11-06 02:57:15 +01:00
return
}
2019-11-15 03:11:41 +01:00
applyToCellsForRepresentedObject ( webFeed , configureIcon ( _ : _ : ) )
2019-04-15 22:03:05 +02:00
}
2019-11-15 03:11:41 +01:00
@objc func webFeedSettingDidChange ( _ note : Notification ) {
guard let webFeed = note . object as ? WebFeed , let key = note . userInfo ? [ WebFeed . WebFeedSettingUserInfoKey ] as ? String else {
2019-04-15 22:03:05 +02:00
return
}
2019-11-15 03:11:41 +01:00
if key = = WebFeed . WebFeedSettingKey . homePageURL || key = = WebFeed . WebFeedSettingKey . faviconURL {
configureCellsForRepresentedObject ( webFeed )
2019-04-15 22:03:05 +02:00
}
}
2019-04-28 17:31:35 +02:00
@objc func contentSizeCategoryDidChange ( _ note : Notification ) {
2019-10-28 23:18:44 +01:00
resetEstimatedRowHeight ( )
2021-10-21 02:03:02 +02:00
tableView . reloadData ( )
2019-04-28 17:31:35 +02:00
}
2019-11-13 20:43:02 +01:00
@objc func willEnterForeground ( _ note : Notification ) {
updateUI ( )
}
2019-04-15 22:03:05 +02:00
// MARK: T a b l e V i e w
2021-10-21 02:03:02 +02:00
override func numberOfSections ( in tableView : UITableView ) -> Int {
coordinator . numberOfSections ( )
}
override func tableView ( _ tableView : UITableView , numberOfRowsInSection section : Int ) -> Int {
coordinator . numberOfRows ( in : section )
}
override func tableView ( _ tableView : UITableView , cellForRowAt indexPath : IndexPath ) -> UITableViewCell {
let cell = tableView . dequeueReusableCell ( withIdentifier : " Cell " , for : indexPath ) as ! MasterFeedTableViewCell
configure ( cell , indexPath )
return cell
}
2021-11-13 17:56:30 +01:00
override func tableView ( _ tableView : UITableView , canEditRowAt indexPath : IndexPath ) -> Bool {
if coordinator . nodeFor ( indexPath ) ? . representedObject is PseudoFeed {
return false
} else {
return true
}
}
2019-04-21 01:20:25 +02:00
override func tableView ( _ tableView : UITableView , heightForHeaderInSection section : Int ) -> CGFloat {
2019-04-28 17:31:35 +02:00
2019-06-29 20:35:12 +02:00
guard let nameProvider = coordinator . rootNode . childAtIndex ( section ) ? . representedObject as ? DisplayNameProvider else {
2019-04-28 17:31:35 +02:00
return 44
}
let headerView = MasterFeedTableViewSectionHeader ( )
headerView . name = nameProvider . nameForDisplay
2019-04-28 18:25:21 +02:00
let size = headerView . sizeThatFits ( CGSize ( width : tableView . bounds . width , height : 0.0 ) )
2019-04-28 17:31:35 +02:00
return size . height
2019-04-21 01:20:25 +02:00
}
2019-04-18 14:24:55 +02:00
override func tableView ( _ tableView : UITableView , viewForHeaderInSection section : Int ) -> UIView ? {
2019-06-29 20:35:12 +02:00
guard let nameProvider = coordinator . rootNode . childAtIndex ( section ) ? . representedObject as ? DisplayNameProvider else {
2019-04-17 20:35:16 +02:00
return nil
}
2019-04-18 14:24:55 +02:00
2019-04-23 15:00:27 +02:00
let headerView = tableView . dequeueReusableHeaderFooterView ( withIdentifier : " SectionHeader " ) as ! MasterFeedTableViewSectionHeader
2020-03-25 14:55:02 +01:00
headerView . delegate = self
2019-04-18 14:24:55 +02:00
headerView . name = nameProvider . nameForDisplay
2019-06-29 20:35:12 +02:00
guard let sectionNode = coordinator . rootNode . childAtIndex ( section ) else {
2019-04-21 01:20:25 +02:00
return headerView
}
if let account = sectionNode . representedObject as ? Account {
2019-04-18 14:24:55 +02:00
headerView . unreadCount = account . unreadCount
2019-04-18 17:49:31 +02:00
} else {
headerView . unreadCount = 0
2019-04-18 14:24:55 +02:00
}
2019-04-18 18:38:38 +02:00
headerView . tag = section
2019-11-25 01:29:00 +01:00
headerView . disclosureExpanded = coordinator . isExpanded ( sectionNode )
2019-11-01 17:40:52 +01:00
if section = = tableView . numberOfSections - 1 {
headerView . isLastSection = true
} else {
headerView . isLastSection = false
}
2019-04-21 01:20:25 +02:00
2019-12-11 22:29:32 +01:00
headerView . gestureRecognizers ? . removeAll ( )
2019-04-18 18:38:38 +02:00
let tap = UITapGestureRecognizer ( target : self , action : #selector ( self . toggleSectionHeader ( _ : ) ) )
headerView . addGestureRecognizer ( tap )
2020-01-20 20:42:25 +01:00
// W i t h o u t t h i s t h e s w i p e g e s t u r e r e g i s t e r s o n t h e c e l l b e l o w
2020-02-01 08:12:54 +01:00
let gestureRecognizer = UIPanGestureRecognizer ( target : nil , action : nil )
gestureRecognizer . delegate = self
headerView . addGestureRecognizer ( gestureRecognizer )
2019-04-18 18:38:38 +02:00
2019-12-11 22:42:45 +01:00
headerView . interactions . removeAll ( )
2019-10-24 02:58:18 +02:00
if section != 0 {
headerView . addInteraction ( UIContextMenuInteraction ( delegate : self ) )
}
2019-04-18 14:24:55 +02:00
return headerView
2019-04-17 20:35:16 +02:00
}
2019-04-21 01:20:25 +02:00
override func tableView ( _ tableView : UITableView , heightForFooterInSection section : Int ) -> CGFloat {
return CGFloat . leastNormalMagnitude
}
override func tableView ( _ tableView : UITableView , viewForFooterInSection section : Int ) -> UIView ? {
return UIView ( frame : CGRect . zero )
}
2019-04-17 20:35:16 +02:00
2019-04-15 22:03:05 +02:00
override func tableView ( _ tableView : UITableView , trailingSwipeActionsConfigurationForRowAt indexPath : IndexPath ) -> UISwipeActionsConfiguration ? {
2019-08-16 02:46:31 +02:00
var actions = [ UIContextualAction ] ( )
2019-04-15 22:03:05 +02:00
// S e t u p t h e d e l e t e a c t i o n
let deleteTitle = NSLocalizedString ( " Delete " , comment : " Delete " )
2019-12-15 01:14:55 +01:00
let deleteAction = UIContextualAction ( style : . normal , title : deleteTitle ) { [ weak self ] ( action , view , completion ) in
2019-04-15 22:03:05 +02:00
self ? . delete ( indexPath : indexPath )
2019-12-15 01:14:55 +01:00
completion ( true )
2019-04-15 22:03:05 +02:00
}
2019-08-16 02:46:31 +02:00
deleteAction . backgroundColor = UIColor . systemRed
actions . append ( deleteAction )
2019-04-15 22:03:05 +02:00
// S e t u p t h e r e n a m e a c t i o n
let renameTitle = NSLocalizedString ( " Rename " , comment : " Rename " )
2019-12-15 01:14:55 +01:00
let renameAction = UIContextualAction ( style : . normal , title : renameTitle ) { [ weak self ] ( action , view , completion ) in
2019-04-15 22:03:05 +02:00
self ? . rename ( indexPath : indexPath )
2019-12-15 01:14:55 +01:00
completion ( true )
2019-04-15 22:03:05 +02:00
}
2019-08-16 02:46:31 +02:00
renameAction . backgroundColor = UIColor . systemOrange
actions . append ( renameAction )
2021-10-21 02:03:02 +02:00
if let webFeed = coordinator . nodeFor ( indexPath ) ? . representedObject as ? WebFeed {
2019-08-16 02:46:31 +02:00
let moreTitle = NSLocalizedString ( " More " , comment : " More " )
2019-12-15 01:14:55 +01:00
let moreAction = UIContextualAction ( style : . normal , title : moreTitle ) { [ weak self ] ( action , view , completion ) in
2019-08-16 02:46:31 +02:00
if let self = self {
2021-10-21 02:03:02 +02:00
let alert = UIAlertController ( title : webFeed . nameForDisplay , message : nil , preferredStyle : . actionSheet )
2019-08-16 02:46:31 +02:00
if let popoverController = alert . popoverPresentationController {
popoverController . sourceView = view
2019-08-16 19:14:55 +02:00
popoverController . sourceRect = CGRect ( x : view . frame . size . width / 2 , y : view . frame . size . height / 2 , width : 1 , height : 1 )
2019-08-16 02:46:31 +02:00
}
2019-12-15 01:14:55 +01:00
if let action = self . getInfoAlertAction ( indexPath : indexPath , completion : completion ) {
2019-09-28 14:00:18 +02:00
alert . addAction ( action )
}
2019-12-15 01:14:55 +01:00
if let action = self . homePageAlertAction ( indexPath : indexPath , completion : completion ) {
2019-08-16 02:46:31 +02:00
alert . addAction ( action )
}
2019-12-15 01:14:55 +01:00
if let action = self . copyFeedPageAlertAction ( indexPath : indexPath , completion : completion ) {
2019-08-16 02:46:31 +02:00
alert . addAction ( action )
}
2019-12-15 01:14:55 +01:00
if let action = self . copyHomePageAlertAction ( indexPath : indexPath , completion : completion ) {
2019-08-16 02:46:31 +02:00
alert . addAction ( action )
}
2020-01-11 19:30:16 +01:00
if let action = self . markAllAsReadAlertAction ( indexPath : indexPath , completion : completion ) {
alert . addAction ( action )
}
2019-08-16 02:46:31 +02:00
let cancelTitle = NSLocalizedString ( " Cancel " , comment : " Cancel " )
alert . addAction ( UIAlertAction ( title : cancelTitle , style : . cancel ) { _ in
2019-12-15 01:14:55 +01:00
completion ( true )
2019-08-16 02:46:31 +02:00
} )
self . present ( alert , animated : true )
}
}
moreAction . backgroundColor = UIColor . systemGray
actions . append ( moreAction )
}
return UISwipeActionsConfiguration ( actions : actions )
2019-04-15 22:03:05 +02:00
}
2019-08-15 20:19:02 +02:00
2019-08-26 03:00:34 +02:00
override func tableView ( _ tableView : UITableView , contextMenuConfigurationForRowAt indexPath : IndexPath , point : CGPoint ) -> UIContextMenuConfiguration ? {
2021-10-21 02:03:02 +02:00
guard let feed = coordinator . nodeFor ( indexPath ) ? . representedObject as ? Feed else {
2019-08-26 03:00:34 +02:00
return nil
}
2021-10-21 02:03:02 +02:00
if feed is WebFeed {
return makeWebFeedContextMenu ( indexPath : indexPath , includeDeleteRename : true )
} else if feed is Folder {
return makeFolderContextMenu ( indexPath : indexPath )
} else if feed is PseudoFeed {
return makePseudoFeedContextMenu ( indexPath : indexPath )
2019-12-30 09:33:06 +01:00
} else {
return nil
2019-08-26 03:00:34 +02:00
}
}
2019-11-24 05:15:29 +01:00
override func tableView ( _ tableView : UITableView , previewForHighlightingContextMenuWithConfiguration configuration : UIContextMenuConfiguration ) -> UITargetedPreview ? {
2021-10-21 02:03:02 +02:00
guard let identifier = configuration . identifier as ? MasterFeedRowIdentifier ,
let cell = tableView . cellForRow ( at : identifier . indexPath ) else {
return nil
}
2020-06-16 01:03:20 +02:00
2019-11-24 05:15:29 +01:00
return UITargetedPreview ( view : cell , parameters : CroppingPreviewParameters ( view : cell ) )
}
2019-04-15 22:03:05 +02:00
override func tableView ( _ tableView : UITableView , didSelectRowAt indexPath : IndexPath ) {
2019-09-04 23:24:16 +02:00
becomeFirstResponder ( )
2020-02-18 02:40:40 +01:00
coordinator . selectFeed ( indexPath : indexPath , animations : [ . navigation , . select , . scroll ] )
2019-04-15 22:03:05 +02:00
}
2019-04-20 03:03:02 +02:00
override func tableView ( _ tableView : UITableView , targetIndexPathForMoveFromRowAt sourceIndexPath : IndexPath , toProposedIndexPath proposedDestinationIndexPath : IndexPath ) -> IndexPath {
// A d j u s t t h e i n d e x p a t h s o t h a t i t w i l l n e v e r b e i n t h e s m a r t f e e d s a r e a
let destIndexPath : IndexPath = {
if proposedDestinationIndexPath . section = = 0 {
return IndexPath ( row : 0 , section : 1 )
}
2019-09-03 19:07:18 +02:00
return coordinator . cappedIndexPath ( proposedDestinationIndexPath )
2019-04-20 03:03:02 +02:00
} ( )
2021-10-21 02:03:02 +02:00
guard let draggedNode = coordinator . nodeFor ( sourceIndexPath ) else {
2019-04-20 03:03:02 +02:00
assertionFailure ( " This should never happen " )
return sourceIndexPath
}
2019-11-05 14:12:51 +01:00
// I f t h e r e i s n o d e s t i n a t i o n n o d e , w e a r e d r a g g i n g o n t o a n e m p t y A c c o u n t
2021-10-21 02:03:02 +02:00
guard let destNode = coordinator . nodeFor ( destIndexPath ) ,
let destParentNode = destNode . parent else {
return proposedDestinationIndexPath
}
2019-11-05 14:12:51 +01:00
2020-11-02 21:53:00 +01:00
// I f t h i s i s a f o l d e r , l e t t h e u s e r s d r o p o n i t
if destNode . representedObject is Folder {
2019-11-23 02:59:25 +01:00
return proposedDestinationIndexPath
2019-04-20 03:03:02 +02:00
}
// I f w e a r e d r a g g i n g a r o u n d i n t h e s a m e c o n t a i n e r , j u s t r e t u r n t h e o r i g i n a l s o u r c e
2020-06-16 01:03:20 +02:00
if destParentNode . childNodes . contains ( draggedNode ) {
2019-04-20 03:03:02 +02:00
return sourceIndexPath
}
// S u g g e s t t o t h e u s e r t h e b e s t p l a c e t o d r o p t h e f e e d
// R e v i s i t i f t h e t r e e c o n t r o l l e r c a n e v e r b e s o r t e d i n s o m e o t h e r w a y .
2020-06-16 01:03:20 +02:00
let nodes = destParentNode . childNodes + [ draggedNode ]
2019-04-20 03:03:02 +02:00
var sortedNodes = nodes . sortedAlphabeticallyWithFoldersAtEnd ( )
let index = sortedNodes . firstIndex ( of : draggedNode ) !
2019-11-26 20:42:25 +01:00
sortedNodes . remove ( at : index )
2019-04-20 03:03:02 +02:00
if index = = 0 {
2019-04-20 16:07:54 +02:00
2020-06-16 01:03:20 +02:00
if destParentNode . representedObject is Account {
2019-04-20 03:03:02 +02:00
return IndexPath ( row : 0 , section : destIndexPath . section )
} else {
2021-10-21 02:03:02 +02:00
if let candidateIndexPath = coordinator . indexPathFor ( sortedNodes [ index ] ) {
2021-04-13 14:38:41 +02:00
let movementAdjustment = sourceIndexPath < destIndexPath ? 1 : 0
return IndexPath ( row : candidateIndexPath . row - movementAdjustment , section : candidateIndexPath . section )
} else {
return sourceIndexPath
}
2019-04-20 03:03:02 +02:00
}
2019-04-20 16:07:54 +02:00
2019-04-20 03:03:02 +02:00
} else {
2019-04-20 16:07:54 +02:00
2019-11-21 20:22:33 +01:00
if index >= sortedNodes . count {
2021-10-21 02:03:02 +02:00
if let lastSortedIndexPath = coordinator . indexPathFor ( sortedNodes [ sortedNodes . count - 1 ] ) {
2021-04-13 14:38:41 +02:00
let movementAdjustment = sourceIndexPath > destIndexPath ? 1 : 0
return IndexPath ( row : lastSortedIndexPath . row + movementAdjustment , section : lastSortedIndexPath . section )
} else {
return sourceIndexPath
}
2019-04-20 16:07:54 +02:00
} else {
2019-11-23 02:59:25 +01:00
let movementAdjustment = sourceIndexPath < destIndexPath ? 1 : 0
2021-10-21 02:03:02 +02:00
return coordinator . indexPathFor ( sortedNodes [ index - movementAdjustment ] ) ? ? sourceIndexPath
2019-04-20 16:07:54 +02:00
}
2019-04-20 03:03:02 +02:00
}
}
2019-04-15 22:03:05 +02:00
// MARK: A c t i o n s
2019-04-25 13:05:49 +02:00
@IBAction func settings ( _ sender : UIBarButtonItem ) {
2019-07-06 19:25:45 +02:00
coordinator . showSettings ( )
2019-04-17 14:00:32 +02:00
}
2019-04-17 01:25:55 +02:00
2020-01-08 01:39:45 +01:00
@IBAction func toggleFilter ( _ sender : Any ) {
2020-03-22 16:18:07 +01:00
coordinator . toggleReadFeedsFilter ( )
2019-11-21 22:55:50 +01:00
}
2020-01-08 01:39:45 +01:00
@IBAction func add ( _ sender : UIBarButtonItem ) {
2020-08-11 22:00:31 +02:00
2021-01-31 15:37:47 +01:00
if #available ( iOS 14 , * ) {
} else {
let title = NSLocalizedString ( " Add Item " , comment : " Add Item " )
let alertController = UIAlertController ( title : title , message : nil , preferredStyle : . actionSheet )
let cancelTitle = NSLocalizedString ( " Cancel " , comment : " Cancel " )
let cancelAction = UIAlertAction ( title : cancelTitle , style : . cancel )
let addWebFeedActionTitle = NSLocalizedString ( " Add Web Feed " , comment : " Add Web Feed " )
let addWebFeedAction = UIAlertAction ( title : addWebFeedActionTitle , style : . default ) { _ in
self . coordinator . showAddWebFeed ( )
}
let addRedditFeedActionTitle = NSLocalizedString ( " Add Reddit Feed " , comment : " Add Reddit Feed " )
let addRedditFeedAction = UIAlertAction ( title : addRedditFeedActionTitle , style : . default ) { _ in
self . coordinator . showAddRedditFeed ( )
}
let addWebFolderdActionTitle = NSLocalizedString ( " Add Folder " , comment : " Add Folder " )
let addWebFolderAction = UIAlertAction ( title : addWebFolderdActionTitle , style : . default ) { _ in
self . coordinator . showAddFolder ( )
}
alertController . addAction ( addWebFeedAction )
if AccountManager . shared . activeAccounts . contains ( where : { $0 . type = = . onMyMac || $0 . type = = . cloudKit } ) {
if ExtensionPointManager . shared . isRedditEnabled {
alertController . addAction ( addRedditFeedAction )
}
2020-08-12 17:27:58 +02:00
}
2021-01-31 15:37:47 +01:00
alertController . addAction ( addWebFolderAction )
alertController . addAction ( cancelAction )
alertController . popoverPresentationController ? . barButtonItem = sender
present ( alertController , animated : true )
2020-08-12 00:33:48 +02:00
}
2020-08-11 22:00:31 +02:00
2019-04-15 22:03:05 +02:00
}
2019-04-18 18:38:38 +02:00
@objc func toggleSectionHeader ( _ sender : UITapGestureRecognizer ) {
2020-03-25 14:55:02 +01:00
guard let headerView = sender . view as ? MasterFeedTableViewSectionHeader else {
return
2019-04-18 18:38:38 +02:00
}
2020-03-25 14:55:02 +01:00
toggle ( headerView )
2019-04-18 18:38:38 +02:00
}
2019-08-15 19:42:25 +02:00
@objc func refreshAccounts ( _ sender : Any ) {
2020-01-03 16:42:43 +01:00
refreshControl ? . endRefreshing ( )
2020-01-11 02:14:21 +01:00
2019-08-15 19:42:25 +02:00
// T h i s i s a h a c k t o m a k e s u r e t h a t a n e r r o r d i a l o g d o e s n ' t i n t e r f e r e w i t h d i s m i s s i n g t h e r e f r e s h C o n t r o l .
// I f t h e e r r o r d i a l o g a p p e a r s t o o c l o s e l y t o t h e c a l l t o e n d R e f r e s h i n g , t h e n t h e r e f r e s h C o n t r o l n e v e r d i s a p p e a r s .
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 0.5 ) {
2020-03-11 21:47:00 +01:00
appDelegate . manualRefresh ( errorHandler : ErrorHandler . present ( self ) )
2019-08-15 19:42:25 +02:00
}
}
2019-08-19 22:45:52 +02:00
2019-09-04 23:24:16 +02:00
// MARK: K e y b o a r d s h o r t c u t s
2019-09-05 04:06:29 +02:00
@objc func selectNextUp ( _ sender : Any ? ) {
coordinator . selectPrevFeed ( )
}
@objc func selectNextDown ( _ sender : Any ? ) {
coordinator . selectNextFeed ( )
}
@objc func navigateToTimeline ( _ sender : Any ? ) {
coordinator . navigateToTimeline ( )
}
2019-09-04 23:24:16 +02:00
@objc func openInBrowser ( _ sender : Any ? ) {
coordinator . showBrowserForCurrentFeed ( )
}
2019-09-05 22:54:58 +02:00
@objc override func delete ( _ sender : Any ? ) {
if let indexPath = coordinator . currentFeedIndexPath {
delete ( indexPath : indexPath )
}
}
2022-08-03 01:10:09 +02:00
override func canPerformAction ( _ action : Selector , withSender sender : Any ? ) -> Bool {
if action = = #selector ( UIResponder . delete ( _ : ) ) {
return isFirstResponder
}
return super . canPerformAction ( action , withSender : sender )
}
2019-09-05 23:04:07 +02:00
@objc func expandSelectedRows ( _ sender : Any ? ) {
2021-10-21 02:03:02 +02:00
if let indexPath = coordinator . currentFeedIndexPath , let node = coordinator . nodeFor ( indexPath ) {
coordinator . expand ( node )
2019-09-05 23:04:07 +02:00
}
}
@objc func collapseSelectedRows ( _ sender : Any ? ) {
2021-10-21 02:03:02 +02:00
if let indexPath = coordinator . currentFeedIndexPath , let node = coordinator . nodeFor ( indexPath ) {
coordinator . collapse ( node )
2019-09-05 23:04:07 +02:00
}
}
2019-09-05 23:38:33 +02:00
@objc func expandAll ( _ sender : Any ? ) {
coordinator . expandAllSectionsAndFolders ( )
}
@objc func collapseAllExceptForGroupItems ( _ sender : Any ? ) {
coordinator . collapseAllFolders ( )
}
2020-05-14 14:43:41 +02:00
@objc func markAllAsRead ( _ sender : Any ) {
guard let indexPath = tableView . indexPathForSelectedRow , let contentView = tableView . cellForRow ( at : indexPath ) ? . contentView else {
return
}
let title = NSLocalizedString ( " Mark All as Read " , comment : " Mark All as Read " )
MarkAsReadAlertController . confirm ( self , coordinator : coordinator , confirmTitle : title , sourceType : contentView ) { [ weak self ] in
self ? . coordinator . markAllAsReadInTimeline ( )
}
}
2020-05-26 06:51:42 +02:00
@objc func showFeedInspector ( _ sender : Any ? ) {
coordinator . showFeedInspector ( )
}
2020-05-14 14:43:41 +02:00
2019-08-19 22:45:52 +02:00
// MARK: A P I
2019-09-10 23:38:59 +02:00
func restoreSelectionIfNecessary ( adjustScroll : Bool ) {
if let indexPath = coordinator . masterFeedIndexPathForCurrentTimeline ( ) {
if adjustScroll {
2020-01-28 05:57:52 +01:00
tableView . selectRowAndScrollIfNotVisible ( at : indexPath , animations : [ ] )
2019-09-10 23:38:59 +02:00
} else {
tableView . selectRow ( at : indexPath , animated : false , scrollPosition : . none )
}
}
}
2020-01-28 05:57:52 +01:00
func updateFeedSelection ( animations : Animations ) {
2021-10-21 02:03:02 +02:00
if let indexPath = coordinator . currentFeedIndexPath {
tableView . selectRowAndScrollIfNotVisible ( at : indexPath , animations : animations )
} else {
if let indexPath = tableView . indexPathForSelectedRow {
if animations . contains ( . select ) {
tableView . deselectRow ( at : indexPath , animated : true )
} else {
tableView . deselectRow ( at : indexPath , animated : false )
}
}
}
2019-08-25 18:38:04 +02:00
}
2021-10-21 03:37:29 +02:00
func reloadFeeds ( initialLoad : Bool , changes : ShadowTableChanges , completion : ( ( ) -> Void ) ? = nil ) {
2019-08-25 18:38:04 +02:00
updateUI ( )
2019-09-01 17:11:03 +02:00
2021-10-21 02:03:02 +02:00
guard ! initialLoad else {
tableView . reloadData ( )
completion ? ( )
return
}
2021-10-21 03:37:29 +02:00
tableView . performBatchUpdates {
if let deletes = changes . deletes , ! deletes . isEmpty {
tableView . deleteSections ( IndexSet ( deletes ) , with : . middle )
}
2021-10-21 02:03:02 +02:00
2021-10-21 03:37:29 +02:00
if let inserts = changes . inserts , ! inserts . isEmpty {
tableView . insertSections ( IndexSet ( inserts ) , with : . middle )
}
if let moves = changes . moves , ! moves . isEmpty {
for move in moves {
tableView . moveSection ( move . from , toSection : move . to )
2021-10-21 02:03:02 +02:00
}
2021-10-21 03:37:29 +02:00
}
if let rowChanges = changes . rowChanges {
for rowChange in rowChanges {
if let deletes = rowChange . deleteIndexPaths , ! deletes . isEmpty {
tableView . deleteRows ( at : deletes , with : . middle )
}
if let inserts = rowChange . insertIndexPaths , ! inserts . isEmpty {
tableView . insertRows ( at : inserts , with : . middle )
}
if let moves = rowChange . moveIndexPaths , ! moves . isEmpty {
for move in moves {
tableView . moveRow ( at : move . 0 , to : move . 1 )
}
2021-10-21 02:03:02 +02:00
}
}
2019-09-05 20:14:14 +02:00
}
}
2021-10-21 02:03:02 +02:00
2022-03-01 18:14:41 +01:00
if let rowChanges = changes . rowChanges {
for rowChange in rowChanges {
if let reloads = rowChange . reloadIndexPaths , ! reloads . isEmpty {
tableView . reloadRows ( at : reloads , with : . none )
}
}
}
2021-10-21 02:03:02 +02:00
completion ? ( )
2019-09-05 20:14:14 +02:00
}
2020-03-22 16:18:07 +01:00
func updateUI ( ) {
if coordinator . isReadFeedsFiltered {
setFilterButtonToActive ( )
} else {
setFilterButtonToInactive ( )
}
2020-05-10 17:00:04 +02:00
refreshProgressView ? . update ( )
2020-03-22 16:18:07 +01:00
addNewItemButton ? . isEnabled = ! AccountManager . shared . activeAccounts . isEmpty
2021-02-01 01:33:37 +01:00
configureContextMenu ( )
}
@objc
func configureContextMenu ( _ : Any ? = nil ) {
2021-01-31 15:37:47 +01:00
if #available ( iOS 14.0 , * ) {
2021-02-24 00:47:52 +01:00
/*
Context Menu Order :
1. Add Web Feed
2. Add Reddit Feed
2023-02-04 04:11:33 +01:00
3. Add Folder
2021-02-24 00:47:52 +01:00
*/
2021-01-31 15:37:47 +01:00
2021-02-24 00:47:52 +01:00
var menuItems : [ UIAction ] = [ ]
2021-01-31 15:37:47 +01:00
2021-02-24 00:47:52 +01:00
let addWebFeedActionTitle = NSLocalizedString ( " Add Web Feed " , comment : " Add Web Feed " )
let addWebFeedAction = UIAction ( title : addWebFeedActionTitle , image : AppAssets . plus ) { _ in
self . coordinator . showAddWebFeed ( )
2021-01-31 15:37:47 +01:00
}
2021-02-24 00:47:52 +01:00
menuItems . append ( addWebFeedAction )
2021-01-31 15:37:47 +01:00
if AccountManager . shared . activeAccounts . contains ( where : { $0 . type = = . onMyMac || $0 . type = = . cloudKit } ) {
2021-02-01 01:14:02 +01:00
if ExtensionPointManager . shared . isRedditEnabled {
2021-02-24 00:47:52 +01:00
let addRedditFeedActionTitle = NSLocalizedString ( " Add Reddit Feed " , comment : " Add Reddit Feed " )
let addRedditFeedAction = UIAction ( title : addRedditFeedActionTitle , image : AppAssets . contextMenuReddit . tinted ( color : . label ) ) { _ in
self . coordinator . showAddRedditFeed ( )
}
menuItems . append ( addRedditFeedAction )
2021-01-31 15:37:47 +01:00
}
}
2021-02-24 00:47:52 +01:00
let addWebFolderActionTitle = NSLocalizedString ( " Add Folder " , comment : " Add Folder " )
let addWebFolderAction = UIAction ( title : addWebFolderActionTitle , image : AppAssets . folderOutlinePlus ) { _ in
self . coordinator . showAddFolder ( )
}
menuItems . append ( addWebFolderAction )
let contextMenu = UIMenu ( title : NSLocalizedString ( " Add Item " , comment : " Add Item " ) , image : nil , identifier : nil , options : [ ] , children : menuItems . reversed ( ) )
2021-01-31 15:37:47 +01:00
2021-02-24 00:47:52 +01:00
self . addNewItemButton . menu = contextMenu
2021-01-31 15:37:47 +01:00
}
2020-03-22 16:18:07 +01:00
}
2023-02-05 01:46:16 +01:00
2019-09-05 04:06:29 +02:00
func focus ( ) {
becomeFirstResponder ( )
}
2020-05-15 09:09:33 +02:00
func openInAppBrowser ( ) {
if let indexPath = coordinator . currentFeedIndexPath ,
let url = coordinator . homePageURLForFeed ( indexPath ) {
let vc = SFSafariViewController ( url : url )
present ( vc , animated : true )
}
}
2021-11-13 17:33:07 +01:00
2023-02-05 01:46:16 +01:00
private func presentTwitterDeprecationAlertIfRequired ( ) {
if AppDefaults . shared . twitterDeprecationAlertShown { return }
2023-02-05 02:04:23 +01:00
let expiryDate = Date ( timeIntervalSince1970 : 1691539200 ) . timeIntervalSince1970 // A u g u s t 9 t h 2 0 2 3 , 0 0 : 0 0 U T C
2023-02-05 01:46:16 +01:00
let currentDate = Date ( ) . timeIntervalSince1970
if currentDate > expiryDate {
return // I f a f t e r A u g u s t 9 t h , d o n ' t s h o w
}
var twitterIsActive : Bool = false
AccountManager . shared . accounts . forEach ( { account in
account . flattenedWebFeeds ( ) . forEach ( { webfeed in
guard let components = URLComponents ( string : webfeed . url ) ,
let host = components . host else {
return
}
if host = = " twitter.com " {
twitterIsActive = true
return
}
} )
} )
if twitterIsActive {
showTwitterDeprecationAlert ( )
}
AppDefaults . shared . twitterDeprecationAlertShown = true
}
private func showTwitterDeprecationAlert ( ) {
let alert = UIAlertController ( title : " Twitter Integration Removed " ,
message : " On February 1, 2023, Twitter announced the end of free access to the Twitter API, effective February 9. \n \n Since Twitter does not provide RSS feeds, we’ ve had to use the Twitter API. Without free access to that API, we can’ t read feeds from Twitter. \n \n We’ ve left your Twitter feeds intact. If you have any starred items from those feeds, they will remain as long as you don’ t delete those feeds. \n \n You can still read whatever you have already downloaded. However, those feeds will no longer update. " ,
preferredStyle : . alert )
alert . addAction ( UIAlertAction ( title : " OK " , style : . cancel ) )
present ( alert , animated : true )
}
2019-08-15 19:42:25 +02:00
}
2019-10-24 02:58:18 +02:00
// MARK: U I C o n t e x t M e n u I n t e r a c t i o n D e l e g a t e
extension MasterFeedViewController : UIContextMenuInteractionDelegate {
func contextMenuInteraction ( _ interaction : UIContextMenuInteraction , configurationForMenuAtLocation location : CGPoint ) -> UIContextMenuConfiguration ? {
guard let sectionIndex = interaction . view ? . tag ,
let sectionNode = coordinator . rootNode . childAtIndex ( sectionIndex ) ,
2019-10-24 03:22:31 +02:00
let account = sectionNode . representedObject as ? Account
2019-10-24 02:58:18 +02:00
else {
return nil
}
2019-11-24 05:15:29 +01:00
return UIContextMenuConfiguration ( identifier : sectionIndex as NSCopying , previewProvider : nil ) { suggestedActions in
2019-12-30 09:33:06 +01:00
2020-11-13 12:23:04 +01:00
var menuElements = [ UIMenuElement ] ( )
menuElements . append ( UIMenu ( title : " " , options : . displayInline , children : [ self . getAccountInfoAction ( account : account ) ] ) )
2019-12-30 09:33:06 +01:00
2020-07-01 09:00:39 +02:00
if let markAllAction = self . markAllAsReadAction ( account : account , contentView : interaction . view ) {
2020-11-13 12:23:04 +01:00
menuElements . append ( UIMenu ( title : " " , options : . displayInline , children : [ markAllAction ] ) )
2019-12-30 09:33:06 +01:00
}
2020-11-13 12:23:04 +01:00
menuElements . append ( UIMenu ( title : " " , options : . displayInline , children : [ self . deactivateAccountAction ( account : account ) ] ) )
return UIMenu ( title : " " , children : menuElements )
2019-10-24 02:58:18 +02:00
}
}
2019-11-24 05:15:29 +01:00
func contextMenuInteraction ( _ interaction : UIContextMenuInteraction , previewForHighlightingMenuWithConfiguration configuration : UIContextMenuConfiguration ) -> UITargetedPreview ? {
guard let sectionIndex = configuration . identifier as ? Int ,
let cell = tableView . headerView ( forSection : sectionIndex ) else {
return nil
}
return UITargetedPreview ( view : cell , parameters : CroppingPreviewParameters ( view : cell ) )
}
2019-10-24 02:58:18 +02:00
}
2020-03-25 14:55:02 +01:00
// MARK: M a s t e r F e e d T a b l e V i e w S e c t i o n H e a d e r D e l e g a t e
extension MasterFeedViewController : MasterFeedTableViewSectionHeaderDelegate {
func masterFeedTableViewSectionHeaderDisclosureDidToggle ( _ sender : MasterFeedTableViewSectionHeader ) {
toggle ( sender )
}
}
2019-08-15 19:42:25 +02:00
// MARK: M a s t e r T a b l e V i e w C e l l D e l e g a t e
extension MasterFeedViewController : MasterFeedTableViewCellDelegate {
2019-04-15 22:03:05 +02:00
2020-03-25 14:55:02 +01:00
func masterFeedTableViewCellDisclosureDidToggle ( _ sender : MasterFeedTableViewCell , expanding : Bool ) {
2019-08-15 19:42:25 +02:00
if expanding {
expand ( sender )
} else {
collapse ( sender )
}
}
}
// MARK: P r i v a t e
private extension MasterFeedViewController {
2019-10-25 20:34:59 +02:00
func configureToolbar ( ) {
guard let refreshProgressView = Bundle . main . loadNibNamed ( " RefreshProgressView " , owner : self , options : nil ) ? [ 0 ] as ? RefreshProgressView else {
return
}
self . refreshProgressView = refreshProgressView
let refreshProgressItemButton = UIBarButtonItem ( customView : refreshProgressView )
2020-01-08 01:39:45 +01:00
toolbarItems ? . insert ( refreshProgressItemButton , at : 2 )
2019-10-25 20:34:59 +02:00
}
2020-01-09 22:38:25 +01:00
func setFilterButtonToActive ( ) {
filterButton ? . image = AppAssets . filterActiveImage
filterButton ? . accLabelText = NSLocalizedString ( " Selected - Filter Read Feeds " , comment : " Selected - Filter Read Feeds " )
}
func setFilterButtonToInactive ( ) {
filterButton ? . image = AppAssets . filterInactiveImage
filterButton ? . accLabelText = NSLocalizedString ( " Filter Read Feeds " , comment : " Filter Read Feeds " )
}
2019-10-28 23:18:44 +01:00
func resetEstimatedRowHeight ( ) {
let titleLabel = NonIntrinsicLabel ( )
titleLabel . text = " But I must explain "
let unreadCountView = MasterFeedUnreadCountView ( )
unreadCountView . unreadCount = 10
let layout = MasterFeedTableViewCellLayout ( cellWidth : tableView . bounds . size . width , insets : tableView . safeAreaInsets , label : titleLabel , unreadCountView : unreadCountView , showingEditingControl : false , indent : false , shouldShowDisclosure : false )
tableView . estimatedRowHeight = layout . height
}
2019-08-29 01:06:27 +02:00
2021-10-21 02:03:02 +02:00
func configure ( _ cell : MasterFeedTableViewCell , _ indexPath : IndexPath ) {
guard let node = coordinator . nodeFor ( indexPath ) else { return }
2019-04-17 17:34:10 +02:00
cell . delegate = self
2021-10-21 02:03:02 +02:00
if node . representedObject is Folder {
2019-04-20 18:25:02 +02:00
cell . indentationLevel = 0
2019-10-29 23:55:49 +01:00
} else {
cell . indentationLevel = 1
2019-04-20 18:11:09 +02:00
}
2019-04-18 01:16:33 +02:00
2021-10-21 02:03:02 +02:00
if let containerID = ( node . representedObject as ? Container ) ? . containerID {
2020-06-16 01:03:20 +02:00
cell . setDisclosure ( isExpanded : coordinator . isExpanded ( containerID ) , animated : false )
cell . isDisclosureAvailable = true
} else {
cell . isDisclosureAvailable = false
}
2019-04-18 01:16:33 +02:00
2021-10-21 02:03:02 +02:00
if let feed = node . representedObject as ? Feed {
cell . name = feed . nameForDisplay
cell . unreadCount = feed . unreadCount
}
configureIcon ( cell , indexPath )
2019-10-29 01:52:50 +01:00
let rowsInSection = tableView . numberOfRows ( inSection : indexPath . section )
if indexPath . row = = rowsInSection - 1 {
cell . isSeparatorShown = false
} else {
cell . isSeparatorShown = true
}
2019-04-15 22:03:05 +02:00
}
2021-10-21 02:03:02 +02:00
func configureIcon ( _ cell : MasterFeedTableViewCell , _ indexPath : IndexPath ) {
guard let node = coordinator . nodeFor ( indexPath ) , let feed = node . representedObject as ? Feed , let feedID = feed . feedID else {
2021-05-08 21:42:44 +02:00
return
2021-05-01 21:29:18 +02:00
}
2021-05-08 21:42:44 +02:00
cell . iconImage = IconImageCache . shared . imageFor ( feedID )
2021-05-01 21:29:18 +02:00
}
2019-04-15 22:03:05 +02:00
func nameFor ( _ node : Node ) -> String {
if let displayNameProvider = node . representedObject as ? DisplayNameProvider {
return displayNameProvider . nameForDisplay
}
return " "
}
2019-08-21 20:10:08 +02:00
2019-08-15 19:42:25 +02:00
func configureCellsForRepresentedObject ( _ representedObject : AnyObject ) {
applyToCellsForRepresentedObject ( representedObject , configure )
}
2021-10-21 02:03:02 +02:00
func applyToCellsForRepresentedObject ( _ representedObject : AnyObject , _ completion : ( MasterFeedTableViewCell , IndexPath ) -> Void ) {
applyToAvailableCells { ( cell , indexPath ) in
if let node = coordinator . nodeFor ( indexPath ) ,
let representedFeed = representedObject as ? Feed ,
let candidate = node . representedObject as ? Feed ,
representedFeed . feedID = = candidate . feedID {
completion ( cell , indexPath )
2019-08-15 19:42:25 +02:00
}
}
}
2021-10-21 02:03:02 +02:00
func applyToAvailableCells ( _ completion : ( MasterFeedTableViewCell , IndexPath ) -> Void ) {
2019-08-15 19:42:25 +02:00
tableView . visibleCells . forEach { cell in
2021-10-21 02:03:02 +02:00
guard let indexPath = tableView . indexPath ( for : cell ) else {
2019-08-15 19:42:25 +02:00
return
}
2021-10-21 02:03:02 +02:00
completion ( cell as ! MasterFeedTableViewCell , indexPath )
2019-08-15 19:42:25 +02:00
}
}
2020-02-18 02:40:40 +01:00
private func reloadAllVisibleCells ( completion : ( ( ) -> Void ) ? = nil ) {
2021-10-21 02:03:02 +02:00
guard let indexPaths = tableView . indexPathsForVisibleRows else { return }
2021-10-21 03:45:20 +02:00
tableView . reloadRows ( at : indexPaths , with : . none )
2021-10-21 16:47:34 +02:00
restoreSelectionIfNecessary ( adjustScroll : false )
2019-08-30 23:19:06 +02:00
}
2019-08-15 19:42:25 +02:00
private func accountForNode ( _ node : Node ) -> Account ? {
if let account = node . representedObject as ? Account {
return account
}
if let folder = node . representedObject as ? Folder {
return folder . account
}
2019-11-15 03:11:41 +01:00
if let feed = node . representedObject as ? WebFeed {
2019-08-15 19:42:25 +02:00
return feed . account
}
return nil
}
2020-03-25 14:55:02 +01:00
func toggle ( _ headerView : MasterFeedTableViewSectionHeader ) {
guard let sectionNode = coordinator . rootNode . childAtIndex ( headerView . tag ) else {
return
}
if coordinator . isExpanded ( sectionNode ) {
headerView . disclosureExpanded = false
coordinator . collapse ( sectionNode )
} else {
headerView . disclosureExpanded = true
coordinator . expand ( sectionNode )
}
}
2019-08-15 19:42:25 +02:00
func expand ( _ cell : MasterFeedTableViewCell ) {
2021-10-21 02:03:02 +02:00
guard let indexPath = tableView . indexPath ( for : cell ) , let node = coordinator . nodeFor ( indexPath ) else {
2019-08-15 19:42:25 +02:00
return
}
2021-10-21 02:03:02 +02:00
coordinator . expand ( node )
2019-08-15 19:42:25 +02:00
}
func collapse ( _ cell : MasterFeedTableViewCell ) {
2021-10-21 02:03:02 +02:00
guard let indexPath = tableView . indexPath ( for : cell ) , let node = coordinator . nodeFor ( indexPath ) else {
2019-08-15 19:42:25 +02:00
return
}
2021-10-21 02:03:02 +02:00
coordinator . collapse ( node )
2019-08-15 19:42:25 +02:00
}
2021-10-21 02:03:02 +02:00
func makeWebFeedContextMenu ( indexPath : IndexPath , includeDeleteRename : Bool ) -> UIContextMenuConfiguration {
return UIContextMenuConfiguration ( identifier : MasterFeedRowIdentifier ( indexPath : indexPath ) , previewProvider : nil , actionProvider : { [ weak self ] suggestedActions in
2019-08-19 22:45:52 +02:00
guard let self = self else { return nil }
2019-08-15 20:19:02 +02:00
2020-11-13 12:23:04 +01:00
var menuElements = [ UIMenuElement ] ( )
2019-08-15 20:19:02 +02:00
2019-09-28 14:00:18 +02:00
if let inspectorAction = self . getInfoAction ( indexPath : indexPath ) {
2020-11-13 12:23:04 +01:00
menuElements . append ( UIMenu ( title : " " , options : . displayInline , children : [ inspectorAction ] ) )
2019-09-28 14:00:18 +02:00
}
2019-08-15 20:19:02 +02:00
if let homePageAction = self . homePageAction ( indexPath : indexPath ) {
2020-11-13 12:23:04 +01:00
menuElements . append ( UIMenu ( title : " " , options : . displayInline , children : [ homePageAction ] ) )
2019-06-06 00:42:35 +02:00
}
2019-08-15 20:19:02 +02:00
2020-11-13 12:23:04 +01:00
var pageActions = [ UIAction ] ( )
2019-08-15 22:19:23 +02:00
if let copyFeedPageAction = self . copyFeedPageAction ( indexPath : indexPath ) {
2020-11-13 12:23:04 +01:00
pageActions . append ( copyFeedPageAction )
2019-08-15 22:19:23 +02:00
}
if let copyHomePageAction = self . copyHomePageAction ( indexPath : indexPath ) {
2020-11-13 12:23:04 +01:00
pageActions . append ( copyHomePageAction )
}
if ! pageActions . isEmpty {
menuElements . append ( UIMenu ( title : " " , options : . displayInline , children : pageActions ) )
2019-08-15 22:19:23 +02:00
}
2019-12-30 09:33:06 +01:00
if let markAllAction = self . markAllAsReadAction ( indexPath : indexPath ) {
2020-11-13 12:23:04 +01:00
menuElements . append ( UIMenu ( title : " " , options : . displayInline , children : [ markAllAction ] ) )
2019-12-30 09:33:06 +01:00
}
2019-08-15 20:19:02 +02:00
2020-01-02 08:16:29 +01:00
if includeDeleteRename {
2020-11-13 12:23:04 +01:00
menuElements . append ( UIMenu ( title : " " ,
options : . displayInline ,
children : [
self . renameAction ( indexPath : indexPath ) ,
self . deleteAction ( indexPath : indexPath )
] ) )
2020-01-02 08:16:29 +01:00
}
2020-11-13 12:23:04 +01:00
return UIMenu ( title : " " , children : menuElements )
2019-08-15 20:19:02 +02:00
} )
2019-04-17 20:35:16 +02:00
2019-08-15 20:19:02 +02:00
}
2021-10-21 02:03:02 +02:00
func makeFolderContextMenu ( indexPath : IndexPath ) -> UIContextMenuConfiguration {
return UIContextMenuConfiguration ( identifier : MasterFeedRowIdentifier ( indexPath : indexPath ) , previewProvider : nil , actionProvider : { [ weak self ] suggestedActions in
2019-08-16 00:46:42 +02:00
2019-08-19 22:45:52 +02:00
guard let self = self else { return nil }
2020-11-13 12:23:04 +01:00
var menuElements = [ UIMenuElement ] ( )
2019-12-30 09:33:06 +01:00
if let markAllAction = self . markAllAsReadAction ( indexPath : indexPath ) {
2020-11-13 12:23:04 +01:00
menuElements . append ( UIMenu ( title : " " , options : . displayInline , children : [ markAllAction ] ) )
2019-12-30 09:33:06 +01:00
}
2019-08-16 00:46:42 +02:00
2020-11-13 12:23:04 +01:00
menuElements . append ( UIMenu ( title : " " ,
options : . displayInline ,
children : [
self . renameAction ( indexPath : indexPath ) ,
self . deleteAction ( indexPath : indexPath )
] ) )
return UIMenu ( title : " " , children : menuElements )
2019-08-16 00:46:42 +02:00
} )
}
2021-10-21 02:03:02 +02:00
func makePseudoFeedContextMenu ( indexPath : IndexPath ) -> UIContextMenuConfiguration ? {
2019-12-30 09:33:06 +01:00
guard let markAllAction = self . markAllAsReadAction ( indexPath : indexPath ) else {
return nil
}
2021-10-21 02:03:02 +02:00
return UIContextMenuConfiguration ( identifier : MasterFeedRowIdentifier ( indexPath : indexPath ) , previewProvider : nil , actionProvider : { suggestedActions in
2019-12-30 09:33:06 +01:00
return UIMenu ( title : " " , children : [ markAllAction ] )
} )
}
2019-08-15 20:19:02 +02:00
func homePageAction ( indexPath : IndexPath ) -> UIAction ? {
2019-09-04 23:24:16 +02:00
guard coordinator . homePageURLForFeed ( indexPath ) != nil else {
return nil
2019-06-06 00:42:35 +02:00
}
2019-04-17 20:35:16 +02:00
2019-08-15 20:19:02 +02:00
let title = NSLocalizedString ( " Open Home Page " , comment : " Open Home Page " )
2019-09-04 23:24:16 +02:00
let action = UIAction ( title : title , image : AppAssets . safariImage ) { [ weak self ] action in
self ? . coordinator . showBrowserForFeed ( indexPath )
2019-08-15 20:19:02 +02:00
}
return action
}
2019-12-15 01:14:55 +01:00
func homePageAlertAction ( indexPath : IndexPath , completion : @ escaping ( Bool ) -> Void ) -> UIAlertAction ? {
2019-09-04 23:24:16 +02:00
guard coordinator . homePageURLForFeed ( indexPath ) != nil else {
return nil
2019-08-16 02:46:31 +02:00
}
2019-09-04 23:24:16 +02:00
2019-08-16 02:46:31 +02:00
let title = NSLocalizedString ( " Open Home Page " , comment : " Open Home Page " )
2019-09-04 23:24:16 +02:00
let action = UIAlertAction ( title : title , style : . default ) { [ weak self ] action in
self ? . coordinator . showBrowserForFeed ( indexPath )
2019-12-15 01:14:55 +01:00
completion ( true )
2019-08-16 02:46:31 +02:00
}
return action
}
2019-08-15 22:19:23 +02:00
func copyFeedPageAction ( indexPath : IndexPath ) -> UIAction ? {
2021-10-21 02:03:02 +02:00
guard let webFeed = coordinator . nodeFor ( indexPath ) ? . representedObject as ? WebFeed ,
let url = URL ( string : webFeed . url ) else {
return nil
}
2019-08-15 22:19:23 +02:00
let title = NSLocalizedString ( " Copy Feed URL " , comment : " Copy Feed URL " )
2019-08-16 20:27:41 +02:00
let action = UIAction ( title : title , image : AppAssets . copyImage ) { action in
2019-08-15 22:19:23 +02:00
UIPasteboard . general . url = url
}
return action
}
2019-12-15 01:14:55 +01:00
func copyFeedPageAlertAction ( indexPath : IndexPath , completion : @ escaping ( Bool ) -> Void ) -> UIAlertAction ? {
2021-10-21 02:03:02 +02:00
guard let webFeed = coordinator . nodeFor ( indexPath ) ? . representedObject as ? WebFeed ,
let url = URL ( string : webFeed . url ) else {
return nil
}
2019-08-16 02:46:31 +02:00
let title = NSLocalizedString ( " Copy Feed URL " , comment : " Copy Feed URL " )
let action = UIAlertAction ( title : title , style : . default ) { action in
UIPasteboard . general . url = url
2019-12-15 01:14:55 +01:00
completion ( true )
2019-08-16 02:46:31 +02:00
}
return action
}
2019-08-15 22:19:23 +02:00
func copyHomePageAction ( indexPath : IndexPath ) -> UIAction ? {
2021-10-21 02:03:02 +02:00
guard let webFeed = coordinator . nodeFor ( indexPath ) ? . representedObject as ? WebFeed ,
let homePageURL = webFeed . homePageURL ,
let url = URL ( string : homePageURL ) else {
return nil
}
2019-08-15 22:19:23 +02:00
let title = NSLocalizedString ( " Copy Home Page URL " , comment : " Copy Home Page URL " )
2019-08-16 20:27:41 +02:00
let action = UIAction ( title : title , image : AppAssets . copyImage ) { action in
2019-08-15 22:19:23 +02:00
UIPasteboard . general . url = url
}
return action
}
2019-12-15 01:14:55 +01:00
func copyHomePageAlertAction ( indexPath : IndexPath , completion : @ escaping ( Bool ) -> Void ) -> UIAlertAction ? {
2021-10-21 02:03:02 +02:00
guard let webFeed = coordinator . nodeFor ( indexPath ) ? . representedObject as ? WebFeed ,
let homePageURL = webFeed . homePageURL ,
let url = URL ( string : homePageURL ) else {
return nil
}
2019-08-16 02:46:31 +02:00
let title = NSLocalizedString ( " Copy Home Page URL " , comment : " Copy Home Page URL " )
let action = UIAlertAction ( title : title , style : . default ) { action in
UIPasteboard . general . url = url
2019-12-15 01:14:55 +01:00
completion ( true )
2019-08-16 02:46:31 +02:00
}
return action
}
2020-01-11 19:30:16 +01:00
func markAllAsReadAlertAction ( indexPath : IndexPath , completion : @ escaping ( Bool ) -> Void ) -> UIAlertAction ? {
2021-10-21 02:03:02 +02:00
guard let webFeed = coordinator . nodeFor ( indexPath ) ? . representedObject as ? WebFeed ,
webFeed . unreadCount > 0 ,
let articles = try ? webFeed . fetchArticles ( ) , let contentView = self . tableView . cellForRow ( at : indexPath ) ? . contentView else {
2020-01-11 19:30:16 +01:00
return nil
}
let localizedMenuText = NSLocalizedString ( " Mark All as Read in “%@” " , comment : " Command " )
2021-10-21 02:03:02 +02:00
let title = NSString . localizedStringWithFormat ( localizedMenuText as NSString , webFeed . nameForDisplay ) as String
2020-01-11 19:30:16 +01:00
let cancel = {
completion ( true )
}
2020-05-13 06:33:51 +02:00
2020-01-11 19:30:16 +01:00
let action = UIAlertAction ( title : title , style : . default ) { [ weak self ] action in
2020-05-13 06:33:51 +02:00
MarkAsReadAlertController . confirm ( self , coordinator : self ? . coordinator , confirmTitle : title , sourceType : contentView , cancelCompletion : cancel ) { [ weak self ] in
2020-01-11 19:30:16 +01:00
self ? . coordinator . markAllAsRead ( Array ( articles ) )
completion ( true )
}
}
return action
}
2019-08-15 20:19:02 +02:00
func deleteAction ( indexPath : IndexPath ) -> UIAction {
let title = NSLocalizedString ( " Delete " , comment : " Delete " )
2019-10-05 05:03:31 +02:00
let action = UIAction ( title : title , image : AppAssets . trashImage , attributes : . destructive ) { [ weak self ] action in
2019-08-16 20:54:19 +02:00
self ? . delete ( indexPath : indexPath )
2019-08-15 20:19:02 +02:00
}
return action
}
func renameAction ( indexPath : IndexPath ) -> UIAction {
let title = NSLocalizedString ( " Rename " , comment : " Rename " )
2019-08-16 20:54:19 +02:00
let action = UIAction ( title : title , image : AppAssets . editImage ) { [ weak self ] action in
self ? . rename ( indexPath : indexPath )
2019-08-15 20:19:02 +02:00
}
return action
2019-04-15 22:03:05 +02:00
}
2019-04-17 20:35:16 +02:00
2019-09-28 14:00:18 +02:00
func getInfoAction ( indexPath : IndexPath ) -> UIAction ? {
2021-10-21 02:03:02 +02:00
guard let webFeed = coordinator . nodeFor ( indexPath ) ? . representedObject as ? WebFeed else {
2019-09-28 14:00:18 +02:00
return nil
}
let title = NSLocalizedString ( " Get Info " , comment : " Get Info " )
let action = UIAction ( title : title , image : AppAssets . infoImage ) { [ weak self ] action in
2021-10-21 02:03:02 +02:00
self ? . coordinator . showFeedInspector ( for : webFeed )
2019-09-28 14:00:18 +02:00
}
return action
}
2019-10-24 02:58:18 +02:00
func getAccountInfoAction ( account : Account ) -> UIAction {
let title = NSLocalizedString ( " Get Info " , comment : " Get Info " )
let action = UIAction ( title : title , image : AppAssets . infoImage ) { [ weak self ] action in
self ? . coordinator . showAccountInspector ( for : account )
}
return action
}
2019-10-24 03:22:31 +02:00
func deactivateAccountAction ( account : Account ) -> UIAction {
let title = NSLocalizedString ( " Deactivate " , comment : " Deactivate " )
let action = UIAction ( title : title , image : AppAssets . deactivateImage ) { action in
account . isActive = false
}
return action
}
2019-12-15 01:14:55 +01:00
func getInfoAlertAction ( indexPath : IndexPath , completion : @ escaping ( Bool ) -> Void ) -> UIAlertAction ? {
2021-10-21 02:03:02 +02:00
guard let webFeed = coordinator . nodeFor ( indexPath ) ? . representedObject as ? WebFeed else {
2019-09-28 14:00:18 +02:00
return nil
}
2019-10-05 05:03:31 +02:00
let title = NSLocalizedString ( " Get Info " , comment : " Get Info " )
2019-09-28 14:00:18 +02:00
let action = UIAlertAction ( title : title , style : . default ) { [ weak self ] action in
2021-10-21 02:03:02 +02:00
self ? . coordinator . showFeedInspector ( for : webFeed )
2019-12-15 01:14:55 +01:00
completion ( true )
2019-09-28 14:00:18 +02:00
}
return action
}
2019-12-30 09:33:06 +01:00
func markAllAsReadAction ( indexPath : IndexPath ) -> UIAction ? {
2021-10-21 02:03:02 +02:00
guard let feed = coordinator . nodeFor ( indexPath ) ? . representedObject as ? Feed ,
let contentView = self . tableView . cellForRow ( at : indexPath ) ? . contentView ,
feed . unreadCount > 0 else {
return nil
}
2021-04-24 09:05:11 +02:00
2020-10-21 00:59:54 +02:00
let localizedMenuText = NSLocalizedString ( " Mark All as Read in “%@” " , comment : " Command " )
let title = NSString . localizedStringWithFormat ( localizedMenuText as NSString , feed . nameForDisplay ) as String
let action = UIAction ( title : title , image : AppAssets . markAllAsReadImage ) { [ weak self ] action in
MarkAsReadAlertController . confirm ( self , coordinator : self ? . coordinator , confirmTitle : title , sourceType : contentView ) { [ weak self ] in
if let articles = try ? feed . fetchUnreadArticles ( ) {
self ? . coordinator . markAllAsRead ( Array ( articles ) )
}
}
2020-07-01 09:00:39 +02:00
}
2020-10-21 00:59:54 +02:00
return action
2020-07-01 09:00:39 +02:00
}
2020-10-21 00:59:54 +02:00
func markAllAsReadAction ( account : Account , contentView : UIView ? ) -> UIAction ? {
guard account . unreadCount > 0 , let contentView = contentView else {
2019-12-30 09:33:06 +01:00
return nil
}
let localizedMenuText = NSLocalizedString ( " Mark All as Read in “%@” " , comment : " Command " )
2020-10-21 00:59:54 +02:00
let title = NSString . localizedStringWithFormat ( localizedMenuText as NSString , account . nameForDisplay ) as String
2020-01-07 02:07:04 +01:00
let action = UIAction ( title : title , image : AppAssets . markAllAsReadImage ) { [ weak self ] action in
2020-05-13 06:33:51 +02:00
MarkAsReadAlertController . confirm ( self , coordinator : self ? . coordinator , confirmTitle : title , sourceType : contentView ) { [ weak self ] in
2021-06-18 02:23:41 +02:00
// I f y o u d o n ' t h a v e t h i s d e l a y t h e s c r e e n f l a s h e s w h e n i t e x e c u t e s t h i s c o d e
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 0.5 ) {
if let articles = try ? account . fetchArticles ( . unread ( ) ) {
self ? . coordinator . markAllAsRead ( Array ( articles ) )
}
2020-10-21 00:59:54 +02:00
}
2020-01-11 19:30:16 +01:00
}
2019-12-30 09:33:06 +01:00
}
return action
}
2020-10-21 00:59:54 +02:00
2019-04-15 22:03:05 +02:00
func rename ( indexPath : IndexPath ) {
2021-10-21 02:03:02 +02:00
guard let feed = coordinator . nodeFor ( indexPath ) ? . representedObject as ? Feed else { return }
2020-06-16 01:03:20 +02:00
2020-09-29 03:21:07 +02:00
let formatString = NSLocalizedString ( " Rename “%@” " , comment : " Rename feed " )
2021-10-21 02:03:02 +02:00
let title = NSString . localizedStringWithFormat ( formatString as NSString , feed . nameForDisplay ) as String
2019-04-15 22:10:30 +02:00
2019-04-15 22:03:05 +02:00
let alertController = UIAlertController ( title : title , message : nil , preferredStyle : . alert )
let cancelTitle = NSLocalizedString ( " Cancel " , comment : " Cancel " )
alertController . addAction ( UIAlertAction ( title : cancelTitle , style : . cancel ) )
let renameTitle = NSLocalizedString ( " Rename " , comment : " Rename " )
let renameAction = UIAlertAction ( title : renameTitle , style : . default ) { [ weak self ] action in
2020-06-16 01:03:20 +02:00
guard let name = alertController . textFields ? [ 0 ] . text , ! name . isEmpty else {
return
2019-04-15 22:03:05 +02:00
}
2020-06-16 01:03:20 +02:00
if let webFeed = feed as ? WebFeed {
webFeed . rename ( to : name ) { result in
2019-06-26 13:23:08 +02:00
switch result {
case . success :
2020-01-19 20:16:59 +01:00
break
2019-06-26 13:23:08 +02:00
case . failure ( let error ) :
self ? . presentError ( error )
}
}
2020-06-16 01:03:20 +02:00
} else if let folder = feed as ? Folder {
2019-06-26 13:23:08 +02:00
folder . rename ( to : name ) { result in
switch result {
case . success :
2020-01-19 20:16:59 +01:00
break
2019-06-26 13:23:08 +02:00
case . failure ( let error ) :
self ? . presentError ( error )
}
}
2019-04-15 22:03:05 +02:00
}
}
alertController . addAction ( renameAction )
2021-01-12 02:57:14 +01:00
alertController . preferredAction = renameAction
2019-04-15 22:03:05 +02:00
alertController . addTextField ( ) { textField in
2021-10-21 02:03:02 +02:00
textField . text = feed . nameForDisplay
2019-04-15 22:03:05 +02:00
textField . placeholder = NSLocalizedString ( " Name " , comment : " Name " )
}
self . present ( alertController , animated : true ) {
}
}
2019-04-18 16:42:41 +02:00
2019-08-15 20:19:02 +02:00
func delete ( indexPath : IndexPath ) {
2021-10-21 02:03:02 +02:00
guard let feed = coordinator . nodeFor ( indexPath ) ? . representedObject as ? Feed else { return }
2020-09-29 03:21:07 +02:00
let title : String
let message : String
if feed is Folder {
title = NSLocalizedString ( " Delete Folder " , comment : " Delete folder " )
let localizedInformativeText = NSLocalizedString ( " Are you sure you want to delete the “%@” folder? " , comment : " Folder delete text " )
message = NSString . localizedStringWithFormat ( localizedInformativeText as NSString , feed . nameForDisplay ) as String
} else {
title = NSLocalizedString ( " Delete Feed " , comment : " Delete feed " )
let localizedInformativeText = NSLocalizedString ( " Are you sure you want to delete the “%@” feed? " , comment : " Feed delete text " )
message = NSString . localizedStringWithFormat ( localizedInformativeText as NSString , feed . nameForDisplay ) as String
}
let alertController = UIAlertController ( title : title , message : message , preferredStyle : . alert )
let cancelTitle = NSLocalizedString ( " Cancel " , comment : " Cancel " )
alertController . addAction ( UIAlertAction ( title : cancelTitle , style : . cancel ) )
let deleteTitle = NSLocalizedString ( " Delete " , comment : " Delete " )
2021-06-16 04:33:23 +02:00
let deleteAction = UIAlertAction ( title : deleteTitle , style : . destructive ) { [ weak self ] action in
2021-10-21 02:03:02 +02:00
self ? . performDelete ( indexPath : indexPath )
2020-09-29 03:21:07 +02:00
}
alertController . addAction ( deleteAction )
2021-01-12 02:57:14 +01:00
alertController . preferredAction = deleteAction
2020-09-29 03:21:07 +02:00
self . present ( alertController , animated : true )
}
2021-10-21 02:03:02 +02:00
func performDelete ( indexPath : IndexPath ) {
2019-08-15 20:19:02 +02:00
guard let undoManager = undoManager ,
2021-10-21 02:03:02 +02:00
let deleteNode = coordinator . nodeFor ( indexPath ) ,
2020-09-29 03:21:07 +02:00
let deleteCommand = DeleteCommand ( nodesToDelete : [ deleteNode ] , undoManager : undoManager , errorHandler : ErrorHandler . present ( self ) ) else {
return
2019-08-15 20:19:02 +02:00
}
2019-08-28 18:30:40 +02:00
if let folder = deleteNode . representedObject as ? Folder {
2019-09-01 02:30:21 +02:00
ActivityManager . cleanUp ( folder )
2019-11-15 03:11:41 +01:00
} else if let feed = deleteNode . representedObject as ? WebFeed {
2019-09-01 02:30:21 +02:00
ActivityManager . cleanUp ( feed )
2019-08-28 18:30:40 +02:00
}
2019-09-08 15:40:15 +02:00
if indexPath = = coordinator . currentFeedIndexPath {
2020-02-18 02:40:40 +01:00
coordinator . selectFeed ( indexPath : nil )
2019-09-08 15:40:15 +02:00
}
2021-05-07 03:07:49 +02:00
pushUndoableCommand ( deleteCommand )
deleteCommand . perform ( )
2019-08-15 20:19:02 +02:00
}
2019-04-15 22:03:05 +02:00
}
2020-02-01 08:12:54 +01:00
extension MasterFeedViewController : UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin ( _ gestureRecognizer : UIGestureRecognizer ) -> Bool {
guard let gestureRecognizer = gestureRecognizer as ? UIPanGestureRecognizer else {
return false
}
let velocity = gestureRecognizer . velocity ( in : self . view )
return abs ( velocity . x ) > abs ( velocity . y ) ;
}
}