From 47280c7cd745e738d415a2d1c50b03472ccd054b Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Thu, 4 Aug 2022 18:20:08 +0300 Subject: [PATCH] fix: review notes --- .../Models/BaseFilterCellAppearance.swift | 52 +++++++++++++++++ .../Models/DefaultFilterPropertyValue.swift | 16 ++--- .../Models/FilterCellViewModelProtocol.swift | 28 +++++++++ .../FilterCellAppearanceProtocol.swift | 30 ++++++++++ .../FilterPropertyValueRepresenter.swift | 9 +-- .../DefaultFilterCellViewModel.swift | 24 ++++---- .../ViewModels/DefaultFiltersViewModel.swift | 58 ++++++++++++++++--- .../Views/BaseFiltersCollectionView.swift | 24 ++++---- .../Views/DefaultFilterCollectionCell.swift | 12 ++-- .../Sources/UpdatableView/UpdatableView.swift | 25 ++++++++ 10 files changed, 223 insertions(+), 55 deletions(-) create mode 100644 TIEcommerce/Sources/Filters/Models/BaseFilterCellAppearance.swift create mode 100644 TIEcommerce/Sources/Filters/Models/FilterCellViewModelProtocol.swift create mode 100644 TIEcommerce/Sources/Filters/Protocols/FilterCellAppearanceProtocol.swift create mode 100644 TIUIKitCore/Sources/UpdatableView/UpdatableView.swift diff --git a/TIEcommerce/Sources/Filters/Models/BaseFilterCellAppearance.swift b/TIEcommerce/Sources/Filters/Models/BaseFilterCellAppearance.swift new file mode 100644 index 00000000..4bc4e447 --- /dev/null +++ b/TIEcommerce/Sources/Filters/Models/BaseFilterCellAppearance.swift @@ -0,0 +1,52 @@ +// +// 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 BaseFilterCellAppearance: FilterCellAppearanceProtocol { + public var selectedColor: UIColor + public var selectedBgColor: UIColor + public var deselectedBgColor: UIColor + public var contentInsets: UIEdgeInsets + + init(selectedColor: UIColor, + selectedBgColor: UIColor, + deselectedBgColor: UIColor, + contentInsets: UIEdgeInsets) { + + self.selectedColor = selectedColor + self.selectedBgColor = selectedBgColor + self.deselectedBgColor = deselectedBgColor + self.contentInsets = contentInsets + } +} + +// MARK: - Default appearance + +public extension BaseFilterCellAppearance { + static var defaultFilterCellAppearance: BaseFilterCellAppearance { + .init(selectedColor: .systemGreen, + selectedBgColor: .white, + deselectedBgColor: .lightGray, + contentInsets: .init(top: 4, left: 8, bottom: 4, right: 8)) + } +} \ No newline at end of file diff --git a/TIEcommerce/Sources/Filters/Models/DefaultFilterPropertyValue.swift b/TIEcommerce/Sources/Filters/Models/DefaultFilterPropertyValue.swift index b5a23494..7f48ee24 100644 --- a/TIEcommerce/Sources/Filters/Models/DefaultFilterPropertyValue.swift +++ b/TIEcommerce/Sources/Filters/Models/DefaultFilterPropertyValue.swift @@ -27,18 +27,9 @@ public struct DefaultFilterPropertyValue: FilterPropertyValueRepresenter, Identi public let id: String public let title: String public let excludingProperties: [String]? + public let cellAppearance: FilterCellAppearanceProtocol public var isSelected: Bool - - public func convertToViewModel() -> FilterCellViewModelRepresentable { - DefaultFilterCellViewModel(id: id, - title: title, - selectedColor: .green, - selectedBgColor: .white, - deselectedBgColor: .lightGray, - insets: .init(top: 4, left: 8, bottom: 4, right: 8), - isSelected: isSelected) - } } public extension DefaultFilterPropertyValue { @@ -46,11 +37,16 @@ public extension DefaultFilterPropertyValue { self.id = id self.title = title self.excludingProperties = excludingProperties + self.cellAppearance = BaseFilterCellAppearance.defaultFilterCellAppearance self.isSelected = false } } extension DefaultFilterPropertyValue: Hashable { + public static func == (lhs: DefaultFilterPropertyValue, rhs: DefaultFilterPropertyValue) -> Bool { + lhs.id == rhs.id + } + public func hash(into hasher: inout Hasher) { hasher.combine(id) } diff --git a/TIEcommerce/Sources/Filters/Models/FilterCellViewModelProtocol.swift b/TIEcommerce/Sources/Filters/Models/FilterCellViewModelProtocol.swift new file mode 100644 index 00000000..431598d2 --- /dev/null +++ b/TIEcommerce/Sources/Filters/Models/FilterCellViewModelProtocol.swift @@ -0,0 +1,28 @@ +// +// 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 FilterCellViewModelProtocol { + var id: String { get set } + var title: String { get set } + var appearance: FilterCellAppearanceProtocol { get set } + var isSelected: Bool { get set } +} \ No newline at end of file diff --git a/TIEcommerce/Sources/Filters/Protocols/FilterCellAppearanceProtocol.swift b/TIEcommerce/Sources/Filters/Protocols/FilterCellAppearanceProtocol.swift new file mode 100644 index 00000000..282a9246 --- /dev/null +++ b/TIEcommerce/Sources/Filters/Protocols/FilterCellAppearanceProtocol.swift @@ -0,0 +1,30 @@ +// +// 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 FilterCellAppearanceProtocol { + var selectedColor: UIColor { get set } + var selectedBgColor: UIColor { get set } + var deselectedBgColor: UIColor { get set } + var contentInsets: UIEdgeInsets { get set } +} \ No newline at end of file diff --git a/TIEcommerce/Sources/Filters/Protocols/FilterPropertyValueRepresenter.swift b/TIEcommerce/Sources/Filters/Protocols/FilterPropertyValueRepresenter.swift index 0e8fda13..96715e7f 100644 --- a/TIEcommerce/Sources/Filters/Protocols/FilterPropertyValueRepresenter.swift +++ b/TIEcommerce/Sources/Filters/Protocols/FilterPropertyValueRepresenter.swift @@ -20,14 +20,9 @@ // THE SOFTWARE. // -public protocol FilterPropertyValueRepresenter: FilterCellViewModelConvertable { +public protocol FilterPropertyValueRepresenter { var id: String { get } var excludingProperties: [String]? { get } + var cellAppearance: FilterCellAppearanceProtocol { get } var isSelected: Bool { get set } } - -public protocol FilterCellViewModelRepresentable { } - -public protocol FilterCellViewModelConvertable { - func convertToViewModel() -> FilterCellViewModelRepresentable -} diff --git a/TIEcommerce/Sources/Filters/ViewModels/DefaultFilterCellViewModel.swift b/TIEcommerce/Sources/Filters/ViewModels/DefaultFilterCellViewModel.swift index d57d4fa9..74453ed0 100644 --- a/TIEcommerce/Sources/Filters/ViewModels/DefaultFilterCellViewModel.swift +++ b/TIEcommerce/Sources/Filters/ViewModels/DefaultFilterCellViewModel.swift @@ -20,22 +20,18 @@ // THE SOFTWARE. // -import UIKit +public struct DefaultFilterCellViewModel: FilterCellViewModelProtocol, Hashable { -public protocol FilterCellViewModelProtocol: FilterCellViewModelRepresentable { - var id: String { get set } - var title: String { get set } - var selectedColor: UIColor { get set } - var insets: UIEdgeInsets { get set } - var isSelected: Bool { get set } -} - -public struct DefaultFilterCellViewModel: FilterCellViewModelProtocol, FilterCellViewModelRepresentable { 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 appearance: FilterCellAppearanceProtocol public var isSelected: Bool + + public static func == (lhs: DefaultFilterCellViewModel, rhs: DefaultFilterCellViewModel) -> Bool { + lhs.id == rhs.id + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + } } diff --git a/TIEcommerce/Sources/Filters/ViewModels/DefaultFiltersViewModel.swift b/TIEcommerce/Sources/Filters/ViewModels/DefaultFiltersViewModel.swift index dd80d5a5..901e0a71 100644 --- a/TIEcommerce/Sources/Filters/ViewModels/DefaultFiltersViewModel.swift +++ b/TIEcommerce/Sources/Filters/ViewModels/DefaultFiltersViewModel.swift @@ -20,28 +20,49 @@ // THE SOFTWARE. // +import TIUIKitCore import UIKit open class DefaultFiltersViewModel: NSObject, FiltersViewModelProtocol { - public var filters: [DefaultFilterPropertyValue] - public var selectedFilters: Set = [] - public var cellsViewModels: [FilterCellViewModelProtocol] + private var cellsViewModels: [FilterCellViewModelProtocol] + + public var filters: [DefaultFilterPropertyValue] { + didSet { + rebuildCellsViewModels() + filtersCollection?.updateView() + } + } + + public var selectedFilters: Set = [] { + didSet { + reselectFilters() + rebuildCellsViewModels() + filtersCollection?.updateView() + } + } + + public weak var filtersCollection: UpdatableView? public init(filters: [DefaultFilterPropertyValue]) { self.filters = filters - self.cellsViewModels = filters.compactMap { $0.convertToViewModel() as? FilterCellViewModelProtocol } + self.cellsViewModels = filters.compactMap { + DefaultFilterCellViewModel(id: $0.id, + title: $0.title, + appearance: $0.cellAppearance, + isSelected: $0.isSelected) + } } - // MARK: - Public methods + // MARK: - Open methods open func filterDidSelected(atIndexPath indexPath: IndexPath) -> [Change] { let (selected, deselected) = toggleFilter(atIndexPath: indexPath) let changedFilters = filters .enumerated() - .filter { isFilterChanged($0.element, filters: selected) || isFilterChanged($0.element, filters: deselected) } + .filter { isFilterInArray($0.element, filters: selected) || isFilterInArray($0.element, filters: deselected) } for (offset, element) in changedFilters { cellsViewModels[offset].isSelected = selectedFilters.contains(element) @@ -57,7 +78,30 @@ open class DefaultFiltersViewModel: NSObject, return changedItems } - public func isFilterChanged(_ filter: DefaultFilterPropertyValue, filters: [DefaultFilterPropertyValue]) -> Bool { + open func rebuildCellsViewModels() { + cellsViewModels = filters.compactMap { + DefaultFilterCellViewModel(id: $0.id, + title: $0.title, + appearance: $0.cellAppearance, + isSelected: $0.isSelected) + } + } + + // MARK: - Public methods + + public func getCellsViewModels() -> [FilterCellViewModelProtocol] { + cellsViewModels + } + + public func isFilterInArray(_ filter: DefaultFilterPropertyValue, filters: [DefaultFilterPropertyValue]) -> Bool { filters.contains(where: { $0.id == filter.id }) } + + public func reselectFilters() { + let selectedFilters = Array(selectedFilters) + + filters.enumerated().forEach { + filters[$0.offset].isSelected = isFilterInArray($0.element, filters: selectedFilters) + } + } } diff --git a/TIEcommerce/Sources/Filters/Views/BaseFiltersCollectionView.swift b/TIEcommerce/Sources/Filters/Views/BaseFiltersCollectionView.swift index 4576bb48..c7a07727 100644 --- a/TIEcommerce/Sources/Filters/Views/BaseFiltersCollectionView.swift +++ b/TIEcommerce/Sources/Filters/Views/BaseFiltersCollectionView.swift @@ -26,7 +26,8 @@ import UIKit @available(iOS 13.0, *) open class BaseFiltersCollectionView: UICollectionView, InitializableViewProtocol, - UICollectionViewDelegate where CellType.ViewModelType: Hashable { + UpdatableView, + UICollectionViewDelegate where CellType.ViewModelType: FilterCellViewModelProtocol & Hashable { public enum Section { case main @@ -85,6 +86,7 @@ open class BaseFiltersCollectionView, ConfigurableView { @@ -41,7 +41,7 @@ open class DefaultFilterCollectionCell: ContainerCollectionViewCell, self.viewModel = viewModel wrappedView.text = viewModel.title - contentInsets = viewModel.insets + contentInsets = viewModel.appearance.contentInsets setSelected(isSelected: viewModel.isSelected) } @@ -49,11 +49,13 @@ open class DefaultFilterCollectionCell: ContainerCollectionViewCell, // MARK: - Public methods open func setSelected(isSelected: Bool) { - let selectedColor = viewModel?.selectedColor ?? .green + let appearance = viewModel?.appearance + + let selectedColor = appearance?.selectedColor ?? .green wrappedView.textColor = isSelected ? selectedColor : .black if isSelected { - backgroundColor = viewModel?.selectedBgColor ?? .white + backgroundColor = appearance?.selectedBgColor ?? .white layer.borderColor = selectedColor.cgColor layer.borderWidth = 1 } else { @@ -63,6 +65,6 @@ open class DefaultFilterCollectionCell: ContainerCollectionViewCell, open func setDeselectAppearance() { layer.borderWidth = 0 - backgroundColor = viewModel?.deselectedBgColor ?? .lightGray + backgroundColor = viewModel?.appearance.deselectedBgColor ?? .lightGray } } diff --git a/TIUIKitCore/Sources/UpdatableView/UpdatableView.swift b/TIUIKitCore/Sources/UpdatableView/UpdatableView.swift new file mode 100644 index 00000000..42d376ff --- /dev/null +++ b/TIUIKitCore/Sources/UpdatableView/UpdatableView.swift @@ -0,0 +1,25 @@ +// +// 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 UpdatableView: AnyObject { + func updateView() +}