Show load end/load failure toasts

This commit is contained in:
Kirill Kamakin
2022-11-04 22:13:57 +01:00
parent 714ff7d33f
commit 33bdaf9726
4 changed files with 72 additions and 1 deletions

View File

@@ -1,10 +1,12 @@
package gq.kirmanak.mealient.extensions package gq.kirmanak.mealient.extensions
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.launchIn
fun <T> Fragment.collectWhenViewResumed( fun <T> Fragment.collectWhenViewResumed(
flow: Flow<T>, flow: Flow<T>,
@@ -14,3 +16,7 @@ fun <T> Fragment.collectWhenViewResumed(
fun Fragment.launchWhenViewResumed( fun Fragment.launchWhenViewResumed(
block: suspend CoroutineScope.() -> Unit, block: suspend CoroutineScope.() -> Unit,
) = viewLifecycleOwner.lifecycleScope.launchWhenResumed(block) ) = viewLifecycleOwner.lifecycleScope.launchWhenResumed(block)
fun <T> Flow<T>.launchIn(lifecycleOwner: LifecycleOwner) {
launchIn(lifecycleOwner.lifecycleScope)
}

View File

@@ -2,21 +2,26 @@ package gq.kirmanak.mealient.ui.recipes
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.Toast
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
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.paging.LoadState
import by.kirich1409.viewbindingdelegate.viewBinding import by.kirich1409.viewbindingdelegate.viewBinding
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import gq.kirmanak.mealient.R 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.extensions.collectWhenViewResumed import gq.kirmanak.mealient.extensions.collectWhenViewResumed
import gq.kirmanak.mealient.extensions.launchIn
import gq.kirmanak.mealient.extensions.refreshRequestFlow import gq.kirmanak.mealient.extensions.refreshRequestFlow
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.RecipeImageLoader import gq.kirmanak.mealient.ui.recipes.images.RecipeImageLoader
import gq.kirmanak.mealient.ui.recipes.images.RecipePreloaderFactory import gq.kirmanak.mealient.ui.recipes.images.RecipePreloaderFactory
import kotlinx.coroutines.flow.*
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@@ -78,6 +83,8 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) {
binding.refresher.isRefreshing = false binding.refresher.isRefreshing = false
} }
observeLoadStateChanges(recipesAdapter)
collectWhenViewResumed(binding.refresher.refreshRequestFlow(logger)) { collectWhenViewResumed(binding.refresher.refreshRequestFlow(logger)) {
logger.v { "setupRecipeAdapter: received refresh request" } logger.v { "setupRecipeAdapter: received refresh request" }
recipesAdapter.refresh() 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<LoadState.Error>()
.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() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
logger.v { "onDestroyView() called" } logger.v { "onDestroyView() called" }

View File

@@ -41,4 +41,10 @@
<string name="fragment_base_url_url_input_helper_text">Пример: demo.mealie.io</string> <string name="fragment_base_url_url_input_helper_text">Пример: demo.mealie.io</string>
<string name="fragment_authentication_email_input_helper_text">Пример: changeme@email.com</string> <string name="fragment_authentication_email_input_helper_text">Пример: changeme@email.com</string>
<string name="fragment_authentication_password_input_helper_text">Пример: demo</string> <string name="fragment_authentication_password_input_helper_text">Пример: demo</string>
<string name="fragment_recipes_last_page_loaded_toast">Последняя страница</string>
<string name="fragment_recipes_load_failure_toast">%1$s %2$s</string>
<string name="fragment_recipes_load_failure_toast_general">Ошибка загрузки.</string>
<string name="fragment_recipes_load_failure_toast_unauthorized">Неавторизован</string>
<string name="fragment_recipes_load_failure_toast_unexpected_response">Неожиданный ответ</string>
<string name="fragment_recipes_load_failure_toast_no_connection">Нет соединения</string>
</resources> </resources>

View File

@@ -45,4 +45,10 @@
<string name="fragment_base_url_url_input_helper_text">Example: demo.mealie.io</string> <string name="fragment_base_url_url_input_helper_text">Example: demo.mealie.io</string>
<string name="fragment_authentication_email_input_helper_text">Example: changeme@email.com</string> <string name="fragment_authentication_email_input_helper_text">Example: changeme@email.com</string>
<string name="fragment_authentication_password_input_helper_text">Example: demo</string> <string name="fragment_authentication_password_input_helper_text">Example: demo</string>
<string name="fragment_recipes_last_page_loaded_toast">Last page loaded</string>
<string name="fragment_recipes_load_failure_toast" comment="EXAMPLE: Load failed! Unauthorized">%1$s %2$s</string>
<string name="fragment_recipes_load_failure_toast_general">Load failed.</string>
<string name="fragment_recipes_load_failure_toast_unauthorized">Unauthorized</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>
</resources> </resources>