diff --git a/App/Comments/CommentTableViewCell.swift b/App/Comments/CommentTableViewCell.swift index 62347ef7..7cfa6d6d 100644 --- a/App/Comments/CommentTableViewCell.swift +++ b/App/Comments/CommentTableViewCell.swift @@ -70,7 +70,7 @@ class CommentTableViewCell: SwipeTableViewCell { if let commentTextView = commentTextView, comment.visibility == .visible { // only for expanded comments let commentFont = UIFont.preferredFont(forTextStyle: .subheadline) - let commentAttributedString = parseToAttributedString(comment.text) + let commentAttributedString = comment.text.parseToAttributedString() let commentRange = NSRange(location: 0, length: commentAttributedString.length) commentAttributedString.addAttribute(NSAttributedString.Key.font, @@ -83,20 +83,6 @@ class CommentTableViewCell: SwipeTableViewCell { commentTextView.attributedText = commentAttributedString } } - - private func parseToAttributedString(_ html: String) -> NSMutableAttributedString { - let paragraphIdentifier = "PARAGRAPH_NEED_NEW_LINES_HERE" - - // swiftlint:disable unused_optional_binding - guard let document = try? SwiftSoup.parse(html), - let _ = try? document.select("p").before(paragraphIdentifier), - let text = try? document.text() else { - return NSMutableAttributedString() - } - // swiftlint:enable unused_optional_binding - - return NSMutableAttributedString(string: text.replacingOccurrences(of: paragraphIdentifier + " ", with: "\n\n")) - } } extension CommentTableViewCell: UITextViewDelegate { @@ -138,12 +124,12 @@ extension CommentTableViewCell: UITextViewDelegate { extension CommentTableViewCell { override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) - selected ? setSelectedBackground() : setUnselectedBackground() + if selected { setSelectedBackground() } else { setUnselectedBackground() } } override func setHighlighted(_ highlighted: Bool, animated: Bool) { super.setHighlighted(highlighted, animated: animated) - highlighted ? setSelectedBackground() : setUnselectedBackground() + if highlighted { setSelectedBackground() } else { setUnselectedBackground() } } private func setSelectedBackground() { diff --git a/App/Comments/CommentsViewController.swift b/App/Comments/CommentsViewController.swift index b4fc7d51..aab50423 100644 --- a/App/Comments/CommentsViewController.swift +++ b/App/Comments/CommentsViewController.swift @@ -65,6 +65,7 @@ class CommentsViewController: UITableViewController { return self.loadComments(for: post) }.done { comments in self.comments = comments + self.post?.commentsCount = comments.count self.tableView.reloadData() }.catch { error in UINotifications.showError() @@ -267,10 +268,26 @@ extension CommentsViewController { self?.shareComment(at: indexPath) } + let copy = UIAction( + title: "Copy", + image: UIImage(systemName: "doc.on.doc"), + identifier: UIAction.Identifier(rawValue: "copy.comment") + ) { [weak self] _ in + self?.copyComment(at: indexPath) + } + + let selectText = UIAction( + title: "Select Text", + image: UIImage(systemName: "selection.pin.in.out"), + identifier: UIAction.Identifier(rawValue: "select.comment") + ) { [weak self] _ in + self?.selectCommentText(at: indexPath) + } + let voteMenu = upvoted ? unvote : upvote let shareMenu = UIMenu(title: "", options: .displayInline, children: [share]) - return UIMenu(title: "", image: nil, identifier: nil, children: [voteMenu, shareMenu]) + return UIMenu(title: "", image: nil, identifier: nil, children: [voteMenu, copy, selectText, shareMenu]) } } @@ -397,6 +414,17 @@ extension CommentsViewController: SwipeTableViewCellDelegate { self.present(activityViewController, animated: true, completion: nil) } + private func selectCommentText(at indexPath: IndexPath) { + let comment = self.commentsController.visibleComments[indexPath.row] + performSegue(withIdentifier: "ShowTextSelectionSegue", sender: comment.text) + } + + private func copyComment(at indexPath: IndexPath) { + let comment = self.commentsController.visibleComments[indexPath.row] + let pasteboard = UIPasteboard.general + pasteboard.string = comment.text.parseToAttributedString().string + } + private func collapseAction() -> SwipeAction { let collapseAction = SwipeAction(style: .default, title: "Collapse") { _, indexPath in let comment = self.commentsController.visibleComments[indexPath.row] @@ -517,3 +545,14 @@ extension CommentsViewController { userActivity?.invalidate() } } + +extension CommentsViewController { + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if let comment = sender as? String, + segue.identifier == "ShowTextSelectionSegue", + let navVC = segue.destination as? UINavigationController, + let textSelectionViewController = navVC.children.first as? TextSelectionViewController { + textSelectionViewController.comment = comment + } + } +} diff --git a/App/Comments/TextSelectionViewController.swift b/App/Comments/TextSelectionViewController.swift new file mode 100644 index 00000000..6edab507 --- /dev/null +++ b/App/Comments/TextSelectionViewController.swift @@ -0,0 +1,56 @@ +// +// TextSelectionViewController.swift +// Hackers +// +// Created by Peter Ajayi on 08/07/2023. +// Copyright © 2023 Glass Umbrella. All rights reserved. +// + +import UIKit + +final class TextSelectionViewController: UIViewController { + + var comment: String? + + @IBOutlet private var commentTextView: UITextView! + + override func viewDidLoad() { + super.viewDidLoad() + setup() + } + + private func setup() { + view.backgroundColor = AppTheme.default.backgroundColor + setupCommentTextView() + } + + private func setupCommentTextView() { + commentTextView.attributedText = attributedComment() + commentTextView.textContainerInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) + commentTextView.textColor = AppTheme.default.textColor + commentTextView.selectAll(nil) + } + + private func attributedComment() -> NSAttributedString? { + guard let comment = comment else { return nil } + + let attributedString = comment.parseToAttributedString() + + let commentRange = NSRange(location: 0, length: attributedString.length) + let commentFont = UIFont.preferredFont(forTextStyle: .subheadline) + + attributedString.addAttribute(NSAttributedString.Key.font, + value: commentFont, + range: commentRange) + + attributedString.addAttribute(NSAttributedString.Key.foregroundColor, + value: AppTheme.default.textColor, + range: commentRange) + + return attributedString + } + + @IBAction private func didPressDone(_ sender: Any) { + dismiss(animated: true) + } +} diff --git a/App/Extensions/StringExtensions.swift b/App/Extensions/StringExtensions.swift index 36c138f7..7e41cdec 100644 --- a/App/Extensions/StringExtensions.swift +++ b/App/Extensions/StringExtensions.swift @@ -7,6 +7,7 @@ // import Foundation +import SwiftSoup extension String { subscript(value: PartialRangeUpTo) -> Substring { @@ -20,4 +21,18 @@ extension String { subscript(value: PartialRangeFrom) -> Substring { return self[index(startIndex, offsetBy: value.lowerBound)...] } + + func parseToAttributedString() -> NSMutableAttributedString { + let paragraphIdentifier = "PARAGRAPH_NEED_NEW_LINES_HERE" + + // swiftlint:disable unused_optional_binding + guard let document = try? SwiftSoup.parse(self), + let _ = try? document.select("p").before(paragraphIdentifier), + let text = try? document.text() else { + return NSMutableAttributedString() + } + // swiftlint:enable unused_optional_binding + + return NSMutableAttributedString(string: text.replacingOccurrences(of: paragraphIdentifier + " ", with: "\n\n")) + } } diff --git a/App/Main.storyboard b/App/Main.storyboard index d361491c..accdac6e 100644 --- a/App/Main.storyboard +++ b/App/Main.storyboard @@ -306,11 +306,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -728,6 +793,9 @@ + + + diff --git a/Hackers.xcodeproj/project.pbxproj b/Hackers.xcodeproj/project.pbxproj index 45976e5f..bfe667a7 100644 --- a/Hackers.xcodeproj/project.pbxproj +++ b/Hackers.xcodeproj/project.pbxproj @@ -91,6 +91,7 @@ 24FF021D226C9BEA002EF8DD /* SwinjectStoryboardExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FF021C226C9BEA002EF8DD /* SwinjectStoryboardExtensions.swift */; }; 24FFFF5A23362C7800D009EF /* HackersKitExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FFFF5923362C7800D009EF /* HackersKitExtensions.swift */; }; 698FE7B523EE5FE300F5828C /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 698FE7B423EE5FE300F5828C /* Colors.xcassets */; }; + 8B782E032A5AB01C00FD6B39 /* TextSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B782E022A5AB01C00FD6B39 /* TextSelectionViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -219,6 +220,7 @@ 24FF021C226C9BEA002EF8DD /* SwinjectStoryboardExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwinjectStoryboardExtensions.swift; sourceTree = ""; }; 24FFFF5923362C7800D009EF /* HackersKitExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HackersKitExtensions.swift; sourceTree = ""; }; 698FE7B423EE5FE300F5828C /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; + 8B782E022A5AB01C00FD6B39 /* TextSelectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextSelectionViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -444,6 +446,7 @@ 2454B9031F655426005A98C7 /* EmptyViewController.swift */, 24D811A41FF90A58000951C3 /* TouchableTextView.swift */, 24191F8C1944C991003C0D98 /* CommentsController.swift */, + 8B782E022A5AB01C00FD6B39 /* TextSelectionViewController.swift */, ); path = Comments; sourceTree = ""; @@ -878,6 +881,7 @@ 249B8F8322A2A48B002A9EC5 /* NotificationToken.swift in Sources */, 2481BD61247C11AE0088CA2B /* HackersKit+CommentVoting.swift in Sources */, 24D330CA248BAC4C00A2AA10 /* HackersKit+Authentication.swift in Sources */, + 8B782E032A5AB01C00FD6B39 /* TextSelectionViewController.swift in Sources */, 24C20D391A7E42D5005B759B /* MainSplitViewController.swift in Sources */, 2464D21F209E27FE00C7F8FC /* UserDefaultsExtensions.swift in Sources */, 24CE5FF819435A90009D2677 /* PostCell.swift in Sources */, diff --git a/Shared Frameworks/HackersKit/Models/Models.swift b/Shared Frameworks/HackersKit/Models/Models.swift index 12e22bdb..19cbdacf 100644 --- a/Shared Frameworks/HackersKit/Models/Models.swift +++ b/Shared Frameworks/HackersKit/Models/Models.swift @@ -13,7 +13,7 @@ class Post: Hashable { let url: URL let title: String let age: String - let commentsCount: Int + var commentsCount: Int let by: String var score: Int let postType: PostType