From 407a73d444a2a9e018546caeaf33b173a91c5bf4 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Mon, 8 Nov 2021 21:09:55 +0300 Subject: [PATCH] Implement showing recipe pictures in the list --- app/build.gradle | 2 +- .../kirmanak/mealie/data/RetrofitBuilder.kt | 3 +-- .../kirmanak/mealie/data/auth/AuthRepoImpl.kt | 5 +++-- .../mealie/data/recipes/RecipeImageLoader.kt | 20 ++++++++++++++++++ .../mealie/data/recipes/RecipeRepoImpl.kt | 2 +- .../java/gq/kirmanak/mealie/ui/ImageLoader.kt | 15 +++++++++++++ .../mealie/ui/recipes/RecipeViewModel.kt | 14 +++++++++++- .../mealie/ui/recipes/RecipesFragment.kt | 2 +- .../mealie/ui/recipes/RecipesPagingAdapter.kt | 13 ++++++++---- .../main/res/drawable/placeholder_recipe.webp | Bin 0 -> 18478 bytes 10 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/gq/kirmanak/mealie/data/recipes/RecipeImageLoader.kt create mode 100644 app/src/main/java/gq/kirmanak/mealie/ui/ImageLoader.kt create mode 100644 app/src/main/res/drawable/placeholder_recipe.webp diff --git a/app/build.gradle b/app/build.gradle index 49c2644..c599abb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -89,7 +89,7 @@ dependencies { def glide_version = "4.12.0" 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.espresso:espresso-core:3.4.0" diff --git a/app/src/main/java/gq/kirmanak/mealie/data/RetrofitBuilder.kt b/app/src/main/java/gq/kirmanak/mealie/data/RetrofitBuilder.kt index b0157e5..d8d699d 100644 --- a/app/src/main/java/gq/kirmanak/mealie/data/RetrofitBuilder.kt +++ b/app/src/main/java/gq/kirmanak/mealie/data/RetrofitBuilder.kt @@ -12,10 +12,9 @@ import javax.inject.Inject class RetrofitBuilder @Inject constructor(private val okHttpBuilder: OkHttpBuilder) { fun buildRetrofit(baseUrl: String, token: String? = null): Retrofit { Timber.v("buildRetrofit() called with: baseUrl = $baseUrl, token = $token") - val url = if (baseUrl.startsWith("http")) baseUrl else "https://$baseUrl" val contentType = "application/json".toMediaType() return Retrofit.Builder() - .baseUrl(url) + .baseUrl(baseUrl) .client(okHttpBuilder.buildOkHttp(token)) .addConverterFactory(Json.asConverterFactory(contentType)) .build() diff --git a/app/src/main/java/gq/kirmanak/mealie/data/auth/AuthRepoImpl.kt b/app/src/main/java/gq/kirmanak/mealie/data/auth/AuthRepoImpl.kt index 4ea3c89..62711eb 100644 --- a/app/src/main/java/gq/kirmanak/mealie/data/auth/AuthRepoImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealie/data/auth/AuthRepoImpl.kt @@ -20,11 +20,12 @@ class AuthRepoImpl @Inject constructor( baseUrl: String ): Throwable? { 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") if (authResult.isFailure) return authResult.exceptionOrNull() val token = checkNotNull(authResult.getOrNull()) - storage.storeAuthData(token, baseUrl) + storage.storeAuthData(token, url) return null } diff --git a/app/src/main/java/gq/kirmanak/mealie/data/recipes/RecipeImageLoader.kt b/app/src/main/java/gq/kirmanak/mealie/data/recipes/RecipeImageLoader.kt new file mode 100644 index 0000000..56f1b4f --- /dev/null +++ b/app/src/main/java/gq/kirmanak/mealie/data/recipes/RecipeImageLoader.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealie/data/recipes/RecipeRepoImpl.kt b/app/src/main/java/gq/kirmanak/mealie/data/recipes/RecipeRepoImpl.kt index 3106d22..dfcb17d 100644 --- a/app/src/main/java/gq/kirmanak/mealie/data/recipes/RecipeRepoImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealie/data/recipes/RecipeRepoImpl.kt @@ -13,7 +13,7 @@ class RecipeRepoImpl @Inject constructor( private val storage: RecipeStorage ) : RecipeRepo { override fun createPager(): Pager { - val pagingConfig = PagingConfig(pageSize = 30) + val pagingConfig = PagingConfig(pageSize = 30, enablePlaceholders = false, prefetchDistance = 10) return Pager(pagingConfig, 0, mediator) { storage.queryRecipes() } diff --git a/app/src/main/java/gq/kirmanak/mealie/ui/ImageLoader.kt b/app/src/main/java/gq/kirmanak/mealie/ui/ImageLoader.kt new file mode 100644 index 0000000..a2604e8 --- /dev/null +++ b/app/src/main/java/gq/kirmanak/mealie/ui/ImageLoader.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealie/ui/recipes/RecipeViewModel.kt b/app/src/main/java/gq/kirmanak/mealie/ui/recipes/RecipeViewModel.kt index 3cd9269..3020093 100644 --- a/app/src/main/java/gq/kirmanak/mealie/ui/recipes/RecipeViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealie/ui/recipes/RecipeViewModel.kt @@ -1,18 +1,30 @@ package gq.kirmanak.mealie.ui.recipes +import android.widget.ImageView import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.Pager import androidx.paging.PagingData import androidx.paging.cachedIn 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.db.RecipeEntity import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch import javax.inject.Inject @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 by lazy { recipeRepo.createPager() } val recipeFlow: Flow> by lazy { pager.flow.cachedIn(viewModelScope) } + + fun loadRecipeImage(view: ImageView, recipe: RecipeEntity?) { + viewModelScope.launch { + recipeImageLoader.loadRecipeImage(view, recipe?.slug) + } + } } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealie/ui/recipes/RecipesFragment.kt b/app/src/main/java/gq/kirmanak/mealie/ui/recipes/RecipesFragment.kt index 944320f..3df02fb 100644 --- a/app/src/main/java/gq/kirmanak/mealie/ui/recipes/RecipesFragment.kt +++ b/app/src/main/java/gq/kirmanak/mealie/ui/recipes/RecipesFragment.kt @@ -31,7 +31,7 @@ class RecipesFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.recipes.layoutManager = LinearLayoutManager(requireContext()) - val recipesPagingAdapter = RecipesPagingAdapter() + val recipesPagingAdapter = RecipesPagingAdapter(viewModel) binding.recipes.adapter = recipesPagingAdapter lifecycleScope.launchWhenResumed { viewModel.recipeFlow.collectLatest { diff --git a/app/src/main/java/gq/kirmanak/mealie/ui/recipes/RecipesPagingAdapter.kt b/app/src/main/java/gq/kirmanak/mealie/ui/recipes/RecipesPagingAdapter.kt index 8567e95..7e3957e 100644 --- a/app/src/main/java/gq/kirmanak/mealie/ui/recipes/RecipesPagingAdapter.kt +++ b/app/src/main/java/gq/kirmanak/mealie/ui/recipes/RecipesPagingAdapter.kt @@ -9,7 +9,9 @@ import gq.kirmanak.mealie.data.recipes.db.RecipeEntity import gq.kirmanak.mealie.databinding.ViewHolderRecipeBinding import timber.log.Timber -class RecipesPagingAdapter : PagingDataAdapter(RecipeDiffCallback) { +class RecipesPagingAdapter( + private val viewModel: RecipeViewModel +) : PagingDataAdapter(RecipeDiffCallback) { override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) { val item = getItem(position) holder.bind(item) @@ -19,14 +21,17 @@ class RecipesPagingAdapter : PagingDataAdapter(R 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) + return RecipeViewHolder(binding, viewModel) } } -class RecipeViewHolder(private val binding: ViewHolderRecipeBinding) : - RecyclerView.ViewHolder(binding.root) { +class RecipeViewHolder( + private val binding: ViewHolderRecipeBinding, + private val recipeViewModel: RecipeViewModel +) : RecyclerView.ViewHolder(binding.root) { fun bind(item: RecipeEntity?) { binding.name.text = item?.name + recipeViewModel.loadRecipeImage(binding.image, item) } } diff --git a/app/src/main/res/drawable/placeholder_recipe.webp b/app/src/main/res/drawable/placeholder_recipe.webp new file mode 100644 index 0000000000000000000000000000000000000000..1ec8080e219cc95b8a6c725e59bfaddf44b4a6dd GIT binary patch literal 18478 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

|w!v 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 literal 0 HcmV?d00001