Start search implementation
This commit is contained in:
@@ -19,12 +19,21 @@
|
|||||||
tools:ignore="UnusedAttribute">
|
tools:ignore="UnusedAttribute">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.activity.MainActivity"
|
android:name=".ui.activity.MainActivity"
|
||||||
|
android:launchMode="singleTop"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEARCH" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.app.searchable"
|
||||||
|
android:resource="@xml/searchable_recipe_main" />
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package gq.kirmanak.mealient.extensions
|
package gq.kirmanak.mealient.extensions
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
@@ -18,10 +17,7 @@ fun <T> Fragment.collectWhenViewResumed(flow: Flow<T>, collector: FlowCollector<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Fragment.showLongToast(@StringRes text: Int) = showLongToast(getString(text))
|
fun Fragment.showLongToast(@StringRes text: Int) = context?.showLongToast(text) != null
|
||||||
|
|
||||||
fun Fragment.showLongToast(text: String) = showToast(text, Toast.LENGTH_LONG)
|
fun Fragment.showLongToast(text: String) = context?.showLongToast(text) != null
|
||||||
|
|
||||||
private fun Fragment.showToast(text: String, length: Int): Boolean {
|
|
||||||
return context?.let { Toast.makeText(it, text, length).show() } != null
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package gq.kirmanak.mealient.extensions
|
package gq.kirmanak.mealient.extensions
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.widget.doAfterTextChanged
|
import androidx.core.widget.doAfterTextChanged
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
@@ -95,4 +97,13 @@ fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observ
|
|||||||
observer.onChanged(value)
|
observer.onChanged(value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Context.showLongToast(text: String) = showToast(text, Toast.LENGTH_LONG)
|
||||||
|
|
||||||
|
fun Context.showLongToast(@StringRes text: Int) = showLongToast(getString(text))
|
||||||
|
|
||||||
|
private fun Context.showToast(text: String, length: Int) {
|
||||||
|
Toast.makeText(this, text, length).show()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
package gq.kirmanak.mealient.ui.activity
|
package gq.kirmanak.mealient.ui.activity
|
||||||
|
|
||||||
|
import android.app.SearchManager
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.widget.SearchView
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
@@ -46,6 +50,15 @@ class MainActivity : AppCompatActivity(R.layout.main_activity) {
|
|||||||
binding.navigationView.setNavigationItemSelectedListener(::onNavigationItemSelected)
|
binding.navigationView.setNavigationItemSelectedListener(::onNavigationItemSelected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent?) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
if (Intent.ACTION_SEARCH == intent?.action) {
|
||||||
|
intent.getStringExtra(SearchManager.QUERY)?.also { query ->
|
||||||
|
viewModel.onSearchQuery(query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun configureNavGraph() {
|
private fun configureNavGraph() {
|
||||||
viewModel.startDestination.observeOnce(this) {
|
viewModel.startDestination.observeOnce(this) {
|
||||||
logger.d { "configureNavGraph: received destination" }
|
logger.d { "configureNavGraph: received destination" }
|
||||||
@@ -104,6 +117,15 @@ class MainActivity : AppCompatActivity(R.layout.main_activity) {
|
|||||||
menuInflater.inflate(R.menu.main_toolbar, menu)
|
menuInflater.inflate(R.menu.main_toolbar, menu)
|
||||||
menu.findItem(R.id.logout).isVisible = uiState.canShowLogout
|
menu.findItem(R.id.logout).isVisible = uiState.canShowLogout
|
||||||
menu.findItem(R.id.login).isVisible = uiState.canShowLogin
|
menu.findItem(R.id.login).isVisible = uiState.canShowLogin
|
||||||
|
val searchItem = menu.findItem(R.id.search_recipe_action)
|
||||||
|
searchItem.isVisible = uiState.searchVisible
|
||||||
|
val searchManager: SearchManager? = getSystemService()
|
||||||
|
val searchView = searchItem.actionView as? SearchView
|
||||||
|
if (searchManager != null && searchView != null) {
|
||||||
|
searchView.setSearchableInfo(searchManager.getSearchableInfo(componentName))
|
||||||
|
} else {
|
||||||
|
logger.e { "onCreateOptionsMenu: either search manager or search view is null" }
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ data class MainActivityUiState(
|
|||||||
val titleVisible: Boolean = true,
|
val titleVisible: Boolean = true,
|
||||||
val isAuthorized: Boolean = false,
|
val isAuthorized: Boolean = false,
|
||||||
val navigationVisible: Boolean = false,
|
val navigationVisible: Boolean = false,
|
||||||
|
val searchVisible: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val canShowLogin: Boolean
|
val canShowLogin: Boolean
|
||||||
get() = !isAuthorized && loginButtonVisible
|
get() = !isAuthorized && loginButtonVisible
|
||||||
|
|||||||
@@ -52,4 +52,8 @@ class MainActivityViewModel @Inject constructor(
|
|||||||
logger.v { "logout() called" }
|
logger.v { "logout() called" }
|
||||||
viewModelScope.launch { authRepo.logout() }
|
viewModelScope.launch { authRepo.logout() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onSearchQuery(query: String) {
|
||||||
|
logger.v { "onSearchQuery() called with: query = $query" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,12 @@ class AddRecipeFragment : Fragment(R.layout.fragment_add_recipe) {
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
logger.v { "onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState" }
|
logger.v { "onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState" }
|
||||||
activityViewModel.updateUiState {
|
activityViewModel.updateUiState {
|
||||||
it.copy(loginButtonVisible = true, titleVisible = false, navigationVisible = true)
|
it.copy(
|
||||||
|
loginButtonVisible = true,
|
||||||
|
titleVisible = false,
|
||||||
|
navigationVisible = true,
|
||||||
|
searchVisible = false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
viewModel.loadPreservedRequest()
|
viewModel.loadPreservedRequest()
|
||||||
setupViews()
|
setupViews()
|
||||||
|
|||||||
@@ -32,7 +32,12 @@ class AuthenticationFragment : Fragment(R.layout.fragment_authentication) {
|
|||||||
logger.v { "onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState" }
|
logger.v { "onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState" }
|
||||||
binding.button.setOnClickListener { onLoginClicked() }
|
binding.button.setOnClickListener { onLoginClicked() }
|
||||||
activityViewModel.updateUiState {
|
activityViewModel.updateUiState {
|
||||||
it.copy(loginButtonVisible = false, titleVisible = true, navigationVisible = false)
|
it.copy(
|
||||||
|
loginButtonVisible = false,
|
||||||
|
titleVisible = true,
|
||||||
|
navigationVisible = false,
|
||||||
|
searchVisible = false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
viewModel.uiState.observe(viewLifecycleOwner, ::onUiStateChange)
|
viewModel.uiState.observe(viewLifecycleOwner, ::onUiStateChange)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,12 @@ class BaseURLFragment : Fragment(R.layout.fragment_base_url) {
|
|||||||
binding.button.setOnClickListener(::onProceedClick)
|
binding.button.setOnClickListener(::onProceedClick)
|
||||||
viewModel.uiState.observe(viewLifecycleOwner, ::onUiStateChange)
|
viewModel.uiState.observe(viewLifecycleOwner, ::onUiStateChange)
|
||||||
activityViewModel.updateUiState {
|
activityViewModel.updateUiState {
|
||||||
it.copy(loginButtonVisible = false, titleVisible = true, navigationVisible = false)
|
it.copy(
|
||||||
|
loginButtonVisible = false,
|
||||||
|
titleVisible = true,
|
||||||
|
navigationVisible = false,
|
||||||
|
searchVisible = false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,12 @@ class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer) {
|
|||||||
}
|
}
|
||||||
viewModel.startCountDown()
|
viewModel.startCountDown()
|
||||||
activityViewModel.updateUiState {
|
activityViewModel.updateUiState {
|
||||||
it.copy(loginButtonVisible = false, titleVisible = true, navigationVisible = false)
|
it.copy(
|
||||||
|
loginButtonVisible = false,
|
||||||
|
titleVisible = true,
|
||||||
|
navigationVisible = false,
|
||||||
|
searchVisible = false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,7 +50,12 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) {
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
logger.v { "onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState" }
|
logger.v { "onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState" }
|
||||||
activityViewModel.updateUiState {
|
activityViewModel.updateUiState {
|
||||||
it.copy(loginButtonVisible = true, titleVisible = false, navigationVisible = true)
|
it.copy(
|
||||||
|
loginButtonVisible = true,
|
||||||
|
titleVisible = false,
|
||||||
|
navigationVisible = true,
|
||||||
|
searchVisible = true,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
setupRecipeAdapter()
|
setupRecipeAdapter()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,24 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/login"
|
android:id="@+id/login"
|
||||||
android:contentDescription="@string/menu_main_toolbar_content_description_login"
|
android:contentDescription="@string/menu_main_toolbar_content_description_login"
|
||||||
android:title="@string/menu_main_toolbar_login"
|
android:title="@string/menu_main_toolbar_login"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/logout"
|
android:id="@+id/logout"
|
||||||
android:contentDescription="@string/menu_main_toolbar_content_description_logout"
|
android:contentDescription="@string/menu_main_toolbar_content_description_logout"
|
||||||
android:title="@string/menu_main_toolbar_logout"
|
android:title="@string/menu_main_toolbar_logout"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/search_recipe_action"
|
||||||
|
android:icon="@android:drawable/ic_menu_search"
|
||||||
|
android:title="@string/searchable_recipe_main_hint"
|
||||||
|
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
@@ -48,4 +48,5 @@
|
|||||||
<string name="fragment_recipes_load_failure_toast_no_connection">нет соединения</string>
|
<string name="fragment_recipes_load_failure_toast_no_connection">нет соединения</string>
|
||||||
<string name="fragment_recipes_load_failure_toast_no_reason">Ошибка загрузки.</string>
|
<string name="fragment_recipes_load_failure_toast_no_reason">Ошибка загрузки.</string>
|
||||||
<string name="menu_bottom_navigation_change_url">Сменить URL</string>
|
<string name="menu_bottom_navigation_change_url">Сменить URL</string>
|
||||||
|
<string name="searchable_recipe_main_hint">Найти рецепты</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -52,4 +52,5 @@
|
|||||||
<string name="fragment_recipes_load_failure_toast_unexpected_response">unexpected response</string>
|
<string name="fragment_recipes_load_failure_toast_unexpected_response">unexpected response</string>
|
||||||
<string name="fragment_recipes_load_failure_toast_no_connection">no connection</string>
|
<string name="fragment_recipes_load_failure_toast_no_connection">no connection</string>
|
||||||
<string name="menu_bottom_navigation_change_url">Change URL</string>
|
<string name="menu_bottom_navigation_change_url">Change URL</string>
|
||||||
|
<string name="searchable_recipe_main_hint">Search recipes</string>
|
||||||
</resources>
|
</resources>
|
||||||
4
app/src/main/res/xml/searchable_recipe_main.xml
Normal file
4
app/src/main/res/xml/searchable_recipe_main.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:hint="@string/searchable_recipe_main_hint"
|
||||||
|
android:label="@string/app_name" />
|
||||||
Reference in New Issue
Block a user