123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387 |
- //
- // ShareVC.swift
- // Share
- //
- // Created by Marcus Rohrmoser on 02.03.20.
- // Copyright © 2020-2022 Marcus Rohrmoser mobile Software http://mro.name/me. All rights reserved.
- //
- // This program is free software: you can redistribute it and/or modify
- // it under the terms of the GNU General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License
- // along with this program. If not, see <http://www.gnu.org/licenses/>.
- //
- import UIKit
- import Social
- import MobileCoreServices
- import AudioToolbox
- private func stringFromPrivacy(_ priv : Bool) -> String {
- return priv
- ? NSLocalizedString("Private 🔐", comment:"ShareVC")
- : NSLocalizedString("Public 🔓", comment:"ShareVC")
- }
- private func privacyFromString(_ s : String) -> Bool {
- return s != stringFromPrivacy(false)
- }
- private func play_sound_ok() {
- // https://github.com/irccloud/ios/blob/6e3255eab82be047be141ced6e482ead5ac413f4/ShareExtension/ShareViewController.m#L155
- AudioServicesPlaySystemSound(1001)
- }
- private func play_sound_err() {
- AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
- }
- @objc (ShareVC) // https://blog.hellocode.co/post/share-extension/
- class ShareVC: SLComposeServiceViewController {
- private let semver = info_to_semver(Bundle.main.infoDictionary)
- private var current : BlogM? // may become local if not for missing cfg error
- private var wasTouched = false
- private var itemTitle : SLComposeSheetConfigurationItem?
- private var itemAudience : SLComposeSheetConfigurationItem?
- private var session : URLSession?
- private var action : URL = URLEmpty
- private var ctx : HtmlFormDict = [:]
- private var url : URL = URLEmpty
- override func viewDidLoad() {
- debugPrint("viewDidLoad")
- super.viewDidLoad()
- }
- override func configurationItems() -> [Any]! {
- debugPrint("configurationItems")
- guard let iTi = SLComposeSheetConfigurationItem() else {return []}
- iTi.title = NSLocalizedString("Title", comment:"ShareVC")
- iTi.value = self.contentText
- guard let iAu = SLComposeSheetConfigurationItem() else {return []}
- iAu.title = NSLocalizedString("Audience", comment:"ShareVC")
- iAu.value = stringFromPrivacy(false)
- weak var wself = self
- iAu.tapHandler = {
- guard let sf = wself else {return}
- guard let iAu = sf.itemAudience else {return}
- iAu.value = stringFromPrivacy( !privacyFromString(iAu.value) )
- sf.wasTouched = true
- }
- itemTitle = iTi
- itemAudience = iAu
- return [iTi, iAu]
- }
- override func viewWillAppear(_ animated: Bool) {
- debugPrint("viewWillAppear")
- super.viewWillAppear(animated)
- view.tintColor = UIColor(red:128 / 255.0, green:173 / 255.0, blue:72 / 255.0, alpha:1.0)
- assert(itemTitle != nil)
- assert(itemAudience != nil)
- let sha = ShaarliM.shared
- // sha.defaults.removePersistentDomain(forName:"group.\(BUNDLE_ID)") // doesn't do it.
- current = sha.loadBlog(sha.defaults)
- guard let current = current else {
- // do nothing here and let viewDidAppear display a error popup
- return
- }
- let cli = ShaarliHtmlClient(semver)
- textView.keyboardType = .twitter
- view.subviews.forEach({ (v) in
- // dark mode?
- v.backgroundColor = UIColor.white.withAlphaComponent(0.89)
- })
- guard let itemTitle = itemTitle else {return}
- guard let itemAudience = itemAudience else {return}
- guard let textView = textView else {return}
- title = current.title
- itemTitle.value = contentText
- let preset = tagsNormalise(description:itemTitle.value, extended:current.tagsDefault, tags:[], known:[])
- textView.text = "\(preset.extended) \(NSLocalizedString("🔄", comment:"ShareVC"))"
- itemAudience.value = stringFromPrivacy(current.privateDefault)
- let tPli = kUTTypePropertyList as String
- let tUrl = kUTTypeURL as String
- let tTxt = kUTTypeText as String
- let RK = NSExtensionJavaScriptPreprocessingResultsKey as String
- weak var ws = self
- for _item in (extensionContext?.inputItems)! {
- let item = _item as! NSExtensionItem
- for ip in (item.attachments!) {
- guard let ws = ws else {return}
- // let ip = _ip as! NSItemProvider // required for Xcode <10
- if( ip.hasItemConformingToTypeIdentifier(tPli)) {
- ip.loadItem(forTypeIdentifier:tPli, options:nil) { (_dic, err) in
- guard let _dic = (_dic as? NSDictionary)?[RK] as? [String:String] else {
- play_sound_err()
- ws.showError(
- title:NSLocalizedString("URL Share Sheet failed", comment: "ShareVC"),
- message:NSLocalizedString("I got no dictionary to share.", comment: "ShareVC"),
- showsettings:false
- )
- return
- }
- let kUrl = "url"
- let kTit = "title"
- let kDsc = "description"
- let kTgs = "keywords"
- let kImg = "image"
- guard let _url = URL(string:_dic[kUrl] ?? "") else {
- play_sound_err()
- ws.showError(
- title:NSLocalizedString("URL Share Sheet failed", comment: "ShareVC"),
- message:NSLocalizedString("I got no url to share.", comment: "ShareVC"),
- showsettings:false
- )
- return
- }
- guard let err = err else {
- let t0 = Date()
- cli.get(current.endpoint, current.credential, current.timeout, _url, { (ses, act, ctx, _url, tit, dsc, tgs, pri, tim, seti, err) in
- guard "" == err else {
- play_sound_err()
- ws.showError(
- title:NSLocalizedString("Can't reach Shaarli", comment: "ShareVC"),
- message:err,
- showsettings:true
- )
- return
- }
- guard URLEmpty != act else {
- play_sound_err()
- ws.showError(
- title:NSLocalizedString("Can't post to Shaarli", comment: "ShareVC"),
- message:NSLocalizedString("the Shaarli responded an empty linkform action, I don't know where to post to.", comment: "ShareVC"),
- showsettings:true
- )
- return
- }
- self.session = ses
- // should 'old' come out of get( callback(... old) )?
- let old = isOld(t0, seti, cli.timeShaarli(current.timezone, tim))
- let dti = _dic[kTit] ?? ""
- let dde = _dic[kDsc] ?? ""
- // let dim = URL(string:_dic[kImg] ?? "", relativeTo:_url)
- let v = old || ("" != tit || "" != dsc)
- ? (tit, dsc, tgs)
- : (dti == "" && dde == ""
- ? (itemTitle.value ?? "", preset.extended, [])
- : (dti, dde, tagsSplit(_dic[kTgs])) )
- let r = tagsNormalise(description:v.0, extended:v.1, tags:v.2.union(preset.tags), known:[])
- DispatchQueue.main.async {
- ws.action = act
- ws.ctx = ctx
- ws.url = _url
- itemTitle.value = r.description
- textView.text = "\(r.extended) "
- itemAudience.value = stringFromPrivacy(pri)
- }
- })
- return
- }
- ws.showError(
- title:NSLocalizedString("URL Share Sheet failed", comment: "ShareVC"),
- message:err.localizedDescription,
- showsettings:false
- )
- }
- }
- // see predicate from http://stackoverflow.com/a/27932776
- else if( ip.hasItemConformingToTypeIdentifier(tUrl) ) {
- // maybe this whole block can go away
- ip.loadItem(forTypeIdentifier:tUrl, options:nil) { (_url, err) in
- guard let _url = _url as? URL else {
- play_sound_err()
- ws.showError(
- title:NSLocalizedString("URL Share Sheet failed", comment: "ShareVC"),
- message:NSLocalizedString("I got no url to share.", comment: "ShareVC"),
- showsettings:false
- )
- return
- }
- guard let err = err else {
- cli.get(current.endpoint, current.credential, current.timeout, _url, { (ses, act, ctx, _url, tit, dsc, tgs, pri, tim, seti, err) in
- guard "" == err else {
- play_sound_err()
- ws.showError(
- title:NSLocalizedString("Can't reach Shaarli", comment: "ShareVC"),
- message:err,
- showsettings:true
- )
- return
- }
- guard URLEmpty != act else {
- play_sound_err()
- ws.showError(
- title:NSLocalizedString("Can't post to Shaarli", comment: "ShareVC"),
- message:NSLocalizedString("the Shaarli responded an empty linkform action, I don't know where to post to.", comment: "ShareVC"),
- showsettings:true
- )
- return
- }
- self.session = ses
- let v = "" == tit && "" == dsc
- ? (itemTitle.value, preset.extended)
- : (tit, dsc)
- let r = tagsNormalise(description:v.0, extended:v.1, tags:tgs.union(preset.tags), known:[])
- DispatchQueue.main.async {
- ws.action = act
- ws.ctx = ctx
- ws.url = _url
- itemTitle.value = r.description
- textView.text = "\(r.extended) "
- itemAudience.value = stringFromPrivacy(pri)
- }
- })
- return
- }
- ws.showError(
- title:NSLocalizedString("URL Share Sheet failed", comment: "ShareVC"),
- message:err.localizedDescription,
- showsettings:false
- )
- }
- }
- else if( ip.hasItemConformingToTypeIdentifier(tTxt) ) {
- ip.loadItem(forTypeIdentifier:tTxt, options:nil) { (_txt, err) in
- guard let err = err else {
- debugPrint("done. title:\(itemTitle.value ?? "-") txt:\(String(describing: _txt))")
- return
- }
- ws.showError(
- title:NSLocalizedString("TXT Share Sheet failed", comment: "ShareVC"),
- message:err.localizedDescription,
- showsettings:false
- )
- }
- }
- }
- }
- }
- override func didSelectPost() {
- debugPrint("didSelectPost")
- let c = ShaarliHtmlClient(semver)
- guard let tit = itemTitle?.value else {return}
- guard let dsc = textView.text else {return}
- let pri = privacyFromString((itemAudience?.value)!)
- let r = tagsNormalise(description:tit, extended:dsc, tags:[], known:[])
- c.add(session!, action, ctx, url, r.description, r.extended, r.tags, pri) { err in
- guard "" == err else {
- play_sound_err()
- self.showError(
- title:NSLocalizedString("Share failed", comment: "ShareVC"),
- message:err,
- showsettings:false
- )
- usleep(750 * 1000)
- return
- }
- play_sound_ok()
- // wait until the sound finished
- usleep(750 * 1000)
- super.didSelectPost()
- }
- // 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.
- // self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
- }
- override func viewDidAppear(_ animated: Bool) {
- super.viewDidAppear(animated)
- guard nil != current else {
- showError(
- title:NSLocalizedString("No Shaarli found", comment:"ShareVC"),
- message:NSLocalizedString("Please add one in the Settings.", comment:"ShareVC"),
- showsettings:true)
- return
- }
- wasTouched = false
- }
- private func showError(title:String, message:String, showsettings:Bool) {
- DispatchQueue.main.async {
- let alert = UIAlertController(
- title:title,
- message:message,
- preferredStyle:.alert
- )
- alert.addAction(UIAlertAction(
- title: NSLocalizedString("Cancel", comment:"ShareVC"),
- style:.cancel,
- handler:{ (_) in
- self.cancel()
- }))
- if showsettings {
- alert.addAction(UIAlertAction(
- title: NSLocalizedString("Settings", comment:"ShareVC"),
- style:.default,
- handler:{ (_) in
- // https://stackoverflow.com/a/44499222/349514
- DispatchGroup().notify(queue: DispatchQueue.main) {
- let _ = self.openURL(URL(string:"\(SELF_URL_PREFIX):///settings")!)
- }
- self.cancel()
- }))
- }
- self.present(alert, animated:true, completion:nil)
- }
- }
- override func presentationAnimationDidFinish() {
- debugPrint("presentationAnimationDidFinish")
- }
- override func isContentValid() -> Bool {
- debugPrint("isContentValid")
- // Do validation of contentText and/or NSExtensionContext attachments here
- wasTouched = true
- return true
- }
- // No preview image right upper inside the share dialog.
- override func loadPreviewView() -> UIView! {
- return nil
- }
- override func didSelectCancel() {
- debugPrint("didSelectCancel")
- super.didSelectCancel()
- }
- // https://stackoverflow.com/a/44499222/349514
- // Function must be named exactly like this so a selector can be found by the compiler!
- // Anyway - it's another selector in another instance that would be "performed" instead.
- @objc private func openURL(_ url: URL) -> Bool {
- var rep: UIResponder? = self
- while rep != nil {
- if let app = rep as? UIApplication {
- return app.perform(#selector(openURL(_:)), with: url) != nil
- }
- rep = rep?.next
- }
- return false
- }
- }
|