2021-02-08 00:39:22 +00:00
//
// P r o f i l e D i r e c t o r y 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 2 / 6 / 2 1 .
// C o p y r i g h t © 2 0 2 1 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 ProfileDirectoryViewController : UIViewController {
weak var mastodonController : MastodonController !
private var collectionView : UICollectionView !
private var dataSource : UICollectionViewDiffableDataSource < Section , Item > !
2022-06-11 03:36:40 +00:00
private var scope : Scope = . everywhere
private var order : DirectoryOrder = . active
2021-02-08 00:39:22 +00:00
init ( mastodonController : MastodonController ) {
self . mastodonController = mastodonController
super . init ( nibName : nil , bundle : nil )
}
required init ? ( coder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
override func viewDidLoad ( ) {
super . viewDidLoad ( )
title = NSLocalizedString ( " Profile Directory " , comment : " profile directory title " )
2022-06-11 03:36:40 +00:00
// t o d o : i t w o u l d b e n i c e i f t h e r e w e r e a b e t t e r " f i l t e r " i c o n
navigationItem . rightBarButtonItem = UIBarButtonItem ( image : UIImage ( systemName : " scope " ) , menu : nil )
updateFilterMenu ( )
2021-02-08 00:39:22 +00:00
let layout = UICollectionViewCompositionalLayout ( sectionProvider : { ( sectionIndex , layoutEnvironment ) in
let itemHeight = NSCollectionLayoutDimension . absolute ( 200 )
let itemWidth : NSCollectionLayoutDimension
if case . compact = layoutEnvironment . traitCollection . horizontalSizeClass {
itemWidth = . fractionalWidth ( 1 )
} else {
2022-01-26 02:34:21 +00:00
itemWidth = . absolute ( ( layoutEnvironment . container . contentSize . width - 16 - 8 * 2 ) / 2 )
2021-02-08 00:39:22 +00:00
}
let itemSize = NSCollectionLayoutSize ( widthDimension : itemWidth , heightDimension : itemHeight )
let item = NSCollectionLayoutItem ( layoutSize : itemSize )
let itemB = NSCollectionLayoutItem ( layoutSize : itemSize )
let groupSize = NSCollectionLayoutSize ( widthDimension : . fractionalWidth ( 1 ) , heightDimension : itemHeight )
let group = NSCollectionLayoutGroup . horizontal ( layoutSize : groupSize , subitems : [ item , itemB ] )
2022-01-26 02:34:21 +00:00
group . interItemSpacing = . flexible ( 16 )
2021-02-08 00:39:22 +00:00
let section = NSCollectionLayoutSection ( group : group )
2022-01-26 02:34:21 +00:00
section . interGroupSpacing = 16
section . contentInsets = NSDirectionalEdgeInsets ( top : 8 , leading : 8 , bottom : 8 , trailing : 8 )
2021-02-08 00:39:22 +00:00
return section
2022-06-11 03:36:40 +00:00
} )
2021-02-08 00:39:22 +00:00
collectionView = UICollectionView ( frame : view . bounds , collectionViewLayout : layout )
collectionView . autoresizingMask = [ . flexibleWidth , . flexibleHeight ]
collectionView . backgroundColor = . secondarySystemBackground
collectionView . register ( UINib ( nibName : " FeaturedProfileCollectionViewCell " , bundle : . main ) , forCellWithReuseIdentifier : " featuredProfileCell " )
collectionView . delegate = self
collectionView . dragDelegate = self
view . addSubview ( collectionView )
dataSource = createDataSource ( )
2022-06-11 03:36:40 +00:00
updateProfiles ( )
2021-02-08 00:39:22 +00:00
}
private func createDataSource ( ) -> UICollectionViewDiffableDataSource < Section , Item > {
let dataSource = UICollectionViewDiffableDataSource < Section , Item > ( collectionView : collectionView ) { ( collectionView , indexPath , item ) in
guard case let . account ( account ) = item else { fatalError ( ) }
let cell = collectionView . dequeueReusableCell ( withReuseIdentifier : " featuredProfileCell " , for : indexPath ) as ! FeaturedProfileCollectionViewCell
cell . updateUI ( account : account )
return cell
}
return dataSource
}
2022-06-11 03:36:40 +00:00
private func updateFilterMenu ( ) {
let scopeMenu = UIMenu ( options : . displayInline , children : [
UIAction ( title : " Everywhere " , subtitle : " Users from the whole network " , image : UIImage ( systemName : " globe " ) , state : scope = = . everywhere ? . on : . off , handler : { [ unowned self ] _ in
self . scope = . everywhere
self . updateFilterMenu ( )
self . updateProfiles ( )
} ) ,
UIAction ( title : mastodonController . accountInfo ! . instanceURL . host ! , subtitle : " Only users from your instance " , image : UIImage ( systemName : " house " ) , state : scope = = . instance ? . on : . off , handler : { [ unowned self ] _ in
self . scope = . instance
self . updateFilterMenu ( )
self . updateProfiles ( )
} ) ,
] )
let orderMenu = UIMenu ( options : . displayInline , children : DirectoryOrder . allCases . map { order in
UIAction ( title : order . title , subtitle : order . subtitle , image : nil , state : self . order = = order ? . on : . off ) { [ unowned self ] _ in
self . order = order
self . updateFilterMenu ( )
self . updateProfiles ( )
}
} )
navigationItem . rightBarButtonItem ! . menu = UIMenu ( children : [
scopeMenu ,
orderMenu ,
] )
}
private func updateProfiles ( ) {
let scope = self . scope
let order = self . order
2022-11-11 23:30:09 +00:00
let local = scope = = . instance
2021-02-08 00:39:22 +00:00
let request = Client . getFeaturedProfiles ( local : local , order : order )
mastodonController . run ( request ) { ( response ) in
2022-06-11 03:36:40 +00:00
guard case let . success ( accounts , _ ) = response ,
self . scope = = scope ,
self . order = = order else {
2021-02-08 00:39:22 +00:00
return
}
var snapshot = NSDiffableDataSourceSnapshot < Section , Item > ( )
snapshot . appendSections ( [ . featuredProfiles ] )
snapshot . appendItems ( accounts . map { . account ( $0 ) } )
DispatchQueue . main . async {
self . dataSource . apply ( snapshot )
}
}
}
}
extension ProfileDirectoryViewController {
enum Section {
case featuredProfiles
}
enum Item : Hashable {
case account ( Account )
func hash ( into hasher : inout Hasher ) {
guard case let . account ( account ) = self else { return }
hasher . combine ( account . id )
}
}
}
extension ProfileDirectoryViewController : TuskerNavigationDelegate {
2022-10-31 20:27:13 +00:00
var apiController : MastodonController ! { mastodonController }
2021-02-08 00:39:22 +00:00
}
2022-05-02 03:04:56 +00:00
extension ProfileDirectoryViewController : ToastableViewController {
}
extension ProfileDirectoryViewController : MenuActionProvider {
2021-02-08 00:39:22 +00:00
}
extension ProfileDirectoryViewController : UICollectionViewDelegate {
func collectionView ( _ collectionView : UICollectionView , didSelectItemAt indexPath : IndexPath ) {
guard let item = dataSource . itemIdentifier ( for : indexPath ) ,
case let . account ( account ) = item else {
return
}
show ( ProfileViewController ( accountID : account . id , mastodonController : mastodonController ) , sender : nil )
}
func collectionView ( _ collectionView : UICollectionView , contextMenuConfigurationForItemAt indexPath : IndexPath , point : CGPoint ) -> UIContextMenuConfiguration ? {
guard let item = dataSource . itemIdentifier ( for : indexPath ) ,
case let . account ( account ) = item else {
return nil
}
return UIContextMenuConfiguration ( identifier : nil ) {
return ProfileViewController ( accountID : account . id , mastodonController : self . mastodonController )
} actionProvider : { ( _ ) in
let actions = self . actionsForProfile ( accountID : account . id , sourceView : self . collectionView . cellForItem ( at : indexPath ) )
return UIMenu ( children : actions )
}
}
func collectionView ( _ collectionView : UICollectionView , willPerformPreviewActionForMenuWith configuration : UIContextMenuConfiguration , animator : UIContextMenuInteractionCommitAnimating ) {
if let viewController = animator . previewViewController {
animator . preferredCommitStyle = . pop
animator . addCompletion {
self . show ( viewController , sender : nil )
}
}
}
}
extension ProfileDirectoryViewController : UICollectionViewDragDelegate {
func collectionView ( _ collectionView : UICollectionView , itemsForBeginning session : UIDragSession , at indexPath : IndexPath ) -> [ UIDragItem ] {
guard let item = dataSource . itemIdentifier ( for : indexPath ) ,
case let . account ( account ) = item ,
let currentAccountID = mastodonController . accountInfo ? . id else {
return [ ]
}
let provider = NSItemProvider ( object : account . url as NSURL )
let activity = UserActivityManager . showProfileActivity ( id : account . id , accountID : currentAccountID )
2022-05-13 21:10:18 +00:00
activity . displaysAuxiliaryScene = true
2021-02-08 00:39:22 +00:00
provider . registerObject ( activity , visibility : . all )
return [ UIDragItem ( itemProvider : provider ) ]
}
}
2022-06-11 03:36:40 +00:00
extension ProfileDirectoryViewController {
enum Scope : CaseIterable {
case instance , everywhere
}
}
private extension DirectoryOrder {
var title : String {
switch self {
case . active :
return " Active "
case . new :
return " New "
}
}
var subtitle : String {
switch self {
case . active :
return " Users who have posted lately "
case . new :
return " Recently joined users "
}
}
}