From 763b6ddefc53045e348db74067f009ca70135da9 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Wed, 20 Mar 2024 23:40:28 +0100 Subject: [PATCH 01/12] Implemented the calculation of reviews' top words for the FeedListViewController view controller in the Feed framework. --- .../Logic/View Models/FeedListViewModel.swift | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedListViewModel.swift b/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedListViewModel.swift index a36237a..4ece55b 100644 --- a/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedListViewModel.swift +++ b/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedListViewModel.swift @@ -18,6 +18,9 @@ extension FeedListViewController { // MARK: Constants private let configuration: Configuration + + private let filterWords: FilterWordsUseCase = .init() + private let topWords: TopWordsUseCase = .init() // MARK: Properties @Published var filter: FilterOption = .all @@ -26,9 +29,11 @@ extension FeedListViewController { @Published var isLoading: Bool = false var items: [Review] = [] + var words: [WordCount] = [] private var reviewsAll: [Review] = [] private var reviewsFiltered: FilteredReviews = [:] + private var reviewsTopWords: TopWordsReviews = [:] lazy private var iTunesService: iTunesService = { .init(configuration: .init(session: configuration.session)) @@ -56,6 +61,12 @@ extension FeedListViewController { .reduce(into: FilteredReviews()) { partialResult, option in partialResult[option] = reviewsAll.filter { $0.rating.stars == option.rawValue } } + reviewsTopWords = reviewsFiltered + .mapValues { reviews in + reviews.map(\.comment) + .compactMap { try? filterWords($0) } + } + .mapValues { topWords($0) } items = reviewsAll isFilterEnabled = !items.isEmpty @@ -74,7 +85,8 @@ extension FeedListViewController { items = option == .all ? reviewsAll : reviewsFiltered[option] ?? [] - + words = reviewsTopWords[option] ?? [] + filter = option } @@ -86,5 +98,6 @@ private extension FeedListViewController.ViewModel { // MARK: Type aliases typealias FilteredReviews = [FilterOption: [Review]] + typealias TopWordsReviews = [FilterOption: [WordCount]] } -- 2.47.1 From d5a288ff39a194c9b54c60c5ec3343f102dd62cb Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 21 Mar 2024 00:04:28 +0100 Subject: [PATCH 02/12] Renamed the FeedItemCell component in the Feed framework as FeedItemView. --- .../FeedListViewController.swift | 4 ++-- .../FeedItemView.swift} | 8 +++---- Reviews.xcodeproj/project.pbxproj | 22 ++++++------------- 3 files changed, 13 insertions(+), 21 deletions(-) rename Frameworks/Feed/Bundle/Sources/UI/{Components/Cells/FeedItemCell.swift => Views/FeedItemView.swift} (94%) diff --git a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift index ed019a8..b9a2797 100644 --- a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift +++ b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift @@ -97,11 +97,11 @@ public class FeedListViewController: UITableViewController { cell.contentConfiguration = { if #available(iOS 16.0, *) { UIHostingConfiguration { - FeedItemCell(items[indexPath.row]) + FeedItemView(items[indexPath.row]) } } else { HostingConfiguration { - FeedItemCell(items[indexPath.row]) + FeedItemView(items[indexPath.row]) } } }() diff --git a/Frameworks/Feed/Bundle/Sources/UI/Components/Cells/FeedItemCell.swift b/Frameworks/Feed/Bundle/Sources/UI/Views/FeedItemView.swift similarity index 94% rename from Frameworks/Feed/Bundle/Sources/UI/Components/Cells/FeedItemCell.swift rename to Frameworks/Feed/Bundle/Sources/UI/Views/FeedItemView.swift index 4b5cef9..63fd399 100644 --- a/Frameworks/Feed/Bundle/Sources/UI/Components/Cells/FeedItemCell.swift +++ b/Frameworks/Feed/Bundle/Sources/UI/Views/FeedItemView.swift @@ -1,5 +1,5 @@ // -// FeedItemCell.swift +// FeedItemView.swift // ReviewsFeed // // Created by Javier Cicchelli on 19/03/2024. @@ -9,7 +9,7 @@ import ReviewsUIKit import SwiftUI -struct FeedItemCell: View { +struct FeedItemView: View { // MARK: Constants private let item: Review @@ -66,8 +66,8 @@ struct FeedItemCell: View { } // MARK: - Previews -#Preview("Feed Item Cell") { - FeedItemCell(.init( +#Preview("Feed Item") { + FeedItemView(.init( author: "Some author name here...", comment: "Some review comment here...", id: 0, diff --git a/Reviews.xcodeproj/project.pbxproj b/Reviews.xcodeproj/project.pbxproj index 9402628..24a85d5 100644 --- a/Reviews.xcodeproj/project.pbxproj +++ b/Reviews.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 0220ADA32BA90646001E6A9F /* FeedItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0220ADA22BA90646001E6A9F /* FeedItemCell.swift */; }; + 0220ADA32BA90646001E6A9F /* FeedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0220ADA22BA90646001E6A9F /* FeedItemView.swift */; }; 023AC7FC2BAA3EC10027D064 /* Int+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023AC7FB2BAA3EC10027D064 /* Int+Constants.swift */; }; 02620B8C2BA89C9A00DE7137 /* FeedListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02620B8B2BA89C9A00DE7137 /* FeedListViewModel.swift */; }; 02909E792BAB6B0200710E14 /* FilterOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02909E782BAB6B0200710E14 /* FilterOption.swift */; }; @@ -52,7 +52,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 0220ADA22BA90646001E6A9F /* FeedItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItemCell.swift; sourceTree = ""; }; + 0220ADA22BA90646001E6A9F /* FeedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItemView.swift; sourceTree = ""; }; 023AC7FB2BAA3EC10027D064 /* Int+Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+Constants.swift"; sourceTree = ""; }; 02620B8B2BA89C9A00DE7137 /* FeedListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListViewModel.swift; sourceTree = ""; }; 02909E782BAB6B0200710E14 /* FilterOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterOption.swift; sourceTree = ""; }; @@ -93,14 +93,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 0220ADA72BA98F8B001E6A9F /* Cells */ = { - isa = PBXGroup; - children = ( - 0220ADA22BA90646001E6A9F /* FeedItemCell.swift */, - ); - path = Cells; - sourceTree = ""; - }; 023AC7FA2BAA3EB60027D064 /* Extensions */ = { isa = PBXGroup; children = ( @@ -114,7 +106,7 @@ 02620B852BA89BF900DE7137 /* UI */ = { isa = PBXGroup; children = ( - 02620B892BA89C2400DE7137 /* Components */, + 02620B892BA89C2400DE7137 /* Views */, 02620B882BA89C1000DE7137 /* View Controllers */, ); path = UI; @@ -148,12 +140,12 @@ path = "View Controllers"; sourceTree = ""; }; - 02620B892BA89C2400DE7137 /* Components */ = { + 02620B892BA89C2400DE7137 /* Views */ = { isa = PBXGroup; children = ( - 0220ADA72BA98F8B001E6A9F /* Cells */, + 0220ADA22BA90646001E6A9F /* FeedItemView.swift */, ); - path = Components; + path = Views; sourceTree = ""; }; 02620B8A2BA89C3300DE7137 /* Models */ = { @@ -426,7 +418,7 @@ 02DC7FAC2BA51B4C000EEEBE /* FeedItemViewController.swift in Sources */, 02909E7B2BAB6D2E00710E14 /* Bundle+Constants.swift in Sources */, 02909E7D2BAB7FFE00710E14 /* Review+DTOs.swift in Sources */, - 0220ADA32BA90646001E6A9F /* FeedItemCell.swift in Sources */, + 0220ADA32BA90646001E6A9F /* FeedItemView.swift in Sources */, 02DC7FAF2BA51B4C000EEEBE /* Review.swift in Sources */, 02DC7FAE2BA51B4C000EEEBE /* FeedListViewController.swift in Sources */, 02909E792BAB6B0200710E14 /* FilterOption.swift in Sources */, -- 2.47.1 From d05f1984718d6d1869daa6609fb1880419d0731a Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 21 Mar 2024 00:12:54 +0100 Subject: [PATCH 03/12] Implemented the FeedItemCell table cell in the Feed framework. --- .../Bundle/Sources/UI/Cells/FeedItemCell.swift | 16 ++++++++++++++++ .../FeedListViewController.swift | 7 +++++-- Reviews.xcodeproj/project.pbxproj | 12 ++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 Frameworks/Feed/Bundle/Sources/UI/Cells/FeedItemCell.swift diff --git a/Frameworks/Feed/Bundle/Sources/UI/Cells/FeedItemCell.swift b/Frameworks/Feed/Bundle/Sources/UI/Cells/FeedItemCell.swift new file mode 100644 index 0000000..1e9f4d7 --- /dev/null +++ b/Frameworks/Feed/Bundle/Sources/UI/Cells/FeedItemCell.swift @@ -0,0 +1,16 @@ +// +// FeedItemCell.swift +// ReviewsFeed +// +// Created by Javier Cicchelli on 21/03/2024. +// Copyright © 2024 Röck+Cöde. All rights reserved. +// + +import UIKit + +final class FeedItemCell: UITableViewCell { + + // MARK: Constants + static let identifier = "FeedItemCell" + +} diff --git a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift index b9a2797..5c301bb 100644 --- a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift +++ b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift @@ -90,7 +90,7 @@ public class FeedListViewController: UITableViewController { _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: .Cell.feedItem) else { + guard let cell = tableView.dequeueReusableCell(withIdentifier: FeedItemCell.identifier) else { return .init() } @@ -159,7 +159,10 @@ private extension FeedListViewController { } func registerTableCells() { - tableView.register(UITableViewCell.self, forCellReuseIdentifier: .Cell.feedItem) + tableView.register( + FeedItemCell.self, + forCellReuseIdentifier: FeedItemCell.identifier + ) } func setNavigationBar() { diff --git a/Reviews.xcodeproj/project.pbxproj b/Reviews.xcodeproj/project.pbxproj index 24a85d5..3e7bec7 100644 --- a/Reviews.xcodeproj/project.pbxproj +++ b/Reviews.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 02DC7FAF2BA51B4C000EEEBE /* Review.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345AD13124C6EE64004E2EE1 /* Review.swift */; }; 02DC7FB32BA52518000EEEBE /* ReviewsKit in Frameworks */ = {isa = PBXBuildFile; productRef = 02DC7FB22BA52518000EEEBE /* ReviewsKit */; }; 02DC7FB52BA52520000EEEBE /* ReviewsKit in Frameworks */ = {isa = PBXBuildFile; productRef = 02DC7FB42BA52520000EEEBE /* ReviewsKit */; }; + 02EACF2E2BABA34600FF8ECD /* FeedItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EACF2D2BABA34600FF8ECD /* FeedItemCell.swift */; }; 345AD11C24C6EDD9004E2EE1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345AD11B24C6EDD9004E2EE1 /* AppDelegate.swift */; }; 345AD12524C6EDDC004E2EE1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 345AD12424C6EDDC004E2EE1 /* Assets.xcassets */; }; 345AD12824C6EDDC004E2EE1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 345AD12624C6EDDC004E2EE1 /* LaunchScreen.storyboard */; }; @@ -62,6 +63,7 @@ 02DC7F8F2BA51793000EEEBE /* ReviewsFeed.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReviewsFeed.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 02DC7F912BA51793000EEEBE /* ReviewsFeed.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReviewsFeed.h; sourceTree = ""; }; 02DC7FB12BA52084000EEEBE /* Libraries */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Libraries; sourceTree = ""; }; + 02EACF2D2BABA34600FF8ECD /* FeedItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItemCell.swift; sourceTree = ""; }; 345AD11824C6EDD9004E2EE1 /* Reviews.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Reviews.app; sourceTree = BUILT_PRODUCTS_DIR; }; 345AD11B24C6EDD9004E2EE1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 345AD12424C6EDDC004E2EE1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -106,6 +108,7 @@ 02620B852BA89BF900DE7137 /* UI */ = { isa = PBXGroup; children = ( + 02EACF2C2BABA32A00FF8ECD /* Cells */, 02620B892BA89C2400DE7137 /* Views */, 02620B882BA89C1000DE7137 /* View Controllers */, ); @@ -265,6 +268,14 @@ path = Sources; sourceTree = ""; }; + 02EACF2C2BABA32A00FF8ECD /* Cells */ = { + isa = PBXGroup; + children = ( + 02EACF2D2BABA34600FF8ECD /* FeedItemCell.swift */, + ); + path = Cells; + sourceTree = ""; + }; 345AD10F24C6EDD9004E2EE1 = { isa = PBXGroup; children = ( @@ -417,6 +428,7 @@ 023AC7FC2BAA3EC10027D064 /* Int+Constants.swift in Sources */, 02DC7FAC2BA51B4C000EEEBE /* FeedItemViewController.swift in Sources */, 02909E7B2BAB6D2E00710E14 /* Bundle+Constants.swift in Sources */, + 02EACF2E2BABA34600FF8ECD /* FeedItemCell.swift in Sources */, 02909E7D2BAB7FFE00710E14 /* Review+DTOs.swift in Sources */, 0220ADA32BA90646001E6A9F /* FeedItemView.swift in Sources */, 02DC7FAF2BA51B4C000EEEBE /* Review.swift in Sources */, -- 2.47.1 From 3632de9518eafaba2c5cb0f4f13590b86b1cd15d Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 21 Mar 2024 00:17:43 +0100 Subject: [PATCH 04/12] Defined the CellIdentifiable protocol in the UI library. --- .../Kit/Sources/Protocols/CellIdentifiable.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Libraries/UI/Kit/Sources/Protocols/CellIdentifiable.swift diff --git a/Libraries/UI/Kit/Sources/Protocols/CellIdentifiable.swift b/Libraries/UI/Kit/Sources/Protocols/CellIdentifiable.swift new file mode 100644 index 0000000..d44056b --- /dev/null +++ b/Libraries/UI/Kit/Sources/Protocols/CellIdentifiable.swift @@ -0,0 +1,16 @@ +// +// CellIdentifiable.swift +// ReviewsUIKit +// +// Created by Javier Cicchelli on 21/03/2024. +// Copyright © 2024 Röck+Cöde VoF. All rights reserved. +// + +import UIKit + +public protocol CellIdentifiable: UITableViewCell { + + // MARK: Properties + static var cellID: String { get } + +} -- 2.47.1 From dc91c93a5eb02cb5a41d28d68fe392d0b07250bc Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 21 Mar 2024 00:18:34 +0100 Subject: [PATCH 05/12] Conformed the FeedItemCell table cell in the Feed framework to the CellIdentifiable protocol. --- Frameworks/Feed/Bundle/Sources/UI/Cells/FeedItemCell.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Frameworks/Feed/Bundle/Sources/UI/Cells/FeedItemCell.swift b/Frameworks/Feed/Bundle/Sources/UI/Cells/FeedItemCell.swift index 1e9f4d7..5d67c30 100644 --- a/Frameworks/Feed/Bundle/Sources/UI/Cells/FeedItemCell.swift +++ b/Frameworks/Feed/Bundle/Sources/UI/Cells/FeedItemCell.swift @@ -6,11 +6,12 @@ // Copyright © 2024 Röck+Cöde. All rights reserved. // +import ReviewsUIKit import UIKit -final class FeedItemCell: UITableViewCell { - +final class FeedItemCell: UITableViewCell, CellIdentifiable { + // MARK: Constants - static let identifier = "FeedItemCell" + static let cellID = "FeedItemCell" } -- 2.47.1 From 863f1c08ade39149762ba9390f06066015cc5864 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 21 Mar 2024 00:19:54 +0100 Subject: [PATCH 06/12] Implemented the TopWordsCell table cell in the Feed framework. --- .../Bundle/Sources/UI/Cells/TopWordsCell.swift | 17 +++++++++++++++++ Reviews.xcodeproj/project.pbxproj | 4 ++++ 2 files changed, 21 insertions(+) create mode 100644 Frameworks/Feed/Bundle/Sources/UI/Cells/TopWordsCell.swift diff --git a/Frameworks/Feed/Bundle/Sources/UI/Cells/TopWordsCell.swift b/Frameworks/Feed/Bundle/Sources/UI/Cells/TopWordsCell.swift new file mode 100644 index 0000000..b0b9520 --- /dev/null +++ b/Frameworks/Feed/Bundle/Sources/UI/Cells/TopWordsCell.swift @@ -0,0 +1,17 @@ +// +// TopWordsCell.swift +// ReviewsFeed +// +// Created by Javier Cicchelli on 21/03/2024. +// Copyright © 2024 Röck+Cöde. All rights reserved. +// + +import ReviewsUIKit +import UIKit + +final class TopWordsCell: UITableViewCell, CellIdentifiable { + + // MARK: Constants + static let cellID = "TopWordsCell" + +} diff --git a/Reviews.xcodeproj/project.pbxproj b/Reviews.xcodeproj/project.pbxproj index 3e7bec7..f674b36 100644 --- a/Reviews.xcodeproj/project.pbxproj +++ b/Reviews.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ 02DC7FB32BA52518000EEEBE /* ReviewsKit in Frameworks */ = {isa = PBXBuildFile; productRef = 02DC7FB22BA52518000EEEBE /* ReviewsKit */; }; 02DC7FB52BA52520000EEEBE /* ReviewsKit in Frameworks */ = {isa = PBXBuildFile; productRef = 02DC7FB42BA52520000EEEBE /* ReviewsKit */; }; 02EACF2E2BABA34600FF8ECD /* FeedItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EACF2D2BABA34600FF8ECD /* FeedItemCell.swift */; }; + 02EACF302BABA50D00FF8ECD /* TopWordsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EACF2F2BABA50D00FF8ECD /* TopWordsCell.swift */; }; 345AD11C24C6EDD9004E2EE1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345AD11B24C6EDD9004E2EE1 /* AppDelegate.swift */; }; 345AD12524C6EDDC004E2EE1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 345AD12424C6EDDC004E2EE1 /* Assets.xcassets */; }; 345AD12824C6EDDC004E2EE1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 345AD12624C6EDDC004E2EE1 /* LaunchScreen.storyboard */; }; @@ -64,6 +65,7 @@ 02DC7F912BA51793000EEEBE /* ReviewsFeed.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReviewsFeed.h; sourceTree = ""; }; 02DC7FB12BA52084000EEEBE /* Libraries */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Libraries; sourceTree = ""; }; 02EACF2D2BABA34600FF8ECD /* FeedItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItemCell.swift; sourceTree = ""; }; + 02EACF2F2BABA50D00FF8ECD /* TopWordsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopWordsCell.swift; sourceTree = ""; }; 345AD11824C6EDD9004E2EE1 /* Reviews.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Reviews.app; sourceTree = BUILT_PRODUCTS_DIR; }; 345AD11B24C6EDD9004E2EE1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 345AD12424C6EDDC004E2EE1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -272,6 +274,7 @@ isa = PBXGroup; children = ( 02EACF2D2BABA34600FF8ECD /* FeedItemCell.swift */, + 02EACF2F2BABA50D00FF8ECD /* TopWordsCell.swift */, ); path = Cells; sourceTree = ""; @@ -424,6 +427,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 02EACF302BABA50D00FF8ECD /* TopWordsCell.swift in Sources */, 02620B8C2BA89C9A00DE7137 /* FeedListViewModel.swift in Sources */, 023AC7FC2BAA3EC10027D064 /* Int+Constants.swift in Sources */, 02DC7FAC2BA51B4C000EEEBE /* FeedItemViewController.swift in Sources */, -- 2.47.1 From fa81de0a66946168839d01c7045e2f48e376a4fa Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 21 Mar 2024 00:33:03 +0100 Subject: [PATCH 07/12] Implemented the TableCell protocol in the UI library. --- .../Sources/Protocols/CellIdentifiable.swift | 16 --------- .../UI/Kit/Sources/Protocols/TableCell.swift | 33 +++++++++++++++++++ 2 files changed, 33 insertions(+), 16 deletions(-) delete mode 100644 Libraries/UI/Kit/Sources/Protocols/CellIdentifiable.swift create mode 100644 Libraries/UI/Kit/Sources/Protocols/TableCell.swift diff --git a/Libraries/UI/Kit/Sources/Protocols/CellIdentifiable.swift b/Libraries/UI/Kit/Sources/Protocols/CellIdentifiable.swift deleted file mode 100644 index d44056b..0000000 --- a/Libraries/UI/Kit/Sources/Protocols/CellIdentifiable.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// CellIdentifiable.swift -// ReviewsUIKit -// -// Created by Javier Cicchelli on 21/03/2024. -// Copyright © 2024 Röck+Cöde VoF. All rights reserved. -// - -import UIKit - -public protocol CellIdentifiable: UITableViewCell { - - // MARK: Properties - static var cellID: String { get } - -} diff --git a/Libraries/UI/Kit/Sources/Protocols/TableCell.swift b/Libraries/UI/Kit/Sources/Protocols/TableCell.swift new file mode 100644 index 0000000..e158213 --- /dev/null +++ b/Libraries/UI/Kit/Sources/Protocols/TableCell.swift @@ -0,0 +1,33 @@ +// +// TableCell.swift +// ReviewsUIKit +// +// Created by Javier Cicchelli on 21/03/2024. +// Copyright © 2024 Röck+Cöde VoF. All rights reserved. +// + +import UIKit + +public protocol TableCell: UITableViewCell { + + // MARK: Properties + static var cellID: String { get } + + // MARK: Functions + static func dequeue(from tableView: UITableView) -> UITableViewCell? + static func register(in tableView: UITableView) + +} + +// MARK: - Implementations +extension TableCell { + + public static func dequeue(from tableView: UITableView) -> UITableViewCell? { + tableView.dequeueReusableCell(withIdentifier: cellID) + } + + public static func register(in tableView: UITableView) { + tableView.register(Self.self, forCellReuseIdentifier: cellID) + } + +} -- 2.47.1 From 8c0fed90077b4dbe7c53f52327e1b4a9628e51ca Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 21 Mar 2024 00:34:01 +0100 Subject: [PATCH 08/12] Conformed the FeedItemCell and the TopWordsCell table cells in the Feed framework to the TableCell protocol from the UI library. --- Frameworks/Feed/Bundle/Sources/UI/Cells/FeedItemCell.swift | 2 +- Frameworks/Feed/Bundle/Sources/UI/Cells/TopWordsCell.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Frameworks/Feed/Bundle/Sources/UI/Cells/FeedItemCell.swift b/Frameworks/Feed/Bundle/Sources/UI/Cells/FeedItemCell.swift index 5d67c30..a877df0 100644 --- a/Frameworks/Feed/Bundle/Sources/UI/Cells/FeedItemCell.swift +++ b/Frameworks/Feed/Bundle/Sources/UI/Cells/FeedItemCell.swift @@ -9,7 +9,7 @@ import ReviewsUIKit import UIKit -final class FeedItemCell: UITableViewCell, CellIdentifiable { +final class FeedItemCell: UITableViewCell, TableCell { // MARK: Constants static let cellID = "FeedItemCell" diff --git a/Frameworks/Feed/Bundle/Sources/UI/Cells/TopWordsCell.swift b/Frameworks/Feed/Bundle/Sources/UI/Cells/TopWordsCell.swift index b0b9520..378ffd2 100644 --- a/Frameworks/Feed/Bundle/Sources/UI/Cells/TopWordsCell.swift +++ b/Frameworks/Feed/Bundle/Sources/UI/Cells/TopWordsCell.swift @@ -9,7 +9,7 @@ import ReviewsUIKit import UIKit -final class TopWordsCell: UITableViewCell, CellIdentifiable { +final class TopWordsCell: UITableViewCell, TableCell { // MARK: Constants static let cellID = "TopWordsCell" -- 2.47.1 From 0f4fb372e3a6a780c37339d577e4e2b892e3f430 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 21 Mar 2024 00:41:08 +0100 Subject: [PATCH 09/12] Improved the FeedListViewController view controller in the Feed framework a little bit. --- .../FeedListViewController.swift | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift index 5c301bb..26876e1 100644 --- a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift +++ b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift @@ -40,11 +40,12 @@ public class FeedListViewController: UITableViewController { image: UIImage.Icon.star, children: { FilterOption.allCases.map { option -> UIAction in - .init(title: option.text, - image: .init(systemName: option.icon) - ) { [weak self] _ in - self?.viewModel.filter(by: option) - } + .init( + title: option.text, + image: .init(systemName: option.icon) + ) { [weak self] _ in + self?.viewModel.filter(by: option) + } } }() ) @@ -90,7 +91,7 @@ public class FeedListViewController: UITableViewController { _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: FeedItemCell.identifier) else { + guard let cell = FeedItemCell.dequeue(from: tableView) else { return .init() } @@ -159,10 +160,8 @@ private extension FeedListViewController { } func registerTableCells() { - tableView.register( - FeedItemCell.self, - forCellReuseIdentifier: FeedItemCell.identifier - ) + FeedItemCell.register(in: tableView) + TopWordsCell.register(in: tableView) } func setNavigationBar() { -- 2.47.1 From c9ea6de3d503ea6a3b6b58dd86459ab47f0fa014 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 21 Mar 2024 02:01:11 +0100 Subject: [PATCH 10/12] Implemented the TopWord model in the Feed framework. --- .../Logic/Extensions/TopWord+DTOs.swift | 23 +++++++++++++++++++ .../Bundle/Sources/Logic/Models/TopWord.swift | 16 +++++++++++++ Reviews.xcodeproj/project.pbxproj | 8 +++++++ 3 files changed, 47 insertions(+) create mode 100644 Frameworks/Feed/Bundle/Sources/Logic/Extensions/TopWord+DTOs.swift create mode 100644 Frameworks/Feed/Bundle/Sources/Logic/Models/TopWord.swift diff --git a/Frameworks/Feed/Bundle/Sources/Logic/Extensions/TopWord+DTOs.swift b/Frameworks/Feed/Bundle/Sources/Logic/Extensions/TopWord+DTOs.swift new file mode 100644 index 0000000..8f1fb6a --- /dev/null +++ b/Frameworks/Feed/Bundle/Sources/Logic/Extensions/TopWord+DTOs.swift @@ -0,0 +1,23 @@ +// +// TopWord+DTOs.swift +// ReviewsFeed +// +// Created by Javier Cicchelli on 21/03/2024. +// Copyright © 2024 Röck+Cöde. All rights reserved. +// + +import Foundation +import ReviewsFilterKit + +extension TopWord { + + // MARK: Initialisers + init(_ dto: WordCount) { + self = .init( + id: UUID().uuidString, + term: dto.word.term, + count: dto.count + ) + } + +} diff --git a/Frameworks/Feed/Bundle/Sources/Logic/Models/TopWord.swift b/Frameworks/Feed/Bundle/Sources/Logic/Models/TopWord.swift new file mode 100644 index 0000000..8db97e8 --- /dev/null +++ b/Frameworks/Feed/Bundle/Sources/Logic/Models/TopWord.swift @@ -0,0 +1,16 @@ +// +// TopWord.swift +// ReviewsFeed +// +// Created by Javier Cicchelli on 21/03/2024. +// Copyright © 2024 Röck+Cöde. All rights reserved. +// + +struct TopWord: Identifiable { + + // MARK: Constants + let id: String + let term: String + let count: Int + +} diff --git a/Reviews.xcodeproj/project.pbxproj b/Reviews.xcodeproj/project.pbxproj index f674b36..d9358a5 100644 --- a/Reviews.xcodeproj/project.pbxproj +++ b/Reviews.xcodeproj/project.pbxproj @@ -24,6 +24,8 @@ 02DC7FB52BA52520000EEEBE /* ReviewsKit in Frameworks */ = {isa = PBXBuildFile; productRef = 02DC7FB42BA52520000EEEBE /* ReviewsKit */; }; 02EACF2E2BABA34600FF8ECD /* FeedItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EACF2D2BABA34600FF8ECD /* FeedItemCell.swift */; }; 02EACF302BABA50D00FF8ECD /* TopWordsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EACF2F2BABA50D00FF8ECD /* TopWordsCell.swift */; }; + 02EACF342BABB28900FF8ECD /* TopWord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EACF332BABB28900FF8ECD /* TopWord.swift */; }; + 02EACF362BABB2F200FF8ECD /* TopWord+DTOs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EACF352BABB2F200FF8ECD /* TopWord+DTOs.swift */; }; 345AD11C24C6EDD9004E2EE1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345AD11B24C6EDD9004E2EE1 /* AppDelegate.swift */; }; 345AD12524C6EDDC004E2EE1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 345AD12424C6EDDC004E2EE1 /* Assets.xcassets */; }; 345AD12824C6EDDC004E2EE1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 345AD12624C6EDDC004E2EE1 /* LaunchScreen.storyboard */; }; @@ -66,6 +68,8 @@ 02DC7FB12BA52084000EEEBE /* Libraries */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Libraries; sourceTree = ""; }; 02EACF2D2BABA34600FF8ECD /* FeedItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItemCell.swift; sourceTree = ""; }; 02EACF2F2BABA50D00FF8ECD /* TopWordsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopWordsCell.swift; sourceTree = ""; }; + 02EACF332BABB28900FF8ECD /* TopWord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopWord.swift; sourceTree = ""; }; + 02EACF352BABB2F200FF8ECD /* TopWord+DTOs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TopWord+DTOs.swift"; sourceTree = ""; }; 345AD11824C6EDD9004E2EE1 /* Reviews.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Reviews.app; sourceTree = BUILT_PRODUCTS_DIR; }; 345AD11B24C6EDD9004E2EE1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 345AD12424C6EDDC004E2EE1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -103,6 +107,7 @@ 02909E7A2BAB6D2E00710E14 /* Bundle+Constants.swift */, 023AC7FB2BAA3EC10027D064 /* Int+Constants.swift */, 02909E7C2BAB7FFE00710E14 /* Review+DTOs.swift */, + 02EACF352BABB2F200FF8ECD /* TopWord+DTOs.swift */, ); path = Extensions; sourceTree = ""; @@ -157,6 +162,7 @@ isa = PBXGroup; children = ( 345AD13124C6EE64004E2EE1 /* Review.swift */, + 02EACF332BABB28900FF8ECD /* TopWord.swift */, ); path = Models; sourceTree = ""; @@ -429,12 +435,14 @@ files = ( 02EACF302BABA50D00FF8ECD /* TopWordsCell.swift in Sources */, 02620B8C2BA89C9A00DE7137 /* FeedListViewModel.swift in Sources */, + 02EACF342BABB28900FF8ECD /* TopWord.swift in Sources */, 023AC7FC2BAA3EC10027D064 /* Int+Constants.swift in Sources */, 02DC7FAC2BA51B4C000EEEBE /* FeedItemViewController.swift in Sources */, 02909E7B2BAB6D2E00710E14 /* Bundle+Constants.swift in Sources */, 02EACF2E2BABA34600FF8ECD /* FeedItemCell.swift in Sources */, 02909E7D2BAB7FFE00710E14 /* Review+DTOs.swift in Sources */, 0220ADA32BA90646001E6A9F /* FeedItemView.swift in Sources */, + 02EACF362BABB2F200FF8ECD /* TopWord+DTOs.swift in Sources */, 02DC7FAF2BA51B4C000EEEBE /* Review.swift in Sources */, 02DC7FAE2BA51B4C000EEEBE /* FeedListViewController.swift in Sources */, 02909E792BAB6B0200710E14 /* FilterOption.swift in Sources */, -- 2.47.1 From 235b8eeba54b236821d2314748c6cbe9aa20e42c Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 21 Mar 2024 02:02:28 +0100 Subject: [PATCH 11/12] Implemented the TopWordsView view in the Feed framework. --- .../Sources/UI/Views/TopWordsView.swift | 82 +++++++++++++++++++ Reviews.xcodeproj/project.pbxproj | 4 + 2 files changed, 86 insertions(+) create mode 100644 Frameworks/Feed/Bundle/Sources/UI/Views/TopWordsView.swift diff --git a/Frameworks/Feed/Bundle/Sources/UI/Views/TopWordsView.swift b/Frameworks/Feed/Bundle/Sources/UI/Views/TopWordsView.swift new file mode 100644 index 0000000..95f9855 --- /dev/null +++ b/Frameworks/Feed/Bundle/Sources/UI/Views/TopWordsView.swift @@ -0,0 +1,82 @@ +// +// TopWordsView.swift +// ReviewsFeed +// +// Created by Javier Cicchelli on 21/03/2024. +// Copyright © 2024 Röck+Cöde. All rights reserved. +// + +import SwiftUI + +struct TopWordsView: View { + + // MARK: Constants + private let topWords: [TopWord] + + // MARK: Initialisers + init(_ topWords: [TopWord]) { + self.topWords = topWords + } + + // MARK: Body + var body: some View { + HStack { + Spacer() + + HStack(spacing: 12) { + ForEach(topWords) { topWord in + Item( + term: topWord.term, + count: String(topWord.count) + ) + } + } + + Spacer() + } + + } + +} + +// MARK: - Structs +private extension TopWordsView { + struct Item: View { + + // MARK: Constants + let term: String + let count: String + + // MARK: Body + var body: some View { + HStack( + alignment: .center, + spacing: 6 + ) { + Text(count) + .font(.footnote) + .foregroundColor(.primary.opacity(0.75)) + + Text(term) + .lineLimit(1) + .font(.body.bold()) + .foregroundColor(.primary) + } + .padding(.horizontal, 10) + .padding(.vertical, 6) + .background(Color.secondary.opacity(0.75)) + .cornerRadius(8) + } + + } +} + +// MARK: - Previews +#Preview { + TopWordsView([ + .init(id: "1", term: "Something", count: 3), + .init(id: "2", term: "Something", count: 2), + .init(id: "3", term: "Something", count: 1), + ]) + .padding(.horizontal) +} diff --git a/Reviews.xcodeproj/project.pbxproj b/Reviews.xcodeproj/project.pbxproj index d9358a5..f6bb822 100644 --- a/Reviews.xcodeproj/project.pbxproj +++ b/Reviews.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 02DC7FB52BA52520000EEEBE /* ReviewsKit in Frameworks */ = {isa = PBXBuildFile; productRef = 02DC7FB42BA52520000EEEBE /* ReviewsKit */; }; 02EACF2E2BABA34600FF8ECD /* FeedItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EACF2D2BABA34600FF8ECD /* FeedItemCell.swift */; }; 02EACF302BABA50D00FF8ECD /* TopWordsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EACF2F2BABA50D00FF8ECD /* TopWordsCell.swift */; }; + 02EACF322BABB23A00FF8ECD /* TopWordsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EACF312BABB23A00FF8ECD /* TopWordsView.swift */; }; 02EACF342BABB28900FF8ECD /* TopWord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EACF332BABB28900FF8ECD /* TopWord.swift */; }; 02EACF362BABB2F200FF8ECD /* TopWord+DTOs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EACF352BABB2F200FF8ECD /* TopWord+DTOs.swift */; }; 345AD11C24C6EDD9004E2EE1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345AD11B24C6EDD9004E2EE1 /* AppDelegate.swift */; }; @@ -68,6 +69,7 @@ 02DC7FB12BA52084000EEEBE /* Libraries */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Libraries; sourceTree = ""; }; 02EACF2D2BABA34600FF8ECD /* FeedItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItemCell.swift; sourceTree = ""; }; 02EACF2F2BABA50D00FF8ECD /* TopWordsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopWordsCell.swift; sourceTree = ""; }; + 02EACF312BABB23A00FF8ECD /* TopWordsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopWordsView.swift; sourceTree = ""; }; 02EACF332BABB28900FF8ECD /* TopWord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopWord.swift; sourceTree = ""; }; 02EACF352BABB2F200FF8ECD /* TopWord+DTOs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TopWord+DTOs.swift"; sourceTree = ""; }; 345AD11824C6EDD9004E2EE1 /* Reviews.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Reviews.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -154,6 +156,7 @@ isa = PBXGroup; children = ( 0220ADA22BA90646001E6A9F /* FeedItemView.swift */, + 02EACF312BABB23A00FF8ECD /* TopWordsView.swift */, ); path = Views; sourceTree = ""; @@ -445,6 +448,7 @@ 02EACF362BABB2F200FF8ECD /* TopWord+DTOs.swift in Sources */, 02DC7FAF2BA51B4C000EEEBE /* Review.swift in Sources */, 02DC7FAE2BA51B4C000EEEBE /* FeedListViewController.swift in Sources */, + 02EACF322BABB23A00FF8ECD /* TopWordsView.swift in Sources */, 02909E792BAB6B0200710E14 /* FilterOption.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; -- 2.47.1 From 516fefd8700a2dbb927dffb945fa1332c209e980 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 21 Mar 2024 02:09:39 +0100 Subject: [PATCH 12/12] Integrated the TopWordsView view into the FeedListViewController view controller in the Feed framework. --- .../Logic/View Models/FeedListViewModel.swift | 28 +++++- .../FeedListViewController.swift | 85 +++++++++++++++---- 2 files changed, 93 insertions(+), 20 deletions(-) diff --git a/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedListViewModel.swift b/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedListViewModel.swift index 4ece55b..a384c3a 100644 --- a/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedListViewModel.swift +++ b/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedListViewModel.swift @@ -29,7 +29,7 @@ extension FeedListViewController { @Published var isLoading: Bool = false var items: [Review] = [] - var words: [WordCount] = [] + var words: [TopWord] = [] private var reviewsAll: [Review] = [] private var reviewsFiltered: FilteredReviews = [:] @@ -43,6 +43,18 @@ extension FeedListViewController { init(configuration: Configuration = .init()) { self.configuration = configuration } + + // MARK: Computed + var itemsCount: Int { + isWordsShowing + ? items.count + : items.count + 1 + } + + var isWordsShowing: Bool { + filter != .all + && !words.isEmpty + } // MARK: Functions func fetch() { @@ -66,7 +78,9 @@ extension FeedListViewController { reviews.map(\.comment) .compactMap { try? filterWords($0) } } - .mapValues { topWords($0) } + .mapValues { + topWords($0).map(TopWord.init) + } items = reviewsAll isFilterEnabled = !items.isEmpty @@ -89,6 +103,14 @@ extension FeedListViewController { filter = option } + + func item(for index: Int) -> Review? { + guard index < items.count else { return nil } + + return isWordsShowing + ? items[index - 1] + : items[index] + } } } @@ -98,6 +120,6 @@ private extension FeedListViewController.ViewModel { // MARK: Type aliases typealias FilteredReviews = [FilterOption: [Review]] - typealias TopWordsReviews = [FilterOption: [WordCount]] + typealias TopWordsReviews = [FilterOption: [TopWord]] } diff --git a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift index 26876e1..eb78d32 100644 --- a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift +++ b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift @@ -84,30 +84,18 @@ public class FeedListViewController: UITableViewController { _ tableView: UITableView, numberOfRowsInSection section: Int ) -> Int { - viewModel.items.count + viewModel.itemsCount } public override func tableView( _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell { - guard let cell = FeedItemCell.dequeue(from: tableView) else { - return .init() + if viewModel.isWordsShowing && indexPath.row == 0 { + makeTopWordsCell(tableView) + } else { + makeFeedItemCell(tableView, at: indexPath.row) } - - cell.contentConfiguration = { - if #available(iOS 16.0, *) { - UIHostingConfiguration { - FeedItemView(items[indexPath.row]) - } - } else { - HostingConfiguration { - FeedItemView(items[indexPath.row]) - } - } - }() - - return cell } // MARK: UITableViewDelegate @@ -159,6 +147,54 @@ private extension FeedListViewController { .store(in: &cancellables) } + func makeFeedItemCell( + _ tableView: UITableView, + at row: Int + ) -> UITableViewCell { + guard + let cell = FeedItemCell.dequeue(from: tableView), + let item = viewModel.item(for: row) + else { + return .init() + } + + cell.separatorInset = .init(width: tableView.bounds.size.width) + cell.contentConfiguration = { + if #available(iOS 16.0, *) { + UIHostingConfiguration { + FeedItemView(item) + } + } else { + HostingConfiguration { + FeedItemView(item) + } + } + }() + + return cell + } + + func makeTopWordsCell(_ tableView: UITableView) -> UITableViewCell { + guard let cell = TopWordsCell.dequeue(from: tableView) else { + return .init() + } + + cell.separatorInset = .init(width: tableView.bounds.size.width) + cell.contentConfiguration = { + if #available(iOS 16.0, *) { + UIHostingConfiguration { + TopWordsView(viewModel.words) + } + } else { + HostingConfiguration { + TopWordsView(viewModel.words) + } + } + }() + + return cell + } + func registerTableCells() { FeedItemCell.register(in: tableView) TopWordsCell.register(in: tableView) @@ -234,6 +270,21 @@ private extension String { } } +// MARK: - UIEdgeInsets+Inits +private extension UIEdgeInsets { + + // MARK: Initialisers + init(width: CGFloat) { + self = .init( + top: 0, + left: width, + bottom: 0, + right: 0 + ) + } + +} + // MARK: - Previews #if DEBUG import ReviewsiTunesKit -- 2.47.1