5 // Created by Marcus Rohrmoser on 02.03.20.
6 // Copyright Β© 2020-2022 Marcus Rohrmoser mobile Software http://mro.name/me. All rights reserved.
8 // This program is free software: you can redistribute it and/or modify
9 // it under the terms of the GNU General Public License as published by
10 // the Free Software Foundation, either version 3 of the License, or
11 // (at your option) any later version.
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU General Public License for more details.
18 // You should have received a copy of the GNU General Public License
19 // along with this program. If not, see <http://www.gnu.org/licenses/>.
24 import MobileCoreServices
27 private func stringFromPrivacy(_ priv : Bool) -> String {
29 ? NSLocalizedString("Private π", comment:"ShareVC")
30 : NSLocalizedString("Public π", comment:"ShareVC")
33 private func privacyFromString(_ s : String) -> Bool {
34 return s != stringFromPrivacy(false)
37 private func play_sound_ok() {
38 // https://github.com/irccloud/ios/blob/6e3255eab82be047be141ced6e482ead5ac413f4/ShareExtension/ShareViewController.m#L155
39 AudioServicesPlaySystemSound(1001)
42 private func play_sound_err() {
43 AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
46 @objc (ShareVC) // https://blog.hellocode.co/post/share-extension/
47 class ShareVC: SLComposeServiceViewController {
49 private let semver = info_to_semver(Bundle.main.infoDictionary)
51 private var current : BlogM? // may become local if not for missing cfg error
52 private var wasTouched = false
53 private var itemTitle : SLComposeSheetConfigurationItem?
54 private var itemAudience : SLComposeSheetConfigurationItem?
55 private var session : URLSession?
56 private var action : URL = URLEmpty
57 private var ctx : HtmlFormDict = [:]
58 private var url : URL = URLEmpty
60 override func viewDidLoad() {
61 debugPrint("viewDidLoad")
65 override func configurationItems() -> [Any]! {
66 debugPrint("configurationItems")
67 guard let iTi = SLComposeSheetConfigurationItem() else {return []}
68 iTi.title = NSLocalizedString("Title", comment:"ShareVC")
69 iTi.value = self.contentText
71 guard let iAu = SLComposeSheetConfigurationItem() else {return []}
72 iAu.title = NSLocalizedString("Audience", comment:"ShareVC")
73 iAu.value = stringFromPrivacy(false)
76 guard let sf = wself else {return}
77 guard let iAu = sf.itemAudience else {return}
78 iAu.value = stringFromPrivacy( !privacyFromString(iAu.value) )
87 override func viewWillAppear(_ animated: Bool) {
88 debugPrint("viewWillAppear")
89 super.viewWillAppear(animated)
90 view.tintColor = UIColor(red:128 / 255.0, green:173 / 255.0, blue:72 / 255.0, alpha:1.0)
91 assert(itemTitle != nil)
92 assert(itemAudience != nil)
94 let sha = ShaarliM.shared
95 // sha.defaults.removePersistentDomain(forName:"group.\(BUNDLE_ID)") // doesn't do it.
96 current = sha.loadBlog(sha.defaults)
97 guard let current = current else {
98 // do nothing here and let viewDidAppear display a error popup
101 let cli = ShaarliHtmlClient(semver)
103 textView.keyboardType = .twitter
104 view.subviews.forEach({ (v) in
106 v.backgroundColor = UIColor.white.withAlphaComponent(0.89)
109 guard let itemTitle = itemTitle else {return}
110 guard let itemAudience = itemAudience else {return}
111 guard let textView = textView else {return}
113 title = current.title
114 itemTitle.value = contentText
115 let preset = tagsNormalise(description:itemTitle.value, extended:current.tagsDefault, tags:[], known:[])
116 textView.text = "\(preset.extended) \(NSLocalizedString("π", comment:"ShareVC"))"
117 itemAudience.value = stringFromPrivacy(current.privateDefault)
119 let tPli = kUTTypePropertyList as String
120 let tUrl = kUTTypeURL as String
121 let tTxt = kUTTypeText as String
122 let RK = NSExtensionJavaScriptPreprocessingResultsKey as String
124 for _item in (extensionContext?.inputItems)! {
125 let item = _item as! NSExtensionItem
126 for ip in (item.attachments!) {
127 guard let ws = ws else {return}
128 // let ip = _ip as! NSItemProvider // required for Xcode <10
129 if( ip.hasItemConformingToTypeIdentifier(tPli)) {
130 ip.loadItem(forTypeIdentifier:tPli, options:nil) { (_dic, err) in
131 guard let _dic = (_dic as? NSDictionary)?[RK] as? [String:String] else {
134 title:NSLocalizedString("URL Share Sheet failed", comment: "ShareVC"),
135 message:NSLocalizedString("I got no dictionary to share.", comment: "ShareVC"),
142 let kDsc = "description"
143 let kTgs = "keywords"
145 guard let _url = URL(string:_dic[kUrl] ?? "") else {
148 title:NSLocalizedString("URL Share Sheet failed", comment: "ShareVC"),
149 message:NSLocalizedString("I got no url to share.", comment: "ShareVC"),
154 guard let err = err else {
156 cli.get(current.endpoint, current.credential, current.timeout, _url, { (ses, act, ctx, _url, tit, dsc, tgs, pri, tim, seti, err) in
157 guard "" == err else {
160 title:NSLocalizedString("Can't reach Shaarli", comment: "ShareVC"),
166 guard URLEmpty != act else {
169 title:NSLocalizedString("Can't post to Shaarli", comment: "ShareVC"),
170 message:NSLocalizedString("the Shaarli responded an empty linkform action, I don't know where to post to.", comment: "ShareVC"),
176 // should 'old' come out of get( callback(... old) )?
177 let old = isOld(t0, seti, cli.timeShaarli(current.timezone, tim))
178 let dti = _dic[kTit] ?? ""
179 let dde = _dic[kDsc] ?? ""
180 // let dim = URL(string:_dic[kImg] ?? "", relativeTo:_url)
181 let v = old || ("" != tit || "" != dsc)
183 : (dti == "" && dde == ""
184 ? (itemTitle.value ?? "", preset.extended, [])
185 : (dti, dde, tagsSplit(_dic[kTgs])) )
186 let r = tagsNormalise(description:v.0, extended:v.1, tags:v.2.union(preset.tags), known:[])
187 DispatchQueue.main.async {
191 itemTitle.value = r.description
192 textView.text = "\(r.extended) "
193 itemAudience.value = stringFromPrivacy(pri)
199 title:NSLocalizedString("URL Share Sheet failed", comment: "ShareVC"),
200 message:err.localizedDescription,
205 // see predicate from http://stackoverflow.com/a/27932776
206 else if( ip.hasItemConformingToTypeIdentifier(tUrl) ) {
207 // maybe this whole block can go away
208 ip.loadItem(forTypeIdentifier:tUrl, options:nil) { (_url, err) in
209 guard let _url = _url as? URL else {
212 title:NSLocalizedString("URL Share Sheet failed", comment: "ShareVC"),
213 message:NSLocalizedString("I got no url to share.", comment: "ShareVC"),
218 guard let err = err else {
219 cli.get(current.endpoint, current.credential, current.timeout, _url, { (ses, act, ctx, _url, tit, dsc, tgs, pri, tim, seti, err) in
220 guard "" == err else {
223 title:NSLocalizedString("Can't reach Shaarli", comment: "ShareVC"),
229 guard URLEmpty != act else {
232 title:NSLocalizedString("Can't post to Shaarli", comment: "ShareVC"),
233 message:NSLocalizedString("the Shaarli responded an empty linkform action, I don't know where to post to.", comment: "ShareVC"),
239 let v = "" == tit && "" == dsc
240 ? (itemTitle.value, preset.extended)
242 let r = tagsNormalise(description:v.0, extended:v.1, tags:tgs.union(preset.tags), known:[])
243 DispatchQueue.main.async {
247 itemTitle.value = r.description
248 textView.text = "\(r.extended) "
249 itemAudience.value = stringFromPrivacy(pri)
255 title:NSLocalizedString("URL Share Sheet failed", comment: "ShareVC"),
256 message:err.localizedDescription,
261 else if( ip.hasItemConformingToTypeIdentifier(tTxt) ) {
262 ip.loadItem(forTypeIdentifier:tTxt, options:nil) { (_txt, err) in
263 guard let err = err else {
264 debugPrint("done. title:\(itemTitle.value ?? "-") txt:\(String(describing: _txt))")
268 title:NSLocalizedString("TXT Share Sheet failed", comment: "ShareVC"),
269 message:err.localizedDescription,
278 override func didSelectPost() {
279 debugPrint("didSelectPost")
280 let c = ShaarliHtmlClient(semver)
281 guard let tit = itemTitle?.value else {return}
282 guard let dsc = textView.text else {return}
283 let pri = privacyFromString((itemAudience?.value)!)
284 let r = tagsNormalise(description:tit, extended:dsc, tags:[], known:[])
285 c.add(session!, action, ctx, url, r.description, r.extended, r.tags, pri) { err in
286 guard "" == err else {
289 title:NSLocalizedString("Share failed", comment: "ShareVC"),
298 // wait until the sound finished
300 super.didSelectPost()
302 // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
303 // self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
306 override func viewDidAppear(_ animated: Bool) {
307 super.viewDidAppear(animated)
309 guard nil != current else {
311 title:NSLocalizedString("No Shaarli found", comment:"ShareVC"),
312 message:NSLocalizedString("Please add one in the Settings.", comment:"ShareVC"),
320 private func showError(title:String, message:String, showsettings:Bool) {
321 DispatchQueue.main.async {
322 let alert = UIAlertController(
325 preferredStyle:.alert
328 alert.addAction(UIAlertAction(
329 title: NSLocalizedString("Cancel", comment:"ShareVC"),
336 alert.addAction(UIAlertAction(
337 title: NSLocalizedString("Settings", comment:"ShareVC"),
340 // https://stackoverflow.com/a/44499222/349514
341 DispatchGroup().notify(queue: DispatchQueue.main) {
342 let _ = self.openURL(URL(string:"\(SELF_URL_PREFIX):///settings")!)
348 self.present(alert, animated:true, completion:nil)
352 override func presentationAnimationDidFinish() {
353 debugPrint("presentationAnimationDidFinish")
356 override func isContentValid() -> Bool {
357 debugPrint("isContentValid")
358 // Do validation of contentText and/or NSExtensionContext attachments here
363 // No preview image right upper inside the share dialog.
364 override func loadPreviewView() -> UIView! {
368 override func didSelectCancel() {
369 debugPrint("didSelectCancel")
370 super.didSelectCancel()
373 // https://stackoverflow.com/a/44499222/349514
374 // Function must be named exactly like this so a selector can be found by the compiler!
375 // Anyway - it's another selector in another instance that would be "performed" instead.
376 @objc private func openURL(_ url: URL) -> Bool {
377 var rep: UIResponder? = self
379 if let app = rep as? UIApplication {
380 return app.perform(#selector(openURL(_:)), with: url) != nil