fix: refactoring and review changes

This commit is contained in:
Nikita Semenov 2022-08-03 21:27:30 +03:00
parent 276fbc8c10
commit edec8bc94f
14 changed files with 46 additions and 397 deletions

View File

@ -36,6 +36,8 @@ public struct DefaultFilterPropertyValue: FilterPropertyValueRepresenter,
DefaultFilterCellViewModel(id: id,
title: title,
selectedColor: .green,
selectedBgColor: .white,
deselectedBgColor: .lightGray,
insets: .init(top: .zero, left: 8, bottom: .zero, right: 8),
isSelected: isSelected)
}

View File

@ -30,8 +30,8 @@ public struct FiltersLayoutConfiguration {
public var verticalItemSpacing: CGFloat
public var contentInsets: UIEdgeInsets
public init(itemSize: NSCollectionLayoutSize = .init(widthDimension: .estimated(32),
heightDimension: .estimated(32)),
public init(itemSize: NSCollectionLayoutSize = .init(widthDimension: .estimated(36),
heightDimension: .estimated(36)),
horizontalItemSpacing: CGFloat = .zero,
verticalItemSpacing: CGFloat = .zero,
contentInsets: UIEdgeInsets) {

View File

@ -1,36 +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.
//
import UIKit
//public protocol CollectionDirectorRepresenter: UICollectionViewDataSource, UICollectionViewDelegate {
//
// associatedtype Item
//
// var collectionView: UICollectionView? { get set }
// var delegate: FilterItemsDelegate? { get set }
//
// func insertItem(_ item: Item, at index: Int)
// func deleteItem(at index: Int)
// func update(item: Item, at index: Int)
// func scrollToItem(at indexPath: IndexPath, animated: Bool)
//}

View File

@ -1,49 +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.
//
//
//import TIUIKitCore
//import UIKit
//
//public protocol FilterCollectionItem {
//
// associatedtype Filter: FilterPropertyValueRepresenter
//
// var identifier: String { get }
// var itemType: AnyClass { get }
// var filter: Filter { get set }
//
// init(filter: Filter, viewModel: FilterCellViewModelRepresentable)
//
// func register(for collectionView: UICollectionView)
// func configure(item: UICollectionViewCell)
// func didSelectItem(atIndexPath indexPath: IndexPath, cell: UICollectionViewCell?)
//}
//
//public extension FilterCollectionItem {
// var identifier: String {
// filter.id
// }
//
// func register(for collectionView: UICollectionView) {
// collectionView.register(itemType, forCellWithReuseIdentifier: identifier)
// }
//}

View File

@ -1,27 +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.
//
import UIKit
//
//public protocol FilterItemsDelegate: AnyObject {
// func didSelectItem(atIndexPath indexPath: IndexPath)
//}

View File

@ -23,10 +23,6 @@
import UIKit
public protocol FiltersCollectionHolder: AnyObject {
var collectionView: UICollectionView { get }
var viewModel: DefaultFiltersViewModel? { get set }
func applyChange(_ changes: [DefaultFiltersViewModel.Change])
func updateView()
func configure(filterCell: UICollectionViewCell, cellViewModel: FilterCellViewModelProtocol)

View File

@ -37,7 +37,7 @@ public protocol FiltersViewModelProtocol: AnyObject {
public extension FiltersViewModelProtocol {
func toggleFilter(atIndexPath indexPath: IndexPath) -> (selected: [Filter], deselected: [Filter]) {
guard let item = getItemSafely(indexPath.item) else { return ([], []) }
guard let item = getFilterSafely(indexPath.item) else { return ([], []) }
return toggleFilter(item)
}
@ -51,30 +51,44 @@ public extension FiltersViewModelProtocol {
}
if let selectedFilter = selectedFilter {
// Removes previously selected filter
selectedFilters.remove(selectedFilter)
filtersToDeselect.append(filter)
} else {
// Selectes unselected filter
selectedFilters.insert(filter)
filtersToSelect.append(filter)
if let filtersToExclude = filter.excludingProperties, !filtersToExclude.isEmpty {
for filtersIdToExclude in filtersToExclude {
let filterToExclude = selectedFilters.first { filter in
filter.id == filtersIdToExclude
}
if let itemToExclude = filterToExclude {
let (_, deselected) = toggleFilter(itemToExclude)
filtersToDeselect.append(contentsOf: deselected)
}
}
}
// If the filter has filters to exclude, these filters marks as deselected
let excludedFilters = excludeFilters(filter)
filtersToDeselect.append(contentsOf: excludedFilters)
}
return (filtersToSelect, filtersToDeselect)
}
private func getItemSafely(_ index: Int) -> Filter? {
private func excludeFilters(_ filter: Filter) -> [Filter] {
guard let filtersToExclude = filter.excludingProperties, !filtersToExclude.isEmpty else {
return []
}
var excludedFilters = [Filter]()
for filtersIdToExclude in filtersToExclude {
let filterToExclude = selectedFilters.first { filter in
filter.id == filtersIdToExclude
}
if let itemToExclude = filterToExclude {
let (_, deselected) = toggleFilter(itemToExclude)
excludedFilters.append(contentsOf: deselected)
}
}
return excludedFilters
}
private func getFilterSafely(_ index: Int) -> Filter? {
guard index >= 0 && index < filters.count else {
return nil
}

View File

@ -1,35 +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.
//
import UIKit
public protocol SelectableCell: UICollectionViewCell {
var isFilterSelected: Bool { get set }
func toggleSelection()
}
public extension SelectableCell {
func toggleSelection() {
isFilterSelected.toggle()
}
}

View File

@ -34,6 +34,8 @@ public struct DefaultFilterCellViewModel: FilterCellViewModelProtocol, FilterCel
public var id: String
public var title: String
public var selectedColor: UIColor
public var selectedBgColor: UIColor
public var deselectedBgColor: UIColor
public var insets: UIEdgeInsets
public var isSelected: Bool
}

View File

@ -1,128 +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.
//
import UIKit
//open class DefaultFiltersCollectionDirector<FilterItem: FilterCollectionItem>: NSObject,
// CollectionDirectorRepresenter,
// UICollectionViewDataSource,
// UICollectionViewDelegate {
//
// private var _collectionItems: [FilterItem] = []
//
// public weak var collectionView: UICollectionView?
// public weak var delegate: FilterItemsDelegate?
//
// public var collectionItems: [FilterItem] {
// get {
// _collectionItems
// }
// set {
// _collectionItems = newValue
// collectionView?.reloadData()
// }
// }
//
// public init(collectionView: UICollectionView) {
// super.init()
//
// bind(to: collectionView)
// }
//
// public func insertItem(_ item: FilterItem, at index: Int) {
// collectionItems.insert(item, at: index)
//
// let indexPath = IndexPath(row: index, section: .zero)
//
// guard collectionItems.count != 1 else {
// return
// }
//
// scrollToItem(at: indexPath)
// }
//
// public func deleteItem(at index: Int) {
// collectionItems.remove(at: index)
//
// let indexPath = IndexPath(row: max(index - 1, .zero), section: .zero)
// scrollToItem(at: indexPath)
// }
//
// public func update(item: FilterItem, at index: Int) {
// _collectionItems[index] = item
// }
//
// public func scrollToItem(at indexPath: IndexPath, animated: Bool = true) {
// collectionView?.performBatchUpdates(nil, completion: { [weak self] isReady in
// if isReady {
// self?.collectionView?.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: animated)
// }
// })
// }
//
// // MARK: - UICollectionViewDataSource
//
// public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// collectionItems.forEach { $0.register(for: collectionView) }
// return collectionItems.count
// }
//
// public func collectionView(_ collectionView: UICollectionView,
// cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//
// let viewModel = collectionItems[indexPath.item]
// let cell = collectionView.dequeueReusableCell(withReuseIdentifier: viewModel.identifier, for: indexPath)
// viewModel.configure(item: cell)
// return cell
// }
//
// // MARK: - UICollectionViewDelegate
//
// public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// let cell = collectionView.cellForItem(at: indexPath)
// didSelectItem(atIndexPath: indexPath, cell: cell)
// }
//
// private func didSelectItem(atIndexPath indexPath: IndexPath,
// cell: UICollectionViewCell?) {
//
// guard let item = item(at: indexPath) else { return }
//
// delegate?.didSelectItem(atIndexPath: indexPath)
//
// item.didSelectItem(atIndexPath: indexPath, cell: cell)
// }
//
// private func bind(to collectionView: UICollectionView) {
// self.collectionView = collectionView
//
// collectionView.delegate = self
// collectionView.dataSource = self
// }
//
// private func item(at indexPath: IndexPath) -> FilterItem? {
// let index = indexPath.item
// let amount = collectionItems.count
//
// return (index < amount && index >= 0) ? collectionItems[index] : nil
// }
//}

View File

@ -79,6 +79,8 @@ open class DefaultFiltersViewModel: NSObject,
filtersCollectionHolder?.applyChange(changedItems)
}
// MARK: - Private methods
private func isFilterChanged(_ filter: DefaultFilterPropertyValue, filters: [DefaultFilterPropertyValue]) -> Bool {
filters.contains(where: { $0.id == filter.id })
}

View File

@ -24,7 +24,6 @@ import UIKit
import TIUIKitCore
open class BaseFilterCollectionCell: ContainerCollectionViewCell<UILabel>,
SelectableCell,
ConfigurableView {
public override var wrappedView: UILabel {
@ -33,12 +32,6 @@ open class BaseFilterCollectionCell: ContainerCollectionViewCell<UILabel>,
public var viewModel: DefaultFilterCellViewModel?
open var isFilterSelected: Bool = false {
didSet {
reloadState()
}
}
open var isLabelHidden: Bool {
get {
wrappedView.isHidden
@ -56,6 +49,7 @@ open class BaseFilterCollectionCell: ContainerCollectionViewCell<UILabel>,
self.viewModel = viewModel
wrappedView.text = viewModel.title
contentInsets = viewModel.insets
setSelected(isSelected: viewModel.isSelected)
}
@ -65,7 +59,7 @@ open class BaseFilterCollectionCell: ContainerCollectionViewCell<UILabel>,
wrappedView.textColor = isSelected ? selectedColor : .black
if isSelected {
backgroundColor = .white
backgroundColor = viewModel?.selectedBgColor ?? .white
layer.borderColor = selectedColor.cgColor
layer.borderWidth = 1
} else {
@ -73,16 +67,8 @@ open class BaseFilterCollectionCell: ContainerCollectionViewCell<UILabel>,
}
}
open func reloadState() {
if isFilterSelected {
backgroundColor = .green
} else {
backgroundColor = .white
}
}
open func setDeselectAppearance() {
layer.borderWidth = 0
backgroundColor = .lightGray
backgroundColor = viewModel?.deselectedBgColor ?? .lightGray
}
}

View File

@ -1,55 +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.
//
import UIKit
import TIUIKitCore
//public typealias AnyCollectionCell = SelectableCell & ConfigurableView
//open class BaseFilterCollectionItem<CellType: AnyCollectionCell,
// Filter: FilterPropertyValueRepresenter>: FilterCollectionItem where CellType.ViewModelType: CellViewModelRepresentable {
//
// public var itemType: AnyClass{
// CellType.self
// }
//
// public var filter: Filter
//
// public var viewModel: CellViewModelRepresentable?
//
// required public init(filter: Filter, viewModel: CellViewModelRepresentable) {
// self.filter = filter
// self.viewModel = viewModel
// }
//
// open func configure(item: UICollectionViewCell) {
// guard let viewModel = viewModel as? CellType.ViewModelType else {
// return
// }
//
// (item as? CellType)?.configure(with: viewModel)
// }
//
// open func didSelectItem(atIndexPath indexPath: IndexPath, cell: UICollectionViewCell?) {
// // Override in subviews
// }
//}

View File

@ -32,14 +32,11 @@ open class BaseFiltersCollectionView<CellType: UICollectionViewCell & Configurab
public weak var viewModel: DefaultFiltersViewModel?
open var collectionView: UICollectionView {
self
}
// MARK: - Init
public init(layout: UICollectionViewLayout) {
public init(layout: UICollectionViewLayout, viewModel: DefaultFiltersViewModel? = nil) {
self.layout = layout
self.viewModel = viewModel
super.init(frame: .zero, collectionViewLayout: layout)
}
@ -51,13 +48,7 @@ open class BaseFiltersCollectionView<CellType: UICollectionViewCell & Configurab
// MARK: - Life cycle
open func addViews() {
addSubview(collectionView)
}
open func configureLayout() {
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(collectionView.edgesEqualToSuperview())
// override in subclass
}
open func bindViews() {
@ -65,9 +56,12 @@ open class BaseFiltersCollectionView<CellType: UICollectionViewCell & Configurab
dataSource = viewModel
}
open func configureLayout() {
// override in subclass
}
open func configureAppearance() {
backgroundColor = .white
collectionView.backgroundColor = .white
}
open func localize() {
@ -112,20 +106,3 @@ open class BaseFiltersCollectionView<CellType: UICollectionViewCell & Configurab
viewModel?.filters.forEach { self.register(CellType.self, forCellWithReuseIdentifier: $0.id) }
}
}
extension UIView {
func edgesEqualToSuperview(constant c: CGFloat = .zero) -> [NSLayoutConstraint] {
guard let superview = self.superview else {
fatalError("\(self.description) doesn't have superview")
}
var constraints = [NSLayoutConstraint]()
constraints.append(topAnchor.constraint(equalTo: superview.topAnchor, constant: c))
constraints.append(trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: c))
constraints.append(bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: c))
constraints.append(leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: c))
return constraints
}
}