Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.utils.BitmapUtils
import dagger.android.AndroidInjection
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject

class DashboardWidgetService : RemoteViewsService() {
Expand Down Expand Up @@ -78,33 +74,29 @@ class StackRemoteViewsFactory(
// TODO show error
Log_OC.e(this, "No user found!")
}

onDataSetChanged()
}

override fun onDataSetChanged() {
CoroutineScope(Dispatchers.IO).launch {
try {
if (!widgetConfiguration.user.isPresent) {
Log_OC.w(TAG, "User not present for widget update")
return@launch
}

val client = clientFactory.createNextcloudClient(widgetConfiguration.user.get())
val result = DashboardGetWidgetItemsRemoteOperation(widgetConfiguration.widgetId, LIMIT_SIZE)
.execute(client)
widgetItems = if (result.isSuccess) {
result.resultData[widgetConfiguration.widgetId] ?: emptyList()
} else {
emptyList()
}
hasLoadMore = widgetConfiguration.moreButton != null && widgetItems.size == LIMIT_SIZE
} catch (e: ClientFactory.CreationException) {
Log_OC.e(TAG, "Error updating widget", e)
}
Log_OC.d(TAG, "onDataSetChanged")

if (!widgetConfiguration.user.isPresent) {
Log_OC.w(TAG, "User not present for widget update")
return
}

Log_OC.d(TAG, "onDataSetChanged")
try {
val client = clientFactory.createNextcloudClient(widgetConfiguration.user.get())
val result = DashboardGetWidgetItemsRemoteOperation(widgetConfiguration.widgetId, LIMIT_SIZE)
.execute(client)
widgetItems = if (result.isSuccess) {
result.resultData[widgetConfiguration.widgetId] ?: emptyList()
} else {
emptyList()
}
hasLoadMore = widgetConfiguration.moreButton != null && widgetItems.size == LIMIT_SIZE
} catch (e: ClientFactory.CreationException) {
Log_OC.e(TAG, "Error updating widget", e)
}
}

override fun onDestroy() {
Expand Down Expand Up @@ -157,17 +149,11 @@ class StackRemoteViewsFactory(
}

private fun loadIcon(widgetItem: DashboardWidgetItem, remoteViews: RemoteViews) {
CoroutineScope(Dispatchers.IO).launch {
val client = OwnCloudClientManagerFactory.getDefaultSingleton()
.getNextcloudClientFor(userAccountManager.user.toOwnCloudAccount(), context)
val pictureDrawable = GlideHelper.getDrawable(context, client, widgetItem.iconUrl)
val bitmap = pictureDrawable?.toBitmap() ?: return@launch

withContext(Dispatchers.Main) {
remoteViews.setRemoteImageView(bitmap)
return@withContext
}
}
val client = OwnCloudClientManagerFactory.getDefaultSingleton()
.getNextcloudClientFor(userAccountManager.user.toOwnCloudAccount(), context)
val iconSize = context.resources.getDimensionPixelSize(R.dimen.dashboard_widget_icon_size)
val bitmap = GlideHelper.getDrawable(context, client, widgetItem.iconUrl, iconSize)?.toBitmap() ?: return
remoteViews.setRemoteImageView(bitmap)
}

@Suppress("TooGenericExceptionCaught")
Expand All @@ -176,7 +162,8 @@ class StackRemoteViewsFactory(
val bitmap: Bitmap = if (widgetConfiguration.roundIcon) {
BitmapUtils.roundBitmap(source)
} else {
source
val radius = context.resources.getDimension(R.dimen.file_icon_rounded_corner_radius)
BitmapUtils.roundCorners(source, radius)
}

setImageViewBitmap(R.id.icon, bitmap)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.os.Build
import android.view.View
import android.widget.RemoteViews
import androidx.core.graphics.drawable.toBitmap
import androidx.core.net.toUri
import com.bumptech.glide.request.target.AppWidgetTarget
import com.nextcloud.android.lib.resources.dashboard.DashboardButton
import com.nextcloud.client.account.CurrentAccountProvider
import com.nextcloud.utils.GlideHelper
Expand All @@ -26,7 +26,6 @@ import com.owncloud.android.utils.BitmapUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject

class DashboardWidgetUpdater @Inject constructor(
Expand All @@ -47,23 +46,24 @@ class DashboardWidgetUpdater @Inject constructor(
data = toUri(Intent.URI_INTENT_SCHEME).toUri()
}

val views = RemoteViews(context.packageName, R.layout.dashboard_widget).apply {
setRemoteAdapter(R.id.list, intent)
setEmptyView(R.id.list, R.id.empty_view)
setTextViewText(R.id.title, title)
scope.launch {
val icon = if (iconUrl.isNotEmpty()) loadIcon(iconUrl) else null

setAddButton(addButton, appWidgetId, this)
setPendingReload(this, appWidgetId)
setPendingClick(this)
val views = RemoteViews(context.packageName, R.layout.dashboard_widget).apply {
icon?.let { setImageViewBitmap(R.id.icon, it) }
setRemoteAdapter(R.id.list, intent)
setEmptyView(R.id.list, R.id.empty_view)
setTextViewText(R.id.title, title)

if (iconUrl.isNotEmpty()) {
loadIcon(appWidgetId, iconUrl, this)
setAddButton(addButton, appWidgetId, this)
setPendingReload(this, appWidgetId)
setPendingClick(this)
}
}

appWidgetManager.run {
updateAppWidget(appWidgetId, views)
notifyAppWidgetViewDataChanged(appWidgetId, R.id.list)
appWidgetManager.run {
updateAppWidget(appWidgetId, views)
notifyAppWidgetViewDataChanged(appWidgetId, R.id.list)
}
}
}

Expand Down Expand Up @@ -152,18 +152,11 @@ class DashboardWidgetUpdater @Inject constructor(
}
// endregion

private fun loadIcon(appWidgetId: Int, iconUrl: String, remoteViews: RemoteViews) {
val target = AppWidgetTarget(context, R.id.icon, remoteViews, appWidgetId)
scope.launch {
val client = OwnCloudClientManagerFactory.getDefaultSingleton()
.getNextcloudClientFor(accountProvider.user.toOwnCloudAccount(), context)
val drawable = GlideHelper.getDrawable(context, client, iconUrl)
val bitmap = drawable?.toBitmap() ?: return@launch
val tintedBitmap = BitmapUtils.tintImage(bitmap, R.color.black)

withContext(Dispatchers.Main) {
target.onResourceReady(tintedBitmap, null)
}
}
private fun loadIcon(iconUrl: String): Bitmap? {
val client = OwnCloudClientManagerFactory.getDefaultSingleton()
.getNextcloudClientFor(accountProvider.user.toOwnCloudAccount(), context)
val iconSize = context.resources.getDimensionPixelSize(R.dimen.dashboard_widget_icon_size)
val bitmap = GlideHelper.getDrawable(context, client, iconUrl, iconSize)?.toBitmap() ?: return null
return BitmapUtils.tintImage(bitmap, context.getColor(R.color.black))
}
}
55 changes: 20 additions & 35 deletions app/src/main/java/com/nextcloud/utils/GlideHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@ 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
import com.bumptech.glide.RequestBuilder
Expand All @@ -33,14 +31,14 @@ 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.DrawableImageViewTarget
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
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.utils.svg.SvgSoftwareLayerSetter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -112,7 +110,7 @@ object GlideHelper {
?.error(placeholder)
?.apply { if (circleCrop) circleCrop() }
?.withLogging("loadIntoImageView", url ?: "null")
?.into(imageView) ?: imageView.setImageResource(placeholder)
?.into(UntintedDrawableImageViewTarget(imageView)) ?: imageView.setImageResource(placeholder)
} catch (e: Exception) {
Log_OC.e(TAG, "exception loadIntoImageView: $e")
imageView.setImageResource(placeholder)
Expand All @@ -121,8 +119,7 @@ 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.
* takes effect.
*
* @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.
Expand Down Expand Up @@ -180,8 +177,16 @@ object GlideHelper {
return bitmap.toDrawable(context.resources)
}

fun getDrawable(context: Context, client: NextcloudClient?, urlString: String?): Drawable? = try {
createRequestBuilder<Drawable>(context, client, urlString)?.submit()?.get()
fun getDrawable(
context: Context,
client: NextcloudClient?,
urlString: String?,
sizePx: Int = Target.SIZE_ORIGINAL
): Drawable? = try {
createRequestBuilder<Drawable>(context, client, urlString)
?.withLogging("getDrawable", urlString ?: "null")
?.submit(sizePx, sizePx)
?.get()
} catch (e: Exception) {
Log_OC.e(TAG, "exception getDrawable: $e")
null
Expand Down Expand Up @@ -251,30 +256,16 @@ object GlideHelper {
}
}

private fun isSVG(url: String): Boolean = (url.toUri().encodedPath?.endsWith(".svg") == true)

private fun <T> RequestBuilder<T>.withLogging(methodName: String, identifier: String): RequestBuilder<T> =
listener(GlideLogger(methodName, identifier))

@SuppressLint("CheckResult")
private fun createSvgRequestBuilder(
context: Context,
uri: String,
client: NextcloudClient,
placeholder: Int? = null
): RequestBuilder<PictureDrawable> {
val glideUrl = createGlideUrl(uri, client)

return Glide.with(context)
.`as`(PictureDrawable::class.java)
.load(glideUrl)
.apply {
placeholder?.let {
placeholder(it)
error(it)
}
private class UntintedDrawableImageViewTarget(imageView: ImageView) : DrawableImageViewTarget(imageView) {
override fun setResource(resource: Drawable?) {
if (resource != null) {
view.imageTintList = null
}
.listener(SvgSoftwareLayerSetter())
super.setResource(resource)
}
}

private fun createUrlRequestBuilder(
Expand All @@ -299,13 +290,7 @@ object GlideHelper {
val validatedUrl = validateAndGetURL(url) ?: return null

return try {
val isSVG = isSVG(validatedUrl)

if (isSVG) {
createSvgRequestBuilder(context, validatedUrl, client)
} else {
createUrlRequestBuilder(context, client, validatedUrl)
}.withLogging("createRequestBuilder", validatedUrl) as RequestBuilder<T>?
createUrlRequestBuilder(context, client, validatedUrl) as RequestBuilder<T>?
} catch (e: Exception) {
Log_OC.e(TAG, "exception createRequestBuilder: $e")
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,16 @@

package com.nextcloud.utils.extensions

import androidx.core.net.toUri
import com.nextcloud.model.SearchResultEntryType
import com.owncloud.android.lib.common.SearchResultEntry

private const val SVG_EXTENSION = ".svg"
private const val COLORED_ICON_SUFFIX = "-color.svg"
private const val PREVIEW_PATH = "/core/preview"
private const val PREVIEW_WIDTH_PARAMETER = "x"
private const val PREVIEW_HEIGHT_PARAMETER = "y"

fun SearchResultEntry.getType(): SearchResultEntryType {
val value = icon.lowercase()

Expand Down Expand Up @@ -42,3 +49,23 @@ fun SearchResultEntry.getType(): SearchResultEntryType {
else -> SearchResultEntryType.Unknown
}
}

fun SearchResultEntry.isMonochromeIcon(): Boolean {
val path = thumbnailUrl.toUri().path ?: return false
return path.endsWith(SVG_EXTENSION, ignoreCase = true) && !path.endsWith(COLORED_ICON_SUFFIX, ignoreCase = true)
}

fun SearchResultEntry.thumbnailUrlForSize(sizePx: Int): String {
val uri = thumbnailUrl.toUri()
if (uri.path?.endsWith(PREVIEW_PATH) != true) {
return thumbnailUrl
}

val builder = uri.buildUpon().clearQuery()
uri.queryParameterNames.forEach { name ->
val isSizeParameter = name == PREVIEW_WIDTH_PARAMETER || name == PREVIEW_HEIGHT_PARAMETER
val value = if (isSizeParameter) sizePx.toString() else uri.getQueryParameter(name)
builder.appendQueryParameter(name, value)
}
return builder.build().toString()
}
Loading
Loading