From 33bdaf9726967cc138760d3deff02cca23dc76b3 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Fri, 4 Nov 2022 22:13:57 +0100 Subject: [PATCH] Show load end/load failure toasts --- .../mealient/extensions/FragmentExtensions.kt | 8 ++- .../mealient/ui/recipes/RecipesFragment.kt | 53 +++++++++++++++++++ app/src/main/res/values-ru/strings.xml | 6 +++ app/src/main/res/values/strings.xml | 6 +++ 4 files changed, 72 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/FragmentExtensions.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/FragmentExtensions.kt index dd4e678..69ca4c8 100644 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/FragmentExtensions.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/FragmentExtensions.kt @@ -1,10 +1,12 @@ package gq.kirmanak.mealient.extensions import androidx.fragment.app.Fragment +import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.launchIn fun Fragment.collectWhenViewResumed( flow: Flow, @@ -13,4 +15,8 @@ fun Fragment.collectWhenViewResumed( fun Fragment.launchWhenViewResumed( block: suspend CoroutineScope.() -> Unit, -) = viewLifecycleOwner.lifecycleScope.launchWhenResumed(block) \ No newline at end of file +) = viewLifecycleOwner.lifecycleScope.launchWhenResumed(block) + +fun Flow.launchIn(lifecycleOwner: LifecycleOwner) { + launchIn(lifecycleOwner.lifecycleScope) +} \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipesFragment.kt b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipesFragment.kt index 38e19d2..280f325 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipesFragment.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/RecipesFragment.kt @@ -2,21 +2,26 @@ package gq.kirmanak.mealient.ui.recipes import android.os.Bundle import android.view.View +import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import androidx.paging.LoadState import by.kirich1409.viewbindingdelegate.viewBinding import dagger.hilt.android.AndroidEntryPoint import gq.kirmanak.mealient.R import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity import gq.kirmanak.mealient.databinding.FragmentRecipesBinding +import gq.kirmanak.mealient.datasource.NetworkError import gq.kirmanak.mealient.extensions.collectWhenViewResumed +import gq.kirmanak.mealient.extensions.launchIn import gq.kirmanak.mealient.extensions.refreshRequestFlow import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.ui.activity.MainActivityViewModel import gq.kirmanak.mealient.ui.recipes.images.RecipeImageLoader import gq.kirmanak.mealient.ui.recipes.images.RecipePreloaderFactory +import kotlinx.coroutines.flow.* import javax.inject.Inject @AndroidEntryPoint @@ -78,6 +83,8 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) { binding.refresher.isRefreshing = false } + observeLoadStateChanges(recipesAdapter) + collectWhenViewResumed(binding.refresher.refreshRequestFlow(logger)) { logger.v { "setupRecipeAdapter: received refresh request" } recipesAdapter.refresh() @@ -93,6 +100,52 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) { } } + private fun observeLoadStateChanges(recipesAdapter: RecipesPagingAdapter) { + recipesAdapter.loadStateFlow + .map { it.append } + .distinctUntilChanged() + .filter { it.endOfPaginationReached } + .onEach { onPaginationEnd() } + .launchIn(viewLifecycleOwner) + + recipesAdapter.loadStateFlow + .map { it.refresh } + .distinctUntilChanged() + .filterIsInstance() + .onEach { onLoadFailure(it.error) } + .launchIn(viewLifecycleOwner) + } + + private fun onPaginationEnd() { + logger.v { "onPaginationEnd() called" } + Toast.makeText( + requireContext(), + getString(R.string.fragment_recipes_last_page_loaded_toast), + Toast.LENGTH_SHORT + ).show() + } + + private fun onLoadFailure(error: Throwable) { + logger.v(error) { "onLoadFailure() called" } + + val reason = when (error) { + is NetworkError.Unauthorized -> R.string.fragment_recipes_load_failure_toast_unauthorized + is NetworkError.NoServerConnection -> R.string.fragment_recipes_load_failure_toast_no_connection + is NetworkError.NotMealie, is NetworkError.MalformedUrl -> R.string.fragment_recipes_load_failure_toast_unexpected_response + else -> null + }?.let { getString(it) } + + val generalText = getString(R.string.fragment_recipes_load_failure_toast_general) + + val text = if (reason == null) { + generalText + } else { + getString(R.string.fragment_recipes_load_failure_toast, generalText, reason) + } + + Toast.makeText(requireContext(), text, Toast.LENGTH_SHORT).show() + } + override fun onDestroyView() { super.onDestroyView() logger.v { "onDestroyView() called" } diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 4bde1ed..6d07ac4 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -41,4 +41,10 @@ Пример: demo.mealie.io Пример: changeme@email.com Пример: demo + Последняя страница + %1$s %2$s + Ошибка загрузки. + Неавторизован + Неожиданный ответ + Нет соединения \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c6b92fa..6a5c59e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -45,4 +45,10 @@ Example: demo.mealie.io Example: changeme@email.com Example: demo + Last page loaded + %1$s %2$s + Load failed. + Unauthorized + Unexpected response + No connection \ No newline at end of file