Implement showing recipe pictures in the list
This commit is contained in:
@@ -89,7 +89,7 @@ dependencies {
|
|||||||
|
|
||||||
def glide_version = "4.12.0"
|
def glide_version = "4.12.0"
|
||||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
implementation "com.github.bumptech.glide:glide:$glide_version"
|
||||||
annotationProcessor "com.github.bumptech.glide:compiler:$glide_version"
|
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
||||||
|
|
||||||
androidTestImplementation "androidx.test.ext:junit:1.1.3"
|
androidTestImplementation "androidx.test.ext:junit:1.1.3"
|
||||||
androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
|
androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
|
||||||
|
|||||||
@@ -12,10 +12,9 @@ import javax.inject.Inject
|
|||||||
class RetrofitBuilder @Inject constructor(private val okHttpBuilder: OkHttpBuilder) {
|
class RetrofitBuilder @Inject constructor(private val okHttpBuilder: OkHttpBuilder) {
|
||||||
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")
|
||||||
val url = if (baseUrl.startsWith("http")) baseUrl else "https://$baseUrl"
|
|
||||||
val contentType = "application/json".toMediaType()
|
val contentType = "application/json".toMediaType()
|
||||||
return Retrofit.Builder()
|
return Retrofit.Builder()
|
||||||
.baseUrl(url)
|
.baseUrl(baseUrl)
|
||||||
.client(okHttpBuilder.buildOkHttp(token))
|
.client(okHttpBuilder.buildOkHttp(token))
|
||||||
.addConverterFactory(Json.asConverterFactory(contentType))
|
.addConverterFactory(Json.asConverterFactory(contentType))
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@@ -20,11 +20,12 @@ class AuthRepoImpl @Inject constructor(
|
|||||||
baseUrl: String
|
baseUrl: String
|
||||||
): Throwable? {
|
): Throwable? {
|
||||||
Timber.v("authenticate() called with: username = $username, password = $password, baseUrl = $baseUrl")
|
Timber.v("authenticate() called with: username = $username, password = $password, baseUrl = $baseUrl")
|
||||||
val authResult = dataSource.authenticate(username, password, baseUrl)
|
val url = if (baseUrl.startsWith("http")) baseUrl else "https://$baseUrl"
|
||||||
|
val authResult = dataSource.authenticate(username, password, url)
|
||||||
Timber.d("authenticate result is $authResult")
|
Timber.d("authenticate result is $authResult")
|
||||||
if (authResult.isFailure) return authResult.exceptionOrNull()
|
if (authResult.isFailure) return authResult.exceptionOrNull()
|
||||||
val token = checkNotNull(authResult.getOrNull())
|
val token = checkNotNull(authResult.getOrNull())
|
||||||
storage.storeAuthData(token, baseUrl)
|
storage.storeAuthData(token, url)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package gq.kirmanak.mealie.data.recipes
|
||||||
|
|
||||||
|
import android.widget.ImageView
|
||||||
|
import gq.kirmanak.mealie.R
|
||||||
|
import gq.kirmanak.mealie.data.auth.AuthRepo
|
||||||
|
import gq.kirmanak.mealie.ui.ImageLoader
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RecipeImageLoader @Inject constructor(
|
||||||
|
private val imageLoader: ImageLoader,
|
||||||
|
private val authRepo: AuthRepo
|
||||||
|
) {
|
||||||
|
suspend fun loadRecipeImage(view: ImageView, slug: String?) {
|
||||||
|
val baseUrl = authRepo.getBaseUrl()
|
||||||
|
val recipeImageUrl =
|
||||||
|
if (baseUrl.isNullOrBlank()) null
|
||||||
|
else "$baseUrl/api/media/recipes/$slug/images/original.webp"
|
||||||
|
imageLoader.loadImage(recipeImageUrl, R.drawable.placeholder_recipe, view)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ class RecipeRepoImpl @Inject constructor(
|
|||||||
private val storage: RecipeStorage
|
private val storage: RecipeStorage
|
||||||
) : RecipeRepo {
|
) : RecipeRepo {
|
||||||
override fun createPager(): Pager<Int, RecipeEntity> {
|
override fun createPager(): Pager<Int, RecipeEntity> {
|
||||||
val pagingConfig = PagingConfig(pageSize = 30)
|
val pagingConfig = PagingConfig(pageSize = 30, enablePlaceholders = false, prefetchDistance = 10)
|
||||||
return Pager(pagingConfig, 0, mediator) {
|
return Pager(pagingConfig, 0, mediator) {
|
||||||
storage.queryRecipes()
|
storage.queryRecipes()
|
||||||
}
|
}
|
||||||
|
|||||||
15
app/src/main/java/gq/kirmanak/mealie/ui/ImageLoader.kt
Normal file
15
app/src/main/java/gq/kirmanak/mealie/ui/ImageLoader.kt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package gq.kirmanak.mealie.ui
|
||||||
|
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ImageLoader @Inject constructor() {
|
||||||
|
fun loadImage(url: String?, @DrawableRes placeholderId: Int, imageView: ImageView) {
|
||||||
|
with(Glide.with(imageView)) {
|
||||||
|
if (url.isNullOrBlank()) clear(imageView)
|
||||||
|
else load(url).placeholder(placeholderId).into(imageView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,30 @@
|
|||||||
package gq.kirmanak.mealie.ui.recipes
|
package gq.kirmanak.mealie.ui.recipes
|
||||||
|
|
||||||
|
import android.widget.ImageView
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.paging.Pager
|
import androidx.paging.Pager
|
||||||
import androidx.paging.PagingData
|
import androidx.paging.PagingData
|
||||||
import androidx.paging.cachedIn
|
import androidx.paging.cachedIn
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
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.RecipeRepo
|
||||||
import gq.kirmanak.mealie.data.recipes.db.RecipeEntity
|
import gq.kirmanak.mealie.data.recipes.db.RecipeEntity
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class RecipeViewModel @Inject constructor(private val recipeRepo: RecipeRepo) : ViewModel() {
|
class RecipeViewModel @Inject constructor(
|
||||||
|
private val recipeRepo: RecipeRepo,
|
||||||
|
private val recipeImageLoader: RecipeImageLoader
|
||||||
|
) : ViewModel() {
|
||||||
private val pager: Pager<Int, RecipeEntity> by lazy { recipeRepo.createPager() }
|
private val pager: Pager<Int, RecipeEntity> by lazy { recipeRepo.createPager() }
|
||||||
val recipeFlow: Flow<PagingData<RecipeEntity>> by lazy { pager.flow.cachedIn(viewModelScope) }
|
val recipeFlow: Flow<PagingData<RecipeEntity>> by lazy { pager.flow.cachedIn(viewModelScope) }
|
||||||
|
|
||||||
|
fun loadRecipeImage(view: ImageView, recipe: RecipeEntity?) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
recipeImageLoader.loadRecipeImage(view, recipe?.slug)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ class RecipesFragment : Fragment() {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
binding.recipes.layoutManager = LinearLayoutManager(requireContext())
|
binding.recipes.layoutManager = LinearLayoutManager(requireContext())
|
||||||
val recipesPagingAdapter = RecipesPagingAdapter()
|
val recipesPagingAdapter = RecipesPagingAdapter(viewModel)
|
||||||
binding.recipes.adapter = recipesPagingAdapter
|
binding.recipes.adapter = recipesPagingAdapter
|
||||||
lifecycleScope.launchWhenResumed {
|
lifecycleScope.launchWhenResumed {
|
||||||
viewModel.recipeFlow.collectLatest {
|
viewModel.recipeFlow.collectLatest {
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import gq.kirmanak.mealie.data.recipes.db.RecipeEntity
|
|||||||
import gq.kirmanak.mealie.databinding.ViewHolderRecipeBinding
|
import gq.kirmanak.mealie.databinding.ViewHolderRecipeBinding
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class RecipesPagingAdapter : PagingDataAdapter<RecipeEntity, RecipeViewHolder>(RecipeDiffCallback) {
|
class RecipesPagingAdapter(
|
||||||
|
private val viewModel: RecipeViewModel
|
||||||
|
) : PagingDataAdapter<RecipeEntity, RecipeViewHolder>(RecipeDiffCallback) {
|
||||||
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
holder.bind(item)
|
holder.bind(item)
|
||||||
@@ -19,14 +21,17 @@ class RecipesPagingAdapter : PagingDataAdapter<RecipeEntity, RecipeViewHolder>(R
|
|||||||
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)
|
return RecipeViewHolder(binding, viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RecipeViewHolder(private val binding: ViewHolderRecipeBinding) :
|
class RecipeViewHolder(
|
||||||
RecyclerView.ViewHolder(binding.root) {
|
private val binding: ViewHolderRecipeBinding,
|
||||||
|
private val recipeViewModel: RecipeViewModel
|
||||||
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
fun bind(item: RecipeEntity?) {
|
fun bind(item: RecipeEntity?) {
|
||||||
binding.name.text = item?.name
|
binding.name.text = item?.name
|
||||||
|
recipeViewModel.loadRecipeImage(binding.image, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
app/src/main/res/drawable/placeholder_recipe.webp
Normal file
BIN
app/src/main/res/drawable/placeholder_recipe.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Reference in New Issue
Block a user