fix: code review notes
This commit is contained in:
parent
373b0db19e
commit
5eb0183d92
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,6 @@
|
|||
|
||||
public protocol FilterPropertyValueRepresenter {
|
||||
var id: String { get }
|
||||
var excludingProperties: [String]? { get }
|
||||
var excludingProperties: [String] { get }
|
||||
var isSelected: Bool { get set }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
|
@ -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 []
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue