2021-06-21 13:52:44 +02:00
//
// A s y n c H o m e T i m e l i n e V i e w C o n t r o l l e r + D e b u g A c t i o n . s w i f t
// M a s t o d o n
//
// C r e a t e d b y M a i n a s u K C i r n o o n 2 0 2 1 - 6 - 2 1 .
//
2021-06-22 07:41:40 +02:00
#if ASDK && DEBUG
2021-06-21 13:52:44 +02:00
import os . log
import UIKit
import CoreData
import CoreDataStack
import FLEX
extension AsyncHomeTimelineViewController {
var debugMenu : UIMenu {
let menu = UIMenu (
title : " Debug Tools " ,
image : nil ,
identifier : nil ,
options : . displayInline ,
children : [
UIAction ( title : " Show FLEX " , image : nil , attributes : [ ] , handler : { [ weak self ] action in
guard let self = self else { return }
self . showFLEXAction ( action )
} ) ,
moveMenu ,
dropMenu ,
UIAction ( title : " Show Welcome " , image : UIImage ( systemName : " figure.walk " ) , attributes : [ ] ) { [ weak self ] action in
guard let self = self else { return }
self . showWelcomeAction ( action )
} ,
UIAction ( title : " Show Or Remove EmptyView " , image : UIImage ( systemName : " clear " ) , attributes : [ ] ) { [ weak self ] action in
guard let self = self else { return }
if self . emptyView . superview != nil {
self . emptyView . removeFromSuperview ( )
} else {
self . showEmptyView ( )
}
} ,
UIAction ( title : " Show Public Timeline " , image : UIImage ( systemName : " list.dash " ) , attributes : [ ] ) { [ weak self ] action in
guard let self = self else { return }
self . showPublicTimelineAction ( action )
} ,
UIAction ( title : " Show Profile " , image : UIImage ( systemName : " person.crop.circle " ) , attributes : [ ] ) { [ weak self ] action in
guard let self = self else { return }
self . showProfileAction ( action )
} ,
UIAction ( title : " Show Thread " , image : UIImage ( systemName : " bubble.left.and.bubble.right " ) , attributes : [ ] ) { [ weak self ] action in
guard let self = self else { return }
self . showThreadAction ( action )
} ,
UIAction ( title : " Settings " , image : UIImage ( systemName : " gear " ) , attributes : [ ] ) { [ weak self ] action in
guard let self = self else { return }
self . showSettings ( action )
} ,
UIAction ( title : " Sign Out " , image : UIImage ( systemName : " escape " ) , attributes : . destructive ) { [ weak self ] action in
guard let self = self else { return }
self . signOutAction ( action )
}
]
)
return menu
}
var moveMenu : UIMenu {
return UIMenu (
title : " Move to… " ,
image : UIImage ( systemName : " arrow.forward.circle " ) ,
identifier : nil ,
options : [ ] ,
children : [
UIAction ( title : " First Gap " , image : nil , attributes : [ ] , handler : { [ weak self ] action in
guard let self = self else { return }
self . moveToTopGapAction ( action )
} ) ,
UIAction ( title : " First Replied Status " , image : nil , attributes : [ ] , handler : { [ weak self ] action in
guard let self = self else { return }
self . moveToFirstRepliedStatus ( action )
} ) ,
UIAction ( title : " First Reblog Status " , image : nil , attributes : [ ] , handler : { [ weak self ] action in
guard let self = self else { return }
self . moveToFirstReblogStatus ( action )
} ) ,
UIAction ( title : " First Poll Status " , image : nil , attributes : [ ] , handler : { [ weak self ] action in
guard let self = self else { return }
self . moveToFirstPollStatus ( action )
} ) ,
UIAction ( title : " First Audio Status " , image : nil , attributes : [ ] , handler : { [ weak self ] action in
guard let self = self else { return }
self . moveToFirstAudioStatus ( action )
} ) ,
UIAction ( title : " First Video Status " , image : nil , attributes : [ ] , handler : { [ weak self ] action in
guard let self = self else { return }
self . moveToFirstVideoStatus ( action )
} ) ,
UIAction ( title : " First GIF status " , image : nil , attributes : [ ] , handler : { [ weak self ] action in
guard let self = self else { return }
self . moveToFirstGIFStatus ( action )
} ) ,
]
)
}
var dropMenu : UIMenu {
return UIMenu (
title : " Drop… " ,
image : UIImage ( systemName : " minus.circle " ) ,
identifier : nil ,
options : [ ] ,
children : [ 10 , 50 , 100 , 150 , 200 , 250 , 300 ] . map { count in
UIAction ( title : " Drop Recent \( count ) Statuses " , image : nil , attributes : [ ] , handler : { [ weak self ] action in
guard let self = self else { return }
self . dropRecentStatusAction ( action , count : count )
} )
}
)
}
}
extension AsyncHomeTimelineViewController {
@objc private func showFLEXAction ( _ sender : UIAction ) {
FLEXManager . shared . showExplorer ( )
}
@objc private func moveToTopGapAction ( _ sender : UIAction ) {
guard let diffableDataSource = viewModel . diffableDataSource else { return }
let snapshotTransitioning = diffableDataSource . snapshot ( )
let item = snapshotTransitioning . itemIdentifiers . first ( where : { item in
switch item {
case . homeMiddleLoader : return true
default : return false
}
} )
if let targetItem = item , let index = snapshotTransitioning . indexOfItem ( targetItem ) {
tableView . scrollToRow ( at : IndexPath ( row : index , section : 0 ) , at : . middle , animated : true )
}
}
@objc private func moveToFirstReblogStatus ( _ sender : UIAction ) {
guard let diffableDataSource = viewModel . diffableDataSource else { return }
let snapshotTransitioning = diffableDataSource . snapshot ( )
let item = snapshotTransitioning . itemIdentifiers . first ( where : { item in
switch item {
case . homeTimelineIndex ( let objectID , _ ) :
let homeTimelineIndex = viewModel . fetchedResultsController . managedObjectContext . object ( with : objectID ) as ! HomeTimelineIndex
return homeTimelineIndex . status . reblog != nil
default :
return false
}
} )
if let targetItem = item , let index = snapshotTransitioning . indexOfItem ( targetItem ) {
tableView . scrollToRow ( at : IndexPath ( row : index , section : 0 ) , at : . middle , animated : true )
tableView . blinkRow ( at : IndexPath ( row : index , section : 0 ) )
} else {
print ( " Not found reblog status " )
}
}
@objc private func moveToFirstPollStatus ( _ sender : UIAction ) {
guard let diffableDataSource = viewModel . diffableDataSource else { return }
let snapshotTransitioning = diffableDataSource . snapshot ( )
let item = snapshotTransitioning . itemIdentifiers . first ( where : { item in
switch item {
case . homeTimelineIndex ( let objectID , _ ) :
let homeTimelineIndex = viewModel . fetchedResultsController . managedObjectContext . object ( with : objectID ) as ! HomeTimelineIndex
let post = homeTimelineIndex . status . reblog ? ? homeTimelineIndex . status
return post . poll != nil
default :
return false
}
} )
if let targetItem = item , let index = snapshotTransitioning . indexOfItem ( targetItem ) {
tableView . scrollToRow ( at : IndexPath ( row : index , section : 0 ) , at : . middle , animated : true )
tableView . blinkRow ( at : IndexPath ( row : index , section : 0 ) )
} else {
print ( " Not found poll status " )
}
}
@objc private func moveToFirstRepliedStatus ( _ sender : UIAction ) {
guard let diffableDataSource = viewModel . diffableDataSource else { return }
let snapshotTransitioning = diffableDataSource . snapshot ( )
let item = snapshotTransitioning . itemIdentifiers . first ( where : { item in
switch item {
case . homeTimelineIndex ( let objectID , _ ) :
let homeTimelineIndex = viewModel . fetchedResultsController . managedObjectContext . object ( with : objectID ) as ! HomeTimelineIndex
guard homeTimelineIndex . status . inReplyToID != nil else {
return false
}
return true
default :
return false
}
} )
if let targetItem = item , let index = snapshotTransitioning . indexOfItem ( targetItem ) {
tableView . scrollToRow ( at : IndexPath ( row : index , section : 0 ) , at : . middle , animated : true )
tableView . blinkRow ( at : IndexPath ( row : index , section : 0 ) )
} else {
print ( " Not found replied status " )
}
}
@objc private func moveToFirstAudioStatus ( _ sender : UIAction ) {
guard let diffableDataSource = viewModel . diffableDataSource else { return }
let snapshotTransitioning = diffableDataSource . snapshot ( )
let item = snapshotTransitioning . itemIdentifiers . first ( where : { item in
switch item {
case . homeTimelineIndex ( let objectID , _ ) :
let homeTimelineIndex = viewModel . fetchedResultsController . managedObjectContext . object ( with : objectID ) as ! HomeTimelineIndex
let status = homeTimelineIndex . status . reblog ? ? homeTimelineIndex . status
return status . mediaAttachments ? . contains ( where : { $0 . type = = . audio } ) ? ? false
default :
return false
}
} )
if let targetItem = item , let index = snapshotTransitioning . indexOfItem ( targetItem ) {
tableView . scrollToRow ( at : IndexPath ( row : index , section : 0 ) , at : . middle , animated : true )
tableView . blinkRow ( at : IndexPath ( row : index , section : 0 ) )
} else {
print ( " Not found audio status " )
}
}
@objc private func moveToFirstVideoStatus ( _ sender : UIAction ) {
guard let diffableDataSource = viewModel . diffableDataSource else { return }
let snapshotTransitioning = diffableDataSource . snapshot ( )
let item = snapshotTransitioning . itemIdentifiers . first ( where : { item in
switch item {
case . homeTimelineIndex ( let objectID , _ ) :
let homeTimelineIndex = viewModel . fetchedResultsController . managedObjectContext . object ( with : objectID ) as ! HomeTimelineIndex
let status = homeTimelineIndex . status . reblog ? ? homeTimelineIndex . status
return status . mediaAttachments ? . contains ( where : { $0 . type = = . video } ) ? ? false
default :
return false
}
} )
if let targetItem = item , let index = snapshotTransitioning . indexOfItem ( targetItem ) {
tableView . scrollToRow ( at : IndexPath ( row : index , section : 0 ) , at : . middle , animated : true )
tableView . blinkRow ( at : IndexPath ( row : index , section : 0 ) )
} else {
print ( " Not found video status " )
}
}
@objc private func moveToFirstGIFStatus ( _ sender : UIAction ) {
guard let diffableDataSource = viewModel . diffableDataSource else { return }
let snapshotTransitioning = diffableDataSource . snapshot ( )
let item = snapshotTransitioning . itemIdentifiers . first ( where : { item in
switch item {
case . homeTimelineIndex ( let objectID , _ ) :
let homeTimelineIndex = viewModel . fetchedResultsController . managedObjectContext . object ( with : objectID ) as ! HomeTimelineIndex
let status = homeTimelineIndex . status . reblog ? ? homeTimelineIndex . status
return status . mediaAttachments ? . contains ( where : { $0 . type = = . gifv } ) ? ? false
default :
return false
}
} )
if let targetItem = item , let index = snapshotTransitioning . indexOfItem ( targetItem ) {
tableView . scrollToRow ( at : IndexPath ( row : index , section : 0 ) , at : . middle , animated : true )
tableView . blinkRow ( at : IndexPath ( row : index , section : 0 ) )
} else {
print ( " Not found GIF status " )
}
}
@objc private func dropRecentStatusAction ( _ sender : UIAction , count : Int ) {
guard let diffableDataSource = viewModel . diffableDataSource else { return }
let snapshotTransitioning = diffableDataSource . snapshot ( )
let droppingObjectIDs = snapshotTransitioning . itemIdentifiers . prefix ( count ) . compactMap { item -> NSManagedObjectID ? in
switch item {
case . homeTimelineIndex ( let objectID , _ ) : return objectID
default : return nil
}
}
var droppingStatusObjectIDs : [ NSManagedObjectID ] = [ ]
context . apiService . backgroundManagedObjectContext . performChanges { [ weak self ] in
guard let self = self else { return }
for objectID in droppingObjectIDs {
guard let homeTimelineIndex = try ? self . context . apiService . backgroundManagedObjectContext . existingObject ( with : objectID ) as ? HomeTimelineIndex else { continue }
droppingStatusObjectIDs . append ( homeTimelineIndex . status . objectID )
self . context . apiService . backgroundManagedObjectContext . delete ( homeTimelineIndex )
}
}
. sink { [ weak self ] result in
guard let self = self else { return }
switch result {
case . success :
self . context . apiService . backgroundManagedObjectContext . performChanges { [ weak self ] in
guard let self = self else { return }
for objectID in droppingStatusObjectIDs {
guard let post = try ? self . context . apiService . backgroundManagedObjectContext . existingObject ( with : objectID ) as ? Status else { continue }
self . context . apiService . backgroundManagedObjectContext . delete ( post )
}
}
. sink { _ in
// d o n o t h i n g
}
. store ( in : & self . disposeBag )
case . failure ( let error ) :
assertionFailure ( error . localizedDescription )
}
}
. store ( in : & disposeBag )
}
@objc private func showWelcomeAction ( _ sender : UIAction ) {
coordinator . present ( scene : . welcome , from : self , transition : . modal ( animated : true , completion : nil ) )
}
@objc private func showPublicTimelineAction ( _ sender : UIAction ) {
coordinator . present ( scene : . publicTimeline , from : self , transition : . show )
}
@objc private func showProfileAction ( _ sender : UIAction ) {
let alertController = UIAlertController ( title : " Enter User ID " , message : nil , preferredStyle : . alert )
alertController . addTextField ( )
let showAction = UIAlertAction ( title : " Show " , style : . default ) { [ weak self , weak alertController ] _ in
guard let self = self else { return }
guard let textField = alertController ? . textFields ? . first else { return }
let profileViewModel = RemoteProfileViewModel ( context : self . context , userID : textField . text ? ? " " )
self . coordinator . present ( scene : . profile ( viewModel : profileViewModel ) , from : self , transition : . show )
}
alertController . addAction ( showAction )
let cancelAction = UIAlertAction ( title : " Cancel " , style : . cancel , handler : nil )
alertController . addAction ( cancelAction )
coordinator . present ( scene : . alertController ( alertController : alertController ) , from : self , transition : . alertController ( animated : true , completion : nil ) )
}
@objc private func showThreadAction ( _ sender : UIAction ) {
let alertController = UIAlertController ( title : " Enter Status ID " , message : nil , preferredStyle : . alert )
alertController . addTextField ( )
let showAction = UIAlertAction ( title : " Show " , style : . default ) { [ weak self , weak alertController ] _ in
guard let self = self else { return }
guard let textField = alertController ? . textFields ? . first else { return }
let threadViewModel = RemoteThreadViewModel ( context : self . context , statusID : textField . text ? ? " " )
self . coordinator . present ( scene : . thread ( viewModel : threadViewModel ) , from : self , transition : . show )
}
alertController . addAction ( showAction )
let cancelAction = UIAlertAction ( title : " Cancel " , style : . cancel , handler : nil )
alertController . addAction ( cancelAction )
coordinator . present ( scene : . alertController ( alertController : alertController ) , from : self , transition : . alertController ( animated : true , completion : nil ) )
}
@objc private func showSettings ( _ sender : UIAction ) {
guard let currentSetting = context . settingService . currentSetting . value else { return }
let settingsViewModel = SettingsViewModel ( context : context , setting : currentSetting )
coordinator . present (
scene : . settings ( viewModel : settingsViewModel ) ,
from : self ,
transition : . modal ( animated : true , completion : nil )
)
}
@objc func signOutAction ( _ sender : UIAction ) {
guard let activeMastodonAuthenticationBox = context . authenticationService . activeMastodonAuthenticationBox . value else {
return
}
context . authenticationService . signOutMastodonUser (
domain : activeMastodonAuthenticationBox . domain ,
userID : activeMastodonAuthenticationBox . userID
)
. receive ( on : DispatchQueue . main )
. sink { [ weak self ] result in
guard let self = self else { return }
switch result {
case . failure ( let error ) :
assertionFailure ( error . localizedDescription )
case . success ( let isSignOut ) :
os_log ( . info , log : . debug , " %{public}s[%{public}ld], %{public}s: sign out %s " , ( ( #file as NSString ) . lastPathComponent ) , #line , #function , isSignOut ? " success " : " fail " )
guard isSignOut else { return }
self . coordinator . setup ( )
self . coordinator . setupOnboardingIfNeeds ( animated : true )
}
}
. store ( in : & disposeBag )
}
}
2021-06-22 07:41:40 +02:00
2021-06-21 13:52:44 +02:00
#endif