From e8c296670107487e245553ecd4cd23120215e143 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Tue, 19 May 2020 01:30:33 +0530 Subject: [PATCH] Added bottom sheet dialog search --- .travis.yml | 2 +- README.md | 24 ++- country/build.gradle | 2 +- .../view/fragment/CountryListBottomSheet.kt | 199 ++++++++++++++++++ ...Dialog.kt => CountryListDialogFragment.kt} | 2 +- .../java/com/ancient/example/MainActivity.kt | 21 +- example/src/main/res/layout/main_activity.xml | 8 + example/src/main/res/values/strings.xml | 1 + screenshots/country.gif | Bin 9730102 -> 6830682 bytes 9 files changed, 244 insertions(+), 15 deletions(-) create mode 100644 country/src/main/java/com/ancient/country/view/fragment/CountryListBottomSheet.kt rename country/src/main/java/com/ancient/country/view/fragment/{CountryListDialog.kt => CountryListDialogFragment.kt} (98%) diff --git a/.travis.yml b/.travis.yml index b9d495a..9e6d24c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ deploy: provider: releases api-key: $GITHUB_API_KEY file: - - $TRAVIS_BUILD_DIR/app/build/outputs/apk/release/example-release.apk + - $TRAVIS_BUILD_DIR/example/build/outputs/apk/release/example-release.apk - $TRAVIS_BUILD_DIR/country/build/outputs/aar/country-release.aar skip_cleanup: true name: $TRAVIS_TAG diff --git a/README.md b/README.md index 14435a1..32ddca9 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Add this below in your app.gradle ```gradle // app.gradle dependencies { - implementation 'com.ancient.country:country:1.0.6' + implementation 'com.ancient.country:country:1.0.7' } ``` @@ -65,15 +65,25 @@ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) Starting country selection as a dialog ```kotlin -val fragmentTransaction = supportFragmentManager.beginTransaction() -val dialogFragment = CountryListDialog() -dialogFragment.countrySelection = { aCountry -> - dialogFragment.dismiss() - //Handle your selection +val ft = supportFragmentManager.beginTransaction() +val countryListDialogFragment = CountryListDialogFragment() +countryListDialogFragment.countrySelection = { + changeValues(it) + countryListDialogFragment.dismiss() } -dialogFragment.show(fragmentTransaction, "dialog") +countryListDialogFragment.show(ft, "dialog") ``` +Starting country selection as a bottom sheet dialog +```kotlin +val ft = supportFragmentManager.beginTransaction() +val countryListBottomSheet = CountryListBottomSheet() +countryListBottomSheet.countrySelection = { + changeValues(it) + countryListBottomSheet.dismiss() +} +countryListBottomSheet.show(ft, "bottom_sheet_dialog") +``` ## Buy Me a Coffee diff --git a/country/build.gradle b/country/build.gradle index e62bffe..f3d0f3b 100644 --- a/country/build.gradle +++ b/country/build.gradle @@ -12,7 +12,7 @@ ext { libraryDescription = 'Search country list and pick one from it' siteUrl = 'https://github.com/naveenkumarn27/country' gitUrl = 'https://github.com/naveenkumarn27/country.git' - libraryVersion = '1.0.6' + libraryVersion = '1.0.7' developerId = 'naveenkumarn27' developerName = 'Naveen Kumar Kuppan' developerEmail = 'naveenkumarn2@gmail.com' diff --git a/country/src/main/java/com/ancient/country/view/fragment/CountryListBottomSheet.kt b/country/src/main/java/com/ancient/country/view/fragment/CountryListBottomSheet.kt new file mode 100644 index 0000000..a52f410 --- /dev/null +++ b/country/src/main/java/com/ancient/country/view/fragment/CountryListBottomSheet.kt @@ -0,0 +1,199 @@ +package com.ancient.country.view.fragment + +import android.content.ActivityNotFoundException +import android.content.Intent +import android.os.Bundle +import android.speech.RecognizerIntent +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.ancient.country.BuildConfig +import com.ancient.country.R +import com.ancient.country.databinding.DialogCountrySearchBinding +import com.ancient.country.extention.autoCleared +import com.ancient.country.model.CountryModel +import com.ancient.country.view.adapter.CountryListAdapter +import com.ancient.country.view.viewmodel.CountryListViewModel +import com.ancient.country.view.viewmodel.SearchViewModel +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.google.android.material.floatingactionbutton.FloatingActionButton +import java.util.* + +/** + * Created by ancientinc on 19/05/20. + **/ +class CountryListBottomSheet : BottomSheetDialogFragment(), SwipeRefreshLayout.OnRefreshListener { + + private var mRecyclerView by autoCleared() + private var mLoaderWidget by autoCleared() + private var mSwipeRefreshLayout by autoCleared() + private var mActionButton by autoCleared() + + private var countrySearchModel: CountryListViewModel by autoCleared() + private var searchViewModel: SearchViewModel by autoCleared() + + private var adapter: CountryListAdapter by autoCleared() + + var countrySelection: ((CountryModel) -> Unit)? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + + val view = inflater.inflate(R.layout.dialog_country_search, container, false) + + searchViewModel = ViewModelProvider(this).get(SearchViewModel::class.java) + + val binding = DialogCountrySearchBinding.bind(view) + binding.viewModel = searchViewModel + + binding.lifecycleOwner = this.viewLifecycleOwner + + return view + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + initializeView(view) + + searchViewModel.searchText.observe(viewLifecycleOwner, androidx.lifecycle.Observer { + processValue(it) + }) + + searchViewModel.backNavigation.observe(viewLifecycleOwner, androidx.lifecycle.Observer { + dismiss() + }) + + searchViewModel.voiceSearch.observe(viewLifecycleOwner, androidx.lifecycle.Observer { + promptSpeechInput() + }) + + searchViewModel.searchHintText.value = getSearchHintText() + + createRequest(true) + } + + private fun searchEntered(aSearchValue: String) { + countrySearchModel.loadCountryList(aSearchValue) + } + + private fun getSearchHintText(): String { + return getString(R.string.search_country) + } + + private fun processValue(it: String) { + searchEntered(it) + } + + /** + * Showing google speech input dialog + */ + private fun promptSpeechInput() { + val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH) + intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM) + intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault()) + intent.putExtra(RecognizerIntent.EXTRA_PROMPT, getString(R.string.speech_prompt)) + try { + startActivityForResult(intent, REQ_CODE_SPEECH_INPUT) + } catch (a: ActivityNotFoundException) { + if (BuildConfig.DEBUG) { + Log.d(TAG, a.localizedMessage ?: "") + } + } + } + + private fun initializeView(aView: View) { + + mRecyclerView = aView.findViewById(R.id.recyclerview) + mLoaderWidget = aView.findViewById(R.id.loader_widget) + mSwipeRefreshLayout = aView.findViewById(R.id.refresh_layout) + mActionButton = aView.findViewById(R.id.action_button) + + mRecyclerView.layoutManager = GridLayoutManager(context, 1) + mRecyclerView.itemAnimator = DefaultItemAnimator() + + mSwipeRefreshLayout.setOnRefreshListener(this) + + hideActionButton() + } + + fun setHasFixedSize(aFixedSize: Boolean) { + mRecyclerView.setHasFixedSize(aFixedSize) + } + + protected fun setActionIconDrawable(@DrawableRes aDrawable: Int) { + mActionButton.setImageResource(aDrawable) + } + + protected fun setActionClickListener(aClickListener: View.OnClickListener) { + mActionButton.visibility = View.VISIBLE + mActionButton.setOnClickListener(aClickListener) + } + + protected fun hideActionButton() { + mActionButton.visibility = View.GONE + } + + protected fun startLoadingRequest() { + mSwipeRefreshLayout.isRefreshing = true + } + + protected fun setNoResult() { + mSwipeRefreshLayout.isRefreshing = false + mLoaderWidget.visibility = View.VISIBLE + } + + protected fun setResult() { + mSwipeRefreshLayout.isRefreshing = false + mLoaderWidget.visibility = View.GONE + } + + protected fun setAdapter(aAdapter: RecyclerView.Adapter<*>) { + mRecyclerView.adapter = aAdapter + } + + override fun onRefresh() { + createRequest(isStart = false) + } + + private fun createRequest(isStart: Boolean) { + + if (isStart) { + + countrySearchModel = ViewModelProvider(this).get(CountryListViewModel::class.java) + + adapter = CountryListAdapter { + countrySelection?.invoke(it) + } + + setAdapter(adapter) + + countrySearchModel.countryList.observe(viewLifecycleOwner, androidx.lifecycle.Observer { + + if (it != null && it.isNotEmpty()) { + adapter.submitList(it) + setResult() + } else { + adapter.submitList(mutableListOf()) + setNoResult() + } + }) + } + + startLoadingRequest() + + countrySearchModel.loadCountryList() + } + + companion object { + private const val REQ_CODE_SPEECH_INPUT = 100 + private const val TAG = "Search" + } +} \ No newline at end of file diff --git a/country/src/main/java/com/ancient/country/view/fragment/CountryListDialog.kt b/country/src/main/java/com/ancient/country/view/fragment/CountryListDialogFragment.kt similarity index 98% rename from country/src/main/java/com/ancient/country/view/fragment/CountryListDialog.kt rename to country/src/main/java/com/ancient/country/view/fragment/CountryListDialogFragment.kt index d96ccc7..a02429a 100644 --- a/country/src/main/java/com/ancient/country/view/fragment/CountryListDialog.kt +++ b/country/src/main/java/com/ancient/country/view/fragment/CountryListDialogFragment.kt @@ -31,7 +31,7 @@ import java.util.* /** * Created by ancientinc on 11/05/20. **/ -class CountryListDialog : DialogFragment(), SwipeRefreshLayout.OnRefreshListener { +class CountryListDialogFragment : DialogFragment(), SwipeRefreshLayout.OnRefreshListener { private var mRecyclerView by autoCleared() private var mLoaderWidget by autoCleared() diff --git a/example/src/main/java/com/ancient/example/MainActivity.kt b/example/src/main/java/com/ancient/example/MainActivity.kt index 42435df..74f0464 100644 --- a/example/src/main/java/com/ancient/example/MainActivity.kt +++ b/example/src/main/java/com/ancient/example/MainActivity.kt @@ -10,7 +10,8 @@ import com.ancient.country.model.CountryModel import com.ancient.country.utils.RequestCode import com.ancient.country.utils.RequestParam import com.ancient.country.view.activity.CountrySearchActivity -import com.ancient.country.view.fragment.CountryListDialog +import com.ancient.country.view.fragment.CountryListBottomSheet +import com.ancient.country.view.fragment.CountryListDialogFragment import com.ancient.example.databinding.MainActivityBinding import com.google.android.material.snackbar.Snackbar @@ -32,12 +33,22 @@ class MainActivity : AppCompatActivity() { dataBinding.searchDialog.setOnClickListener { val ft = supportFragmentManager.beginTransaction() - val dialogFragment = CountryListDialog() - dialogFragment.countrySelection = { + val countryListDialogFragment = CountryListDialogFragment() + countryListDialogFragment.countrySelection = { changeValues(it) - dialogFragment.dismiss() + countryListDialogFragment.dismiss() } - dialogFragment.show(ft, "dialog") + countryListDialogFragment.show(ft, "dialog") + } + + dataBinding.searchBottomSheet.setOnClickListener { + val ft = supportFragmentManager.beginTransaction() + val countryListBottomSheet = CountryListBottomSheet() + countryListBottomSheet.countrySelection = { + changeValues(it) + countryListBottomSheet.dismiss() + } + countryListBottomSheet.show(ft, "bottom_sheet_dialog") } } diff --git a/example/src/main/res/layout/main_activity.xml b/example/src/main/res/layout/main_activity.xml index 74e85d1..56cd2a1 100644 --- a/example/src/main/res/layout/main_activity.xml +++ b/example/src/main/res/layout/main_activity.xml @@ -79,5 +79,13 @@ android:text="@string/country_search_as_dialog" android:textColor="@color/icon_white_color" /> +