Replace "Mealie" with "Mealient" everywhere
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
package gq.kirmanak.mealient.ui.recipes
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import gq.kirmanak.mealient.R
|
||||
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeSummaryEntity
|
||||
import gq.kirmanak.mealient.databinding.ViewHolderRecipeBinding
|
||||
|
||||
class RecipeViewHolder(
|
||||
private val binding: ViewHolderRecipeBinding,
|
||||
private val recipeViewModel: RecipeViewModel,
|
||||
private val clickListener: (RecipeSummaryEntity) -> Unit
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
private val loadingPlaceholder by lazy {
|
||||
binding.root.resources.getString(R.string.view_holder_recipe_text_placeholder)
|
||||
}
|
||||
|
||||
fun bind(item: RecipeSummaryEntity?) {
|
||||
binding.name.text = item?.name ?: loadingPlaceholder
|
||||
recipeViewModel.loadRecipeImage(binding.image, item)
|
||||
item?.let { entity -> binding.root.setOnClickListener { clickListener(entity) } }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package gq.kirmanak.mealient.ui.recipes
|
||||
|
||||
import android.widget.ImageView
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import gq.kirmanak.mealient.data.recipes.RecipeImageLoader
|
||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeSummaryEntity
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class RecipeViewModel @Inject constructor(
|
||||
recipeRepo: RecipeRepo,
|
||||
private val recipeImageLoader: RecipeImageLoader
|
||||
) : ViewModel() {
|
||||
val recipeFlow = recipeRepo.createPager().flow
|
||||
|
||||
fun loadRecipeImage(view: ImageView, recipeSummary: RecipeSummaryEntity?) {
|
||||
viewModelScope.launch {
|
||||
recipeImageLoader.loadRecipeImage(view, recipeSummary?.slug)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package gq.kirmanak.mealient.ui.recipes
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeSummaryEntity
|
||||
import gq.kirmanak.mealient.databinding.FragmentRecipesBinding
|
||||
import gq.kirmanak.mealient.ui.SwipeRefreshLayoutHelper.listenToRefreshRequests
|
||||
import gq.kirmanak.mealient.ui.auth.AuthenticationViewModel
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import timber.log.Timber
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@AndroidEntryPoint
|
||||
class RecipesFragment : Fragment() {
|
||||
private var _binding: FragmentRecipesBinding? = null
|
||||
private val binding: FragmentRecipesBinding
|
||||
get() = checkNotNull(_binding) { "Binding requested when fragment is off screen" }
|
||||
private val viewModel by viewModels<RecipeViewModel>()
|
||||
private val authViewModel by viewModels<AuthenticationViewModel>()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
Timber.v("onCreateView() called with: inflater = $inflater, container = $container, savedInstanceState = $savedInstanceState")
|
||||
_binding = FragmentRecipesBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
|
||||
setupRecipeAdapter()
|
||||
listenToAuthStatuses()
|
||||
}
|
||||
|
||||
private fun navigateToRecipeInfo(recipeSummaryEntity: RecipeSummaryEntity) {
|
||||
findNavController().navigate(
|
||||
RecipesFragmentDirections.actionRecipesFragmentToRecipeInfoFragment(
|
||||
recipeSlug = recipeSummaryEntity.slug,
|
||||
recipeId = recipeSummaryEntity.remoteId
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun listenToAuthStatuses() {
|
||||
Timber.v("listenToAuthStatuses() called")
|
||||
lifecycleScope.launchWhenCreated {
|
||||
authViewModel.authenticationStatuses().collectLatest {
|
||||
Timber.v("listenToAuthStatuses: new auth status = $it")
|
||||
if (!it) navigateToAuthFragment()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToAuthFragment() {
|
||||
Timber.v("navigateToAuthFragment() called")
|
||||
findNavController().navigate(RecipesFragmentDirections.actionRecipesFragmentToAuthenticationFragment())
|
||||
}
|
||||
|
||||
private fun setupRecipeAdapter() {
|
||||
Timber.v("setupRecipeAdapter() called")
|
||||
binding.recipes.layoutManager = LinearLayoutManager(requireContext())
|
||||
val adapter = RecipesPagingAdapter(viewModel) { navigateToRecipeInfo(it) }
|
||||
binding.recipes.adapter = adapter
|
||||
viewLifecycleOwner.lifecycleScope.launchWhenResumed {
|
||||
viewModel.recipeFlow.collect {
|
||||
Timber.d("Received update")
|
||||
adapter.submitData(it)
|
||||
}
|
||||
}
|
||||
viewLifecycleOwner.lifecycleScope.launchWhenResumed {
|
||||
adapter.listenToRefreshRequests(binding.refresher)
|
||||
}
|
||||
viewLifecycleOwner.lifecycleScope.launchWhenResumed {
|
||||
adapter.onPagesUpdatedFlow.collect {
|
||||
Timber.d("Pages have been updated")
|
||||
binding.refresher.isRefreshing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
Timber.v("onDestroyView() called")
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package gq.kirmanak.mealient.ui.recipes
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.paging.PagingDataAdapter
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeSummaryEntity
|
||||
import gq.kirmanak.mealient.databinding.ViewHolderRecipeBinding
|
||||
import timber.log.Timber
|
||||
|
||||
class RecipesPagingAdapter(
|
||||
private val viewModel: RecipeViewModel,
|
||||
private val clickListener: (RecipeSummaryEntity) -> Unit
|
||||
) : PagingDataAdapter<RecipeSummaryEntity, RecipeViewHolder>(RecipeDiffCallback) {
|
||||
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
holder.bind(item)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder {
|
||||
Timber.v("onCreateViewHolder() called with: parent = $parent, viewType = $viewType")
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
val binding = ViewHolderRecipeBinding.inflate(inflater, parent, false)
|
||||
return RecipeViewHolder(binding, viewModel, clickListener)
|
||||
}
|
||||
|
||||
private object RecipeDiffCallback : DiffUtil.ItemCallback<RecipeSummaryEntity>() {
|
||||
override fun areItemsTheSame(
|
||||
oldItem: RecipeSummaryEntity,
|
||||
newItem: RecipeSummaryEntity
|
||||
): Boolean {
|
||||
return oldItem.remoteId == newItem.remoteId
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(
|
||||
oldItem: RecipeSummaryEntity,
|
||||
newItem: RecipeSummaryEntity
|
||||
): Boolean {
|
||||
return oldItem.name == newItem.name && oldItem.slug == newItem.slug
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package gq.kirmanak.mealient.ui.recipes.info
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import gq.kirmanak.mealient.databinding.FragmentRecipeInfoBinding
|
||||
import gq.kirmanak.mealient.ui.auth.AuthenticationViewModel
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import timber.log.Timber
|
||||
|
||||
@AndroidEntryPoint
|
||||
class RecipeInfoFragment : Fragment() {
|
||||
private var _binding: FragmentRecipeInfoBinding? = null
|
||||
private val binding: FragmentRecipeInfoBinding
|
||||
get() = checkNotNull(_binding) { "Binding requested when fragment is off screen" }
|
||||
private val authViewModel by viewModels<AuthenticationViewModel>()
|
||||
private val arguments by navArgs<RecipeInfoFragmentArgs>()
|
||||
private val viewModel by viewModels<RecipeInfoViewModel>()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
Timber.v("onCreateView() called with: inflater = $inflater, container = $container, savedInstanceState = $savedInstanceState")
|
||||
_binding = FragmentRecipeInfoBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
|
||||
listenToAuthStatuses()
|
||||
viewModel.loadRecipeImage(binding.image, arguments.recipeSlug)
|
||||
viewModel.loadRecipeInfo(arguments.recipeId, arguments.recipeSlug)
|
||||
viewModel.recipeInfo.observe(viewLifecycleOwner) {
|
||||
binding.title.text = it.recipeSummaryEntity.name
|
||||
binding.description.text = it.recipeSummaryEntity.description
|
||||
|
||||
val recipeIngredientsAdapter = RecipeIngredientsAdapter()
|
||||
binding.ingredientsList.adapter = recipeIngredientsAdapter
|
||||
binding.ingredientsList.layoutManager = LinearLayoutManager(requireContext())
|
||||
recipeIngredientsAdapter.submitList(it.recipeIngredients)
|
||||
|
||||
val recipeInstructionsAdapter = RecipeInstructionsAdapter()
|
||||
binding.instructionsList.adapter = recipeInstructionsAdapter
|
||||
binding.instructionsList.layoutManager = LinearLayoutManager(requireContext())
|
||||
recipeInstructionsAdapter.submitList(it.recipeInstructions)
|
||||
}
|
||||
}
|
||||
|
||||
private fun listenToAuthStatuses() {
|
||||
Timber.v("listenToAuthStatuses() called")
|
||||
lifecycleScope.launchWhenCreated {
|
||||
authViewModel.authenticationStatuses().collectLatest {
|
||||
Timber.v("listenToAuthStatuses: new auth status = $it")
|
||||
if (!it) navigateToAuthFragment()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToAuthFragment() {
|
||||
Timber.v("navigateToAuthFragment() called")
|
||||
findNavController().navigate(RecipeInfoFragmentDirections.actionRecipeInfoFragmentToAuthenticationFragment())
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
Timber.v("onDestroyView() called")
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package gq.kirmanak.mealient.ui.recipes.info
|
||||
|
||||
import android.widget.ImageView
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import gq.kirmanak.mealient.data.recipes.RecipeImageLoader
|
||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||
import gq.kirmanak.mealient.data.recipes.impl.FullRecipeInfo
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class RecipeInfoViewModel @Inject constructor(
|
||||
private val recipeRepo: RecipeRepo,
|
||||
private val recipeImageLoader: RecipeImageLoader
|
||||
) : ViewModel() {
|
||||
private val _recipeInfo = MutableLiveData<FullRecipeInfo>()
|
||||
val recipeInfo: LiveData<FullRecipeInfo> = _recipeInfo
|
||||
|
||||
fun loadRecipeImage(view: ImageView, recipeSlug: String) {
|
||||
viewModelScope.launch {
|
||||
recipeImageLoader.loadRecipeImage(view, recipeSlug)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadRecipeInfo(recipeId: Long, recipeSlug: String) {
|
||||
Timber.v("loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug")
|
||||
viewModelScope.launch {
|
||||
runCatching {
|
||||
recipeRepo.loadRecipeInfo(recipeId, recipeSlug)
|
||||
}.onSuccess {
|
||||
Timber.d("loadRecipeInfo: received recipe info = $it")
|
||||
_recipeInfo.value = it
|
||||
}.onFailure {
|
||||
Timber.e(it, "loadRecipeInfo: can't load recipe info")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package gq.kirmanak.mealient.ui.recipes.info
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeIngredientEntity
|
||||
import gq.kirmanak.mealient.databinding.ViewHolderIngredientBinding
|
||||
import gq.kirmanak.mealient.ui.recipes.info.RecipeIngredientsAdapter.RecipeIngredientViewHolder
|
||||
|
||||
class RecipeIngredientsAdapter() :
|
||||
ListAdapter<RecipeIngredientEntity, RecipeIngredientViewHolder>(RecipeIngredientDiffCallback) {
|
||||
|
||||
class RecipeIngredientViewHolder(
|
||||
private val binding: ViewHolderIngredientBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: RecipeIngredientEntity) {
|
||||
binding.checkBox.text = item.note
|
||||
}
|
||||
}
|
||||
|
||||
private object RecipeIngredientDiffCallback : DiffUtil.ItemCallback<RecipeIngredientEntity>() {
|
||||
override fun areItemsTheSame(
|
||||
oldItem: RecipeIngredientEntity,
|
||||
newItem: RecipeIngredientEntity
|
||||
): Boolean = oldItem.localId == newItem.localId
|
||||
|
||||
override fun areContentsTheSame(
|
||||
oldItem: RecipeIngredientEntity,
|
||||
newItem: RecipeIngredientEntity
|
||||
): Boolean = oldItem == newItem
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeIngredientViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
return RecipeIngredientViewHolder(
|
||||
ViewHolderIngredientBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecipeIngredientViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package gq.kirmanak.mealient.ui.recipes.info
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeInstructionEntity
|
||||
import gq.kirmanak.mealient.databinding.ViewHolderInstructionBinding
|
||||
|
||||
class RecipeInstructionsAdapter :
|
||||
ListAdapter<RecipeInstructionEntity, RecipeInstructionsAdapter.RecipeInstructionViewHolder>(
|
||||
RecipeInstructionDiffCallback
|
||||
) {
|
||||
|
||||
private object RecipeInstructionDiffCallback :
|
||||
DiffUtil.ItemCallback<RecipeInstructionEntity>() {
|
||||
override fun areItemsTheSame(
|
||||
oldItem: RecipeInstructionEntity,
|
||||
newItem: RecipeInstructionEntity
|
||||
): Boolean = oldItem.localId == newItem.localId
|
||||
|
||||
override fun areContentsTheSame(
|
||||
oldItem: RecipeInstructionEntity,
|
||||
newItem: RecipeInstructionEntity
|
||||
): Boolean = oldItem == newItem
|
||||
}
|
||||
|
||||
class RecipeInstructionViewHolder(private val binding: ViewHolderInstructionBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: RecipeInstructionEntity, position: Int) {
|
||||
binding.step.text = "Step: ${position + 1}"
|
||||
binding.instruction.text = item.text
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeInstructionViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
return RecipeInstructionViewHolder(
|
||||
ViewHolderInstructionBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecipeInstructionViewHolder, position: Int) {
|
||||
holder.bind(getItem(position), position)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user