Implement showing recipe pictures in the list

This commit is contained in:
Kirill Kamakin
2021-11-08 21:09:55 +03:00
parent 67b75b710d
commit 407a73d444
10 changed files with 64 additions and 12 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB