fix: merging filters_api
This commit is contained in:
parent
d9cdfdec0f
commit
ec1fe892ad
|
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// 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 struct FilterCellStateAppearance {
|
||||
|
||||
public let borderColor: UIColor
|
||||
public let backgroundColor: UIColor
|
||||
public let fontColor: UIColor
|
||||
|
||||
public let borderWidth: CGFloat
|
||||
public let contentInsets: UIEdgeInsets
|
||||
public let cornerRadius: CGFloat
|
||||
|
||||
public init(borderColor: UIColor,
|
||||
backgroundColor: UIColor,
|
||||
fontColor: UIColor,
|
||||
borderWidth: CGFloat,
|
||||
contentInsets: UIEdgeInsets = .init(top: 4, left: 8, bottom: 4, right: 8),
|
||||
cornerRadius: CGFloat = 6) {
|
||||
|
||||
self.borderColor = borderColor
|
||||
self.backgroundColor = backgroundColor
|
||||
self.fontColor = fontColor
|
||||
self.borderWidth = borderWidth
|
||||
self.contentInsets = contentInsets
|
||||
self.cornerRadius = cornerRadius
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// 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 }
|
||||
var title: String { get }
|
||||
var isSelected: Bool { get set }
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// 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 DefaultFilterCellViewModel: FilterCellViewModelProtocol, Hashable {
|
||||
|
||||
public let id: String
|
||||
public let title: String
|
||||
public var isSelected: Bool
|
||||
|
||||
public init(id: String,
|
||||
title: String,
|
||||
isSelected: Bool) {
|
||||
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.isSelected = isSelected
|
||||
}
|
||||
|
||||
public static func == (lhs: DefaultFilterCellViewModel, rhs: DefaultFilterCellViewModel) -> Bool {
|
||||
lhs.id == rhs.id
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// 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 TIUIElements
|
||||
import UIKit
|
||||
|
||||
open class DefaultFilterCollectionCell: ContainerCollectionViewCell<UILabel>, ConfigurableView {
|
||||
|
||||
open var selectedStateAppearance: FilterCellStateAppearance {
|
||||
.defaultSelectedAppearance
|
||||
}
|
||||
|
||||
open var normalStateAppearance: FilterCellStateAppearance {
|
||||
.defaultNormalAppearance
|
||||
}
|
||||
|
||||
open override var isSelected: Bool {
|
||||
didSet {
|
||||
let appearance = isSelected ? selectedStateAppearance : normalStateAppearance
|
||||
updateAppearance(with: appearance)
|
||||
}
|
||||
}
|
||||
|
||||
open override func configureAppearance() {
|
||||
super.configureAppearance()
|
||||
|
||||
updateAppearance(with: normalStateAppearance)
|
||||
}
|
||||
|
||||
// MARK: - ConfigurableView
|
||||
|
||||
open func configure(with viewModel: DefaultFilterCellViewModel) {
|
||||
wrappedView.text = viewModel.title
|
||||
}
|
||||
|
||||
// MARK: - Open methdos
|
||||
|
||||
open func updateAppearance(with appearance: FilterCellStateAppearance) {
|
||||
contentInsets = appearance.contentInsets
|
||||
wrappedView.textColor = appearance.fontColor
|
||||
|
||||
backgroundColor = appearance.backgroundColor
|
||||
layer.borderColor = appearance.borderColor.cgColor
|
||||
layer.borderWidth = appearance.borderWidth
|
||||
layer.round(corners: .allCorners, radius: appearance.cornerRadius)
|
||||
}
|
||||
}
|
||||
|
||||
extension FilterCellStateAppearance {
|
||||
static var defaultSelectedAppearance: FilterCellStateAppearance {
|
||||
.init(borderColor: .systemGreen, backgroundColor: .white, fontColor: .systemGreen, borderWidth: 1)
|
||||
}
|
||||
|
||||
static var defaultNormalAppearance: FilterCellStateAppearance {
|
||||
|
||||
.init(borderColor: .lightGray, backgroundColor: .lightGray, fontColor: .black, borderWidth: 0)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 extension Array where Element: FilterPropertyValueRepresenter {
|
||||
|
||||
func contains(_ property: Element) -> Bool {
|
||||
contains(where: { $0.id == property.id })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
//
|
||||
// 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
|
||||
|
||||
@available(iOS 13, *)
|
||||
public extension UICollectionViewLayout {
|
||||
|
||||
static func gridLayout(_ configuration: FiltersLayoutConfiguration) -> UICollectionViewLayout {
|
||||
let item = NSCollectionLayoutItem(layoutSize: configuration.itemSize)
|
||||
|
||||
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
|
||||
heightDimension: configuration.itemSize.heightDimension)
|
||||
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
|
||||
group.interItemSpacing = .fixed(configuration.horizontalItemSpacing)
|
||||
|
||||
let section = NSCollectionLayoutSection(group: group)
|
||||
section.contentInsets = NSDirectionalEdgeInsets(insets: configuration.contentInsets)
|
||||
section.interGroupSpacing = configuration.verticalItemSpacing
|
||||
|
||||
return UICollectionViewCompositionalLayout(section: section)
|
||||
}
|
||||
|
||||
static func horizontalScrollLayout(_ configuration: FiltersLayoutConfiguration) -> UICollectionViewLayout {
|
||||
let item = NSCollectionLayoutItem(layoutSize: configuration.itemSize)
|
||||
|
||||
let groupSize = NSCollectionLayoutSize(widthDimension: configuration.itemSize.widthDimension,
|
||||
heightDimension: .fractionalHeight(1))
|
||||
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
|
||||
|
||||
let section = NSCollectionLayoutSection(group: group)
|
||||
section.contentInsets = NSDirectionalEdgeInsets(insets: configuration.contentInsets)
|
||||
section.interGroupSpacing = configuration.horizontalItemSpacing
|
||||
section.orthogonalScrollingBehavior = .continuous
|
||||
|
||||
return UICollectionViewCompositionalLayout(section: section)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
public struct DefaultFilterPropertyValue: FilterPropertyValueRepresenter {
|
||||
|
||||
public let id: String
|
||||
public let title: String
|
||||
public let excludingPropertiesIds: [String]
|
||||
|
||||
public var isSelected: Bool
|
||||
|
||||
public init(id: String, title: String, excludingPropertiesIds: [String] = [], isSelected: Bool = false) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.excludingPropertiesIds = excludingPropertiesIds
|
||||
self.isSelected = isSelected
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13, *)
|
||||
extension DefaultFilterPropertyValue: Identifiable { }
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// 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
|
||||
|
||||
@available(iOS 13, *)
|
||||
public struct FiltersLayoutConfiguration {
|
||||
|
||||
public var itemSize: NSCollectionLayoutSize
|
||||
public var horizontalItemSpacing: CGFloat
|
||||
public var verticalItemSpacing: CGFloat
|
||||
public var contentInsets: UIEdgeInsets
|
||||
|
||||
public init(itemSize: NSCollectionLayoutSize = .init(widthDimension: .estimated(36),
|
||||
heightDimension: .estimated(36)),
|
||||
horizontalItemSpacing: CGFloat = .zero,
|
||||
verticalItemSpacing: CGFloat = .zero,
|
||||
contentInsets: UIEdgeInsets) {
|
||||
|
||||
self.itemSize = itemSize
|
||||
self.horizontalItemSpacing = horizontalItemSpacing
|
||||
self.verticalItemSpacing = verticalItemSpacing
|
||||
self.contentInsets = contentInsets
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13, *)
|
||||
public extension FiltersLayoutConfiguration {
|
||||
static let horizontalScrollConfiguration = FiltersLayoutConfiguration(horizontalItemSpacing: 16,
|
||||
contentInsets: .init(top: .zero,
|
||||
left: 8,
|
||||
bottom: .zero,
|
||||
right: 8))
|
||||
|
||||
static let gridConfiguration = FiltersLayoutConfiguration(horizontalItemSpacing: 16,
|
||||
verticalItemSpacing: 16,
|
||||
contentInsets: .init(top: .zero,
|
||||
left: 8,
|
||||
bottom: .zero,
|
||||
right: 8))
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// 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 FilterPropertyValueRepresenter {
|
||||
var id: String { get }
|
||||
var excludingPropertiesIds: [String] { get }
|
||||
var isSelected: Bool { get set }
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// 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 Foundation
|
||||
import TISwiftUtils
|
||||
|
||||
public protocol FilterViewModelProtocol: AnyObject {
|
||||
|
||||
associatedtype PropertyValue: FilterPropertyValueRepresenter & Hashable
|
||||
associatedtype CellViewModelType: FilterCellViewModelProtocol & Hashable
|
||||
|
||||
var values: [PropertyValue] { get set }
|
||||
var selectedValues: [PropertyValue] { get set }
|
||||
|
||||
func filterDidSelected(atIndexPath indexPath: IndexPath) -> [(indexPath: IndexPath, viewModel: CellViewModelType)]
|
||||
func toggleProperty(atIndexPath indexPath: IndexPath) -> (selected: [PropertyValue], deselected: [PropertyValue])
|
||||
}
|
||||
|
||||
public extension FilterViewModelProtocol {
|
||||
|
||||
func toggleProperty(atIndexPath indexPath: IndexPath) -> (selected: [PropertyValue], deselected: [PropertyValue]) {
|
||||
guard let item = values[safe: indexPath.item] else { return ([], []) }
|
||||
|
||||
return toggleProperty(item)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func toggleProperty(_ value: PropertyValue) -> (selected: [PropertyValue], deselected: [PropertyValue]) {
|
||||
var valuesToDeselect = [PropertyValue]()
|
||||
var valuesToSelect = [PropertyValue]()
|
||||
let selectedValueId = selectedValues.firstIndex { selectedValue in
|
||||
selectedValue.id == value.id
|
||||
}
|
||||
|
||||
if let selectedValueId = selectedValueId {
|
||||
// Removes previously selected filter
|
||||
selectedValues.remove(at: selectedValueId)
|
||||
valuesToDeselect.append(value)
|
||||
} else {
|
||||
// Selectes unselected filter
|
||||
selectedValues.append(value)
|
||||
valuesToSelect.append(value)
|
||||
|
||||
// If the filter has filters to exclude, these filters marks as deselected
|
||||
let excludedValues = excludeValues(value)
|
||||
valuesToDeselect.append(contentsOf: excludedValues)
|
||||
}
|
||||
|
||||
return (valuesToSelect, valuesToDeselect)
|
||||
}
|
||||
|
||||
private func excludeValues(_ values: PropertyValue) -> [PropertyValue] {
|
||||
let valuesIdsToExclude = values.excludingPropertiesIds
|
||||
|
||||
guard !valuesIdsToExclude.isEmpty else {
|
||||
return []
|
||||
}
|
||||
|
||||
var excludedValues = [PropertyValue]()
|
||||
|
||||
for valuesIdToExclude in valuesIdsToExclude {
|
||||
let propertyToExclude = selectedValues.first { property in
|
||||
property.id == valuesIdToExclude
|
||||
}
|
||||
|
||||
if let propertyToExclude = propertyToExclude {
|
||||
let (_, deselected) = toggleProperty(propertyToExclude)
|
||||
excludedValues.append(contentsOf: deselected)
|
||||
}
|
||||
}
|
||||
|
||||
return excludedValues
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// 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 TISwiftUtils
|
||||
import UIKit
|
||||
|
||||
open class BaseFilterViewModel<CellViewModelType: FilterCellViewModelProtocol & Hashable,
|
||||
PropertyValue: FilterPropertyValueRepresenter & Hashable>: FilterViewModelProtocol {
|
||||
|
||||
// MARK: - FilterViewModelProtocol
|
||||
|
||||
public typealias Change = (indexPath: IndexPath, viewModel: CellViewModelType)
|
||||
|
||||
public var values: [PropertyValue] = [] {
|
||||
didSet {
|
||||
filtersCollection?.update()
|
||||
}
|
||||
}
|
||||
public var selectedValues: [PropertyValue] = [] {
|
||||
didSet {
|
||||
filtersCollection?.update()
|
||||
}
|
||||
}
|
||||
|
||||
public weak var filtersCollection: Updatable?
|
||||
|
||||
public private(set) var cellsViewModels: [CellViewModelType]
|
||||
|
||||
public init(filterPropertyValues: [PropertyValue], cellsViewModels: [CellViewModelType] = []) {
|
||||
self.values = filterPropertyValues
|
||||
self.cellsViewModels = cellsViewModels
|
||||
}
|
||||
|
||||
open func filterDidSelected(atIndexPath indexPath: IndexPath) -> [Change] {
|
||||
let (selected, deselected) = toggleProperty(atIndexPath: indexPath)
|
||||
|
||||
let changedValues = values
|
||||
.enumerated()
|
||||
.filter { selected.contains($0.element) || deselected.contains($0.element) }
|
||||
|
||||
changedValues.forEach { index, element in
|
||||
let isSelected = selectedValues.contains(element)
|
||||
|
||||
setSelectedCell(atIndex: index, isSelected: isSelected)
|
||||
setSelectedProperty(atIndex: index, isSelected: isSelected)
|
||||
}
|
||||
|
||||
let changedItems = changedValues
|
||||
.map {
|
||||
Change(indexPath: IndexPath(item: $0.offset, section: .zero),
|
||||
viewModel: cellsViewModels[$0.offset])
|
||||
}
|
||||
|
||||
return changedItems
|
||||
}
|
||||
|
||||
open func setSelectedCell(atIndex index: Int, isSelected: Bool) {
|
||||
cellsViewModels[index].isSelected = isSelected
|
||||
}
|
||||
|
||||
open func setSelectedProperty(atIndex index: Int, isSelected: Bool) {
|
||||
values[index].isSelected = isSelected
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
open class DefaultFilterViewModel: BaseFilterViewModel<DefaultFilterCellViewModel, DefaultFilterPropertyValue> {
|
||||
|
||||
public init(filterPropertyValues: [DefaultFilterPropertyValue]) {
|
||||
let cellsViewModel = filterPropertyValues.compactMap {
|
||||
DefaultFilterCellViewModel(id: $0.id,
|
||||
title: $0.title,
|
||||
isSelected: $0.isSelected)
|
||||
}
|
||||
|
||||
super.init(filterPropertyValues: filterPropertyValues, cellsViewModels: cellsViewModel)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
//
|
||||
// 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 TISwiftUtils
|
||||
import TIUIKitCore
|
||||
import UIKit
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
open class BaseFiltersCollectionView<CellType: UICollectionViewCell & ConfigurableView,
|
||||
PropertyValue: FilterPropertyValueRepresenter & Hashable>:
|
||||
UICollectionView,
|
||||
InitializableViewProtocol,
|
||||
Updatable,
|
||||
UICollectionViewDelegate where CellType.ViewModelType: FilterCellViewModelProtocol & Hashable {
|
||||
|
||||
public enum DefaultSection: String {
|
||||
case main
|
||||
}
|
||||
|
||||
public typealias DataSource = UICollectionViewDiffableDataSource<String, CellType.ViewModelType>
|
||||
public typealias Snapshot = NSDiffableDataSourceSnapshot<String, CellType.ViewModelType>
|
||||
|
||||
public var layout: UICollectionViewLayout
|
||||
|
||||
public weak var viewModel: BaseFilterViewModel<CellType.ViewModelType, PropertyValue>?
|
||||
|
||||
public lazy var collectionViewDataSource = createDataSource()
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
public init(layout: UICollectionViewLayout, viewModel: BaseFilterViewModel<CellType.ViewModelType, PropertyValue>? = nil) {
|
||||
self.layout = layout
|
||||
self.viewModel = viewModel
|
||||
|
||||
super.init(frame: .zero, collectionViewLayout: layout)
|
||||
|
||||
initializeView()
|
||||
viewDidLoad()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
open func addViews() {
|
||||
// override in subclass
|
||||
}
|
||||
|
||||
open func bindViews() {
|
||||
delegate = self
|
||||
}
|
||||
|
||||
open func configureLayout() {
|
||||
// override in subclass
|
||||
}
|
||||
|
||||
open func configureAppearance() {
|
||||
backgroundColor = .white
|
||||
}
|
||||
|
||||
open func localize() {
|
||||
// override in subclass
|
||||
}
|
||||
|
||||
open func viewDidLoad() {
|
||||
registerCell()
|
||||
|
||||
viewModel?.filtersCollection = self
|
||||
}
|
||||
|
||||
open func viewDidAppear() {
|
||||
applySnapshot()
|
||||
}
|
||||
|
||||
// MARK: - UICollectionViewDelegate
|
||||
|
||||
open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
filterDidTapped(atIndexPath: indexPath)
|
||||
}
|
||||
|
||||
open func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
|
||||
filterDidTapped(atIndexPath: indexPath)
|
||||
}
|
||||
|
||||
// MARK: - UpdatableView
|
||||
|
||||
open func update() {
|
||||
applySnapshot()
|
||||
}
|
||||
|
||||
// MARK: - Open methods
|
||||
|
||||
open func registerCell() {
|
||||
register(CellType.self, forCellWithReuseIdentifier: CellType.reuseIdentifier)
|
||||
}
|
||||
|
||||
open func filterDidTapped(atIndexPath indexPath: IndexPath) {
|
||||
guard let viewModel = viewModel else { return }
|
||||
|
||||
let changes = viewModel.filterDidSelected(atIndexPath: indexPath)
|
||||
|
||||
applyChange(changes)
|
||||
}
|
||||
|
||||
open func applySnapshot() {
|
||||
guard let viewModel = viewModel else {
|
||||
return
|
||||
}
|
||||
|
||||
var snapshot = Snapshot()
|
||||
|
||||
snapshot.appendSections([DefaultSection.main.rawValue])
|
||||
snapshot.appendItems(viewModel.cellsViewModels, toSection: DefaultSection.main.rawValue)
|
||||
|
||||
collectionViewDataSource.apply(snapshot, animatingDifferences: true)
|
||||
}
|
||||
|
||||
open func createDataSource() -> DataSource {
|
||||
let cellProvider: DataSource.CellProvider = { collectionView, indexPath, itemIdentifier in
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellType.reuseIdentifier,
|
||||
for: indexPath) as? CellType
|
||||
|
||||
cell?.configure(with: itemIdentifier)
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
return DataSource(collectionView: self, cellProvider: cellProvider)
|
||||
}
|
||||
|
||||
open func applyChange(_ changes: [BaseFilterViewModel<CellType.ViewModelType, PropertyValue>.Change]) {
|
||||
changes.forEach { change in
|
||||
guard let cell = cellForItem(at: change.indexPath) as? CellType else {
|
||||
return
|
||||
}
|
||||
|
||||
cell.configure(with: change.viewModel)
|
||||
|
||||
change.viewModel.isSelected
|
||||
? selectItem(at: change.indexPath, animated: false, scrollPosition: [])
|
||||
: deselectItem(at: change.indexPath, animated: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public typealias DefaultFiltersCollectionView = BaseFiltersCollectionView<DefaultFilterCollectionCell, DefaultFilterPropertyValue>
|
||||
Loading…
Reference in New Issue