diff --git a/search/src/main/kotlin/com/alfresco/content/search/SearchFragment.kt b/search/src/main/kotlin/com/alfresco/content/search/SearchFragment.kt index f291d27ce..b84a2a526 100644 --- a/search/src/main/kotlin/com/alfresco/content/search/SearchFragment.kt +++ b/search/src/main/kotlin/com/alfresco/content/search/SearchFragment.kt @@ -28,8 +28,8 @@ import com.alfresco.content.fragmentViewModelWithArgs import com.alfresco.content.getLocalizedName import com.alfresco.content.hideSoftInput import com.alfresco.content.models.CategoriesItem +import com.alfresco.content.search.components.ComponentBuilder import com.alfresco.content.search.components.ComponentMetaData -import com.alfresco.content.search.components.CreateComponentsSheet import com.alfresco.content.search.databinding.FragmentSearchBinding import com.alfresco.content.simpleController import kotlin.coroutines.Continuation @@ -328,20 +328,14 @@ class SearchFragment : Fragment(), MavericksView { if (data.selectedName.isNotEmpty()) { (chipView as FilterChip).isChecked = true } - when (data.category.component?.selector) { - ChipComponentType.TEXT.component -> { - viewLifecycleOwner.lifecycleScope.launch { - val result = showComponentSheetDialog(requireContext(), data) - Logger.d("component result = ", result) - if (result != null) { - viewModel.updateChipName(state, data, result.name) - } else { - val isSelected = data.selectedName.isNotEmpty() - viewModel.updateSelected(state, data, isSelected) - } - } - } - else -> { + viewLifecycleOwner.lifecycleScope.launch { + val result = showComponentSheetDialog(requireContext(), data) + Logger.d("component result = ", result) + if (result != null) { + viewModel.updateChipComponentResult(state, data, result) + } else { + val isSelected = data.selectedName.isNotEmpty() + viewModel.updateSelected(state, data, isSelected) } } } @@ -353,7 +347,7 @@ class SearchFragment : Fragment(), MavericksView { searchChipCategory: SearchChipCategory ) = withContext(Dispatchers.Main) { suspendCoroutine { - CreateComponentsSheet.Builder(context, searchChipCategory) + ComponentBuilder(context, searchChipCategory) .onApply { name, query -> executeContinuation(it, name, query) } diff --git a/search/src/main/kotlin/com/alfresco/content/search/SearchViewModel.kt b/search/src/main/kotlin/com/alfresco/content/search/SearchViewModel.kt index d83ff84f5..32a994652 100644 --- a/search/src/main/kotlin/com/alfresco/content/search/SearchViewModel.kt +++ b/search/src/main/kotlin/com/alfresco/content/search/SearchViewModel.kt @@ -16,6 +16,7 @@ import com.alfresco.content.listview.ListViewModel import com.alfresco.content.listview.ListViewState import com.alfresco.content.models.AppConfigModel import com.alfresco.content.models.SearchItem +import com.alfresco.content.search.components.ComponentMetaData import java.util.concurrent.CancellationException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -203,17 +204,19 @@ class SearchViewModel( } /** - * update chip name after apply or reset the component + * update chip component data after apply or reset the component */ - fun updateChipName(state: SearchResultsState, model: SearchChipCategory, name: String) { + fun updateChipComponentResult(state: SearchResultsState, model: SearchChipCategory, metaData: ComponentMetaData) { val list = mutableListOf() - state.listSearchCategoryChips?.forEachIndexed { index, obj -> + state.listSearchCategoryChips?.forEach { obj -> if (obj == model) { list.add( SearchChipCategory( obj.category, - isSelected = name.isNotEmpty(), selectedName = name, selectedQuery = obj.selectedQuery + isSelected = metaData.name.isNotEmpty(), + selectedName = metaData.name, + selectedQuery = metaData.query ) ) } else diff --git a/search/src/main/kotlin/com/alfresco/content/search/components/ComponentBuilder.kt b/search/src/main/kotlin/com/alfresco/content/search/components/ComponentBuilder.kt new file mode 100644 index 000000000..c866fd74c --- /dev/null +++ b/search/src/main/kotlin/com/alfresco/content/search/components/ComponentBuilder.kt @@ -0,0 +1,59 @@ +package com.alfresco.content.search.components + +import android.content.Context +import androidx.appcompat.app.AppCompatActivity +import androidx.core.os.bundleOf +import androidx.fragment.app.Fragment +import com.airbnb.mvrx.Mavericks +import com.alfresco.content.search.SearchChipCategory + +internal typealias ComponentApplyCallback = (String, String) -> Unit +internal typealias ComponentResetCallback = (String, String) -> Unit +internal typealias ComponentCancelCallback = () -> Unit + +/** + * Builder for build the component sheet + */ + data class ComponentBuilder( + val context: Context, + val searchChipCategory: SearchChipCategory, + var onApply: ComponentApplyCallback? = null, + var onReset: ComponentResetCallback? = null, + var onCancel: ComponentCancelCallback? = null + ) { + + /** + * Component sheet apply callback + */ + fun onApply(callback: ComponentApplyCallback?) = + apply { this.onApply = callback } + + /** + * Component sheet reset callback + */ + fun onReset(callback: ComponentResetCallback?) = + apply { this.onReset = callback } + + /** + * Component sheet cancel callback + */ + fun onCancel(callback: ComponentCancelCallback?) = + apply { this.onCancel = callback } + + /** + * Component sheet show method + */ + fun show() { + val fragmentManager = when (context) { + is AppCompatActivity -> context.supportFragmentManager + is Fragment -> context.childFragmentManager + else -> throw IllegalArgumentException() + } + CreateComponentsSheet().apply { + arguments = bundleOf(Mavericks.KEY_ARG to searchChipCategory) + onApply = this@ComponentBuilder.onApply + onReset = this@ComponentBuilder.onReset + onCancel = this@ComponentBuilder.onCancel + }.show(fragmentManager, CreateComponentsSheet::class.java.simpleName) + } + } diff --git a/search/src/main/kotlin/com/alfresco/content/search/components/CreateComponentsSheet.kt b/search/src/main/kotlin/com/alfresco/content/search/components/CreateComponentsSheet.kt index 26fa1f14f..91121c5f5 100644 --- a/search/src/main/kotlin/com/alfresco/content/search/components/CreateComponentsSheet.kt +++ b/search/src/main/kotlin/com/alfresco/content/search/components/CreateComponentsSheet.kt @@ -2,13 +2,11 @@ package com.alfresco.content.search.components import android.content.Context import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.appcompat.app.AppCompatActivity -import androidx.core.os.bundleOf -import androidx.fragment.app.Fragment -import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksView import com.airbnb.mvrx.MavericksViewModel @@ -16,19 +14,86 @@ import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import com.alfresco.content.models.Options import com.alfresco.content.search.ChipComponentType import com.alfresco.content.search.R import com.alfresco.content.search.SearchChipCategory import com.alfresco.content.search.databinding.SheetComponentCreateBinding import com.alfresco.ui.BottomSheetDialogFragment -internal data class ComponentCreateState(val parent: SearchChipCategory) : MavericksState +internal data class ComponentCreateState( + val parent: SearchChipCategory +) : MavericksState internal class ComponentCreateViewModel( val context: Context, stateChipCreate: ComponentCreateState ) : MavericksViewModel(stateChipCreate) { + private var listOptionsData: MutableList = mutableListOf() + + fun buildCheckListModel() = withState { state -> + + if (state.parent.selectedQuery.isNotEmpty()) { + if (state.parent.selectedQuery.contains(",")) { + val arrayQuery = state.parent.selectedQuery.split(",") + val arrayName = state.parent.selectedName.split(",") + + arrayQuery.forEachIndexed { index, query -> + listOptionsData.add(ComponentMetaData(arrayName[index], query)) + } + } else { + listOptionsData.add(ComponentMetaData(state.parent.selectedName, state.parent.selectedQuery)) + } + } + } + + fun updateTextComponentData(name: String) = withState { state -> + val obj = SearchChipCategory( + category = state.parent.category, + isSelected = state.parent.isSelected, + selectedName = name, + selectedQuery = state.parent.category.component?.settings?.field ?: "" + ) + + setState { copy(parent = obj) } + } + + fun updateComponentData(name: String, query: String) = withState { state -> + + if (listOptionsData.find { it.query == query } == null) { + listOptionsData.add(ComponentMetaData(name, query)) + } else { + val list = listOptionsData.filter { it.query != query }.toMutableList() + listOptionsData = list + } + + val selectedName = listOptionsData.joinToString(",") { it.name } + val selectedQuery = listOptionsData.joinToString(",") { it.query } + + val obj = SearchChipCategory( + category = state.parent.category, + isSelected = state.parent.isSelected, + selectedName = selectedName, + selectedQuery = selectedQuery + ) + + setState { copy(parent = obj) } + } + + fun isOptionSelected(state: ComponentCreateState, options: Options): Boolean { + val selectedQuery = state.parent.selectedQuery + if (selectedQuery.contains(",")) { + selectedQuery.split(",").forEach { query -> + if (query == options.value) + return true + } + } else { + return selectedQuery == options.value + } + return false + } + companion object : MavericksViewModelFactory { override fun create( viewModelContext: ViewModelContext, @@ -37,10 +102,6 @@ internal class ComponentCreateViewModel( } } -internal typealias ComponentApplyCallback = (String, String) -> Unit -internal typealias ComponentResetCallback = (String, String) -> Unit -internal typealias ComponentCancelCallback = () -> Unit - /** * Component sheet for chip components */ @@ -67,81 +128,76 @@ class CreateComponentsSheet : BottomSheetDialogFragment(), MavericksView { dialog?.setOnCancelListener { onCancel?.invoke() } + setupComponents() + setListeners() + } + private fun setupComponents() { withState(viewModel) { state -> - if (state.parent.category.component?.selector == ChipComponentType.TEXT.component) { - binding.textComponent.nameInputLayout.hint = state.parent.category.component?.settings?.placeholder - binding.textComponent.nameInput.setText(state.parent.selectedName) - binding.title.text = getString(R.string.title_text_filter) + when (state.parent.category.component?.selector) { + ChipComponentType.TEXT.component -> { + binding.textComponent.componentParent.visibility = View.VISIBLE + binding.textComponent.nameInputLayout.hint = state.parent.category.component?.settings?.placeholder + binding.textComponent.nameInput.setText(state.parent.selectedName) + binding.title.text = getString(R.string.title_text_filter) + } + ChipComponentType.CHECK_LIST.component -> { + viewModel.buildCheckListModel() + binding.checkListComponent.componentParent.visibility = View.VISIBLE + binding.title.text = getString(R.string.title_file_type) + } } } + } + private fun setListeners() { binding.applyButton.setOnClickListener { withState(viewModel) { state -> - val query = state.parent.category.component?.settings?.field ?: "" - onApply?.invoke(binding.textComponent.nameInput.text.toString(), query) + onApply?.invoke(state.parent.selectedName, state.parent.selectedQuery) dismiss() } } binding.resetButton.setOnClickListener { - println("CreateComponentsSheet.onViewCreated resetButton") onReset?.invoke("", "") dismiss() } binding.cancelButton.setOnClickListener { - println("CreateComponentsSheet.onViewCreated cancelButton") onCancel?.invoke() dismiss() } + + binding.textComponent.nameInputLayout.editText?.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { + // no-op + } + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + // no-op + } + + override fun afterTextChanged(s: Editable?) { + viewModel.updateTextComponentData(s.toString()) + } + }) } override fun invalidate() = withState(viewModel) { state -> - } - /** - * Builder for build the component sheet - */ - data class Builder( - val context: Context, - val searchChipCategory: SearchChipCategory, - var onApply: ComponentApplyCallback? = null, - var onReset: ComponentResetCallback? = null, - var onCancel: ComponentCancelCallback? = null - ) { - - /** - * Component sheet apply callback - */ - fun onApply(callback: ComponentApplyCallback?) = - apply { this.onApply = callback } - - /** - * Component sheet reset callback - */ - fun onReset(callback: ComponentResetCallback?) = - apply { this.onReset = callback } - - /** - * Component sheet cancel callback - */ - fun onCancel(callback: ComponentCancelCallback?) = - apply { this.onCancel = callback } - - /** - * Component sheet show method - */ - fun show() { - val fragmentManager = when (context) { - is AppCompatActivity -> context.supportFragmentManager - is Fragment -> context.childFragmentManager - else -> throw IllegalArgumentException() - } - CreateComponentsSheet().apply { - arguments = bundleOf(Mavericks.KEY_ARG to searchChipCategory) - onApply = this@Builder.onApply - onReset = this@Builder.onReset - onCancel = this@Builder.onCancel - }.show(fragmentManager, CreateComponentsSheet::class.java.simpleName) + binding.checkListComponent.recyclerView.withModels { + state.parent.category + .component?.settings?.options?.forEach { option -> + listViewCheckRow { + id(option.hashCode()) + data(option) + optionSelected(viewModel.isOptionSelected(state, option)) + clickListener { model, _, _, _ -> + viewModel.updateComponentData( + model.data().name ?: "", + model.data().value ?: "" + ) + } + } + } } } } diff --git a/search/src/main/kotlin/com/alfresco/content/search/components/ListViewCheckRow.kt b/search/src/main/kotlin/com/alfresco/content/search/components/ListViewCheckRow.kt new file mode 100644 index 000000000..730e65b5b --- /dev/null +++ b/search/src/main/kotlin/com/alfresco/content/search/components/ListViewCheckRow.kt @@ -0,0 +1,42 @@ +package com.alfresco.content.search.components + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.CompoundButton +import android.widget.FrameLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.alfresco.content.models.Options +import com.alfresco.content.search.databinding.ViewCheckListRowBinding + +@ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) +internal class ListViewCheckRow @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr) { + private val binding = ViewCheckListRowBinding.inflate(LayoutInflater.from(context), this, true) + + @ModelProp + fun setData(options: Options) { + println("ListViewCheckRow.setData") + binding.title.text = options.name + } + + @ModelProp + fun setOptionSelected(isSelected: Boolean) { + binding.checkBox.isChecked = isSelected + } + + @CallbackProp + fun setClickListener(listener: OnClickListener?) { + binding.checkBox.setOnClickListener(listener) + } + + @CallbackProp + fun setCheckedChangeListener(listener: CompoundButton.OnCheckedChangeListener?) { + binding.checkBox.setOnCheckedChangeListener(listener) + } +} diff --git a/search/src/main/res/layout/sheet_component_create.xml b/search/src/main/res/layout/sheet_component_create.xml index fe6267ed1..59b1cdfb0 100644 --- a/search/src/main/res/layout/sheet_component_create.xml +++ b/search/src/main/res/layout/sheet_component_create.xml @@ -54,6 +54,10 @@ android:id="@+id/textComponent" layout="@layout/view_text_component" /> + + @@ -87,8 +91,8 @@ android:layout_width="0dp" android:layout_height="48dp" android:layout_marginStart="@dimen/component_button_margin" - android:text="@string/component_action_reset" android:backgroundTint="@color/color_component_secondary" + android:text="@string/component_action_reset" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/apply_button" app:layout_constraintTop_toTopOf="parent" /> diff --git a/search/src/main/res/layout/view_check_list_component.xml b/search/src/main/res/layout/view_check_list_component.xml new file mode 100644 index 000000000..dfab27dce --- /dev/null +++ b/search/src/main/res/layout/view_check_list_component.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/search/src/main/res/layout/view_check_list_row.xml b/search/src/main/res/layout/view_check_list_row.xml new file mode 100644 index 000000000..c7ae9861d --- /dev/null +++ b/search/src/main/res/layout/view_check_list_row.xml @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/search/src/main/res/layout/view_text_component.xml b/search/src/main/res/layout/view_text_component.xml index aa3fd3a13..e8d498211 100644 --- a/search/src/main/res/layout/view_text_component.xml +++ b/search/src/main/res/layout/view_text_component.xml @@ -1,13 +1,15 @@ + android:layout_height="wrap_content" + android:visibility="gone"> Default Folder Text filter + File type Reset Apply Cancel