@@ -17,8 +17,8 @@ plugins {
|
|||||||
android {
|
android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "gq.kirmanak.mealient"
|
applicationId = "gq.kirmanak.mealient"
|
||||||
versionCode = 17
|
versionCode = 18
|
||||||
versionName = "0.3.2"
|
versionName = "0.3.3"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ package gq.kirmanak.mealient.extensions
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.widget.doAfterTextChanged
|
import androidx.core.widget.doAfterTextChanged
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
@@ -107,3 +110,8 @@ fun Context.showLongToast(@StringRes text: Int) = showLongToast(getString(text))
|
|||||||
private fun Context.showToast(text: String, length: Int) {
|
private fun Context.showToast(text: String, length: Int) {
|
||||||
Toast.makeText(this, text, length).show()
|
Toast.makeText(this, text, length).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun View.hideKeyboard() {
|
||||||
|
val imm = context.getSystemService<InputMethodManager>()
|
||||||
|
imm?.hideSoftInputFromWindow(windowToken, 0)
|
||||||
|
}
|
||||||
@@ -123,7 +123,13 @@ class MainActivity : AppCompatActivity(R.layout.main_activity) {
|
|||||||
logger.e { "setupSearchItem: search item's actionView is null or not SearchView" }
|
logger.e { "setupSearchItem: search item's actionView is null or not SearchView" }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
searchView.queryHint = getString(R.string.search_recipes_hint)
|
searchView.queryHint = getString(R.string.search_recipes_hint)
|
||||||
|
searchView.isSubmitButtonEnabled = false
|
||||||
|
|
||||||
|
searchView.setQuery(viewModel.lastSearchQuery, false)
|
||||||
|
searchView.isIconified = viewModel.lastSearchQuery.isNullOrBlank()
|
||||||
|
|
||||||
searchView.setOnCloseListener {
|
searchView.setOnCloseListener {
|
||||||
logger.v { "onClose() called" }
|
logger.v { "onClose() called" }
|
||||||
viewModel.onSearchQuery(null)
|
viewModel.onSearchQuery(null)
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ class MainActivityViewModel @Inject constructor(
|
|||||||
private val _startDestination = MutableLiveData<Int>()
|
private val _startDestination = MutableLiveData<Int>()
|
||||||
val startDestination: LiveData<Int> = _startDestination
|
val startDestination: LiveData<Int> = _startDestination
|
||||||
|
|
||||||
|
var lastSearchQuery: String? = null
|
||||||
|
private set
|
||||||
|
|
||||||
init {
|
init {
|
||||||
authRepo.isAuthorizedFlow
|
authRepo.isAuthorizedFlow
|
||||||
.onEach { isAuthorized -> updateUiState { it.copy(isAuthorized = isAuthorized) } }
|
.onEach { isAuthorized -> updateUiState { it.copy(isAuthorized = isAuthorized) } }
|
||||||
@@ -57,6 +60,9 @@ class MainActivityViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun onSearchQuery(query: String?) {
|
fun onSearchQuery(query: String?) {
|
||||||
logger.v { "onSearchQuery() called with: query = $query" }
|
logger.v { "onSearchQuery() called with: query = $query" }
|
||||||
recipeRepo.updateNameQuery(query)
|
if (lastSearchQuery != query) {
|
||||||
|
lastSearchQuery = query
|
||||||
|
recipeRepo.updateNameQuery(query)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,9 +127,8 @@ class AddRecipeFragment : Fragment(R.layout.fragment_add_recipe) {
|
|||||||
|
|
||||||
private fun saveValues() = with(binding) {
|
private fun saveValues() = with(binding) {
|
||||||
logger.v { "saveValues() called" }
|
logger.v { "saveValues() called" }
|
||||||
val instructions =
|
val instructions = parseInputRows(instructionsFlow).map { AddRecipeInstructionInfo(it) }
|
||||||
parseInputRows(instructionsFlow).map { AddRecipeInstructionInfo(text = it) }
|
val ingredients = parseInputRows(ingredientsFlow).map { AddRecipeIngredientInfo(it) }
|
||||||
val ingredients = parseInputRows(ingredientsFlow).map { AddRecipeIngredientInfo(note = it) }
|
|
||||||
val settings = AddRecipeSettingsInfo(
|
val settings = AddRecipeSettingsInfo(
|
||||||
public = publicRecipe.isChecked,
|
public = publicRecipe.isChecked,
|
||||||
disableComments = disableComments.isChecked,
|
disableComments = disableComments.isChecked,
|
||||||
@@ -156,17 +155,18 @@ class AddRecipeFragment : Fragment(R.layout.fragment_add_recipe) {
|
|||||||
|
|
||||||
private fun onSavedInputLoaded(request: AddRecipeInfo) = with(binding) {
|
private fun onSavedInputLoaded(request: AddRecipeInfo) = with(binding) {
|
||||||
logger.v { "onSavedInputLoaded() called with: request = $request" }
|
logger.v { "onSavedInputLoaded() called with: request = $request" }
|
||||||
recipeNameInput.setText(request.name)
|
|
||||||
recipeDescriptionInput.setText(request.description)
|
|
||||||
recipeYieldInput.setText(request.recipeYield)
|
|
||||||
publicRecipe.isChecked = request.settings.public
|
|
||||||
disableComments.isChecked = request.settings.disableComments
|
|
||||||
|
|
||||||
request.recipeIngredient.map { it.note }
|
request.recipeIngredient.map { it.note }
|
||||||
.showIn(ingredientsFlow, R.string.fragment_add_recipe_ingredient_hint)
|
.showIn(ingredientsFlow, R.string.fragment_add_recipe_ingredient_hint)
|
||||||
|
|
||||||
request.recipeInstructions.map { it.text }
|
request.recipeInstructions.map { it.text }
|
||||||
.showIn(instructionsFlow, R.string.fragment_add_recipe_instruction_hint)
|
.showIn(instructionsFlow, R.string.fragment_add_recipe_instruction_hint)
|
||||||
|
|
||||||
|
recipeNameInput.setText(request.name)
|
||||||
|
recipeDescriptionInput.setText(request.description)
|
||||||
|
recipeYieldInput.setText(request.recipeYield)
|
||||||
|
publicRecipe.isChecked = request.settings.public
|
||||||
|
disableComments.isChecked = request.settings.disableComments
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Iterable<String>.showIn(flow: Flow, @StringRes hintId: Int) {
|
private fun Iterable<String>.showIn(flow: Flow, @StringRes hintId: Int) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package gq.kirmanak.mealient.ui.recipes
|
package gq.kirmanak.mealient.ui.recipes
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
@@ -17,10 +18,7 @@ import gq.kirmanak.mealient.R
|
|||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||||
import gq.kirmanak.mealient.databinding.FragmentRecipesListBinding
|
import gq.kirmanak.mealient.databinding.FragmentRecipesListBinding
|
||||||
import gq.kirmanak.mealient.datasource.NetworkError
|
import gq.kirmanak.mealient.datasource.NetworkError
|
||||||
import gq.kirmanak.mealient.extensions.collectWhenViewResumed
|
import gq.kirmanak.mealient.extensions.*
|
||||||
import gq.kirmanak.mealient.extensions.refreshRequestFlow
|
|
||||||
import gq.kirmanak.mealient.extensions.showLongToast
|
|
||||||
import gq.kirmanak.mealient.extensions.valueUpdatesOnly
|
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import gq.kirmanak.mealient.ui.activity.MainActivityViewModel
|
import gq.kirmanak.mealient.ui.activity.MainActivityViewModel
|
||||||
import gq.kirmanak.mealient.ui.recipes.RecipesListFragmentDirections.Companion.actionRecipesFragmentToRecipeInfoFragment
|
import gq.kirmanak.mealient.ui.recipes.RecipesListFragmentDirections.Companion.actionRecipesFragmentToRecipeInfoFragment
|
||||||
@@ -59,6 +57,15 @@ class RecipesListFragment : Fragment(R.layout.fragment_recipes_list) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
setupRecipeAdapter()
|
setupRecipeAdapter()
|
||||||
|
hideKeyboardOnScroll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
private fun hideKeyboardOnScroll() {
|
||||||
|
binding.recipes.setOnTouchListener { view, _ ->
|
||||||
|
view?.hideKeyboard()
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateToRecipeInfo(id: String) {
|
private fun navigateToRecipeInfo(id: String) {
|
||||||
@@ -70,11 +77,9 @@ class RecipesListFragment : Fragment(R.layout.fragment_recipes_list) {
|
|||||||
private fun onRecipeClicked(recipe: RecipeSummaryEntity) {
|
private fun onRecipeClicked(recipe: RecipeSummaryEntity) {
|
||||||
logger.v { "onRecipeClicked() called with: recipe = $recipe" }
|
logger.v { "onRecipeClicked() called with: recipe = $recipe" }
|
||||||
binding.progress.isVisible = true
|
binding.progress.isVisible = true
|
||||||
viewModel.refreshRecipeInfo(recipe.slug).observe(viewLifecycleOwner) { result ->
|
viewModel.refreshRecipeInfo(recipe.slug).observe(viewLifecycleOwner) {
|
||||||
binding.progress.isVisible = false
|
binding.progress.isVisible = false
|
||||||
if (result.isSuccess && !isNavigatingSomewhere()) {
|
if (!isNavigatingSomewhere()) navigateToRecipeInfo(recipe.remoteId)
|
||||||
navigateToRecipeInfo(recipe.remoteId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,8 @@
|
|||||||
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_global_authenticationFragment"
|
android:id="@+id/action_global_authenticationFragment"
|
||||||
app:destination="@id/authenticationFragment" />
|
app:destination="@id/authenticationFragment"
|
||||||
|
app:popUpTo="@id/recipesListFragment" />
|
||||||
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_global_recipesListFragment"
|
android:id="@+id/action_global_recipesListFragment"
|
||||||
@@ -71,9 +72,11 @@
|
|||||||
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_global_addRecipeFragment"
|
android:id="@+id/action_global_addRecipeFragment"
|
||||||
app:destination="@id/addRecipeFragment" />
|
app:destination="@id/addRecipeFragment"
|
||||||
|
app:popUpTo="@id/recipesListFragment" />
|
||||||
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_global_baseURLFragment"
|
android:id="@+id/action_global_baseURLFragment"
|
||||||
app:destination="@id/baseURLFragment" />
|
app:destination="@id/baseURLFragment"
|
||||||
|
app:popUpTo="@id/recipesListFragment" />
|
||||||
</navigation>
|
</navigation>
|
||||||
@@ -4,7 +4,9 @@ import gq.kirmanak.mealient.data.auth.AuthRepo
|
|||||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||||
import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage
|
import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||||
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL
|
||||||
import gq.kirmanak.mealient.test.BaseUnitTest
|
import gq.kirmanak.mealient.test.BaseUnitTest
|
||||||
|
import io.mockk.coEvery
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.impl.annotations.MockK
|
import io.mockk.impl.annotations.MockK
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
@@ -32,6 +34,8 @@ class MainActivityViewModelTest : BaseUnitTest() {
|
|||||||
override fun setUp() {
|
override fun setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
every { authRepo.isAuthorizedFlow } returns emptyFlow()
|
every { authRepo.isAuthorizedFlow } returns emptyFlow()
|
||||||
|
coEvery { disclaimerStorage.isDisclaimerAccepted() } returns true
|
||||||
|
coEvery { serverInfoRepo.getUrl() } returns TEST_BASE_URL
|
||||||
subject = MainActivityViewModel(
|
subject = MainActivityViewModel(
|
||||||
authRepo = authRepo,
|
authRepo = authRepo,
|
||||||
logger = logger,
|
logger = logger,
|
||||||
@@ -49,6 +53,7 @@ class MainActivityViewModelTest : BaseUnitTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when onSearchQuery with null expect call to recipe repo`() {
|
fun `when onSearchQuery with null expect call to recipe repo`() {
|
||||||
|
subject.onSearchQuery("query")
|
||||||
subject.onSearchQuery(null)
|
subject.onSearchQuery(null)
|
||||||
verify { recipeRepo.updateNameQuery(null) }
|
verify { recipeRepo.updateNameQuery(null) }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user