From 24600daefa65de8ecd09760b6ce32bf41acecf74 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 18 Mar 2024 16:59:35 +0100 Subject: [PATCH 01/15] Rearranged the folder structure for the Feed framework. --- .../Sources/{ => Logic/Models}/Review.swift | 0 .../{ => UI/Components}/ReviewCell.swift | 0 .../DetailsViewController.swift | 0 .../FeedViewController.swift | 0 Reviews.xcodeproj/project.pbxproj | 56 +++++++++++++++++-- 5 files changed, 52 insertions(+), 4 deletions(-) rename Frameworks/Feed/Bundle/Sources/{ => Logic/Models}/Review.swift (100%) rename Frameworks/Feed/Bundle/Sources/{ => UI/Components}/ReviewCell.swift (100%) rename Frameworks/Feed/Bundle/Sources/{ => UI/View Controllers}/DetailsViewController.swift (100%) rename Frameworks/Feed/Bundle/Sources/{ => UI/View Controllers}/FeedViewController.swift (100%) diff --git a/Frameworks/Feed/Bundle/Sources/Review.swift b/Frameworks/Feed/Bundle/Sources/Logic/Models/Review.swift similarity index 100% rename from Frameworks/Feed/Bundle/Sources/Review.swift rename to Frameworks/Feed/Bundle/Sources/Logic/Models/Review.swift diff --git a/Frameworks/Feed/Bundle/Sources/ReviewCell.swift b/Frameworks/Feed/Bundle/Sources/UI/Components/ReviewCell.swift similarity index 100% rename from Frameworks/Feed/Bundle/Sources/ReviewCell.swift rename to Frameworks/Feed/Bundle/Sources/UI/Components/ReviewCell.swift diff --git a/Frameworks/Feed/Bundle/Sources/DetailsViewController.swift b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/DetailsViewController.swift similarity index 100% rename from Frameworks/Feed/Bundle/Sources/DetailsViewController.swift rename to Frameworks/Feed/Bundle/Sources/UI/View Controllers/DetailsViewController.swift diff --git a/Frameworks/Feed/Bundle/Sources/FeedViewController.swift b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift similarity index 100% rename from Frameworks/Feed/Bundle/Sources/FeedViewController.swift rename to Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift diff --git a/Reviews.xcodeproj/project.pbxproj b/Reviews.xcodeproj/project.pbxproj index 6bbe025..927a495 100644 --- a/Reviews.xcodeproj/project.pbxproj +++ b/Reviews.xcodeproj/project.pbxproj @@ -81,6 +81,56 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 02620B852BA89BF900DE7137 /* UI */ = { + isa = PBXGroup; + children = ( + 02620B892BA89C2400DE7137 /* Components */, + 02620B882BA89C1000DE7137 /* View Controllers */, + ); + path = UI; + sourceTree = ""; + }; + 02620B862BA89C0000DE7137 /* Logic */ = { + isa = PBXGroup; + children = ( + 02620B8A2BA89C3300DE7137 /* Models */, + 02620B872BA89C0700DE7137 /* View Models */, + ); + path = Logic; + sourceTree = ""; + }; + 02620B872BA89C0700DE7137 /* View Models */ = { + isa = PBXGroup; + children = ( + ); + path = "View Models"; + sourceTree = ""; + }; + 02620B882BA89C1000DE7137 /* View Controllers */ = { + isa = PBXGroup; + children = ( + 345AD13224C6EE64004E2EE1 /* DetailsViewController.swift */, + 345AD12F24C6EE64004E2EE1 /* FeedViewController.swift */, + ); + path = "View Controllers"; + sourceTree = ""; + }; + 02620B892BA89C2400DE7137 /* Components */ = { + isa = PBXGroup; + children = ( + 345AD13024C6EE64004E2EE1 /* ReviewCell.swift */, + ); + path = Components; + sourceTree = ""; + }; + 02620B8A2BA89C3300DE7137 /* Models */ = { + isa = PBXGroup; + children = ( + 345AD13124C6EE64004E2EE1 /* Review.swift */, + ); + path = Models; + sourceTree = ""; + }; 02A6DA2F2BA591C000B943E2 /* Bundle */ = { isa = PBXGroup; children = ( @@ -159,10 +209,8 @@ 02DC7FB02BA51B4F000EEEBE /* Sources */ = { isa = PBXGroup; children = ( - 345AD13224C6EE64004E2EE1 /* DetailsViewController.swift */, - 345AD12F24C6EE64004E2EE1 /* FeedViewController.swift */, - 345AD13124C6EE64004E2EE1 /* Review.swift */, - 345AD13024C6EE64004E2EE1 /* ReviewCell.swift */, + 02620B862BA89C0000DE7137 /* Logic */, + 02620B852BA89BF900DE7137 /* UI */, ); path = Sources; sourceTree = ""; -- 2.47.1 From 22a48e18880afbdd056db265b4ef54d0ea6a33a4 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 18 Mar 2024 17:07:33 +0100 Subject: [PATCH 02/15] Created the FeedViewModel view model in the Feed framework and integrated it to the FeedViewController view controller. --- .../Bundle/Sources/Logic/Models/Review.swift | 2 +- .../Logic/View Models/FeedViewModel.swift | 15 +++++++++++++ .../Sources/UI/Components/ReviewCell.swift | 2 +- .../DetailsViewController.swift | 2 +- .../View Controllers/FeedViewController.swift | 21 +++++++++++++++---- Reviews.xcodeproj/project.pbxproj | 4 ++++ 6 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedViewModel.swift diff --git a/Frameworks/Feed/Bundle/Sources/Logic/Models/Review.swift b/Frameworks/Feed/Bundle/Sources/Logic/Models/Review.swift index d83086c..5793335 100644 --- a/Frameworks/Feed/Bundle/Sources/Logic/Models/Review.swift +++ b/Frameworks/Feed/Bundle/Sources/Logic/Models/Review.swift @@ -1,6 +1,6 @@ // // AppDelegate.swift -// AppStoreReviews +// ReviewsFeed // // Created by Dmitrii Ivanov on 21/07/2020. // Copyright © 2020 ING. All rights reserved. diff --git a/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedViewModel.swift b/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedViewModel.swift new file mode 100644 index 0000000..a5a92c9 --- /dev/null +++ b/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedViewModel.swift @@ -0,0 +1,15 @@ +// +// FeedViewModel.swift +// ReviewsFeed +// +// Created by Javier Cicchelli on 18/03/2024. +// Copyright © 2024 Röck+Cöde. All rights reserved. +// + +import Foundation + +extension FeedViewController { + final class ViewModel: ObservableObject { + + } +} diff --git a/Frameworks/Feed/Bundle/Sources/UI/Components/ReviewCell.swift b/Frameworks/Feed/Bundle/Sources/UI/Components/ReviewCell.swift index 3fbaa1f..1d60847 100644 --- a/Frameworks/Feed/Bundle/Sources/UI/Components/ReviewCell.swift +++ b/Frameworks/Feed/Bundle/Sources/UI/Components/ReviewCell.swift @@ -1,6 +1,6 @@ // // AppDelegate.swift -// AppStoreReviews +// ReviewsFeed // // Created by Dmitrii Ivanov on 21/07/2020. // Copyright © 2020 ING. All rights reserved. diff --git a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/DetailsViewController.swift b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/DetailsViewController.swift index 800a7d1..ffe65dc 100644 --- a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/DetailsViewController.swift +++ b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/DetailsViewController.swift @@ -1,6 +1,6 @@ // // AppDelegate.swift -// AppStoreReviews +// ReviewsFeed // // Created by Dmitrii Ivanov on 21/07/2020. // Copyright © 2020 ING. All rights reserved. diff --git a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift index 96db814..f780f01 100644 --- a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift +++ b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift @@ -1,6 +1,6 @@ // // AppDelegate.swift -// AppStoreReviews +// ReviewsFeed // // Created by Dmitrii Ivanov on 21/07/2020. // Copyright © 2020 ING. All rights reserved. @@ -10,6 +10,10 @@ import UIKit public class FeedViewController: UITableViewController { + // MARK: Constants + private let viewModel: ViewModel = .init() + + // MARK: Initialisers public init() { super.init(style: .plain) } @@ -18,28 +22,37 @@ public class FeedViewController: UITableViewController { fatalError("init(coder:) has not been implemented") } + // MARK: UIViewController public override func viewDidLoad() { super.viewDidLoad() tableView.register(ReviewCell.self, forCellReuseIdentifier: "cellId") tableView.rowHeight = 160 } - + + // MARK: UITableViewDataSource public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 100 } - + public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let c = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! ReviewCell c.update(item: randomReview()) return c } + // MARK: UITableViewDelegate public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) let vc = DetailsViewController(review: randomReview()) navigationController!.pushViewController(vc, animated: true) } +} + +// MARK: - Helpers +private extension FeedViewController { + + // MARK: Functions func randomReview() -> Review { let author = ["Dan Auerbach", "Bo Diddley", "Otis Rush", "Jimi Hendrix", "Albert King", "Buddy Guy", "Muddy Waters", "Eric Clapton"].randomElement()! let version = ["3.11", "3.12"].randomElement()! @@ -54,5 +67,5 @@ public class FeedViewController: UITableViewController { id: id, content: content) } + } - diff --git a/Reviews.xcodeproj/project.pbxproj b/Reviews.xcodeproj/project.pbxproj index 927a495..0e46e54 100644 --- a/Reviews.xcodeproj/project.pbxproj +++ b/Reviews.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 02620B8C2BA89C9A00DE7137 /* FeedViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02620B8B2BA89C9A00DE7137 /* FeedViewModel.swift */; }; 02DC7F9F2BA51793000EEEBE /* ReviewsFeed.h in Headers */ = {isa = PBXBuildFile; fileRef = 02DC7F912BA51793000EEEBE /* ReviewsFeed.h */; settings = {ATTRIBUTES = (Public, ); }; }; 02DC7FA22BA51793000EEEBE /* ReviewsFeed.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02DC7F8F2BA51793000EEEBE /* ReviewsFeed.framework */; }; 02DC7FA32BA51793000EEEBE /* ReviewsFeed.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 02DC7F8F2BA51793000EEEBE /* ReviewsFeed.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -46,6 +47,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 02620B8B2BA89C9A00DE7137 /* FeedViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedViewModel.swift; sourceTree = ""; }; 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 = ""; }; @@ -102,6 +104,7 @@ 02620B872BA89C0700DE7137 /* View Models */ = { isa = PBXGroup; children = ( + 02620B8B2BA89C9A00DE7137 /* FeedViewModel.swift */, ); path = "View Models"; sourceTree = ""; @@ -362,6 +365,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 02620B8C2BA89C9A00DE7137 /* FeedViewModel.swift in Sources */, 02DC7FAC2BA51B4C000EEEBE /* DetailsViewController.swift in Sources */, 02DC7FAF2BA51B4C000EEEBE /* Review.swift in Sources */, 02DC7FAE2BA51B4C000EEEBE /* FeedViewController.swift in Sources */, -- 2.47.1 From 908ca1d4c94824f6a956f05f4a01c8206ee2433e Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 18 Mar 2024 23:07:01 +0100 Subject: [PATCH 03/15] Updated the Review model in the Feed framework to include the Rating struct. --- .../Bundle/Sources/Logic/Models/Review.swift | 30 +++++++++++++------ .../Sources/UI/Components/ReviewCell.swift | 2 +- .../DetailsViewController.swift | 2 +- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/Frameworks/Feed/Bundle/Sources/Logic/Models/Review.swift b/Frameworks/Feed/Bundle/Sources/Logic/Models/Review.swift index 5793335..aa88fd0 100644 --- a/Frameworks/Feed/Bundle/Sources/Logic/Models/Review.swift +++ b/Frameworks/Feed/Bundle/Sources/Logic/Models/Review.swift @@ -1,5 +1,5 @@ // -// AppDelegate.swift +// Review.swift // ReviewsFeed // // Created by Dmitrii Ivanov on 21/07/2020. @@ -9,18 +9,30 @@ import Foundation struct Review { - let author: String - let version: String - let rating: Int - let title: String - let id: String - let content: String + // MARK: Constants + let author: String + let comment: String + let id: Int + let rating: Rating + let title: String + func ratingVersionText() -> String { var stars = "" - for _ in 0.. Date: Mon, 18 Mar 2024 23:22:46 +0100 Subject: [PATCH 04/15] Implemented the FeedViewModel view model in the Feed framework. --- .../Logic/View Models/FeedViewModel.swift | 54 +++++++++++++++++++ .../ServiceConfiguration+Inits.swift | 2 +- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedViewModel.swift b/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedViewModel.swift index a5a92c9..624bf03 100644 --- a/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedViewModel.swift +++ b/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedViewModel.swift @@ -7,9 +7,63 @@ // import Foundation +import ReviewsiTunesKit extension FeedViewController { final class ViewModel: ObservableObject { + // MARK: Constants + private let iTunesService: iTunesService + + // MARK: Properties + @Published var loading: Bool = false + + var items: [Review] = [] + + // MARK: Initialisers + init(_ configuration: Configuration = .init()) { + self.iTunesService = .init(configuration: .init( + session: configuration.session + )) + } + + // MARK: Functions + func fetch() { + Task { + loading = true + + do { + let output = try await iTunesService.getReviews(.init( + appID: "474495017", + countryCode: "nl" + )) + + items = output.reviews + .map { review -> Review in + .init( + author: review.author, + comment: review.content, + id: review.id, + rating: .init( + stars: review.rating, + appVersion: review.version + ), + title: review.title + ) + } + } catch { + // TODO: handle this error gracefully. + } + + loading = false + } + } + } +} + +// MARK: - Structs +extension FeedViewController.ViewModel { + struct Configuration { + let session: URLSessionConfiguration = .ephemeral } } diff --git a/Libraries/iTunes/Kit/Extensions/ServiceConfiguration+Inits.swift b/Libraries/iTunes/Kit/Extensions/ServiceConfiguration+Inits.swift index f156e71..2962ba9 100644 --- a/Libraries/iTunes/Kit/Extensions/ServiceConfiguration+Inits.swift +++ b/Libraries/iTunes/Kit/Extensions/ServiceConfiguration+Inits.swift @@ -13,7 +13,7 @@ import ReviewsFeedKit extension ServiceConfiguration { // MARK: Initialisers - init(session: URLSessionConfiguration = .ephemeral) { + public init(session: URLSessionConfiguration = .ephemeral) { self.init( host: .iTunes, session: session, -- 2.47.1 From c0c05b1faa370c52b1475fa9646a2a923a9b4904 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 18 Mar 2024 23:23:24 +0100 Subject: [PATCH 05/15] Integrated the FeedViewModel view model into the FeedViewController view controller in the Feed framework. --- .../View Controllers/FeedViewController.swift | 80 +++++++++++++------ 1 file changed, 57 insertions(+), 23 deletions(-) diff --git a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift index f780f01..840275f 100644 --- a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift +++ b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift @@ -6,6 +6,7 @@ // Copyright © 2020 ING. All rights reserved. // +import Combine import UIKit public class FeedViewController: UITableViewController { @@ -13,6 +14,9 @@ public class FeedViewController: UITableViewController { // MARK: Constants private let viewModel: ViewModel = .init() + // MARK: Properties + private var cancellables: Set = [] + // MARK: Initialisers public init() { super.init(style: .plain) @@ -25,26 +29,54 @@ public class FeedViewController: UITableViewController { // MARK: UIViewController public override func viewDidLoad() { super.viewDidLoad() + tableView.register(ReviewCell.self, forCellReuseIdentifier: "cellId") tableView.rowHeight = 160 + + bindViewModel() + + viewModel.fetch() } // MARK: UITableViewDataSource - public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return 100 + public override func tableView( + _ tableView: UITableView, + numberOfRowsInSection section: Int + ) -> Int { + viewModel.items.count } - public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let c = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! ReviewCell - c.update(item: randomReview()) - return c + public override func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { + guard + let cell = tableView.dequeueReusableCell( + withIdentifier: "cellId", + for: indexPath + ) as? ReviewCell + else { + return .init() + } + + cell.update(item: viewModel.items[indexPath.row]) + + return cell } // MARK: UITableViewDelegate - public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - let vc = DetailsViewController(review: randomReview()) - navigationController!.pushViewController(vc, animated: true) + public override func tableView( + _ tableView: UITableView, + didSelectRowAt indexPath: IndexPath + ) { + let details = DetailsViewController(review: viewModel.items[indexPath.row]) + + tableView.deselectRow( + at: indexPath, + animated: true + ) + + navigationController?.pushViewController(details, animated: true) } } @@ -53,19 +85,21 @@ public class FeedViewController: UITableViewController { private extension FeedViewController { // MARK: Functions - func randomReview() -> Review { - let author = ["Dan Auerbach", "Bo Diddley", "Otis Rush", "Jimi Hendrix", "Albert King", "Buddy Guy", "Muddy Waters", "Eric Clapton"].randomElement()! - let version = ["3.11", "3.12"].randomElement()! - let rating = Int.random(in: 1...5) - let title = ["Awesome app", "Could be better", "Gimme my money back!!", "Lemme tell you a story..."].randomElement()! - let id = UUID().uuidString - let content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." - return Review(author: author, - version: version, - rating: rating, - title: title, - id: id, - content: content) + func bindViewModel() { + viewModel.$loading + .sink { loading in + print("LOADING: \(loading)") + } + .store(in: &cancellables) + + viewModel.$loading + .filter { $0 == false } + .dropFirst() + .receive(on: RunLoop.main) + .sink { [weak self] _ in + self?.tableView.reloadData() + } + .store(in: &cancellables) } } -- 2.47.1 From ae9d79ddcdaed250ad902b0669fa0996dcfc0be9 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 18 Mar 2024 23:24:23 +0100 Subject: [PATCH 06/15] Disabled the BUILD_LIBRARY_FOR_DISTRIBUTION configuration in the Feed framework to remove an Xcode warning. --- Reviews.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Reviews.xcodeproj/project.pbxproj b/Reviews.xcodeproj/project.pbxproj index 0e46e54..71b89fa 100644 --- a/Reviews.xcodeproj/project.pbxproj +++ b/Reviews.xcodeproj/project.pbxproj @@ -407,7 +407,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CODE_SIGN_IDENTITY = ""; @@ -456,7 +456,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CODE_SIGN_IDENTITY = ""; -- 2.47.1 From 644e8f863001780e95fafb44671528715ea96d32 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 19 Mar 2024 00:02:45 +0100 Subject: [PATCH 07/15] Created the UI library in the Libraries package. --- Libraries/Package.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Libraries/Package.swift b/Libraries/Package.swift index dcc0630..387f970 100644 --- a/Libraries/Package.swift +++ b/Libraries/Package.swift @@ -15,6 +15,7 @@ let package = Package( .Target.filter.kit, .Target.foundation.kit, .Target.iTunes.kit, + .Target.ui.kit, ] ), ], @@ -45,6 +46,13 @@ let package = Package( ], path: "iTunes/Kit" ), + .target( + name: .Target.ui.kit, + dependencies: [ + .byName(name: .Target.foundation.kit), + ], + path: "UI/Kit" + ), .testTarget( name: .Target.feed.test, dependencies: [ @@ -73,6 +81,13 @@ let package = Package( ], path: "iTunes/Test" ), + .testTarget( + name: .Target.ui.test, + dependencies: [ + .byName(name: .Target.ui.kit), + ], + path: "UI/Test" + ), ] ) @@ -91,6 +106,7 @@ private extension String { static let filter = "\(String.Product.name)Filter" static let foundation = "\(String.Product.name)Foundation" static let iTunes = "\(String.Product.name)iTunes" + static let ui = "\(String.Product.name)UI" } } -- 2.47.1 From b522cebea8739657805c1c60244dd304e30c37cf Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 19 Mar 2024 00:28:17 +0100 Subject: [PATCH 08/15] Implemented the HostingConfiguration component in the UI library to add support for UIHostingConfiguration below iOS16. --- .../Components/HostingConfiguration.swift | 100 ++++++++++++++++++ .../Sources/Extensions/UIView+Functions.swift | 24 +++++ 2 files changed, 124 insertions(+) create mode 100644 Libraries/UI/Kit/Sources/Components/HostingConfiguration.swift create mode 100644 Libraries/UI/Kit/Sources/Extensions/UIView+Functions.swift diff --git a/Libraries/UI/Kit/Sources/Components/HostingConfiguration.swift b/Libraries/UI/Kit/Sources/Components/HostingConfiguration.swift new file mode 100644 index 0000000..0f2550f --- /dev/null +++ b/Libraries/UI/Kit/Sources/Components/HostingConfiguration.swift @@ -0,0 +1,100 @@ +// +// HostingConfiguration.swift +// ReviewsUIKit +// +// Created by Javier Cicchelli on 18/03/2024. +// Copyright © 2024 Röck+Cöde. All rights reserved. +// + +import SwiftUI + +public struct HostingConfiguration: UIContentConfiguration { + + // MARK: Type aliases + public typealias ContentClosure = () -> Content + + // MARK: Constants + fileprivate let hostingController: UIHostingController + + // MARK: Initialisers + public init(@ViewBuilder content: ContentClosure) { + hostingController = UIHostingController(rootView: content()) + } + + // MARK: Functions + public func makeContentView() -> UIView & UIContentView { + ContentView(self) + } + + public func updated( + for state: UIConfigurationState + ) -> HostingConfiguration { + self + } + +} + +// MARK: - Classes +class ContentView: UIView, UIContentView { + + // MARK: Properties + var configuration: UIContentConfiguration { + didSet { + configure(configuration) + } + } + + // MARK: Initialisers + init(_ configuration: UIContentConfiguration) { + self.configuration = configuration + + super.init(frame: .zero) + } + + required init?(coder: NSCoder) { + // This view shouldn't be initialized this way so we crash + fatalError("init(coder:) has not been implemented") + } + +} + +// MARK: - Helpers +private extension ContentView { + + // MARK: Functions + func configure(_ configuration: UIContentConfiguration) { + guard + let configuration = configuration as? HostingConfiguration, + let parent = findNextViewController() + else { + return + } + + let hostingController = configuration.hostingController + + guard + let swiftUICellView = hostingController.view, + subviews.isEmpty + else { + hostingController.view.invalidateIntrinsicContentSize() + return + } + + hostingController.view.backgroundColor = .clear + + parent.addChild(hostingController) + addSubview(hostingController.view) + + hostingController.view.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + leadingAnchor.constraint(equalTo: swiftUICellView.leadingAnchor), + trailingAnchor.constraint(equalTo: swiftUICellView.trailingAnchor), + topAnchor.constraint(equalTo: swiftUICellView.topAnchor), + bottomAnchor.constraint(equalTo: swiftUICellView.bottomAnchor) + ]) + + hostingController.didMove(toParent: parent) + } + +} diff --git a/Libraries/UI/Kit/Sources/Extensions/UIView+Functions.swift b/Libraries/UI/Kit/Sources/Extensions/UIView+Functions.swift new file mode 100644 index 0000000..ead5fb8 --- /dev/null +++ b/Libraries/UI/Kit/Sources/Extensions/UIView+Functions.swift @@ -0,0 +1,24 @@ +// +// UIView+Functions.swift +// ReviewsUIKit +// +// Created by Javier Cicchelli on 19/03/2024. +// Copyright © 2024 Röck+Cöde VoF. All rights reserved. +// + +import UIKit + +extension UIView { + + // MARK: Functions + func findNextViewController() -> UIViewController? { + if let nextResponder = next as? UIViewController { + return nextResponder + } else if let nextResponder = next as? UIView { + return nextResponder.findNextViewController() + } + + return nil + } + +} -- 2.47.1 From 58ac11eaf74d29fbbd8fe4681a6e15d5250b116a Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 19 Mar 2024 01:06:09 +0100 Subject: [PATCH 09/15] Implemented the FullWidthModifier modifier in the UI library, and implemented the "fullWidth(alignment: )" function for its View+Modifiers extension. --- .../Sources/Extensions/View+Modifiers.swift | 18 ++++++++ .../Sources/Modifiers/FullWidthModifier.swift | 43 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 Libraries/UI/Kit/Sources/Extensions/View+Modifiers.swift create mode 100644 Libraries/UI/Kit/Sources/Modifiers/FullWidthModifier.swift diff --git a/Libraries/UI/Kit/Sources/Extensions/View+Modifiers.swift b/Libraries/UI/Kit/Sources/Extensions/View+Modifiers.swift new file mode 100644 index 0000000..a3cf82a --- /dev/null +++ b/Libraries/UI/Kit/Sources/Extensions/View+Modifiers.swift @@ -0,0 +1,18 @@ +// +// View+Modifiers.swift +// ReviewsUIKit +// +// Created by Javier Cicchelli on 19/03/2024. +// Copyright © 2024 Röck+Cöde VoF. All rights reserved. +// + +import SwiftUI + +extension View { + + // MARK: Functions + public func fullWidth(alignment: Alignment = .leading) -> some View { + modifier(FullWidthModifier(alignment: alignment)) + } + +} diff --git a/Libraries/UI/Kit/Sources/Modifiers/FullWidthModifier.swift b/Libraries/UI/Kit/Sources/Modifiers/FullWidthModifier.swift new file mode 100644 index 0000000..d834b47 --- /dev/null +++ b/Libraries/UI/Kit/Sources/Modifiers/FullWidthModifier.swift @@ -0,0 +1,43 @@ +// +// FullWidthModifier.swift +// ReviewsUIKit +// +// Created by Javier Cicchelli on 19/03/2024. +// Copyright © 2024 Röck+Cöde VoF. All rights reserved. +// + +import SwiftUI + +struct FullWidthModifier: ViewModifier { + + // MARK: Constants + private let alignment: Alignment + + // MARK: Initialisers + init(alignment: Alignment) { + self.alignment = alignment + } + + // MARK: Functions + func body(content: Content) -> some View { + content + .frame( + maxWidth: .infinity, + alignment: alignment + ) + } + +} + +// MARK: - Previews +#Preview { + Group { + Text("Hello, world!") + .modifier(FullWidthModifier(alignment: .leading)) + Text("Hello, world!") + .modifier(FullWidthModifier(alignment: .center)) + Text("Hello, world!") + .modifier(FullWidthModifier(alignment: .trailing)) + } + .padding(.horizontal) +} -- 2.47.1 From de79b45b16aea1bf674d35e374bb99853b204b9c Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 19 Mar 2024 01:53:06 +0100 Subject: [PATCH 10/15] Implemented the FeedItem component in the Feed framework. --- .../Sources/UI/Components/FeedItem.swift | 90 +++++++++++++++++++ Reviews.xcodeproj/project.pbxproj | 4 + 2 files changed, 94 insertions(+) create mode 100644 Frameworks/Feed/Bundle/Sources/UI/Components/FeedItem.swift diff --git a/Frameworks/Feed/Bundle/Sources/UI/Components/FeedItem.swift b/Frameworks/Feed/Bundle/Sources/UI/Components/FeedItem.swift new file mode 100644 index 0000000..a472ed0 --- /dev/null +++ b/Frameworks/Feed/Bundle/Sources/UI/Components/FeedItem.swift @@ -0,0 +1,90 @@ +// +// FeedItem.swift +// ReviewsFeed +// +// Created by Javier Cicchelli on 19/03/2024. +// Copyright © 2024 Röck+Cöde. All rights reserved. +// + +import SwiftUI + +struct FeedItem: View { + + // MARK: Constants + private let item: Review + + // MARK: Initialisers + init(_ item: Review) { + self.item = item + } + + // MARK: Body + var body: some View { + VStack( + alignment: .leading, + spacing: 16 + ) { + HStack( + alignment: .bottom, + spacing: 8 + ) { + Image(systemName: "person.crop.circle") + + Text(item.author) + .font(.subheadline) + } + + VStack(spacing: 8) { + Text(item.title) + .font(.headline) + .lineLimit(2) + .fullWidth() + + Text(item.comment) + .font(.body) + .lineLimit(4) + .fullWidth() + } + .multilineTextAlignment(.leading) + + HStack(alignment: .bottom) { + ForEach(1...5, id: \.self) { index in + if #available(iOS 15.0, *) { + Image(systemName: "star") + .symbolVariant(index <= item.rating.stars ? .fill : .none) + } else { + Image(systemName: index <= item.rating.stars ? "star.fill" : "star") + } + } + + Spacer() + + HStack( + alignment: .bottom, + spacing: 4 + ) { + Text(item.rating.appVersion) + + Image(systemName: "iphone.gen3.circle") + } + } + .font(.subheadline) + } + .padding(.vertical, 8) + } + +} + +// MARK: - Previews +#Preview("Feed Item") { + FeedItem(.init( + author: "Some author name here...", + comment: "Some review comment here...", + id: 0, + rating: .init( + stars: 1, + appVersion: "v1.2.3" + ), + title: "Some review title here..." + )) +} diff --git a/Reviews.xcodeproj/project.pbxproj b/Reviews.xcodeproj/project.pbxproj index 71b89fa..7ff6150 100644 --- a/Reviews.xcodeproj/project.pbxproj +++ b/Reviews.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 0220ADA32BA90646001E6A9F /* FeedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0220ADA22BA90646001E6A9F /* FeedItem.swift */; }; 02620B8C2BA89C9A00DE7137 /* FeedViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02620B8B2BA89C9A00DE7137 /* FeedViewModel.swift */; }; 02DC7F9F2BA51793000EEEBE /* ReviewsFeed.h in Headers */ = {isa = PBXBuildFile; fileRef = 02DC7F912BA51793000EEEBE /* ReviewsFeed.h */; settings = {ATTRIBUTES = (Public, ); }; }; 02DC7FA22BA51793000EEEBE /* ReviewsFeed.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02DC7F8F2BA51793000EEEBE /* ReviewsFeed.framework */; }; @@ -47,6 +48,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0220ADA22BA90646001E6A9F /* FeedItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItem.swift; sourceTree = ""; }; 02620B8B2BA89C9A00DE7137 /* FeedViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedViewModel.swift; sourceTree = ""; }; 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 = ""; }; @@ -122,6 +124,7 @@ isa = PBXGroup; children = ( 345AD13024C6EE64004E2EE1 /* ReviewCell.swift */, + 0220ADA22BA90646001E6A9F /* FeedItem.swift */, ); path = Components; sourceTree = ""; @@ -367,6 +370,7 @@ files = ( 02620B8C2BA89C9A00DE7137 /* FeedViewModel.swift in Sources */, 02DC7FAC2BA51B4C000EEEBE /* DetailsViewController.swift in Sources */, + 0220ADA32BA90646001E6A9F /* FeedItem.swift in Sources */, 02DC7FAF2BA51B4C000EEEBE /* Review.swift in Sources */, 02DC7FAE2BA51B4C000EEEBE /* FeedViewController.swift in Sources */, 02DC7FAD2BA51B4C000EEEBE /* ReviewCell.swift in Sources */, -- 2.47.1 From a4fa2210e14d132e30fc7bd8c3cdcfaddc440ae5 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 19 Mar 2024 01:59:18 +0100 Subject: [PATCH 11/15] Integrated the FeedItem component into the FeedViewController view controller in the Feed framework. --- .../View Controllers/FeedViewController.swift | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift index 840275f..d64aa4e 100644 --- a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift +++ b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift @@ -7,6 +7,8 @@ // import Combine +import ReviewsUIKit +import SwiftUI import UIKit public class FeedViewController: UITableViewController { @@ -30,9 +32,11 @@ public class FeedViewController: UITableViewController { public override func viewDidLoad() { super.viewDidLoad() - tableView.register(ReviewCell.self, forCellReuseIdentifier: "cellId") - tableView.rowHeight = 160 - + tableView.register( + UITableViewCell.self, + forCellReuseIdentifier: .Cell.feedItem + ) + bindViewModel() viewModel.fetch() @@ -50,16 +54,21 @@ public class FeedViewController: UITableViewController { _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell { - guard - let cell = tableView.dequeueReusableCell( - withIdentifier: "cellId", - for: indexPath - ) as? ReviewCell - else { + guard let cell = tableView.dequeueReusableCell(withIdentifier: .Cell.feedItem) else { return .init() } - cell.update(item: viewModel.items[indexPath.row]) + cell.contentConfiguration = { + if #available(iOS 16.0, *) { + UIHostingConfiguration { + FeedItem(viewModel.items[indexPath.row]) + } + } else { + HostingConfiguration { + FeedItem(viewModel.items[indexPath.row]) + } + } + }() return cell } @@ -93,8 +102,8 @@ private extension FeedViewController { .store(in: &cancellables) viewModel.$loading - .filter { $0 == false } .dropFirst() + .filter { $0 == false } .receive(on: RunLoop.main) .sink { [weak self] _ in self?.tableView.reloadData() @@ -103,3 +112,10 @@ private extension FeedViewController { } } + +// MARK: - String+Constants +private extension String { + enum Cell { + static let feedItem = "FeedItemCell" + } +} -- 2.47.1 From 36ea9659c631d8d91ed98ad67f427766b3d8a743 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 19 Mar 2024 02:00:30 +0100 Subject: [PATCH 12/15] Removed the unused ReviewCell table cell from the Feed framework. --- .../Bundle/Sources/Logic/Models/Review.swift | 9 +-- .../Sources/UI/Components/ReviewCell.swift | 66 ------------------- Reviews.xcodeproj/project.pbxproj | 4 -- 3 files changed, 1 insertion(+), 78 deletions(-) delete mode 100644 Frameworks/Feed/Bundle/Sources/UI/Components/ReviewCell.swift diff --git a/Frameworks/Feed/Bundle/Sources/Logic/Models/Review.swift b/Frameworks/Feed/Bundle/Sources/Logic/Models/Review.swift index aa88fd0..a315a88 100644 --- a/Frameworks/Feed/Bundle/Sources/Logic/Models/Review.swift +++ b/Frameworks/Feed/Bundle/Sources/Logic/Models/Review.swift @@ -16,14 +16,7 @@ struct Review { let id: Int let rating: Rating let title: String - - func ratingVersionText() -> String { - var stars = "" - for _ in 0.. Date: Tue, 19 Mar 2024 09:24:58 +0100 Subject: [PATCH 13/15] Changed the Feed model in the iTunes library to be public. --- Libraries/iTunes/Kit/Models/Feed.swift | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Libraries/iTunes/Kit/Models/Feed.swift b/Libraries/iTunes/Kit/Models/Feed.swift index 718004c..1c0236e 100644 --- a/Libraries/iTunes/Kit/Models/Feed.swift +++ b/Libraries/iTunes/Kit/Models/Feed.swift @@ -8,11 +8,16 @@ import ReviewsFeedKit -struct Feed { +public struct Feed { // MARK: Constants let entries: [Review] + // MARK: Initialisers + public init(entries: [Review]) { + self.entries = entries + } + } // MARK: - Decodable @@ -28,7 +33,7 @@ extension Feed: Decodable { } // MARK: Initialisers - init(from decoder: any Decoder) throws { + public init(from decoder: any Decoder) throws { let feed = try decoder.container(keyedBy: FeedKeys.self) let feedEntry = try feed.nestedContainer(keyedBy: EntryKeys.self, forKey: .feed) @@ -41,7 +46,7 @@ extension Feed: Decodable { extension Feed: Encodable { // MARK: Functions - func encode(to encoder: any Encoder) throws { + public func encode(to encoder: any Encoder) throws { var feed = encoder.container(keyedBy: FeedKeys.self) var feedEntry = feed.nestedContainer(keyedBy: EntryKeys.self, forKey: .feed) -- 2.47.1 From 5bd1de11e4d7def70c02778b9127c49484f257b7 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 19 Mar 2024 09:27:10 +0100 Subject: [PATCH 14/15] Moved the Configuration struct from the FeedViewModel view model to the FeedViewController view controller in the Feed framework. --- .../Logic/View Models/FeedViewModel.swift | 28 ++++++++--------- .../DetailsViewController.swift | 2 +- .../View Controllers/FeedViewController.swift | 31 +++++++++++++++++-- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedViewModel.swift b/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedViewModel.swift index 624bf03..8198e6d 100644 --- a/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedViewModel.swift +++ b/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedViewModel.swift @@ -12,21 +12,27 @@ import ReviewsiTunesKit extension FeedViewController { final class ViewModel: ObservableObject { - // MARK: Constants - private let iTunesService: iTunesService + // MARK: Type aliases + typealias Configuration = FeedViewController.Configuration + // MARK: Constants + private let configuration: Configuration + // MARK: Properties @Published var loading: Bool = false var items: [Review] = [] // MARK: Initialisers - init(_ configuration: Configuration = .init()) { - self.iTunesService = .init(configuration: .init( - session: configuration.session - )) + init(configuration: Configuration = .init()) { + self.configuration = configuration } + // MARK: Computed + lazy private var iTunesService: iTunesService = { + .init(configuration: .init(session: configuration.session)) + }() + // MARK: Functions func fetch() { Task { @@ -34,8 +40,8 @@ extension FeedViewController { do { let output = try await iTunesService.getReviews(.init( - appID: "474495017", - countryCode: "nl" + appID: configuration.appID, + countryCode: configuration.countryCode )) items = output.reviews @@ -58,12 +64,6 @@ extension FeedViewController { loading = false } } - } -} -// MARK: - Structs -extension FeedViewController.ViewModel { - struct Configuration { - let session: URLSessionConfiguration = .ephemeral } } diff --git a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/DetailsViewController.swift b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/DetailsViewController.swift index 526ed1f..b27f16d 100644 --- a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/DetailsViewController.swift +++ b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/DetailsViewController.swift @@ -43,7 +43,7 @@ class DetailsViewController: UIViewController { view.addSubview(titleLabel) view.addSubview(contentLabel) - ratingVersionLabel.text = review.ratingVersionText() + ratingVersionLabel.text = review.rating.appVersion ratingVersionLabel.font = UIFont.italicSystemFont(ofSize: 18) authorLabel.text = review.author diff --git a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift index d64aa4e..5f14cf1 100644 --- a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift +++ b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift @@ -12,15 +12,17 @@ import SwiftUI import UIKit public class FeedViewController: UITableViewController { - + // MARK: Constants - private let viewModel: ViewModel = .init() + private let viewModel: ViewModel // MARK: Properties private var cancellables: Set = [] // MARK: Initialisers - public init() { + public init(configuration: Configuration = .init()) { + self.viewModel = .init(configuration: configuration) + super.init(style: .plain) } @@ -113,6 +115,29 @@ private extension FeedViewController { } +// MARK: - Configuration +extension FeedViewController { + public struct Configuration { + + // MARK: Constants + let appID: String + let countryCode: String + let session: URLSessionConfiguration + + // MARK: Initialisers + public init( + appID: String = "474495017", + countryCode: String = "nl", + session: URLSessionConfiguration = .ephemeral + ) { + self.appID = appID + self.countryCode = countryCode + self.session = session + } + + } +} + // MARK: - String+Constants private extension String { enum Cell { -- 2.47.1 From 08acf467a5b540916f69a340c0beecda916d9912 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 19 Mar 2024 09:27:53 +0100 Subject: [PATCH 15/15] Implemented the previews for the FeedViewController view controller in the Feed framework. --- .../View Controllers/FeedViewController.swift | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift index 5f14cf1..dd91f38 100644 --- a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift +++ b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift @@ -144,3 +144,57 @@ private extension String { static let feedItem = "FeedItemCell" } } + +// MARK: - Previews +#if DEBUG +import ReviewsFoundationKit +import ReviewsiTunesKit + +@available(iOS 17.0, *) +#Preview("Feed View Controller with few reviews") { + MockURLProtocol.response = .init( + statusCode: 200, + object: Feed(entries: [ + .init( + id: 1, + author: "Some author name #1 here", + title: "Some review title #1 goes here...", + content: "Some long, explanatory review comment #1 goes here...", + rating: 3, + version: "v1.0.0", + updated: .init() + ), + .init( + id: 2, + author: "Some author name #2 here", + title: "Some review title #2 goes here...", + content: "Some long, explanatory review comment #2 goes here...", + rating: 5, + version: "v1.0.0", + updated: .init() + ), + .init( + id: 3, + author: "Some author name #3 here", + title: "Some review title #3 goes here...", + content: "Some long, explanatory review comment #3 goes here...", + rating: 1, + version: "v1.0.0", + updated: .init() + ), + ]) + ) + + return FeedViewController(configuration: .init(session: .mock)) +} + +@available(iOS 17.0, *) +#Preview("Feed View Controller with no reviews") { + MockURLProtocol.response = .init( + statusCode: 200, + object: Feed(entries: []) + ) + + return FeedViewController(configuration: .init(session: .mock)) +} +#endif -- 2.47.1