[Framework] Feed item filtering in the Feed List view (#11)

This PR contains the work done to implement the filtering of the items shown in the `FeedListViewController` view controller by star rating.

Reviewed-on: rock-n-code/app-reviews#11
Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
This commit was merged in pull request #11.
This commit is contained in:
2024-03-20 21:35:46 +00:00
committed by javier
parent c9f4b9a677
commit eac34c61c1
11 changed files with 517 additions and 94 deletions
@@ -0,0 +1,88 @@
//
// FilterOption.swift
// ReviewsFeed
//
// Created by Javier Cicchelli on 20/03/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import Foundation
import ReviewsFoundationKit
import ReviewsUIKit
import UIKit
enum FilterOption: Int, CaseIterable {
case all = 0
case only1Star
case only2Star
case only3Star
case only4Star
case only5Star
}
// MARK: - Computed
extension FilterOption {
var icon: String {
switch self {
case .all: .Icon.starAll
case .only1Star: .Icon.star1
case .only2Star: .Icon.star2
case .only3Star: .Icon.star3
case .only4Star: .Icon.star4
case .only5Star: .Icon.star5
}
}
var text: String {
switch self {
case .all: NSLocalizedString(
.Key.Menu.Action.allReviews,
bundle: .module,
comment: .empty
)
case .only1Star: NSLocalizedString(
.Key.Menu.Action.only1StarReviews,
bundle: .module,
comment: .empty
)
case .only2Star: NSLocalizedString(
.Key.Menu.Action.only2StarReviews,
bundle: .module,
comment: .empty
)
case .only3Star: NSLocalizedString(
.Key.Menu.Action.only3StarReviews,
bundle: .module,
comment: .empty
)
case .only4Star: NSLocalizedString(
.Key.Menu.Action.only4StarReviews,
bundle: .module,
comment: .empty
)
case .only5Star: NSLocalizedString(
.Key.Menu.Action.only5StarReviews,
bundle: .module,
comment: .empty
)
}
}
}
// MARK: - String+Constants
private extension String {
enum Key {
enum Menu {
enum Action {
static let allReviews = "common.filter.menu.all-reviews.action.title.text"
static let only1StarReviews = "common.filter.menu.only-1-star-reviews.action.title.text"
static let only2StarReviews = "common.filter.menu.only-2-star-reviews.action.title.text"
static let only3StarReviews = "common.filter.menu.only-3-star-reviews.action.title.text"
static let only4StarReviews = "common.filter.menu.only-4-star-reviews.action.title.text"
static let only5StarReviews = "common.filter.menu.only-5-star-reviews.action.title.text"
}
}
}
}
@@ -0,0 +1,16 @@
//
// Bundle+Constants.swift
// ReviewsFeed
//
// Created by Javier Cicchelli on 20/03/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import Foundation
extension Bundle {
// MARK: Constants
static let module = Bundle(for: FeedItemViewController.self)
}
@@ -0,0 +1,27 @@
//
// Review+DTOs.swift
// ReviewsFeed
//
// Created by Javier Cicchelli on 20/03/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import ReviewsFeedKit
extension Review {
// MARK: Initialisers
init(_ dto: ReviewsFeedKit.Review) {
self = .init(
author: dto.author,
comment: dto.content,
id: dto.id,
rating: .init(
stars: dto.rating,
appVersion: dto.version
),
title: dto.title
)
}
}
@@ -0,0 +1,90 @@
//
// FeedListViewModel.swift
// ReviewsFeed
//
// Created by Javier Cicchelli on 18/03/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import Foundation
import ReviewsFilterKit
import ReviewsiTunesKit
extension FeedListViewController {
final class ViewModel: ObservableObject {
// MARK: Type aliases
typealias Configuration = FeedListViewController.Configuration
// MARK: Constants
private let configuration: Configuration
// MARK: Properties
@Published var filter: FilterOption = .all
@Published var isFilterEnabled: Bool = false
@Published var isFiltering: Bool = false
@Published var isLoading: Bool = false
var items: [Review] = []
private var reviewsAll: [Review] = []
private var reviewsFiltered: FilteredReviews = [:]
lazy private var iTunesService: iTunesService = {
.init(configuration: .init(session: configuration.session))
}()
// MARK: Initialisers
init(configuration: Configuration = .init()) {
self.configuration = configuration
}
// MARK: Functions
func fetch() {
Task {
isFilterEnabled = false
isLoading = items.isEmpty
do {
let output = try await iTunesService.getReviews(.init(
appID: configuration.appID,
countryCode: configuration.countryCode
))
reviewsAll = output.reviews.map(Review.init)
reviewsFiltered = FilterOption.allCases
.reduce(into: FilteredReviews()) { partialResult, option in
partialResult[option] = reviewsAll.filter { $0.rating.stars == option.rawValue }
}
items = reviewsAll
isFilterEnabled = !items.isEmpty
} catch {
// TODO: handle this error gracefully.
print("ERROR: \(error.localizedDescription)")
}
isLoading = false
}
}
func filter(by option: FilterOption) {
guard option != filter else { return }
items = option == .all
? reviewsAll
: reviewsFiltered[option] ?? []
filter = option
}
}
}
// MARK: - Helpers
private extension FeedListViewController.ViewModel {
// MARK: Type aliases
typealias FilteredReviews = [FilterOption: [Review]]
}
@@ -1,69 +0,0 @@
//
// FeedViewModel.swift
// ReviewsFeed
//
// Created by Javier Cicchelli on 18/03/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import Foundation
import ReviewsiTunesKit
extension FeedListViewController {
final class ViewModel: ObservableObject {
// MARK: Type aliases
typealias Configuration = FeedListViewController.Configuration
// MARK: Constants
private let configuration: Configuration
// MARK: Properties
@Published var loading: Bool = false
var items: [Review] = []
// MARK: Initialisers
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 {
loading = true
do {
let output = try await iTunesService.getReviews(.init(
appID: configuration.appID,
countryCode: configuration.countryCode
))
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
}
}
}
}