2023-04-19 01:55:14 +00:00
//
// S h a r e V i e w C o n t r o l l e r . s w i f t
// S h a r e E x t e n s i o n
//
// C r e a t e d b y S h a d o w f a c t s o n 4 / 1 7 / 2 3 .
// C o p y r i g h t © 2 0 2 3 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 SwiftUI
import UserAccounts
import ComposeUI
import UniformTypeIdentifiers
import TuskerPreferences
2023-04-21 21:24:40 +00:00
import Combine
2023-10-27 19:58:15 +00:00
import Pachyderm
2023-04-19 01:55:14 +00:00
class ShareViewController : UIViewController {
private var state : State = . loading
required init ? ( coder : NSCoder ) {
super . init ( coder : coder )
}
override func viewDidLoad ( ) {
super . viewDidLoad ( )
view . tintColor = Preferences . shared . accentColor . color
if let account = UserAccountsManager . shared . getMostRecentAccount ( ) {
Task { @ MainActor in
let draft = await createDraft ( account : account )
state = . ok
let context = ShareMastodonContext ( accountInfo : account )
let host = ShareHostingController ( draft : draft , mastodonContext : context )
2023-05-04 14:01:32 +00:00
host . view . translatesAutoresizingMaskIntoConstraints = false
addChild ( host )
self . view . addSubview ( host . view )
2023-04-19 01:55:14 +00:00
NSLayoutConstraint . activate ( [
2023-05-04 14:01:32 +00:00
host . view . leadingAnchor . constraint ( equalTo : self . view . leadingAnchor ) ,
host . view . trailingAnchor . constraint ( equalTo : self . view . trailingAnchor ) ,
host . view . topAnchor . constraint ( equalTo : self . view . topAnchor ) ,
host . view . bottomAnchor . constraint ( equalTo : self . view . bottomAnchor ) ,
2023-04-19 01:55:14 +00:00
] )
2023-05-04 14:01:32 +00:00
host . didMove ( toParent : self )
2023-04-19 01:55:14 +00:00
}
} else {
state = . notLoggedIn
}
}
private func createDraft ( account : UserAccountInfo ) async -> Draft {
2023-10-27 19:58:15 +00:00
async let ( text , attachments ) = getDraftConfigurationFromExtensionContext ( )
// TODO: I r e a l l y d o n ' t l i k e t h a t t h e r e ' s a n e t w o r k r e q u e s t i n t h e h o t p a t h h e r e , b u t w e d o n ' t h a v e e a s y a c c e s s t o A c c o u n t P r e f e r e n c e s : /
let serverPrefs = try ? await Client ( baseURL : account . instanceURL , accessToken : account . accessToken ) . run ( Client . getPreferences ( ) ) . 0
let visibility = Preferences . shared . defaultPostVisibility . resolved ( withServerDefault : serverPrefs ? . postingDefaultVisibility )
2023-04-23 01:16:30 +00:00
let draft = DraftsPersistentContainer . shared . createDraft (
2023-04-19 01:55:14 +00:00
accountID : account . id ,
2023-10-27 19:58:15 +00:00
text : await text ,
2023-04-19 01:55:14 +00:00
contentWarning : " " ,
inReplyToID : nil ,
2023-10-27 19:58:15 +00:00
visibility : visibility ,
language : serverPrefs ? . postingDefaultLanguage ,
2023-10-27 20:12:48 +00:00
localOnly : ! ( serverPrefs ? . postingDefaultFederation ? ? true )
2023-04-19 01:55:14 +00:00
)
2023-04-23 01:16:30 +00:00
2023-10-27 19:58:15 +00:00
for attachment in await attachments {
2023-04-23 01:16:30 +00:00
DraftsPersistentContainer . shared . viewContext . insert ( attachment )
}
2023-10-27 19:58:15 +00:00
draft . draftAttachments = await attachments
2023-04-23 01:16:30 +00:00
2023-04-19 01:55:14 +00:00
return draft
}
private func getDraftConfigurationFromExtensionContext ( ) async -> ( String , [ DraftAttachment ] ) {
guard let extensionContext ,
2023-05-13 02:00:00 +00:00
let inputItem = ( extensionContext . inputItems as ? [ NSExtensionItem ] ) ? . first else {
2023-04-19 01:55:14 +00:00
return ( " " , [ ] )
}
2023-05-13 02:00:00 +00:00
var text : String = " "
var url : URL ?
var attachments : [ DraftAttachment ] = [ ]
for itemProvider in inputItem . attachments ? ? [ ] {
2023-07-08 22:37:45 +00:00
// a t t a c h m e n t s h a v e t h e h i g h e s t p r i o r i t y , b u t o n l y g i v e n t h i s h e u r i s t i c
// o t h e r w i s e a t t a c h m e n t d e c o d i n g e n d s u p b e i n g o v e r z e a l o u s
let likelyAttachment = [ UTType . image , . movie ] . contains ( where : { itemProvider . hasItemConformingToTypeIdentifier ( $0 . identifier ) } )
if likelyAttachment ,
let attachment : DraftAttachment = await getObject ( from : itemProvider ) {
attachments . append ( attachment )
} else if let attached : NSURL = await getObject ( from : itemProvider ) {
2023-05-13 02:00:00 +00:00
if url = = nil {
url = attached as URL
2023-04-23 02:43:00 +00:00
}
2023-05-13 02:00:00 +00:00
} else if let s : NSString = await getObject ( from : itemProvider ) {
if text . isEmpty {
text = s as String
}
}
}
if text . isEmpty ,
let s = inputItem . attributedTitle ? ? inputItem . attributedContentText {
text = s . string
}
if let url {
if ! text . isEmpty {
text += " \n "
2023-04-23 02:43:00 +00:00
}
2023-05-13 02:00:00 +00:00
text += url . absoluteString
}
if ! text . isEmpty {
text = " \n \n \( text ) "
2023-04-19 01:55:14 +00:00
}
2023-05-13 02:00:00 +00:00
return ( text , attachments )
2023-04-19 01:55:14 +00:00
}
private func getObject < T : NSItemProviderReading > ( from itemProvider : NSItemProvider ) async -> T ? {
guard itemProvider . canLoadObject ( ofClass : T . self ) else {
return nil
}
return await withCheckedContinuation ( { continuation in
itemProvider . loadObject ( ofClass : T . self ) { object , error in
continuation . resume ( returning : object as ? T )
}
} )
}
override func viewDidAppear ( _ animated : Bool ) {
super . viewDidAppear ( animated )
if case . notLoggedIn = state {
let alert = UIAlertController ( title : " Not Logged In " , message : " You need to log in to an account through the app before you can post. " , preferredStyle : . alert )
alert . addAction ( UIAlertAction ( title : " OK " , style : . default , handler : { [ unowned self ] _ in
self . extensionContext ! . cancelRequest ( withError : Error . notLoggedIn )
} ) )
present ( alert , animated : true )
}
}
enum State {
case loading
case notLoggedIn
case ok
}
enum Error : Swift . Error {
case notLoggedIn
}
}