## Types of changes - [ ] I have read the **CONTRIBUTING** document. - [ ] My code follows the code style of this project. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. - [ ] This is **NOT** translation related. This closes issue #XXX This is implements feature #YYY This finishes chore #ZZZ Reviewed-on: #175 Co-authored-by: Amine <amine.bouabdallaoui@pm.me> Co-committed-by: Amine <amine.bouabdallaoui@pm.me>
This commit is contained in:
parent
7bcf4574b4
commit
3b3a575dae
@ -118,13 +118,13 @@ class ItemCardAdapter(
|
||||
binding.itemImage.setImageDrawable(null)
|
||||
} else {
|
||||
binding.itemImage.visibility = View.VISIBLE
|
||||
c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage)
|
||||
c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage, appSettingsService)
|
||||
}
|
||||
|
||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||
binding.sourceImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
|
||||
} else {
|
||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage)
|
||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage, appSettingsService)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,10 +65,10 @@ class ItemListAdapter(
|
||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||
binding.itemImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
|
||||
} else {
|
||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
|
||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage, appSettingsService)
|
||||
}
|
||||
} else {
|
||||
c.circularDrawable(itm.getThumbnail(repository.baseUrl), binding.itemImage)
|
||||
c.circularDrawable(itm.getThumbnail(repository.baseUrl), binding.itemImage, appSettingsService)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBindi
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -36,6 +37,7 @@ class SourcesListAdapter(
|
||||
|
||||
override val di: DI by closestDI(app)
|
||||
private val repository: Repository by instance()
|
||||
private val appSettingsService: AppSettingsService by instance()
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
@ -82,7 +84,7 @@ class SourcesListAdapter(
|
||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||
binding.itemImage.setBackgroundAndText(itm.title.getHtmlDecoded())
|
||||
} else {
|
||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
|
||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage, appSettingsService)
|
||||
}
|
||||
|
||||
if (!itm.error.isNullOrBlank()) {
|
||||
|
@ -63,7 +63,7 @@ class LoadingWorker(
|
||||
handleNewItemsNotification(apiItems, notificationManager)
|
||||
}
|
||||
}
|
||||
apiItems.map { it.preloadImages(context) }
|
||||
apiItems.map { it.preloadImages(context, appSettingsService) }
|
||||
}
|
||||
}
|
||||
return Result.success()
|
||||
|
@ -32,7 +32,9 @@ import bou.amine.apps.readerforselfossv2.android.model.ParecelableItem
|
||||
import bou.amine.apps.readerforselfossv2.android.model.toModel
|
||||
import bou.amine.apps.readerforselfossv2.android.model.toParcelable
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapFitCenter
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.getGlideImageForResource
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.isUrlValid
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrlInBrowserAsNewTask
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowser
|
||||
@ -46,9 +48,6 @@ import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import bou.amine.apps.readerforselfossv2.utils.getImages
|
||||
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
|
||||
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -65,6 +64,8 @@ import java.util.Locale
|
||||
import java.util.concurrent.ExecutionException
|
||||
|
||||
private const val IMAGE_JPG = "image/jpg"
|
||||
private const val IMAGE_PNG = "image/png"
|
||||
private const val IMAGE_WEBP = "image/webp"
|
||||
|
||||
private const val WHITE_COLOR_HEX = 0xFFFFFF
|
||||
|
||||
@ -208,12 +209,7 @@ class ArticleFragment :
|
||||
|
||||
if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
|
||||
binding.imageView.visibility = View.VISIBLE
|
||||
Glide
|
||||
.with(requireContext())
|
||||
.asBitmap()
|
||||
.load(contentImage)
|
||||
.apply(RequestOptions.fitCenterTransform())
|
||||
.into(binding.imageView)
|
||||
requireContext().bitmapFitCenter(contentImage, binding.imageView, appSettingsService)
|
||||
} else {
|
||||
binding.imageView.visibility = View.GONE
|
||||
}
|
||||
@ -327,13 +323,7 @@ class ArticleFragment :
|
||||
private fun handleLeadImage(leadImageUrl: String?) {
|
||||
if (!leadImageUrl.isNullOrEmpty() && context != null) {
|
||||
binding.imageView.visibility = View.VISIBLE
|
||||
Glide
|
||||
.with(requireContext())
|
||||
.asBitmap()
|
||||
.load(
|
||||
leadImageUrl,
|
||||
).apply(RequestOptions.fitCenterTransform())
|
||||
.into(binding.imageView)
|
||||
requireContext().bitmapFitCenter(leadImageUrl, binding.imageView, appSettingsService)
|
||||
} else {
|
||||
binding.imageView.visibility = View.GONE
|
||||
}
|
||||
@ -357,78 +347,37 @@ class ArticleFragment :
|
||||
false
|
||||
}
|
||||
|
||||
@Suppress("detekt:LongMethod", "detekt:SwallowedException")
|
||||
@Suppress("detekt:SwallowedException", "detekt:ReturnCount")
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun shouldInterceptRequest(
|
||||
view: WebView,
|
||||
url: String,
|
||||
): WebResourceResponse? {
|
||||
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
||||
var glideResource: WebResourceResponse? = null
|
||||
if (url.lowercase(Locale.US).contains(".jpg") ||
|
||||
url
|
||||
.lowercase(Locale.US)
|
||||
.contains(".jpeg")
|
||||
) {
|
||||
try {
|
||||
val image =
|
||||
Glide
|
||||
.with(view)
|
||||
.asBitmap()
|
||||
.apply(glideOptions)
|
||||
.load(url)
|
||||
.submit()
|
||||
.get()
|
||||
glideResource =
|
||||
WebResourceResponse(
|
||||
IMAGE_JPG,
|
||||
"UTF-8",
|
||||
getBitmapInputStream(image, Bitmap.CompressFormat.JPEG),
|
||||
)
|
||||
} catch (e: ExecutionException) {
|
||||
// Do nothing
|
||||
val (mime: String?, compression: Bitmap.CompressFormat) =
|
||||
if (url
|
||||
.lowercase(Locale.US)
|
||||
.contains(".jpg") ||
|
||||
url.lowercase(Locale.US).contains(".jpeg")
|
||||
) {
|
||||
Pair(IMAGE_JPG, Bitmap.CompressFormat.JPEG)
|
||||
} else if (url.lowercase(Locale.US).contains(".png")) {
|
||||
Pair(IMAGE_PNG, Bitmap.CompressFormat.PNG)
|
||||
} else if (url.lowercase(Locale.US).contains(".webp")) {
|
||||
Pair(IMAGE_WEBP, Bitmap.CompressFormat.WEBP)
|
||||
} else {
|
||||
return super.shouldInterceptRequest(view, url)
|
||||
}
|
||||
} else if (url.lowercase(Locale.US).contains(".png")) {
|
||||
try {
|
||||
val image =
|
||||
Glide
|
||||
.with(view)
|
||||
.asBitmap()
|
||||
.apply(glideOptions)
|
||||
.load(url)
|
||||
.submit()
|
||||
.get()
|
||||
glideResource =
|
||||
WebResourceResponse(
|
||||
IMAGE_JPG,
|
||||
"UTF-8",
|
||||
getBitmapInputStream(image, Bitmap.CompressFormat.PNG),
|
||||
)
|
||||
} catch (e: ExecutionException) {
|
||||
// Do nothing
|
||||
}
|
||||
} else if (url.lowercase(Locale.US).contains(".webp")) {
|
||||
try {
|
||||
val image =
|
||||
Glide
|
||||
.with(view)
|
||||
.asBitmap()
|
||||
.apply(glideOptions)
|
||||
.load(url)
|
||||
.submit()
|
||||
.get()
|
||||
glideResource =
|
||||
WebResourceResponse(
|
||||
IMAGE_JPG,
|
||||
"UTF-8",
|
||||
getBitmapInputStream(image, Bitmap.CompressFormat.WEBP),
|
||||
)
|
||||
} catch (e: ExecutionException) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
return glideResource ?: super.shouldInterceptRequest(view, url)
|
||||
try {
|
||||
val image = view.getGlideImageForResource(url, appSettingsService)
|
||||
return WebResourceResponse(
|
||||
mime,
|
||||
"UTF-8",
|
||||
getBitmapInputStream(image, compression),
|
||||
)
|
||||
} catch (e: ExecutionException) {
|
||||
return super.shouldInterceptRequest(view, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,11 +16,12 @@ import bou.amine.apps.readerforselfossv2.android.HomeActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.imageIntoViewTarget
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.getColorHexCode
|
||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.ViewTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
@ -41,6 +42,7 @@ class FilterSheetFragment :
|
||||
private lateinit var binding: FilterFragmentBinding
|
||||
override val di: DI by closestDI()
|
||||
private val repository: Repository by instance()
|
||||
private val appSettingsService: AppSettingsService by instance()
|
||||
|
||||
private var selectedChip: Chip? = null
|
||||
|
||||
@ -84,23 +86,22 @@ class FilterSheetFragment :
|
||||
val c = Chip(context)
|
||||
c.ellipsize = TextUtils.TruncateAt.END
|
||||
|
||||
Glide
|
||||
.with(context)
|
||||
.load(source.getIcon(repository.baseUrl))
|
||||
.into(
|
||||
object : ViewTarget<Chip?, Drawable?>(c) {
|
||||
override fun onResourceReady(
|
||||
resource: Drawable,
|
||||
transition: Transition<in Drawable?>?,
|
||||
) {
|
||||
try {
|
||||
c.chipIcon = resource
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("sources > onResourceReady")
|
||||
}
|
||||
context.imageIntoViewTarget(
|
||||
source.getIcon(repository.baseUrl),
|
||||
object : ViewTarget<Chip?, Drawable?>(c) {
|
||||
override fun onResourceReady(
|
||||
resource: Drawable,
|
||||
transition: Transition<in Drawable?>?,
|
||||
) {
|
||||
try {
|
||||
c.chipIcon = resource
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("sources > onResourceReady")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
appSettingsService,
|
||||
)
|
||||
|
||||
c.text = source.title.getHtmlDecoded()
|
||||
|
||||
|
@ -6,13 +6,19 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.FragmentImageBinding
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapWithCache
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.x.closestDI
|
||||
import org.kodein.di.instance
|
||||
|
||||
class ImageFragment : Fragment() {
|
||||
class ImageFragment :
|
||||
Fragment(),
|
||||
DIAware {
|
||||
override val di: DI by closestDI()
|
||||
private val appSettingsService: AppSettingsService by instance()
|
||||
private lateinit var imageUrl: String
|
||||
private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
||||
private var _binding: FragmentImageBinding? = null
|
||||
val binding get() = _binding
|
||||
|
||||
@ -31,12 +37,7 @@ class ImageFragment : Fragment() {
|
||||
val view = binding?.root
|
||||
|
||||
binding!!.photoView.visibility = View.VISIBLE
|
||||
Glide
|
||||
.with(requireActivity())
|
||||
.asBitmap()
|
||||
.apply(glideOptions)
|
||||
.load(imageUrl)
|
||||
.into(binding!!.photoView)
|
||||
requireActivity().bitmapWithCache(imageUrl, binding!!.photoView, appSettingsService)
|
||||
|
||||
return view
|
||||
}
|
||||
|
@ -3,28 +3,21 @@ package bou.amine.apps.readerforselfossv2.android.model
|
||||
import android.content.Context
|
||||
import android.webkit.URLUtil
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.preloadImage
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.getImages
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
|
||||
private const val PRELOAD_IMAGE_TIMEOUT = 10000
|
||||
|
||||
fun SelfossModel.Item.preloadImages(context: Context): Boolean {
|
||||
fun SelfossModel.Item.preloadImages(
|
||||
context: Context,
|
||||
appSettingsService: AppSettingsService,
|
||||
): Boolean {
|
||||
val imageUrls = this.getImages()
|
||||
|
||||
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(PRELOAD_IMAGE_TIMEOUT)
|
||||
|
||||
try {
|
||||
for (url in imageUrls) {
|
||||
if (URLUtil.isValidUrl(url)) {
|
||||
Glide
|
||||
.with(context)
|
||||
.asBitmap()
|
||||
.apply(glideOptions)
|
||||
.load(url)
|
||||
.submit()
|
||||
context.preloadImage(url, appSettingsService)
|
||||
}
|
||||
}
|
||||
} catch (e: Error) {
|
||||
|
@ -2,33 +2,119 @@ package bou.amine.apps.readerforselfossv2.android.utils.glide
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.webkit.WebView
|
||||
import android.widget.ImageView
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.bumptech.glide.load.model.LazyHeaders
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.bumptech.glide.request.target.ViewTarget
|
||||
import com.google.android.material.chip.Chip
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import kotlin.io.encoding.Base64
|
||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||
|
||||
private const val PRELOAD_IMAGE_TIMEOUT = 10000
|
||||
|
||||
@OptIn(ExperimentalEncodingApi::class)
|
||||
fun String.toGlideUrl(appSettingsService: AppSettingsService): GlideUrl {
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty()) {
|
||||
val authString = "${appSettingsService.getBasicUserName()}:${appSettingsService.getBasicPassword()}"
|
||||
val authBuf = Base64.encode(authString.toByteArray(Charsets.UTF_8))
|
||||
|
||||
return GlideUrl(
|
||||
this,
|
||||
LazyHeaders
|
||||
.Builder()
|
||||
.addHeader("Authorization", "Basic $authBuf")
|
||||
.build(),
|
||||
)
|
||||
} else {
|
||||
return GlideUrl(
|
||||
this,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun WebView.getGlideImageForResource(
|
||||
url: String,
|
||||
appSettingsService: AppSettingsService,
|
||||
) = Glide
|
||||
.with(this)
|
||||
.asBitmap()
|
||||
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL))
|
||||
.load(url.toGlideUrl(appSettingsService))
|
||||
.submit()
|
||||
.get()
|
||||
|
||||
fun Context.preloadImage(
|
||||
url: String,
|
||||
appSettingsService: AppSettingsService,
|
||||
) = Glide
|
||||
.with(this)
|
||||
.asBitmap()
|
||||
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(PRELOAD_IMAGE_TIMEOUT))
|
||||
.load(url.toGlideUrl(appSettingsService))
|
||||
.submit()
|
||||
|
||||
fun Context.imageIntoViewTarget(
|
||||
url: String,
|
||||
target: ViewTarget<Chip?, Drawable?>,
|
||||
appSettingsService: AppSettingsService,
|
||||
) = Glide
|
||||
.with(this)
|
||||
.load(url.toGlideUrl(appSettingsService))
|
||||
.into(target)
|
||||
|
||||
fun Context.bitmapWithCache(
|
||||
url: String,
|
||||
iv: ImageView,
|
||||
appSettingsService: AppSettingsService,
|
||||
) = Glide
|
||||
.with(this)
|
||||
.asBitmap()
|
||||
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL))
|
||||
.load(url.toGlideUrl(appSettingsService))
|
||||
.into(iv)
|
||||
|
||||
fun Context.bitmapCenterCrop(
|
||||
url: String,
|
||||
iv: ImageView,
|
||||
appSettingsService: AppSettingsService,
|
||||
) = Glide
|
||||
.with(this)
|
||||
.asBitmap()
|
||||
.load(url)
|
||||
.load(url.toGlideUrl(appSettingsService))
|
||||
.apply(RequestOptions.centerCropTransform())
|
||||
.into(iv)
|
||||
|
||||
fun Context.bitmapFitCenter(
|
||||
url: String,
|
||||
iv: ImageView,
|
||||
appSettingsService: AppSettingsService,
|
||||
) = Glide
|
||||
.with(this)
|
||||
.asBitmap()
|
||||
.load(url.toGlideUrl(appSettingsService))
|
||||
.apply(RequestOptions.fitCenterTransform())
|
||||
.into(iv)
|
||||
|
||||
fun Context.circularDrawable(
|
||||
url: String,
|
||||
view: CircleImageView,
|
||||
appSettingsService: AppSettingsService,
|
||||
) {
|
||||
view.textView.text = ""
|
||||
|
||||
Glide
|
||||
.with(this)
|
||||
.load(url)
|
||||
.load(url.toGlideUrl(appSettingsService))
|
||||
.into(view.imageView)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user