Replace Picasso with Glide
This commit is contained in:
@@ -148,8 +148,9 @@ dependencies {
|
|||||||
// https://github.com/Kotlin/kotlinx-datetime/releases
|
// https://github.com/Kotlin/kotlinx-datetime/releases
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.3.1"
|
implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.3.1"
|
||||||
|
|
||||||
// https://github.com/square/picasso/releases
|
// https://github.com/bumptech/glide/releases
|
||||||
implementation "com.squareup.picasso:picasso:2.8"
|
def glide_version = "4.13.1"
|
||||||
|
implementation "com.github.bumptech.glide:glide:$glide_version"
|
||||||
|
|
||||||
// https://github.com/androidbroadcast/ViewBindingPropertyDelegate/releases
|
// https://github.com/androidbroadcast/ViewBindingPropertyDelegate/releases
|
||||||
implementation "com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.6"
|
implementation "com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.6"
|
||||||
|
|||||||
9
app/proguard-rules.pro
vendored
9
app/proguard-rules.pro
vendored
@@ -48,3 +48,12 @@
|
|||||||
-dontwarn org.bouncycastle.**
|
-dontwarn org.bouncycastle.**
|
||||||
-dontwarn org.openjsse.**
|
-dontwarn org.openjsse.**
|
||||||
### OkHttp warnings ###
|
### OkHttp warnings ###
|
||||||
|
|
||||||
|
### Glide https://bumptech.github.io/glide/doc/download-setup.html#proguard ###
|
||||||
|
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||||
|
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||||
|
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
||||||
|
**[] $VALUES;
|
||||||
|
public *;
|
||||||
|
}
|
||||||
|
### Glide https://bumptech.github.io/glide/doc/download-setup.html#proguard ###
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
package="gq.kirmanak.mealient">
|
package="gq.kirmanak.mealient">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="gq.kirmanak.mealient.App"
|
android:name="gq.kirmanak.mealient.App"
|
||||||
|
|||||||
@@ -2,24 +2,29 @@ package gq.kirmanak.mealient.data.recipes.impl
|
|||||||
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import dagger.hilt.android.scopes.FragmentScoped
|
||||||
import gq.kirmanak.mealient.R
|
import gq.kirmanak.mealient.R
|
||||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||||
|
import gq.kirmanak.mealient.extensions.launchWhenViewResumed
|
||||||
import gq.kirmanak.mealient.ui.images.ImageLoader
|
import gq.kirmanak.mealient.ui.images.ImageLoader
|
||||||
import gq.kirmanak.mealient.ui.recipes.RecipeImageLoader
|
import gq.kirmanak.mealient.ui.recipes.RecipeImageLoader
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
@FragmentScoped
|
||||||
class RecipeImageLoaderImpl @Inject constructor(
|
class RecipeImageLoaderImpl @Inject constructor(
|
||||||
private val imageLoader: ImageLoader,
|
private val imageLoader: ImageLoader,
|
||||||
private val baseURLStorage: BaseURLStorage,
|
private val baseURLStorage: BaseURLStorage,
|
||||||
|
private val fragment: Fragment,
|
||||||
): RecipeImageLoader {
|
): RecipeImageLoader {
|
||||||
|
|
||||||
override suspend fun loadRecipeImage(view: ImageView, slug: String?) {
|
override fun loadRecipeImage(view: ImageView, slug: String?) {
|
||||||
Timber.v("loadRecipeImage() called with: view = $view, slug = $slug")
|
Timber.v("loadRecipeImage() called with: view = $view, slug = $slug")
|
||||||
imageLoader.loadImage(generateImageUrl(slug), R.drawable.placeholder_recipe, view)
|
fragment.launchWhenViewResumed {
|
||||||
|
imageLoader.loadImage(generateImageUrl(slug), R.drawable.placeholder_recipe, view)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
|||||||
@@ -13,12 +13,10 @@ import gq.kirmanak.mealient.data.network.createServiceFactory
|
|||||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||||
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
|
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
|
||||||
import gq.kirmanak.mealient.data.recipes.db.RecipeStorageImpl
|
import gq.kirmanak.mealient.data.recipes.db.RecipeStorageImpl
|
||||||
import gq.kirmanak.mealient.data.recipes.impl.RecipeImageLoaderImpl
|
|
||||||
import gq.kirmanak.mealient.data.recipes.impl.RecipeRepoImpl
|
import gq.kirmanak.mealient.data.recipes.impl.RecipeRepoImpl
|
||||||
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
||||||
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSourceImpl
|
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSourceImpl
|
||||||
import gq.kirmanak.mealient.data.recipes.network.RecipeService
|
import gq.kirmanak.mealient.data.recipes.network.RecipeService
|
||||||
import gq.kirmanak.mealient.ui.recipes.RecipeImageLoader
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import javax.inject.Named
|
import javax.inject.Named
|
||||||
@@ -40,10 +38,6 @@ interface RecipeModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
fun provideRecipeRepo(recipeRepoImpl: RecipeRepoImpl): RecipeRepo
|
fun provideRecipeRepo(recipeRepoImpl: RecipeRepoImpl): RecipeRepo
|
||||||
|
|
||||||
@Binds
|
|
||||||
@Singleton
|
|
||||||
fun provideRecipeImageLoader(recipeImageLoaderImpl: RecipeImageLoaderImpl): RecipeImageLoader
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -1,27 +1,25 @@
|
|||||||
package gq.kirmanak.mealient.di
|
package gq.kirmanak.mealient.di
|
||||||
|
|
||||||
import com.squareup.picasso.Picasso
|
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.android.components.FragmentComponent
|
||||||
|
import dagger.hilt.android.scopes.FragmentScoped
|
||||||
|
import gq.kirmanak.mealient.data.recipes.impl.RecipeImageLoaderImpl
|
||||||
import gq.kirmanak.mealient.ui.images.ImageLoader
|
import gq.kirmanak.mealient.ui.images.ImageLoader
|
||||||
import gq.kirmanak.mealient.ui.images.ImageLoaderPicasso
|
import gq.kirmanak.mealient.ui.images.ImageLoaderGlide
|
||||||
import gq.kirmanak.mealient.ui.images.PicassoBuilder
|
import gq.kirmanak.mealient.ui.recipes.RecipeImageLoader
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(FragmentComponent::class)
|
||||||
interface UiModule {
|
interface UiModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@Singleton
|
@FragmentScoped
|
||||||
fun bindImageLoader(imageLoaderGlide: ImageLoaderPicasso): ImageLoader
|
fun bindImageLoader(imageLoaderGlide: ImageLoaderGlide): ImageLoader
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@FragmentScoped
|
||||||
|
fun provideRecipeImageLoader(recipeImageLoaderImpl: RecipeImageLoaderImpl): RecipeImageLoader
|
||||||
|
|
||||||
companion object {
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun providePicasso(picassoBuilder: PicassoBuilder): Picasso = picassoBuilder.buildPicasso()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -2,11 +2,15 @@ package gq.kirmanak.mealient.extensions
|
|||||||
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
inline fun <T> Fragment.collectWithViewLifecycle(
|
inline fun <T> Fragment.collectWhenViewResumed(
|
||||||
flow: Flow<T>,
|
flow: Flow<T>,
|
||||||
crossinline collector: suspend (T) -> Unit,
|
crossinline collector: suspend (T) -> Unit,
|
||||||
) = viewLifecycleOwner.lifecycleScope.launch { flow.collect(collector) }
|
) = launchWhenViewResumed { flow.collect(collector) }
|
||||||
|
|
||||||
|
fun Fragment.launchWhenViewResumed(
|
||||||
|
block: suspend CoroutineScope.() -> Unit,
|
||||||
|
) = viewLifecycleOwner.lifecycleScope.launchWhenResumed(block)
|
||||||
@@ -4,5 +4,6 @@ import android.widget.ImageView
|
|||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
|
|
||||||
interface ImageLoader {
|
interface ImageLoader {
|
||||||
|
|
||||||
fun loadImage(url: String?, @DrawableRes placeholderId: Int, imageView: ImageView)
|
fun loadImage(url: String?, @DrawableRes placeholderId: Int, imageView: ImageView)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package gq.kirmanak.mealient.ui.images
|
||||||
|
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.RequestManager
|
||||||
|
import dagger.hilt.android.scopes.FragmentScoped
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@FragmentScoped
|
||||||
|
class ImageLoaderGlide @Inject constructor(
|
||||||
|
private val fragment: Fragment,
|
||||||
|
) : ImageLoader {
|
||||||
|
|
||||||
|
private val requestManager: RequestManager
|
||||||
|
get() = Glide.with(fragment)
|
||||||
|
|
||||||
|
init {
|
||||||
|
Timber.v("init called with fragment = ${fragment.javaClass.simpleName}")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadImage(url: String?, placeholderId: Int, imageView: ImageView) {
|
||||||
|
Timber.v("loadImage() called with: url = $url, placeholderId = $placeholderId, imageView = $imageView")
|
||||||
|
requestManager.load(url)
|
||||||
|
.placeholder(placeholderId)
|
||||||
|
.centerCrop()
|
||||||
|
.into(imageView)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.ui.images
|
|
||||||
|
|
||||||
import android.widget.ImageView
|
|
||||||
import com.squareup.picasso.Picasso
|
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class ImageLoaderPicasso @Inject constructor(
|
|
||||||
private val picasso: Picasso
|
|
||||||
) : ImageLoader {
|
|
||||||
|
|
||||||
override fun loadImage(url: String?, placeholderId: Int, imageView: ImageView) {
|
|
||||||
Timber.v("loadImage() called with: url = $url, placeholderId = $placeholderId, imageView = $imageView")
|
|
||||||
val width = imageView.measuredWidth
|
|
||||||
val height = imageView.measuredHeight
|
|
||||||
Timber.d("loadImage: width = $width, height = $height")
|
|
||||||
picasso.load(url).apply {
|
|
||||||
placeholder(placeholderId)
|
|
||||||
if (width > 0 && height > 0) resize(width, height).centerCrop()
|
|
||||||
into(imageView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.ui.images
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.squareup.picasso.OkHttp3Downloader
|
|
||||||
import com.squareup.picasso.Picasso
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import gq.kirmanak.mealient.BuildConfig
|
|
||||||
import gq.kirmanak.mealient.di.AUTH_OK_HTTP
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Named
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class PicassoBuilder @Inject constructor(
|
|
||||||
@ApplicationContext private val context: Context,
|
|
||||||
@Named(AUTH_OK_HTTP) private val okHttpClient: OkHttpClient
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun buildPicasso(): Picasso {
|
|
||||||
Timber.v("buildPicasso() called")
|
|
||||||
val builder = Picasso.Builder(context)
|
|
||||||
builder.downloader(OkHttp3Downloader(okHttpClient))
|
|
||||||
if (BuildConfig.DEBUG_PICASSO) {
|
|
||||||
builder.loggingEnabled(true)
|
|
||||||
builder.indicatorsEnabled(true)
|
|
||||||
builder.listener { _, uri, exception ->
|
|
||||||
Timber.tag("Picasso").e(exception, "Can't load from $uri")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return builder.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,5 +3,6 @@ package gq.kirmanak.mealient.ui.recipes
|
|||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
|
||||||
interface RecipeImageLoader {
|
interface RecipeImageLoader {
|
||||||
suspend fun loadRecipeImage(view: ImageView, slug: String?)
|
|
||||||
|
fun loadRecipeImage(view: ImageView, slug: String?)
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ import timber.log.Timber
|
|||||||
|
|
||||||
class RecipeViewHolder(
|
class RecipeViewHolder(
|
||||||
private val binding: ViewHolderRecipeBinding,
|
private val binding: ViewHolderRecipeBinding,
|
||||||
private val recipeViewModel: RecipeViewModel,
|
private val recipeImageLoader: RecipeImageLoader,
|
||||||
private val clickListener: (RecipeSummaryEntity) -> Unit,
|
private val clickListener: (RecipeSummaryEntity) -> Unit,
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
private val loadingPlaceholder by lazy {
|
private val loadingPlaceholder by lazy {
|
||||||
@@ -18,7 +18,7 @@ class RecipeViewHolder(
|
|||||||
fun bind(item: RecipeSummaryEntity?) {
|
fun bind(item: RecipeSummaryEntity?) {
|
||||||
Timber.v("bind() called with: item = $item")
|
Timber.v("bind() called with: item = $item")
|
||||||
binding.name.text = item?.name ?: loadingPlaceholder
|
binding.name.text = item?.name ?: loadingPlaceholder
|
||||||
recipeViewModel.loadRecipeImage(binding.image, item)
|
recipeImageLoader.loadRecipeImage(binding.image, item?.slug)
|
||||||
item?.let { entity ->
|
item?.let { entity ->
|
||||||
binding.root.setOnClickListener {
|
binding.root.setOnClickListener {
|
||||||
Timber.d("bind: item clicked $entity")
|
Timber.d("bind: item clicked $entity")
|
||||||
|
|||||||
@@ -1,28 +1,15 @@
|
|||||||
package gq.kirmanak.mealient.ui.recipes
|
package gq.kirmanak.mealient.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.cachedIn
|
import androidx.paging.cachedIn
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||||
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeSummaryEntity
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class RecipeViewModel @Inject constructor(
|
class RecipeViewModel @Inject constructor(recipeRepo: RecipeRepo) : ViewModel() {
|
||||||
recipeRepo: RecipeRepo,
|
|
||||||
private val recipeImageLoader: RecipeImageLoader
|
|
||||||
) : ViewModel() {
|
|
||||||
|
|
||||||
val pagingData = recipeRepo.createPager().flow.cachedIn(viewModelScope)
|
val pagingData = recipeRepo.createPager().flow.cachedIn(viewModelScope)
|
||||||
|
|
||||||
fun loadRecipeImage(view: ImageView, recipeSummary: RecipeSummaryEntity?) {
|
|
||||||
Timber.v("loadRecipeImage() called with: view = $view, recipeSummary = $recipeSummary")
|
|
||||||
viewModelScope.launch {
|
|
||||||
recipeImageLoader.loadRecipeImage(view, recipeSummary?.slug)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -11,10 +11,11 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||||||
import gq.kirmanak.mealient.R
|
import gq.kirmanak.mealient.R
|
||||||
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeSummaryEntity
|
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeSummaryEntity
|
||||||
import gq.kirmanak.mealient.databinding.FragmentRecipesBinding
|
import gq.kirmanak.mealient.databinding.FragmentRecipesBinding
|
||||||
import gq.kirmanak.mealient.extensions.collectWithViewLifecycle
|
import gq.kirmanak.mealient.extensions.collectWhenViewResumed
|
||||||
import gq.kirmanak.mealient.extensions.refreshRequestFlow
|
import gq.kirmanak.mealient.extensions.refreshRequestFlow
|
||||||
import gq.kirmanak.mealient.ui.activity.MainActivityViewModel
|
import gq.kirmanak.mealient.ui.activity.MainActivityViewModel
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class RecipesFragment : Fragment(R.layout.fragment_recipes) {
|
class RecipesFragment : Fragment(R.layout.fragment_recipes) {
|
||||||
@@ -22,6 +23,9 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) {
|
|||||||
private val viewModel by viewModels<RecipeViewModel>()
|
private val viewModel by viewModels<RecipeViewModel>()
|
||||||
private val activityViewModel by activityViewModels<MainActivityViewModel>()
|
private val activityViewModel by activityViewModels<MainActivityViewModel>()
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var recipeImageLoader: RecipeImageLoader
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
|
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
|
||||||
@@ -41,17 +45,17 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) {
|
|||||||
|
|
||||||
private fun setupRecipeAdapter() {
|
private fun setupRecipeAdapter() {
|
||||||
Timber.v("setupRecipeAdapter() called")
|
Timber.v("setupRecipeAdapter() called")
|
||||||
val adapter = RecipesPagingAdapter(viewModel, ::navigateToRecipeInfo)
|
val adapter = RecipesPagingAdapter(recipeImageLoader, ::navigateToRecipeInfo)
|
||||||
binding.recipes.adapter = adapter
|
binding.recipes.adapter = adapter
|
||||||
collectWithViewLifecycle(viewModel.pagingData) {
|
collectWhenViewResumed(viewModel.pagingData) {
|
||||||
Timber.v("setupRecipeAdapter: received data update")
|
Timber.v("setupRecipeAdapter: received data update")
|
||||||
adapter.submitData(lifecycle, it)
|
adapter.submitData(lifecycle, it)
|
||||||
}
|
}
|
||||||
collectWithViewLifecycle(adapter.onPagesUpdatedFlow) {
|
collectWhenViewResumed(adapter.onPagesUpdatedFlow) {
|
||||||
Timber.v("setupRecipeAdapter: pages updated")
|
Timber.v("setupRecipeAdapter: pages updated")
|
||||||
binding.refresher.isRefreshing = false
|
binding.refresher.isRefreshing = false
|
||||||
}
|
}
|
||||||
collectWithViewLifecycle(binding.refresher.refreshRequestFlow()) {
|
collectWhenViewResumed(binding.refresher.refreshRequestFlow()) {
|
||||||
Timber.v("setupRecipeAdapter: received refresh request")
|
Timber.v("setupRecipeAdapter: received refresh request")
|
||||||
adapter.refresh()
|
adapter.refresh()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import gq.kirmanak.mealient.databinding.ViewHolderRecipeBinding
|
|||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class RecipesPagingAdapter(
|
class RecipesPagingAdapter(
|
||||||
private val viewModel: RecipeViewModel,
|
private val recipeImageLoader: RecipeImageLoader,
|
||||||
private val clickListener: (RecipeSummaryEntity) -> Unit
|
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) {
|
||||||
@@ -21,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, clickListener)
|
return RecipeViewHolder(binding, recipeImageLoader, clickListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
private object RecipeDiffCallback : DiffUtil.ItemCallback<RecipeSummaryEntity>() {
|
private object RecipeDiffCallback : DiffUtil.ItemCallback<RecipeSummaryEntity>() {
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
|||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import gq.kirmanak.mealient.R
|
import gq.kirmanak.mealient.R
|
||||||
import gq.kirmanak.mealient.databinding.FragmentRecipeInfoBinding
|
import gq.kirmanak.mealient.databinding.FragmentRecipeInfoBinding
|
||||||
|
import gq.kirmanak.mealient.ui.recipes.RecipeImageLoader
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class RecipeInfoFragment : BottomSheetDialogFragment() {
|
class RecipeInfoFragment : BottomSheetDialogFragment() {
|
||||||
@@ -25,6 +27,9 @@ class RecipeInfoFragment : BottomSheetDialogFragment() {
|
|||||||
private val ingredientsAdapter = RecipeIngredientsAdapter()
|
private val ingredientsAdapter = RecipeIngredientsAdapter()
|
||||||
private val instructionsAdapter = RecipeInstructionsAdapter()
|
private val instructionsAdapter = RecipeInstructionsAdapter()
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var recipeImageLoader: RecipeImageLoader
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
@@ -41,10 +46,10 @@ class RecipeInfoFragment : BottomSheetDialogFragment() {
|
|||||||
with(binding) {
|
with(binding) {
|
||||||
ingredientsList.adapter = ingredientsAdapter
|
ingredientsList.adapter = ingredientsAdapter
|
||||||
instructionsList.adapter = instructionsAdapter
|
instructionsList.adapter = instructionsAdapter
|
||||||
|
recipeImageLoader.loadRecipeImage(image, arguments.recipeSlug)
|
||||||
}
|
}
|
||||||
|
|
||||||
with(viewModel) {
|
with(viewModel) {
|
||||||
loadRecipeImage(binding.image, arguments.recipeSlug)
|
|
||||||
loadRecipeInfo(arguments.recipeId, arguments.recipeSlug)
|
loadRecipeInfo(arguments.recipeId, arguments.recipeSlug)
|
||||||
uiState.observe(viewLifecycleOwner, ::onUiStateChange)
|
uiState.observe(viewLifecycleOwner, ::onUiStateChange)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package gq.kirmanak.mealient.ui.recipes.info
|
package gq.kirmanak.mealient.ui.recipes.info
|
||||||
|
|
||||||
import android.widget.ImageView
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
@@ -8,7 +7,6 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
||||||
import gq.kirmanak.mealient.ui.recipes.RecipeImageLoader
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -16,17 +14,11 @@ import javax.inject.Inject
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class RecipeInfoViewModel @Inject constructor(
|
class RecipeInfoViewModel @Inject constructor(
|
||||||
private val recipeRepo: RecipeRepo,
|
private val recipeRepo: RecipeRepo,
|
||||||
private val recipeImageLoader: RecipeImageLoader,
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _uiState = MutableLiveData(RecipeInfoUiState())
|
private val _uiState = MutableLiveData(RecipeInfoUiState())
|
||||||
val uiState: LiveData<RecipeInfoUiState> get() = _uiState
|
val uiState: LiveData<RecipeInfoUiState> get() = _uiState
|
||||||
|
|
||||||
fun loadRecipeImage(view: ImageView, recipeSlug: String) {
|
|
||||||
Timber.v("loadRecipeImage() called with: view = $view, recipeSlug = $recipeSlug")
|
|
||||||
viewModelScope.launch { recipeImageLoader.loadRecipeImage(view, recipeSlug) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadRecipeInfo(recipeId: Long, recipeSlug: String) {
|
fun loadRecipeInfo(recipeId: Long, recipeSlug: String) {
|
||||||
Timber.v("loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug")
|
Timber.v("loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug")
|
||||||
_uiState.value = RecipeInfoUiState()
|
_uiState.value = RecipeInfoUiState()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package gq.kirmanak.mealient.data.recipes.impl
|
package gq.kirmanak.mealient.data.recipes.impl
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||||
import gq.kirmanak.mealient.ui.images.ImageLoader
|
import gq.kirmanak.mealient.ui.images.ImageLoader
|
||||||
@@ -21,10 +22,13 @@ class RecipeImageLoaderImplTest {
|
|||||||
@MockK
|
@MockK
|
||||||
lateinit var imageLoader: ImageLoader
|
lateinit var imageLoader: ImageLoader
|
||||||
|
|
||||||
|
@MockK
|
||||||
|
lateinit var fragment: Fragment
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockKAnnotations.init(this)
|
MockKAnnotations.init(this)
|
||||||
subject = RecipeImageLoaderImpl(imageLoader, baseURLStorage)
|
subject = RecipeImageLoaderImpl(imageLoader, baseURLStorage, fragment)
|
||||||
prepareBaseURL("https://google.com/")
|
prepareBaseURL("https://google.com/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user