From aad4c398e820b990857ce932755b75e728367624 Mon Sep 17 00:00:00 2001 From: AnastasiyaK97 Date: Wed, 27 Jul 2022 15:54:02 +0300 Subject: [PATCH 01/26] add base-filter module --- base-filters/.gitignore | 1 + base-filters/build.gradle | 23 +++++++++++++++++++++++ base-filters/src/main/AndroidManifest.xml | 2 ++ 3 files changed, 26 insertions(+) create mode 100644 base-filters/.gitignore create mode 100644 base-filters/build.gradle create mode 100644 base-filters/src/main/AndroidManifest.xml diff --git a/base-filters/.gitignore b/base-filters/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/base-filters/.gitignore @@ -0,0 +1 @@ +/build diff --git a/base-filters/build.gradle b/base-filters/build.gradle new file mode 100644 index 0000000..65ea18b --- /dev/null +++ b/base-filters/build.gradle @@ -0,0 +1,23 @@ +apply from: "../android-configs/lib-config.gradle" + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-stdlib") + + implementation("androidx.core:core-ktx") + + implementation("androidx.appcompat:appcompat") + + constraints { + implementation("androidx.appcompat:appcompat") { + version { + require '1.0.0' + } + } + + implementation("androidx.core:core-ktx") { + version { + require '1.0.0' + } + } + } +} diff --git a/base-filters/src/main/AndroidManifest.xml b/base-filters/src/main/AndroidManifest.xml new file mode 100644 index 0000000..47d581d --- /dev/null +++ b/base-filters/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + From eb9d4adcf20d85858ddf3049eb316068d6a369f6 Mon Sep 17 00:00:00 2001 From: AnastasiyaK97 Date: Thu, 28 Jul 2022 19:54:04 +0300 Subject: [PATCH 02/26] =?UTF-8?q?INTERNAL-300=20+=20INTERNAL-299:=20=D0=94?= =?UTF-8?q?=D0=BE=D0=B1=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BD=D0=B0=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=B0=D0=B8=D0=B2=D0=B0=D0=B5=D0=BC=D0=B0=D1=8F=20?= =?UTF-8?q?=D0=B2=D1=8C=D1=8E=20=D1=81=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80?= =?UTF-8?q?=D0=BE=D0=BC=20=D0=BE=D0=B4=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=B8?= =?UTF-8?q?=D0=BB=D0=B8=20=D0=BD=D0=B5=D1=81=D0=BA=D0=BE=D0=BB=D1=8C=D0=BA?= =?UTF-8?q?=D0=B8=D1=85=20=D0=B2=D0=B0=D1=80=D0=B8=D0=B0=D0=BD=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=B8=D0=B7=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base-filters/build.gradle | 14 ++++ base-filters/src/main/AndroidManifest.xml | 2 +- .../select_list_item/ListSelectionView.kt | 83 +++++++++++++++++++ .../adapter/SheetSelectionAdapter.kt | 24 ++++++ .../adapter/SheetSelectionDelegate.kt | 47 +++++++++++ .../model/RowSelectionItem.kt | 11 +++ .../src/main/res/layout/selection_item.xml | 29 +++++++ .../res/layout/single_selection_layout.xml | 10 +++ base-filters/src/main/res/values/attrs.xml | 9 ++ base-filters/src/main/res/values/styles.xml | 21 +++++ 10 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/ListSelectionView.kt create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/RowSelectionItem.kt create mode 100644 base-filters/src/main/res/layout/selection_item.xml create mode 100644 base-filters/src/main/res/layout/single_selection_layout.xml create mode 100644 base-filters/src/main/res/values/attrs.xml create mode 100644 base-filters/src/main/res/values/styles.xml diff --git a/base-filters/build.gradle b/base-filters/build.gradle index 65ea18b..a8904a1 100644 --- a/base-filters/build.gradle +++ b/base-filters/build.gradle @@ -1,11 +1,25 @@ apply from: "../android-configs/lib-config.gradle" +apply plugin: 'kotlin-parcelize' + +android { + buildFeatures { + viewBinding true + } +} dependencies { + implementation project(":utils") + implementation project(":recyclerview-adapters") + implementation project(":navigation-base") + implementation project(":kotlin-extensions") + implementation("org.jetbrains.kotlin:kotlin-stdlib") implementation("androidx.core:core-ktx") implementation("androidx.appcompat:appcompat") + implementation("com.google.android.material:material") + implementation("androidx.constraintlayout:constraintlayout:2.2.0-alpha03") constraints { implementation("androidx.appcompat:appcompat") { diff --git a/base-filters/src/main/AndroidManifest.xml b/base-filters/src/main/AndroidManifest.xml index 47d581d..14e5b35 100644 --- a/base-filters/src/main/AndroidManifest.xml +++ b/base-filters/src/main/AndroidManifest.xml @@ -1,2 +1,2 @@ + package="ru.touchin.roboswag.base_filters"/> 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 new file mode 100644 index 0000000..8e30fc8 --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/ListSelectionView.kt @@ -0,0 +1,83 @@ +package ru.touchin.roboswag.base_filters.select_list_item + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.FrameLayout +import androidx.recyclerview.widget.RecyclerView +import ru.touchin.roboswag.base_filters.databinding.SingleSelectionLayoutBinding +import ru.touchin.roboswag.base_filters.select_list_item.adapter.SheetSelectionAdapter +import ru.touchin.roboswag.base_filters.select_list_item.model.RowSelectionItem + +typealias OnItemSelectedListener = (item: RowSelectionItem) -> Unit +typealias OnSelectionResultListener = (items: List) -> Unit + +class ListSelectionView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) { + + private var mutableItems: List = emptyList() + private var selectionType = SelectionType.SINGLE_SELECT + + private var onItemsClickListener: OnSelectionResultListener? = null + private var onItemClickListener: OnItemSelectedListener? = null + + private val binding = SingleSelectionLayoutBinding.inflate(LayoutInflater.from(context), this, true) + + private val adapter by lazy { + SheetSelectionAdapter(onItemSelectAction = onItemSelectedListener) + } + + private val onItemSelectedListener: OnItemSelectedListener = { item -> + onItemClickListener?.invoke(item) + updateAfterSelection(item) + onItemsClickListener?.invoke(mutableItems) + } + + private fun updateList() { + adapter.submitList(mutableItems) + } + + private fun updateAfterSelection(selectedItem: RowSelectionItem) { + mutableItems = mutableItems.map { item -> + when { + item.id == selectedItem.id -> selectedItem + selectionType == SelectionType.SINGLE_SELECT -> item.copy(isSelected = false) + else -> item + } + } + updateList() + } + + fun setItems(items: List) = apply { + binding.itemsRecycler.adapter = adapter + mutableItems = items + updateList() + } + + fun setItems( + source: List, + mapper: (T) -> RowSelectionItem + ) = setItems(source.map { item -> mapper.invoke(item) }) + + fun addItemDecoration(itemDecoration: RecyclerView.ItemDecoration) = apply { + binding.itemsRecycler.addItemDecoration(itemDecoration) + } + + fun onItemClickListener(listener: OnItemSelectedListener) = apply { + this@ListSelectionView.onItemClickListener = listener + } + + fun onResultListener(listener: OnSelectionResultListener) = apply { + this@ListSelectionView.onItemsClickListener = listener + } + + fun withSelectionType(type: SelectionType) = apply { + selectionType = type + } + + enum class SelectionType { SINGLE_SELECT, MULTI_SELECT } +} 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 new file mode 100644 index 0000000..6b92b5a --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt @@ -0,0 +1,24 @@ +package ru.touchin.roboswag.base_filters.select_list_item.adapter + +import androidx.recyclerview.widget.DiffUtil +import ru.touchin.roboswag.base_filters.select_list_item.OnItemSelectedListener +import ru.touchin.roboswag.base_filters.select_list_item.model.RowSelectionItem +import ru.touchin.roboswag.recyclerview_adapters.adapters.DelegationListAdapter + +class SheetSelectionAdapter( + onItemSelectAction: OnItemSelectedListener +): DelegationListAdapter(object : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: RowSelectionItem, newItem: RowSelectionItem): Boolean = + oldItem.id == newItem.id + + override fun areContentsTheSame(oldItem: RowSelectionItem, newItem: RowSelectionItem): Boolean = + oldItem == newItem + +}) { + + init { + addDelegate(SheetSelectionDelegate(onItemSelectAction)) + } + +} diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt new file mode 100644 index 0000000..4dbfa35 --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt @@ -0,0 +1,47 @@ +package ru.touchin.roboswag.base_filters.select_list_item.adapter + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import ru.touchin.roboswag.base_filters.databinding.SelectionItemBinding +import ru.touchin.roboswag.base_filters.select_list_item.OnItemSelectedListener +import ru.touchin.roboswag.base_filters.select_list_item.model.RowSelectionItem +import ru.touchin.roboswag.recyclerview_adapters.adapters.ItemAdapterDelegate + +class SheetSelectionDelegate( + private val onItemSelectAction: OnItemSelectedListener +) : ItemAdapterDelegate() { + + override fun onCreateViewHolder(parent: ViewGroup): SelectionItemViewHolder = SelectionItemViewHolder( + binding = SelectionItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder( + holder: SelectionItemViewHolder, + item: RowSelectionItem, + adapterPosition: Int, + collectionPosition: Int, + payloads: MutableList + ) = holder.bind(item) + + inner class SelectionItemViewHolder(val binding: SelectionItemBinding) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: RowSelectionItem) { + binding.run { + val checkListener = View.OnClickListener { + itemRadiobutton.isChecked = true + onItemSelectAction.invoke(item.copy(isSelected = !item.isSelected)) + } + + itemTitle.text = item.title + root.setOnClickListener(checkListener) + + itemRadiobutton.setOnClickListener(checkListener) + itemRadiobutton.isChecked = item.isSelected + } + } + + } + +} diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/RowSelectionItem.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/RowSelectionItem.kt new file mode 100644 index 0000000..007eb69 --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/RowSelectionItem.kt @@ -0,0 +1,11 @@ +package ru.touchin.roboswag.base_filters.select_list_item.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class RowSelectionItem( + val id: Int, + val title: String, + val isSelected: Boolean = false +): Parcelable diff --git a/base-filters/src/main/res/layout/selection_item.xml b/base-filters/src/main/res/layout/selection_item.xml new file mode 100644 index 0000000..72bb7eb --- /dev/null +++ b/base-filters/src/main/res/layout/selection_item.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/base-filters/src/main/res/layout/single_selection_layout.xml b/base-filters/src/main/res/layout/single_selection_layout.xml new file mode 100644 index 0000000..2db23f9 --- /dev/null +++ b/base-filters/src/main/res/layout/single_selection_layout.xml @@ -0,0 +1,10 @@ + + diff --git a/base-filters/src/main/res/values/attrs.xml b/base-filters/src/main/res/values/attrs.xml new file mode 100644 index 0000000..bd6618a --- /dev/null +++ b/base-filters/src/main/res/values/attrs.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/base-filters/src/main/res/values/styles.xml b/base-filters/src/main/res/values/styles.xml new file mode 100644 index 0000000..3520002 --- /dev/null +++ b/base-filters/src/main/res/values/styles.xml @@ -0,0 +1,21 @@ + + + + + + + + + + From d08800af46e4a72a84ded333fa6c35711cb0385d Mon Sep 17 00:00:00 2001 From: AnastasiyaK97 Date: Fri, 29 Jul 2022 12:41:30 +0300 Subject: [PATCH 03/26] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20readme=20=D1=84=D0=B0=D0=B9=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base-filters/README.md | 70 +++++++++++++++++++ .../select_list_item/ListSelectionView.kt | 12 +++- .../adapter/SheetSelectionAdapter.kt | 6 +- .../adapter/SheetSelectionDelegate.kt | 10 ++- 4 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 base-filters/README.md diff --git a/base-filters/README.md b/base-filters/README.md new file mode 100644 index 0000000..cb6175b --- /dev/null +++ b/base-filters/README.md @@ -0,0 +1,70 @@ +# Описание + +Модуль содержит реализацию следующих типов фильтров: + +1. Выбор одного из доступных значений списка +2. Выбор нескольких доступных значений из списка +3. добавить остальные по ходу реализаации + +# Использование + +## Выбор одного/нескольких из доступных значений списка + +### Как использовать +``` 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() +``` +### Конфигурации +* в метод `setItems(List)` необходимо передать список объектов +* метод `addItemDecoration()` можно использовать для передачи объекта `RecyclerView.ItemDecoration` +* метод `withSelectionType()` используется для указания типа выбора: + * `SINGLE_SELECT` - по умолчанию - позволяет выбрать один выариант, при этом будет выбран всегда как минимум один вариант + * `MULTI_SELECT` - позволяет выбрать несколько вариантов из списка, при этом можно полностью выбрать все варианты и убрать выделение со всех вариантов +* колбэк `onResultListener()` можно использовать для получения списка всех элементов `RowSelectionItem` после каждого выбора +* колбэк `onItemClickListener()` можно использовать для получения элемента списка `RowSelectionItem`, по которому произошел клик +* после вызова конфигурационных методов обязательно необходимо вызать метод `build()` + +### Кастомизация стиля + +#### 1. Определить кастомную тему и стили элементов +1. Стиль для **текста элемента списка** должен быть наследником стиля `Widget.FilterSelection.Item` +``` xml + +``` +2. Стиль для **индикатора выбора** должен быть наследником стиля `Widget.FilterSelection.Radio` +Передайте `selector-drawable` для кастомизации вида индикатора в конце строки +``` xml + +``` +3. Создайте **тему**, которая должна быть наследником `Theme.FilterSelection` +``` xml + +``` +#### 2. Применить тему при создании view +При создании вью в коде можно указать тему, используя `ContextThemeWrapper` +``` kotlin +val newContext = ContextThemeWrapper(requireContext(), R.style.Theme_Custom_FilterSelection) +val selectorView = ListSelectionView(newContext) + ... + .build() +``` 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 8e30fc8..f217c65 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 @@ -28,7 +28,10 @@ class ListSelectionView @JvmOverloads constructor( private val binding = SingleSelectionLayoutBinding.inflate(LayoutInflater.from(context), this, true) private val adapter by lazy { - SheetSelectionAdapter(onItemSelectAction = onItemSelectedListener) + SheetSelectionAdapter( + onItemSelectAction = onItemSelectedListener, + selectionType = selectionType + ) } private val onItemSelectedListener: OnItemSelectedListener = { item -> @@ -53,9 +56,7 @@ class ListSelectionView @JvmOverloads constructor( } fun setItems(items: List) = apply { - binding.itemsRecycler.adapter = adapter mutableItems = items - updateList() } fun setItems( @@ -79,5 +80,10 @@ class ListSelectionView @JvmOverloads constructor( selectionType = type } + fun build() = apply { + binding.itemsRecycler.adapter = adapter + updateList() + } + enum class SelectionType { SINGLE_SELECT, MULTI_SELECT } } 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 6b92b5a..93fadef 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 @@ -1,12 +1,14 @@ package ru.touchin.roboswag.base_filters.select_list_item.adapter import androidx.recyclerview.widget.DiffUtil +import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView import ru.touchin.roboswag.base_filters.select_list_item.OnItemSelectedListener import ru.touchin.roboswag.base_filters.select_list_item.model.RowSelectionItem import ru.touchin.roboswag.recyclerview_adapters.adapters.DelegationListAdapter class SheetSelectionAdapter( - onItemSelectAction: OnItemSelectedListener + onItemSelectAction: OnItemSelectedListener, + selectionType: ListSelectionView.SelectionType ): DelegationListAdapter(object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: RowSelectionItem, newItem: RowSelectionItem): Boolean = @@ -18,7 +20,7 @@ class SheetSelectionAdapter( }) { init { - addDelegate(SheetSelectionDelegate(onItemSelectAction)) + addDelegate(SheetSelectionDelegate(onItemSelectAction, selectionType)) } } diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt index 4dbfa35..31982ba 100644 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt @@ -5,12 +5,15 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import ru.touchin.roboswag.base_filters.databinding.SelectionItemBinding +import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView +import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView.SelectionType import ru.touchin.roboswag.base_filters.select_list_item.OnItemSelectedListener import ru.touchin.roboswag.base_filters.select_list_item.model.RowSelectionItem import ru.touchin.roboswag.recyclerview_adapters.adapters.ItemAdapterDelegate class SheetSelectionDelegate( - private val onItemSelectAction: OnItemSelectedListener + private val onItemSelectAction: OnItemSelectedListener, + private val selectionType: ListSelectionView.SelectionType ) : ItemAdapterDelegate() { override fun onCreateViewHolder(parent: ViewGroup): SelectionItemViewHolder = SelectionItemViewHolder( @@ -31,7 +34,10 @@ class SheetSelectionDelegate( binding.run { val checkListener = View.OnClickListener { itemRadiobutton.isChecked = true - onItemSelectAction.invoke(item.copy(isSelected = !item.isSelected)) + onItemSelectAction.invoke(item.copy(isSelected = when (selectionType) { + SelectionType.SINGLE_SELECT -> true + else -> !item.isSelected + })) } itemTitle.text = item.title From 719252a3e182df32333893fcf2483aac39787b01 Mon Sep 17 00:00:00 2001 From: AnastasiyaK97 Date: Mon, 1 Aug 2022 11:37:49 +0300 Subject: [PATCH 04/26] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8C=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D0=B2=20=D1=81=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D0=BA=D0=B5=20=D0=BA=D0=B0=D1=81=D1=82=D0=BE=D0=BC?= =?UTF-8?q?=D0=BD=D1=83=D1=8E=20=D1=80=D0=B0=D0=B7=D0=BC=D0=B5=D1=82=D0=BA?= =?UTF-8?q?=D1=83=20=D0=B8=20=D1=82=D0=B8=D0=BF=20=D0=B4=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base-filters/README.md | 29 ++++++-- .../select_list_item/ListSelectionView.kt | 73 +++++++++++++------ .../adapter/BaseSelectionViewHolder.kt | 11 +++ .../adapter/SheetSelectionAdapter.kt | 26 ++++--- .../adapter/SheetSelectionDelegate.kt | 63 ++++++++-------- .../model/BaseSelectionItem.kt | 14 ++++ .../model/DefaultSelectionItem.kt | 24 ++++++ .../model/RowSelectionItem.kt | 11 --- 8 files changed, 171 insertions(+), 80 deletions(-) create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/BaseSelectionViewHolder.kt create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/BaseSelectionItem.kt create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/DefaultSelectionItem.kt delete mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/RowSelectionItem.kt diff --git a/base-filters/README.md b/base-filters/README.md index cb6175b..09d83de 100644 --- a/base-filters/README.md +++ b/base-filters/README.md @@ -12,7 +12,7 @@ ### Как использовать ``` kotlin -val selectorView = ListSelectionView(context) +val selectorView = ListSelectionView>(context) .setItems(navArgs.items) .addItemDecoration((TopDividerItemDecoration( context = requireContext(), @@ -25,16 +25,31 @@ val selectorView = ListSelectionView(context) .build() ``` ### Конфигурации -* в метод `setItems(List)` необходимо передать список объектов -* метод `addItemDecoration()` можно использовать для передачи объекта `RecyclerView.ItemDecoration` -* метод `withSelectionType()` используется для указания типа выбора: +* при создании `ListSelectionView` необходимо передлать `ItemType` - класс модели данных в списке, `HolderType` - класс viewHolder-а в recyclerView. +Для использования дефолтной реализации необходимо использовать типы `>` +* в метод `setItems(List)` необходимо передать список объектов +* метод `addItemDecoration(itemDecoration: RecyclerView.ItemDecoration)` можно использовать для передачи объекта `RecyclerView.ItemDecoration` +* метод `withSelectionType(type: SelectionType)` используется для указания типа выбора: * `SINGLE_SELECT` - по умолчанию - позволяет выбрать один выариант, при этом будет выбран всегда как минимум один вариант * `MULTI_SELECT` - позволяет выбрать несколько вариантов из списка, при этом можно полностью выбрать все варианты и убрать выделение со всех вариантов -* колбэк `onResultListener()` можно использовать для получения списка всех элементов `RowSelectionItem` после каждого выбора -* колбэк `onItemClickListener()` можно использовать для получения элемента списка `RowSelectionItem`, по которому произошел клик +* метод `showInHolder(HolderFactoryType)` используется для определения кастомного 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() +``` +* колбэк `onSelectedItemsListener(listener: OnSelectedItemsListener)` можно использовать для получения списка всех элементов `ItemType` после каждого выбора +* колбэк `onSelectedItemListener(listener: OnSelectedItemListener)` можно использовать для получения элемента списка `ItemType`, по которому произошел клик * после вызова конфигурационных методов обязательно необходимо вызать метод `build()` -### Кастомизация стиля +### Кастомизация стиля дефолтной реализации ViewHolder без необходимости создания кастомного layout и viewHolder #### 1. Определить кастомную тему и стили элементов 1. Стиль для **текста элемента списка** должен быть наследником стиля `Widget.FilterSelection.Item` 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 f217c65..e095ada 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 @@ -5,75 +5,106 @@ import android.util.AttributeSet import android.view.LayoutInflater import android.widget.FrameLayout import androidx.recyclerview.widget.RecyclerView +import ru.touchin.roboswag.base_filters.databinding.SelectionItemBinding import ru.touchin.roboswag.base_filters.databinding.SingleSelectionLayoutBinding +import ru.touchin.roboswag.base_filters.select_list_item.adapter.BaseSelectionViewHolder +import ru.touchin.roboswag.base_filters.select_list_item.adapter.HolderFactoryType +import ru.touchin.roboswag.base_filters.select_list_item.adapter.SelectionItemViewHolder import ru.touchin.roboswag.base_filters.select_list_item.adapter.SheetSelectionAdapter -import ru.touchin.roboswag.base_filters.select_list_item.model.RowSelectionItem +import ru.touchin.roboswag.base_filters.select_list_item.model.BaseSelectionItem -typealias OnItemSelectedListener = (item: RowSelectionItem) -> Unit -typealias OnSelectionResultListener = (items: List) -> Unit +typealias OnSelectedItemListener = (item: ItemType) -> Unit +typealias OnSelectedItemsListener = (items: List) -> Unit -class ListSelectionView @JvmOverloads constructor( +/** + * Base [ListSelectionView] to use in filters screen for choosing single or multi items in list. + * + * @param ItemType Type of model's element in list. + * It must implement [BaseSelectionItem] abstract class. + * + * @param HolderType Type of viewHolder in recyclerView. + * It must implement [BaseSelectionViewHolder] abstract class. + * +**/ + +class ListSelectionView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 -) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) { +) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) + where ItemType: BaseSelectionItem, + HolderType: BaseSelectionViewHolder{ - private var mutableItems: List = emptyList() + private var mutableItems: List = emptyList() private var selectionType = SelectionType.SINGLE_SELECT - private var onItemsClickListener: OnSelectionResultListener? = null - private var onItemClickListener: OnItemSelectedListener? = null + private var onSelectedItemChanged: OnSelectedItemListener? = null + private var onSelectedItemsChanged: OnSelectedItemsListener? = null + private var factory: HolderFactoryType = getDefaultFactory() + + private fun getDefaultFactory(): HolderFactoryType = { parent, clickListener, selectionType -> + SelectionItemViewHolder( + binding = SelectionItemBinding.inflate(LayoutInflater.from(parent.context), parent, false), + onItemSelectAction = clickListener, + selectionType = selectionType + ) + } private val binding = SingleSelectionLayoutBinding.inflate(LayoutInflater.from(context), this, true) private val adapter by lazy { SheetSelectionAdapter( onItemSelectAction = onItemSelectedListener, - selectionType = selectionType + selectionType = selectionType, + factory = factory ) } - private val onItemSelectedListener: OnItemSelectedListener = { item -> - onItemClickListener?.invoke(item) + private val onItemSelectedListener: (item: ItemType) -> Unit = { item -> + onSelectedItemChanged?.invoke(item) updateAfterSelection(item) - onItemsClickListener?.invoke(mutableItems) + onSelectedItemsChanged?.invoke(mutableItems) } private fun updateList() { adapter.submitList(mutableItems) } - private fun updateAfterSelection(selectedItem: RowSelectionItem) { + private fun updateAfterSelection(selectedItem: ItemType) { mutableItems = mutableItems.map { item -> when { - item.id == selectedItem.id -> selectedItem - selectionType == SelectionType.SINGLE_SELECT -> item.copy(isSelected = false) + item.isItemTheSame(selectedItem) -> selectedItem + selectionType == SelectionType.SINGLE_SELECT -> item.copyWithSelection(isSelected = false) else -> item } } updateList() } - fun setItems(items: List) = apply { + fun setItems(items: List) = apply { mutableItems = items } fun setItems( source: List, - mapper: (T) -> RowSelectionItem + mapper: (T) -> ItemType ) = setItems(source.map { item -> mapper.invoke(item) }) + fun showInHolder(holderFactory: HolderFactoryType) = apply { + factory = holderFactory + } + fun addItemDecoration(itemDecoration: RecyclerView.ItemDecoration) = apply { binding.itemsRecycler.addItemDecoration(itemDecoration) } - fun onItemClickListener(listener: OnItemSelectedListener) = apply { - this@ListSelectionView.onItemClickListener = listener + fun onSelectedItemListener(listener: OnSelectedItemListener) = apply { + this@ListSelectionView.onSelectedItemChanged = listener } - fun onResultListener(listener: OnSelectionResultListener) = apply { - this@ListSelectionView.onItemsClickListener = listener + fun onSelectedItemsListener(listener: OnSelectedItemsListener) = apply { + this@ListSelectionView.onSelectedItemsChanged = listener } fun withSelectionType(type: SelectionType) = apply { 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 new file mode 100644 index 0000000..a4b9af0 --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/BaseSelectionViewHolder.kt @@ -0,0 +1,11 @@ +package ru.touchin.roboswag.base_filters.select_list_item.adapter + +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) + : 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/SheetSelectionAdapter.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionAdapter.kt index 93fadef..c88d331 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 @@ -2,25 +2,29 @@ package ru.touchin.roboswag.base_filters.select_list_item.adapter import androidx.recyclerview.widget.DiffUtil import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView -import ru.touchin.roboswag.base_filters.select_list_item.OnItemSelectedListener -import ru.touchin.roboswag.base_filters.select_list_item.model.RowSelectionItem +import ru.touchin.roboswag.base_filters.select_list_item.model.BaseSelectionItem import ru.touchin.roboswag.recyclerview_adapters.adapters.DelegationListAdapter -class SheetSelectionAdapter( - onItemSelectAction: OnItemSelectedListener, - selectionType: ListSelectionView.SelectionType -): DelegationListAdapter(object : DiffUtil.ItemCallback() { +class SheetSelectionAdapter( + onItemSelectAction: (ItemType) -> Unit, + selectionType: ListSelectionView.SelectionType, + factory: HolderFactoryType +): DelegationListAdapter(object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: RowSelectionItem, newItem: RowSelectionItem): Boolean = - oldItem.id == newItem.id + override fun areItemsTheSame(oldItem: BaseSelectionItem, newItem: BaseSelectionItem): Boolean = + oldItem.isItemTheSame(newItem) - override fun areContentsTheSame(oldItem: RowSelectionItem, newItem: RowSelectionItem): Boolean = - oldItem == newItem + override fun areContentsTheSame(oldItem: BaseSelectionItem, newItem: BaseSelectionItem): Boolean = + oldItem.isContentTheSame(newItem) }) { init { - addDelegate(SheetSelectionDelegate(onItemSelectAction, selectionType)) + addDelegate(SheetSelectionDelegate( + onItemSelectAction = onItemSelectAction, + selectionType = selectionType, + factory = factory + )) } } diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt index 31982ba..15540a5 100644 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt @@ -1,53 +1,56 @@ package ru.touchin.roboswag.base_filters.select_list_item.adapter -import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView import ru.touchin.roboswag.base_filters.databinding.SelectionItemBinding -import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView.SelectionType -import ru.touchin.roboswag.base_filters.select_list_item.OnItemSelectedListener -import ru.touchin.roboswag.base_filters.select_list_item.model.RowSelectionItem +import ru.touchin.roboswag.base_filters.select_list_item.model.BaseSelectionItem import ru.touchin.roboswag.recyclerview_adapters.adapters.ItemAdapterDelegate -class SheetSelectionDelegate( - private val onItemSelectAction: OnItemSelectedListener, - private val selectionType: ListSelectionView.SelectionType -) : ItemAdapterDelegate() { +typealias HolderFactoryType = (ViewGroup, (ItemType) -> Unit, SelectionType) -> BaseSelectionViewHolder - override fun onCreateViewHolder(parent: ViewGroup): SelectionItemViewHolder = SelectionItemViewHolder( - binding = SelectionItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) - ) +class SheetSelectionDelegate( + private val onItemSelectAction: (ItemType) -> Unit, + private val selectionType: SelectionType, + private val factory: HolderFactoryType +) : ItemAdapterDelegate, ItemType>() + where ItemType : BaseSelectionItem { + + override fun onCreateViewHolder(parent: ViewGroup): BaseSelectionViewHolder = + factory.invoke(parent, onItemSelectAction, selectionType) override fun onBindViewHolder( - holder: SelectionItemViewHolder, - item: RowSelectionItem, + holder: BaseSelectionViewHolder, + item: ItemType, adapterPosition: Int, collectionPosition: Int, payloads: MutableList ) = holder.bind(item) - inner class SelectionItemViewHolder(val binding: SelectionItemBinding) : RecyclerView.ViewHolder(binding.root) { +} - fun bind(item: RowSelectionItem) { - binding.run { - val checkListener = View.OnClickListener { - itemRadiobutton.isChecked = true - onItemSelectAction.invoke(item.copy(isSelected = when (selectionType) { - SelectionType.SINGLE_SELECT -> true - else -> !item.isSelected - })) - } +class SelectionItemViewHolder(private val binding: SelectionItemBinding, + private val onItemSelectAction: (ItemType) -> Unit, + private val selectionType: SelectionType + ) : BaseSelectionViewHolder(binding.root) { - itemTitle.text = item.title - root.setOnClickListener(checkListener) - - itemRadiobutton.setOnClickListener(checkListener) - itemRadiobutton.isChecked = item.isSelected + 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 + })) } - } + itemTitle.text = item.title + root.setOnClickListener(checkListener) + + itemRadiobutton.setOnClickListener(checkListener) + itemRadiobutton.isChecked = item.isSelected + } } } + 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 new file mode 100644 index 0000000..b19d0d0 --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/BaseSelectionItem.kt @@ -0,0 +1,14 @@ +package ru.touchin.roboswag.base_filters.select_list_item.model + +abstract class BaseSelectionItem( + open val id: Int, + open val title: String, + open val isSelected: Boolean +) { + + abstract fun isItemTheSame(compareItem: BaseSelectionItem): Boolean + + abstract fun isContentTheSame(compareItem: BaseSelectionItem): Boolean + + abstract fun copyWithSelection(isSelected: Boolean): ItemType +} diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/DefaultSelectionItem.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/DefaultSelectionItem.kt new file mode 100644 index 0000000..603961f --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/DefaultSelectionItem.kt @@ -0,0 +1,24 @@ +package ru.touchin.roboswag.base_filters.select_list_item.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class DefaultSelectionItem( + override val id: Int, + override val title: String, + override val isSelected: Boolean = false +) : BaseSelectionItem(id, title, isSelected), Parcelable { + + override fun isItemTheSame(compareItem: BaseSelectionItem): Boolean = when { + compareItem is DefaultSelectionItem && id == compareItem.id -> true + else -> false + } + + override fun isContentTheSame(compareItem: BaseSelectionItem): Boolean = + this == compareItem + + @Suppress("UNCHECKED_CAST") + override fun copyWithSelection(isSelected: Boolean): ItemType = + this.copy(isSelected = isSelected) as ItemType +} diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/RowSelectionItem.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/RowSelectionItem.kt deleted file mode 100644 index 007eb69..0000000 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/model/RowSelectionItem.kt +++ /dev/null @@ -1,11 +0,0 @@ -package ru.touchin.roboswag.base_filters.select_list_item.model - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -@Parcelize -data class RowSelectionItem( - val id: Int, - val title: String, - val isSelected: Boolean = false -): Parcelable From d8dc470805e5302499ec29d2d0fd7f35e84bc831 Mon Sep 17 00:00:00 2001 From: AnastasiyaK97 Date: Mon, 1 Aug 2022 12:42:00 +0300 Subject: [PATCH 05/26] =?UTF-8?q?SelectionItemViewHolder=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=20=D0=B2=20=D0=BE=D1=82=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/SelectionItemViewHolder.kt | 31 +++++++++++++++++++ .../adapter/SheetSelectionDelegate.kt | 28 ----------------- 2 files changed, 31 insertions(+), 28 deletions(-) create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SelectionItemViewHolder.kt 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 new file mode 100644 index 0000000..cc81c0c --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SelectionItemViewHolder.kt @@ -0,0 +1,31 @@ +package ru.touchin.roboswag.base_filters.select_list_item.adapter + +import android.view.View +import ru.touchin.roboswag.base_filters.databinding.SelectionItemBinding +import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView +import ru.touchin.roboswag.base_filters.select_list_item.model.BaseSelectionItem + +class SelectionItemViewHolder(private val binding: SelectionItemBinding, + private val onItemSelectAction: (ItemType) -> Unit, + private val selectionType: ListSelectionView.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) { + ListSelectionView.SelectionType.SINGLE_SELECT -> true + else -> !item.isSelected + })) + } + + itemTitle.text = item.title + root.setOnClickListener(checkListener) + + itemRadiobutton.setOnClickListener(checkListener) + itemRadiobutton.isChecked = item.isSelected + } + } + +} diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt index 15540a5..14f5460 100644 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt @@ -1,8 +1,6 @@ package ru.touchin.roboswag.base_filters.select_list_item.adapter -import android.view.View import android.view.ViewGroup -import ru.touchin.roboswag.base_filters.databinding.SelectionItemBinding import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView.SelectionType import ru.touchin.roboswag.base_filters.select_list_item.model.BaseSelectionItem import ru.touchin.roboswag.recyclerview_adapters.adapters.ItemAdapterDelegate @@ -28,29 +26,3 @@ class SheetSelectionDelegate( ) = holder.bind(item) } - -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 - })) - } - - itemTitle.text = item.title - root.setOnClickListener(checkListener) - - itemRadiobutton.setOnClickListener(checkListener) - itemRadiobutton.isChecked = item.isSelected - } - } - -} - From 2dd57ee308cfd3e76a90a49744b823114527d4f8 Mon Sep 17 00:00:00 2001 From: AnastasiyaK97 Date: Thu, 4 Aug 2022 14:55:54 +0300 Subject: [PATCH 06/26] =?UTF-8?q?INTERNAL-296=20+=20INTERNAL-297=20+=20INT?= =?UTF-8?q?ERNAL-298:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=84=D1=83?= =?UTF-8?q?=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0=D0=BB=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=B2=20=D0=B2=D0=B8=D0=B4=D0=B5=20=D1=82=D0=B5=D0=B3=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base-filters/README.md | 40 ++++- .../roboswag/base_filters/SelectionType.kt | 5 + .../select_list_item/ListSelectionView.kt | 3 +- .../adapter/SelectionItemViewHolder.kt | 6 +- .../adapter/SheetSelectionAdapter.kt | 4 +- .../adapter/SheetSelectionDelegate.kt | 2 +- .../base_filters/tags/TagLayoutView.kt | 167 ++++++++++++++++++ .../roboswag/base_filters/tags/TagView.kt | 26 +++ .../base_filters/tags/model/FilterProperty.kt | 32 ++++ .../src/main/res/color/color_chip_choice.xml | 5 + .../src/main/res/drawable/background_chip.xml | 7 + .../res/drawable/background_chip_checked.xml | 10 ++ .../res/drawable/background_chip_choice.xml | 5 + .../main/res/layout/layout_default_tag.xml | 12 ++ .../main/res/layout/tag_selection_layout.xml | 34 ++++ 15 files changed, 344 insertions(+), 14 deletions(-) create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/SelectionType.kt create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/tags/TagLayoutView.kt create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/tags/TagView.kt create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/tags/model/FilterProperty.kt create mode 100644 base-filters/src/main/res/color/color_chip_choice.xml create mode 100644 base-filters/src/main/res/drawable/background_chip.xml create mode 100644 base-filters/src/main/res/drawable/background_chip_checked.xml create mode 100644 base-filters/src/main/res/drawable/background_chip_choice.xml create mode 100644 base-filters/src/main/res/layout/layout_default_tag.xml create mode 100644 base-filters/src/main/res/layout/tag_selection_layout.xml diff --git a/base-filters/README.md b/base-filters/README.md index 09d83de..bd35f8f 100644 --- a/base-filters/README.md +++ b/base-filters/README.md @@ -2,13 +2,12 @@ Модуль содержит реализацию следующих типов фильтров: -1. Выбор одного из доступных значений списка -2. Выбор нескольких доступных значений из списка -3. добавить остальные по ходу реализаации +1. Выбор одного/нескольких из доступных значений списка +2. Выбор одного/нескольких значений из перечня тегов # Использование -## Выбор одного/нескольких из доступных значений списка +## 1. Выбор одного/нескольких из доступных значений списка ### Как использовать ``` kotlin @@ -51,7 +50,7 @@ val selectorView = ListSelectionView(cont ### Кастомизация стиля дефолтной реализации ViewHolder без необходимости создания кастомного layout и viewHolder -#### 1. Определить кастомную тему и стили элементов +#### 1) Определить кастомную тему и стили элементов 1. Стиль для **текста элемента списка** должен быть наследником стиля `Widget.FilterSelection.Item` ``` xml ``` -#### 2. Применить тему при создании view +#### 2) Применить тему при создании view При создании вью в коде можно указать тему, используя `ContextThemeWrapper` ``` kotlin val newContext = ContextThemeWrapper(requireContext(), R.style.Theme_Custom_FilterSelection) @@ -83,3 +82,32 @@ val selectorView = ListSelectionView(newContext) ... .build() ``` + +## 2. Выбор одного/нескольких значений из перечня тегов + +`TagLayoutView` - view-контейнер для тегов +`TagView` - view для тега. Кастомная разметка для тега должна содержать в корне `TagView` + +### Как использовать +``` kotlin +binding.tagItemLayout + .setSpacing(16) + .setSelectionType(SelectionType.MULTI_SELECT) // по умолчанию + .isSingleLine(false) // по умолчанию + .onPropertySelectedAction { filterProperty: FilterProperty -> + //Do something + } + .build(getFilterItem()) + } +``` +### Конфигурации +* метод `setSelectionType(SelectionType)` конфигурирует тип выбора: + * `SINGLE_SELECT` - выбор одного варианта сбрасывает select у всех остальных + * `MULTI_SELECT` - по умолчанию - мультивыбор тегов с учетом исключающих фильтров +* метод `isSingleLine(Boolean)` конфигурирует вид контейнера с тегами: `true` соответствует горизонтальному контейнеру со скроллом +* `setTagLayout(Int)` устанавливает разметку для тега. Если не задано - то используется дефолтная разметка `layout_default_tag.xml` +* `setMaxTagCount(Int)` позволяет ограничить количество отображаемых тегов. По умолчанию ограничения нет. +* `setMoreTagLayout(Int, String)` устанавливает разметку для тега, который отображается для дополнительного тега. Если не указана - то тег не будет создан +* `setSpacing(Int)`, `setSpacingHorizontal(Int`) и мsetSpacingVertical(Int)` можно использовать для настройки расстояния между тегами. По умолчанию - 0 +* `onMoreValuesAction(FilterMoreAction)` и `onPropertySelectedAction(PropertySelectedAction)` используются для передачи колбэков на клик по тегу типа "Еще" и обычного тега соответственно +* после вызова конфигурационных методов обязательно необходимо вызать метод `build(FilterItem)` diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/SelectionType.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/SelectionType.kt new file mode 100644 index 0000000..31f1f0e --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/SelectionType.kt @@ -0,0 +1,5 @@ +package ru.touchin.roboswag.base_filters + +enum class SelectionType { + SINGLE_SELECT, MULTI_SELECT +} 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 e095ada..03c1413 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 @@ -12,6 +12,7 @@ import ru.touchin.roboswag.base_filters.select_list_item.adapter.HolderFactoryTy import ru.touchin.roboswag.base_filters.select_list_item.adapter.SelectionItemViewHolder import ru.touchin.roboswag.base_filters.select_list_item.adapter.SheetSelectionAdapter 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 @@ -115,6 +116,4 @@ class ListSelectionView @JvmOverloads constructor( binding.itemsRecycler.adapter = adapter updateList() } - - enum class SelectionType { SINGLE_SELECT, MULTI_SELECT } } 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 cc81c0c..59a05c2 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 @@ -2,12 +2,12 @@ package ru.touchin.roboswag.base_filters.select_list_item.adapter import android.view.View import ru.touchin.roboswag.base_filters.databinding.SelectionItemBinding -import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView 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: ListSelectionView.SelectionType + private val selectionType: SelectionType ) : BaseSelectionViewHolder(binding.root) { override fun bind(item: ItemType) { @@ -15,7 +15,7 @@ class SelectionItemViewHolder(private val binding: val checkListener = View.OnClickListener { itemRadiobutton.isChecked = true onItemSelectAction.invoke(item.copyWithSelection(isSelected = when (selectionType) { - ListSelectionView.SelectionType.SINGLE_SELECT -> true + SelectionType.SINGLE_SELECT -> true else -> !item.isSelected })) } 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 c88d331..44df5ee 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 @@ -1,13 +1,13 @@ package ru.touchin.roboswag.base_filters.select_list_item.adapter import androidx.recyclerview.widget.DiffUtil -import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView 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( onItemSelectAction: (ItemType) -> Unit, - selectionType: ListSelectionView.SelectionType, + selectionType: SelectionType, factory: HolderFactoryType ): DelegationListAdapter(object : DiffUtil.ItemCallback() { diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt index 14f5460..39021ab 100644 --- a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/select_list_item/adapter/SheetSelectionDelegate.kt @@ -1,8 +1,8 @@ package ru.touchin.roboswag.base_filters.select_list_item.adapter import android.view.ViewGroup -import ru.touchin.roboswag.base_filters.select_list_item.ListSelectionView.SelectionType 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.ItemAdapterDelegate typealias HolderFactoryType = (ViewGroup, (ItemType) -> Unit, SelectionType) -> BaseSelectionViewHolder 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 new file mode 100644 index 0000000..a81dfbc --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/tags/TagLayoutView.kt @@ -0,0 +1,167 @@ +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 +import ru.touchin.roboswag.components.utils.px +import kotlin.properties.Delegates + +typealias PropertySelectedAction = (FilterProperty) -> Unit +typealias FilterMoreAction = (FilterItem) -> Unit + +class TagLayoutView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + 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 propertySelectedAction: PropertySelectedAction? = null + private var moreValuesAction: FilterMoreAction? = null + + private var selectionType = SelectionType.MULTI_SELECT + private var isSingleLine = false + private var tagSpacingHorizontalDp: Int = 0 + private var tagSpacingVerticalDp: Int = 0 + + @LayoutRes + private var tagLayout: Int = R.layout.layout_default_tag + + private var moreTagText: String = "" + private var maxTagCount: Int? = null + + @LayoutRes + private var moreTagLayout: Int? = null + + 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 createTag(property: FilterProperty): TagView = + (UiUtils.inflate(tagLayout, this) as? TagView)?.apply { + text = property.title + isChecked = property.isSelected + tagId = property.id + + setOnCheckAction { view, isChecked -> + when { + selectionType == SelectionType.SINGLE_SELECT && isChecked -> clearCheck(property.id) + selectionType == SelectionType.MULTI_SELECT && isChecked -> clearExcludedCheck(property) + } + view.isChecked = isChecked + propertySelectedAction?.invoke(property.copyWithSelected(isSelected = isChecked)) + } + } ?: 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 clearCheck(selectedId: Int) { + for (i in 0 until tagsContainer.childCount) { + val child = tagsContainer.getChildAt(i) + if (child is TagView && child.tagId != selectedId) { + child.isChecked = false + } + } + } + + private fun clearExcludedCheck(property: FilterProperty) { + val excludingIds = property.excludes.map { it.id } + + for (i in 0 until tagsContainer.childCount) { + val child = tagsContainer.getChildAt(i) + if (child is TagView && child.tagId in excludingIds) { + child.isChecked = false + } + } + } +} diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/tags/TagView.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/tags/TagView.kt new file mode 100644 index 0000000..4a4ee35 --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/tags/TagView.kt @@ -0,0 +1,26 @@ +package ru.touchin.roboswag.base_filters.tags + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatCheckBox + +class TagView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +): AppCompatCheckBox(context, attrs, defStyleAttr) { + + var tagId: Int? = null + + private var action: (( view: TagView, isChecked: Boolean) -> Unit)? = null + + init { + setOnClickListener { + action?.invoke(this, isChecked) + } + } + + fun setOnCheckAction(action: (view: TagView, isChecked: Boolean) -> Unit) { + this.action = action + } +} diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/tags/model/FilterProperty.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/tags/model/FilterProperty.kt new file mode 100644 index 0000000..c45742d --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/tags/model/FilterProperty.kt @@ -0,0 +1,32 @@ +package ru.touchin.roboswag.base_filters.tags.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +open class FilterItem( + val id: Int, + val title: String, + val properties: List +) : Parcelable + +@Parcelize +open class FilterProperty( + val id: Int, + val title: String, + val excludes: List, + val isSelected: Boolean = false +) : Parcelable { + + open fun copyWithSelected(isSelected: Boolean) = FilterProperty( + id = id, + title = title, + excludes = excludes, + isSelected = isSelected + ) +} + +@Parcelize +open class PropertyExcludingValue( + val id: Int +) : Parcelable diff --git a/base-filters/src/main/res/color/color_chip_choice.xml b/base-filters/src/main/res/color/color_chip_choice.xml new file mode 100644 index 0000000..d19976d --- /dev/null +++ b/base-filters/src/main/res/color/color_chip_choice.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/base-filters/src/main/res/drawable/background_chip.xml b/base-filters/src/main/res/drawable/background_chip.xml new file mode 100644 index 0000000..177d7bd --- /dev/null +++ b/base-filters/src/main/res/drawable/background_chip.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/base-filters/src/main/res/drawable/background_chip_checked.xml b/base-filters/src/main/res/drawable/background_chip_checked.xml new file mode 100644 index 0000000..9e00394 --- /dev/null +++ b/base-filters/src/main/res/drawable/background_chip_checked.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/base-filters/src/main/res/drawable/background_chip_choice.xml b/base-filters/src/main/res/drawable/background_chip_choice.xml new file mode 100644 index 0000000..cef4af3 --- /dev/null +++ b/base-filters/src/main/res/drawable/background_chip_choice.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/base-filters/src/main/res/layout/layout_default_tag.xml b/base-filters/src/main/res/layout/layout_default_tag.xml new file mode 100644 index 0000000..a5236c7 --- /dev/null +++ b/base-filters/src/main/res/layout/layout_default_tag.xml @@ -0,0 +1,12 @@ + + diff --git a/base-filters/src/main/res/layout/tag_selection_layout.xml b/base-filters/src/main/res/layout/tag_selection_layout.xml new file mode 100644 index 0000000..1701e7c --- /dev/null +++ b/base-filters/src/main/res/layout/tag_selection_layout.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + From d41f8b311177002bd28bc7e7240c1b0db7addf74 Mon Sep 17 00:00:00 2001 From: AnastasiyaK97 Date: Fri, 5 Aug 2022 23:42:43 +0300 Subject: [PATCH 07/26] =?UTF-8?q?INTERNAL-301=20+=20INTERNAL-302:=20=D0=94?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D1=84=D0=B8=D0=BB?= =?UTF-8?q?=D1=8C=D1=82=D1=80=20=D0=B4=D0=BB=D1=8F=20=D0=B2=D1=8B=D0=B1?= =?UTF-8?q?=D0=BE=D1=80=D0=B0=20=D0=BC=D0=B8=D0=BD=D0=B8=D0=BC=D0=B0=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=B8=20=D0=BC=D0=B0=D0=BA?= =?UTF-8?q?=D1=81=D0=B8=D0=BC=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=B7=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=D0=B7?= =?UTF-8?q?=20=D0=B4=D0=B8=D0=B0=D0=BF=D0=BE=D0=B7=D0=BE=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base-filters/README.md | 64 ++++++- .../base_filters/range/FilterRangeSlider.kt | 170 ++++++++++++++++++ .../base_filters/range/HintInputView.kt | 42 +++++ .../base_filters/range/RangeChoiceView.kt | 167 +++++++++++++++++ .../range/model/FilterRangeItem.kt | 34 ++++ .../res/drawable/background_hint_input.xml | 9 + .../cursor_background_text_input_view.xml | 11 ++ .../src/main/res/layout/range_choice_view.xml | 52 ++++++ .../src/main/res/layout/view_hint_input.xml | 25 +++ base-filters/src/main/res/values/attrs.xml | 27 +++ base-filters/src/main/res/values/colors.xml | 8 + base-filters/src/main/res/values/strings.xml | 5 + base-filters/src/main/res/values/styles.xml | 51 ++++++ 13 files changed, 663 insertions(+), 2 deletions(-) create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/range/FilterRangeSlider.kt create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/range/HintInputView.kt create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/range/RangeChoiceView.kt create mode 100644 base-filters/src/main/java/ru/touchin/roboswag/base_filters/range/model/FilterRangeItem.kt create mode 100644 base-filters/src/main/res/drawable/background_hint_input.xml create mode 100644 base-filters/src/main/res/drawable/cursor_background_text_input_view.xml create mode 100644 base-filters/src/main/res/layout/range_choice_view.xml create mode 100644 base-filters/src/main/res/layout/view_hint_input.xml create mode 100644 base-filters/src/main/res/values/colors.xml create mode 100644 base-filters/src/main/res/values/strings.xml diff --git a/base-filters/README.md b/base-filters/README.md index bd35f8f..2e66d86 100644 --- a/base-filters/README.md +++ b/base-filters/README.md @@ -4,6 +4,7 @@ 1. Выбор одного/нескольких из доступных значений списка 2. Выбор одного/нескольких значений из перечня тегов +3. Выбор минимального и максимального значения из диапозона # Использование @@ -85,8 +86,8 @@ val selectorView = ListSelectionView(newContext) ## 2. Выбор одного/нескольких значений из перечня тегов -`TagLayoutView` - view-контейнер для тегов -`TagView` - view для тега. Кастомная разметка для тега должна содержать в корне `TagView` +* `TagLayoutView` - view-контейнер для тегов +* `TagView` - view для тега. Кастомная разметка для тега должна содержать в корне `TagView` ### Как использовать ``` kotlin @@ -111,3 +112,62 @@ binding.tagItemLayout * `setSpacing(Int)`, `setSpacingHorizontal(Int`) и мsetSpacingVertical(Int)` можно использовать для настройки расстояния между тегами. По умолчанию - 0 * `onMoreValuesAction(FilterMoreAction)` и `onPropertySelectedAction(PropertySelectedAction)` используются для передачи колбэков на клик по тегу типа "Еще" и обычного тега соответственно * после вызова конфигурационных методов обязательно необходимо вызать метод `build(FilterItem)` + + +## 3. Выбор минимального и максимального значения из диапозона + +* `RangeChoiceView` - контейнер для слайдера и редактируемых полей +* `FilterRangeSlider` - слайдер - Можно использовать как отдельный элемент +* `HintInputView` - view для редактируемого поля начала и окончания диапозона + +### Как использовать + +В разметке +``` xml + +``` + +Настройка в коде +``` kotlin +fun setupValues(item: FilterRangeItem) { + binding.rangeValuesTest.setupRangeValues( + rangeFilterItem = item, + onChangeCallback = callback + ) + } + + fun resetValues() { + binding.rangeValuesTest.resetRangeValue() + } +``` +### Конфигурации +Вся конфигурация вьюх осуществляется через стили: +* Для `RangeChoiceView`: + * `filterRange_sliderMargin` - расстояние от слайдера до редактируемых полей + * `filterRange_startHint` - ссылка на строку с текстом подсказки в редактируемом поле для начального значения + * `filterRange_endHint` - ссылка на строку с текстом подсказки в редактируемом поле для конечного значения + * `filterRange_theme` - ссылка на тему +* В теме: + * атрибут `filterRange_sliderStyle` - ссылка на стиль слайдера + * атрибут `filterRange_hintViewStyle` - ссылка на стиль `HintInputView` + * атрибут `filterRange_hintTextStyle` - ссылка на стиль `TextView` внутри `HintInputView` + * атрибут `filterRange_valueEditTextStyle` - ссылка на стиль `EditText` внутри `HintInputView` +* Для `FilterRangeSlider`: + * `trackColorActive` + * `trackColorInactive` + * `trackHeight` + * `thumbElevation` + * `thumbColor` + * `labelBehavior` + * `haloRadius` + * `filterRange_stepTextAppearance` + * `filterRange_activeTickColor` + * `filterRange_inactiveTickColor` + * `filterRange_stepValueMarginTop` + * `filterRange_sliderPointSize` + * `filterRange_pointShape` diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/range/FilterRangeSlider.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/range/FilterRangeSlider.kt new file mode 100644 index 0000000..1f3a371 --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/range/FilterRangeSlider.kt @@ -0,0 +1,170 @@ +package ru.touchin.roboswag.base_filters.range + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Paint.Cap +import android.util.AttributeSet +import android.widget.TextView +import androidx.annotation.StyleRes +import androidx.core.content.withStyledAttributes +import com.google.android.material.shape.CornerFamily +import com.google.android.material.shape.MaterialShapeDrawable +import com.google.android.material.shape.ShapeAppearanceModel +import com.google.android.material.slider.RangeSlider +import ru.touchin.roboswag.base_filters.R +import ru.touchin.roboswag.components.utils.getColorSimple + +class FilterRangeSlider @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : RangeSlider(context, attrs, defStyleAttr) { + + var points: List? = null + set(value) { + field = value?.sorted()?.filter { it > valueFrom && it < valueTo } + } + + private val innerThumbRadius: Int = thumbRadius + + private var stepValueMarginTop = 0f + private var inactiveTickColor: Int = context.getColorSimple(R.color.slider_point_inactive) + private var activeTickColor: Int = context.getColorSimple(R.color.slider_point_active) + private var sliderPointSize: Float = 5f + @StyleRes private var stepTextAppearance: Int = -1 + private var shape: Shape = Shape.CIRCLE + + private var trackCenterY: Float = -1F + + init { + // Set original thumb radius to zero to draw custom one on top + thumbRadius = 0 + + context.withStyledAttributes(attrs, R.styleable.FilterRangeSlider, defStyleAttr, defStyleRes) { + stepValueMarginTop = getDimension(R.styleable.FilterRangeSlider_filterRange_stepValueMarginTop, stepValueMarginTop) + inactiveTickColor = getColor(R.styleable.FilterRangeSlider_filterRange_inactiveTickColor, inactiveTickColor) + activeTickColor = getColor(R.styleable.FilterRangeSlider_filterRange_activeTickColor, activeTickColor) + sliderPointSize = getDimension(R.styleable.FilterRangeSlider_filterRange_sliderPointSize, sliderPointSize) + stepTextAppearance = getResourceId(R.styleable.FilterRangeSlider_filterRange_stepTextAppearance, -1) + shape = Shape.values()[getInt(R.styleable.FilterRangeSlider_filterRange_pointShape, Shape.CIRCLE.ordinal)] + } + } + + private val thumbDrawable = MaterialShapeDrawable().apply { + shadowCompatibilityMode = MaterialShapeDrawable.SHADOW_COMPAT_MODE_ALWAYS + setBounds(0, 0, innerThumbRadius * 2, innerThumbRadius * 2) + elevation = thumbElevation + state = drawableState + fillColor = thumbTintList + shapeAppearanceModel = ShapeAppearanceModel + .builder() + .setAllCorners(shape.value, innerThumbRadius.toFloat()) + .build() + } + + private val inactiveTicksPaint = getDefaultTickPaint().apply { color = inactiveTickColor } + + private val activeTicksPaint = getDefaultTickPaint().apply { color = activeTickColor } + + private fun getDefaultTickPaint() = Paint().apply { + isAntiAlias = true + style = Paint.Style.STROKE + strokeCap = Cap.ROUND + strokeWidth = sliderPointSize + } + + // Using TextView as a bridge to get text params + private val stepValuePaint: Paint = TextView(context) + .apply { stepTextAppearance.takeIf { it != -1 }?.let { setTextAppearance(it) } } + .let { textView -> + Paint().apply { + isAntiAlias = true + color = textView.currentTextColor + textSize = textView.textSize + typeface = textView.typeface + textAlign = Paint.Align.CENTER + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + + trackCenterY = measuredHeight / 2F + + val height = trackCenterY + trackHeight / 2F + stepValueMarginTop + stepValuePaint.textSize + setMeasuredDimension(measuredWidth, height.toInt()) + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + drawTicks(canvas) + drawThumb(canvas) + drawStepValues(canvas) + } + + private fun drawTicks(canvas: Canvas) { + if (points.isNullOrEmpty()) return + + val ticksCoordinates = mutableListOf() + points?.forEach { point -> + ticksCoordinates.add(normalizeValue(point.toFloat()) * trackWidth + trackSidePadding) + ticksCoordinates.add(trackCenterY) + } + + val leftPointsSize = points?.count { it < values[0] } ?: 0 + val rightPointSize = points?.count { it > values[1] } ?: 0 + val activePointSize = (points?.size ?: 0) - leftPointsSize - rightPointSize + + // Draw inactive ticks to the left of the smallest thumb. + canvas.drawPoints(ticksCoordinates.toFloatArray(), 0, leftPointsSize * 2, inactiveTicksPaint) + + // Draw active ticks between the thumbs. + canvas.drawPoints( + ticksCoordinates.toFloatArray(), + leftPointsSize * 2, + activePointSize * 2, + activeTicksPaint + ) + + // Draw inactive ticks to the right of the largest thumb. + canvas.drawPoints( + ticksCoordinates.toFloatArray(), + leftPointsSize * 2 + activePointSize * 2, + rightPointSize * 2, + inactiveTicksPaint + ) + } + + private fun drawThumb(canvas: Canvas) { + for (value in values) { + canvas.save() + canvas.translate( + (trackSidePadding + (normalizeValue(value) * trackWidth).toInt() - innerThumbRadius).toFloat(), + trackCenterY - innerThumbRadius + ) + thumbDrawable.draw(canvas) + canvas.restore() + } + } + + private fun drawStepValues(canvas: Canvas) { + points?.forEach { point -> + canvas.drawText( + point.toString(), + normalizeValue(point.toFloat()) * trackWidth + trackSidePadding, + trackCenterY + trackHeight / 2F + stepValueMarginTop + stepValuePaint.textSize - 3F, + stepValuePaint + ) + } + } + + private fun normalizeValue(value: Float) = (value - valueFrom) / (valueTo - valueFrom) + + private enum class Shape(val value: Int) { + CIRCLE(CornerFamily.ROUNDED), + CUT(CornerFamily.CUT) + } +} diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/range/HintInputView.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/range/HintInputView.kt new file mode 100644 index 0000000..e7934c3 --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/range/HintInputView.kt @@ -0,0 +1,42 @@ +package ru.touchin.roboswag.base_filters.range + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.withStyledAttributes +import androidx.core.view.updateLayoutParams +import ru.touchin.roboswag.base_filters.R +import ru.touchin.roboswag.base_filters.databinding.ViewHintInputBinding +import ru.touchin.roboswag.components.utils.px + +class HintInputView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) { + + private val binding = ViewHintInputBinding.inflate(LayoutInflater.from(context), this) + + var inputText: String = "" + set(value) { + setText(value) + field = value + } + + fun setHint(value: String?) { + binding.startHint.text = value.orEmpty() + } + + private fun setText(value: String) { + binding.editText.run { + setText(value) + setSelection(text?.length ?: 0) + } + } + + fun getEditText() = binding.editText + +} diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/range/RangeChoiceView.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/range/RangeChoiceView.kt new file mode 100644 index 0000000..644c270 --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/range/RangeChoiceView.kt @@ -0,0 +1,167 @@ +package ru.touchin.roboswag.base_filters.range + +import android.content.Context +import android.util.AttributeSet +import android.view.ContextThemeWrapper +import android.view.LayoutInflater +import android.view.inputmethod.EditorInfo +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.withStyledAttributes +import androidx.core.view.updateLayoutParams +import com.google.android.material.slider.RangeSlider +import ru.touchin.roboswag.base_filters.R +import ru.touchin.roboswag.base_filters.databinding.RangeChoiceViewBinding +import ru.touchin.roboswag.base_filters.range.model.FilterRangeItem +import ru.touchin.roboswag.base_filters.range.model.SelectedValues +import kotlin.properties.Delegates + +typealias FilterRangeChanged = (FilterRangeItem) -> Unit + +class RangeChoiceView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) { + + private val defaultTheme = R.style.Theme_FilterRangeSlider + private var binding: RangeChoiceViewBinding by Delegates.notNull() + + private var valueChangedAction: FilterRangeChanged? = null + + private var rangeItem: FilterRangeItem? = null + set(value) { + field = value + + binding.fromInput.inputText = value?.selectedValues?.min?.toString() + ?: value?.start?.toString().orEmpty() + binding.toInput.inputText = value?.selectedValues?.max?.toString() + ?: value?.end?.toString().orEmpty() + + binding.rangeSlider.run { + values = listOf( + value?.selectedValues?.min?.toFloat() ?: value?.start?.toFloat(), + value?.selectedValues?.max?.toFloat() ?: value?.end?.toFloat() + ) + } + } + + init { + context.withStyledAttributes(attrs, R.styleable.FilterRangeChoice, defStyleAttr, defStyleRes) { + val theme = getResourceId(R.styleable.FilterRangeChoice_filterRange_theme, defaultTheme) + val themeContext = ContextThemeWrapper(context, theme) + binding = RangeChoiceViewBinding.inflate(LayoutInflater.from(themeContext), this@RangeChoiceView) + + binding.fromInput.setHint(getString(R.styleable.FilterRangeChoice_filterRange_startHint)) + binding.toInput.setHint(getString(R.styleable.FilterRangeChoice_filterRange_endHint)) + binding.rangeSliderGuideline.updateLayoutParams { + topMargin = getDimension(R.styleable.FilterRangeChoice_filterRange_sliderMargin, 0f).toInt() + } + } + } + + fun setupRangeValues( + rangeFilterItem: FilterRangeItem, + onChangeCallback: FilterRangeChanged + ) { + rangeItem = rangeFilterItem + valueChangedAction = onChangeCallback + + with(binding) { + addChangeValuesListener() + setupRangeSlider(rangeFilterItem) + } + } + + fun resetRangeValue() { + rangeItem = rangeItem?.resetSelectedValues() + } + + private fun addChangeValuesListener() { + binding.fromInput.addChangeValueListener { rangeItem?.setValue(selectedMinValue = it.toIntOrNull()) } + binding.toInput.addChangeValueListener { rangeItem?.setValue(selectedMaxValue = it.toIntOrNull()) } + } + + private fun setupRangeSlider(rangeFilterItem: FilterRangeItem) { + with(binding) { + rangeSlider.apply { + valueFrom = rangeFilterItem.start.toFloat() + valueTo = rangeFilterItem.end.toFloat() + points = rangeFilterItem.intermediates + } + + rangeSlider.addOnChangeListener { _, _, _ -> + fromInput.inputText = rangeSlider.values[0].toInt().toString() + toInput.inputText = rangeSlider.values[1].toInt().toString() + } + + rangeSlider.addOnSliderTouchListener(object : RangeSlider.OnSliderTouchListener { + @SuppressWarnings("detekt.EmptyFunctionBlock") + override fun onStartTrackingTouch(slider: RangeSlider) { + } + + override fun onStopTrackingTouch(slider: RangeSlider) { + binding.rangeSlider.apply { + when(focusedThumbIndex) { + 0 -> { + rangeItem = rangeItem?.setValue(selectedMinValue = from().toInt()) + rangeItem?.let { valueChangedAction?.invoke(it) } + } + 1 -> { + rangeItem = rangeItem?.setValue(selectedMaxValue = to().toInt()) + rangeItem?.let { valueChangedAction?.invoke(it) } + } + } + } + } + }) + } + } + + private fun HintInputView.addChangeValueListener(updateValue: (String) -> FilterRangeItem?) { + getEditText().setOnEditorActionListener { view, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_DONE) { + rangeItem = updateValue(view.text.toString().filterNot { it.isWhitespace() }) + rangeItem?.let { valueChangedAction?.invoke(it) } + } + false + } + } + + private fun RangeSlider.from() = values[0].toInt().toString() + + private fun RangeSlider.to() = values[1].toInt().toString() + + @SuppressWarnings("detekt.ComplexMethod") + private fun FilterRangeItem.setValue( + selectedMaxValue: Int? = selectedValues?.max, + selectedMinValue: Int? = selectedValues?.min + ): FilterRangeItem { + val isMaxValueUpdated = selectedMaxValue != selectedValues?.max + val isMinValueUpdated = selectedMinValue != selectedValues?.min + + val isMinValueOutOfRange = selectedMinValue != null && isMinValueUpdated && selectedMinValue > selectedMaxValue ?: end + val isMaxValueOutOfRange = selectedMaxValue != null && isMaxValueUpdated && selectedMaxValue < selectedMinValue ?: start + + val updatedValues = when { + selectedMaxValue == end && selectedMinValue == start -> null + isMinValueOutOfRange -> SelectedValues( + max = selectedMaxValue ?: end, + min = selectedMaxValue ?: end + ) + isMaxValueOutOfRange -> SelectedValues( + max = selectedMinValue ?: start, + min = selectedMinValue ?: start + ) + else -> SelectedValues( + max = selectedMaxValue?.takeIf { it < end } ?: end, + min = selectedMinValue?.takeIf { it > start } ?: start + ) + } + + return copyWithSelectedValue(selectedValues = updatedValues) + } + + private fun FilterRangeItem.resetSelectedValues() = copyWithSelectedValue(selectedValues = null) + +} diff --git a/base-filters/src/main/java/ru/touchin/roboswag/base_filters/range/model/FilterRangeItem.kt b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/range/model/FilterRangeItem.kt new file mode 100644 index 0000000..1c452de --- /dev/null +++ b/base-filters/src/main/java/ru/touchin/roboswag/base_filters/range/model/FilterRangeItem.kt @@ -0,0 +1,34 @@ +package ru.touchin.roboswag.base_filters.range.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +open class FilterRangeItem( + val id: String, + val start: Int, + val end: Int, + val title: String, + val intermediates: List? = null, + val step: Int? = null, + val selectedValues: SelectedValues? = null +) : Parcelable { + + fun isCorrectValues() = end > start + + fun copyWithSelectedValue(selectedValues: SelectedValues?) = FilterRangeItem( + id = id, + start = start, + end = end, + title = title, + intermediates = intermediates, + step = step, + selectedValues = selectedValues + ) +} + +@Parcelize +data class SelectedValues( + val max: Int, + val min: Int +) : Parcelable diff --git a/base-filters/src/main/res/drawable/background_hint_input.xml b/base-filters/src/main/res/drawable/background_hint_input.xml new file mode 100644 index 0000000..354e22c --- /dev/null +++ b/base-filters/src/main/res/drawable/background_hint_input.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/base-filters/src/main/res/drawable/cursor_background_text_input_view.xml b/base-filters/src/main/res/drawable/cursor_background_text_input_view.xml new file mode 100644 index 0000000..4633444 --- /dev/null +++ b/base-filters/src/main/res/drawable/cursor_background_text_input_view.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/base-filters/src/main/res/layout/range_choice_view.xml b/base-filters/src/main/res/layout/range_choice_view.xml new file mode 100644 index 0000000..6d749ef --- /dev/null +++ b/base-filters/src/main/res/layout/range_choice_view.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + diff --git a/base-filters/src/main/res/layout/view_hint_input.xml b/base-filters/src/main/res/layout/view_hint_input.xml new file mode 100644 index 0000000..1f66eb4 --- /dev/null +++ b/base-filters/src/main/res/layout/view_hint_input.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/base-filters/src/main/res/values/attrs.xml b/base-filters/src/main/res/values/attrs.xml index bd6618a..32e1c69 100644 --- a/base-filters/src/main/res/values/attrs.xml +++ b/base-filters/src/main/res/values/attrs.xml @@ -6,4 +6,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base-filters/src/main/res/values/colors.xml b/base-filters/src/main/res/values/colors.xml new file mode 100644 index 0000000..aed3bb2 --- /dev/null +++ b/base-filters/src/main/res/values/colors.xml @@ -0,0 +1,8 @@ + + + #B9B9B9 + #E35100 + #EE9766 + #E7E7E7 + #E35100 + diff --git a/base-filters/src/main/res/values/strings.xml b/base-filters/src/main/res/values/strings.xml new file mode 100644 index 0000000..6f75b53 --- /dev/null +++ b/base-filters/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + от + до + diff --git a/base-filters/src/main/res/values/styles.xml b/base-filters/src/main/res/values/styles.xml index 3520002..66d37ff 100644 --- a/base-filters/src/main/res/values/styles.xml +++ b/base-filters/src/main/res/values/styles.xml @@ -18,4 +18,55 @@ 16dp + + + + + + + + + + + + From bf55cc957970e17f9874f4bd5f0c76e3c3c895ef Mon Sep 17 00:00:00 2001 From: AnastasiyaK97 Date: Mon, 29 Aug 2022 19:29:18 +0300 Subject: [PATCH 08/26] PR issues --- base-filters/README.md | 46 +++++++------- base-filters/build.gradle | 6 +- .../select_list_item/ListSelectionView.kt | 63 ++++++++++--------- .../adapter/BaseSelectionViewHolder.kt | 2 +- .../adapter/SelectionItemViewHolder.kt | 34 +++++----- .../adapter/SheetSelectionAdapter.kt | 4 +- .../model/BaseSelectionItem.kt | 2 +- base-filters/src/main/res/values/styles.xml | 5 +- 8 files changed, 86 insertions(+), 76 deletions(-) diff --git a/base-filters/README.md b/base-filters/README.md index 09d83de..27c7be0 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,6 +83,7 @@ val selectorView = ListSelectionView(cont ``` kotlin val newContext = ContextThemeWrapper(requireContext(), R.style.Theme_Custom_FilterSelection) val selectorView = ListSelectionView(newContext) - ... - .build() + .Builder() + ... + .build() ``` 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 e095ada..b47bcbc 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 @@ -13,8 +13,8 @@ import ru.touchin.roboswag.base_filters.select_list_item.adapter.SelectionItemVi import ru.touchin.roboswag.base_filters.select_list_item.adapter.SheetSelectionAdapter import ru.touchin.roboswag.base_filters.select_list_item.model.BaseSelectionItem -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. @@ -25,7 +25,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, @@ -33,8 +33,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 @@ -82,38 +82,41 @@ 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() + } } enum class SelectionType { SINGLE_SELECT, MULTI_SELECT } 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 cc81c0c..0fc9a88 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.ListSelectionView import ru.touchin.roboswag.base_filters.select_list_item.model.BaseSelectionItem -class SelectionItemViewHolder(private val binding: SelectionItemBinding, - private val onItemSelectAction: (ItemType) -> Unit, - private val selectionType: ListSelectionView.SelectionType - ) : BaseSelectionViewHolder(binding.root) { +class SelectionItemViewHolder( + private val binding: SelectionItemBinding, + private val onItemSelectAction: (ItemType) -> Unit, + private val selectionType: ListSelectionView.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) { - ListSelectionView.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) { + ListSelectionView.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 c88d331..b8d6449 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.ListSelectionView import ru.touchin.roboswag.base_filters.select_list_item.model.BaseSelectionItem import ru.touchin.roboswag.recyclerview_adapters.adapters.DelegationListAdapter -class SheetSelectionAdapter( +class SheetSelectionAdapter( onItemSelectAction: (ItemType) -> Unit, selectionType: ListSelectionView.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/res/values/styles.xml b/base-filters/src/main/res/values/styles.xml index 3520002..380b4da 100644 --- a/base-filters/src/main/res/values/styles.xml +++ b/base-filters/src/main/res/values/styles.xml @@ -7,10 +7,7 @@ + + + + From aa952617abef23d427c0ce4bc93ee8f16df8bc80 Mon Sep 17 00:00:00 2001 From: Grigorii Date: Fri, 7 Apr 2023 11:59:54 +0400 Subject: [PATCH 25/26] add bottomsheet readme --- bottom-sheet/readme.md | 27 +++++++++++++++++++ .../bottomsheet/BottomSheetOptions.kt | 3 +++ 2 files changed, 30 insertions(+) create mode 100644 bottom-sheet/readme.md diff --git a/bottom-sheet/readme.md b/bottom-sheet/readme.md new file mode 100644 index 0000000..436fcc2 --- /dev/null +++ b/bottom-sheet/readme.md @@ -0,0 +1,27 @@ +# BottomSheet Utils + +- `BaseBottomSheet` - класс, содержащий парамерты `BottomSheetOptions` + +- `DefaultBottomSheet` - класс с классическим хедером и скруглением, в котором нужно переопределить `createContentView()` + +## BottomSheetOptions +- `styleId` - xml-стиль, в котором можно задать скругление +- `canDismiss` - может ли модалка быть срыта по тапу/свайпу/backButton +- `canTouchOutside` - возможность передавать жесты под модалкой +- `isSkipCollapsed` - убирает промежуточное состояние модалки +- `isFullscreen` - модалка откроется на весь экран, даже при маленьком контенте +- `isShiftedWithKeyboard` - модалка будет полностью подниматься при открытии клавиатуры +- `defaultDimAmount` - константное затемнение +- `animatedMaxDimAmount` - максимальное затемнение, при этом будет анимироваться в зависимости от offset +- `fadeAnimationOptions` - позволяет настроить fade анимацию при изменении высоты +- `heightStatesOptions` - позволяет задать 3 состояния высоты модалки + +## ContentFadeAnimationOptions +- `foregroundRes` - drawableId, который будет показыватся сверху во время анимации +- `duration` - длительность fade анимации +- `minAlpha` - минимальная прозрачность во время анимации + +## HeightStatesOptions +- `collapsedHeightPx` - высота минимального состояния +- `halfExpandedHalfPx` - высота промежуточного состояния +- `canTouchOutsideWhenCollapsed` - могут ли жесты передаватья под модалку в минимальном состоянии diff --git a/bottom-sheet/src/main/java/ru/touchin/roboswag/bottomsheet/BottomSheetOptions.kt b/bottom-sheet/src/main/java/ru/touchin/roboswag/bottomsheet/BottomSheetOptions.kt index ae73d16..98a28db 100644 --- a/bottom-sheet/src/main/java/ru/touchin/roboswag/bottomsheet/BottomSheetOptions.kt +++ b/bottom-sheet/src/main/java/ru/touchin/roboswag/bottomsheet/BottomSheetOptions.kt @@ -3,6 +3,9 @@ package ru.touchin.roboswag.bottomsheet import androidx.annotation.DrawableRes import androidx.annotation.StyleRes +/** + * See explanation in readme + * */ data class BottomSheetOptions( @StyleRes val styleId: Int? = null, val canDismiss: Boolean = true, From b3cb64eb4480748e714ae263cf46df6e14c2cb47 Mon Sep 17 00:00:00 2001 From: Grigorii Date: Fri, 7 Apr 2023 12:08:00 +0400 Subject: [PATCH 26/26] Add test project link --- bottom-sheet/readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bottom-sheet/readme.md b/bottom-sheet/readme.md index 436fcc2..4ac7334 100644 --- a/bottom-sheet/readme.md +++ b/bottom-sheet/readme.md @@ -25,3 +25,5 @@ - `collapsedHeightPx` - высота минимального состояния - `halfExpandedHalfPx` - высота промежуточного состояния - `canTouchOutsideWhenCollapsed` - могут ли жесты передаватья под модалку в минимальном состоянии + +Тестовый проект: https://github.com/duwna/BottomSheets