mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Fix lint issues.
This commit is contained in:
@@ -19,7 +19,7 @@ struct MainTimelineAccessibilityCellLayout: MainTimelineCellLayout {
|
||||
let summaryRect: CGRect
|
||||
let feedNameRect: CGRect
|
||||
let dateRect: CGRect
|
||||
|
||||
|
||||
init(width: CGFloat, insets: UIEdgeInsets, cellData: MainTimelineCellData) {
|
||||
|
||||
var currentPoint = CGPoint.zero
|
||||
@@ -40,13 +40,13 @@ struct MainTimelineAccessibilityCellLayout: MainTimelineCellLayout {
|
||||
} else {
|
||||
self.iconImageRect = CGRect.zero
|
||||
}
|
||||
|
||||
|
||||
let textAreaWidth = width - (currentPoint.x + MainTimelineDefaultCellLayout.cellPadding.right + insets.right)
|
||||
|
||||
// Title Text Block
|
||||
let (titleRect, numberOfLinesForTitle) = MainTimelineAccessibilityCellLayout.rectForTitle(cellData, currentPoint, textAreaWidth)
|
||||
self.titleRect = titleRect
|
||||
|
||||
|
||||
// Summary Text Block
|
||||
if self.titleRect != CGRect.zero {
|
||||
currentPoint.y = self.titleRect.maxY + MainTimelineDefaultCellLayout.titleBottomMargin
|
||||
@@ -54,21 +54,21 @@ struct MainTimelineAccessibilityCellLayout: MainTimelineCellLayout {
|
||||
self.summaryRect = MainTimelineAccessibilityCellLayout.rectForSummary(cellData, currentPoint, textAreaWidth, numberOfLinesForTitle)
|
||||
|
||||
currentPoint.y = [self.titleRect, self.summaryRect].maxY()
|
||||
|
||||
|
||||
if cellData.showFeedName != .none {
|
||||
self.feedNameRect = MainTimelineAccessibilityCellLayout.rectForFeedName(cellData, currentPoint, textAreaWidth)
|
||||
currentPoint.y = self.feedNameRect.maxY
|
||||
} else {
|
||||
self.feedNameRect = CGRect.zero
|
||||
}
|
||||
|
||||
|
||||
// Feed Name and Pub Date
|
||||
self.dateRect = MainTimelineAccessibilityCellLayout.rectForDate(cellData, currentPoint, textAreaWidth)
|
||||
|
||||
self.height = self.dateRect.maxY + MainTimelineDefaultCellLayout.cellPadding.bottom
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Calculate Rects
|
||||
@@ -78,13 +78,13 @@ private extension MainTimelineAccessibilityCellLayout {
|
||||
static func rectForDate(_ cellData: MainTimelineCellData, _ point: CGPoint, _ textAreaWidth: CGFloat) -> CGRect {
|
||||
|
||||
var r = CGRect.zero
|
||||
|
||||
|
||||
let size = SingleLineUILabelSizer.size(for: cellData.dateString, font: MainTimelineDefaultCellLayout.dateFont)
|
||||
r.size = size
|
||||
r.origin = point
|
||||
|
||||
|
||||
return r
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import Articles
|
||||
struct MainTimelineCellData {
|
||||
|
||||
private static let noText = NSLocalizedString("(No Text)", comment: "No Text")
|
||||
|
||||
|
||||
let title: String
|
||||
let attributedTitle: NSAttributedString
|
||||
let summary: String
|
||||
@@ -38,16 +38,15 @@ struct MainTimelineCellData {
|
||||
} else {
|
||||
self.summary = truncatedSummary
|
||||
}
|
||||
|
||||
|
||||
self.dateString = ArticleStringFormatter.dateString(article.logicalDatePublished)
|
||||
|
||||
if let feedName = feedName {
|
||||
self.feedName = ArticleStringFormatter.truncatedFeedName(feedName)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
self.feedName = ""
|
||||
}
|
||||
|
||||
|
||||
if let byline = byline {
|
||||
self.byline = byline
|
||||
} else {
|
||||
@@ -63,10 +62,10 @@ struct MainTimelineCellData {
|
||||
self.starred = article.status.starred
|
||||
self.numberOfLines = numberOfLines
|
||||
self.iconSize = iconSize
|
||||
|
||||
|
||||
}
|
||||
|
||||
init() { //Empty
|
||||
init() { // Empty
|
||||
self.title = ""
|
||||
self.attributedTitle = NSAttributedString()
|
||||
self.summary = ""
|
||||
@@ -81,5 +80,5 @@ struct MainTimelineCellData {
|
||||
self.numberOfLines = 0
|
||||
self.iconSize = .medium
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ protocol MainTimelineCellLayout {
|
||||
var summaryRect: CGRect {get}
|
||||
var feedNameRect: CGRect {get}
|
||||
var dateRect: CGRect {get}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension MainTimelineCellLayout {
|
||||
@@ -30,8 +30,7 @@ extension MainTimelineCellLayout {
|
||||
r.origin.y = point.y + 5
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
|
||||
static func rectForStar(_ point: CGPoint) -> CGRect {
|
||||
var r = CGRect.zero
|
||||
r.size.width = MainTimelineDefaultCellLayout.starDimension
|
||||
@@ -40,7 +39,7 @@ extension MainTimelineCellLayout {
|
||||
r.origin.y = point.y + 3
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
static func rectForIconView(_ point: CGPoint, iconSize: IconSize) -> CGRect {
|
||||
var r = CGRect.zero
|
||||
r.size = iconSize.size
|
||||
@@ -48,16 +47,16 @@ extension MainTimelineCellLayout {
|
||||
r.origin.y = point.y + 4
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
static func rectForTitle(_ cellData: MainTimelineCellData, _ point: CGPoint, _ textAreaWidth: CGFloat) -> (CGRect, Int) {
|
||||
|
||||
var r = CGRect.zero
|
||||
if cellData.title.isEmpty {
|
||||
return (r, 0)
|
||||
}
|
||||
|
||||
|
||||
r.origin = point
|
||||
|
||||
|
||||
let sizeInfo = MultilineUILabelSizer.size(for: cellData.title, font: MainTimelineDefaultCellLayout.titleFont, numberOfLines: cellData.numberOfLines, width: Int(textAreaWidth))
|
||||
|
||||
r.size.width = textAreaWidth
|
||||
@@ -65,22 +64,22 @@ extension MainTimelineCellLayout {
|
||||
if sizeInfo.numberOfLinesUsed < 1 {
|
||||
r.size.height = 0
|
||||
}
|
||||
|
||||
|
||||
return (r, sizeInfo.numberOfLinesUsed)
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
static func rectForSummary(_ cellData: MainTimelineCellData, _ point: CGPoint, _ textAreaWidth: CGFloat, _ linesUsed: Int) -> CGRect {
|
||||
|
||||
let linesLeft = cellData.numberOfLines - linesUsed
|
||||
|
||||
|
||||
var r = CGRect.zero
|
||||
if cellData.summary.isEmpty || linesLeft < 1 {
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
r.origin = point
|
||||
|
||||
|
||||
let sizeInfo = MultilineUILabelSizer.size(for: cellData.summary, font: MainTimelineDefaultCellLayout.summaryFont, numberOfLines: linesLeft, width: Int(textAreaWidth))
|
||||
|
||||
r.size.width = textAreaWidth
|
||||
@@ -88,26 +87,26 @@ extension MainTimelineCellLayout {
|
||||
if sizeInfo.numberOfLinesUsed < 1 {
|
||||
r.size.height = 0
|
||||
}
|
||||
|
||||
|
||||
return r
|
||||
|
||||
|
||||
}
|
||||
|
||||
static func rectForFeedName(_ cellData: MainTimelineCellData, _ point: CGPoint, _ textAreaWidth: CGFloat) -> CGRect {
|
||||
|
||||
var r = CGRect.zero
|
||||
r.origin = point
|
||||
|
||||
|
||||
let feedName = cellData.showFeedName == .feed ? cellData.feedName : cellData.byline
|
||||
let size = SingleLineUILabelSizer.size(for: feedName, font: MainTimelineDefaultCellLayout.feedNameFont)
|
||||
r.size = size
|
||||
|
||||
|
||||
if r.size.width > textAreaWidth {
|
||||
r.size.width = textAreaWidth
|
||||
}
|
||||
|
||||
|
||||
return r
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import RSCore
|
||||
struct MainTimelineDefaultCellLayout: MainTimelineCellLayout {
|
||||
|
||||
static let cellPadding = UIEdgeInsets(top: 12, left: 8, bottom: 12, right: 20)
|
||||
|
||||
|
||||
static let unreadCircleMarginLeft = CGFloat(integerLiteral: 0)
|
||||
static let unreadCircleDimension = CGFloat(integerLiteral: 12)
|
||||
static let unreadCircleSize = CGSize(width: MainTimelineDefaultCellLayout.unreadCircleDimension, height: MainTimelineDefaultCellLayout.unreadCircleDimension)
|
||||
@@ -33,7 +33,7 @@ struct MainTimelineDefaultCellLayout: MainTimelineCellLayout {
|
||||
return UIFont.preferredFont(forTextStyle: .footnote)
|
||||
}
|
||||
static let feedRightMargin = CGFloat(integerLiteral: 8)
|
||||
|
||||
|
||||
static var dateFont: UIFont {
|
||||
return UIFont.preferredFont(forTextStyle: .footnote)
|
||||
}
|
||||
@@ -72,13 +72,13 @@ struct MainTimelineDefaultCellLayout: MainTimelineCellLayout {
|
||||
} else {
|
||||
self.iconImageRect = CGRect.zero
|
||||
}
|
||||
|
||||
|
||||
let textAreaWidth = width - (currentPoint.x + MainTimelineDefaultCellLayout.cellPadding.right + insets.right)
|
||||
|
||||
// Title Text Block
|
||||
let (titleRect, numberOfLinesForTitle) = MainTimelineDefaultCellLayout.rectForTitle(cellData, currentPoint, textAreaWidth)
|
||||
self.titleRect = titleRect
|
||||
|
||||
|
||||
// Summary Text Block
|
||||
if self.titleRect != CGRect.zero {
|
||||
currentPoint.y = self.titleRect.maxY + MainTimelineDefaultCellLayout.titleBottomMargin
|
||||
@@ -93,7 +93,7 @@ struct MainTimelineDefaultCellLayout: MainTimelineCellLayout {
|
||||
y -= tmp.height
|
||||
}
|
||||
currentPoint.y = y
|
||||
|
||||
|
||||
// Feed Name and Pub Date
|
||||
self.dateRect = MainTimelineDefaultCellLayout.rectForDate(cellData, currentPoint, textAreaWidth)
|
||||
|
||||
@@ -103,7 +103,7 @@ struct MainTimelineDefaultCellLayout: MainTimelineCellLayout {
|
||||
self.height = [self.iconImageRect, self.feedNameRect].maxY() + MainTimelineDefaultCellLayout.cellPadding.bottom
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Calculate Rects
|
||||
@@ -113,14 +113,14 @@ extension MainTimelineDefaultCellLayout {
|
||||
static func rectForDate(_ cellData: MainTimelineCellData, _ point: CGPoint, _ textAreaWidth: CGFloat) -> CGRect {
|
||||
|
||||
var r = CGRect.zero
|
||||
|
||||
|
||||
let size = SingleLineUILabelSizer.size(for: cellData.dateString, font: MainTimelineDefaultCellLayout.dateFont)
|
||||
r.size = size
|
||||
r.origin.x = (point.x + textAreaWidth) - size.width
|
||||
r.origin.y = point.y
|
||||
|
||||
return r
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -18,11 +18,11 @@ class MainTimelineTableViewCell: VibrantTableViewCell {
|
||||
private let feedNameView = MainTimelineTableViewCell.singleLineUILabel()
|
||||
|
||||
private lazy var iconView = IconView()
|
||||
|
||||
|
||||
private lazy var starView = {
|
||||
return NonIntrinsicImageView(image: AppAssets.timelineStarImage)
|
||||
}()
|
||||
|
||||
|
||||
private var unreadIndicatorPropertyAnimator: UIViewPropertyAnimator?
|
||||
private var starViewPropertyAnimator: UIViewPropertyAnimator?
|
||||
|
||||
@@ -31,12 +31,12 @@ class MainTimelineTableViewCell: VibrantTableViewCell {
|
||||
updateSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
commonInit()
|
||||
}
|
||||
|
||||
|
||||
override func prepareForReuse() {
|
||||
unreadIndicatorPropertyAnimator?.stopAnimation(true)
|
||||
unreadIndicatorPropertyAnimator = nil
|
||||
@@ -46,19 +46,19 @@ class MainTimelineTableViewCell: VibrantTableViewCell {
|
||||
starViewPropertyAnimator = nil
|
||||
starView.isHidden = true
|
||||
}
|
||||
|
||||
|
||||
override var frame: CGRect {
|
||||
didSet {
|
||||
setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override func updateVibrancy(animated: Bool) {
|
||||
updateLabelVibrancy(titleView, color: labelColor, animated: animated)
|
||||
updateLabelVibrancy(summaryView, color: labelColor, animated: animated)
|
||||
updateLabelVibrancy(dateView, color: secondaryLabelColor, animated: animated)
|
||||
updateLabelVibrancy(feedNameView, color: secondaryLabelColor, animated: animated)
|
||||
|
||||
|
||||
if animated {
|
||||
UIView.animate(withDuration: Self.duration) {
|
||||
if self.isHighlighted || self.isSelected {
|
||||
@@ -75,16 +75,16 @@ class MainTimelineTableViewCell: VibrantTableViewCell {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override func sizeThatFits(_ size: CGSize) -> CGSize {
|
||||
let layout = updatedLayout(width: size.width)
|
||||
return CGSize(width: size.width, height: layout.height)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
|
||||
|
||||
super.layoutSubviews()
|
||||
|
||||
|
||||
let layout = updatedLayout(width: bounds.width)
|
||||
|
||||
unreadIndicatorView.setFrameIfNotEqual(layout.unreadIndicatorRect)
|
||||
@@ -97,11 +97,11 @@ class MainTimelineTableViewCell: VibrantTableViewCell {
|
||||
|
||||
separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
|
||||
}
|
||||
|
||||
|
||||
func setIconImage(_ image: IconImage) {
|
||||
iconView.iconImage = image
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
@@ -115,7 +115,7 @@ private extension MainTimelineTableViewCell {
|
||||
label.adjustsFontForContentSizeCategory = true
|
||||
return label
|
||||
}
|
||||
|
||||
|
||||
static func multiLineUILabel() -> UILabel {
|
||||
let label = NonIntrinsicLabel()
|
||||
label.numberOfLines = 0
|
||||
@@ -124,16 +124,16 @@ private extension MainTimelineTableViewCell {
|
||||
label.adjustsFontForContentSizeCategory = true
|
||||
return label
|
||||
}
|
||||
|
||||
|
||||
func setFrame(for label: UILabel, rect: CGRect) {
|
||||
|
||||
|
||||
if Int(floor(rect.height)) == 0 || Int(floor(rect.width)) == 0 {
|
||||
hideView(label)
|
||||
} else {
|
||||
showView(label)
|
||||
label.setFrameIfNotEqual(rect)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
func addSubviewAtInit(_ view: UIView, hidden: Bool) {
|
||||
@@ -141,9 +141,9 @@ private extension MainTimelineTableViewCell {
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.isHidden = hidden
|
||||
}
|
||||
|
||||
|
||||
func commonInit() {
|
||||
|
||||
|
||||
addSubviewAtInit(titleView, hidden: false)
|
||||
addSubviewAtInit(summaryView, hidden: true)
|
||||
addSubviewAtInit(unreadIndicatorView, hidden: true)
|
||||
@@ -152,7 +152,7 @@ private extension MainTimelineTableViewCell {
|
||||
addSubviewAtInit(iconView, hidden: true)
|
||||
addSubviewAtInit(starView, hidden: true)
|
||||
}
|
||||
|
||||
|
||||
func updatedLayout(width: CGFloat) -> MainTimelineCellLayout {
|
||||
if UIApplication.shared.preferredContentSizeCategory.isAccessibilityCategory {
|
||||
return MainTimelineAccessibilityCellLayout(width: width, insets: safeAreaInsets, cellData: cellData)
|
||||
@@ -160,25 +160,25 @@ private extension MainTimelineTableViewCell {
|
||||
return MainTimelineDefaultCellLayout(width: width, insets: safeAreaInsets, cellData: cellData)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func updateTitleView() {
|
||||
titleView.font = MainTimelineDefaultCellLayout.titleFont
|
||||
titleView.textColor = labelColor
|
||||
updateTextFieldAttributedText(titleView, cellData?.attributedTitle)
|
||||
}
|
||||
|
||||
|
||||
func updateSummaryView() {
|
||||
summaryView.font = MainTimelineDefaultCellLayout.summaryFont
|
||||
summaryView.textColor = labelColor
|
||||
updateTextFieldText(summaryView, cellData?.summary)
|
||||
}
|
||||
|
||||
|
||||
func updateDateView() {
|
||||
dateView.font = MainTimelineDefaultCellLayout.dateFont
|
||||
dateView.textColor = secondaryLabelColor
|
||||
updateTextFieldText(dateView, cellData.dateString)
|
||||
}
|
||||
|
||||
|
||||
func updateTextFieldText(_ label: UILabel, _ text: String?) {
|
||||
let s = text ?? ""
|
||||
if label.text != s {
|
||||
@@ -199,7 +199,7 @@ private extension MainTimelineTableViewCell {
|
||||
setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func updateFeedNameView() {
|
||||
switch cellData.showFeedName {
|
||||
case .feed:
|
||||
@@ -216,7 +216,7 @@ private extension MainTimelineTableViewCell {
|
||||
hideView(feedNameView)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func updateUnreadIndicator() {
|
||||
if !unreadIndicatorView.isHidden && cellData.read && !cellData.starred {
|
||||
unreadIndicatorPropertyAnimator = UIViewPropertyAnimator(duration: 0.66, curve: .easeInOut) { [weak self] in
|
||||
@@ -233,7 +233,7 @@ private extension MainTimelineTableViewCell {
|
||||
showOrHideView(unreadIndicatorView, cellData.read || cellData.starred)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func updateStarView() {
|
||||
if !starView.isHidden && cellData.read && !cellData.starred {
|
||||
starViewPropertyAnimator = UIViewPropertyAnimator(duration: 0.66, curve: .easeInOut) { [weak self] in
|
||||
@@ -250,7 +250,7 @@ private extension MainTimelineTableViewCell {
|
||||
showOrHideView(starView, !cellData.starred)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func updateIconImage() {
|
||||
guard let image = cellData.iconImage, cellData.showIcon else {
|
||||
makeIconEmpty()
|
||||
@@ -258,20 +258,20 @@ private extension MainTimelineTableViewCell {
|
||||
}
|
||||
|
||||
showView(iconView)
|
||||
|
||||
|
||||
if iconView.iconImage !== cellData.iconImage {
|
||||
iconView.iconImage = image
|
||||
setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func updateAccessiblityLabel() {
|
||||
let starredStatus = cellData.starred ? "\(NSLocalizedString("Starred", comment: "Starred article for accessibility")), " : ""
|
||||
let unreadStatus = cellData.read ? "" : "\(NSLocalizedString("Unread", comment: "Unread")), "
|
||||
let label = starredStatus + unreadStatus + "\(cellData.feedName), \(cellData.title), \(cellData.summary), \(cellData.dateString)"
|
||||
accessibilityLabel = label
|
||||
}
|
||||
|
||||
|
||||
func makeIconEmpty() {
|
||||
if iconView.iconImage != nil {
|
||||
iconView.iconImage = nil
|
||||
@@ -279,23 +279,23 @@ private extension MainTimelineTableViewCell {
|
||||
}
|
||||
hideView(iconView)
|
||||
}
|
||||
|
||||
|
||||
func hideView(_ view: UIView) {
|
||||
if !view.isHidden {
|
||||
view.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func showView(_ view: UIView) {
|
||||
if view.isHidden {
|
||||
view.isHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func showOrHideView(_ view: UIView, _ shouldHide: Bool) {
|
||||
shouldHide ? hideView(view) : showView(view)
|
||||
}
|
||||
|
||||
|
||||
func updateSubviews() {
|
||||
updateTitleView()
|
||||
updateSummaryView()
|
||||
@@ -306,5 +306,5 @@ private extension MainTimelineTableViewCell {
|
||||
updateIconImage()
|
||||
updateAccessiblityLabel()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -15,5 +15,5 @@ class MainUnreadIndicatorView: UIView {
|
||||
layer.cornerRadius = frame.size.width / 2.0
|
||||
clipsToBounds = true
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ final class MultilineUILabelSizer {
|
||||
|
||||
self.singleLineHeightEstimate = MultilineUILabelSizer.calculateHeight("AqLjJ0/y", 200, font)
|
||||
self.doubleLineHeightEstimate = MultilineUILabelSizer.calculateHeight("AqLjJ0/y\nAqLjJ0/y", 200, font)
|
||||
|
||||
|
||||
}
|
||||
|
||||
static func size(for string: String, font: UIFont, numberOfLines: Int, width: Int) -> TextFieldSizeInfo {
|
||||
@@ -52,7 +52,7 @@ final class MultilineUILabelSizer {
|
||||
static func emptyCache() {
|
||||
sizers = [UILabelSizerSpecifier: MultilineUILabelSizer]()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
@@ -69,7 +69,7 @@ private extension MultilineUILabelSizer {
|
||||
let newSizer = MultilineUILabelSizer(numberOfLines: numberOfLines, font: font)
|
||||
sizers[specifier] = newSizer
|
||||
return newSizer
|
||||
|
||||
|
||||
}
|
||||
|
||||
func sizeInfo(for string: String, width: Int) -> TextFieldSizeInfo {
|
||||
@@ -80,7 +80,7 @@ private extension MultilineUILabelSizer {
|
||||
let size = CGSize(width: width, height: textFieldHeight)
|
||||
let sizeInfo = TextFieldSizeInfo(size: size, numberOfLinesUsed: numberOfLinesUsed)
|
||||
return sizeInfo
|
||||
|
||||
|
||||
}
|
||||
|
||||
func height(for string: String, width: Int) -> Int {
|
||||
@@ -98,14 +98,14 @@ private extension MultilineUILabelSizer {
|
||||
}
|
||||
|
||||
var height = MultilineUILabelSizer.calculateHeight(string, width, font)
|
||||
|
||||
|
||||
if numberOfLines != 0 {
|
||||
let maxHeight = singleLineHeightEstimate * numberOfLines
|
||||
if height > maxHeight {
|
||||
height = maxHeight
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cache[string]![width] = height
|
||||
|
||||
return height
|
||||
@@ -123,7 +123,7 @@ private extension MultilineUILabelSizer {
|
||||
let averageHeight = CGFloat(doubleLineHeightEstimate) / 2.0
|
||||
let lines = Int(round(CGFloat(height) / averageHeight))
|
||||
return lines
|
||||
|
||||
|
||||
}
|
||||
|
||||
func heightIsProbablySingleLineHeight(_ height: Int) -> Bool {
|
||||
@@ -140,7 +140,7 @@ private extension MultilineUILabelSizer {
|
||||
let minimum = estimate - slop
|
||||
let maximum = estimate + slop
|
||||
return height >= minimum && height <= maximum
|
||||
|
||||
|
||||
}
|
||||
|
||||
func heightConsideringNeighbors(_ heightCache: WidthHeightCache, _ width: Int) -> Int? {
|
||||
@@ -165,8 +165,7 @@ private extension MultilineUILabelSizer {
|
||||
|
||||
if oneWidth < width && (oneWidth > smallNeighbor.width || smallNeighbor.width == 0) {
|
||||
smallNeighbor = (oneWidth, oneHeight)
|
||||
}
|
||||
else if oneWidth > width && (oneWidth < largeNeighbor.width || largeNeighbor.width == 0) {
|
||||
} else if oneWidth > width && (oneWidth < largeNeighbor.width || largeNeighbor.width == 0) {
|
||||
largeNeighbor = (oneWidth, oneHeight)
|
||||
}
|
||||
|
||||
@@ -176,7 +175,7 @@ private extension MultilineUILabelSizer {
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -30,10 +30,10 @@ final class SingleLineUILabelSizer {
|
||||
let height = text.height(withConstrainedWidth: .greatestFiniteMagnitude, font: font)
|
||||
let width = text.width(withConstrainedHeight: .greatestFiniteMagnitude, font: font)
|
||||
let calculatedSize = CGSize(width: ceil(width), height: ceil(height))
|
||||
|
||||
|
||||
cache[text] = calculatedSize
|
||||
return calculatedSize
|
||||
|
||||
|
||||
}
|
||||
|
||||
static private var sizers = [UIFont: SingleLineUILabelSizer]()
|
||||
@@ -48,7 +48,7 @@ final class SingleLineUILabelSizer {
|
||||
sizers[font] = newSizer
|
||||
|
||||
return newSizer
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Use this call. It’s easiest.
|
||||
@@ -60,5 +60,5 @@ final class SingleLineUILabelSizer {
|
||||
static func emptyCache() {
|
||||
sizers = [UIFont: SingleLineUILabelSizer]()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -8,11 +8,10 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class MainTimelineDataSource<SectionIdentifierType, ItemIdentifierType>: UITableViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable {
|
||||
class MainTimelineDataSource<SectionIdentifierType, ItemIdentifierType>: UITableViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> where SectionIdentifierType: Hashable, ItemIdentifierType: Hashable {
|
||||
|
||||
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@ class MainTimelineTitleView: UIView {
|
||||
if let name = label.text {
|
||||
let unreadLabel = NSLocalizedString("unread", comment: "Unread label for accessibility")
|
||||
return "\(name) \(unreadCountView.unreadCount) \(unreadLabel)"
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -36,7 +35,7 @@ class MainTimelineTitleView: UIView {
|
||||
accessibilityTraits = .button
|
||||
addInteraction(pointerInteraction)
|
||||
}
|
||||
|
||||
|
||||
func debuttonize() {
|
||||
heightAnchor.constraint(equalToConstant: 40.0).isActive = true
|
||||
accessibilityTraits.remove(.button)
|
||||
@@ -45,7 +44,7 @@ class MainTimelineTitleView: UIView {
|
||||
}
|
||||
|
||||
extension MainTimelineTitleView: UIPointerInteractionDelegate {
|
||||
|
||||
|
||||
func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? {
|
||||
var rect = self.frame
|
||||
rect.origin.x = rect.origin.x - 10
|
||||
|
||||
@@ -17,11 +17,11 @@ class MainTimelineUnreadCountView: MainFeedUnreadCountView {
|
||||
override var textColor: UIColor {
|
||||
return UIColor.systemBackground
|
||||
}
|
||||
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
return contentSize
|
||||
}
|
||||
|
||||
|
||||
override func draw(_ dirtyRect: CGRect) {
|
||||
|
||||
let cornerRadii = CGSize(width: cornerRadius, height: cornerRadius)
|
||||
@@ -33,7 +33,7 @@ class MainTimelineUnreadCountView: MainFeedUnreadCountView {
|
||||
if unreadCount > 0 {
|
||||
unreadCountString.draw(at: textRect().origin, withAttributes: textAttributes)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -14,21 +14,20 @@ extension CGRect: MarkAsReadAlertControllerSourceType {}
|
||||
extension UIView: MarkAsReadAlertControllerSourceType {}
|
||||
extension UIBarButtonItem: MarkAsReadAlertControllerSourceType {}
|
||||
|
||||
|
||||
struct MarkAsReadAlertController {
|
||||
|
||||
|
||||
static func confirm<T>(_ controller: UIViewController?,
|
||||
coordinator: SceneCoordinator?,
|
||||
confirmTitle: String,
|
||||
sourceType: T,
|
||||
cancelCompletion: (() -> Void)? = nil,
|
||||
completion: @escaping () -> Void) where T: MarkAsReadAlertControllerSourceType {
|
||||
|
||||
|
||||
guard let controller = controller, let coordinator = coordinator else {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if AppDefaults.shared.confirmMarkAllAsRead {
|
||||
let alertController = MarkAsReadAlertController.alert(coordinator: coordinator, confirmTitle: confirmTitle, cancelCompletion: cancelCompletion, sourceType: sourceType) { _ in
|
||||
completion()
|
||||
@@ -38,20 +37,19 @@ struct MarkAsReadAlertController {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static func alert<T>(coordinator: SceneCoordinator,
|
||||
confirmTitle: String,
|
||||
cancelCompletion: (() -> Void)?,
|
||||
sourceType: T,
|
||||
completion: @escaping (UIAlertAction) -> Void) -> UIAlertController where T: MarkAsReadAlertControllerSourceType {
|
||||
|
||||
|
||||
completion: @escaping (UIAlertAction) -> Void) -> UIAlertController where T: MarkAsReadAlertControllerSourceType {
|
||||
|
||||
let title = NSLocalizedString("Mark As Read", comment: "Mark As Read")
|
||||
let message = NSLocalizedString("You can turn this confirmation off in Settings.",
|
||||
comment: "You can turn this confirmation off in Settings.")
|
||||
let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel")
|
||||
let settingsTitle = NSLocalizedString("Open Settings", comment: "Open Settings")
|
||||
|
||||
|
||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)
|
||||
let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel) { _ in
|
||||
cancelCompletion?()
|
||||
@@ -60,24 +58,24 @@ struct MarkAsReadAlertController {
|
||||
coordinator.showSettings(scrollToArticlesSection: true)
|
||||
}
|
||||
let markAction = UIAlertAction(title: confirmTitle, style: .default, handler: completion)
|
||||
|
||||
|
||||
alertController.addAction(markAction)
|
||||
alertController.addAction(settingsAction)
|
||||
alertController.addAction(cancelAction)
|
||||
|
||||
|
||||
if let barButtonItem = sourceType as? UIBarButtonItem {
|
||||
alertController.popoverPresentationController?.barButtonItem = barButtonItem
|
||||
}
|
||||
|
||||
|
||||
if let rect = sourceType as? CGRect {
|
||||
alertController.popoverPresentationController?.sourceRect = rect
|
||||
}
|
||||
|
||||
|
||||
if let view = sourceType as? UIView {
|
||||
alertController.popoverPresentationController?.sourceView = view
|
||||
}
|
||||
|
||||
return alertController
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
|
||||
private var numberOfTextLines = 0
|
||||
private var iconSize = IconSize.medium
|
||||
private lazy var feedTapGestureRecognizer = UITapGestureRecognizer(target: self, action:#selector(showFeedInspector(_:)))
|
||||
|
||||
private lazy var feedTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(showFeedInspector(_:)))
|
||||
|
||||
private var refreshProgressView: RefreshProgressView?
|
||||
|
||||
@IBOutlet weak var markAllAsReadButton: UIBarButtonItem!
|
||||
@@ -28,28 +28,28 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
|
||||
private lazy var dataSource = makeDataSource()
|
||||
private let searchController = UISearchController(searchResultsController: nil)
|
||||
|
||||
|
||||
weak var coordinator: SceneCoordinator!
|
||||
var undoableCommands = [UndoableCommand]()
|
||||
let scrollPositionQueue = CoalescingQueue(name: "Timeline Scroll Position", interval: 0.3, maxInterval: 1.0)
|
||||
|
||||
private let keyboardManager = KeyboardManager(type: .timeline)
|
||||
override var keyCommands: [UIKeyCommand]? {
|
||||
|
||||
|
||||
// If the first responder is the WKWebView we don't want to supply any keyboard
|
||||
// commands that the system is looking for by going up the responder chain. They will interfere with
|
||||
// the WKWebViews built in hardware keyboard shortcuts, specifically the up and down arrow keys.
|
||||
guard let current = UIResponder.currentFirstResponder, !(current is WKWebView) else { return nil }
|
||||
|
||||
|
||||
return keyboardManager.keyCommands
|
||||
}
|
||||
|
||||
|
||||
override var canBecomeFirstResponder: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
||||
@@ -68,11 +68,11 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange), name: .DisplayNameDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
|
||||
|
||||
|
||||
// Initialize Programmatic Buttons
|
||||
filterButton = UIBarButtonItem(image: AppAssets.filterInactiveImage, style: .plain, target: self, action: #selector(toggleFilter(_:)))
|
||||
firstUnreadButton = UIBarButtonItem(image: AppAssets.nextUnreadArticleImage, style: .plain, target: self, action: #selector(firstUnread(_:)))
|
||||
|
||||
|
||||
// Setup the Search Controller
|
||||
searchController.delegate = self
|
||||
searchController.searchResultsUpdater = self
|
||||
@@ -97,27 +97,27 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
if let titleView = Bundle.main.loadNibNamed("MainTimelineTitleView", owner: self, options: nil)?[0] as? MainTimelineTitleView {
|
||||
navigationItem.titleView = titleView
|
||||
}
|
||||
|
||||
|
||||
refreshControl = UIRefreshControl()
|
||||
refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged)
|
||||
|
||||
|
||||
configureToolbar()
|
||||
resetUI(resetScroll: true)
|
||||
|
||||
|
||||
// Load the table and then scroll to the saved position if available
|
||||
applyChanges(animated: false) {
|
||||
if let restoreIndexPath = self.coordinator.timelineMiddleIndexPath {
|
||||
self.tableView.scrollToRow(at: restoreIndexPath, at: .middle, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Disable swipe back on iPad Mice
|
||||
guard let gesture = self.navigationController?.interactivePopGestureRecognizer as? UIPanGestureRecognizer else {
|
||||
return
|
||||
}
|
||||
gesture.allowedScrollTypesMask = []
|
||||
}
|
||||
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
self.navigationController?.isToolbarHidden = false
|
||||
|
||||
@@ -125,10 +125,10 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
if navigationController?.navigationBar.isHidden ?? false {
|
||||
navigationController?.navigationBar.alpha = 0
|
||||
}
|
||||
|
||||
|
||||
super.viewWillAppear(animated)
|
||||
}
|
||||
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(true)
|
||||
coordinator.isTimelineViewControllerPending = false
|
||||
@@ -139,9 +139,9 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Actions
|
||||
|
||||
|
||||
@objc func openInBrowser(_ sender: Any?) {
|
||||
coordinator.showBrowserForCurrentArticle()
|
||||
}
|
||||
@@ -149,35 +149,35 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
@objc func openInAppBrowser(_ sender: Any?) {
|
||||
coordinator.showInAppBrowser()
|
||||
}
|
||||
|
||||
|
||||
@IBAction func toggleFilter(_ sender: Any) {
|
||||
coordinator.toggleReadArticlesFilter()
|
||||
}
|
||||
|
||||
|
||||
@IBAction func markAllAsRead(_ sender: Any) {
|
||||
let title = NSLocalizedString("Mark All as Read", comment: "Mark All as Read")
|
||||
|
||||
|
||||
if let source = sender as? UIBarButtonItem {
|
||||
MarkAsReadAlertController.confirm(self, coordinator: coordinator, confirmTitle: title, sourceType: source) { [weak self] in
|
||||
self?.coordinator.markAllAsReadInTimeline()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let _ = sender as? UIKeyCommand {
|
||||
guard let indexPath = tableView.indexPathForSelectedRow, let contentView = tableView.cellForRow(at: indexPath)?.contentView else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
MarkAsReadAlertController.confirm(self, coordinator: coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
|
||||
self?.coordinator.markAllAsReadInTimeline()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@IBAction func firstUnread(_ sender: Any) {
|
||||
coordinator.selectFirstUnread()
|
||||
}
|
||||
|
||||
|
||||
@objc func refreshAccounts(_ sender: Any) {
|
||||
refreshControl?.endRefreshing()
|
||||
|
||||
@@ -187,9 +187,9 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
appDelegate.manualRefresh(errorHandler: ErrorHandler.present(self))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Keyboard shortcuts
|
||||
|
||||
|
||||
@objc func selectNextUp(_ sender: Any?) {
|
||||
coordinator.selectPrevArticle()
|
||||
}
|
||||
@@ -201,17 +201,17 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
@objc func navigateToSidebar(_ sender: Any?) {
|
||||
coordinator.navigateToFeeds()
|
||||
}
|
||||
|
||||
|
||||
@objc func navigateToDetail(_ sender: Any?) {
|
||||
coordinator.navigateToDetail()
|
||||
}
|
||||
|
||||
|
||||
@objc func showFeedInspector(_ sender: Any?) {
|
||||
coordinator.showFeedInspector()
|
||||
}
|
||||
|
||||
// MARK: API
|
||||
|
||||
|
||||
func restoreSelectionIfNecessary(adjustScroll: Bool) {
|
||||
if let article = coordinator.currentArticle, let indexPath = dataSource.indexPath(for: article) {
|
||||
if adjustScroll {
|
||||
@@ -225,11 +225,11 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
func reinitializeArticles(resetScroll: Bool) {
|
||||
resetUI(resetScroll: resetScroll)
|
||||
}
|
||||
|
||||
|
||||
func reloadArticles(animated: Bool) {
|
||||
applyChanges(animated: animated)
|
||||
}
|
||||
|
||||
|
||||
func updateArticleSelection(animations: Animations) {
|
||||
if let article = coordinator.currentArticle, let indexPath = dataSource.indexPath(for: article) {
|
||||
if tableView.indexPathForSelectedRow != indexPath {
|
||||
@@ -238,7 +238,7 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
} else {
|
||||
tableView.selectRow(at: nil, animated: animations.contains(.select), scrollPosition: .none)
|
||||
}
|
||||
|
||||
|
||||
updateUI()
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
updateTitleUnreadCount()
|
||||
updateToolbar()
|
||||
}
|
||||
|
||||
|
||||
func hideSearch() {
|
||||
navigationItem.searchController?.isActive = false
|
||||
}
|
||||
@@ -257,7 +257,7 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
navigationItem.searchController?.searchBar.selectedScopeButtonIndex = 1
|
||||
navigationItem.searchController?.searchBar.becomeFirstResponder()
|
||||
}
|
||||
|
||||
|
||||
func focus() {
|
||||
becomeFirstResponder()
|
||||
}
|
||||
@@ -265,7 +265,7 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
func setRefreshToolbarItemVisibility(visible: Bool) {
|
||||
refreshProgressView?.alpha = visible ? 1.0 : 0
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Table view
|
||||
|
||||
override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||
@@ -276,41 +276,41 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
let readTitle = article.status.read ?
|
||||
NSLocalizedString("Mark as Unread", comment: "Mark as Unread") :
|
||||
NSLocalizedString("Mark as Read", comment: "Mark as Read")
|
||||
|
||||
let readAction = UIContextualAction(style: .normal, title: readTitle) { [weak self] (action, view, completion) in
|
||||
|
||||
let readAction = UIContextualAction(style: .normal, title: readTitle) { [weak self] (_, _, completion) in
|
||||
self?.coordinator.toggleRead(article)
|
||||
completion(true)
|
||||
}
|
||||
|
||||
|
||||
readAction.image = article.status.read ? AppAssets.circleClosedImage : AppAssets.circleOpenImage
|
||||
readAction.backgroundColor = AppAssets.primaryAccentColor
|
||||
|
||||
|
||||
return UISwipeActionsConfiguration(actions: [readAction])
|
||||
}
|
||||
|
||||
|
||||
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||
|
||||
|
||||
guard let article = dataSource.itemIdentifier(for: indexPath) else { return nil }
|
||||
|
||||
|
||||
// Set up the star action
|
||||
let starTitle = article.status.starred ?
|
||||
NSLocalizedString("Unstar", comment: "Unstar") :
|
||||
NSLocalizedString("Star", comment: "Star")
|
||||
|
||||
let starAction = UIContextualAction(style: .normal, title: starTitle) { [weak self] (action, view, completion) in
|
||||
|
||||
let starAction = UIContextualAction(style: .normal, title: starTitle) { [weak self] (_, _, completion) in
|
||||
self?.coordinator.toggleStar(article)
|
||||
completion(true)
|
||||
}
|
||||
|
||||
|
||||
starAction.image = article.status.starred ? AppAssets.starOpenImage : AppAssets.starClosedImage
|
||||
starAction.backgroundColor = AppAssets.starColor
|
||||
|
||||
|
||||
// Set up the read action
|
||||
let moreTitle = NSLocalizedString("More", comment: "More")
|
||||
let moreAction = UIContextualAction(style: .normal, title: moreTitle) { [weak self] (action, view, completion) in
|
||||
|
||||
|
||||
if let self = self {
|
||||
|
||||
|
||||
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
if let popoverController = alert.popoverPresentationController {
|
||||
popoverController.sourceView = view
|
||||
@@ -324,11 +324,11 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
if let action = self.markBelowAsReadAlertAction(article, indexPath: indexPath, completion: completion) {
|
||||
alert.addAction(action)
|
||||
}
|
||||
|
||||
|
||||
if let action = self.discloseFeedAlertAction(article, completion: completion) {
|
||||
alert.addAction(action)
|
||||
}
|
||||
|
||||
|
||||
if let action = self.markAllInFeedAsReadAlertAction(article, indexPath: indexPath, completion: completion) {
|
||||
alert.addAction(action)
|
||||
}
|
||||
@@ -347,28 +347,28 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
})
|
||||
|
||||
self.present(alert, animated: true)
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
moreAction.image = AppAssets.moreImage
|
||||
moreAction.backgroundColor = UIColor.systemGray
|
||||
|
||||
return UISwipeActionsConfiguration(actions: [starAction, moreAction])
|
||||
|
||||
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
||||
|
||||
guard let article = dataSource.itemIdentifier(for: indexPath) else { return nil }
|
||||
|
||||
return UIContextMenuConfiguration(identifier: indexPath.row as NSCopying, previewProvider: nil, actionProvider: { [weak self] suggestedActions in
|
||||
|
||||
return UIContextMenuConfiguration(identifier: indexPath.row as NSCopying, previewProvider: nil, actionProvider: { [weak self] _ in
|
||||
|
||||
guard let self = self else { return nil }
|
||||
|
||||
|
||||
var menuElements = [UIMenuElement]()
|
||||
|
||||
|
||||
var markActions = [UIAction]()
|
||||
if let action = self.toggleArticleReadStatusAction(article) {
|
||||
markActions.append(action)
|
||||
@@ -381,7 +381,7 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
markActions.append(action)
|
||||
}
|
||||
menuElements.append(UIMenu(title: "", options: .displayInline, children: markActions))
|
||||
|
||||
|
||||
var secondaryActions = [UIAction]()
|
||||
if let action = self.discloseFeedAction(article) {
|
||||
secondaryActions.append(action)
|
||||
@@ -392,7 +392,7 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
if !secondaryActions.isEmpty {
|
||||
menuElements.append(UIMenu(title: "", options: .displayInline, children: secondaryActions))
|
||||
}
|
||||
|
||||
|
||||
var copyActions = [UIAction]()
|
||||
if let action = self.copyArticleURLAction(article) {
|
||||
copyActions.append(action)
|
||||
@@ -403,19 +403,19 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
if !copyActions.isEmpty {
|
||||
menuElements.append(UIMenu(title: "", options: .displayInline, children: copyActions))
|
||||
}
|
||||
|
||||
|
||||
if let action = self.openInBrowserAction(article) {
|
||||
menuElements.append(UIMenu(title: "", options: .displayInline, children: [action]))
|
||||
}
|
||||
|
||||
|
||||
if let action = self.shareAction(article, indexPath: indexPath) {
|
||||
menuElements.append(UIMenu(title: "", options: .displayInline, children: [action]))
|
||||
}
|
||||
|
||||
|
||||
return UIMenu(title: "", children: menuElements)
|
||||
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
|
||||
@@ -423,7 +423,7 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
let cell = tableView.cellForRow(at: IndexPath(row: row, section: 0)) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
return UITargetedPreview(view: cell, parameters: CroppingPreviewParameters(view: cell))
|
||||
}
|
||||
|
||||
@@ -432,17 +432,17 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
let article = dataSource.itemIdentifier(for: indexPath)
|
||||
coordinator.selectArticle(article, animations: [.scroll, .select, .navigation])
|
||||
}
|
||||
|
||||
|
||||
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
scrollPositionQueue.add(self, #selector(scrollPositionDidChange))
|
||||
}
|
||||
|
||||
|
||||
// MARK: Notifications
|
||||
|
||||
@objc dynamic func unreadCountDidChange(_ notification: Notification) {
|
||||
updateUI()
|
||||
}
|
||||
|
||||
|
||||
@objc func statusesDidChange(_ note: Notification) {
|
||||
guard let articleIDs = note.userInfo?[Account.UserInfoKey.articleIDs] as? Set<String>, !articleIDs.isEmpty else {
|
||||
return
|
||||
@@ -461,11 +461,11 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
}
|
||||
|
||||
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
|
||||
|
||||
|
||||
if let titleView = navigationItem.titleView as? MainTimelineTitleView {
|
||||
titleView.iconView.iconImage = coordinator.timelineIconImage
|
||||
}
|
||||
|
||||
|
||||
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
|
||||
return
|
||||
}
|
||||
@@ -519,27 +519,27 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
self.updateToolbar()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@objc func contentSizeCategoryDidChange(_ note: Notification) {
|
||||
reloadAllVisibleCells()
|
||||
}
|
||||
|
||||
|
||||
@objc func displayNameDidChange(_ note: Notification) {
|
||||
if let titleView = navigationItem.titleView as? MainTimelineTitleView {
|
||||
titleView.label.text = coordinator.timelineFeed?.nameForDisplay
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@objc func willEnterForeground(_ note: Notification) {
|
||||
updateUI()
|
||||
}
|
||||
|
||||
|
||||
@objc func scrollPositionDidChange() {
|
||||
coordinator.timelineMiddleIndexPath = tableView.middleVisibleRow()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Reloading
|
||||
|
||||
|
||||
func queueReloadAvailableCells() {
|
||||
CoalescingQueue.standard.add(self, #selector(reloadAllVisibleCells))
|
||||
}
|
||||
@@ -566,7 +566,7 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, feedID: prototypeID, uniqueID: prototypeID, title: Constants.prototypeText, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, datePublished: nil, dateModified: nil, authors: nil, status: status)
|
||||
|
||||
let prototypeCellData = MainTimelineCellData(article: prototypeArticle, showFeedName: .feed, feedName: "Prototype Feed Name", byline: nil, iconImage: nil, showIcon: false, numberOfLines: numberOfTextLines, iconSize: iconSize)
|
||||
|
||||
|
||||
if UIApplication.shared.preferredContentSizeCategory.isAccessibilityCategory {
|
||||
let layout = MainTimelineAccessibilityCellLayout(width: tableView.bounds.width, insets: tableView.safeAreaInsets, cellData: prototypeCellData)
|
||||
tableView.estimatedRowHeight = layout.height
|
||||
@@ -574,9 +574,9 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
|
||||
let layout = MainTimelineDefaultCellLayout(width: tableView.bounds.width, insets: tableView.safeAreaInsets, cellData: prototypeCellData)
|
||||
tableView.estimatedRowHeight = layout.height
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: Searching
|
||||
@@ -619,7 +619,7 @@ private extension TimelineViewController {
|
||||
guard !(splitViewController?.isCollapsed ?? true) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let refreshProgressView = Bundle.main.loadNibNamed("RefreshProgressView", owner: self, options: nil)?[0] as? RefreshProgressView else {
|
||||
return
|
||||
}
|
||||
@@ -630,7 +630,7 @@ private extension TimelineViewController {
|
||||
}
|
||||
|
||||
func resetUI(resetScroll: Bool) {
|
||||
|
||||
|
||||
title = coordinator.timelineFeed?.nameForDisplay ?? "Timeline"
|
||||
|
||||
if let titleView = navigationItem.titleView as? MainTimelineTitleView {
|
||||
@@ -641,7 +641,7 @@ private extension TimelineViewController {
|
||||
} else {
|
||||
titleView.iconView.tintColor = nil
|
||||
}
|
||||
|
||||
|
||||
titleView.label.text = coordinator.timelineFeed?.nameForDisplay
|
||||
updateTitleUnreadCount()
|
||||
|
||||
@@ -652,7 +652,7 @@ private extension TimelineViewController {
|
||||
titleView.debuttonize()
|
||||
titleView.removeGestureRecognizer(feedTapGestureRecognizer)
|
||||
}
|
||||
|
||||
|
||||
navigationItem.titleView = titleView
|
||||
}
|
||||
|
||||
@@ -662,7 +662,7 @@ private extension TimelineViewController {
|
||||
case .alwaysRead:
|
||||
navigationItem.rightBarButtonItem = nil
|
||||
}
|
||||
|
||||
|
||||
if coordinator.isReadArticlesFiltered {
|
||||
filterButton?.image = AppAssets.filterActiveImage
|
||||
filterButton?.accLabelText = NSLocalizedString("Selected - Filter Read Articles", comment: "Selected - Filter Read Articles")
|
||||
@@ -679,16 +679,16 @@ private extension TimelineViewController {
|
||||
tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
updateToolbar()
|
||||
}
|
||||
|
||||
|
||||
func updateToolbar() {
|
||||
guard firstUnreadButton != nil else { return }
|
||||
|
||||
|
||||
markAllAsReadButton.isEnabled = coordinator.isTimelineUnreadAvailable
|
||||
firstUnreadButton.isEnabled = coordinator.isTimelineUnreadAvailable
|
||||
|
||||
|
||||
if coordinator.isRootSplitCollapsed {
|
||||
if let toolbarItems = toolbarItems, toolbarItems.last != firstUnreadButton {
|
||||
var items = toolbarItems
|
||||
@@ -702,20 +702,20 @@ private extension TimelineViewController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func updateTitleUnreadCount() {
|
||||
if let titleView = navigationItem.titleView as? MainTimelineTitleView {
|
||||
titleView.unreadCountView.unreadCount = coordinator.timelineUnreadCount
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func applyChanges(animated: Bool, completion: (() -> Void)? = nil) {
|
||||
if coordinator.articles.count == 0 {
|
||||
tableView.rowHeight = tableView.estimatedRowHeight
|
||||
} else {
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
}
|
||||
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Int, Article>()
|
||||
snapshot.appendSections([0])
|
||||
snapshot.appendItems(coordinator.articles, toSection: 0)
|
||||
@@ -725,7 +725,7 @@ private extension TimelineViewController {
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func makeDataSource() -> UITableViewDiffableDataSource<Int, Article> {
|
||||
let dataSource: UITableViewDiffableDataSource<Int, Article> =
|
||||
MainTimelineDataSource(tableView: tableView, cellProvider: { [weak self] tableView, indexPath, article in
|
||||
@@ -736,7 +736,7 @@ private extension TimelineViewController {
|
||||
dataSource.defaultRowAnimation = .middle
|
||||
return dataSource
|
||||
}
|
||||
|
||||
|
||||
func configure(_ cell: MainTimelineTableViewCell, article: Article) {
|
||||
|
||||
let iconImage = iconImageFor(article)
|
||||
@@ -746,29 +746,29 @@ private extension TimelineViewController {
|
||||
cell.cellData = MainTimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, byline: article.byline(), iconImage: iconImage, showIcon: showIcon, numberOfLines: numberOfTextLines, iconSize: iconSize)
|
||||
|
||||
}
|
||||
|
||||
|
||||
func iconImageFor(_ article: Article) -> IconImage? {
|
||||
if !coordinator.showIcons {
|
||||
return nil
|
||||
}
|
||||
return article.iconImage()
|
||||
}
|
||||
|
||||
|
||||
func toggleArticleReadStatusAction(_ article: Article) -> UIAction? {
|
||||
guard !article.status.read || article.isAvailableToMarkUnread else { return nil }
|
||||
|
||||
|
||||
let title = article.status.read ?
|
||||
NSLocalizedString("Mark as Unread", comment: "Mark as Unread") :
|
||||
NSLocalizedString("Mark as Read", comment: "Mark as Read")
|
||||
let image = article.status.read ? AppAssets.circleClosedImage : AppAssets.circleOpenImage
|
||||
|
||||
let action = UIAction(title: title, image: image) { [weak self] action in
|
||||
let action = UIAction(title: title, image: image) { [weak self] _ in
|
||||
self?.coordinator.toggleRead(article)
|
||||
}
|
||||
|
||||
|
||||
return action
|
||||
}
|
||||
|
||||
|
||||
func toggleArticleStarStatusAction(_ article: Article) -> UIAction {
|
||||
|
||||
let title = article.status.starred ?
|
||||
@@ -776,10 +776,10 @@ private extension TimelineViewController {
|
||||
NSLocalizedString("Mark as Starred", comment: "Mark as Starred")
|
||||
let image = article.status.starred ? AppAssets.starOpenImage : AppAssets.starClosedImage
|
||||
|
||||
let action = UIAction(title: title, image: image) { [weak self] action in
|
||||
let action = UIAction(title: title, image: image) { [weak self] _ in
|
||||
self?.coordinator.toggleStar(article)
|
||||
}
|
||||
|
||||
|
||||
return action
|
||||
}
|
||||
|
||||
@@ -790,14 +790,14 @@ private extension TimelineViewController {
|
||||
|
||||
let title = NSLocalizedString("Mark Above as Read", comment: "Mark Above as Read")
|
||||
let image = AppAssets.markAboveAsReadImage
|
||||
let action = UIAction(title: title, image: image) { [weak self] action in
|
||||
let action = UIAction(title: title, image: image) { [weak self] _ in
|
||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
|
||||
self?.coordinator.markAboveAsRead(article)
|
||||
}
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
|
||||
func markBelowAsReadAction(_ article: Article, indexPath: IndexPath) -> UIAction? {
|
||||
guard coordinator.canMarkBelowAsRead(for: article), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
|
||||
return nil
|
||||
@@ -805,14 +805,14 @@ private extension TimelineViewController {
|
||||
|
||||
let title = NSLocalizedString("Mark Below as Read", comment: "Mark Below as Read")
|
||||
let image = AppAssets.markBelowAsReadImage
|
||||
let action = UIAction(title: title, image: image) { [weak self] action in
|
||||
let action = UIAction(title: title, image: image) { [weak self] _ in
|
||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
|
||||
self?.coordinator.markBelowAsRead(article)
|
||||
}
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
|
||||
func markAboveAsReadAlertAction(_ article: Article, indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
|
||||
guard coordinator.canMarkAboveAsRead(for: article), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
|
||||
return nil
|
||||
@@ -823,7 +823,7 @@ private extension TimelineViewController {
|
||||
completion(true)
|
||||
}
|
||||
|
||||
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
||||
let action = UIAlertAction(title: title, style: .default) { [weak self] _ in
|
||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView, cancelCompletion: cancel) { [weak self] in
|
||||
self?.coordinator.markAboveAsRead(article)
|
||||
completion(true)
|
||||
@@ -841,8 +841,8 @@ private extension TimelineViewController {
|
||||
let cancel = {
|
||||
completion(true)
|
||||
}
|
||||
|
||||
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
||||
|
||||
let action = UIAlertAction(title: title, style: .default) { [weak self] _ in
|
||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView, cancelCompletion: cancel) { [weak self] in
|
||||
self?.coordinator.markBelowAsRead(article)
|
||||
completion(true)
|
||||
@@ -854,26 +854,26 @@ private extension TimelineViewController {
|
||||
func discloseFeedAction(_ article: Article) -> UIAction? {
|
||||
guard let feed = article.feed,
|
||||
!coordinator.timelineFeedIsEqualTo(feed) else { return nil }
|
||||
|
||||
|
||||
let title = NSLocalizedString("Go to Feed", comment: "Go to Feed")
|
||||
let action = UIAction(title: title, image: AppAssets.openInSidebarImage) { [weak self] action in
|
||||
let action = UIAction(title: title, image: AppAssets.openInSidebarImage) { [weak self] _ in
|
||||
self?.coordinator.discloseFeed(feed, animations: [.scroll, .navigation])
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
|
||||
func discloseFeedAlertAction(_ article: Article, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
|
||||
guard let feed = article.feed,
|
||||
!coordinator.timelineFeedIsEqualTo(feed) else { return nil }
|
||||
|
||||
let title = NSLocalizedString("Go to Feed", comment: "Go to Feed")
|
||||
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
||||
let action = UIAlertAction(title: title, style: .default) { [weak self] _ in
|
||||
self?.coordinator.discloseFeed(feed, animations: [.scroll, .navigation])
|
||||
completion(true)
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
|
||||
func markAllInFeedAsReadAction(_ article: Article, indexPath: IndexPath) -> UIAction? {
|
||||
guard let feed = article.feed else { return nil }
|
||||
guard let fetchedArticles = try? feed.fetchArticles() else {
|
||||
@@ -884,12 +884,11 @@ private extension TimelineViewController {
|
||||
guard articles.canMarkAllAsRead(), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Command")
|
||||
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String
|
||||
|
||||
let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] action in
|
||||
|
||||
let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] _ in
|
||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
|
||||
self?.coordinator.markAllAsRead(articles)
|
||||
}
|
||||
@@ -902,19 +901,19 @@ private extension TimelineViewController {
|
||||
guard let fetchedArticles = try? feed.fetchArticles() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
let articles = Array(fetchedArticles)
|
||||
guard articles.canMarkAllAsRead(), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Mark All as Read in Feed")
|
||||
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String
|
||||
let cancel = {
|
||||
completion(true)
|
||||
}
|
||||
|
||||
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
||||
|
||||
let action = UIAlertAction(title: title, style: .default) { [weak self] _ in
|
||||
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView, cancelCompletion: cancel) { [weak self] in
|
||||
self?.coordinator.markAllAsRead(articles)
|
||||
completion(true)
|
||||
@@ -922,30 +921,29 @@ private extension TimelineViewController {
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
|
||||
func copyArticleURLAction(_ article: Article) -> UIAction? {
|
||||
guard let url = article.preferredURL else { return nil }
|
||||
let title = NSLocalizedString("Copy Article URL", comment: "Copy Article URL")
|
||||
let action = UIAction(title: title, image: AppAssets.copyImage) { action in
|
||||
UIPasteboard.general.url = url
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
func copyExternalURLAction(_ article: Article) -> UIAction? {
|
||||
guard let externalLink = article.externalLink, externalLink != article.preferredLink, let url = URL(string: externalLink) else { return nil }
|
||||
let title = NSLocalizedString("Copy External URL", comment: "Copy External URL")
|
||||
let action = UIAction(title: title, image: AppAssets.copyImage) { action in
|
||||
let action = UIAction(title: title, image: AppAssets.copyImage) { _ in
|
||||
UIPasteboard.general.url = url
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
func copyExternalURLAction(_ article: Article) -> UIAction? {
|
||||
guard let externalLink = article.externalLink, externalLink != article.preferredLink, let url = URL(string: externalLink) else { return nil }
|
||||
let title = NSLocalizedString("Copy External URL", comment: "Copy External URL")
|
||||
let action = UIAction(title: title, image: AppAssets.copyImage) { _ in
|
||||
UIPasteboard.general.url = url
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
func openInBrowserAction(_ article: Article) -> UIAction? {
|
||||
guard let _ = article.preferredURL else { return nil }
|
||||
let title = NSLocalizedString("Open in Browser", comment: "Open in Browser")
|
||||
let action = UIAction(title: title, image: AppAssets.safariImage) { [weak self] action in
|
||||
let action = UIAction(title: title, image: AppAssets.safariImage) { [weak self] _ in
|
||||
self?.coordinator.showBrowserForArticle(article)
|
||||
}
|
||||
return action
|
||||
@@ -955,41 +953,41 @@ private extension TimelineViewController {
|
||||
guard let _ = article.preferredURL else { return nil }
|
||||
|
||||
let title = NSLocalizedString("Open in Browser", comment: "Open in Browser")
|
||||
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
||||
let action = UIAlertAction(title: title, style: .default) { [weak self] _ in
|
||||
self?.coordinator.showBrowserForArticle(article)
|
||||
completion(true)
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
|
||||
func shareDialogForTableCell(indexPath: IndexPath, url: URL, title: String?) {
|
||||
let activityViewController = UIActivityViewController(url: url, title: title, applicationActivities: nil)
|
||||
|
||||
|
||||
guard let cell = tableView.cellForRow(at: indexPath) else { return }
|
||||
let popoverController = activityViewController.popoverPresentationController
|
||||
popoverController?.sourceView = cell
|
||||
popoverController?.sourceRect = CGRect(x: 0, y: 0, width: cell.frame.size.width, height: cell.frame.size.height)
|
||||
|
||||
|
||||
present(activityViewController, animated: true)
|
||||
}
|
||||
|
||||
|
||||
func shareAction(_ article: Article, indexPath: IndexPath) -> UIAction? {
|
||||
guard let url = article.preferredURL else { return nil }
|
||||
let title = NSLocalizedString("Share", comment: "Share")
|
||||
let action = UIAction(title: title, image: AppAssets.shareImage) { [weak self] action in
|
||||
let action = UIAction(title: title, image: AppAssets.shareImage) { [weak self] _ in
|
||||
self?.shareDialogForTableCell(indexPath: indexPath, url: url, title: article.title)
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
|
||||
func shareAlertAction(_ article: Article, indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
|
||||
guard let url = article.preferredURL else { return nil }
|
||||
let title = NSLocalizedString("Share", comment: "Share")
|
||||
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
||||
let action = UIAlertAction(title: title, style: .default) { [weak self] _ in
|
||||
completion(true)
|
||||
self?.shareDialogForTableCell(indexPath: indexPath, url: url, title: article.title)
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user