Replace "Mealie" with "Mealient" everywhere

This commit is contained in:
Kirill Kamakin
2021-11-20 13:41:47 +03:00
parent d789bfcf97
commit 5866584d14
81 changed files with 283 additions and 284 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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