Skip to content

Commit

Permalink
Improve Item and SavedItem access via @query (#1100)
Browse files Browse the repository at this point in the history
* feat(swiftui home): improve access to Item and SavedItem via @query

* feat(swiftui home): rename HomeCard to HomeCardConfiguration
  • Loading branch information
Gio2018 authored Oct 30, 2024
1 parent ecaa5d2 commit 701befc
Show file tree
Hide file tree
Showing 18 changed files with 86 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

@preconcurrency import Sync

// TODO: SWIFTUI - Add analytics
/// Type that contains all the actions that can be performed from Home and its detail views
struct HomeActions {
// TODO: SWIFTUI - the following methods use a reference to Services that only lives in their scope.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,17 @@ import Localization
import SwiftUI
import Textile

// TODO: SWIFTUI - Add analytics

/// Representation of an `Item` suitable for being displayed in a `Hero` or a `Carousel` card.
/// Card configuration
@MainActor
struct HomeCard: Identifiable, @preconcurrency Equatable, Hashable {
static func == (lhs: HomeCard, rhs: HomeCard) -> Bool {
struct HomeCardConfiguration: Identifiable, @preconcurrency Equatable, Hashable {
static func == (lhs: HomeCardConfiguration, rhs: HomeCardConfiguration) -> Bool {
lhs.givenURL == rhs.givenURL &&
lhs.imageURL == rhs.imageURL &&
lhs.sharedWithYouUrlString == rhs.sharedWithYouUrlString
}

var id = UUID()
let givenURL: String
let imageURL: URL?
let sharedWithYouUrlString: String?
let shareURL: String?
let showExcerpt: Bool

// actions configuration
Expand All @@ -36,9 +31,7 @@ struct HomeCard: Identifiable, @preconcurrency Equatable, Hashable {

init(
givenURL: String,
imageURL: URL?,
sharedWithYouUrlString: String? = nil,
ShareURL: String? = nil,
showExcerpt: Bool = false,
enableSaveAction: Bool = false,
enableFavoriteAction: Bool = false,
Expand All @@ -48,9 +41,7 @@ struct HomeCard: Identifiable, @preconcurrency Equatable, Hashable {
enableDeleteMenuAction: Bool = false
) {
self.givenURL = givenURL
self.imageURL = imageURL
self.sharedWithYouUrlString = sharedWithYouUrlString
self.shareURL = ShareURL
self.showExcerpt = showExcerpt
self.enableSaveAction = enableSaveAction
self.enableFavoriteAction = enableFavoriteAction
Expand All @@ -60,22 +51,3 @@ struct HomeCard: Identifiable, @preconcurrency Equatable, Hashable {
self.enableDeleteMenuAction = enableDeleteMenuAction
}
}

// MARK: Styler
extension HomeCard {
var collectionStyle: Style {
.recommendation.collection
}

func titleStyle(largeTitle: Bool) -> Style {
.recommendation.adaptiveTitle(largeTitle)
}

var domainStyle: Style {
.recommendation.domain
}

func timeToRead(_ timeToRead: Int32) -> AttributedString {
AttributedString(NSAttributedString(string: Localization.Home.Recommendation.readTime(timeToRead), style: .recommendation.timeToRead))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ import Foundation
/// Model for a row of cards in a grid
struct HomeRow: Identifiable {
var id = UUID()
let cards: [HomeCard]
let cards: [HomeCardConfiguration]
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import Textile
/// - `wide`: 2-column vertical grid
/// - `extraWide`: 3-column vertical grid
struct CardCollection: View {
let cards: [HomeCard]
let cards: [HomeCardConfiguration]
let size: CardSize
let layoutWidth: LayoutWidth
let header: CollectionHeader?
Expand All @@ -22,7 +22,7 @@ struct CardCollection: View {

@Namespace private var scrollViewCoordinateSpace

init(cards: [HomeCard], size: CardSize, layoutWidth: LayoutWidth, header: CollectionHeader? = nil) {
init(cards: [HomeCardConfiguration], size: CardSize, layoutWidth: LayoutWidth, header: CollectionHeader? = nil) {
self.cards = cards
self.size = size
self.layoutWidth = layoutWidth
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import SwiftUI
import Sync

struct CarouselView: View {
let cards: [HomeCard]
let cards: [HomeCardConfiguration]
let useGrid: Bool

var body: some View {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Sync

struct HeroView: View {
let remoteID: String
let cards: [HomeCard]
let cards: [HomeCardConfiguration]

var body: some View {
if cards.count == 1, let card = cards.first {
Expand All @@ -21,7 +21,7 @@ struct HeroView: View {

// MARK: View builders
private extension HeroView {
func makeHeroCard(_ card: HomeCard) -> some View {
func makeHeroCard(_ card: HomeCardConfiguration) -> some View {
CardView(card: card, size: .large)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import SwiftData
struct SlateView: View {
let remoteID: String
let slateTitle: String?
let cards: [HomeCard]
let cards: [HomeCardConfiguration]

@Environment(\.layoutWidth)
private var layoutWidth
Expand Down Expand Up @@ -51,12 +51,12 @@ private extension SlateView {
}

/// Extract the Hero recommendations
var heroCards: [HomeCard] {
var heroCards: [HomeCardConfiguration] {
Array(cards.prefix(upTo: heroCount))
}

/// Extract the carousel recommendations
var carouselCards: [HomeCard] {
var carouselCards: [HomeCardConfiguration] {
Array(cards.dropFirst(heroCount))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import SwiftUI
import Textile

struct CardFooter: View {
let card: HomeCard
let card: HomeCardConfiguration
let shareURL: String?
let domain: String?
let timeToRead: Int32?
let isSaved: Bool
Expand All @@ -17,9 +18,7 @@ struct CardFooter: View {

@State private var showReportArticle: Bool = false
@State private var showReportError: Bool = false

@State private var showDeleteAlert: Bool = false

@State private var showShareSheet: Bool = false

@Environment(\.homeActions)
Expand Down Expand Up @@ -67,18 +66,27 @@ private extension CardFooter {
VStack(alignment: .leading, spacing: Self.stackSpacing) {
if let domain {
makeDomain(domain)
.style(card.domainStyle)
.style(.recommendation.domain)
.lineLimit(Self.footerElementLineLimit)
.accessibilityIdentifier("domain-label")
}
if let timeToRead, timeToRead > 0 {
Text(card.timeToRead(timeToRead))
Text(makeTimeToRead(timeToRead))
.lineLimit(Self.footerElementLineLimit)
.accessibilityIdentifier("time-to-read-label")
}
}
}

func makeTimeToRead(_ timeToRead: Int32) -> AttributedString {
AttributedString(
NSAttributedString(
string: Localization.Home.Recommendation.readTime(timeToRead),
style: .recommendation.timeToRead
)
)
}

func makeDomain(_ domain: String) -> Text {
if isSyndicated {
return Text(domain) + Text(" ") + Text(Image(systemName: "checkmark.seal"))
Expand Down Expand Up @@ -178,7 +186,7 @@ private extension CardFooter {
}

if card.enableShareMenuAction {
ShareableURLView(givenURL: card.givenURL, shareURL: card.shareURL)
ShareableURLView(givenURL: card.givenURL, shareURL: shareURL)
}
} label: {
Image(asset: .overflow)
Expand Down
31 changes: 20 additions & 11 deletions PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Cards/CardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ enum CardSize {

/// Card view for the Home screen. Can have various sizes, specified by the `size` property.
struct CardView: View {
let card: HomeCard
let card: HomeCardConfiguration
let size: CardSize

@Environment(\.carouselWidth)
Expand All @@ -29,24 +29,28 @@ struct CardView: View {

@State private var presentWebView: Bool = false

@Query private var items: [Item]

@Query private var fetchedItem: [Item]
private var item: Item? {
items.first
fetchedItem.first
}

@Query private var fetchedSavedItem: [SavedItem]
private var savedItem: SavedItem? {
item?.savedItem
fetchedSavedItem.first
}

init(card: HomeCard, size: CardSize) {
init(card: HomeCardConfiguration, size: CardSize) {
self.card = card
self.size = size

let givenUrl = card.givenURL
var itemDescriptor = FetchDescriptor<Item>(predicate: #Predicate<Item> { $0.givenURL == givenUrl })
itemDescriptor.fetchLimit = 1
_items = Query(itemDescriptor, animation: .easeIn)
_fetchedItem = Query(itemDescriptor, animation: .easeIn)

var savedItemDescriptor = FetchDescriptor<SavedItem>(predicate: #Predicate<SavedItem> { $0.item?.givenURL == givenUrl })
savedItemDescriptor.fetchLimit = 1
_fetchedSavedItem = Query(savedItemDescriptor, animation: .easeIn)
}

var body: some View {
Expand Down Expand Up @@ -88,6 +92,7 @@ private extension CardView {
Spacer()
CardFooter(
card: card,
shareURL: item?.shareURL,
domain: item?.bestDomain,
timeToRead: item?.timeToRead,
isSaved: savedItem != nil && savedItem?.isArchived == false,
Expand Down Expand Up @@ -146,11 +151,11 @@ private extension CardView {
VStack(alignment: .leading) {
if item?.isCollection == true {
Text(Localization.Constants.collection)
.style(card.collectionStyle)
.style(.recommendation.collection)
.accessibilityIdentifier("collection-label")
}
Text(item?.bestTitle ?? card.givenURL)
.style(card.titleStyle(largeTitle: !card.showExcerpt && size == .large))
.style(makeTitleStyle(largeTitle: !card.showExcerpt && size == .large))
.lineSpacing(Constants.titleLineSpacing)
.lineLimit(Constants.titleLineLimit)
.accessibilityIdentifier("title-label")
Expand All @@ -174,21 +179,25 @@ private extension CardView {
.padding(Constants.textStackPadding(size))
}

func makeTitleStyle(largeTitle: Bool) -> Style {
.recommendation.adaptiveTitle(largeTitle)
}

/// Thumbnail
@ViewBuilder
func makeImage() -> some View {
switch size {
case .medium:
VStack {
RemoteImage(url: card.imageURL, imageSize: Constants.smallThumbnailSize, usePlaceholder: false)
RemoteImage(url: item?.topImageURL, imageSize: Constants.smallThumbnailSize, usePlaceholder: false)
.aspectRatio(contentMode: .fit)
.frame(width: Constants.smallThumbnailSize.width, height: Constants.smallThumbnailSize.height)
.clipShape(RoundedRectangle(cornerRadius: Constants.cornerRadius))
.clipped()
Spacer()
}
case .large:
RemoteImage(url: card.imageURL, imageSize: largeImageSize, usePlaceholder: true)
RemoteImage(url: item?.topImageURL, imageSize: largeImageSize, usePlaceholder: true)
.aspectRatio(Constants.largeThumbnailAspectRatio, contentMode: .fit)
.fixedSize(horizontal: false, vertical: true)
.frame(minWidth: 0, maxWidth: .infinity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct CollectionStoriesView: View {

@Query private var stories: [CollectionStory]

@State private var cards: [HomeCard] = []
@State private var cards: [HomeCardConfiguration] = []

@Environment(\.horizontalSizeClass)
var horizontalSizeClass
Expand Down Expand Up @@ -48,14 +48,12 @@ struct CollectionStoriesView: View {

// MARK: helpers
private extension CollectionStoriesView {
var proposedCards: [HomeCard] {
var proposedCards: [HomeCardConfiguration] {
stories.compactMap {
if let item = $0.item {
return HomeCard(
return HomeCardConfiguration(
givenURL: item.givenURL,
imageURL: item.topImageURL,
sharedWithYouUrlString: nil,
ShareURL: item.shareURL,
showExcerpt: true,
enableSaveAction: true,
enableShareMenuAction: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,45 @@ import Textile
struct NativeCollectionView: View {
let destination: NativeCollectionDestination

@Query private var items: [Item]

@State private var showDeleteAlert: Bool = false
@State private var showReportError: Bool = false
@State private var showReportArticle: Bool = false

@Query private var fetchedItem: [Item]
private var item: Item? {
items.first
fetchedItem.first
}

private var collection: Collection? {
item?.collection
}
private var recommendationID: String? {
item?.recommendation?.analyticsID
}

@Query private var fetchedSavedItem: [SavedItem]
private var savedItem: SavedItem? {
item?.savedItem
fetchedSavedItem.first
}

private var recommendationID: String? {
item?.recommendation?.analyticsID
private var isSaved: Bool {
savedItem != nil && savedItem?.isArchived == false
}

@Environment(\.homeActions)
var homeActions

@EnvironmentObject var navigation: HomeNavigation

var isSaved: Bool {
savedItem != nil && savedItem?.isArchived == false
}

init(destination: NativeCollectionDestination) {
self.destination = destination
var itemDescriptor = FetchDescriptor<Item>(predicate: #Predicate<Item> { $0.givenURL == destination.givenURL })
let givenURL = destination.givenURL

var itemDescriptor = FetchDescriptor<Item>(predicate: #Predicate<Item> { $0.givenURL == givenURL })
itemDescriptor.fetchLimit = 1
_items = Query(itemDescriptor, animation: .easeIn)
_fetchedItem = Query(itemDescriptor, animation: .easeIn)

var savedItemDescriptor = FetchDescriptor<SavedItem>(predicate: #Predicate<SavedItem> { $0.item?.givenURL == givenURL })
savedItemDescriptor.fetchLimit = 1
_fetchedSavedItem = Query(savedItemDescriptor, animation: .easeIn)
}

var body: some View {
Expand Down
Loading

0 comments on commit 701befc

Please sign in to comment.