2018-10-12 01:20:58 +00:00
//
// P r e v i e w V i e w C o n t r o l l e r P r o v i d 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 1 0 / 1 0 / 1 8 .
// C o p y r i g h t © 2 0 1 8 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
2018-10-20 19:35:56 +00:00
import SafariServices
2019-06-04 21:04:37 +00:00
import Pachyderm
2022-07-09 15:45:27 +00:00
import WebURLFoundationExtras
2022-11-12 04:29:15 +00:00
import SwiftUI
2018-10-12 01:20:58 +00:00
2022-05-02 03:04:56 +00:00
protocol MenuActionProvider : AnyObject {
var navigationDelegate : TuskerNavigationDelegate ? { get }
var toastableViewController : ToastableViewController ? { get }
}
2021-05-22 17:42:53 +00:00
protocol MenuPreviewProvider : AnyObject {
2020-06-27 04:22:14 +00:00
typealias PreviewProviders = ( content : UIContextMenuContentPreviewProvider , actions : ( ) -> [ UIMenuElement ] )
2019-06-04 21:04:37 +00:00
func getPreviewProviders ( for location : CGPoint , sourceViewController : UIViewController ) -> PreviewProviders ?
2018-10-12 01:20:58 +00:00
}
2020-03-21 02:13:04 +00:00
protocol CustomPreviewPresenting {
func presentFromPreview ( presenter : UIViewController )
}
2022-05-02 03:04:56 +00:00
extension MenuActionProvider where Self : TuskerNavigationDelegate {
var navigationDelegate : TuskerNavigationDelegate ? { self }
}
extension MenuActionProvider where Self : ToastableViewController {
var toastableViewController : ToastableViewController ? { self }
}
extension MenuActionProvider {
2019-06-04 21:04:37 +00:00
2020-01-06 00:54:28 +00:00
private var mastodonController : MastodonController ? { navigationDelegate ? . apiController }
2022-12-22 23:27:04 +00:00
func actionsForProfile ( accountID : String , source : PopoverSource , fetchRelationship : Bool = true ) -> [ UIMenuElement ] {
2020-01-06 00:54:28 +00:00
guard let mastodonController = mastodonController ,
2020-05-02 23:52:35 +00:00
let account = mastodonController . persistentContainer . account ( for : accountID ) else { return [ ] }
2020-06-27 04:22:14 +00:00
2022-11-12 00:07:50 +00:00
var shareSection = [
openInSafariAction ( url : account . url ) ,
2022-11-30 03:41:36 +00:00
createAction ( identifier : " share " , title : " Share " , systemImageName : " square.and.arrow.up " , handler : { [ weak self ] ( _ ) in
2022-11-12 00:07:50 +00:00
guard let self = self else { return }
2022-11-30 03:41:36 +00:00
self . navigationDelegate ? . showMoreOptions ( forAccount : accountID , source : source )
2022-11-12 00:07:50 +00:00
} )
]
2021-06-10 14:52:27 +00:00
guard let loggedInAccountID = mastodonController . accountInfo ? . id else {
// u s e r i s l o g g e d o u t
2022-11-12 00:07:50 +00:00
return shareSection
2020-09-13 19:51:06 +00:00
}
2022-11-12 00:07:50 +00:00
var actionsSection : [ UIMenuElement ] = [
2020-07-08 03:23:39 +00:00
createAction ( identifier : " sendmessage " , title : " Send Message " , systemImageName : " envelope " , handler : { [ weak self ] ( _ ) in
guard let self = self else { return }
2022-04-02 15:39:13 +00:00
let draft = self . mastodonController ! . createDraft ( mentioningAcct : account . acct )
draft . visibility = . direct
self . navigationDelegate ? . compose ( editing : draft )
2022-03-29 16:52:14 +00:00
} )
]
2022-11-12 00:07:50 +00:00
var suppressSection : [ UIMenuElement ] = [ ]
2020-06-27 04:22:14 +00:00
2022-11-19 19:10:19 +00:00
if let ownAccount = mastodonController . account ,
accountID != ownAccount . id {
2022-12-22 23:27:04 +00:00
actionsSection . append ( relationshipAction ( fetchRelationship , accountID : accountID , mastodonController : mastodonController , builder : { [ unowned self ] in self . followAction ( for : $0 , mastodonController : $1 ) } ) )
2022-11-19 19:16:31 +00:00
actionsSection . append ( UIDeferredMenuElement . uncached ( { elementHandler in
2023-01-15 16:49:20 +00:00
var listActions = mastodonController . lists . map { list in
UIAction ( title : list . title , image : UIImage ( systemName : " list.bullet " ) ) { [ unowned self ] _ in
2022-11-19 19:16:31 +00:00
let req = List . add ( list , accounts : [ accountID ] )
mastodonController . run ( req ) { response in
if case . failure ( let error ) = response {
self . handleError ( error , title : " Error Adding to List " )
}
}
}
}
2023-01-15 16:49:20 +00:00
listActions . append ( UIAction ( title : " New List… " , image : UIImage ( systemName : " plus " ) , handler : { [ unowned self ] _ in
Task { @ MainActor in
let service = CreateListService ( mastodonController : mastodonController , present : { [ unowned self ] in
self . navigationDelegate ! . present ( $0 , animated : true )
} ) { list in
let req = List . add ( list , accounts : [ accountID ] )
mastodonController . run ( req ) { response in
if case . failure ( let error ) = response {
self . handleError ( error , title : " Error Adding to List " )
}
}
}
service . run ( )
}
} ) )
2022-11-19 19:16:31 +00:00
elementHandler ( [ UIMenu ( title : " Add to List " , image : UIImage ( systemName : " list.bullet " ) , children : listActions ) ] )
} ) )
2022-12-22 23:27:04 +00:00
suppressSection . append ( relationshipAction ( fetchRelationship , accountID : accountID , mastodonController : mastodonController , builder : { [ unowned self ] in self . blockAction ( for : $0 , mastodonController : $1 ) } ) )
suppressSection . append ( relationshipAction ( fetchRelationship , accountID : accountID , mastodonController : mastodonController , builder : { [ unowned self ] in self . muteAction ( for : $0 , mastodonController : $1 ) } ) )
2023-01-14 16:03:39 +00:00
suppressSection . append ( createAction ( identifier : " report " , title : " Report " , systemImageName : " flag " , handler : { [ unowned self ] _ in
let view = ReportView ( report : EditedReport ( accountID : accountID ) , mastodonController : mastodonController )
let host = UIHostingController ( rootView : view )
self . navigationDelegate ? . present ( host , animated : true )
} ) )
2022-11-12 00:07:50 +00:00
}
2020-06-27 04:22:14 +00:00
2021-06-10 14:52:27 +00:00
addOpenInNewWindow ( actions : & shareSection , activity : UserActivityManager . showProfileActivity ( id : accountID , accountID : loggedInAccountID ) )
2020-06-27 04:22:14 +00:00
return [
2022-11-12 00:07:50 +00:00
UIMenu ( options : . displayInline , children : shareSection ) ,
UIMenu ( options : . displayInline , children : actionsSection ) ,
UIMenu ( options : . displayInline , children : suppressSection ) ,
2020-06-27 04:22:14 +00:00
]
2018-10-12 01:20:58 +00:00
}
2022-11-30 03:41:36 +00:00
func actionsForURL ( _ url : URL , source : PopoverSource ) -> [ UIAction ] {
2019-06-04 21:04:37 +00:00
return [
2022-05-02 03:04:56 +00:00
openInSafariAction ( url : url ) ,
2022-11-30 03:41:36 +00:00
createAction ( identifier : " share " , title : " Share " , systemImageName : " square.and.arrow.up " , handler : { [ weak self ] ( _ ) in
2020-07-08 03:23:39 +00:00
guard let self = self else { return }
2022-11-30 03:41:36 +00:00
self . navigationDelegate ? . showMoreOptions ( forURL : url , source : source )
2019-07-27 22:53:07 +00:00
} )
2019-06-04 21:04:37 +00:00
]
2018-10-12 01:20:58 +00:00
}
2019-06-04 21:04:37 +00:00
2022-11-30 03:41:36 +00:00
func actionsForHashtag ( _ hashtag : Hashtag , source : PopoverSource ) -> [ UIMenuElement ] {
2022-11-30 03:52:31 +00:00
var actionsSection : [ UIMenuElement ] = [ ]
2022-05-11 02:57:46 +00:00
if let mastodonController = mastodonController ,
mastodonController . loggedIn {
2022-11-30 03:52:31 +00:00
let name = hashtag . name . lowercased ( )
2022-05-11 02:57:46 +00:00
let context = mastodonController . persistentContainer . viewContext
2022-12-19 15:58:14 +00:00
let existing = try ? context . fetch ( SavedHashtag . fetchRequest ( name : name , account : mastodonController . accountInfo ! ) ) . first
2022-11-30 03:52:31 +00:00
let saveSubtitle = " Saved hashtags appear in the Explore section of Tusker "
let saveImage = UIImage ( systemName : existing != nil ? " minus " : " plus " )
2022-05-11 02:57:46 +00:00
actionsSection = [
2022-11-30 03:52:31 +00:00
UIAction ( title : existing != nil ? " Unsave Hashtag " : " Save Hashtag " , subtitle : saveSubtitle , image : saveImage , handler : { ( _ ) in
2022-05-11 02:57:46 +00:00
if let existing = existing {
context . delete ( existing )
} else {
2022-12-19 15:58:14 +00:00
_ = SavedHashtag ( hashtag : hashtag , account : mastodonController . accountInfo ! , context : context )
2022-05-11 02:57:46 +00:00
}
2022-10-09 21:06:10 +00:00
mastodonController . persistentContainer . save ( context : context )
2022-05-11 02:57:46 +00:00
} )
]
2022-11-30 03:52:31 +00:00
if mastodonController . instanceFeatures . canFollowHashtags {
let existing = mastodonController . followedHashtags . first ( where : { $0 . name . lowercased ( ) = = name } )
let subtitle = " Posts tagged with followed hashtags appear in your Home timeline "
let image = UIImage ( systemName : existing != nil ? " person.badge.minus " : " person.badge.plus " )
actionsSection . append ( UIAction ( title : existing != nil ? " Unfollow " : " Follow " , subtitle : subtitle , image : image ) { [ unowned self ] _ in
Task {
await ToggleFollowHashtagService ( hashtag : hashtag , presenter : navigationDelegate ! ) . toggleFollow ( )
}
} )
}
2022-05-11 02:57:46 +00:00
}
2020-06-27 04:22:14 +00:00
2022-07-09 15:45:27 +00:00
let shareSection : [ UIMenuElement ]
if let url = URL ( hashtag . url ) {
2022-11-30 03:41:36 +00:00
shareSection = actionsForURL ( url , source : source )
2022-07-09 15:45:27 +00:00
} else {
shareSection = [ ]
}
2020-06-27 04:22:14 +00:00
return [
2020-08-15 21:19:45 +00:00
UIMenu ( title : " " , image : nil , identifier : nil , options : [ . displayInline ] , children : shareSection ) ,
2020-06-27 04:22:14 +00:00
UIMenu ( title : " " , image : nil , identifier : nil , options : [ . displayInline ] , children : actionsSection ) ,
]
2019-06-04 21:04:37 +00:00
}
2022-11-30 03:41:36 +00:00
func actionsForStatus ( _ status : StatusMO , source : PopoverSource , includeStatusButtonActions : Bool = true ) -> [ UIMenuElement ] {
2020-12-24 22:13:44 +00:00
guard let mastodonController = mastodonController else { return [ ] }
2020-09-13 19:51:06 +00:00
2023-01-17 20:37:03 +00:00
guard let accountID = mastodonController . accountInfo ? . id ,
let account = mastodonController . account else {
2021-06-10 14:52:27 +00:00
// u s e r i s l o g g e d o u t
2020-09-13 19:51:06 +00:00
return [
openInSafariAction ( url : status . url ! ) ,
2022-11-30 03:41:36 +00:00
createAction ( identifier : " share " , title : " Share " , systemImageName : " square.and.arrow.up " , handler : { [ weak self ] ( _ ) in
2020-09-13 19:51:06 +00:00
guard let self = self else { return }
2022-11-30 03:41:36 +00:00
self . navigationDelegate ? . showMoreOptions ( forStatus : status . id , source : source )
2020-09-13 19:51:06 +00:00
} )
]
}
2020-06-27 04:22:14 +00:00
let bookmarked = status . bookmarked ? ? false
2022-06-07 02:58:14 +00:00
var toggleableSection = [
2020-07-08 03:23:39 +00:00
createAction ( identifier : " bookmark " , title : bookmarked ? " Unbookmark " : " Bookmark " , systemImageName : bookmarked ? " bookmark.fill " : " bookmark " , handler : { [ weak self ] ( _ ) in
guard let self = self else { return }
2020-12-24 22:13:44 +00:00
let request = ( bookmarked ? Status . unbookmark : Status . bookmark ) ( status . id )
2020-06-27 04:22:14 +00:00
self . mastodonController ? . run ( request ) { ( response ) in
2022-05-02 03:06:59 +00:00
switch response {
case . success ( let status , _ ) :
2022-05-01 19:15:35 +00:00
self . mastodonController ? . persistentContainer . addOrUpdate ( status : status )
2022-05-02 03:06:59 +00:00
case . failure ( let error ) :
2022-11-12 04:34:41 +00:00
self . handleError ( error , title : " Error \( bookmarked ? " Unb " : " B " ) ookmarking " )
2020-06-27 04:22:14 +00:00
}
}
2019-07-27 22:53:07 +00:00
} ) ,
2020-06-27 04:22:14 +00:00
]
2022-11-23 16:47:00 +00:00
if #available ( iOS 16.0 , * ) ,
includeStatusButtonActions {
2022-06-07 02:58:14 +00:00
let favorited = status . favourited
// TODO: m o v e t h i s c o l o r i n t o a n a s s e t c a t a l o g o r s o m e t h i n g
var favImage = UIImage ( systemName : favorited ? " star.fill " : " star " ) !
if favorited {
favImage = favImage . withTintColor ( UIColor ( displayP3Red : 1 , green : 0.8 , blue : 0 , alpha : 1 ) , renderingMode : . alwaysOriginal )
}
toggleableSection . insert ( createAction ( identifier : " favorite " , title : favorited ? " Unfavorite " : " Favorite " , image : favImage , handler : { [ weak self ] _ in
2022-11-03 22:48:39 +00:00
guard let self ,
let navigationDelegate = self . navigationDelegate else { return }
Task { @ MainActor in
await FavoriteService ( status : status , mastodonController : mastodonController , presenter : navigationDelegate ) . toggleFavorite ( )
}
2022-06-07 02:58:14 +00:00
} ) , at : 0 )
let reblogged = status . reblogged
var reblogImage = UIImage ( systemName : " repeat " ) !
if reblogged {
reblogImage = reblogImage . withTintColor ( UIColor ( displayP3Red : 1 , green : 0.8 , blue : 0 , alpha : 1 ) , renderingMode : . alwaysOriginal )
}
toggleableSection . insert ( createAction ( identifier : " reblog " , title : reblogged ? " Unreblog " : " Reblog " , image : reblogImage , handler : { [ weak self ] _ in
2022-11-03 22:48:39 +00:00
guard let self ,
let navigationDelegate = self . navigationDelegate else { return }
Task { @ MainActor in
await ReblogService ( status : status , mastodonController : mastodonController , presenter : navigationDelegate ) . toggleReblog ( )
2022-09-18 04:26:37 +00:00
}
2022-06-07 02:58:14 +00:00
} ) , at : 1 )
}
2023-01-18 00:32:50 +00:00
var actionsSection : [ UIMenuElement ] = [ ]
2022-06-07 02:58:14 +00:00
2022-11-23 16:47:00 +00:00
if includeStatusButtonActions {
2021-06-09 21:10:44 +00:00
actionsSection . insert ( createAction ( identifier : " reply " , title : " Reply " , systemImageName : " arrowshape.turn.up.left " , handler : { [ weak self ] ( _ ) in
guard let self = self else { return }
self . navigationDelegate ? . compose ( inReplyToID : status . id )
} ) , at : 0 )
}
2023-01-17 20:37:03 +00:00
// o n l y a l l o w m u t i n g c o n v e r s a t i o n s t h a t e i t h e r c u r r e n t u s e r p o s t e d o r i s p a r t i c i p a t i n g i n ( t e c h n i c a l l y , i s m e n t i o n e d , s i n c e t h a t ' s t h e b e s t w e c a n d o )
if status . account . id = = account . id || status . mentions . contains ( where : { $0 . id = = account . id } ) {
let muted = status . muted
toggleableSection . append ( createAction ( identifier : " mute " , title : muted ? " Unmute Conversation " : " Mute Conversation " , systemImageName : muted ? " speaker " : " speaker.slash " , handler : { [ weak self ] ( _ ) in
guard let self = self else { return }
let request = ( muted ? Status . unmuteConversation : Status . muteConversation ) ( status . id )
self . mastodonController ? . run ( request ) { ( response ) in
switch response {
case . success ( let status , _ ) :
self . mastodonController ? . persistentContainer . addOrUpdate ( status : status )
case . failure ( let error ) :
self . handleError ( error , title : " Error \( muted ? " Unm " : " M " ) uting " )
2020-06-27 04:22:14 +00:00
}
2023-01-17 20:37:03 +00:00
}
} ) )
}
2021-05-05 21:51:11 +00:00
if status . poll != nil {
2023-01-18 00:32:50 +00:00
actionsSection . append ( createAction ( identifier : " refresh " , title : " Refresh Poll " , systemImageName : " arrow.clockwise " , handler : { [ weak self ] ( _ ) in
2021-05-05 21:51:11 +00:00
guard let mastodonController = self ? . mastodonController else { return }
let request = Client . getStatus ( id : status . id )
mastodonController . run ( request , completion : { ( response ) in
2022-05-02 03:06:59 +00:00
switch response {
case . success ( let status , _ ) :
2021-05-05 21:51:11 +00:00
// t o d o : t h i s s h o u l d n ' t r e a l l y u s e t h e v i e w C o n t e x t , b u t f o r s o m e r e a s o n s a v i n g t h e
// b a c k g r o u n d C o n t e x t w i t h t h e n e w v e r s i o n o f t h e s t a t u s i s n ' t u p d a t i n g t h e v i e w C o n t e x t
2022-05-09 19:54:27 +00:00
DispatchQueue . main . async {
mastodonController . persistentContainer . addOrUpdate ( status : status , context : mastodonController . persistentContainer . viewContext )
}
2022-05-02 03:06:59 +00:00
case . failure ( let error ) :
2022-11-12 04:34:41 +00:00
self ? . handleError ( error , title : " Error Refreshing Poll " )
2021-05-05 21:51:11 +00:00
}
} )
2023-01-18 00:32:50 +00:00
} ) )
2021-05-05 21:51:11 +00:00
}
2023-01-18 00:32:50 +00:00
if account . id = = status . account . id {
if mastodonController . instanceFeatures . profilePinnedStatuses {
let pinned = status . pinned ? ? false
toggleableSection . append ( createAction ( identifier : " pin " , title : pinned ? " Unpin from Profile " : " Pin to Profile " , systemImageName : pinned ? " pin.slash " : " pin " , handler : { [ weak self ] ( _ ) in
guard let self = self else { return }
let request = ( pinned ? Status . unpin : Status . pin ) ( status . id )
self . mastodonController ? . run ( request , completion : { [ weak self ] ( response ) in
guard let self = self else { return }
switch response {
case . success ( let status , _ ) :
self . mastodonController ? . persistentContainer . addOrUpdate ( status : status )
case . failure ( let error ) :
self . handleError ( error , title : " Error \( pinned ? " Unp " : " P " ) inning " )
}
} )
} ) )
}
2023-01-18 20:05:12 +00:00
actionsSection . append ( UIMenu ( title : " Delete Post " , image : UIImage ( systemName : " trash " ) , children : [
2023-01-18 00:32:50 +00:00
UIAction ( title : " Cancel " , handler : { _ in } ) ,
2023-01-18 20:05:12 +00:00
UIAction ( title : " Delete Post " , image : UIImage ( systemName : " trash " ) , attributes : . destructive , handler : { [ weak self ] _ in
2023-01-18 00:32:50 +00:00
guard let self ,
let navigationDelegate = self . navigationDelegate else {
return
}
Task { @ MainActor in
let service = DeleteStatusService ( status : status , mastodonController : mastodonController , presenter : navigationDelegate )
await service . run ( )
}
} )
] ) )
} else {
2023-01-17 20:37:03 +00:00
actionsSection . append ( createAction ( identifier : " report " , title : " Report " , systemImageName : " flag " , handler : { [ weak self ] _ in
let report = EditedReport ( accountID : status . account . id )
report . statusIDs = [ status . id ]
let view = ReportView ( report : report , mastodonController : mastodonController )
let host = UIHostingController ( rootView : view )
self ? . navigationDelegate ? . present ( host , animated : true )
} ) )
}
2022-10-10 18:21:12 +00:00
var shareSection : [ UIAction ] = [ ]
if let url = status . url {
shareSection . append ( openInSafariAction ( url : url ) )
} else {
Logging . general . fault ( " Status missing URL: id= \( status . id , privacy : . public ) , reblog= \( ( status . reblog ? . id ) . debugDescription , privacy : . public ) " )
}
2022-11-30 03:41:36 +00:00
shareSection . append ( createAction ( identifier : " share " , title : " Share " , systemImageName : " square.and.arrow.up " , handler : { [ weak self ] ( _ ) in
2022-10-10 18:21:12 +00:00
guard let self = self else { return }
2022-11-30 03:41:36 +00:00
self . navigationDelegate ? . showMoreOptions ( forStatus : status . id , source : source )
2022-10-10 18:21:12 +00:00
} ) )
2020-06-27 04:22:14 +00:00
2021-06-10 14:52:27 +00:00
addOpenInNewWindow ( actions : & shareSection , activity : UserActivityManager . showConversationActivity ( mainStatusID : status . id , accountID : accountID ) )
2020-12-14 03:37:37 +00:00
2022-06-07 02:58:14 +00:00
if #available ( iOS 16.0 , * ) {
2022-11-23 16:47:00 +00:00
let toggleableAndActions = toggleableSection + actionsSection
2022-06-07 02:58:14 +00:00
return [
2022-11-23 16:47:00 +00:00
UIMenu ( options : . displayInline , preferredElementSize : toggleableAndActions . count = = 1 ? . large : . medium , children : toggleableAndActions ) ,
2022-06-07 02:58:14 +00:00
UIMenu ( options : . displayInline , children : shareSection ) ,
]
} else {
return [
UIMenu ( options : . displayInline , children : shareSection ) ,
UIMenu ( options : . displayInline , children : toggleableSection ) ,
UIMenu ( options : . displayInline , children : actionsSection ) ,
]
}
2019-06-04 21:04:37 +00:00
}
2022-04-02 15:36:45 +00:00
func actionsForTrendingLink ( card : Card ) -> [ UIMenuElement ] {
guard let url = URL ( card . url ) else {
return [ ]
}
return [
openInSafariAction ( url : url ) ,
createAction ( identifier : " postlink " , title : " Post this Link " , systemImageName : " square.and.pencil " , handler : { [ weak self ] _ in
guard let self = self else { return }
let draft = self . mastodonController ! . createDraft ( )
let title = card . title . trimmingCharacters ( in : . whitespacesAndNewlines )
if ! title . isEmpty {
draft . text += title
draft . text += " : \n "
}
draft . text += url . absoluteString
// p r e v e n t s t h e d r a f t f r o m b e i n g s a v e d a u t o m a t i c a l l y u n t i l t h e u s e r m a k e s a c h a n g e
// a l s o p r e v e n t s i t f r o m b e i n g p o s t e d w i t h o u t b e i n g c h a n g e d
draft . initialText = draft . text
self . navigationDelegate ? . compose ( editing : draft )
} )
]
}
2019-09-06 21:56:45 +00:00
2022-11-03 22:48:39 +00:00
private func createAction ( identifier : String , title : String , systemImageName : String ? , handler : @ escaping ( UIAction ) -> Void ) -> UIAction {
2020-12-14 03:37:37 +00:00
let image : UIImage ?
if let name = systemImageName {
image = UIImage ( systemName : name )
} else {
image = nil
}
2022-06-07 02:58:14 +00:00
return createAction ( identifier : identifier , title : title , image : image , handler : handler )
}
private func createAction ( identifier : String , title : String , image : UIImage ? , handler : @ escaping UIActionHandler ) -> UIAction {
2020-12-14 03:37:37 +00:00
return UIAction ( title : title , image : image , identifier : UIAction . Identifier ( identifier ) , discoverabilityTitle : nil , attributes : [ ] , state : . off , handler : handler )
2019-07-27 22:53:07 +00:00
}
2020-06-27 04:22:14 +00:00
private func openInSafariAction ( url : URL ) -> UIAction {
2021-02-06 20:29:35 +00:00
return createAction ( identifier : " openinsafari " , title : " Open in Safari " , systemImageName : " safari " , handler : { [ weak self ] ( _ ) in
2023-01-23 15:35:23 +00:00
self ? . navigationDelegate ? . selected ( url : url , allowResolveStatuses : false , allowUniversalLinks : false )
2020-06-27 04:22:14 +00:00
} )
}
2021-06-10 14:52:27 +00:00
private func addOpenInNewWindow ( actions : inout [ UIAction ] , activity : @ escaping @autoclosure ( ) -> NSUserActivity ) {
2022-07-01 01:41:05 +00:00
let options = UIWindowScene . ActivationRequestOptions ( )
options . preferredPresentationStyle = . automatic
actions . append ( UIWindowScene . ActivationAction { ( _ ) in
let activity = activity ( )
activity . displaysAuxiliaryScene = true
return . init ( userActivity : activity , options : options , preview : nil )
} )
2021-06-10 14:52:27 +00:00
}
2022-11-12 04:34:41 +00:00
private func handleError ( _ error : Client . Error , title : String ) {
if let toastable = self . toastableViewController {
let config = ToastConfiguration ( from : error , with : title , in : toastable , retryAction : nil )
DispatchQueue . main . async {
toastable . showToast ( configuration : config , animated : true )
}
}
}
2022-11-29 02:41:56 +00:00
private func handleSuccess ( title : String ) {
if let toastable = self . toastableViewController {
var config = ToastConfiguration ( title : title )
config . systemImageName = " checkmark "
config . dismissAutomaticallyAfter = 2
DispatchQueue . main . async {
toastable . showToast ( configuration : config , animated : true )
}
}
}
2022-12-22 23:27:04 +00:00
private func relationshipAction ( _ fetch : Bool , accountID : String , mastodonController : MastodonController , builder : @ escaping @ MainActor ( RelationshipMO , MastodonController ) -> UIMenuElement ) -> UIDeferredMenuElement {
2022-11-12 00:07:50 +00:00
return UIDeferredMenuElement . uncached ( { @ MainActor elementHandler in
// w o r k a r o u n d f o r # 1 9 8 , m a y r e s u l t i n s h o w i n g o u t d a t e d r e l a t i o n s h i p , s o o n l y d o s o w h e r e n e c e s s a r y
2022-12-22 23:27:04 +00:00
if ! fetch || ProcessInfo . processInfo . isiOSAppOnMac ,
2022-11-12 00:07:50 +00:00
let mo = mastodonController . persistentContainer . relationship ( forAccount : accountID ) {
elementHandler ( [ builder ( mo , mastodonController ) ] )
} else {
2022-12-22 23:27:04 +00:00
let relationship = Task {
await fetchRelationship ( accountID : accountID , mastodonController : mastodonController )
}
2022-11-12 00:07:50 +00:00
Task { @ MainActor in
if let relationship = await relationship . value {
elementHandler ( [ builder ( relationship , mastodonController ) ] )
} else {
elementHandler ( [ ] )
}
}
}
} )
}
2022-10-30 22:54:14 +00:00
@ MainActor
2022-11-12 00:07:50 +00:00
private func followAction ( for relationship : RelationshipMO , mastodonController : MastodonController ) -> UIMenuElement {
2022-10-30 22:54:14 +00:00
let accountID = relationship . accountID
2022-03-29 16:52:14 +00:00
let following = relationship . following
2022-11-29 02:41:56 +00:00
let requested = relationship . requested
let title = following ? " Unfollow " : requested ? " Cancel Request " : " Follow "
let imageName = following || requested ? " person.badge.minus " : " person.badge.plus "
return createAction ( identifier : " follow " , title : title , systemImageName : imageName ) { [ weak self ] _ in
let request = ( following || requested ? Account . unfollow : Account . follow ) ( accountID )
2022-03-29 16:52:14 +00:00
mastodonController . run ( request ) { response in
switch response {
2022-05-02 03:06:59 +00:00
case . failure ( let error ) :
2022-11-29 02:41:56 +00:00
self ? . handleError ( error , title : following ? " Error Unfollowing " : requested ? " Error Cancelinng Request " : " Error Following " )
2022-03-29 16:52:14 +00:00
case . success ( let relationship , _ ) :
mastodonController . persistentContainer . addOrUpdate ( relationship : relationship )
2022-11-29 02:41:56 +00:00
if requested { // w a s r e q u e s t e d , n o w c a n c e l l e d
self ? . handleSuccess ( title : " Follow Request Cancelled " )
} else if following { // w a s f o l l o w i n g , n o w u n f o l l o w e d
self ? . handleSuccess ( title : " Unfollowed " )
} else if relationship . followRequested { // w a s n o t f o l l o w i n g , n o w r e q u e s t e d
self ? . handleSuccess ( title : " Request Sent " )
} else { // w a s n o t f o l l o w i n g , n o t n o w r e q u e s t e d , a s s u m e s u c c e s s
self ? . handleSuccess ( title : " Followed " )
}
2022-03-29 16:52:14 +00:00
}
}
}
}
2022-11-12 00:07:50 +00:00
@ MainActor
private func blockAction ( for relationship : RelationshipMO , mastodonController : MastodonController ) -> UIMenuElement {
let accountID = relationship . accountID
2022-11-12 03:44:58 +00:00
let displayName = relationship . account ! . displayOrUserName
let host = relationship . account ! . url . host !
let handler = { ( block : Bool ) in
2022-11-12 04:34:41 +00:00
return { [ weak self ] ( _ : UIAction ) in
2022-11-12 03:44:58 +00:00
let req = block ? Account . block ( accountID ) : Account . unblock ( accountID )
_ = mastodonController . run ( req ) { response in
switch response {
case . failure ( let error ) :
2022-11-12 04:34:41 +00:00
self ? . handleError ( error , title : " Error \( block ? " B " : " Unb " ) locking " )
2022-11-12 03:44:58 +00:00
case . success ( let relationship , _ ) :
mastodonController . persistentContainer . addOrUpdate ( relationship : relationship )
2022-11-29 02:41:56 +00:00
self ? . handleSuccess ( title : " \( block ? " B " : " Unb " ) locked " )
2022-11-12 03:44:58 +00:00
}
}
}
}
let domainHandler = { ( block : Bool ) in
2022-11-12 04:34:41 +00:00
return { [ weak self ] ( _ : UIAction ) in
2022-11-12 03:44:58 +00:00
let req = block ? Client . block ( domain : host ) : Client . unblock ( domain : host )
mastodonController . run ( req ) { response in
2022-11-29 02:41:56 +00:00
switch response {
case . failure ( let error ) :
2022-11-12 04:34:41 +00:00
self ? . handleError ( error , title : " Error \( block ? " B " : " Unb " ) locking " )
2022-11-29 02:41:56 +00:00
case . success ( _ , _ ) :
self ? . handleSuccess ( title : " Domain \( block ? " B " : " Unb " ) locked " )
2022-11-12 00:07:50 +00:00
}
}
}
}
2022-11-12 03:44:58 +00:00
if relationship . domainBlocking {
return createAction ( identifier : " block " , title : " Unblock \( host ) " , systemImageName : " circle.slash " , handler : domainHandler ( false ) )
} else if relationship . blocking {
return createAction ( identifier : " block " , title : " Unblock \( displayName ) " , systemImageName : " circle.slash " , handler : handler ( false ) )
2022-11-12 00:07:50 +00:00
} else {
2022-11-12 03:44:58 +00:00
let image = UIImage ( systemName : " circle.slash " )
return UIMenu ( title : " Block " , image : image , children : [
2022-11-12 00:07:50 +00:00
UIAction ( title : " Cancel " , handler : { _ in } ) ,
2022-11-12 03:44:58 +00:00
UIAction ( title : " Block \( displayName ) " , image : image , attributes : . destructive , handler : handler ( true ) ) ,
UIAction ( title : " Block \( host ) " , image : image , attributes : . destructive , handler : domainHandler ( true ) )
2022-11-12 00:07:50 +00:00
] )
}
}
2022-11-12 04:29:15 +00:00
@ MainActor
private func muteAction ( for relationship : RelationshipMO , mastodonController : MastodonController ) -> UIMenuElement {
if relationship . muting || relationship . mutingNotifications {
2022-11-12 04:34:41 +00:00
return UIAction ( title : " Unmute " , image : UIImage ( systemName : " speaker " ) ) { [ weak self ] _ in
2022-11-12 04:29:15 +00:00
let req = Account . unmute ( relationship . accountID )
mastodonController . run ( req ) { response in
switch response {
case . failure ( let error ) :
2022-11-12 04:34:41 +00:00
self ? . handleError ( error , title : " Error Unmuting " )
2022-11-12 04:29:15 +00:00
case . success ( let relationship , _ ) :
mastodonController . persistentContainer . addOrUpdate ( relationship : relationship )
2022-11-29 02:41:56 +00:00
self ? . handleSuccess ( title : " Unmuted " )
2022-11-12 04:29:15 +00:00
}
}
}
} else {
2022-11-12 04:34:41 +00:00
return UIAction ( title : " Mute " , image : UIImage ( systemName : " speaker.slash " ) ) { [ weak self ] _ in
2022-11-12 04:29:15 +00:00
let view = MuteAccountView ( account : relationship . account ! , mastodonController : mastodonController )
let host = UIHostingController ( rootView : view )
2022-11-12 04:34:41 +00:00
self ? . navigationDelegate ? . present ( host , animated : true )
2022-11-12 04:29:15 +00:00
}
}
}
2018-10-12 01:20:58 +00:00
}
2020-03-21 02:13:04 +00:00
2022-11-03 02:59:44 +00:00
private func fetchRelationship ( accountID : String , mastodonController : MastodonController ) async -> RelationshipMO ? {
let req = Client . getRelationships ( accounts : [ accountID ] )
guard let ( relationships , _ ) = try ? await mastodonController . run ( req ) ,
let r = relationships . first else {
return nil
}
return await withCheckedContinuation { continuation in
mastodonController . persistentContainer . addOrUpdate ( relationship : r , in : mastodonController . persistentContainer . viewContext ) { mo in
continuation . resume ( returning : mo )
}
}
}
2022-10-06 03:12:03 +00:00
struct MenuPreviewHelper {
static func willPerformPreviewAction ( animator : UIContextMenuInteractionCommitAnimating , presenter : UIViewController ) {
if let viewController = animator . previewViewController {
animator . preferredCommitStyle = . pop
animator . addCompletion {
if let customPresenting = viewController as ? CustomPreviewPresenting {
customPresenting . presentFromPreview ( presenter : presenter )
} else {
presenter . show ( viewController , sender : nil )
}
}
}
}
}
2020-03-21 02:13:04 +00:00
extension LargeImageViewController : CustomPreviewPresenting {
func presentFromPreview ( presenter : UIViewController ) {
presenter . present ( self , animated : true )
}
}
extension GalleryViewController : CustomPreviewPresenting {
func presentFromPreview ( presenter : UIViewController ) {
presenter . present ( self , animated : true )
}
}
extension SFSafariViewController : CustomPreviewPresenting {
func presentFromPreview ( presenter : UIViewController ) {
presenter . present ( self , animated : true )
}
}