Implement opening of recipe info card
This commit is contained in:
@@ -10,7 +10,7 @@ import javax.inject.Singleton
|
|||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
version = 1,
|
version = 1,
|
||||||
entities = [CategoryEntity::class, CategoryRecipeEntity::class, TagEntity::class, TagRecipeEntity::class, RecipeSummaryEntity::class],
|
entities = [CategoryEntity::class, CategoryRecipeEntity::class, TagEntity::class, TagRecipeEntity::class, RecipeSummaryEntity::class, RecipeEntity::class, RecipeIngredientEntity::class, RecipeInstructionEntity::class],
|
||||||
exportSchema = false
|
exportSchema = false
|
||||||
)
|
)
|
||||||
@TypeConverters(RoomTypeConverters::class)
|
@TypeConverters(RoomTypeConverters::class)
|
||||||
|
|||||||
@@ -10,7 +10,12 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@ExperimentalSerializationApi
|
@ExperimentalSerializationApi
|
||||||
class RetrofitBuilder @Inject constructor(private val okHttpBuilder: OkHttpBuilder) {
|
class RetrofitBuilder @Inject constructor(private val okHttpBuilder: OkHttpBuilder) {
|
||||||
private val json by lazy { Json { coerceInputValues = true } }
|
private val json by lazy {
|
||||||
|
Json {
|
||||||
|
coerceInputValues = true
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun buildRetrofit(baseUrl: String, token: String? = null): Retrofit {
|
fun buildRetrofit(baseUrl: String, token: String? = null): Retrofit {
|
||||||
Timber.v("buildRetrofit() called with: baseUrl = $baseUrl, token = $token")
|
Timber.v("buildRetrofit() called with: baseUrl = $baseUrl, token = $token")
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ package gq.kirmanak.mealie.data.recipes
|
|||||||
|
|
||||||
import androidx.paging.Pager
|
import androidx.paging.Pager
|
||||||
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeSummaryEntity
|
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeSummaryEntity
|
||||||
|
import gq.kirmanak.mealie.data.recipes.impl.FullRecipeInfo
|
||||||
|
|
||||||
interface RecipeRepo {
|
interface RecipeRepo {
|
||||||
fun createPager(): Pager<Int, RecipeSummaryEntity>
|
fun createPager(): Pager<Int, RecipeSummaryEntity>
|
||||||
|
|
||||||
suspend fun clearLocalData()
|
suspend fun clearLocalData()
|
||||||
|
|
||||||
|
suspend fun loadRecipeInfo(recipeId: Long, recipeSlug: String): FullRecipeInfo
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
package gq.kirmanak.mealie.data.recipes.db
|
package gq.kirmanak.mealie.data.recipes.db
|
||||||
|
|
||||||
import androidx.paging.PagingSource
|
import androidx.paging.PagingSource
|
||||||
import androidx.room.Dao
|
import androidx.room.*
|
||||||
import androidx.room.Insert
|
|
||||||
import androidx.room.OnConflictStrategy
|
|
||||||
import androidx.room.Query
|
|
||||||
import gq.kirmanak.mealie.data.recipes.db.entity.*
|
import gq.kirmanak.mealie.data.recipes.db.entity.*
|
||||||
|
import gq.kirmanak.mealie.data.recipes.impl.FullRecipeInfo
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface RecipeDao {
|
interface RecipeDao {
|
||||||
@@ -57,12 +55,16 @@ interface RecipeDao {
|
|||||||
@Query("SELECT * FROM tag_recipe")
|
@Query("SELECT * FROM tag_recipe")
|
||||||
suspend fun queryAllTagRecipes(): List<TagRecipeEntity>
|
suspend fun queryAllTagRecipes(): List<TagRecipeEntity>
|
||||||
|
|
||||||
@Insert
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insertRecipe(recipe: RecipeEntity)
|
suspend fun insertRecipe(recipe: RecipeEntity)
|
||||||
|
|
||||||
@Insert
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insertRecipeInstructions(instructions: List<RecipeInstructionEntity>)
|
suspend fun insertRecipeInstructions(instructions: List<RecipeInstructionEntity>)
|
||||||
|
|
||||||
@Insert
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insertRecipeIngredients(ingredients: List<RecipeIngredientEntity>)
|
suspend fun insertRecipeIngredients(ingredients: List<RecipeIngredientEntity>)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("SELECT * FROM recipe JOIN recipe_summaries ON recipe.remote_id = recipe_summaries.remote_id JOIN recipe_ingredient ON recipe_ingredient.recipe_id = recipe.remote_id JOIN recipe_instruction ON recipe_instruction.recipe_id = recipe.remote_id WHERE recipe.remote_id = :recipeId")
|
||||||
|
suspend fun queryFullRecipeInfo(recipeId: Long): FullRecipeInfo?
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package gq.kirmanak.mealie.data.recipes.db
|
|||||||
|
|
||||||
import androidx.paging.PagingSource
|
import androidx.paging.PagingSource
|
||||||
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeSummaryEntity
|
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeSummaryEntity
|
||||||
|
import gq.kirmanak.mealie.data.recipes.impl.FullRecipeInfo
|
||||||
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeResponse
|
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeResponse
|
||||||
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeSummaryResponse
|
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeSummaryResponse
|
||||||
|
|
||||||
@@ -15,4 +16,6 @@ interface RecipeStorage {
|
|||||||
suspend fun clearAllLocalData()
|
suspend fun clearAllLocalData()
|
||||||
|
|
||||||
suspend fun saveRecipeInfo(recipe: GetRecipeResponse)
|
suspend fun saveRecipeInfo(recipe: GetRecipeResponse)
|
||||||
|
|
||||||
|
suspend fun queryRecipeInfo(recipeId: Long): FullRecipeInfo
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import androidx.paging.PagingSource
|
|||||||
import androidx.room.withTransaction
|
import androidx.room.withTransaction
|
||||||
import gq.kirmanak.mealie.data.MealieDb
|
import gq.kirmanak.mealie.data.MealieDb
|
||||||
import gq.kirmanak.mealie.data.recipes.db.entity.*
|
import gq.kirmanak.mealie.data.recipes.db.entity.*
|
||||||
|
import gq.kirmanak.mealie.data.recipes.impl.FullRecipeInfo
|
||||||
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeIngredientResponse
|
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeIngredientResponse
|
||||||
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeInstructionResponse
|
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeInstructionResponse
|
||||||
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeResponse
|
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeResponse
|
||||||
@@ -154,4 +155,13 @@ class RecipeStorageImpl @Inject constructor(
|
|||||||
title = title,
|
title = title,
|
||||||
text = text
|
text = text
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun queryRecipeInfo(recipeId: Long): FullRecipeInfo {
|
||||||
|
Timber.v("queryRecipeInfo() called with: recipeId = $recipeId")
|
||||||
|
val fullRecipeInfo = checkNotNull(recipeDao.queryFullRecipeInfo(recipeId)) {
|
||||||
|
"Can't find recipe by id $recipeId in DB"
|
||||||
|
}
|
||||||
|
Timber.v("queryRecipeInfo() returned: $fullRecipeInfo")
|
||||||
|
return fullRecipeInfo
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,8 @@ import androidx.room.PrimaryKey
|
|||||||
|
|
||||||
@Entity(tableName = "recipe_ingredient")
|
@Entity(tableName = "recipe_ingredient")
|
||||||
data class RecipeIngredientEntity(
|
data class RecipeIngredientEntity(
|
||||||
@PrimaryKey @ColumnInfo(name = "recipe_id") val recipeId: Long,
|
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "local_id") val localId: Long = 0,
|
||||||
|
@ColumnInfo(name = "recipe_id") val recipeId: Long,
|
||||||
@ColumnInfo(name = "title") val title: String,
|
@ColumnInfo(name = "title") val title: String,
|
||||||
@ColumnInfo(name = "note") val note: String,
|
@ColumnInfo(name = "note") val note: String,
|
||||||
@ColumnInfo(name = "unit") val unit: String,
|
@ColumnInfo(name = "unit") val unit: String,
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import androidx.room.PrimaryKey
|
|||||||
|
|
||||||
@Entity(tableName = "recipe_instruction")
|
@Entity(tableName = "recipe_instruction")
|
||||||
data class RecipeInstructionEntity(
|
data class RecipeInstructionEntity(
|
||||||
@PrimaryKey @ColumnInfo(name = "recipe_id") val recipeId: Long,
|
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "local_id") val localId: Long = 0,
|
||||||
|
@ColumnInfo(name = "recipe_id") val recipeId: Long,
|
||||||
@ColumnInfo(name = "title") val title: String,
|
@ColumnInfo(name = "title") val title: String,
|
||||||
@ColumnInfo(name = "text") val text: String,
|
@ColumnInfo(name = "text") val text: String,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package gq.kirmanak.mealie.data.recipes.impl
|
||||||
|
|
||||||
|
import androidx.room.Embedded
|
||||||
|
import androidx.room.Relation
|
||||||
|
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeEntity
|
||||||
|
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeIngredientEntity
|
||||||
|
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeInstructionEntity
|
||||||
|
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeSummaryEntity
|
||||||
|
|
||||||
|
data class FullRecipeInfo(
|
||||||
|
@Embedded val recipeEntity: RecipeEntity,
|
||||||
|
@Relation(
|
||||||
|
parentColumn = "remote_id",
|
||||||
|
entityColumn = "remote_id"
|
||||||
|
)
|
||||||
|
val recipeSummaryEntity: RecipeSummaryEntity,
|
||||||
|
@Relation(
|
||||||
|
parentColumn = "remote_id",
|
||||||
|
entityColumn = "recipe_id"
|
||||||
|
)
|
||||||
|
val recipeIngredients: List<RecipeIngredientEntity>,
|
||||||
|
@Relation(
|
||||||
|
parentColumn = "remote_id",
|
||||||
|
entityColumn = "recipe_id"
|
||||||
|
)
|
||||||
|
val recipeInstructions: List<RecipeInstructionEntity>,
|
||||||
|
)
|
||||||
@@ -6,6 +6,8 @@ import androidx.paging.PagingConfig
|
|||||||
import gq.kirmanak.mealie.data.recipes.RecipeRepo
|
import gq.kirmanak.mealie.data.recipes.RecipeRepo
|
||||||
import gq.kirmanak.mealie.data.recipes.db.RecipeStorage
|
import gq.kirmanak.mealie.data.recipes.db.RecipeStorage
|
||||||
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeSummaryEntity
|
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeSummaryEntity
|
||||||
|
import gq.kirmanak.mealie.data.recipes.network.RecipeDataSource
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -13,7 +15,8 @@ import javax.inject.Inject
|
|||||||
class RecipeRepoImpl @Inject constructor(
|
class RecipeRepoImpl @Inject constructor(
|
||||||
private val mediator: RecipesRemoteMediator,
|
private val mediator: RecipesRemoteMediator,
|
||||||
private val storage: RecipeStorage,
|
private val storage: RecipeStorage,
|
||||||
private val pagingSourceFactory: RecipePagingSourceFactory
|
private val pagingSourceFactory: RecipePagingSourceFactory,
|
||||||
|
private val dataSource: RecipeDataSource,
|
||||||
) : RecipeRepo {
|
) : RecipeRepo {
|
||||||
override fun createPager(): Pager<Int, RecipeSummaryEntity> {
|
override fun createPager(): Pager<Int, RecipeSummaryEntity> {
|
||||||
Timber.v("createPager() called")
|
Timber.v("createPager() called")
|
||||||
@@ -29,4 +32,18 @@ class RecipeRepoImpl @Inject constructor(
|
|||||||
Timber.v("clearLocalData() called")
|
Timber.v("clearLocalData() called")
|
||||||
storage.clearAllLocalData()
|
storage.clearAllLocalData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun loadRecipeInfo(recipeId: Long, recipeSlug: String): FullRecipeInfo {
|
||||||
|
Timber.v("loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug")
|
||||||
|
|
||||||
|
try {
|
||||||
|
storage.saveRecipeInfo(dataSource.requestRecipeInfo(recipeSlug))
|
||||||
|
} catch (e: CancellationException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Timber.e(e, "loadRecipeInfo: can't update full recipe info")
|
||||||
|
}
|
||||||
|
|
||||||
|
return storage.queryRecipeInfo(recipeId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ interface RecipeService {
|
|||||||
@Query("limit") limit: Int
|
@Query("limit") limit: Int
|
||||||
): List<GetRecipeSummaryResponse>
|
): List<GetRecipeSummaryResponse>
|
||||||
|
|
||||||
@GET("/api/recipes/:recipe_slug")
|
@GET("/api/recipes/{recipe_slug}")
|
||||||
suspend fun getRecipe(
|
suspend fun getRecipe(
|
||||||
@Path("recipe_slug") recipeSlug: String
|
@Path("recipe_slug") recipeSlug: String
|
||||||
): GetRecipeResponse
|
): GetRecipeResponse
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import gq.kirmanak.mealie.databinding.ViewHolderRecipeBinding
|
|||||||
|
|
||||||
class RecipeViewHolder(
|
class RecipeViewHolder(
|
||||||
private val binding: ViewHolderRecipeBinding,
|
private val binding: ViewHolderRecipeBinding,
|
||||||
private val recipeViewModel: RecipeViewModel
|
private val recipeViewModel: RecipeViewModel,
|
||||||
|
private val clickListener: (RecipeSummaryEntity) -> Unit
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
private val loadingPlaceholder by lazy {
|
private val loadingPlaceholder by lazy {
|
||||||
binding.root.resources.getString(R.string.view_holder_recipe_text_placeholder)
|
binding.root.resources.getString(R.string.view_holder_recipe_text_placeholder)
|
||||||
@@ -16,5 +17,6 @@ class RecipeViewHolder(
|
|||||||
fun bind(item: RecipeSummaryEntity?) {
|
fun bind(item: RecipeSummaryEntity?) {
|
||||||
binding.name.text = item?.name ?: loadingPlaceholder
|
binding.name.text = item?.name ?: loadingPlaceholder
|
||||||
recipeViewModel.loadRecipeImage(binding.image, item)
|
recipeViewModel.loadRecipeImage(binding.image, item)
|
||||||
|
item?.let { entity -> binding.root.setOnClickListener { clickListener(entity) } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,7 @@ import androidx.lifecycle.lifecycleScope
|
|||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeSummaryEntity
|
||||||
import gq.kirmanak.mealie.databinding.FragmentRecipesBinding
|
import gq.kirmanak.mealie.databinding.FragmentRecipesBinding
|
||||||
import gq.kirmanak.mealie.ui.SwipeRefreshLayoutHelper.listenToRefreshRequests
|
import gq.kirmanak.mealie.ui.SwipeRefreshLayoutHelper.listenToRefreshRequests
|
||||||
import gq.kirmanak.mealie.ui.auth.AuthenticationViewModel
|
import gq.kirmanak.mealie.ui.auth.AuthenticationViewModel
|
||||||
@@ -44,6 +45,15 @@ class RecipesFragment : Fragment() {
|
|||||||
listenToAuthStatuses()
|
listenToAuthStatuses()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun navigateToRecipeInfo(recipeSummaryEntity: RecipeSummaryEntity) {
|
||||||
|
findNavController().navigate(
|
||||||
|
RecipesFragmentDirections.actionRecipesFragmentToRecipeInfoFragment(
|
||||||
|
recipeSlug = recipeSummaryEntity.slug,
|
||||||
|
recipeId = recipeSummaryEntity.remoteId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun listenToAuthStatuses() {
|
private fun listenToAuthStatuses() {
|
||||||
Timber.v("listenToAuthStatuses() called")
|
Timber.v("listenToAuthStatuses() called")
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launchWhenCreated {
|
||||||
@@ -62,7 +72,7 @@ class RecipesFragment : Fragment() {
|
|||||||
private fun setupRecipeAdapter() {
|
private fun setupRecipeAdapter() {
|
||||||
Timber.v("setupRecipeAdapter() called")
|
Timber.v("setupRecipeAdapter() called")
|
||||||
binding.recipes.layoutManager = LinearLayoutManager(requireContext())
|
binding.recipes.layoutManager = LinearLayoutManager(requireContext())
|
||||||
val adapter = RecipesPagingAdapter(viewModel)
|
val adapter = RecipesPagingAdapter(viewModel) { navigateToRecipeInfo(it) }
|
||||||
binding.recipes.adapter = adapter
|
binding.recipes.adapter = adapter
|
||||||
viewLifecycleOwner.lifecycleScope.launchWhenResumed {
|
viewLifecycleOwner.lifecycleScope.launchWhenResumed {
|
||||||
viewModel.recipeFlow.collect {
|
viewModel.recipeFlow.collect {
|
||||||
@@ -79,11 +89,6 @@ class RecipesFragment : Fragment() {
|
|||||||
binding.refresher.isRefreshing = false
|
binding.refresher.isRefreshing = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
viewLifecycleOwner.lifecycleScope.launchWhenResumed {
|
|
||||||
adapter.loadStateFlow.collect {
|
|
||||||
Timber.d("New load state: $it")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import gq.kirmanak.mealie.databinding.ViewHolderRecipeBinding
|
|||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class RecipesPagingAdapter(
|
class RecipesPagingAdapter(
|
||||||
private val viewModel: RecipeViewModel
|
private val viewModel: RecipeViewModel,
|
||||||
|
private val clickListener: (RecipeSummaryEntity) -> Unit
|
||||||
) : PagingDataAdapter<RecipeSummaryEntity, RecipeViewHolder>(RecipeDiffCallback) {
|
) : PagingDataAdapter<RecipeSummaryEntity, RecipeViewHolder>(RecipeDiffCallback) {
|
||||||
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
@@ -20,7 +21,7 @@ class RecipesPagingAdapter(
|
|||||||
Timber.v("onCreateViewHolder() called with: parent = $parent, viewType = $viewType")
|
Timber.v("onCreateViewHolder() called with: parent = $parent, viewType = $viewType")
|
||||||
val inflater = LayoutInflater.from(parent.context)
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
val binding = ViewHolderRecipeBinding.inflate(inflater, parent, false)
|
val binding = ViewHolderRecipeBinding.inflate(inflater, parent, false)
|
||||||
return RecipeViewHolder(binding, viewModel)
|
return RecipeViewHolder(binding, viewModel, clickListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
private object RecipeDiffCallback : DiffUtil.ItemCallback<RecipeSummaryEntity>() {
|
private object RecipeDiffCallback : DiffUtil.ItemCallback<RecipeSummaryEntity>() {
|
||||||
|
|||||||
@@ -1,8 +1,69 @@
|
|||||||
package gq.kirmanak.mealie.ui.recipes.info
|
package gq.kirmanak.mealie.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.Fragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.navigation.fragment.navArgs
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import gq.kirmanak.mealie.databinding.FragmentRecipeInfoBinding
|
||||||
|
import gq.kirmanak.mealie.ui.auth.AuthenticationViewModel
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class RecipeInfoFragment : Fragment() {
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.mealie.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.mealie.data.recipes.RecipeImageLoader
|
||||||
|
import gq.kirmanak.mealie.data.recipes.RecipeRepo
|
||||||
|
import gq.kirmanak.mealie.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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,5 +25,24 @@
|
|||||||
app:destination="@id/authenticationFragment"
|
app:destination="@id/authenticationFragment"
|
||||||
app:popUpTo="@id/nav_graph"
|
app:popUpTo="@id/nav_graph"
|
||||||
app:popUpToInclusive="true" />
|
app:popUpToInclusive="true" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_recipesFragment_to_recipeInfoFragment"
|
||||||
|
app:destination="@id/recipeInfoFragment" />
|
||||||
|
</fragment>
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/recipeInfoFragment"
|
||||||
|
android:name="gq.kirmanak.mealie.ui.recipes.info.RecipeInfoFragment"
|
||||||
|
android:label="RecipeInfoFragment">
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_recipeInfoFragment_to_authenticationFragment"
|
||||||
|
app:destination="@id/authenticationFragment"
|
||||||
|
app:popUpTo="@id/nav_graph"
|
||||||
|
app:popUpToInclusive="true" />
|
||||||
|
<argument
|
||||||
|
android:name="recipe_slug"
|
||||||
|
app:argType="string" />
|
||||||
|
<argument
|
||||||
|
android:name="recipe_id"
|
||||||
|
app:argType="long" />
|
||||||
</fragment>
|
</fragment>
|
||||||
</navigation>
|
</navigation>
|
||||||
Reference in New Issue
Block a user