Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement autocompleteDropDownMenu #37

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ sealed class Destination(val route: String) {
object CustomView : Destination("customViewScreen")
object MarqueeText: Destination("marqueeText")
object Autofill: Destination("autofill")
object AutoComplete: Destination("autoComplete")
object DogFeed : Destination("dogFeed")
object DogDetails : Destination("dogDetails/{dogId}") {
fun createRoute(dogId: String) = "dogDetails/$dogId"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.google.accompanist.pager.ExperimentalPagerApi
import com.skyyo.samples.application.Destination
import com.skyyo.samples.application.ProfileGraph
import com.skyyo.samples.features.appBarElevation.AppBarElevation
import com.skyyo.samples.features.autoComplete.AutoCompleteScreen
import com.skyyo.samples.features.autofill.AutofillScreen
import com.skyyo.samples.features.autoscroll.AutoScrollScreen
import com.skyyo.samples.features.bottomSheets.BottomSheetScaffoldScreen
Expand Down Expand Up @@ -125,6 +126,7 @@ fun PopulatedNavHost(
composable(Destination.CustomView.route) { CustomViewScreen() }
composable(Destination.MarqueeText.route) { MarqueeTextScreen() }
composable(Destination.Autofill.route) { AutofillScreen() }
composable(Destination.AutoComplete.route) { AutoCompleteScreen() }
navigation(
route = ProfileGraph.route,
startDestination = ProfileGraph.Profile.route
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.skyyo.samples.features.autoComplete

import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import com.google.android.material.textfield.TextInputLayout
import com.skyyo.samples.R

@Composable
fun AndroidViewTextFieldWithDropDownSample(
modifier: Modifier = Modifier,
suggestions: List<String>,
selectedValue: String = "",
onSelect: (Int) -> Unit = {},
) {
val context = LocalContext.current
val adapter = remember(suggestions) {
ArrayAdapter(context, android.R.layout.simple_list_item_1, suggestions)
}
val textInputLayout = remember {
(TextInputLayout.inflate(
context,
R.layout.text_input_field,
null
) as TextInputLayout).also { til ->
(til.editText as AutoCompleteTextView).setOnItemClickListener { _, _, index, _ ->
onSelect(index)
}
}
}
val autoCompleteTextView = remember { textInputLayout.editText as? AutoCompleteTextView }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

? can be deleted

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I wanted to demonstrate how it should behave depends on layout position. But yeah, it's not necessary
  2. That's true. But this is how compose implementation of autocomplete works now and that's why I created own implementation which not lagging.
    about suggestions - I'll check


AndroidView(
modifier = modifier,
factory = { textInputLayout },
update = {
autoCompleteTextView?.setAdapter(adapter)
autoCompleteTextView?.setText(selectedValue, false)
},
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.skyyo.samples.features.autoComplete

import androidx.compose.foundation.layout.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel

@Composable
fun AutoCompleteScreen(
viewModel: AutoCompleteViewModel = hiltViewModel()
) {
val query = viewModel.query.collectAsState()
val isExpanded = viewModel.isExpanded.collectAsState()
val suggestions = viewModel.suggestions.collectAsState()

Column(
Modifier
.fillMaxSize()
.padding(top = 32.dp, start = 16.dp, end = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(16.dp))
Text(text = "AndroidView")
AndroidViewTextFieldWithDropDownSample(
modifier = Modifier.fillMaxWidth(),
suggestions = viewModel.countries,
selectedValue = "",
)
Spacer(modifier = Modifier.height(16.dp))
Text(text = "Native exposed dropdown menu")
NativeExposedDropDownMenuSample(
modifier = Modifier.fillMaxWidth(),
countries = viewModel.countries
)
Spacer(modifier = Modifier.height(60.dp))
Text(text = "Custom exposed dropdown menu")
CustomExposedDropdownMenuSample(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 32.dp),
onSuggestionSelected = viewModel::onCountrySelected,
onExpandedChange = viewModel::onExpandedChange,
onValueChange = viewModel::onCountryEntered,
onClick = viewModel::onExpandedFieldClick,
suggestions = suggestions.value,
expanded = isExpanded.value,
query = query.value,
)
Spacer(modifier = Modifier.height(16.dp))
Text(text = "AndroidView")
AndroidViewTextFieldWithDropDownSample(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 32.dp),
suggestions = viewModel.countries,
selectedValue = "",
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.skyyo.samples.features.autoComplete

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.skyyo.samples.extensions.getStateFlow
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.*
import javax.inject.Inject

@HiltViewModel
class AutoCompleteViewModel @Inject constructor(
handle: SavedStateHandle
) : ViewModel() {

val countries = provideCountries()
val query = handle.getStateFlow(viewModelScope, "query", "")
val suggestions = handle.getStateFlow(viewModelScope, "suggestions", countries)
val isExpanded = handle.getStateFlow(viewModelScope, "isExpanded", false)

fun onCountryEntered(input: String) {
query.value = input

viewModelScope.launch(Dispatchers.Default) {
suggestions.value = if (input.isEmpty()) {
countries.also { onExpandedChange(false) }
} else {
val filteredList = countries.filter { country ->
country
.lowercase()
.startsWith(input.lowercase()) && country != input
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems we are not using filteredList anywhere

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we actually use:

handle[SUGGESTIONS] = if (input.isEmpty()) countries else ... filteredList

filteredList.also { onExpandedChange(true) }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any reason not to do filteredList.also { onExpandedChange(true) } -> onExpandedChange(true) ?

}
}
}

fun onExpandedChange(value: Boolean) {
isExpanded.value = value
}

fun onCountrySelected(value: String) {
query.value = value
}

fun onExpandedFieldClick() {
onExpandedChange(!isExpanded.value)
}

private fun provideCountries(): List<String> {
val locales = Locale.getAvailableLocales()
val countries = ArrayList<String>()
for (locale in locales) {
val country: String = locale.displayCountry
if (country.trim { it <= ' ' }.isNotEmpty() && !countries.contains(country)) {
countries.add(country)
}
}
countries.sort()

return countries
}
}
Loading