Fix jumping recipe info sheet
This commit is contained in:
@@ -10,4 +10,6 @@ interface RecipeRepo {
|
|||||||
suspend fun clearLocalData()
|
suspend fun clearLocalData()
|
||||||
|
|
||||||
suspend fun loadRecipeInfo(recipeId: String, recipeSlug: String): FullRecipeEntity
|
suspend fun loadRecipeInfo(recipeId: String, recipeSlug: String): FullRecipeEntity
|
||||||
|
|
||||||
|
suspend fun loadRecipeInfoFromDb(recipeId: String, recipeSlug: String): FullRecipeEntity?
|
||||||
}
|
}
|
||||||
@@ -49,4 +49,14 @@ class RecipeRepoImpl @Inject constructor(
|
|||||||
|
|
||||||
return storage.queryRecipeInfo(recipeId)
|
return storage.queryRecipeInfo(recipeId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun loadRecipeInfoFromDb(
|
||||||
|
recipeId: String,
|
||||||
|
recipeSlug: String
|
||||||
|
): FullRecipeEntity? {
|
||||||
|
logger.v { "loadRecipeInfoFromDb() called with: recipeId = $recipeId, recipeSlug = $recipeSlug" }
|
||||||
|
return runCatchingExceptCancel { storage.queryRecipeInfo(recipeId) }
|
||||||
|
.onFailure { logger.e(it) { "loadRecipeInfoFromDb failed" } }
|
||||||
|
.getOrNull()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package gq.kirmanak.mealient.ui.recipes
|
package gq.kirmanak.mealient.ui.recipes
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.*
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import androidx.paging.cachedIn
|
import androidx.paging.cachedIn
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||||
|
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity
|
||||||
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||||
|
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||||
import gq.kirmanak.mealient.extensions.valueUpdatesOnly
|
import gq.kirmanak.mealient.extensions.valueUpdatesOnly
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
@@ -16,7 +16,7 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class RecipeViewModel @Inject constructor(
|
class RecipeViewModel @Inject constructor(
|
||||||
recipeRepo: RecipeRepo,
|
private val recipeRepo: RecipeRepo,
|
||||||
authRepo: AuthRepo,
|
authRepo: AuthRepo,
|
||||||
private val logger: Logger,
|
private val logger: Logger,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
@@ -37,4 +37,13 @@ class RecipeViewModel @Inject constructor(
|
|||||||
logger.v { "onAuthorizationSuccessHandled() called" }
|
logger.v { "onAuthorizationSuccessHandled() called" }
|
||||||
_isAuthorized.postValue(null)
|
_isAuthorized.postValue(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loadRecipeInfo(
|
||||||
|
summaryEntity: RecipeSummaryEntity
|
||||||
|
): LiveData<Result<FullRecipeEntity>> = liveData {
|
||||||
|
val result = runCatchingExceptCancel {
|
||||||
|
recipeRepo.loadRecipeInfo(summaryEntity.remoteId, summaryEntity.slug)
|
||||||
|
}
|
||||||
|
emit(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ package gq.kirmanak.mealient.ui.recipes
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
@@ -16,10 +17,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.FragmentRecipesBinding
|
import gq.kirmanak.mealient.databinding.FragmentRecipesBinding
|
||||||
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.images.RecipePreloaderFactory
|
import gq.kirmanak.mealient.ui.recipes.images.RecipePreloaderFactory
|
||||||
@@ -45,6 +43,8 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var recipePreloaderFactory: RecipePreloaderFactory
|
lateinit var recipePreloaderFactory: RecipePreloaderFactory
|
||||||
|
|
||||||
|
private var ignoreRecipeClicks = false
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
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" }
|
||||||
@@ -63,10 +63,22 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onRecipeClicked(recipe: RecipeSummaryEntity) {
|
||||||
|
logger.d { "onRecipeClicked() called with: recipe = $recipe" }
|
||||||
|
if (ignoreRecipeClicks) return
|
||||||
|
binding.progress.isVisible = true
|
||||||
|
ignoreRecipeClicks = true // TODO doesn't really work
|
||||||
|
viewModel.loadRecipeInfo(recipe).observeOnce(viewLifecycleOwner) { result ->
|
||||||
|
binding.progress.isVisible = false
|
||||||
|
if (result.isSuccess) navigateToRecipeInfo(recipe)
|
||||||
|
ignoreRecipeClicks = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupRecipeAdapter() {
|
private fun setupRecipeAdapter() {
|
||||||
logger.v { "setupRecipeAdapter() called" }
|
logger.v { "setupRecipeAdapter() called" }
|
||||||
|
|
||||||
val recipesAdapter = recipePagingAdapterFactory.build { navigateToRecipeInfo(it) }
|
val recipesAdapter = recipePagingAdapterFactory.build { onRecipeClicked(it) }
|
||||||
|
|
||||||
with(binding.recipes) {
|
with(binding.recipes) {
|
||||||
adapter = recipesAdapter
|
adapter = recipesAdapter
|
||||||
@@ -135,17 +147,11 @@ private fun Throwable.toLoadErrorReasonText(): Int? = when (this) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun <T : Any, VH : RecyclerView.ViewHolder> PagingDataAdapter<T, VH>.refreshErrors(): Flow<Throwable> {
|
private fun <T : Any, VH : RecyclerView.ViewHolder> PagingDataAdapter<T, VH>.refreshErrors(): Flow<Throwable> {
|
||||||
return loadStateFlow
|
return loadStateFlow.map { it.refresh }.valueUpdatesOnly().filterIsInstance<LoadState.Error>()
|
||||||
.map { it.refresh }
|
|
||||||
.valueUpdatesOnly()
|
|
||||||
.filterIsInstance<LoadState.Error>()
|
|
||||||
.map { it.error }
|
.map { it.error }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T : Any, VH : RecyclerView.ViewHolder> PagingDataAdapter<T, VH>.appendPaginationEnd(): Flow<Unit> {
|
private fun <T : Any, VH : RecyclerView.ViewHolder> PagingDataAdapter<T, VH>.appendPaginationEnd(): Flow<Unit> {
|
||||||
return loadStateFlow
|
return loadStateFlow.map { it.append.endOfPaginationReached }.valueUpdatesOnly().filter { it }
|
||||||
.map { it.append.endOfPaginationReached }
|
|
||||||
.valueUpdatesOnly()
|
|
||||||
.filter { it }
|
|
||||||
.map { }
|
.map { }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.liveData
|
import androidx.lifecycle.liveData
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||||
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -21,20 +20,14 @@ class RecipeInfoViewModel @Inject constructor(
|
|||||||
|
|
||||||
val uiState: LiveData<RecipeInfoUiState> = liveData {
|
val uiState: LiveData<RecipeInfoUiState> = liveData {
|
||||||
logger.v { "Initializing UI state with args = $args" }
|
logger.v { "Initializing UI state with args = $args" }
|
||||||
emit(RecipeInfoUiState())
|
val state = recipeRepo.loadRecipeInfoFromDb(args.recipeId, args.recipeSlug)?.let {
|
||||||
runCatchingExceptCancel {
|
RecipeInfoUiState(
|
||||||
recipeRepo.loadRecipeInfo(args.recipeId, args.recipeSlug)
|
|
||||||
}.onSuccess {
|
|
||||||
logger.d { "loadRecipeInfo: received recipe info = $it" }
|
|
||||||
val newState = RecipeInfoUiState(
|
|
||||||
areIngredientsVisible = it.recipeIngredients.isNotEmpty(),
|
areIngredientsVisible = it.recipeIngredients.isNotEmpty(),
|
||||||
areInstructionsVisible = it.recipeInstructions.isNotEmpty(),
|
areInstructionsVisible = it.recipeInstructions.isNotEmpty(),
|
||||||
recipeInfo = it,
|
recipeInfo = it,
|
||||||
)
|
)
|
||||||
emit(newState)
|
} ?: RecipeInfoUiState()
|
||||||
}.onFailure {
|
emit(state)
|
||||||
logger.e(it) { "loadRecipeInfo: can't load recipe info" }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,4 +19,10 @@
|
|||||||
tools:listitem="@layout/view_holder_recipe" />
|
tools:listitem="@layout/view_holder_recipe" />
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
android:id="@+id/progress"
|
||||||
|
style="@style/IndeterminateProgress"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
Reference in New Issue
Block a user