diff --git a/App/Sources/AppDelegate.swift b/App/Sources/AppDelegate.swift index 13338b5..5975da1 100644 --- a/App/Sources/AppDelegate.swift +++ b/App/Sources/AppDelegate.swift @@ -16,7 +16,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) - let viewController = FeedViewController() + let viewController = FeedListViewController() window?.rootViewController = UINavigationController(rootViewController: viewController) window?.makeKeyAndVisible() return true diff --git a/Frameworks/Feed/Bundle/Sources/Logic/Extensions/Int+Constants.swift b/Frameworks/Feed/Bundle/Sources/Logic/Extensions/Int+Constants.swift new file mode 100644 index 0000000..1c6f9b2 --- /dev/null +++ b/Frameworks/Feed/Bundle/Sources/Logic/Extensions/Int+Constants.swift @@ -0,0 +1,16 @@ +// +// Int+Constants.swift +// ReviewsFeed +// +// Created by Javier Cicchelli on 19/03/2024. +// Copyright © 2024 Röck+Cöde. All rights reserved. +// + +extension Int { + + // MARK: Constants + enum Rating { + static let total: Int = 5 + } + +} diff --git a/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedViewModel.swift b/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedViewModel.swift index 8198e6d..6a0cf7d 100644 --- a/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedViewModel.swift +++ b/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedViewModel.swift @@ -9,11 +9,11 @@ import Foundation import ReviewsiTunesKit -extension FeedViewController { +extension FeedListViewController { final class ViewModel: ObservableObject { // MARK: Type aliases - typealias Configuration = FeedViewController.Configuration + typealias Configuration = FeedListViewController.Configuration // MARK: Constants private let configuration: Configuration diff --git a/Frameworks/Feed/Bundle/Sources/UI/Components/FeedItem.swift b/Frameworks/Feed/Bundle/Sources/UI/Components/Cells/FeedItemCell.swift similarity index 53% rename from Frameworks/Feed/Bundle/Sources/UI/Components/FeedItem.swift rename to Frameworks/Feed/Bundle/Sources/UI/Components/Cells/FeedItemCell.swift index a472ed0..4b5cef9 100644 --- a/Frameworks/Feed/Bundle/Sources/UI/Components/FeedItem.swift +++ b/Frameworks/Feed/Bundle/Sources/UI/Components/Cells/FeedItemCell.swift @@ -1,14 +1,15 @@ // -// FeedItem.swift +// FeedItemCell.swift // ReviewsFeed // // Created by Javier Cicchelli on 19/03/2024. // Copyright © 2024 Röck+Cöde. All rights reserved. // +import ReviewsUIKit import SwiftUI -struct FeedItem: View { +struct FeedItemCell: View { // MARK: Constants private let item: Review @@ -24,15 +25,11 @@ struct FeedItem: View { alignment: .leading, spacing: 16 ) { - HStack( - alignment: .bottom, - spacing: 8 - ) { - Image(systemName: "person.crop.circle") - - Text(item.author) - .font(.subheadline) - } + FakeLabel( + systemIcon: .Icon.person, + title: item.author + ) + .foregroundColor(.secondary) VStack(spacing: 8) { Text(item.title) @@ -47,28 +44,21 @@ struct FeedItem: View { } .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: .center, + spacing: 32 + ) { + StarRating( + item.rating.stars, + of: .Rating.total + ) - HStack( - alignment: .bottom, - spacing: 4 - ) { - Text(item.rating.appVersion) - - Image(systemName: "iphone.gen3.circle") - } + FakeLabel( + systemIcon: .Icon.info, + title: item.rating.appVersion + ) + .foregroundColor(.secondary) } - .font(.subheadline) } .padding(.vertical, 8) } @@ -76,8 +66,8 @@ struct FeedItem: View { } // MARK: - Previews -#Preview("Feed Item") { - FeedItem(.init( +#Preview("Feed Item Cell") { + FeedItemCell(.init( author: "Some author name here...", comment: "Some review comment here...", id: 0, @@ -87,4 +77,5 @@ struct FeedItem: View { ), title: "Some review title here..." )) + .padding(.horizontal) } diff --git a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/DetailsViewController.swift b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/DetailsViewController.swift deleted file mode 100644 index b27f16d..0000000 --- a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/DetailsViewController.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// AppDelegate.swift -// ReviewsFeed -// -// Created by Dmitrii Ivanov on 21/07/2020. -// Copyright © 2020 ING. All rights reserved. -// - -import UIKit - -class DetailsViewController: UIViewController { - - private let review: Review - - private var titleLabel = UILabel() - private var authorLabel = UILabel() - private var contentLabel = UILabel() - private var ratingVersionLabel = UILabel() - - init(review: Review) { - self.review = review - super.init(nibName: nil, bundle: nil) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - view.backgroundColor = UIColor.white - setupViews() - } - - func setupViews() { - titleLabel.translatesAutoresizingMaskIntoConstraints = false - authorLabel.translatesAutoresizingMaskIntoConstraints = false - contentLabel.translatesAutoresizingMaskIntoConstraints = false - ratingVersionLabel.translatesAutoresizingMaskIntoConstraints = false - - view.addSubview(ratingVersionLabel) - view.addSubview(authorLabel) - view.addSubview(titleLabel) - view.addSubview(contentLabel) - - ratingVersionLabel.text = review.rating.appVersion - ratingVersionLabel.font = UIFont.italicSystemFont(ofSize: 18) - - authorLabel.text = review.author - authorLabel.font = UIFont.systemFont(ofSize: 18) - - titleLabel.text = review.title - titleLabel.numberOfLines = 0 - titleLabel.font = UIFont.boldSystemFont(ofSize: 22) - - contentLabel.text = review.comment - contentLabel.numberOfLines = 0 - - ratingVersionLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8).isActive = true - ratingVersionLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8).isActive = true - ratingVersionLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8).isActive = true - ratingVersionLabel.heightAnchor.constraint(equalToConstant: 24).isActive = true - - authorLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8).isActive = true - authorLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8).isActive = true - authorLabel.topAnchor.constraint(equalTo: ratingVersionLabel.bottomAnchor, constant: 8).isActive = true - authorLabel.heightAnchor.constraint(equalToConstant: 24).isActive = true - - titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8).isActive = true - titleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8).isActive = true - titleLabel.topAnchor.constraint(equalTo: authorLabel.bottomAnchor, constant: 8).isActive = true - titleLabel.heightAnchor.constraint(lessThanOrEqualToConstant: 72).isActive = true - - contentLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8).isActive = true - contentLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8).isActive = true - contentLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8).isActive = true - contentLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 24).isActive = true - } -} diff --git a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedItemViewController.swift b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedItemViewController.swift new file mode 100644 index 0000000..791b869 --- /dev/null +++ b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedItemViewController.swift @@ -0,0 +1,225 @@ +// +// FeedItemViewController.swift +// ReviewsFeed +// +// Created by Dmitrii Ivanov on 21/07/2020. +// Copyright © 2020 ING. All rights reserved. +// + +import ReviewsUIKit +import SwiftUI +import UIKit + +final class FeedItemViewController: UIViewController { + + // MARK: Constants + private let item: Review + + // MARK: Properties + private lazy var appVersionController = { + UIHostingController(rootView: FakeLabel( + systemIcon: .Icon.info, + title: item.rating.appVersion + )) + }() + + private lazy var authorController = { + UIHostingController(rootView: FakeLabel( + systemIcon: .Icon.person, + title: item.author + )) + }() + + private lazy var starRatingController = { + UIHostingController(rootView: StarRating( + item.rating.stars, + of: .Rating.total + )) + }() + + // MARK: Outlets + private lazy var appVersionView = { + guard let view = appVersionController.view else { + fatalError("The StarRating component must be initialised") + } + + view.translatesAutoresizingMaskIntoConstraints = false + + return view + }() + + private lazy var authorView = { + guard let view = authorController.view else { + fatalError("The StarRating component must be initialised") + } + + view.translatesAutoresizingMaskIntoConstraints = false + + return view + }() + + private lazy var commentLabel = { + let label = UILabel() + + label.font = UIFont.preferredFont(forTextStyle: .body) + label.numberOfLines = 0 + label.translatesAutoresizingMaskIntoConstraints = false + label.text = item.comment + + return label + }() + + private lazy var ratingView = { + let stack = UIStackView() + + stack.axis = .horizontal + stack.backgroundColor = .clear + stack.distribution = .fillProportionally + stack.translatesAutoresizingMaskIntoConstraints = false + + stack.addArrangedSubview(starRatingView) + stack.addArrangedSubview(appVersionView) + + return stack + }() + + private lazy var scrollView = { + let scroll = UIScrollView() + + scroll.backgroundColor = .clear + scroll.showsVerticalScrollIndicator = true + scroll.translatesAutoresizingMaskIntoConstraints = false + + return scroll + }() + + private lazy var stackView = { + let stack = UIStackView() + + stack.axis = .vertical + stack.alignment = .leading + stack.backgroundColor = .clear + stack.distribution = .fill + stack.spacing = 16 + stack.translatesAutoresizingMaskIntoConstraints = false + + return stack + }() + + private lazy var starRatingView = { + guard let view = starRatingController.view else { + fatalError("The StarRating component must be initialised") + } + + view.translatesAutoresizingMaskIntoConstraints = false + + return view + }() + + private lazy var titleLabel = { + let label = UILabel() + + label.font = UIFont.preferredFont(forTextStyle: .headline) + label.numberOfLines = 0 + label.translatesAutoresizingMaskIntoConstraints = false + label.text = item.title + + return label + }() + + // MARK: Initialisers + init(_ item: Review) { + self.item = item + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: UIViewController + override func viewDidLoad() { + super.viewDidLoad() + + addChild(appVersionController) + addChild(authorController) + addChild(starRatingController) + + setView() + setNavigationBar() + setLayout() + + appVersionController.didMove(toParent: self) + authorController.didMove(toParent: self) + starRatingController.didMove(toParent: self) + } + +} + +// MARK: - Helpers +private extension FeedItemViewController { + + // MARK: Functions + func setLayout() { + let scrollContentGuide = scrollView.contentLayoutGuide + let scrollFrameGuide = scrollView.frameLayoutGuide + + NSLayoutConstraint.activate([ + scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + scrollView.topAnchor.constraint(equalTo: view.topAnchor), + scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + + stackView.bottomAnchor.constraint(equalTo: scrollContentGuide.bottomAnchor, constant: -16), + stackView.leadingAnchor.constraint(equalTo: scrollContentGuide.leadingAnchor), + stackView.topAnchor.constraint(equalTo: scrollContentGuide.topAnchor, constant: 8), + stackView.trailingAnchor.constraint(equalTo: scrollContentGuide.trailingAnchor), + stackView.leadingAnchor.constraint(equalTo: scrollFrameGuide.leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: scrollFrameGuide.trailingAnchor), + + authorView.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 16), + + ratingView.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 16), + + titleLabel.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 16), + titleLabel.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: -16), + + commentLabel.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 16), + commentLabel.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: -16), + ]) + } + + func setNavigationBar() { + navigationController?.navigationBar.prefersLargeTitles = true + navigationController?.navigationBar.isTranslucent = true + + navigationItem.title = "# \(String(item.id))" + } + + func setView() { + view.backgroundColor = .systemBackground + + view.addSubview(scrollView) + + scrollView.addSubview(stackView) + + stackView.addArrangedSubview(authorView) + stackView.addArrangedSubview(ratingView) + stackView.addArrangedSubview(titleLabel) + stackView.addArrangedSubview(commentLabel) + } + +} + +// MARK: - Previews +@available(iOS 17.0, *) +#Preview("Feed Item with a review") { + UINavigationController(rootViewController: FeedItemViewController(.init( + author: "Some author name here...", + comment: "Some long, explanatory review comment goes here...", + id: 1, + rating: .init(stars: 3, appVersion: "v1.0.0"), + title: "Some review title goes here..." + ))) +} diff --git a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift similarity index 66% rename from Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift rename to Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift index dd91f38..09ee7bd 100644 --- a/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedViewController.swift +++ b/Frameworks/Feed/Bundle/Sources/UI/View Controllers/FeedListViewController.swift @@ -1,5 +1,5 @@ // -// AppDelegate.swift +// FeedListViewController.swift // ReviewsFeed // // Created by Dmitrii Ivanov on 21/07/2020. @@ -11,7 +11,7 @@ import ReviewsUIKit import SwiftUI import UIKit -public class FeedViewController: UITableViewController { +public class FeedListViewController: UITableViewController { // MARK: Constants private let viewModel: ViewModel @@ -33,12 +33,9 @@ public class FeedViewController: UITableViewController { // MARK: UIViewController public override func viewDidLoad() { super.viewDidLoad() - - tableView.register( - UITableViewCell.self, - forCellReuseIdentifier: .Cell.feedItem - ) + setNavigationBar() + registerTableCells() bindViewModel() viewModel.fetch() @@ -63,11 +60,11 @@ public class FeedViewController: UITableViewController { cell.contentConfiguration = { if #available(iOS 16.0, *) { UIHostingConfiguration { - FeedItem(viewModel.items[indexPath.row]) + FeedItemCell(viewModel.items[indexPath.row]) } } else { HostingConfiguration { - FeedItem(viewModel.items[indexPath.row]) + FeedItemCell(viewModel.items[indexPath.row]) } } }() @@ -80,7 +77,7 @@ public class FeedViewController: UITableViewController { _ tableView: UITableView, didSelectRowAt indexPath: IndexPath ) { - let details = DetailsViewController(review: viewModel.items[indexPath.row]) + let details = FeedItemViewController(viewModel.items[indexPath.row]) tableView.deselectRow( at: indexPath, @@ -93,7 +90,7 @@ public class FeedViewController: UITableViewController { } // MARK: - Helpers -private extension FeedViewController { +private extension FeedListViewController { // MARK: Functions func bindViewModel() { @@ -113,10 +110,21 @@ private extension FeedViewController { .store(in: &cancellables) } + func registerTableCells() { + tableView.register(UITableViewCell.self, forCellReuseIdentifier: .Cell.feedItem) + } + + func setNavigationBar() { + navigationController?.navigationBar.prefersLargeTitles = true + navigationController?.navigationBar.isTranslucent = true + + navigationItem.title = "Latest reviews" + } + } // MARK: - Configuration -extension FeedViewController { +extension FeedListViewController { public struct Configuration { // MARK: Constants @@ -151,7 +159,16 @@ import ReviewsFoundationKit import ReviewsiTunesKit @available(iOS 17.0, *) -#Preview("Feed View Controller with few reviews") { +#Preview("Feed List loading reviews") { + MockURLProtocol.response = .init(statusCode: 200) + + return UINavigationController( + rootViewController: FeedListViewController(configuration: .init(session: .mock)) + ) +} + +@available(iOS 17.0, *) +#Preview("Feed List with few reviews") { MockURLProtocol.response = .init( statusCode: 200, object: Feed(entries: [ @@ -182,19 +199,46 @@ import ReviewsiTunesKit version: "v1.0.0", updated: .init() ), + .init( + id: 4, + author: "Some author name #4 here", + title: "Some review title #4 goes here...", + content: "Some long, explanatory review comment #4 goes here...", + rating: 4, + version: "v1.0.0", + updated: .init() + ), + .init( + id: 5, + author: "Some author name #5 here", + title: "Some review title #5 goes here...", + content: "Some long, explanatory review comment #5 goes here...", + rating: 2, + version: "v1.0.0", + updated: .init() + ), ]) ) - return FeedViewController(configuration: .init(session: .mock)) + return UINavigationController( + rootViewController: FeedListViewController(configuration: .init(session: .mock)) + ) } @available(iOS 17.0, *) -#Preview("Feed View Controller with no reviews") { +#Preview("Feed List with no reviews") { MockURLProtocol.response = .init( statusCode: 200, object: Feed(entries: []) ) - return FeedViewController(configuration: .init(session: .mock)) + return UINavigationController( + rootViewController: FeedListViewController(configuration: .init(session: .mock)) + ) +} + +@available(iOS 17.0, *) +#Preview("Feed List with live reviews") { + UINavigationController(rootViewController: FeedListViewController()) } #endif diff --git a/Libraries/UI/Kit/Sources/Components/FakeLabel.swift b/Libraries/UI/Kit/Sources/Components/FakeLabel.swift new file mode 100644 index 0000000..a365342 --- /dev/null +++ b/Libraries/UI/Kit/Sources/Components/FakeLabel.swift @@ -0,0 +1,95 @@ +// +// FakeLabel.swift +// ReviewsUIKit +// +// Created by Javier Cicchelli on 20/03/2024. +// Copyright © 2024 Röck+Cöde. All rights reserved. +// + +import ReviewsFoundationKit +import SwiftUI + +public struct FakeLabel: View { + + // MARK: Constants + private let systemIcon: String + private let title: String + + // MARK: Initialisers + public init( + systemIcon: String, + title: String + ) { + self.systemIcon = systemIcon + self.title = title + } + + // MARK: Body + public var body: some View { + HStack( + alignment: .bottom, + spacing: 8 + ) { + iconOrQuestionMark + + Text(titleOrDash) + .font(.body) + } + } + +} + +// MARK: - Helpers +private extension FakeLabel { + + // MARK: Computed + var iconOrQuestionMark: Image { + .init(systemName: systemIcon.isEmpty + ? .Icon.questionMark + : systemIcon + ) + } + + var titleOrDash: String { + title.isEmpty + ? .Title.none + : title + } + +} + +// MARK: - String+Constants +private extension String { + enum Title { + static let none = "-" + } +} + +// MARK: - Previews +#Preview("Fake Label with system icon and title") { + FakeLabel( + systemIcon: .Icon.person, + title: "Some title goes here..." + ) +} + +#Preview("Fake Label with empty system icon and title") { + FakeLabel( + systemIcon: .empty, + title: "Some title goes here..." + ) +} + +#Preview("Fake Label with system icon and empty title") { + FakeLabel( + systemIcon: .Icon.person, + title: .empty + ) +} + +#Preview("Fake Label with both empty system icon and title") { + FakeLabel( + systemIcon: .empty, + title: .empty + ) +} diff --git a/Libraries/UI/Kit/Sources/Components/StarRating.swift b/Libraries/UI/Kit/Sources/Components/StarRating.swift new file mode 100644 index 0000000..ec7f189 --- /dev/null +++ b/Libraries/UI/Kit/Sources/Components/StarRating.swift @@ -0,0 +1,81 @@ +// +// StarRating.swift +// ReviewsUIKit +// +// Created by Javier Cicchelli on 19/03/2024. +// Copyright © 2024 Röck+Cöde. All rights reserved. +// + +import SwiftUI + +public struct StarRating: View { + + // MARK: Constants + private let rating: Int + private let total: Int + + // MARK: Initialisers + public init( + _ rating: Int, + of total: Int + ) { + self.rating = rating + self.total = total + } + + // MARK: Body + public var body: some View { + HStack { + ForEach( + 1...total, + id: \.self + ) { index in + if #available(iOS 15.0, *) { + Image.Icon.star + .symbolVariant(symbolVariant(for: index)) + } else { + image(for: index) + } + } + } + } + +} + +// MARK: - Helpers +private extension StarRating { + + // MARK: Functions + func image(for index: Int) -> Image { + index <= rating + ? .Icon.starFill + : .Icon.star + } + + @available(iOS 15.0, *) + func symbolVariant(for index: Int) -> SymbolVariants { + index <= rating + ? .fill + : .none + } + +} + +// MARK: - Image+Constants +private extension Image { + enum Icon { + static let star: Image = .init(systemName: "star") + static let starFill: Image = .init(systemName: "star.fill") + } +} + +// MARK: - Previews +#Preview("Star Rating") { + VStack(spacing: 8) { + StarRating(1, of: 5) + StarRating(2, of: 5) + StarRating(3, of: 5) + StarRating(4, of: 5) + StarRating(5, of: 5) + } +} diff --git a/Libraries/UI/Kit/Sources/Components/HostingConfiguration.swift b/Libraries/UI/Kit/Sources/Configurations/HostingConfiguration.swift similarity index 100% rename from Libraries/UI/Kit/Sources/Components/HostingConfiguration.swift rename to Libraries/UI/Kit/Sources/Configurations/HostingConfiguration.swift diff --git a/Libraries/UI/Kit/Sources/Extensions/String+Icons.swift b/Libraries/UI/Kit/Sources/Extensions/String+Icons.swift new file mode 100644 index 0000000..bfe3ffc --- /dev/null +++ b/Libraries/UI/Kit/Sources/Extensions/String+Icons.swift @@ -0,0 +1,18 @@ +// +// String+Icons.swift +// ReviewsUIKit +// +// Created by Javier Cicchelli on 20/03/2024. +// Copyright © 2024 Röck+Cöde VoF. All rights reserved. +// + +public extension String { + enum Icon { + + // MARK: Constants + static let questionMark = "questionmark.circle.fill" + + public static let info = "info.circle.fill" + public static let person = "person.crop.circle.fill" + } +} diff --git a/Reviews.xcodeproj/project.pbxproj b/Reviews.xcodeproj/project.pbxproj index 62a1186..7bcda2a 100644 --- a/Reviews.xcodeproj/project.pbxproj +++ b/Reviews.xcodeproj/project.pbxproj @@ -7,13 +7,14 @@ objects = { /* Begin PBXBuildFile section */ - 0220ADA32BA90646001E6A9F /* FeedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0220ADA22BA90646001E6A9F /* FeedItem.swift */; }; + 0220ADA32BA90646001E6A9F /* FeedItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0220ADA22BA90646001E6A9F /* FeedItemCell.swift */; }; + 023AC7FC2BAA3EC10027D064 /* Int+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023AC7FB2BAA3EC10027D064 /* Int+Constants.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 */; }; 02DC7FA32BA51793000EEEBE /* ReviewsFeed.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 02DC7F8F2BA51793000EEEBE /* ReviewsFeed.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 02DC7FAC2BA51B4C000EEEBE /* DetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345AD13224C6EE64004E2EE1 /* DetailsViewController.swift */; }; - 02DC7FAE2BA51B4C000EEEBE /* FeedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345AD12F24C6EE64004E2EE1 /* FeedViewController.swift */; }; + 02DC7FAC2BA51B4C000EEEBE /* FeedItemViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345AD13224C6EE64004E2EE1 /* FeedItemViewController.swift */; }; + 02DC7FAE2BA51B4C000EEEBE /* FeedListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345AD12F24C6EE64004E2EE1 /* FeedListViewController.swift */; }; 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 */; }; @@ -47,7 +48,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 0220ADA22BA90646001E6A9F /* FeedItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItem.swift; sourceTree = ""; }; + 0220ADA22BA90646001E6A9F /* FeedItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItemCell.swift; sourceTree = ""; }; + 023AC7FB2BAA3EC10027D064 /* Int+Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+Constants.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 = ""; }; @@ -57,9 +59,9 @@ 345AD12424C6EDDC004E2EE1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 345AD12724C6EDDC004E2EE1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 345AD12924C6EDDC004E2EE1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 345AD12F24C6EE64004E2EE1 /* FeedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedViewController.swift; sourceTree = ""; }; + 345AD12F24C6EE64004E2EE1 /* FeedListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedListViewController.swift; sourceTree = ""; }; 345AD13124C6EE64004E2EE1 /* Review.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Review.swift; sourceTree = ""; }; - 345AD13224C6EE64004E2EE1 /* DetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailsViewController.swift; sourceTree = ""; }; + 345AD13224C6EE64004E2EE1 /* FeedItemViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedItemViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -83,6 +85,22 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0220ADA72BA98F8B001E6A9F /* Cells */ = { + isa = PBXGroup; + children = ( + 0220ADA22BA90646001E6A9F /* FeedItemCell.swift */, + ); + path = Cells; + sourceTree = ""; + }; + 023AC7FA2BAA3EB60027D064 /* Extensions */ = { + isa = PBXGroup; + children = ( + 023AC7FB2BAA3EC10027D064 /* Int+Constants.swift */, + ); + path = Extensions; + sourceTree = ""; + }; 02620B852BA89BF900DE7137 /* UI */ = { isa = PBXGroup; children = ( @@ -95,6 +113,7 @@ 02620B862BA89C0000DE7137 /* Logic */ = { isa = PBXGroup; children = ( + 023AC7FA2BAA3EB60027D064 /* Extensions */, 02620B8A2BA89C3300DE7137 /* Models */, 02620B872BA89C0700DE7137 /* View Models */, ); @@ -112,8 +131,8 @@ 02620B882BA89C1000DE7137 /* View Controllers */ = { isa = PBXGroup; children = ( - 345AD13224C6EE64004E2EE1 /* DetailsViewController.swift */, - 345AD12F24C6EE64004E2EE1 /* FeedViewController.swift */, + 345AD13224C6EE64004E2EE1 /* FeedItemViewController.swift */, + 345AD12F24C6EE64004E2EE1 /* FeedListViewController.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -121,7 +140,7 @@ 02620B892BA89C2400DE7137 /* Components */ = { isa = PBXGroup; children = ( - 0220ADA22BA90646001E6A9F /* FeedItem.swift */, + 0220ADA72BA98F8B001E6A9F /* Cells */, ); path = Components; sourceTree = ""; @@ -366,10 +385,11 @@ buildActionMask = 2147483647; files = ( 02620B8C2BA89C9A00DE7137 /* FeedViewModel.swift in Sources */, - 02DC7FAC2BA51B4C000EEEBE /* DetailsViewController.swift in Sources */, - 0220ADA32BA90646001E6A9F /* FeedItem.swift in Sources */, + 023AC7FC2BAA3EC10027D064 /* Int+Constants.swift in Sources */, + 02DC7FAC2BA51B4C000EEEBE /* FeedItemViewController.swift in Sources */, + 0220ADA32BA90646001E6A9F /* FeedItemCell.swift in Sources */, 02DC7FAF2BA51B4C000EEEBE /* Review.swift in Sources */, - 02DC7FAE2BA51B4C000EEEBE /* FeedViewController.swift in Sources */, + 02DC7FAE2BA51B4C000EEEBE /* FeedListViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };