[Framework] Show top words for filtered items in the Feed List (#12)

This PR contains the work done to show the top 3 words belonging to the filtered reviews  in the `FeedListViewController` view controller.

Reviewed-on: #12
Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
This commit is contained in:
Javier Cicchelli 2024-03-21 01:13:12 +00:00 committed by Javier Cicchelli
parent eac34c61c1
commit d00cfceb32
10 changed files with 339 additions and 43 deletions

View File

@ -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
)
}
}

View File

@ -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
}

View File

@ -18,6 +18,9 @@ extension FeedListViewController {
// MARK: Constants // MARK: Constants
private let configuration: Configuration private let configuration: Configuration
private let filterWords: FilterWordsUseCase = .init()
private let topWords: TopWordsUseCase = .init()
// MARK: Properties // MARK: Properties
@Published var filter: FilterOption = .all @Published var filter: FilterOption = .all
@ -26,9 +29,11 @@ extension FeedListViewController {
@Published var isLoading: Bool = false @Published var isLoading: Bool = false
var items: [Review] = [] var items: [Review] = []
var words: [TopWord] = []
private var reviewsAll: [Review] = [] private var reviewsAll: [Review] = []
private var reviewsFiltered: FilteredReviews = [:] private var reviewsFiltered: FilteredReviews = [:]
private var reviewsTopWords: TopWordsReviews = [:]
lazy private var iTunesService: iTunesService = { lazy private var iTunesService: iTunesService = {
.init(configuration: .init(session: configuration.session)) .init(configuration: .init(session: configuration.session))
@ -38,6 +43,18 @@ extension FeedListViewController {
init(configuration: Configuration = .init()) { init(configuration: Configuration = .init()) {
self.configuration = configuration self.configuration = configuration
} }
// MARK: Computed
var itemsCount: Int {
isWordsShowing
? items.count
: items.count + 1
}
var isWordsShowing: Bool {
filter != .all
&& !words.isEmpty
}
// MARK: Functions // MARK: Functions
func fetch() { func fetch() {
@ -56,6 +73,14 @@ extension FeedListViewController {
.reduce(into: FilteredReviews()) { partialResult, option in .reduce(into: FilteredReviews()) { partialResult, option in
partialResult[option] = reviewsAll.filter { $0.rating.stars == option.rawValue } partialResult[option] = reviewsAll.filter { $0.rating.stars == option.rawValue }
} }
reviewsTopWords = reviewsFiltered
.mapValues { reviews in
reviews.map(\.comment)
.compactMap { try? filterWords($0) }
}
.mapValues {
topWords($0).map(TopWord.init)
}
items = reviewsAll items = reviewsAll
isFilterEnabled = !items.isEmpty isFilterEnabled = !items.isEmpty
@ -74,9 +99,18 @@ extension FeedListViewController {
items = option == .all items = option == .all
? reviewsAll ? reviewsAll
: reviewsFiltered[option] ?? [] : reviewsFiltered[option] ?? []
words = reviewsTopWords[option] ?? []
filter = option filter = option
} }
func item(for index: Int) -> Review? {
guard index < items.count else { return nil }
return isWordsShowing
? items[index - 1]
: items[index]
}
} }
} }
@ -86,5 +120,6 @@ private extension FeedListViewController.ViewModel {
// MARK: Type aliases // MARK: Type aliases
typealias FilteredReviews = [FilterOption: [Review]] typealias FilteredReviews = [FilterOption: [Review]]
typealias TopWordsReviews = [FilterOption: [TopWord]]
} }

View File

@ -0,0 +1,17 @@
//
// FeedItemCell.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 FeedItemCell: UITableViewCell, TableCell {
// MARK: Constants
static let cellID = "FeedItemCell"
}

View File

@ -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, TableCell {
// MARK: Constants
static let cellID = "TopWordsCell"
}

View File

@ -40,11 +40,12 @@ public class FeedListViewController: UITableViewController {
image: UIImage.Icon.star, image: UIImage.Icon.star,
children: { children: {
FilterOption.allCases.map { option -> UIAction in FilterOption.allCases.map { option -> UIAction in
.init(title: option.text, .init(
image: .init(systemName: option.icon) title: option.text,
) { [weak self] _ in image: .init(systemName: option.icon)
self?.viewModel.filter(by: option) ) { [weak self] _ in
} self?.viewModel.filter(by: option)
}
} }
}() }()
) )
@ -83,30 +84,18 @@ public class FeedListViewController: UITableViewController {
_ tableView: UITableView, _ tableView: UITableView,
numberOfRowsInSection section: Int numberOfRowsInSection section: Int
) -> Int { ) -> Int {
viewModel.items.count viewModel.itemsCount
} }
public override func tableView( public override func tableView(
_ tableView: UITableView, _ tableView: UITableView,
cellForRowAt indexPath: IndexPath cellForRowAt indexPath: IndexPath
) -> UITableViewCell { ) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: .Cell.feedItem) else { if viewModel.isWordsShowing && indexPath.row == 0 {
return .init() makeTopWordsCell(tableView)
} else {
makeFeedItemCell(tableView, at: indexPath.row)
} }
cell.contentConfiguration = {
if #available(iOS 16.0, *) {
UIHostingConfiguration {
FeedItemCell(items[indexPath.row])
}
} else {
HostingConfiguration {
FeedItemCell(items[indexPath.row])
}
}
}()
return cell
} }
// MARK: UITableViewDelegate // MARK: UITableViewDelegate
@ -158,8 +147,57 @@ private extension FeedListViewController {
.store(in: &cancellables) .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() { func registerTableCells() {
tableView.register(UITableViewCell.self, forCellReuseIdentifier: .Cell.feedItem) FeedItemCell.register(in: tableView)
TopWordsCell.register(in: tableView)
} }
func setNavigationBar() { func setNavigationBar() {
@ -232,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 // MARK: - Previews
#if DEBUG #if DEBUG
import ReviewsiTunesKit import ReviewsiTunesKit

View File

@ -1,5 +1,5 @@
// //
// FeedItemCell.swift // FeedItemView.swift
// ReviewsFeed // ReviewsFeed
// //
// Created by Javier Cicchelli on 19/03/2024. // Created by Javier Cicchelli on 19/03/2024.
@ -9,7 +9,7 @@
import ReviewsUIKit import ReviewsUIKit
import SwiftUI import SwiftUI
struct FeedItemCell: View { struct FeedItemView: View {
// MARK: Constants // MARK: Constants
private let item: Review private let item: Review
@ -66,8 +66,8 @@ struct FeedItemCell: View {
} }
// MARK: - Previews // MARK: - Previews
#Preview("Feed Item Cell") { #Preview("Feed Item") {
FeedItemCell(.init( FeedItemView(.init(
author: "Some author name here...", author: "Some author name here...",
comment: "Some review comment here...", comment: "Some review comment here...",
id: 0, id: 0,

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -7,7 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* 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 */; }; 023AC7FC2BAA3EC10027D064 /* Int+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023AC7FB2BAA3EC10027D064 /* Int+Constants.swift */; };
02620B8C2BA89C9A00DE7137 /* FeedListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02620B8B2BA89C9A00DE7137 /* FeedListViewModel.swift */; }; 02620B8C2BA89C9A00DE7137 /* FeedListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02620B8B2BA89C9A00DE7137 /* FeedListViewModel.swift */; };
02909E792BAB6B0200710E14 /* FilterOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02909E782BAB6B0200710E14 /* FilterOption.swift */; }; 02909E792BAB6B0200710E14 /* FilterOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02909E782BAB6B0200710E14 /* FilterOption.swift */; };
@ -22,6 +22,11 @@
02DC7FAF2BA51B4C000EEEBE /* Review.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345AD13124C6EE64004E2EE1 /* Review.swift */; }; 02DC7FAF2BA51B4C000EEEBE /* Review.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345AD13124C6EE64004E2EE1 /* Review.swift */; };
02DC7FB32BA52518000EEEBE /* ReviewsKit in Frameworks */ = {isa = PBXBuildFile; productRef = 02DC7FB22BA52518000EEEBE /* ReviewsKit */; }; 02DC7FB32BA52518000EEEBE /* ReviewsKit in Frameworks */ = {isa = PBXBuildFile; productRef = 02DC7FB22BA52518000EEEBE /* ReviewsKit */; };
02DC7FB52BA52520000EEEBE /* ReviewsKit in Frameworks */ = {isa = PBXBuildFile; productRef = 02DC7FB42BA52520000EEEBE /* 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 */; };
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 */; }; 345AD11C24C6EDD9004E2EE1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345AD11B24C6EDD9004E2EE1 /* AppDelegate.swift */; };
345AD12524C6EDDC004E2EE1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 345AD12424C6EDDC004E2EE1 /* Assets.xcassets */; }; 345AD12524C6EDDC004E2EE1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 345AD12424C6EDDC004E2EE1 /* Assets.xcassets */; };
345AD12824C6EDDC004E2EE1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 345AD12624C6EDDC004E2EE1 /* LaunchScreen.storyboard */; }; 345AD12824C6EDDC004E2EE1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 345AD12624C6EDDC004E2EE1 /* LaunchScreen.storyboard */; };
@ -52,7 +57,7 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
0220ADA22BA90646001E6A9F /* FeedItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItemCell.swift; sourceTree = "<group>"; }; 0220ADA22BA90646001E6A9F /* FeedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItemView.swift; sourceTree = "<group>"; };
023AC7FB2BAA3EC10027D064 /* Int+Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+Constants.swift"; sourceTree = "<group>"; }; 023AC7FB2BAA3EC10027D064 /* Int+Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+Constants.swift"; sourceTree = "<group>"; };
02620B8B2BA89C9A00DE7137 /* FeedListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListViewModel.swift; sourceTree = "<group>"; }; 02620B8B2BA89C9A00DE7137 /* FeedListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListViewModel.swift; sourceTree = "<group>"; };
02909E782BAB6B0200710E14 /* FilterOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterOption.swift; sourceTree = "<group>"; }; 02909E782BAB6B0200710E14 /* FilterOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterOption.swift; sourceTree = "<group>"; };
@ -62,6 +67,11 @@
02DC7F8F2BA51793000EEEBE /* ReviewsFeed.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReviewsFeed.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = "<group>"; }; 02DC7F912BA51793000EEEBE /* ReviewsFeed.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReviewsFeed.h; sourceTree = "<group>"; };
02DC7FB12BA52084000EEEBE /* Libraries */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Libraries; sourceTree = "<group>"; }; 02DC7FB12BA52084000EEEBE /* Libraries */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Libraries; sourceTree = "<group>"; };
02EACF2D2BABA34600FF8ECD /* FeedItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItemCell.swift; sourceTree = "<group>"; };
02EACF2F2BABA50D00FF8ECD /* TopWordsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopWordsCell.swift; sourceTree = "<group>"; };
02EACF312BABB23A00FF8ECD /* TopWordsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopWordsView.swift; sourceTree = "<group>"; };
02EACF332BABB28900FF8ECD /* TopWord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopWord.swift; sourceTree = "<group>"; };
02EACF352BABB2F200FF8ECD /* TopWord+DTOs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TopWord+DTOs.swift"; sourceTree = "<group>"; };
345AD11824C6EDD9004E2EE1 /* Reviews.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Reviews.app; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = "<group>"; }; 345AD11B24C6EDD9004E2EE1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
345AD12424C6EDDC004E2EE1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 345AD12424C6EDDC004E2EE1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -93,20 +103,13 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
0220ADA72BA98F8B001E6A9F /* Cells */ = {
isa = PBXGroup;
children = (
0220ADA22BA90646001E6A9F /* FeedItemCell.swift */,
);
path = Cells;
sourceTree = "<group>";
};
023AC7FA2BAA3EB60027D064 /* Extensions */ = { 023AC7FA2BAA3EB60027D064 /* Extensions */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
02909E7A2BAB6D2E00710E14 /* Bundle+Constants.swift */, 02909E7A2BAB6D2E00710E14 /* Bundle+Constants.swift */,
023AC7FB2BAA3EC10027D064 /* Int+Constants.swift */, 023AC7FB2BAA3EC10027D064 /* Int+Constants.swift */,
02909E7C2BAB7FFE00710E14 /* Review+DTOs.swift */, 02909E7C2BAB7FFE00710E14 /* Review+DTOs.swift */,
02EACF352BABB2F200FF8ECD /* TopWord+DTOs.swift */,
); );
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
@ -114,7 +117,8 @@
02620B852BA89BF900DE7137 /* UI */ = { 02620B852BA89BF900DE7137 /* UI */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
02620B892BA89C2400DE7137 /* Components */, 02EACF2C2BABA32A00FF8ECD /* Cells */,
02620B892BA89C2400DE7137 /* Views */,
02620B882BA89C1000DE7137 /* View Controllers */, 02620B882BA89C1000DE7137 /* View Controllers */,
); );
path = UI; path = UI;
@ -148,18 +152,20 @@
path = "View Controllers"; path = "View Controllers";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
02620B892BA89C2400DE7137 /* Components */ = { 02620B892BA89C2400DE7137 /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0220ADA72BA98F8B001E6A9F /* Cells */, 0220ADA22BA90646001E6A9F /* FeedItemView.swift */,
02EACF312BABB23A00FF8ECD /* TopWordsView.swift */,
); );
path = Components; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
02620B8A2BA89C3300DE7137 /* Models */ = { 02620B8A2BA89C3300DE7137 /* Models */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
345AD13124C6EE64004E2EE1 /* Review.swift */, 345AD13124C6EE64004E2EE1 /* Review.swift */,
02EACF332BABB28900FF8ECD /* TopWord.swift */,
); );
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
@ -273,6 +279,15 @@
path = Sources; path = Sources;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
02EACF2C2BABA32A00FF8ECD /* Cells */ = {
isa = PBXGroup;
children = (
02EACF2D2BABA34600FF8ECD /* FeedItemCell.swift */,
02EACF2F2BABA50D00FF8ECD /* TopWordsCell.swift */,
);
path = Cells;
sourceTree = "<group>";
};
345AD10F24C6EDD9004E2EE1 = { 345AD10F24C6EDD9004E2EE1 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -421,14 +436,19 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
02EACF302BABA50D00FF8ECD /* TopWordsCell.swift in Sources */,
02620B8C2BA89C9A00DE7137 /* FeedListViewModel.swift in Sources */, 02620B8C2BA89C9A00DE7137 /* FeedListViewModel.swift in Sources */,
02EACF342BABB28900FF8ECD /* TopWord.swift in Sources */,
023AC7FC2BAA3EC10027D064 /* Int+Constants.swift in Sources */, 023AC7FC2BAA3EC10027D064 /* Int+Constants.swift in Sources */,
02DC7FAC2BA51B4C000EEEBE /* FeedItemViewController.swift in Sources */, 02DC7FAC2BA51B4C000EEEBE /* FeedItemViewController.swift in Sources */,
02909E7B2BAB6D2E00710E14 /* Bundle+Constants.swift in Sources */, 02909E7B2BAB6D2E00710E14 /* Bundle+Constants.swift in Sources */,
02EACF2E2BABA34600FF8ECD /* FeedItemCell.swift in Sources */,
02909E7D2BAB7FFE00710E14 /* Review+DTOs.swift in Sources */, 02909E7D2BAB7FFE00710E14 /* Review+DTOs.swift in Sources */,
0220ADA32BA90646001E6A9F /* FeedItemCell.swift in Sources */, 0220ADA32BA90646001E6A9F /* FeedItemView.swift in Sources */,
02EACF362BABB2F200FF8ECD /* TopWord+DTOs.swift in Sources */,
02DC7FAF2BA51B4C000EEEBE /* Review.swift in Sources */, 02DC7FAF2BA51B4C000EEEBE /* Review.swift in Sources */,
02DC7FAE2BA51B4C000EEEBE /* FeedListViewController.swift in Sources */, 02DC7FAE2BA51B4C000EEEBE /* FeedListViewController.swift in Sources */,
02EACF322BABB23A00FF8ECD /* TopWordsView.swift in Sources */,
02909E792BAB6B0200710E14 /* FilterOption.swift in Sources */, 02909E792BAB6B0200710E14 /* FilterOption.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;