mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-08-05 16:58:59 +00:00
293 lines
9.9 KiB
Swift
293 lines
9.9 KiB
Swift
![]() |
//
|
||
|
// 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")
|
||
|
}
|
||
|
}
|
||
|
}
|