feat: add per-widget configuration (#333)

* feat: add per-widget configuration

* fix: no need to overengineer it

* feat: add cache to bitmap download

dfgdfg

* fix: elvis has left the operation
This commit is contained in:
TwistedUmbrellaX 2024-04-07 22:21:24 -04:00 committed by GitHub
parent 47d05e737d
commit f96d2ffaa5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 75 additions and 104 deletions

View file

@ -1,33 +1,62 @@
package ani.dantotsu.util package ani.dantotsu.util
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.BitmapShader import android.graphics.BitmapShader
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Paint import android.graphics.Paint
import android.graphics.RectF import android.graphics.RectF
import android.graphics.Shader import android.graphics.Shader
import android.graphics.drawable.Drawable import androidx.collection.LruCache
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URL
class BitmapUtil { object BitmapUtil {
companion object { private fun roundCorners(bitmap: Bitmap, cornerRadius: Float = 20f): Bitmap {
fun roundCorners(bitmap: Bitmap, cornerRadius: Float = 20f): Bitmap { val output = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
val output = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888) val canvas = Canvas(output)
val canvas = Canvas(output) val paint = Paint()
val paint = Paint() paint.isAntiAlias = true
paint.isAntiAlias = true paint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
paint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) val rect = RectF(0f, 0f, bitmap.width.toFloat(), bitmap.height.toFloat())
val rect = RectF(0f, 0f, bitmap.width.toFloat(), bitmap.height.toFloat()) canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint)
canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint)
return output return output
} }
fun convertDrawableToBitmap(drawable: Drawable, width: Int, height: Int): Bitmap { private val cacheSize = (Runtime.getRuntime().maxMemory() / 1024 / 16).toInt()
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) private val bitmapCache = LruCache<String, Bitmap>(cacheSize)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height) fun downloadImageAsBitmap(imageUrl: String): Bitmap? {
drawable.draw(canvas) var bitmap: Bitmap? = null
return bitmap
runBlocking(Dispatchers.IO) {
val cacheName = imageUrl.substringAfterLast("/")
bitmap = bitmapCache[cacheName]
if (bitmap != null) return@runBlocking
var inputStream: InputStream? = null
var urlConnection: HttpURLConnection? = null
try {
val url = URL(imageUrl)
urlConnection = url.openConnection() as HttpURLConnection
urlConnection.requestMethod = "GET"
urlConnection.connect()
if (urlConnection.responseCode == HttpURLConnection.HTTP_OK) {
inputStream = urlConnection.inputStream
bitmap = BitmapFactory.decodeStream(inputStream)
bitmap?.let { bitmapCache.put(cacheName, it) }
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
inputStream?.close()
urlConnection?.disconnect()
}
} }
return bitmap?.let { roundCorners(it) }
} }
} }

View file

@ -60,7 +60,11 @@ class ProfileStatsConfigure : AppCompatActivity(),
binding = StatisticsWidgetConfigureBinding.inflate(layoutInflater) binding = StatisticsWidgetConfigureBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
val prefs = getSharedPreferences(ProfileStatsWidget.PREFS_NAME, Context.MODE_PRIVATE) appWidgetId = intent.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID
)
val prefs = getSharedPreferences(ProfileStatsWidget.getPrefsName(appWidgetId), Context.MODE_PRIVATE)
val topBackground = prefs.getInt(ProfileStatsWidget.PREF_BACKGROUND_COLOR, Color.parseColor("#80000000")) val topBackground = prefs.getInt(ProfileStatsWidget.PREF_BACKGROUND_COLOR, Color.parseColor("#80000000"))
(binding.topBackgroundButton as MaterialButton).iconTint = ColorStateList.valueOf(topBackground) (binding.topBackgroundButton as MaterialButton).iconTint = ColorStateList.valueOf(topBackground)
binding.topBackgroundButton.setOnClickListener { binding.topBackgroundButton.setOnClickListener {
@ -192,7 +196,7 @@ class ProfileStatsConfigure : AppCompatActivity(),
) )
val subTextColor = typedValueOutline.data val subTextColor = typedValueOutline.data
getSharedPreferences(ProfileStatsWidget.PREFS_NAME, Context.MODE_PRIVATE).edit().apply { getSharedPreferences(ProfileStatsWidget.getPrefsName(appWidgetId), Context.MODE_PRIVATE).edit().apply {
putInt(ProfileStatsWidget.PREF_BACKGROUND_COLOR, backgroundColor) putInt(ProfileStatsWidget.PREF_BACKGROUND_COLOR, backgroundColor)
putInt(ProfileStatsWidget.PREF_BACKGROUND_FADE, backgroundColor) putInt(ProfileStatsWidget.PREF_BACKGROUND_FADE, backgroundColor)
putInt(ProfileStatsWidget.PREF_TITLE_TEXT_COLOR, textColor) putInt(ProfileStatsWidget.PREF_TITLE_TEXT_COLOR, textColor)
@ -204,12 +208,13 @@ class ProfileStatsConfigure : AppCompatActivity(),
override fun onResult(dialogTag: String, which: Int, extras: Bundle): Boolean { override fun onResult(dialogTag: String, which: Int, extras: Bundle): Boolean {
if (which == SimpleDialog.OnDialogResultListener.BUTTON_POSITIVE) { if (which == SimpleDialog.OnDialogResultListener.BUTTON_POSITIVE) {
if (!isMonetEnabled) { if (!isMonetEnabled) {
val prefs = getSharedPreferences(
ProfileStatsWidget.getPrefsName(appWidgetId),
Context.MODE_PRIVATE
)
when (dialogTag) { when (dialogTag) {
ProfileStatsWidget.PREF_BACKGROUND_COLOR -> { ProfileStatsWidget.PREF_BACKGROUND_COLOR -> {
getSharedPreferences( prefs.edit()
ProfileStatsWidget.PREFS_NAME,
Context.MODE_PRIVATE
).edit()
.putInt( .putInt(
ProfileStatsWidget.PREF_BACKGROUND_COLOR, ProfileStatsWidget.PREF_BACKGROUND_COLOR,
extras.getInt(SimpleColorDialog.COLOR) extras.getInt(SimpleColorDialog.COLOR)
@ -220,10 +225,7 @@ class ProfileStatsConfigure : AppCompatActivity(),
} }
ProfileStatsWidget.PREF_BACKGROUND_FADE -> { ProfileStatsWidget.PREF_BACKGROUND_FADE -> {
getSharedPreferences( prefs.edit()
ProfileStatsWidget.PREFS_NAME,
Context.MODE_PRIVATE
).edit()
.putInt( .putInt(
ProfileStatsWidget.PREF_BACKGROUND_FADE, ProfileStatsWidget.PREF_BACKGROUND_FADE,
extras.getInt(SimpleColorDialog.COLOR) extras.getInt(SimpleColorDialog.COLOR)
@ -234,10 +236,7 @@ class ProfileStatsConfigure : AppCompatActivity(),
} }
ProfileStatsWidget.PREF_TITLE_TEXT_COLOR -> { ProfileStatsWidget.PREF_TITLE_TEXT_COLOR -> {
getSharedPreferences( prefs.edit()
ProfileStatsWidget.PREFS_NAME,
Context.MODE_PRIVATE
).edit()
.putInt( .putInt(
ProfileStatsWidget.PREF_TITLE_TEXT_COLOR, ProfileStatsWidget.PREF_TITLE_TEXT_COLOR,
extras.getInt(SimpleColorDialog.COLOR) extras.getInt(SimpleColorDialog.COLOR)
@ -248,10 +247,7 @@ class ProfileStatsConfigure : AppCompatActivity(),
} }
ProfileStatsWidget.PREF_STATS_TEXT_COLOR -> { ProfileStatsWidget.PREF_STATS_TEXT_COLOR -> {
getSharedPreferences( prefs.edit()
ProfileStatsWidget.PREFS_NAME,
Context.MODE_PRIVATE
).edit()
.putInt( .putInt(
ProfileStatsWidget.PREF_STATS_TEXT_COLOR, ProfileStatsWidget.PREF_STATS_TEXT_COLOR,
extras.getInt(SimpleColorDialog.COLOR) extras.getInt(SimpleColorDialog.COLOR)

View file

@ -5,28 +5,24 @@ import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider import android.appwidget.AppWidgetProvider
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
import android.net.Uri
import android.widget.RemoteViews import android.widget.RemoteViews
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.toBitmap
import ani.dantotsu.MainActivity import ani.dantotsu.MainActivity
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.util.BitmapUtil import ani.dantotsu.util.BitmapUtil.Companion.downloadImageAsBitmap
import ani.dantotsu.widgets.WidgetSizeProvider import ani.dantotsu.widgets.WidgetSizeProvider
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchIO
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URL
/** /**
* Implementation of App Widget functionality. * Implementation of App Widget functionality.
@ -56,32 +52,6 @@ class ProfileStatsWidget : AppWidgetProvider() {
} }
companion object { companion object {
private fun downloadImageAsBitmap(imageUrl: String): Bitmap? {
var bitmap: Bitmap? = null
runBlocking(Dispatchers.IO) {
var inputStream: InputStream? = null
var urlConnection: HttpURLConnection? = null
try {
val url = URL(imageUrl)
urlConnection = url.openConnection() as HttpURLConnection
urlConnection.requestMethod = "GET"
urlConnection.connect()
if (urlConnection.responseCode == HttpURLConnection.HTTP_OK) {
inputStream = urlConnection.inputStream
bitmap = BitmapFactory.decodeStream(inputStream)
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
inputStream?.close()
urlConnection?.disconnect()
}
}
return bitmap?.let { BitmapUtil.roundCorners(it) }
}
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
fun updateAppWidget( fun updateAppWidget(
context: Context, context: Context,
@ -89,7 +59,7 @@ class ProfileStatsWidget : AppWidgetProvider() {
appWidgetId: Int appWidgetId: Int
) { ) {
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) val prefs = context.getSharedPreferences(getPrefsName(appWidgetId), Context.MODE_PRIVATE)
val backgroundColor = val backgroundColor =
prefs.getInt(PREF_BACKGROUND_COLOR, Color.parseColor("#80000000")) prefs.getInt(PREF_BACKGROUND_COLOR, Color.parseColor("#80000000"))
val backgroundFade = prefs.getInt(PREF_BACKGROUND_FADE, Color.parseColor("#00000000")) val backgroundFade = prefs.getInt(PREF_BACKGROUND_FADE, Color.parseColor("#00000000"))
@ -120,8 +90,7 @@ class ProfileStatsWidget : AppWidgetProvider() {
val views = RemoteViews(context.packageName, R.layout.statistics_widget).apply { val views = RemoteViews(context.packageName, R.layout.statistics_widget).apply {
setImageViewBitmap( setImageViewBitmap(
R.id.backgroundView, R.id.backgroundView,
BitmapUtil.convertDrawableToBitmap( gradientDrawable.toBitmap(
gradientDrawable,
width, width,
height height
) )
@ -133,6 +102,7 @@ class ProfileStatsWidget : AppWidgetProvider() {
1, 1,
Intent(context, ProfileStatsConfigure::class.java).apply { Intent(context, ProfileStatsConfigure::class.java).apply {
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
}, },
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
@ -248,7 +218,9 @@ class ProfileStatsWidget : AppWidgetProvider() {
} }
} }
const val PREFS_NAME = "ani.dantotsu.widgets.ResumableWidget" fun getPrefsName(appWidgetId: Int): String {
return "ani.dantotsu.widgets.Statistics.${appWidgetId}"
}
const val PREF_BACKGROUND_COLOR = "background_color" const val PREF_BACKGROUND_COLOR = "background_color"
const val PREF_BACKGROUND_FADE = "background_fade" const val PREF_BACKGROUND_FADE = "background_fade"
const val PREF_TITLE_TEXT_COLOR = "title_text_color" const val PREF_TITLE_TEXT_COLOR = "title_text_color"

View file

@ -12,6 +12,7 @@ import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.util.BitmapUtil.Companion.downloadImageAsBitmap
import ani.dantotsu.util.BitmapUtil.Companion.roundCorners import ani.dantotsu.util.BitmapUtil.Companion.roundCorners
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
@ -183,33 +184,6 @@ class UpcomingRemoteViewsFactory(private val context: Context) :
return rv return rv
} }
private fun downloadImageAsBitmap(imageUrl: String): Bitmap? {
var bitmap: Bitmap? = null
var inputStream: InputStream? = null
var urlConnection: HttpURLConnection? = null
try {
val url = URL(imageUrl)
urlConnection = url.openConnection() as HttpURLConnection
urlConnection.requestMethod = "GET"
urlConnection.connect()
if (urlConnection.responseCode == HttpURLConnection.HTTP_OK) {
inputStream = urlConnection.inputStream
bitmap = BitmapFactory.decodeStream(inputStream)
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
inputStream?.close()
urlConnection?.disconnect()
}
return bitmap?.let { roundCorners(it) }
}
override fun getLoadingView(): RemoteViews { override fun getLoadingView(): RemoteViews {
return RemoteViews(context.packageName, R.layout.item_upcoming_widget) return RemoteViews(context.packageName, R.layout.item_upcoming_widget)
} }

View file

@ -11,9 +11,9 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.widget.RemoteViews import android.widget.RemoteViews
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.toBitmap
import ani.dantotsu.MainActivity import ani.dantotsu.MainActivity
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.util.BitmapUtil.Companion.convertDrawableToBitmap
import ani.dantotsu.widgets.WidgetSizeProvider import ani.dantotsu.widgets.WidgetSizeProvider
/** /**
@ -97,7 +97,7 @@ class UpcomingWidget : AppWidgetProvider() {
intentTemplate.putExtra("fromWidget", true) intentTemplate.putExtra("fromWidget", true)
val views = RemoteViews(context.packageName, R.layout.upcoming_widget).apply { val views = RemoteViews(context.packageName, R.layout.upcoming_widget).apply {
setImageViewBitmap(R.id.backgroundView, convertDrawableToBitmap(gradientDrawable, width, height)) setImageViewBitmap(R.id.backgroundView, gradientDrawable.toBitmap(width, height))
setTextColor(R.id.text_show_title, titleTextColor) setTextColor(R.id.text_show_title, titleTextColor)
setTextColor(R.id.text_show_countdown, countdownTextColor) setTextColor(R.id.text_show_countdown, countdownTextColor)
setTextColor(R.id.widgetTitle, titleTextColor) setTextColor(R.id.widgetTitle, titleTextColor)