Add WidgetIconProvider

pull/3891/head
Alex Baker 3 months ago
parent 27b21118eb
commit d9ddd45f13

@ -387,6 +387,13 @@
android:resource="@xml/file_provider_paths"/> android:resource="@xml/file_provider_paths"/>
</provider> </provider>
<provider
android:name=".widget.WidgetIconProvider"
android:authorities="${applicationId}.widgeticons"
android:exported="true"
android:grantUriPermissions="true"
tools:ignore="ExportedContentProvider" />
<receiver <receiver
android:name="org.dmfs.provider.tasks.TaskProviderBroadcastReceiver" android:name="org.dmfs.provider.tasks.TaskProviderBroadcastReceiver"
tools:node="remove"/> tools:node="remove"/>

@ -3,7 +3,6 @@ package org.tasks.widget
import android.content.Context import android.content.Context
import android.widget.RemoteViews import android.widget.RemoteViews
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import com.mikepenz.iconics.IconicsDrawable
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.tasks.BuildConfig import org.tasks.BuildConfig
@ -19,7 +18,6 @@ import org.tasks.filters.Filter
import org.tasks.filters.PlaceFilter import org.tasks.filters.PlaceFilter
import org.tasks.filters.TagFilter import org.tasks.filters.TagFilter
import org.tasks.filters.getIcon import org.tasks.filters.getIcon
import org.tasks.icons.OutlinedGoogleMaterial
import org.tasks.kmp.org.tasks.time.getRelativeDateTime import org.tasks.kmp.org.tasks.time.getRelativeDateTime
import org.tasks.kmp.org.tasks.time.getTimeString import org.tasks.kmp.org.tasks.time.getTimeString
import org.tasks.time.startOfDay import org.tasks.time.startOfDay
@ -115,14 +113,16 @@ class WidgetChipProvider @Inject constructor(
setTextViewText(R.id.chip_text, filter.title) setTextViewText(R.id.chip_text, filter.title)
filter filter
.getIcon(inventory) .getIcon(inventory)
?.let { ?.let { iconName ->
try { try {
OutlinedGoogleMaterial.getIcon("gmo_$it") val iconUri = WidgetIconProvider.getIconUri(
} catch (_: IllegalArgumentException) { iconName = iconName,
null )
setImageViewUri(R.id.chip_icon, iconUri)
} catch (_: Exception) {
setImageViewResource(R.id.chip_icon, defaultIcon)
} }
} }
?.let { setImageViewBitmap(R.id.chip_icon, IconicsDrawable(context, it).toBitmap()) }
?: setImageViewResource(R.id.chip_icon, defaultIcon) ?: setImageViewResource(R.id.chip_icon, defaultIcon)
} }
@ -138,4 +138,4 @@ class WidgetChipProvider @Inject constructor(
setColorFilter(R.id.chip_background, tint) setColorFilter(R.id.chip_background, tint)
setTextColor(R.id.chip_text, tint) setTextColor(R.id.chip_text, tint)
} }
} }

@ -0,0 +1,118 @@
package org.tasks.widget
import android.content.ContentProvider
import android.content.ContentValues
import android.database.Cursor
import android.graphics.Bitmap
import android.graphics.Canvas
import android.net.Uri
import android.os.ParcelFileDescriptor
import androidx.core.graphics.createBitmap
import androidx.core.net.toUri
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.utils.sizeDp
import org.tasks.BuildConfig
import org.tasks.icons.OutlinedGoogleMaterial
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
class WidgetIconProvider : ContentProvider() {
override fun onCreate() = true
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
if (mode != "r") {
throw SecurityException("Only read access allowed")
}
return try {
val segments = uri.pathSegments
if (segments.size != 2) return null
val iconName = segments[1]
if (!iconName.matches(Regex("^[a-zA-Z0-9_]+$"))) return null
val cacheFile = getCacheFile(iconName)
if (!cacheFile.exists()) {
generateIcon(cacheFile, iconName)
}
if (cacheFile.exists()) {
ParcelFileDescriptor.open(cacheFile, ParcelFileDescriptor.MODE_READ_ONLY)
} else {
null
}
} catch (e: Exception) {
Timber.e(e, "Failed to open icon file for URI: $uri")
null
}
}
private fun generateIcon(file: File, iconName: String) {
try {
val icon = OutlinedGoogleMaterial.getIcon("gmo_$iconName")
val context = context ?: return
val drawable = IconicsDrawable(context, icon).apply {
this.sizeDp = 24
}
val bitmap = createBitmap(
drawable.intrinsicWidth.coerceAtLeast(1),
drawable.intrinsicHeight.coerceAtLeast(1)
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
file.parentFile?.mkdirs()
FileOutputStream(file).use { out ->
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
}
bitmap.recycle()
} catch (e: Exception) {
Timber.e(e, "Failed to generate icon: $iconName")
file.delete()
}
}
private fun getCacheFile(iconName: String): File {
val context = context ?: throw IllegalStateException("Context is null")
val cacheDir = File(context.cacheDir, "widget_icons")
cacheDir.mkdirs()
return File(cacheDir, "${iconName}.png")
}
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?,
): Cursor? = null
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?,
): Int = 0
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0
override fun getType(uri: Uri): String = "image/png"
companion object {
const val AUTHORITY = "${BuildConfig.APPLICATION_ID}.widgeticons"
fun getIconUri(iconName: String): Uri {
return "content://$AUTHORITY/icon/$iconName".toUri()
}
}
}
Loading…
Cancel
Save