fix: code review notes

This commit is contained in:
Nikita Semenov 2022-08-09 16:41:52 +03:00
parent 373b0db19e
commit 5eb0183d92
13 changed files with 80 additions and 143 deletions

View File

@ -26,17 +26,26 @@ open class BaseFilterCellAppearance: FilterCellAppearanceProtocol {
public var selectedColor: UIColor
public var selectedBgColor: UIColor
public var deselectedBgColor: UIColor
public var selectedFontColor: UIColor
public var deselectedFontColor: UIColor
public var contentInsets: UIEdgeInsets
public var cornerRadius: CGFloat
init(selectedColor: UIColor,
selectedBgColor: UIColor,
deselectedBgColor: UIColor,
contentInsets: UIEdgeInsets) {
public init(selectedColor: UIColor,
selectedBgColor: UIColor,
deselectedBgColor: UIColor,
selectedFontColor: UIColor,
deselectedFontColor: UIColor,
contentInsets: UIEdgeInsets,
cornerRadius: CGFloat) {
self.selectedColor = selectedColor
self.selectedBgColor = selectedBgColor
self.deselectedBgColor = deselectedBgColor
self.selectedFontColor = selectedFontColor
self.deselectedFontColor = deselectedFontColor
self.contentInsets = contentInsets
self.cornerRadius = cornerRadius
}
}
@ -47,6 +56,9 @@ public extension BaseFilterCellAppearance {
.init(selectedColor: .systemGreen,
selectedBgColor: .white,
deselectedBgColor: .lightGray,
contentInsets: .init(top: 4, left: 8, bottom: 4, right: 8))
selectedFontColor: .systemGreen,
deselectedFontColor: .black,
contentInsets: .init(top: 4, left: 8, bottom: 4, right: 8),
cornerRadius: 6)
}
}
}

View File

@ -1,41 +0,0 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public struct DefaultFilterModel: FilterRepresenter {
public let id: String
public let property: DefaultFilterPropertyValue?
public let properties: [DefaultFilterPropertyValue]?
}
public extension DefaultFilterModel {
init(id: String, property: DefaultFilterPropertyValue) {
self.id = id
self.property = property
self.properties = nil
}
init(id: String, properties: [DefaultFilterPropertyValue]) {
self.id = id
self.property = nil
self.properties = properties
}
}

View File

@ -22,17 +22,19 @@
import UIKit
@available(iOS 13, *)
public struct DefaultFilterPropertyValue: FilterPropertyValueRepresenter, Identifiable {
public let id: String
public let title: String
public let excludingProperties: [String]?
public let excludingProperties: [String]
public var isSelected: Bool
}
@available(iOS 13, *)
public extension DefaultFilterPropertyValue {
init(id: String, title: String, excludingProperties: [String]? = nil) {
init(id: String, title: String, excludingProperties: [String] = []) {
self.id = id
self.title = title
self.excludingProperties = excludingProperties
@ -40,6 +42,7 @@ public extension DefaultFilterPropertyValue {
}
}
@available(iOS 13, *)
extension DefaultFilterPropertyValue: Hashable {
public static func == (lhs: DefaultFilterPropertyValue, rhs: DefaultFilterPropertyValue) -> Bool {
lhs.id == rhs.id

View File

@ -24,5 +24,4 @@ public protocol FilterCellViewModelProtocol {
var id: String { get set }
var title: String { get set }
var isSelected: Bool { get set }
var appearance: FilterCellAppearanceProtocol { get }
}

View File

@ -26,5 +26,8 @@ public protocol FilterCellAppearanceProtocol {
var selectedColor: UIColor { get set }
var selectedBgColor: UIColor { get set }
var deselectedBgColor: UIColor { get set }
var selectedFontColor: UIColor { get set }
var deselectedFontColor: UIColor { get set }
var contentInsets: UIEdgeInsets { get set }
}
var cornerRadius: CGFloat { get set }
}

View File

@ -22,6 +22,6 @@
public protocol FilterPropertyValueRepresenter {
var id: String { get }
var excludingProperties: [String]? { get }
var excludingProperties: [String] { get }
var isSelected: Bool { get set }
}

View File

@ -1,30 +0,0 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public protocol FilterRepresenter {
associatedtype FilterProperty: FilterPropertyValueRepresenter
var id: String { get }
var property: FilterProperty? { get }
var properties: [FilterProperty]? { get }
}

View File

@ -24,17 +24,17 @@ import Foundation
public protocol FilterViewModelProtocol: AnyObject {
associatedtype Property: FilterPropertyValueRepresenter, Hashable
associatedtype Property: FilterPropertyValueRepresenter & Hashable
associatedtype CellViewModel: FilterCellViewModelProtocol & Hashable
typealias Change = (indexPath: IndexPath, viewModel: CellViewModel)
var properties: [Property] { get set }
var selectedProperties: Set<Property> { get set }
var selectedProperties: [Property] { get set }
func filterDidSelected(atIndexPath indexPath: IndexPath) -> [Change]
func toggleProperty(atIndexPath indexPath: IndexPath) -> (selected: [Property], deselected: [Property])
func getCellsViewModels() -> [CellViewModel]
// func getCellsViewModels() -> [CellViewModel]
}
public extension FilterViewModelProtocol {
@ -49,17 +49,17 @@ public extension FilterViewModelProtocol {
private func toggleProperty(_ property: Property) -> (selected: [Property], deselected: [Property]) {
var propertiesToDeselect = [Property]()
var propertiesToSelect = [Property]()
let selectedProperty = selectedProperties.first { selectedProperty in
let selectedPropertyId = selectedProperties.firstIndex { selectedProperty in
selectedProperty.id == property.id
}
if let selectedProperty = selectedProperty {
if let selectedPropertyId = selectedPropertyId {
// Removes previously selected filter
selectedProperties.remove(selectedProperty)
selectedProperties.remove(at: selectedPropertyId)
propertiesToDeselect.append(property)
} else {
// Selectes unselected filter
selectedProperties.insert(property)
selectedProperties.append(property)
propertiesToSelect.append(property)
// If the filter has filters to exclude, these filters marks as deselected
@ -71,7 +71,9 @@ public extension FilterViewModelProtocol {
}
private func excludeProperties(_ filter: Property) -> [Property] {
guard let propertiesToExclude = filter.excludingProperties, !propertiesToExclude.isEmpty else {
let propertiesToExclude = filter.excludingProperties
guard !propertiesToExclude.isEmpty else {
return []
}

View File

@ -36,7 +36,7 @@ open class BaseFilterViewModel<CellViewModelType: FilterCellViewModelProtocol &
filtersCollection?.updateView()
}
}
public var selectedProperties: Set<PropertyValue> = [] {
public var selectedProperties: [PropertyValue] = [] {
didSet {
filtersCollection?.updateView()
}
@ -44,8 +44,11 @@ open class BaseFilterViewModel<CellViewModelType: FilterCellViewModelProtocol &
public weak var filtersCollection: UpdatableView?
public init(filters: [PropertyValue]) {
public private(set) var cellsViewModels: [CellViewModelType]
public init(filters: [PropertyValue], cellsViewModels: [CellViewModelType] = []) {
self.properties = filters
self.cellsViewModels = cellsViewModels
}
open func filterDidSelected(atIndexPath indexPath: IndexPath) -> [Change] {
@ -56,8 +59,6 @@ open class BaseFilterViewModel<CellViewModelType: FilterCellViewModelProtocol &
.filter { isFilterInArray($0.element, filters: selected) || isFilterInArray($0.element, filters: deselected) }
for (offset, element) in changedFilters {
guard !getCellsViewModels().isEmpty else { return [] }
setSelectedCell(atIndex: offset, isSelected: selectedProperties.contains(element))
properties[offset].isSelected = selectedProperties.contains(element)
}
@ -65,24 +66,17 @@ open class BaseFilterViewModel<CellViewModelType: FilterCellViewModelProtocol &
let changedItems = changedFilters
.map {
Change(indexPath: IndexPath(item: $0.offset, section: .zero),
viewModel: getCellsViewModels()[$0.offset])
viewModel: cellsViewModels[$0.offset])
}
return changedItems
}
open func setSelectedCell(atIndex index: Int, isSelected: Bool) {
// override in subclasses
cellsViewModels[index].isSelected = isSelected
}
open func getCellsViewModels() -> [CellViewModelType] {
// override in subclasses
return []
}
// MARK: - Public methods
public func isFilterInArray(_ filter: PropertyValue, filters: [PropertyValue]) -> Bool {
open func isFilterInArray(_ filter: PropertyValue, filters: [PropertyValue]) -> Bool {
filters.contains(where: { $0.id == filter.id })
}
}

View File

@ -26,10 +26,6 @@ public struct DefaultFilterCellViewModel: FilterCellViewModelProtocol, Hashable
public var title: String
public var isSelected: Bool
public var appearance: FilterCellAppearanceProtocol {
BaseFilterCellAppearance.defaultFilterCellAppearance
}
public init(id: String,
title: String,
isSelected: Bool) {

View File

@ -20,24 +20,16 @@
// THE SOFTWARE.
//
@available(iOS 13, *)
open class DefaultFilterViewModel: BaseFilterViewModel<DefaultFilterCellViewModel, DefaultFilterPropertyValue> {
private var cellsViewModels: [DefaultFilterCellViewModel] = []
public override init(filters: [DefaultFilterPropertyValue]) {
super.init(filters: filters)
self.cellsViewModels = filters.compactMap {
public init(filters: [DefaultFilterPropertyValue]) {
let cellsViewModel = filters.compactMap {
DefaultFilterCellViewModel(id: $0.id,
title: $0.title,
isSelected: $0.isSelected)
}
}
open override func setSelectedCell(atIndex index: Int, isSelected: Bool) {
cellsViewModels[index].isSelected = isSelected
}
open override func getCellsViewModels() -> [DefaultFilterCellViewModel] {
cellsViewModels
super.init(filters: filters, cellsViewModels: cellsViewModel)
}
}

View File

@ -85,7 +85,9 @@ open class BaseFiltersCollectionView<CellType: UICollectionViewCell & Configurab
registerCell()
viewModel?.filtersCollection = self
}
open func viewDidAppear() {
applySnapshot()
}
@ -119,7 +121,7 @@ open class BaseFiltersCollectionView<CellType: UICollectionViewCell & Configurab
var snapshot = Snapshot()
snapshot.appendSections([DefaultSection.main.rawValue])
snapshot.appendItems(viewModel.getCellsViewModels(), toSection: DefaultSection.main.rawValue)
snapshot.appendItems(viewModel.cellsViewModels, toSection: DefaultSection.main.rawValue)
collectionViewDataSource.apply(snapshot, animatingDifferences: true)
}
@ -145,7 +147,5 @@ open class BaseFiltersCollectionView<CellType: UICollectionViewCell & Configurab
cell.configure(with: change.viewModel)
}
applySnapshot()
}
}

View File

@ -29,14 +29,31 @@ open class DefaultFilterCollectionCell: ContainerCollectionViewCell<UILabel>,
public var viewModel: DefaultFilterCellViewModel?
open override var reuseIdentifier: String? {
open var cellAppearance: BaseFilterCellAppearance {
.defaultFilterCellAppearance
}
open class var reuseIdentifier: String {
"default-filter-cell"
}
open override var isSelected: Bool {
didSet {
if isSelected {
setSelectedAppearance()
} else {
setDeselectAppearance()
}
}
}
open override func configureAppearance() {
super.configureAppearance()
layer.round(corners: .allCorners, radius: 6)
layer.round(corners: .allCorners, radius: cellAppearance.cornerRadius)
contentInsets = cellAppearance.contentInsets
setDeselectAppearance()
}
// MARK: - ConfigurableView
@ -45,30 +62,20 @@ open class DefaultFilterCollectionCell: ContainerCollectionViewCell<UILabel>,
self.viewModel = viewModel
wrappedView.text = viewModel.title
contentInsets = viewModel.appearance.contentInsets
setSelected(isSelected: viewModel.isSelected)
isSelected = viewModel.isSelected
}
// MARK: - Public methods
open func setSelected(isSelected: Bool) {
let appearance = viewModel?.appearance
let selectedColor = appearance?.selectedColor ?? .green
wrappedView.textColor = isSelected ? selectedColor : .black
if isSelected {
backgroundColor = appearance?.selectedBgColor ?? .white
layer.borderColor = selectedColor.cgColor
layer.borderWidth = 1
} else {
setDeselectAppearance()
}
open func setSelectedAppearance() {
wrappedView.textColor = cellAppearance.selectedFontColor
backgroundColor = cellAppearance.selectedBgColor
layer.borderColor = cellAppearance.selectedColor.cgColor
layer.borderWidth = 1
}
open func setDeselectAppearance() {
wrappedView.textColor = cellAppearance.deselectedFontColor
layer.borderWidth = 0
backgroundColor = viewModel?.appearance.deselectedBgColor ?? .lightGray
backgroundColor = cellAppearance.deselectedBgColor
}
}