2022-11-22 20:21:36 +00:00
//
// S t a t u s A c t i o n A c c o u n t L i s t V i e w C o n t r o l l e r . s w i f t
// T u s k e r
//
// C r e a t e d b y S h a d o w f a c t s o n 9 / 5 / 1 9 .
// C o p y r i g h t © 2 0 1 9 S h a d o w f a c t s . A l l r i g h t s r e s e r v e d .
//
import UIKit
import Pachyderm
class StatusActionAccountListViewController : UIViewController {
private let mastodonController : MastodonController
private let actionType : ActionType
private let statusID : String
private let statusState : StatusState
private var accountIDs : [ String ] ?
// / I f ` t r u e ` , a w a r n i n g w i l l b e s h o w n b e l o w t h e a c c o u n t l i s t d e s c r i b i n g t h a t t h e t o t a l f a v s / r e b l o g s m a y b e i n n a c u r a t e .
var showInacurateCountWarning = false
private var collectionView : UICollectionView {
view as ! UICollectionView
}
private var dataSource : UICollectionViewDiffableDataSource < Section , Item > !
/* *
Creates a new view controller showing the accounts that performed the given action on the given status .
- Parameter actionType The action that this VC is for .
- Parameter statusID The ID of the status to show .
- Parameter accountIDs The accounts that will be shown . If ` nil ` is passed , a request will be performed to load the accounts .
- Parameter mastodonController The ` MastodonController ` instance this view controller uses .
*/
init ( actionType : ActionType , statusID : String , statusState : StatusState , accountIDs : [ String ] ? , mastodonController : MastodonController ) {
self . mastodonController = mastodonController
self . actionType = actionType
self . statusID = statusID
self . statusState = statusState
self . accountIDs = accountIDs
super . init ( nibName : nil , bundle : nil )
switch actionType {
case . favorite :
title = NSLocalizedString ( " Favorited By " , comment : " status favorited by accounts list title " )
case . reblog :
title = NSLocalizedString ( " Reblogged By " , comment : " status reblogged by accounts list title " )
}
}
required init ? ( coder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
override func loadView ( ) {
let layout = UICollectionViewCompositionalLayout { [ unowned self ] sectionIndex , environment in
switch dataSource . sectionIdentifier ( for : sectionIndex ) ! {
case . status :
var config = UICollectionLayoutListConfiguration ( appearance : . grouped )
config . footerMode = self . showInacurateCountWarning ? . supplementary : . none
config . leadingSwipeActionsConfigurationProvider = { [ unowned self ] in
( collectionView . cellForItem ( at : $0 ) as ? TimelineStatusCollectionViewCell ) ? . leadingSwipeActions ( )
}
config . trailingSwipeActionsConfigurationProvider = { [ unowned self ] in
( collectionView . cellForItem ( at : $0 ) as ? TimelineStatusCollectionViewCell ) ? . trailingSwipeActions ( )
}
return NSCollectionLayoutSection . list ( using : config , layoutEnvironment : environment )
case . accounts :
return NSCollectionLayoutSection . list ( using : . init ( appearance : . grouped ) , layoutEnvironment : environment )
}
}
view = UICollectionView ( frame : . zero , collectionViewLayout : layout )
collectionView . delegate = self
collectionView . dragDelegate = self
dataSource = createDataSource ( )
}
private func createDataSource ( ) -> UICollectionViewDiffableDataSource < Section , Item > {
let statusCell = UICollectionView . CellRegistration < TimelineStatusCollectionViewCell , Void > { [ unowned self ] cell , indexPath , _ in
cell . delegate = self
cell . updateUI ( statusID : self . statusID , state : self . statusState )
}
let accountCell = UICollectionView . CellRegistration < AccountCollectionViewCell , String > { [ unowned self ] cell , indexPath , item in
cell . delegate = self
cell . updateUI ( accountID : item )
}
let dataSource = UICollectionViewDiffableDataSource < Section , Item > ( collectionView : collectionView ) { collectionView , indexPath , itemIdentifier in
switch itemIdentifier {
case . status :
return collectionView . dequeueConfiguredReusableCell ( using : statusCell , for : indexPath , item : ( ) )
case . account ( let id ) :
return collectionView . dequeueConfiguredReusableCell ( using : accountCell , for : indexPath , item : id )
}
}
let sectionHeaderCell = UICollectionView . SupplementaryRegistration < UICollectionViewListCell > ( elementKind : UICollectionView . elementKindSectionFooter ) { ( headerView , collectionView , indexPath ) in
var config = headerView . defaultContentConfiguration ( )
config . text = NSLocalizedString ( " Favorite and reblog counts for posts originating from instances other than your own may not be accurate. " , comment : " shown on lists of status total actions " )
headerView . contentConfiguration = config
}
dataSource . supplementaryViewProvider = { collectionView , elementKind , indexPath in
return collectionView . dequeueConfiguredReusableSupplementary ( using : sectionHeaderCell , for : indexPath )
}
return dataSource
}
override func viewDidLoad ( ) {
super . viewDidLoad ( )
var snapshot = NSDiffableDataSourceSnapshot < Section , Item > ( )
snapshot . appendSections ( [ . status , . accounts ] )
snapshot . appendItems ( [ . status ] , toSection : . status )
if let accountIDs {
snapshot . appendItems ( accountIDs . map { . account ( $0 ) } , toSection : . accounts )
}
dataSource . apply ( snapshot , animatingDifferences : false )
}
override func viewWillAppear ( _ animated : Bool ) {
super . viewWillAppear ( animated )
2022-11-24 17:30:56 +00:00
collectionView . indexPathsForSelectedItems ? . forEach {
collectionView . deselectItem ( at : $0 , animated : true )
}
2022-11-22 20:21:36 +00:00
if accountIDs = = nil {
Task {
await loadAccounts ( )
}
}
}
private func loadAccounts ( ) async {
let request : Request < [ Account ] >
switch actionType {
case . favorite :
request = Status . getFavourites ( statusID )
case . reblog :
request = Status . getReblogs ( statusID )
}
do {
// TODO: p a g i n a t i o n
let ( accounts , _ ) = try await mastodonController . run ( request )
await withCheckedContinuation { continuation in
mastodonController . persistentContainer . addAll ( accounts : accounts ) {
continuation . resume ( )
}
}
accountIDs = accounts . map ( \ . id )
var snapshot = dataSource . snapshot ( )
snapshot . appendItems ( accounts . map { . account ( $0 . id ) } , toSection : . accounts )
dataSource . apply ( snapshot , animatingDifferences : true ) { }
} catch {
let config = ToastConfiguration ( from : error , with : " Error Loading Accounts " , in : self ) { toast in
toast . dismissToast ( animated : true )
await self . loadAccounts ( )
}
self . showToast ( configuration : config , animated : true )
}
}
}
extension StatusActionAccountListViewController {
enum ActionType {
case favorite , reblog
}
}
extension StatusActionAccountListViewController {
enum Section {
case status
case accounts
}
enum Item : Hashable {
case status
case account ( String )
}
}
extension StatusActionAccountListViewController : UICollectionViewDelegate {
func collectionView ( _ collectionView : UICollectionView , didSelectItemAt indexPath : IndexPath ) {
switch dataSource . itemIdentifier ( for : indexPath ) {
case nil :
return
case . status :
selected ( status : statusID , state : statusState . copy ( ) )
case . account ( let id ) :
selected ( account : id )
}
}
func collectionView ( _ collectionView : UICollectionView , contextMenuConfigurationForItemAt indexPath : IndexPath , point : CGPoint ) -> UIContextMenuConfiguration ? {
guard let item = dataSource . itemIdentifier ( for : indexPath ) ,
let cell = collectionView . cellForItem ( at : indexPath ) else {
return nil
}
switch item {
case . status :
return ( cell as ? TimelineStatusCollectionViewCell ) ? . contextMenuConfiguration ( )
case . account ( let id ) :
return UIContextMenuConfiguration {
ProfileViewController ( accountID : id , mastodonController : self . mastodonController )
} actionProvider : { _ in
2022-11-30 03:41:36 +00:00
UIMenu ( children : self . actionsForProfile ( accountID : id , source : . view ( cell ) ) )
2022-11-22 20:21:36 +00:00
}
}
}
func collectionView ( _ collectionView : UICollectionView , willPerformPreviewActionForMenuWith configuration : UIContextMenuConfiguration , animator : UIContextMenuInteractionCommitAnimating ) {
MenuPreviewHelper . willPerformPreviewAction ( animator : animator , presenter : self )
}
}
extension StatusActionAccountListViewController : UICollectionViewDragDelegate {
func collectionView ( _ collectionView : UICollectionView , itemsForBeginning session : UIDragSession , at indexPath : IndexPath ) -> [ UIDragItem ] {
guard let currentAccountID = mastodonController . accountInfo ? . id ,
let item = dataSource . itemIdentifier ( for : indexPath ) else {
return [ ]
}
let provider : NSItemProvider
switch item {
case . status :
guard let status = mastodonController . persistentContainer . status ( for : statusID ) else {
return [ ]
}
provider = NSItemProvider ( object : status . url ! as NSURL )
let activity = UserActivityManager . showConversationActivity ( mainStatusID : statusID , accountID : currentAccountID )
activity . displaysAuxiliaryScene = true
provider . registerObject ( activity , visibility : . all )
case . account ( let id ) :
guard let account = mastodonController . persistentContainer . account ( for : id ) else {
return [ ]
}
provider = NSItemProvider ( object : account . url as NSURL )
let activity = UserActivityManager . showProfileActivity ( id : account . id , accountID : currentAccountID )
activity . displaysAuxiliaryScene = true
provider . registerObject ( activity , visibility : . all )
}
return [ UIDragItem ( itemProvider : provider ) ]
}
}
extension StatusActionAccountListViewController : TuskerNavigationDelegate {
var apiController : MastodonController ! { mastodonController }
}
extension StatusActionAccountListViewController : MenuActionProvider {
}
extension StatusActionAccountListViewController : StatusCollectionViewCellDelegate {
func statusCellNeedsReconfigure ( _ cell : StatusCollectionViewCell , animated : Bool , completion : ( ( ) -> Void ) ? ) {
if let indexPath = collectionView . indexPath ( for : cell ) {
var snapshot = dataSource . snapshot ( )
snapshot . reconfigureItems ( [ dataSource . itemIdentifier ( for : indexPath ) ! ] )
dataSource . apply ( snapshot , animatingDifferences : animated , completion : completion )
}
}
}
extension StatusActionAccountListViewController : StatusBarTappableViewController {
func handleStatusBarTapped ( xPosition : CGFloat ) -> StatusBarTapActionResult {
collectionView . scrollToTop ( )
return . stop
}
}