Skip to content

Commit

Permalink
Merge pull request #8 from Trendyol/feature/selection-dialog-search
Browse files Browse the repository at this point in the history
Feature/selection dialog search
  • Loading branch information
bilgehankalkan authored Dec 11, 2019
2 parents f488331 + 4d7b79c commit faa84e6
Show file tree
Hide file tree
Showing 23 changed files with 370 additions and 61 deletions.
3 changes: 3 additions & 0 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified images/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/dialogs-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 22 additions & 3 deletions libraries/dialogs/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

<img src="https://raw.githubusercontent.com/Trendyol/android-ui-components/master/images/dialogs-1.png" width="240"/> <img src="https://raw.githubusercontent.com/Trendyol/android-ui-components/master/images/dialogs-2.png" width="240"/> <img src="https://raw.githubusercontent.com/Trendyol/android-ui-components/master/images/dialogs-3.png" width="240"/>
<img src="https://raw.githubusercontent.com/Trendyol/android-ui-components/master/images/dialogs-1.png" width="240"/> <img src="https://raw.githubusercontent.com/Trendyol/android-ui-components/master/images/dialogs-2.png" width="240"/> <img src="https://raw.githubusercontent.com/Trendyol/android-ui-components/master/images/dialogs-3.png" width="240"/> <img src="https://raw.githubusercontent.com/Trendyol/android-ui-components/master/images/dialogs-4.png" width="240"/>

$dialogsVersion = dialogs-1.0.2 [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
$dialogsVersion = dialogs-1.0.3 [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

## Dialogs
Dialogs is a bunch of BottomSheetDialogs to use in app to show user an information, agreement or list.
Expand Down Expand Up @@ -89,6 +89,9 @@ All **Info Dialog** arguments plus these arguments will be applicable to show se
| `items` | List<Pair<Boolean, String>> | Item list that will be listed on dialog. | null |
| `showItemsAsHtml` | Boolean | Item texts will be parsed as Html if this flag setted as true. | false |
| `onItemSelectedListener` | (DialogFragment, Int) -> Unit | Listener to notify selected index. | null |
| `enableSearch` | Boolean | Enables search function in given `items` | false |
| `showClearSearchButton` | Boolean | Shows clean button on the right of the search input line. | false |
| `searchHint` | String | Hint to show on search input line. | "" |

Sample usage:
```kotlin
Expand All @@ -104,10 +107,26 @@ selectionDialog {
}
```

Sample usage with search:
```kotlin
selectionDialog {
title = "Selection Dialog with Search Title"
content = getContent()
items = getItemsAsHtml()
showItemsAsHtml = true
onItemSelectedListener = { dialog, index ->
onItemSelected(index)
}
enableSearch = true
showClearSearchButton = true
searchHint = "Hint for search"
}
```

## TODOs
* Implement ListDialog.
* ~~Implement SelectionDialog~~
* Implement search line.
* ~~Implement search line.~
* Implement multiple selectable type.
* Provide theme for more styling.
* Update builder for Java.
Expand Down
5 changes: 4 additions & 1 deletion libraries/dialogs/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ apply plugin: 'kotlin-kapt'
apply plugin: 'com.github.dcendents.android-maven'

group="com.trendyol.ui-components"
version="1.0.2"
version="1.0.3"

android {
compileSdkVersion 29
Expand Down Expand Up @@ -40,4 +40,7 @@ dependencies {
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
kapt 'androidx.lifecycle:lifecycle-compiler:2.1.0'
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import com.trendyol.dialog.R

abstract class BaseBottomSheetDialog<DB : ViewDataBinding> : BottomSheetDialogFragment() {

protected lateinit var binding: DB
lateinit var binding: DB

@LayoutRes
abstract fun getLayoutResId(): Int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,3 @@ internal fun AppCompatImageView.setDrawableResource(@DrawableRes drawableResId:
internal fun View.setVisibility(isVisible: Boolean) {
visibility = if (isVisible) View.VISIBLE else View.GONE
}

@BindingAdapter("items")
internal fun RecyclerView.setItems(items: List<Pair<Boolean, CharSequence>>?) {
if (items != null) {
(adapter as? DialogListAdapter)?.setItems(items)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ class SelectionDialogBuilder internal constructor() : InfoDialogBuilder() {
var items: List<Pair<Boolean, CharSequence>> = emptyList()
var showItemsAsHtml: Boolean = false
var onItemSelectedListener: ((DialogFragment, Int) -> Unit)? = null
var enableSearch: Boolean = false
var showClearSearchButton: Boolean = false
var searchHint: String = ""

internal fun buildSelectionDialog(block: SelectionDialogBuilder.() -> Unit): DialogFragment =
SelectionDialogBuilder().apply(block).let {
Expand All @@ -72,7 +75,10 @@ class SelectionDialogBuilder internal constructor() : InfoDialogBuilder() {
contentImage = it.contentImage,
items = it.items,
showItemsAsHtml = it.showItemsAsHtml,
onItemSelectedListener = it.onItemSelectedListener
onItemSelectedListener = it.onItemSelectedListener,
enableSearch = it.enableSearch,
showClearSearchButton = it.showClearSearchButton,
searchHint = it.searchHint
)
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package com.trendyol.uicomponents.dialogs

import android.text.SpannableString
import android.view.ViewGroup.FOCUS_AFTER_DESCENDANTS
import androidx.annotation.DrawableRes
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.trendyol.dialog.R
import com.trendyol.dialog.databinding.FragmentDialogBinding
import com.trendyol.uicomponents.dialogs.list.DialogListAdapter
import com.trendyol.uicomponents.dialogs.list.DialogListViewModel

class DialogFragment internal constructor(
private val title: String? = null,
Expand All @@ -20,14 +26,22 @@ class DialogFragment internal constructor(
private val rightButtonClickListener: ((DialogFragment) -> Unit)? = null,
private val items: List<Pair<Boolean, CharSequence>>? = null,
private val showItemsAsHtml: Boolean = false,
private val onItemSelectedListener: ((DialogFragment, Int) -> Unit)? = null
private val onItemSelectedListener: ((DialogFragment, Int) -> Unit)? = null,
private val enableSearch: Boolean = false,
private val showClearSearchButton: Boolean = false,
private val searchHint: String = ""
) : BaseBottomSheetDialog<FragmentDialogBinding>() {

private val itemsAdapter by lazy { DialogListAdapter(showItemsAsHtml) }
private val dialogListViewModel by lazy {
ViewModelProviders.of(this)[DialogListViewModel::class.java]
}

override fun getLayoutResId(): Int = R.layout.fragment_dialog

override fun setUpView() {
animateCornerRadiusWithStateChanged()

with(binding) {
imageClose.setOnClickListener {
dismiss()
Expand All @@ -40,11 +54,32 @@ class DialogFragment internal constructor(
rightButtonClickListener?.invoke(this@DialogFragment)
}

recyclerViewItems.adapter = itemsAdapter
recyclerViewItems.isNestedScrollingEnabled = false
if (items != null) {
recyclerViewItems.adapter = itemsAdapter
recyclerViewItems.isNestedScrollingEnabled = false

itemsAdapter.onItemSelectedListener = { position ->
onItemSelectedListener?.invoke(this@DialogFragment, position)
itemsAdapter.onItemSelectedListener = { position ->
dialogListViewModel.onSelectionChanged(position)
}

editTextSearch.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
setBottomSheetState(BottomSheetBehavior.STATE_EXPANDED)
}
}

editTextSearch.doAfterTextChanged {
dialogListViewModel.search(it.toString())
}

imageClearSearchQuery.setOnClickListener {
editTextSearch.text?.clear()
dialogListViewModel.clearSearch()
}

constraintLayout.descendantFocusability = FOCUS_AFTER_DESCENDANTS

setUpViewModel(items)
}
}
}
Expand All @@ -58,7 +93,10 @@ class DialogFragment internal constructor(
contentImage = contentImage,
leftButtonText = leftButtonText,
rightButtonText = rightButtonText,
listItems = items
isListVisible = items != null,
isSearchEnabled = enableSearch,
isClearSearchButtonVisible = showClearSearchButton,
searchHint = searchHint
)

binding.viewState = viewState
Expand All @@ -69,6 +107,18 @@ class DialogFragment internal constructor(
show(fragmentManager, TAG)
}

private fun setUpViewModel(items: List<Pair<Boolean, CharSequence>>) {
with(dialogListViewModel) {
getDialogSearchItemsLiveData().observe(viewLifecycleOwner, Observer { items ->
itemsAdapter.setItems(items)
})
getLastChangedItemLiveData().observeNonNull(viewLifecycleOwner) { position ->
onItemSelectedListener?.invoke(this@DialogFragment, position)
}
setInitialItems(items)
}
}

companion object {

const val TAG: String = "TRENDYOL_BOTTOM_SHEET_DIALOG"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ data class DialogViewState(
private val content: CharSequence,
val showContentAsHtml: Boolean,
@DrawableRes val contentImage: Int?,
val leftButtonText: String? = null,
val rightButtonText: String? = null,
val listItems: List<Pair<Boolean, CharSequence>>? = null
val leftButtonText: String?,
val rightButtonText: String?,
val isListVisible: Boolean,
val isSearchEnabled: Boolean,
val isClearSearchButtonVisible: Boolean,
val searchHint: String
) {

fun isLeftButtonVisible(): Boolean = leftButtonText != null
Expand All @@ -27,6 +30,4 @@ data class DialogViewState(
fun isContentVisible(): Boolean = content.isNotEmpty()

fun isContentImageVisible(): Boolean = contentImage != null

fun isListVisible(): Boolean = !listItems.isNullOrEmpty()
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
package com.trendyol.uicomponents.dialogs

import android.animation.ObjectAnimator
import android.app.Dialog
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.DimenRes
import androidx.annotation.LayoutRes
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.trendyol.dialog.R

internal fun <T : ViewDataBinding> ViewGroup?.inflate(
@LayoutRes layoutId: Int,
Expand All @@ -21,3 +31,59 @@ internal fun <T : ViewDataBinding> ViewGroup?.inflate(
attachToParent
)
}

internal fun Context.pixel(@DimenRes dimenResId: Int): Float =
resources.getDimensionPixelSize(dimenResId).toFloat()

internal fun <V : View> BottomSheetDialogFragment.getBottomSheetBehavior(dialog: Dialog? = getDialog()): BottomSheetBehavior<V>? {
return dialog
?.findViewById<V>(com.google.android.material.R.id.design_bottom_sheet)
?.let { BottomSheetBehavior.from(it) }
}

internal fun BottomSheetDialogFragment.setBottomSheetState(state: Int) {
getBottomSheetBehavior<View>()?.state = state
}

internal fun <T> LiveData<T>.observeNonNull(owner: LifecycleOwner, onNotNull: (T) -> Unit) =
observe(owner, Observer {
if (it != null) {
onNotNull.invoke(it)
}
})

internal fun DialogFragment.animateCornerRadiusWithStateChanged() {
val expandAnimator =
ObjectAnimator.ofFloat(
binding.cardView,
"radius",
requireContext().pixel(R.dimen.dialogs_corner_radius),
0F
).apply {
duration = 250
}
val collapseAnimator =
ObjectAnimator.ofFloat(
binding.cardView,
"radius",
0F,
requireContext().pixel(R.dimen.dialogs_corner_radius)
).apply {
duration = 250
}

getBottomSheetBehavior<View>()?.setBottomSheetCallback(object :
BottomSheetBehavior.BottomSheetCallback() {

override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
expandAnimator.start()
} else if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
collapseAnimator.start()
}
}

override fun onSlide(bottomSheet: View, slideOffset: Float) {
}
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.trendyol.uicomponents.dialogs

import androidx.annotation.MainThread
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean

/**
* A lifecycle-aware observable that sends only new updates after subscription, used for events like
* navigation and Snackbar messages.
*
*
* This avoids a common problem with events: on configuration change (like rotation) an update
* can be emitted if the observer is active. This LiveData only calls the observable if there's an
* explicit call to setValue() or call().
*
*
* Note that only one observer is going to be notified of changes.
*
* Source: https://github.com/android/play-billing-samples/blob/master/ClassyTaxi/android/ClassyTaxi/app/src/main/java/com/example/subscriptions/ui/SingleLiveEvent.kt
*/
internal class SingleLiveEvent<T> : MutableLiveData<T>() {

private val pending = AtomicBoolean(false)

@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
// Observe the internal MutableLiveData
super.observe(owner, Observer { t ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}

@MainThread
override fun setValue(t: T?) {
pending.set(true)
super.setValue(t)
}
}
Loading

0 comments on commit faa84e6

Please sign in to comment.