Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions app/src/main/java/com/nextcloud/utils/GlideHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@ package com.nextcloud.utils
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.PictureDrawable
import android.widget.ImageView
import androidx.activity.ComponentActivity
import androidx.annotation.DrawableRes
import androidx.core.graphics.createBitmap
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
import androidx.core.graphics.drawable.toDrawable
import androidx.core.graphics.scale
import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
Expand All @@ -27,7 +32,9 @@ import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.model.LazyHeaders
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.BitmapImageViewTarget
import com.bumptech.glide.request.target.CustomViewTarget
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
import com.nextcloud.common.NextcloudClient
import com.nextcloud.utils.LinkHelper.validateAndGetURL
import com.owncloud.android.lib.common.OwnCloudAccount
Expand Down Expand Up @@ -112,6 +119,67 @@ object GlideHelper {
}
}

/**
* Loads an image into an [ImageView], rasterizing the result into a tintable drawable so a tint set on the view
* takes effect. SVGs decode into a [PictureDrawable] that ignores color filters and tint lists; rasterizing
* works around that.
*
* @param context context used to build the Glide request and the rasterized drawable.
* @param client authenticated client whose credentials are attached to the request; the load is skipped when null.
* @param url image URL to load; an invalid or null URL leaves the [placeholder] in place.
* @param imageView target view that receives the loaded drawable.
* @param placeholder drawable resource shown while loading and on failure.
* @param sizePx width and height, in pixels, of the rasterized square drawable.
*/
fun loadTintableIconIntoImageView(
context: Context,
client: NextcloudClient?,
url: String?,
imageView: ImageView,
@DrawableRes placeholder: Int,
sizePx: Int
) {
imageView.setImageResource(placeholder)
try {
createRequestBuilder<Drawable>(context, client, url)
?.error(placeholder)
?.withLogging("loadTintableIconIntoImageView", url ?: "null")
?.into(object : CustomViewTarget<ImageView, Drawable>(imageView) {
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
imageView.setImageDrawable(rasterizeToTintableDrawable(context, resource, sizePx))
}

override fun onLoadFailed(errorDrawable: Drawable?) {
errorDrawable?.let { imageView.setImageDrawable(it) }
}

override fun onResourceCleared(placeholderDrawable: Drawable?) = Unit
})
} catch (e: Exception) {
Log_OC.e(TAG, "exception loadTintableIconIntoImageView: $e")
imageView.setImageResource(placeholder)
}
}

private fun rasterizeToTintableDrawable(context: Context, drawable: Drawable, sizePx: Int): Drawable {
if (drawable is BitmapDrawable) {
return drawable.bitmap.scale(sizePx, sizePx).toDrawable(context.resources)
}

val width = drawable.intrinsicWidth
val height = drawable.intrinsicHeight
val bitmap = createBitmap(sizePx, sizePx)
val canvas = Canvas(bitmap)
if (width > 0 && height > 0) {
canvas.scale(sizePx / width.toFloat(), sizePx / height.toFloat())
drawable.setBounds(0, 0, width, height)
} else {
drawable.setBounds(0, 0, sizePx, sizePx)
}
drawable.draw(canvas)
return bitmap.toDrawable(context.resources)
}

fun getDrawable(context: Context, client: NextcloudClient?, urlString: String?): Drawable? = try {
createRequestBuilder<Drawable>(context, client, urlString)?.submit()?.get()
} catch (e: Exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
package com.owncloud.android.ui.activities.adapter

import android.content.Context
import android.content.res.Configuration
import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.Typeface
import android.text.Spannable
import android.text.SpannableStringBuilder
Expand All @@ -30,6 +27,7 @@ import androidx.annotation.DrawableRes
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.client.account.CurrentAccountProvider
import com.nextcloud.common.NextcloudClient
import com.nextcloud.utils.GlideHelper
Expand Down Expand Up @@ -134,20 +132,20 @@ open class ActivityListAdapter(
}

if (activity.icon.isNotEmpty()) {
loadImageAsync(activity.icon, holder.binding.icon, R.drawable.ic_activity)
GlideHelper.loadTintableIconIntoImageView(
context,
client,
activity.icon,
holder.binding.icon,
R.drawable.ic_activity,
context.resources.getDimensionPixelSize(R.dimen.activity_icon_width)
)
}

val isNightMode =
(context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
Configuration.UI_MODE_NIGHT_YES
val isFileCreatedOrDeleted = activity.type.equals("file_created", ignoreCase = true) ||
activity.type.equals("file_deleted", ignoreCase = true)

if (!isFileCreatedOrDeleted) {
holder.binding.icon.setColorFilter(
if (isNightMode) Color.WHITE else Color.BLACK,
PorterDuff.Mode.SRC_IN
)
if (activity.icon.endsWith(COLORED_ICON_SUFFIX, ignoreCase = true)) {
holder.binding.icon.imageTintList = null
} else {
viewThemeUtils.platform.colorImageView(holder.binding.icon, ColorRole.ON_SURFACE_VARIANT)
}

val richObjectList = activity.richSubjectElement.richObjectList
Expand Down Expand Up @@ -178,14 +176,15 @@ open class ActivityListAdapter(
}
}

private suspend fun nextcloudClient(): NextcloudClient = withContext(Dispatchers.IO) {
OwnCloudClientManagerFactory.getDefaultSingleton()
.getNextcloudClientFor(currentAccountProvider.user.toOwnCloudAccount(), context)
}

private fun loadImageAsync(url: String, imageView: ImageView, @DrawableRes placeholder: Int) {
context.lifecycleScope.launch {
runCatching {
val client = withContext(Dispatchers.IO) {
OwnCloudClientManagerFactory.getDefaultSingleton()
.getNextcloudClientFor(currentAccountProvider.user.toOwnCloudAccount(), context)
}
GlideHelper.loadIntoImageView(context, client, url, imageView, placeholder, false)
GlideHelper.loadIntoImageView(context, nextcloudClient(), url, imageView, placeholder, false)
}.onFailure {
Log_OC.e(TAG, "Exception loading image: $it")
}
Expand Down Expand Up @@ -318,6 +317,7 @@ open class ActivityListAdapter(
companion object {
const val HEADER_TYPE = 100
const val ACTIVITY_TYPE = 101
private const val COLORED_ICON_SUFFIX = "-color.svg"
private val TAG: String = ActivityListAdapter::class.java.simpleName
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -359,13 +359,13 @@ class NotificationsFragment :
}

override fun onBindIcon(imageView: ImageView, url: String) {
GlideHelper.loadIntoImageView(
GlideHelper.loadTintableIconIntoImageView(
requireContext(),
client,
url,
imageView,
R.drawable.ic_notification,
false
resources.getDimensionPixelSize(R.dimen.notification_icon_width)
)
}

Expand Down
Loading