diff --git a/base-filters/README.md b/base-filters/README.md index 2e66d86..95bf577 100644 --- a/base-filters/README.md +++ b/base-filters/README.md @@ -13,16 +13,18 @@ ### Как использовать ``` kotlin val selectorView = ListSelectionView>(context) - .setItems(navArgs.items) - .addItemDecoration((TopDividerItemDecoration( - context = requireContext(), - drawableId = R.drawable.list_divider_1dp, - startMargin = START_MARGIN_DIVIDER_DP.px - ))) - .withSelectionType(ListSelectionView.SelectionType.SINGLE_SELECT) - .onResultListener { items -> - viewModel.dispatchAction(SelectItemAction.SelectItem(items)) } - .build() + .Builder() + .setItems(navArgs.items) + .addItemDecoration((TopDividerItemDecoration( + context = requireContext(), + drawableId = R.drawable.list_divider_1dp, + startMargin = START_MARGIN_DIVIDER_DP.px + ))) + .withSelectionType(ListSelectionView.SelectionType.SINGLE_SELECT) + .onResultListener { items -> + viewModel.dispatchAction(SelectItemAction.SelectItem(items)) + } + .build() ``` ### Конфигурации * при создании `ListSelectionView` необходимо передлать `ItemType` - класс модели данных в списке, `HolderType` - класс viewHolder-а в recyclerView. @@ -35,15 +37,16 @@ val selectorView = ListSelectionView)` используется для определения кастомного viewHolder для списка с недефолтной разметкой. ``` kotlin val selectorView = ListSelectionView(context) - .showInHolder { parent, clickListener, selectionType -> - TestItemViewHolder( - binding = TestSelectionItemBinding.inflate(LayoutInflater.from(parent.context), parent, false), - onItemSelectAction = clickListener, - selectionType = selectionType - ) - } - ... - .build() + .Builder() + .showInHolder { parent, clickListener, selectionType -> + TestItemViewHolder( + binding = TestSelectionItemBinding.inflate(LayoutInflater.from(parent.context), parent, false), + onItemSelectAction = clickListener, + selectionType = selectionType + ) + } + ... + .build() ``` * колбэк `onSelectedItemsListener(listener: OnSelectedItemsListener)` можно использовать для получения списка всех элементов `ItemType` после каждого выбора * колбэк `onSelectedItemListener(listener: OnSelectedItemListener)` можно использовать для получения элемента списка `ItemType`, по которому произошел клик @@ -80,8 +83,9 @@ val selectorView = ListSelectionView(cont ``` kotlin val newContext = ContextThemeWrapper(requireContext(), R.style.Theme_Custom_FilterSelection) val selectorView = ListSelectionView(newContext) - ... - .build() + .Builder() + ... + .build() ``` ## 2. Выбор одного/нескольких значений из перечня тегов @@ -92,14 +96,14 @@ val selectorView = ListSelectionView(newContext) ### Как использовать ``` kotlin binding.tagItemLayout - .setSpacing(16) - .setSelectionType(SelectionType.MULTI_SELECT) // по умолчанию - .isSingleLine(false) // по умолчанию - .onPropertySelectedAction { filterProperty: FilterProperty -> - //Do something - } - .build(getFilterItem()) + .Builder(getFilterItem()) + .setSpacing(16) + .setSelectionType(SelectionType.MULTI_SELECT) // по умолчанию + .isSingleLine(false) // по умолчанию + .onPropertySelectedAction { filterProperty: FilterProperty -> + //Do something } + .build() ``` ### Конфигурации * метод `setSelectionType(SelectionType)` конфигурирует тип выбора: @@ -109,9 +113,10 @@ binding.tagItemLayout * `setTagLayout(Int)` устанавливает разметку для тега. Если не задано - то используется дефолтная разметка `layout_default_tag.xml` * `setMaxTagCount(Int)` позволяет ограничить количество отображаемых тегов. По умолчанию ограничения нет. * `setMoreTagLayout(Int, String)` устанавливает разметку для тега, который отображается для дополнительного тега. Если не указана - то тег не будет создан -* `setSpacing(Int)`, `setSpacingHorizontal(Int`) и мsetSpacingVertical(Int)` можно использовать для настройки расстояния между тегами. По умолчанию - 0 +* `setSpacing(Int)`, `setSpacingHorizontal(Int)` и `setSpacingVertical(Int)` можно использовать для настройки расстояния между тегами. По умолчанию - 0 * `onMoreValuesAction(FilterMoreAction)` и `onPropertySelectedAction(PropertySelectedAction)` используются для передачи колбэков на клик по тегу типа "Еще" и обычного тега соответственно -* после вызова конфигурационных методов обязательно необходимо вызать метод `build(FilterItem)` +* после вызова конфигурационных методов обязательно необходимо вызать метод `build()` +* в Builder необходимо передать объект `filterItem: FilterItem` ## 3. Выбор минимального и максимального значения из диапозона diff --git a/base-filters/build.gradle b/base-filters/build.gradle index a8904a1..6c57558 100644 --- a/base-filters/build.gradle +++ b/base-filters/build.gradle @@ -19,7 +19,11 @@ dependencies { implementation("androidx.appcompat:appcompat") implementation("com.google.android.material:material") - implementation("androidx.constraintlayout:constraintlayout:2.2.0-alpha03") + implementation("androidx.constraintlayout:constraintlayout") { + version { + require '2.0.0' + } + } constraints { implementation("androidx.appcompat:appcompat") { diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/ListSelectionView.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/ListSelectionView.kt index 03c1413..bdd5646 100644 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/ListSelectionView.kt +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/ListSelectionView.kt @@ -14,8 +14,8 @@ import ru.touchin.roboswag.base_filters.select_list_item.adapter.SheetSelectionA import ru.touchin.roboswag.base_filters.select_list_item.model.BaseSelectionItem import ru.touchin.roboswag.base_filters.SelectionType -typealias OnSelectedItemListener = (item: ItemType) -> Unit -typealias OnSelectedItemsListener = (items: List) -> Unit +private typealias OnSelectedItemListener = (item: ItemType) -> Unit +private typealias OnSelectedItemsListener = (items: List) -> Unit /** * Base [ListSelectionView] to use in filters screen for choosing single or multi items in list. @@ -26,7 +26,7 @@ typealias OnSelectedItemsListener = (items: List) -> Unit * @param HolderType Type of viewHolder in recyclerView. * It must implement [BaseSelectionViewHolder] abstract class. * -**/ + **/ class ListSelectionView @JvmOverloads constructor( context: Context, @@ -34,8 +34,8 @@ class ListSelectionView @JvmOverloads constructor( defStyleAttr: Int = 0, defStyleRes: Int = 0 ) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) - where ItemType: BaseSelectionItem, - HolderType: BaseSelectionViewHolder{ + where ItemType : BaseSelectionItem, + HolderType : BaseSelectionViewHolder { private var mutableItems: List = emptyList() private var selectionType = SelectionType.SINGLE_SELECT @@ -83,37 +83,40 @@ class ListSelectionView @JvmOverloads constructor( updateList() } - fun setItems(items: List) = apply { - mutableItems = items - } + inner class Builder { - fun setItems( - source: List, - mapper: (T) -> ItemType - ) = setItems(source.map { item -> mapper.invoke(item) }) + fun setItems(items: List) = apply { + mutableItems = items + } - fun showInHolder(holderFactory: HolderFactoryType) = apply { - factory = holderFactory - } + fun setItems( + source: List, + mapper: (T) -> ItemType + ) = setItems(source.map { item -> mapper.invoke(item) }) - fun addItemDecoration(itemDecoration: RecyclerView.ItemDecoration) = apply { - binding.itemsRecycler.addItemDecoration(itemDecoration) - } + fun showInHolder(holderFactory: HolderFactoryType) = apply { + factory = holderFactory + } - fun onSelectedItemListener(listener: OnSelectedItemListener) = apply { - this@ListSelectionView.onSelectedItemChanged = listener - } + fun addItemDecoration(itemDecoration: RecyclerView.ItemDecoration) = apply { + binding.itemsRecycler.addItemDecoration(itemDecoration) + } - fun onSelectedItemsListener(listener: OnSelectedItemsListener) = apply { - this@ListSelectionView.onSelectedItemsChanged = listener - } + fun onSelectedItemListener(listener: OnSelectedItemListener) = apply { + this@ListSelectionView.onSelectedItemChanged = listener + } - fun withSelectionType(type: SelectionType) = apply { - selectionType = type - } + fun onSelectedItemsListener(listener: OnSelectedItemsListener) = apply { + this@ListSelectionView.onSelectedItemsChanged = listener + } - fun build() = apply { - binding.itemsRecycler.adapter = adapter - updateList() + fun withSelectionType(type: SelectionType) = apply { + selectionType = type + } + + fun build() = this@ListSelectionView.also { + binding.itemsRecycler.adapter = adapter + updateList() + } } } diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/BaseSelectionViewHolder.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/BaseSelectionViewHolder.kt index a4b9af0..847bfc2 100644 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/BaseSelectionViewHolder.kt +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/BaseSelectionViewHolder.kt @@ -4,7 +4,7 @@ import android.view.View import androidx.recyclerview.widget.RecyclerView import ru.touchin.roboswag.base_filters.select_list_item.model.BaseSelectionItem -abstract class BaseSelectionViewHolder(val view: View) +abstract class BaseSelectionViewHolder(val view: View) : RecyclerView.ViewHolder(view) { abstract fun bind(item: ItemType) diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SelectionItemViewHolder.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SelectionItemViewHolder.kt index 59a05c2..f08dd4b 100644 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SelectionItemViewHolder.kt +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SelectionItemViewHolder.kt @@ -5,27 +5,29 @@ import ru.touchin.roboswag.base_filters.databinding.SelectionItemBinding import ru.touchin.roboswag.base_filters.select_list_item.model.BaseSelectionItem import ru.touchin.roboswag.base_filters.SelectionType -class SelectionItemViewHolder(private val binding: SelectionItemBinding, - private val onItemSelectAction: (ItemType) -> Unit, - private val selectionType: SelectionType - ) : BaseSelectionViewHolder(binding.root) { +class SelectionItemViewHolder( + private val binding: SelectionItemBinding, + private val onItemSelectAction: (ItemType) -> Unit, + private val selectionType: SelectionType +) : BaseSelectionViewHolder(binding.root) { override fun bind(item: ItemType) { - binding.run { - val checkListener = View.OnClickListener { - itemRadiobutton.isChecked = true - onItemSelectAction.invoke(item.copyWithSelection(isSelected = when (selectionType) { - SelectionType.SINGLE_SELECT -> true - else -> !item.isSelected - })) - } + binding.itemTitle.text = item.title + binding.itemRadiobutton.isChecked = item.isSelected - itemTitle.text = item.title - root.setOnClickListener(checkListener) + setupCheckListener(item) + } - itemRadiobutton.setOnClickListener(checkListener) - itemRadiobutton.isChecked = item.isSelected + private fun setupCheckListener(item: ItemType) = with(binding) { + val checkListener = View.OnClickListener { + itemRadiobutton.isChecked = true + onItemSelectAction.invoke(item.copyWithSelection(isSelected = when (selectionType) { + SelectionType.SINGLE_SELECT -> true + else -> !item.isSelected + })) } + root.setOnClickListener(checkListener) + itemRadiobutton.setOnClickListener(checkListener) } } diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt index 44df5ee..9424309 100644 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt @@ -5,11 +5,11 @@ import ru.touchin.roboswag.base_filters.select_list_item.model.BaseSelectionItem import ru.touchin.roboswag.base_filters.SelectionType import ru.touchin.roboswag.recyclerview_adapters.adapters.DelegationListAdapter -class SheetSelectionAdapter( +class SheetSelectionAdapter( onItemSelectAction: (ItemType) -> Unit, selectionType: SelectionType, factory: HolderFactoryType -): DelegationListAdapter(object : DiffUtil.ItemCallback() { +) : DelegationListAdapter(object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: BaseSelectionItem, newItem: BaseSelectionItem): Boolean = oldItem.isItemTheSame(newItem) diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/BaseSelectionItem.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/BaseSelectionItem.kt index b19d0d0..8f59990 100644 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/BaseSelectionItem.kt +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/BaseSelectionItem.kt @@ -10,5 +10,5 @@ abstract class BaseSelectionItem( abstract fun isContentTheSame(compareItem: BaseSelectionItem): Boolean - abstract fun copyWithSelection(isSelected: Boolean): ItemType + abstract fun copyWithSelection(isSelected: Boolean): ItemType } diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/tags/TagLayoutView.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/tags/TagLayoutView.kt index a81dfbc..8c169b5 100644 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/tags/TagLayoutView.kt +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/tags/TagLayoutView.kt @@ -2,16 +2,13 @@ package ru.touchin.roboswag.base_filters.tags import android.content.Context import android.util.AttributeSet -import android.view.LayoutInflater import android.view.View import android.widget.FrameLayout import android.widget.TextView import androidx.annotation.LayoutRes -import androidx.core.view.isVisible import com.google.android.material.chip.ChipGroup import ru.touchin.roboswag.base_filters.R import ru.touchin.roboswag.base_filters.SelectionType -import ru.touchin.roboswag.base_filters.databinding.TagSelectionLayoutBinding import ru.touchin.roboswag.base_filters.tags.model.FilterItem import ru.touchin.roboswag.base_filters.tags.model.FilterProperty import ru.touchin.roboswag.components.utils.UiUtils @@ -27,10 +24,9 @@ class TagLayoutView @JvmOverloads constructor( defStyleAttr: Int = 0 ) : FrameLayout(context, attrs, defStyleAttr) { - private val binding = TagSelectionLayoutBinding.inflate(LayoutInflater.from(context), this) private var filterItem: FilterItem by Delegates.notNull() - private var tagsContainer: ChipGroup = binding.multiLineTagGroup + private var tagsContainer: ChipGroup by Delegates.notNull() private var propertySelectedAction: PropertySelectedAction? = null private var moreValuesAction: FilterMoreAction? = null @@ -44,82 +40,16 @@ class TagLayoutView @JvmOverloads constructor( private var tagLayout: Int = R.layout.layout_default_tag private var moreTagText: String = "" - private var maxTagCount: Int? = null + private var maxTagCount = Int.MAX_VALUE @LayoutRes - private var moreTagLayout: Int? = null + private var moreTagLayout: Int = tagLayout - fun onMoreValuesAction(action: FilterMoreAction) = apply { - moreValuesAction = action - } - - fun onPropertySelectedAction(action: PropertySelectedAction) = apply { - propertySelectedAction = action - } - - fun setMaxTagCount(count: Int) = apply { - maxTagCount = count - } - - fun setSpacingHorizontal(horizontalSpacingDp: Int) = apply { - tagSpacingHorizontalDp = horizontalSpacingDp - } - - fun setSpacingVertical(verticalSpacingDp: Int) = apply { - tagSpacingVerticalDp = verticalSpacingDp - } - - fun setSpacing(value: Int) = apply { - tagSpacingHorizontalDp = value - tagSpacingVerticalDp = value - } - - fun setSelectionType(type: SelectionType) = apply { - selectionType = type - } - - fun isSingleLine(value: Boolean) = apply { - isSingleLine = value - } - - fun setTagLayout(@LayoutRes layoutId: Int) = apply { - tagLayout = layoutId - } - - fun setMoreTagLayout(@LayoutRes layoutId: Int, text: String) = apply { - moreTagLayout = layoutId - moreTagText = text - } - - fun build(filterItem: FilterItem) { - this.filterItem = filterItem - tagsContainer = getTagView(isSingleLine) - - with(tagsContainer) { - removeAllViews() - - this.isSingleLine = isSingleLine - - chipSpacingHorizontal = tagSpacingHorizontalDp.px - chipSpacingVertical = tagSpacingVerticalDp.px - - val properties = maxTagCount - ?.let { count -> filterItem.properties.take(count) } - ?: filterItem.properties - properties.forEach { property -> - addView(createTag(property)) - } - - if (maxTagCount != null && filterItem.properties.size > maxTagCount!!) { - createMoreChip(filterItem)?.let { addView(it) } - } - } - } - - private fun getTagView(isSingleLine: Boolean): ChipGroup { - binding.lineTagContainer.isVisible = isSingleLine - binding.multiLineTagGroup.isVisible = !isSingleLine - return if (isSingleLine) binding.singleLineTagGroup else binding.multiLineTagGroup + private fun inflateAndGetChipGroup(isSingleLine: Boolean): ChipGroup { + val layoutId = if (isSingleLine) R.layout.layout_single_line_tag_group else R.layout.layout_multi_line_tag_group + return UiUtils.inflate(layoutId, this) + .also { addView(it) } + .findViewById(R.id.tag_group) } private fun createTag(property: FilterProperty): TagView = @@ -138,12 +68,11 @@ class TagLayoutView @JvmOverloads constructor( } } ?: throw IllegalArgumentException("Layout for tag must be extended from TagView") - private fun createMoreChip(filter: FilterItem): View? = moreTagLayout?.let { - (UiUtils.inflate(it, this) as? TextView)?.apply { - text = moreTagText - setOnClickListener { moreValuesAction?.invoke(filter) } - } - } + private fun createMoreChip(filter: FilterItem): View? = + (UiUtils.inflate(moreTagLayout, this) as? TextView)?.apply { + text = moreTagText + setOnClickListener { moreValuesAction?.invoke(filter) } + } private fun clearCheck(selectedId: Int) { for (i in 0 until tagsContainer.childCount) { @@ -164,4 +93,71 @@ class TagLayoutView @JvmOverloads constructor( } } } + + inner class Builder(private val filterItem: FilterItem) { + + fun onMoreValuesAction(action: FilterMoreAction) = apply { + moreValuesAction = action + } + + fun onPropertySelectedAction(action: PropertySelectedAction) = apply { + propertySelectedAction = action + } + + fun setMaxTagCount(count: Int) = apply { + maxTagCount = count + } + + fun setSpacingHorizontal(horizontalSpacingDp: Int) = apply { + tagSpacingHorizontalDp = horizontalSpacingDp + } + + fun setSpacingVertical(verticalSpacingDp: Int) = apply { + tagSpacingVerticalDp = verticalSpacingDp + } + + fun setSpacing(value: Int) = apply { + tagSpacingHorizontalDp = value + tagSpacingVerticalDp = value + } + + fun setSelectionType(type: SelectionType) = apply { + selectionType = type + } + + fun isSingleLine(value: Boolean) = apply { + isSingleLine = value + } + + fun setTagLayout(@LayoutRes layoutId: Int) = apply { + tagLayout = layoutId + } + + fun setMoreTagLayout(@LayoutRes layoutId: Int, text: String) = apply { + moreTagLayout = layoutId + moreTagText = text + } + + fun build() { + this@TagLayoutView.filterItem = filterItem + tagsContainer = inflateAndGetChipGroup(isSingleLine) + + with(tagsContainer) { + removeAllViews() + + this.isSingleLine = isSingleLine + + chipSpacingHorizontal = tagSpacingHorizontalDp.px + chipSpacingVertical = tagSpacingVerticalDp.px + + filterItem.properties.take(maxTagCount).forEach { property -> + addView(createTag(property)) + } + + if (filterItem.properties.size > maxTagCount) { + createMoreChip(filterItem)?.let { addView(it) } + } + } + } + } } diff --git a/base-filters/src/main/res/layout/layout_multi_line_tag_group.xml b/base-filters/src/main/res/layout/layout_multi_line_tag_group.xml new file mode 100644 index 0000000..27118fd --- /dev/null +++ b/base-filters/src/main/res/layout/layout_multi_line_tag_group.xml @@ -0,0 +1,11 @@ + + diff --git a/base-filters/src/main/res/layout/layout_single_line_tag_group.xml b/base-filters/src/main/res/layout/layout_single_line_tag_group.xml new file mode 100644 index 0000000..3a32ef3 --- /dev/null +++ b/base-filters/src/main/res/layout/layout_single_line_tag_group.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/base-filters/src/main/res/layout/tag_selection_layout.xml b/base-filters/src/main/res/layout/tag_selection_layout.xml deleted file mode 100644 index 1701e7c..0000000 --- a/base-filters/src/main/res/layout/tag_selection_layout.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - diff --git a/base-filters/src/main/res/values/styles.xml b/base-filters/src/main/res/values/styles.xml index 66d37ff..8565d5b 100644 --- a/base-filters/src/main/res/values/styles.xml +++ b/base-filters/src/main/res/values/styles.xml @@ -7,10 +7,7 @@