NewsBlur/clients/ios/Share Extension/ShareViewController.swift

293 lines
9.9 KiB
Swift
Raw Normal View History

//
// ShareViewController.swift
// Share Extension
//
// Created by David Sinclair on 2021-07-18.
// Copyright © 2021 NewsBlur. All rights reserved.
//
import UIKit
import MobileCoreServices
class ShareViewController: UIViewController {
@IBOutlet var delegate: ShareViewDelegate!
@IBOutlet weak var modeSegmentedControl: UISegmentedControl!
@IBOutlet weak var tableView: UITableView!
/// The group preferences, shared with the main app.
lazy var prefs: UserDefaults = {
return UserDefaults(suiteName: "group.com.newsblur.NewsBlur-Group") ?? UserDefaults.standard
}()
/// Whether we are saving the story privately or sharing publicly.
enum Mode {
/// Save privately.
case save
/// Share publicly.
case share
}
/// Whether we are saving the story privately or sharing publicly.
var mode: Mode = .save
/// Dictionary representation of a tag.
typealias TagDict = [String : Any]
/// Dictionary of tag dictionaries.
typealias TagsDict = [String : TagDict]
/// Tag structure.
struct Tag: Identifiable, Hashable {
/// Identifier of the tag.
let id: String
/// Name of the tag.
let name: String
/// Count of stories with this tag.
let count: Int
}
/// An array of tags, from the main app.
var tags = [Tag]()
/// New tag to add, if any.
var newTag = ""
/// User-entered comments, only used when sharing.
var comments = ""
/// Title of the item being shared.
var itemTitle: String? = nil
/// The index path of the new tag field.
lazy var indexPathForNewTag: IndexPath = {
return IndexPath(item: tags.count, section: 0)
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.isEditing = mode == .save
if let dicts = prefs.object(forKey: "share:tags") as? TagsDict {
tags = dicts.map { (key: String, value: TagDict) in
return Tag(id: key, name: value["feed_title"] as? String ?? "tag", count: value["ps"] as? Int ?? 0)
}
tags.sort { tag1, tag2 in
return tag1.name.lowercased() < tag2.name.lowercased()
}
}
updateSaveButtonState()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow(notification:)), name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
func updateSaveButtonState() {
if mode == .save {
if let rows = tableView.indexPathsForSelectedRows {
navigationItem.rightBarButtonItem?.isEnabled = !rows.isEmpty
} else {
navigationItem.rightBarButtonItem?.isEnabled = false
}
} else {
navigationItem.rightBarButtonItem?.isEnabled = true
}
}
@objc private func keyboardDidShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height + tableView.rowHeight, right: 0)
}
}
@objc private func keyboardWillHide(notification: NSNotification) {
tableView.contentInset = .zero
}
@IBAction func newTagFieldChanged(_ sender: UITextField) {
newTag = sender.text ?? ""
if newTag.isEmpty {
tableView.deselectRow(at: indexPathForNewTag, animated: false)
} else {
tableView.selectRow(at: indexPathForNewTag, animated: false, scrollPosition: .none)
}
updateSaveButtonState()
}
@IBAction func newTagFieldReturn(_ sender: UITextField) {
sender.resignFirstResponder()
}
@IBAction func cancel(_ sender: Any) {
extensionContext?.cancelRequest(withError: NSError(domain: Bundle.main.bundleIdentifier!, code: 0))
}
@IBAction func save(_ sender: Any) {
itemTitle = nil
if let itemProvider = providerWithURL {
itemProvider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil) { item, error in
if let url = item as? URL {
self.send(url: url)
}
self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
}
} else if let itemProvider = providerWithText {
itemProvider.loadItem(forTypeIdentifier: kUTTypeText as String, options: nil) { item, error in
if let text = item as? String {
self.send(text: text)
}
self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
}
}
}
@IBAction func changedMode(_ sender: Any) {
mode = modeSegmentedControl.selectedSegmentIndex == 0 ? .save : .share
navigationItem.rightBarButtonItem?.title = mode == .save ? "Save" : "Share"
tableView.isEditing = mode == .save
tableView.reloadData()
updateSaveButtonState()
}
}
private extension ShareViewController {
var providerWithURL: NSItemProvider? {
guard let extensionItems = extensionContext?.inputItems as? [NSExtensionItem] else {
return nil
}
for extensionItem in extensionItems {
if let itemProviders = extensionItem.attachments {
for itemProvider in itemProviders {
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
itemTitle = extensionItem.attributedTitle?.string
if itemTitle == nil {
itemTitle = extensionItem.attributedContentText?.string
}
return itemProvider
}
}
}
}
return nil
}
var providerWithText: NSItemProvider? {
guard let extensionItems = extensionContext?.inputItems as? [NSExtensionItem] else {
return nil
}
for extensionItem in extensionItems {
if let itemProviders = extensionItem.attachments {
for itemProvider in itemProviders {
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeText as String) {
return itemProvider
}
}
}
}
return nil
}
func send(url: URL? = nil, text: String? = nil) {
guard let host = prefs.object(forKey: "share:host") as? String,
let token = prefs.object(forKey: "share:token") as? String,
let requestURL = URL(string: "\(host)/\(requestPath)/\(token)") else {
return
}
let postBody = postBody(url: url, text: text)
var request = URLRequest(url: requestURL)
request.httpMethod = "POST"
request.httpBody = postBody.data(using: .utf8)
let config = URLSessionConfiguration.background(withIdentifier: "group.com.newsblur.share")
config.sharedContainerIdentifier = "group.com.newsblur.NewsBlur-Group"
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
let task = session.dataTask(with: request as URLRequest)
task.resume()
}
var requestPath: String {
return mode == .save ? "api/save_story" : "api/share_story"
}
func encoded(_ string: String?) -> String {
return string?.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) ?? ""
}
func postBody(url: URL?, text: String?) -> String {
let title = itemTitle
let encodedURL = encoded(url?.absoluteString)
let encodedTitle = encoded(title)
let encodedContent = encoded(text)
if mode == .save {
let indexPaths = tableView.indexPathsForSelectedRows ?? []
var selectedTagsArray = [String]()
for index in 0..<tags.count {
if indexPaths.contains(IndexPath(item: index, section: 0)) {
selectedTagsArray.append(encoded(tags[index].name))
}
}
let selectedTags = selectedTagsArray.joined(separator: ",")
let encodedNewTag = encoded(newTag)
let postBody = "story_url=\(encodedURL)&title=\(encodedTitle)&content=\(encodedContent)&user_tags=\(selectedTags)&add_user_tag=\(encodedNewTag)"
return postBody
} else {
var comments = comments
// Don't really need this stuff if I don't populate the comments from the title or text; leave for now just in case that is wanted.
if title != nil && comments == title {
comments = ""
}
if text != nil && comments == text {
comments = ""
}
let encodedComments = encoded(comments)
let postBody = "story_url=\(encodedURL)&title=\(encodedTitle)&content=\(encodedContent)&comments=\(encodedComments)"
return postBody
}
}
}
extension ShareViewController: URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
print("task completed with error: \(error)")
} else {
print("task completed successfully")
}
}
}