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 0000000..1ec8080 Binary files /dev/null and b/app/src/main/res/drawable/placeholder_recipe.webp differ