2021-07-21 21:11:39 -07:00
|
|
|
//
|
|
|
|
// 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
|
2021-12-23 16:18:26 -07:00
|
|
|
|
|
|
|
/// Add site.
|
|
|
|
case add
|
2021-07-21 21:11:39 -07:00
|
|
|
}
|
|
|
|
|
2021-12-23 16:18:26 -07:00
|
|
|
/// Whether we are saving the story privately, sharing publicly, or adding a site.
|
2021-07-21 21:11:39 -07:00
|
|
|
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 = ""
|
|
|
|
|
2021-12-23 16:18:26 -07:00
|
|
|
/// An array of folders, from the main app.
|
|
|
|
var folders = [String]()
|
|
|
|
|
|
|
|
/// New folder name, only used when adding.
|
|
|
|
var newFolder = ""
|
|
|
|
|
|
|
|
/// Index path of the selected folder.
|
|
|
|
var selectedFolderIndexPath = IndexPath(item: 0, section: 0)
|
|
|
|
|
2021-07-21 21:11:39 -07:00
|
|
|
/// 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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-23 16:18:26 -07:00
|
|
|
if let foldersArray = prefs.object(forKey: "share:folders") as? [String] {
|
|
|
|
folders = foldersArray
|
|
|
|
|
|
|
|
folders.removeAll { ["river_global", "river_blurblogs", "infrequent", "read_stories", "saved_searches", "saved_stories"].contains($0) }
|
|
|
|
}
|
|
|
|
|
2021-07-21 21:11:39 -07:00
|
|
|
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() {
|
2021-12-23 16:18:26 -07:00
|
|
|
switch mode {
|
|
|
|
case .save:
|
2021-07-21 21:11:39 -07:00
|
|
|
if let rows = tableView.indexPathsForSelectedRows {
|
|
|
|
navigationItem.rightBarButtonItem?.isEnabled = !rows.isEmpty
|
|
|
|
} else {
|
|
|
|
navigationItem.rightBarButtonItem?.isEnabled = false
|
|
|
|
}
|
2021-12-23 16:18:26 -07:00
|
|
|
default:
|
2021-07-21 21:11:39 -07:00
|
|
|
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) {
|
2021-12-23 16:18:26 -07:00
|
|
|
if mode == .save {
|
|
|
|
newTag = sender.text ?? ""
|
|
|
|
|
|
|
|
if newTag.isEmpty {
|
|
|
|
tableView.deselectRow(at: indexPathForNewTag, animated: false)
|
|
|
|
} else {
|
|
|
|
tableView.selectRow(at: indexPathForNewTag, animated: false, scrollPosition: .none)
|
|
|
|
}
|
|
|
|
} else if mode == .add {
|
|
|
|
newFolder = sender.text ?? ""
|
2021-07-21 21:11:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2021-12-23 16:18:26 -07:00
|
|
|
switch modeSegmentedControl.selectedSegmentIndex {
|
|
|
|
case 1:
|
|
|
|
mode = .share
|
|
|
|
navigationItem.rightBarButtonItem?.title = "Share"
|
|
|
|
case 2:
|
|
|
|
mode = .add
|
|
|
|
navigationItem.rightBarButtonItem?.title = "Add"
|
|
|
|
default:
|
|
|
|
mode = .save
|
|
|
|
navigationItem.rightBarButtonItem?.title = "Save"
|
|
|
|
}
|
2021-07-21 21:11:39 -07:00
|
|
|
|
|
|
|
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 {
|
2021-12-23 16:18:26 -07:00
|
|
|
switch mode {
|
|
|
|
case .share:
|
|
|
|
return "api/share_story"
|
|
|
|
case .save:
|
|
|
|
return "api/save_story"
|
|
|
|
case .add:
|
|
|
|
return "reader/add_url"
|
|
|
|
}
|
2021-07-21 21:11:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func encoded(_ string: String?) -> String {
|
|
|
|
return string?.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) ?? ""
|
|
|
|
}
|
|
|
|
|
2021-12-23 16:18:26 -07:00
|
|
|
func postSave(url: URL?, text: String?) -> String {
|
2021-07-21 21:11:39 -07:00
|
|
|
let title = itemTitle
|
|
|
|
let encodedURL = encoded(url?.absoluteString)
|
|
|
|
let encodedTitle = encoded(title)
|
|
|
|
let encodedContent = encoded(text)
|
|
|
|
|
2021-12-23 16:18:26 -07:00
|
|
|
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))
|
2021-07-21 21:11:39 -07:00
|
|
|
}
|
2021-12-23 16:18:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
func postShare(url: URL?, text: String?) -> String {
|
|
|
|
let title = itemTitle
|
|
|
|
let encodedURL = encoded(url?.absoluteString)
|
|
|
|
let encodedTitle = encoded(title)
|
|
|
|
let encodedContent = encoded(text)
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
func postAdd(url: URL?, text: String?) -> String {
|
|
|
|
let folder = folders[selectedFolderIndexPath.row]
|
|
|
|
let encodedFolder = encoded(folder)
|
|
|
|
let encodedURL = encoded(url?.absoluteString)
|
|
|
|
|
|
|
|
var postBody = "folder=\(encodedFolder)&url=\(encodedURL)"
|
|
|
|
|
|
|
|
if newFolder != "" {
|
|
|
|
postBody += "&new_folder=\(encoded(newFolder))"
|
|
|
|
}
|
|
|
|
|
|
|
|
return postBody
|
|
|
|
}
|
|
|
|
|
|
|
|
func postBody(url: URL?, text: String?) -> String {
|
|
|
|
switch mode {
|
|
|
|
case .save:
|
|
|
|
return postSave(url: url, text: text)
|
|
|
|
case .share:
|
|
|
|
return postShare(url: url, text: text)
|
|
|
|
case .add:
|
|
|
|
return postAdd(url: url, text: text)
|
2021-07-21 21:11:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|