Fix jumping recipe info sheet

This commit is contained in:
Kirill Kamakin
2022-11-05 14:24:59 +01:00
parent ff38ce655d
commit cc73f68751
6 changed files with 55 additions and 29 deletions

View File

@@ -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?
} }

View File

@@ -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()
}
} }

View File

@@ -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)
}
} }

View File

@@ -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 { }
} }

View File

@@ -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" }
}
} }
} }

View File

@@ -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>