From 44d939b6acbdf832e345c99641e63a2a8755f6da Mon Sep 17 00:00:00 2001 From: vitalir2 Date: Sun, 14 May 2023 13:34:21 +0300 Subject: [PATCH 1/2] RENTING-65: Base filter classes --- .../filters/DefaultFiltersComponent.kt | 15 +++++- .../app/feature/filters/FiltersComponent.kt | 9 +++- .../app/feature/filters/FiltersStore.kt | 51 +++++++++++++++++++ .../app/feature/filters/PropertyFilter.kt | 49 ++++++++++++++++++ .../feature/filters/PropertyFilterGroup.kt | 9 ++++ .../feature/filters/StubFiltersComponent.kt | 14 ++++- .../renting/app/feature/filters/UiFilter.kt | 28 ++++++++++ .../app/feature/filters/UiFilterGroup.kt | 9 ++++ 8 files changed, 181 insertions(+), 3 deletions(-) create mode 100644 shared/src/commonMain/kotlin/com/renting/app/feature/filters/FiltersStore.kt create mode 100644 shared/src/commonMain/kotlin/com/renting/app/feature/filters/PropertyFilter.kt create mode 100644 shared/src/commonMain/kotlin/com/renting/app/feature/filters/PropertyFilterGroup.kt create mode 100644 shared/src/commonMain/kotlin/com/renting/app/feature/filters/UiFilter.kt create mode 100644 shared/src/commonMain/kotlin/com/renting/app/feature/filters/UiFilterGroup.kt diff --git a/shared/src/commonMain/kotlin/com/renting/app/feature/filters/DefaultFiltersComponent.kt b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/DefaultFiltersComponent.kt index afff9a0..15b45b8 100644 --- a/shared/src/commonMain/kotlin/com/renting/app/feature/filters/DefaultFiltersComponent.kt +++ b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/DefaultFiltersComponent.kt @@ -4,4 +4,17 @@ import com.arkivanov.decompose.ComponentContext internal class DefaultFiltersComponent( componentContext: ComponentContext, -) : FiltersComponent, ComponentContext by componentContext +) : FiltersComponent, ComponentContext by componentContext { + + override fun onFilterClick(id: String) { + // TODO add store + } + + override fun onRangeFilterChange(id: String, value: IntRange) { + // TODO add store + } + + override fun onTextFilterChange(id: String, value: String) { + // TODO add store + } +} diff --git a/shared/src/commonMain/kotlin/com/renting/app/feature/filters/FiltersComponent.kt b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/FiltersComponent.kt index 1aeec2a..f94df8d 100644 --- a/shared/src/commonMain/kotlin/com/renting/app/feature/filters/FiltersComponent.kt +++ b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/FiltersComponent.kt @@ -1,3 +1,10 @@ package com.renting.app.feature.filters -interface FiltersComponent +interface FiltersComponent { + + fun onFilterClick(id: String) + + fun onRangeFilterChange(id: String, value: IntRange) + + fun onTextFilterChange(id: String, value: String) +} diff --git a/shared/src/commonMain/kotlin/com/renting/app/feature/filters/FiltersStore.kt b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/FiltersStore.kt new file mode 100644 index 0000000..107d88b --- /dev/null +++ b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/FiltersStore.kt @@ -0,0 +1,51 @@ +package com.renting.app.feature.filters + +import com.renting.app.feature.property.PropertySnippet +import com.renting.app.feature.property.PropertyType + +interface FiltersStore { + + data class State( + val filterGroups: List = createFilters(), + ) +} + +private fun createFilters(): List = buildList { + val category = PropertyFilterGroup( + name = "Category", + filter = object : ToggleablePropertyFilter( + toggles = listOf( + Toggle(name = "House", value = PropertyType.FAMILY_HOUSE), + Toggle(name = "Apartment", value = PropertyType.APARTMENT), + Toggle(name = "Land", value = PropertyType.LAND), + ), + ) { + override val predicate: ( + PropertyType, + snippet: PropertySnippet, + ) -> Boolean = { type, snippet -> type == snippet.type } + } + ) + add(category) + + val price = PropertyFilterGroup( + name = "Price Range", + filter = PricePropertyFilter(), + ) + add(price) + + val location = PropertyFilterGroup( + name = "Location", + filter = PropertyLocationSelector( + values = listOf( + "Moscow", + "Saint Petersburg", + "Yekaterinburg", + "Novosibirsk", + "Kazan", + ), + selectedValue = "Moscow", + ), + ) + add(location) +} diff --git a/shared/src/commonMain/kotlin/com/renting/app/feature/filters/PropertyFilter.kt b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/PropertyFilter.kt new file mode 100644 index 0000000..fbac44f --- /dev/null +++ b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/PropertyFilter.kt @@ -0,0 +1,49 @@ +package com.renting.app.feature.filters + +import com.renting.app.feature.property.PropertySnippet + +interface PropertyFilter { + + fun isIncluded(snippet: PropertySnippet): Boolean +} + +abstract class ToggleablePropertyFilter( + val toggles: List>, + val activeToggle: Toggle? = null, +) : PropertyFilter { + + abstract val predicate: (T, snippet: PropertySnippet) -> Boolean + + override fun isIncluded(snippet: PropertySnippet): Boolean { + return if (activeToggle == null) { + true + } else { + predicate(activeToggle.value, snippet) + } + } + + data class Toggle(val name: String, val value: T) +} + +data class PricePropertyFilter( + val range: IntRange = DEFAULT_RANGE, +) : PropertyFilter { + + override fun isIncluded(snippet: PropertySnippet): Boolean { + return snippet.price in range + } + + companion object { + val DEFAULT_RANGE = 0..100 + } +} + +data class PropertyLocationSelector( + val values: List, + val selectedValue: String, +) : PropertyFilter { + + override fun isIncluded(snippet: PropertySnippet): Boolean { + return selectedValue in snippet.location + } +} diff --git a/shared/src/commonMain/kotlin/com/renting/app/feature/filters/PropertyFilterGroup.kt b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/PropertyFilterGroup.kt new file mode 100644 index 0000000..527fe65 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/PropertyFilterGroup.kt @@ -0,0 +1,9 @@ +package com.renting.app.feature.filters + +data class PropertyFilterGroup( + val name: String, + val filters: List, +) { + + constructor(name: String, filter: PropertyFilter) : this(name, listOf(filter)) +} diff --git a/shared/src/commonMain/kotlin/com/renting/app/feature/filters/StubFiltersComponent.kt b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/StubFiltersComponent.kt index 19f60ee..9bf71a1 100644 --- a/shared/src/commonMain/kotlin/com/renting/app/feature/filters/StubFiltersComponent.kt +++ b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/StubFiltersComponent.kt @@ -1,3 +1,15 @@ package com.renting.app.feature.filters -class StubFiltersComponent : FiltersComponent +class StubFiltersComponent : FiltersComponent { + override fun onFilterClick(id: String) { + // Nothing to do + } + + override fun onRangeFilterChange(id: String, value: IntRange) { + // Nothing to do + } + + override fun onTextFilterChange(id: String, value: String) { + // Nothing to do + } +} diff --git a/shared/src/commonMain/kotlin/com/renting/app/feature/filters/UiFilter.kt b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/UiFilter.kt new file mode 100644 index 0000000..b152a67 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/UiFilter.kt @@ -0,0 +1,28 @@ +package com.renting.app.feature.filters + +interface UiFilter { + + val id: String +} + +data class ToggleableFilter( + override val id: String, + val toggles: List, + val activeToggle: Toggle?, +) : UiFilter { + + data class Toggle( + val name: String, + ) +} + +data class SlideFilter( + override val id: String, + val range: IntRange, +) : UiFilter + +data class TextSelectorFilter( + override val id: String, + val availableValues: List, + val value: String, +) : UiFilter diff --git a/shared/src/commonMain/kotlin/com/renting/app/feature/filters/UiFilterGroup.kt b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/UiFilterGroup.kt new file mode 100644 index 0000000..41acf74 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/UiFilterGroup.kt @@ -0,0 +1,9 @@ +package com.renting.app.feature.filters + +data class UiFilterGroup( + val name: String, + val filters: List, +) { + + constructor(name: String, filter: UiFilter) : this(name, listOf(filter)) +} From a5c73fc9e6ccd0fa4afc6273758f65876bc4956e Mon Sep 17 00:00:00 2001 From: vitalir2 Date: Mon, 17 Jul 2023 21:11:58 +0300 Subject: [PATCH 2/2] RENTING-65: Filters Logic, base --- .../app/feature/filters/FiltersStore.kt | 45 +++++--- .../feature/filters/FiltersStoreFactory.kt | 106 ++++++++++++++++++ .../app/feature/filters/PropertyFilter.kt | 38 ++++++- 3 files changed, 171 insertions(+), 18 deletions(-) create mode 100644 shared/src/commonMain/kotlin/com/renting/app/feature/filters/FiltersStoreFactory.kt diff --git a/shared/src/commonMain/kotlin/com/renting/app/feature/filters/FiltersStore.kt b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/FiltersStore.kt index 107d88b..cde970f 100644 --- a/shared/src/commonMain/kotlin/com/renting/app/feature/filters/FiltersStore.kt +++ b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/FiltersStore.kt @@ -1,9 +1,27 @@ package com.renting.app.feature.filters -import com.renting.app.feature.property.PropertySnippet +import com.arkivanov.mvikotlin.core.store.Store +import com.renting.app.feature.filters.FiltersStore.Intent +import com.renting.app.feature.filters.FiltersStore.State import com.renting.app.feature.property.PropertyType -interface FiltersStore { +interface FiltersStore : Store { + + sealed interface Intent { + data class SelectToggle( + val id: String, + val name: String, + ) : Intent + data class ChangeRangeFilter( + val id: String, + val range: IntRange, + ) : Intent + + data class SelectFilterValue( + val id: String, + val value: String, + ) : Intent + } data class State( val filterGroups: List = createFilters(), @@ -13,30 +31,29 @@ interface FiltersStore { private fun createFilters(): List = buildList { val category = PropertyFilterGroup( name = "Category", - filter = object : ToggleablePropertyFilter( + filter = PropertyTypeFilter( + id = "PROPERTY_TYPE", toggles = listOf( - Toggle(name = "House", value = PropertyType.FAMILY_HOUSE), - Toggle(name = "Apartment", value = PropertyType.APARTMENT), - Toggle(name = "Land", value = PropertyType.LAND), + SelectionPropertyFilter.Toggle(name = "House", value = PropertyType.FAMILY_HOUSE), + SelectionPropertyFilter.Toggle(name = "Apartment", value = PropertyType.APARTMENT), + SelectionPropertyFilter.Toggle(name = "Land", value = PropertyType.LAND), ), - ) { - override val predicate: ( - PropertyType, - snippet: PropertySnippet, - ) -> Boolean = { type, snippet -> type == snippet.type } - } + ) ) add(category) val price = PropertyFilterGroup( name = "Price Range", - filter = PricePropertyFilter(), + filter = PricePropertyFilter( + id = "PRICE" + ), ) add(price) val location = PropertyFilterGroup( name = "Location", - filter = PropertyLocationSelector( + filter = PropertyLocationChooser( + id = "PROPERTY_LOCATION", values = listOf( "Moscow", "Saint Petersburg", diff --git a/shared/src/commonMain/kotlin/com/renting/app/feature/filters/FiltersStoreFactory.kt b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/FiltersStoreFactory.kt new file mode 100644 index 0000000..567f77d --- /dev/null +++ b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/FiltersStoreFactory.kt @@ -0,0 +1,106 @@ +package com.renting.app.feature.filters + +import com.arkivanov.mvikotlin.core.store.Reducer +import com.arkivanov.mvikotlin.core.store.Store +import com.arkivanov.mvikotlin.core.store.StoreFactory +import com.arkivanov.mvikotlin.core.utils.ExperimentalMviKotlinApi +import com.arkivanov.mvikotlin.extensions.coroutines.coroutineExecutorFactory +import com.renting.app.feature.filters.FiltersStore.Intent +import com.renting.app.feature.filters.FiltersStore.State + +class FiltersStoreFactory( + private val storeFactory: StoreFactory, +) { + + private fun createReducer() = Reducer { msg -> + when (msg) { + is Msg.SelectToggle -> copy( + filterGroups = filterGroups.map { group -> + val updatedFilters = group.filters.map { filter -> + if (filter is PropertyTypeFilter && filter.id == msg.id) { + filter.setActiveToggle(msg.name) + } else { + filter + } + } + group.copy(filters = updatedFilters) + } + ) + is Msg.ChangeRangeFilter -> copy( + filterGroups = filterGroups.map { group -> + val updatedFilters = group.filters.map { filter -> + if (filter is PricePropertyFilter && filter.id == msg.id) { + filter.copy(range = msg.range) + } else { + filter + } + } + group.copy(filters = updatedFilters) + }, + ) + is Msg.SelectFilterValue -> copy( + filterGroups = filterGroups.map { group -> + val updatedFilters = group.filters.map { filter -> + if (filter is PropertyLocationChooser && filter.id == msg.id) { + filter.copy(selectedValue = msg.value) + } else { + filter + } + } + group.copy(filters = updatedFilters) + }, + ) + } + } + + @OptIn(ExperimentalMviKotlinApi::class) + fun create(): FiltersStore = + object : FiltersStore, Store by storeFactory.create( + name = "Filters", + initialState = State(), + executorFactory = coroutineExecutorFactory { + onIntent { intent -> + dispatch( + Msg.SelectToggle( + id = intent.id, + name = intent.name, + ) + ) + } + onIntent { intent -> + dispatch( + Msg.ChangeRangeFilter( + id = intent.id, + range = intent.range, + ) + ) + } + onIntent { intent -> + dispatch( + Msg.SelectFilterValue( + id = intent.id, + value = intent.value, + ) + ) + } + }, + reducer = createReducer(), + ) {} + + private sealed interface Msg { + data class SelectToggle( + val id: String, + val name: String, + ) : Msg + + data class ChangeRangeFilter( + val id: String, + val range: IntRange, + ) : Msg + + class SelectFilterValue( + val id: String, + val value: String, + ) : Msg + } +} diff --git a/shared/src/commonMain/kotlin/com/renting/app/feature/filters/PropertyFilter.kt b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/PropertyFilter.kt index fbac44f..40ef298 100644 --- a/shared/src/commonMain/kotlin/com/renting/app/feature/filters/PropertyFilter.kt +++ b/shared/src/commonMain/kotlin/com/renting/app/feature/filters/PropertyFilter.kt @@ -1,20 +1,26 @@ package com.renting.app.feature.filters import com.renting.app.feature.property.PropertySnippet +import com.renting.app.feature.property.PropertyType interface PropertyFilter { + val id: String fun isIncluded(snippet: PropertySnippet): Boolean } -abstract class ToggleablePropertyFilter( - val toggles: List>, - val activeToggle: Toggle? = null, +abstract class SelectionPropertyFilter( + override val id: String, + open val toggles: List>, + open val activeToggle: Toggle? = null, ) : PropertyFilter { abstract val predicate: (T, snippet: PropertySnippet) -> Boolean + abstract fun setActiveToggle(name: String): SelectionPropertyFilter + override fun isIncluded(snippet: PropertySnippet): Boolean { + val activeToggle = activeToggle return if (activeToggle == null) { true } else { @@ -26,6 +32,7 @@ abstract class ToggleablePropertyFilter( } data class PricePropertyFilter( + override val id: String, val range: IntRange = DEFAULT_RANGE, ) : PropertyFilter { @@ -38,7 +45,8 @@ data class PricePropertyFilter( } } -data class PropertyLocationSelector( +data class PropertyLocationChooser( + override val id: String, val values: List, val selectedValue: String, ) : PropertyFilter { @@ -47,3 +55,25 @@ data class PropertyLocationSelector( return selectedValue in snippet.location } } + +data class PropertyTypeFilter( + override val id: String, + override val toggles: List>, + override val activeToggle: Toggle? = null, +): SelectionPropertyFilter( + id = id, + toggles = toggles, + activeToggle = activeToggle, +) { + + override val predicate: ( + PropertyType, + snippet: PropertySnippet, + ) -> Boolean = { type, snippet -> type == snippet.type } + + override fun setActiveToggle(name: String): PropertyTypeFilter { + return copy( + activeToggle = toggles.firstOrNull { it.name == name }, + ) + } +}