Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Highlights - Swipe from the trailing edge to highlight an entire paragraph #974

Merged
merged 2 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,30 @@ class ImageComponentCell: UICollectionViewCell {
required init?(coder: NSCoder) {
fatalError("Unable to instantiate \(Self.self) from xib/storyboard")
}

var imageHeight: CGFloat?

private var preferredSize: CGSize {
let availableWidth = readableContentGuide.layoutFrame.width

var height = imageHeight ?? availableWidth * 9 / 16

if let caption = captionTextView.attributedText, !caption.string.isEmpty {
height += ImageComponentCell.Constants.captionSpacing
height += caption.sizeFitting(availableWidth: availableWidth).height
}

height += ImageComponentCell.Constants.layoutMargins.top
height += ImageComponentCell.Constants.layoutMargins.bottom

return CGSize(width: availableWidth, height: height)
}

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
let attributes = super.preferredLayoutAttributesFitting(layoutAttributes)
attributes.size.height = preferredSize.height
return attributes
}
}

extension ImageComponentCell {
Expand Down Expand Up @@ -100,7 +124,9 @@ extension ImageComponentCell {
switch result {
case .success(let result):
self?.imageView.backgroundColor = model.imageViewBackgroundColor(imageSize: result.image.size)
self?.imageHeight = result.image.size.height
imageLoaded?(result.image)
self?.layoutIfNeeded()
case .failure:
break
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class VimeoComponentCell: UICollectionViewCell {
}
}

var oembedSize: CGSize?

weak var delegate: VimeoComponentCellDelegate?

private let webView: WKWebView = {
Expand All @@ -48,6 +50,13 @@ class VimeoComponentCell: UICollectionViewCell {
return view
}()

private var preferredSize: CGSize {
CGSize(
width: oembedSize?.width ?? readableContentGuide.layoutFrame.width,
height: oembedSize?.height ?? readableContentGuide.layoutFrame.width * 9 / 16
)
}

override init(frame: CGRect) {
super.init(frame: frame)

Expand Down Expand Up @@ -83,6 +92,12 @@ class VimeoComponentCell: UICollectionViewCell {
fatalError("init(coder:) has not been implemented")
}

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
let attributes = super.preferredLayoutAttributesFitting(layoutAttributes)
attributes.size.height = preferredSize.height
return attributes
}

private func updateContent() {
switch mode {
case .loading(let content):
Expand All @@ -105,6 +120,7 @@ class VimeoComponentCell: UICollectionViewCell {
loadingView.isHidden = true
loadingView.stopAnimating()
}
layoutIfNeeded()
}

private func invokeErrorViewAction() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ class YouTubeVideoComponentCell: UICollectionViewCell {
return webView
}()

private var preferredSize: CGSize {
CGSize(width: readableContentGuide.layoutFrame.width, height: readableContentGuide.layoutFrame.width * 9 / 16)
}

var player: YouTubePlayer {
hostingView.player
}
Expand Down Expand Up @@ -80,6 +84,12 @@ class YouTubeVideoComponentCell: UICollectionViewCell {
fatalError("Unable to instantiate \(Self.self) from xib/storyboard")
}

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
let attributes = super.preferredLayoutAttributesFitting(layoutAttributes)
attributes.size.height = preferredSize.height
return attributes
}

func cue(vid: String) {
player.cue(source: .video(id: vid))
}
Expand All @@ -103,6 +113,7 @@ private extension YouTubeVideoComponentCell {
case .error:
setError()
}
layoutIfNeeded()
}

func setLoading() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ class VimeoComponentPresenter: ArticleComponentPresenter {

do {
oEmbed = try await oEmbedService.fetch(request: request)
if let width = oEmbed?.width, let height = oEmbed?.height {
vimeoCell.oembedSize = CGSize(width: width, height: height)
}
} catch {
vimeoCell.mode = .error
return
Expand Down
50 changes: 26 additions & 24 deletions PocketKit/Sources/PocketKit/Article/ReadableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ class ReadableViewController: UIViewController {
}

collectionView.selectItem(at: userProgress, animated: true, scrollPosition: .centeredVertically)
collectionView.setNeedsLayout()
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
Expand Down Expand Up @@ -387,30 +388,31 @@ extension ReadableViewController {
section.contentInsetsReference = .readableContent
return section
default:
let availableItemWidth = view.readableContentGuide.layoutFrame.width

var height: CGFloat = 0
let subitems = presenters?.compactMap { presenter -> NSCollectionLayoutItem? in
let size = presenter.size(for: availableItemWidth)
height += size.height
let layoutSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .estimated(size.height)
)

return NSCollectionLayoutItem(layoutSize: layoutSize)
// for image presenters, calling size will set the image size used by Kingfisher
if let imagePresenters = presenters?.compactMap({ $0 as? ImageComponentPresenter }) {
let availableItemWidth = view.readableContentGuide.layoutFrame.width
imagePresenters.forEach {
_ = $0.size(for: availableItemWidth)
}
}
var config = UICollectionLayoutListConfiguration(appearance: .plain)
config.backgroundColor = UIColor(.ui.white1)
config.showsSeparators = false
config.trailingSwipeActionsConfigurationProvider = { [unowned self] indexPath in
guard let presenter = presenters?[safe: indexPath.item],
presenter.highlightIndexes == nil,
let currentCell = self.collectionView.cellForItem(at: indexPath) as? ArticleComponentTextCell else {
return nil
}
let action = UIContextualAction(style: .normal, title: "Highlight") {_, _, completion in
currentCell.highlightAll()
completion(true)
}
action.backgroundColor = UIColor(.ui.highlightAction)
return UISwipeActionsConfiguration(actions: [action])
}
let section = NSCollectionLayoutSection.list(using: config, layoutEnvironment: environment)

let group = NSCollectionLayoutGroup.vertical(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .estimated(height)
),
subitems: subitems ?? []
)
group.interItemSpacing = .fixed(0)

let section = NSCollectionLayoutSection(group: group)
// Zero out the default leading/trailing contentInsets, but preserve the default top/bottom values.
// This ensures each section will be inset horizontally exactly to the readable content width.
var contentInsets = section.contentInsets
Expand All @@ -434,7 +436,7 @@ extension ReadableViewController {
return MarkdownComponentPresenter(component: component, readerSettings: readerSettings, componentType: .heading, componentIndex: index)
case .image(let component):
return ImageComponentPresenter(component: component, readerSettings: readerSettings, componentIndex: index) { [weak self] in
self?.layout.invalidateLayout()
self?.collectionView.layoutIfNeeded()
}
case .divider(let component):
return DividerComponentPresenter(component: component, componentIndex: index)
Expand Down Expand Up @@ -463,7 +465,7 @@ extension ReadableViewController {
component: component,
componentIndex: index
) { [weak self] in
self?.layout.invalidateLayout()
self?.collectionView.layoutIfNeeded()
}
default:
return UnsupportedComponentPresenter(readableViewModel: readableViewModel, componentIndex: index)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x00",
"green" : "0x53",
"red" : "0xE5"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x05",
"green" : "0x46",
"red" : "0x98"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
1 change: 1 addition & 0 deletions PocketKit/Sources/Textile/Style/Colors/Palettes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public struct UIPalette {
public let skeletonCellImageBackground = ColorAsset.ui("skeletonCellImageBackground")

public let highlight = ColorAsset.ui("Highlight")
public let highlightAction = ColorAsset.ui("HighlightAction")
}

public struct BrandingPalette {
Expand Down