#1575 (subscribe to site in share sheet)

This commit is contained in:
David Sinclair 2021-12-23 16:18:26 -07:00
parent 738bdda220
commit 10c405c44e
4 changed files with 183 additions and 56 deletions

View file

@ -579,6 +579,7 @@ static NSArray<NSString *> *NewsBlurTopSectionNames;
[defaults setObject:[results objectForKey:@"share_ext_token"] forKey:@"share:token"]; [defaults setObject:[results objectForKey:@"share_ext_token"] forKey:@"share:token"];
[defaults setObject:self.appDelegate.url forKey:@"share:host"]; [defaults setObject:self.appDelegate.url forKey:@"share:host"];
[defaults setObject:appDelegate.dictSavedStoryTags forKey:@"share:tags"]; [defaults setObject:appDelegate.dictSavedStoryTags forKey:@"share:tags"];
[defaults setObject:appDelegate.dictFoldersArray forKey:@"share:folders"];
[self validateWidgetFeedsForGroupDefaults:defaults usingResults:results]; [self validateWidgetFeedsForGroupDefaults:defaults usingResults:results];
[defaults synchronize]; [defaults synchronize];

View file

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Jka-P1-QHB"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19455" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Jka-P1-QHB">
<device id="retina4_7" orientation="portrait" appearance="light"/> <device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/> <capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
@ -36,10 +37,11 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="nek-0G-qOK"> <segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="nek-0G-qOK">
<rect key="frame" x="66" y="64" width="243" height="32"/> <rect key="frame" x="5" y="64" width="365" height="32"/>
<segments> <segments>
<segment title="Save This Story"/> <segment title="Save This Story"/>
<segment title="Share This Story"/> <segment title="Share This Story"/>
<segment title="Add This Site"/>
</segments> </segments>
<connections> <connections>
<action selector="changedMode:" destination="j1y-V4-xli" eventType="valueChanged" id="UPm-xD-bxX"/> <action selector="changedMode:" destination="j1y-V4-xli" eventType="valueChanged" id="UPm-xD-bxX"/>
@ -52,7 +54,7 @@
<viewLayoutGuide key="frameLayoutGuide" id="PCs-SD-lDm"/> <viewLayoutGuide key="frameLayoutGuide" id="PCs-SD-lDm"/>
<prototypes> <prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="ShareSaveTagCell" id="QfW-wS-4rz" customClass="ShareSaveTagCell" customModule="Share_Extension" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="ShareSaveTagCell" id="QfW-wS-4rz" customClass="ShareSaveTagCell" customModule="Share_Extension" customModuleProvider="target">
<rect key="frame" x="0.0" y="24.5" width="375" height="43.5"/> <rect key="frame" x="0.0" y="44.5" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="QfW-wS-4rz" id="FUw-X2-Tgd"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="QfW-wS-4rz" id="FUw-X2-Tgd">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/> <rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
@ -76,14 +78,14 @@
</connections> </connections>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="ShareSaveNewCell" id="efQ-d6-znU" customClass="ShareSaveNewCell" customModule="Share_Extension" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="ShareSaveNewCell" id="efQ-d6-znU" customClass="ShareSaveNewCell" customModule="Share_Extension" customModuleProvider="target">
<rect key="frame" x="0.0" y="68" width="375" height="43.5"/> <rect key="frame" x="0.0" y="88" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="efQ-d6-znU" id="74T-Fj-bga"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="efQ-d6-znU" id="74T-Fj-bga">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/> <rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="new tag" textAlignment="natural" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="qAx-nx-hOD"> <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="new tag" textAlignment="natural" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="qAx-nx-hOD">
<rect key="frame" x="20" y="11" width="335" height="22"/> <rect key="frame" x="20" y="11.5" width="335" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<textInputTraits key="textInputTraits" autocapitalizationType="words"/> <textInputTraits key="textInputTraits" autocapitalizationType="words"/>
<connections> <connections>
@ -105,7 +107,7 @@
</connections> </connections>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="ShareCommentCell" rowHeight="252" id="Y1T-la-hCi" customClass="ShareCommentCell" customModule="Share_Extension" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="ShareCommentCell" rowHeight="252" id="Y1T-la-hCi" customClass="ShareCommentCell" customModule="Share_Extension" customModuleProvider="target">
<rect key="frame" x="0.0" y="111.5" width="375" height="252"/> <rect key="frame" x="0.0" y="131.5" width="375" height="252"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Y1T-la-hCi" id="F3X-7c-ujj"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Y1T-la-hCi" id="F3X-7c-ujj">
<rect key="frame" x="0.0" y="0.0" width="375" height="252"/> <rect key="frame" x="0.0" y="0.0" width="375" height="252"/>

View file

@ -28,9 +28,12 @@ class ShareViewController: UIViewController {
/// Share publicly. /// Share publicly.
case share case share
/// Add site.
case add
} }
/// Whether we are saving the story privately or sharing publicly. /// Whether we are saving the story privately, sharing publicly, or adding a site.
var mode: Mode = .save var mode: Mode = .save
/// Dictionary representation of a tag. /// Dictionary representation of a tag.
@ -60,6 +63,15 @@ class ShareViewController: UIViewController {
/// User-entered comments, only used when sharing. /// User-entered comments, only used when sharing.
var comments = "" var comments = ""
/// 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)
/// Title of the item being shared. /// Title of the item being shared.
var itemTitle: String? = nil var itemTitle: String? = nil
@ -83,6 +95,12 @@ class ShareViewController: UIViewController {
} }
} }
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) }
}
updateSaveButtonState() updateSaveButtonState()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow(notification:)), name: UIResponder.keyboardDidShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow(notification:)), name: UIResponder.keyboardDidShowNotification, object: nil)
@ -90,13 +108,14 @@ class ShareViewController: UIViewController {
} }
func updateSaveButtonState() { func updateSaveButtonState() {
if mode == .save { switch mode {
case .save:
if let rows = tableView.indexPathsForSelectedRows { if let rows = tableView.indexPathsForSelectedRows {
navigationItem.rightBarButtonItem?.isEnabled = !rows.isEmpty navigationItem.rightBarButtonItem?.isEnabled = !rows.isEmpty
} else { } else {
navigationItem.rightBarButtonItem?.isEnabled = false navigationItem.rightBarButtonItem?.isEnabled = false
} }
} else { default:
navigationItem.rightBarButtonItem?.isEnabled = true navigationItem.rightBarButtonItem?.isEnabled = true
} }
} }
@ -112,12 +131,16 @@ class ShareViewController: UIViewController {
} }
@IBAction func newTagFieldChanged(_ sender: UITextField) { @IBAction func newTagFieldChanged(_ sender: UITextField) {
newTag = sender.text ?? "" if mode == .save {
newTag = sender.text ?? ""
if newTag.isEmpty {
tableView.deselectRow(at: indexPathForNewTag, animated: false) if newTag.isEmpty {
} else { tableView.deselectRow(at: indexPathForNewTag, animated: false)
tableView.selectRow(at: indexPathForNewTag, animated: false, scrollPosition: .none) } else {
tableView.selectRow(at: indexPathForNewTag, animated: false, scrollPosition: .none)
}
} else if mode == .add {
newFolder = sender.text ?? ""
} }
updateSaveButtonState() updateSaveButtonState()
@ -154,9 +177,17 @@ class ShareViewController: UIViewController {
} }
@IBAction func changedMode(_ sender: Any) { @IBAction func changedMode(_ sender: Any) {
mode = modeSegmentedControl.selectedSegmentIndex == 0 ? .save : .share switch modeSegmentedControl.selectedSegmentIndex {
case 1:
navigationItem.rightBarButtonItem?.title = mode == .save ? "Save" : "Share" mode = .share
navigationItem.rightBarButtonItem?.title = "Share"
case 2:
mode = .add
navigationItem.rightBarButtonItem?.title = "Add"
default:
mode = .save
navigationItem.rightBarButtonItem?.title = "Save"
}
tableView.isEditing = mode == .save tableView.isEditing = mode == .save
tableView.reloadData() tableView.reloadData()
@ -231,52 +262,89 @@ private extension ShareViewController {
} }
var requestPath: String { var requestPath: String {
return mode == .save ? "api/save_story" : "api/share_story" switch mode {
case .share:
return "api/share_story"
case .save:
return "api/save_story"
case .add:
return "reader/add_url"
}
} }
func encoded(_ string: String?) -> String { func encoded(_ string: String?) -> String {
return string?.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) ?? "" return string?.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) ?? ""
} }
func postBody(url: URL?, text: String?) -> String { func postSave(url: URL?, text: String?) -> String {
let title = itemTitle let title = itemTitle
let encodedURL = encoded(url?.absoluteString) let encodedURL = encoded(url?.absoluteString)
let encodedTitle = encoded(title) let encodedTitle = encoded(title)
let encodedContent = encoded(text) let encodedContent = encoded(text)
if mode == .save { let indexPaths = tableView.indexPathsForSelectedRows ?? []
let indexPaths = tableView.indexPathsForSelectedRows ?? [] var selectedTagsArray = [String]()
var selectedTagsArray = [String]()
for index in 0..<tags.count {
for index in 0..<tags.count { if indexPaths.contains(IndexPath(item: index, section: 0)) {
if indexPaths.contains(IndexPath(item: index, section: 0)) { selectedTagsArray.append(encoded(tags[index].name))
selectedTagsArray.append(encoded(tags[index].name))
}
} }
}
let selectedTags = selectedTagsArray.joined(separator: ",")
let encodedNewTag = encoded(newTag) 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)"
let postBody = "story_url=\(encodedURL)&title=\(encodedTitle)&content=\(encodedContent)&user_tags=\(selectedTags)&add_user_tag=\(encodedNewTag)"
return postBody
} else { return postBody
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. func postShare(url: URL?, text: String?) -> String {
if title != nil && comments == title { let title = itemTitle
comments = "" let encodedURL = encoded(url?.absoluteString)
} let encodedTitle = encoded(title)
let encodedContent = encoded(text)
if text != nil && comments == text {
comments = "" 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.
let encodedComments = encoded(comments) if title != nil && comments == title {
comments = ""
let postBody = "story_url=\(encodedURL)&title=\(encodedTitle)&content=\(encodedContent)&comments=\(encodedComments)" }
return postBody 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)
} }
} }
} }

View file

@ -14,6 +14,11 @@ class ShareViewDelegate: NSObject {
extension ShareViewDelegate: UITableViewDelegate { extension ShareViewDelegate: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if viewController.mode == .add, indexPath.section == 0 {
viewController.selectedFolderIndexPath = indexPath
tableView.reloadData()
}
viewController.updateSaveButtonState() viewController.updateSaveButtonState()
} }
@ -23,14 +28,37 @@ extension ShareViewDelegate: UITableViewDelegate {
} }
extension ShareViewDelegate: UITableViewDataSource { extension ShareViewDelegate: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { func numberOfSections(in tableView: UITableView) -> Int {
if viewController.mode == .save { if viewController.mode == .add {
return viewController.tags.count + 1 return 2
} else { } else {
return 1 return 1
} }
} }
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if viewController.mode == .add, section == 1 {
return "Add new sub-folder:"
} else {
return nil
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch viewController.mode {
case .save:
return viewController.tags.count + 1
case .share:
return 1
case .add:
if section == 0 {
return viewController.folders.count
} else {
return 1
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if viewController.mode == .save { if viewController.mode == .save {
if indexPath.item < viewController.tags.count { if indexPath.item < viewController.tags.count {
@ -47,6 +75,34 @@ extension ShareViewDelegate: UITableViewDataSource {
} }
cell.tagField.text = "" cell.tagField.text = ""
cell.tagField.placeholder = "new tag"
return cell
}
} else if viewController.mode == .add {
if indexPath.section == 0 {
guard let cell = tableView.dequeueReusableCell(withIdentifier: ShareSaveTagCell.reuseIdentifier, for: indexPath) as? ShareSaveTagCell else {
preconditionFailure("Expected to dequeue a ShareSaveTagCell")
}
let components = viewController.folders[indexPath.item].components(separatedBy: "")
if components.first == "everything" {
cell.tagLabel.text = "🗃 Top Level"
} else {
cell.tagLabel.text = "\(String(repeating: " ", count: components.count))📁 \(components.last ?? "?")"
}
cell.accessoryType = indexPath == viewController.selectedFolderIndexPath ? .checkmark : .none
return cell
} else {
guard let cell = tableView.dequeueReusableCell(withIdentifier: ShareSaveNewCell.reuseIdentifier, for: indexPath) as? ShareSaveNewCell else {
preconditionFailure("Expected to dequeue a ShareSaveNewCell")
}
cell.tagField.text = viewController.newFolder
cell.tagField.placeholder = "new folder title"
return cell return cell
} }