mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
fix: game shortcuts use the cover art when Steam's icon is too small (#825)
Steam keeps a 32x32 icon for each game, and the home screen stretched it to launcher size. A square from the middle of the cover art is used instead unless the icon is at least 128 px; the small icon is still the fallback when there is no art. Co-authored-by: Kirk <[email protected]>
This commit is contained in:
1 parent
b3ff93e80f
commit
e45d6f7192
2 files changed
+94
-10
No files matched your search
@@ -5,12 +5,14 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.content.pm.ShortcutManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.drawable.Icon
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import com.droiddeck.launcher.MainActivity
|
||||
import com.droiddeck.launcher.R
|
||||
import java.io.File
|
||||
|
||||
object GameShortcuts {
|
||||
fun launchIntent(context: Context, gameId: String) = Intent(Intent.ACTION_VIEW,
|
||||
@@ -19,22 +21,35 @@ object GameShortcuts {
|
||||
|
||||
private fun info(context: Context, game: Library.SteamGame): ShortcutInfo {
|
||||
val name = game.name.ifBlank { context.getString(R.string.app_name) }
|
||||
val icon = listOfNotNull(game.icon, game.art).firstNotNullOfOrNull { file -> runCatching {
|
||||
val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||
BitmapFactory.decodeFile(file.path, bounds)
|
||||
if (bounds.outWidth <= 0 || bounds.outHeight <= 0) return@runCatching null
|
||||
val options = BitmapFactory.Options().apply {
|
||||
inSampleSize = 1
|
||||
while (maxOf(bounds.outWidth, bounds.outHeight) / inSampleSize > 256) inSampleSize *= 2
|
||||
}
|
||||
BitmapFactory.decodeFile(file.path, options)?.let(Icon::createWithBitmap)
|
||||
}.getOrNull() } ?: Icon.createWithResource(context, R.mipmap.ic_launcher)
|
||||
val icon = iconBitmap(game)?.let(Icon::createWithBitmap) ?: Icon.createWithResource(context, R.mipmap.ic_launcher)
|
||||
return ShortcutInfo.Builder(context, "game:${game.gameIdString}")
|
||||
.setActivity(android.content.ComponentName(context, MainActivity::class.java))
|
||||
.setShortLabel(name.take(40)).setLongLabel(name).setIcon(icon)
|
||||
.setIntent(launchIntent(context, game.gameIdString)).build()
|
||||
}
|
||||
|
||||
internal fun iconBitmap(game: Library.SteamGame): Bitmap? {
|
||||
val icon = game.icon?.let(::decode)
|
||||
if (icon != null && minOf(icon.width, icon.height) >= SHARP_ICON_PX) return icon
|
||||
return game.art?.let(::decode)?.let(::square) ?: icon
|
||||
}
|
||||
|
||||
private fun decode(file: File): Bitmap? = runCatching {
|
||||
val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||
BitmapFactory.decodeFile(file.path, bounds)
|
||||
if (bounds.outWidth <= 0 || bounds.outHeight <= 0) return@runCatching null
|
||||
val options = BitmapFactory.Options().apply {
|
||||
inSampleSize = 1
|
||||
while (minOf(bounds.outWidth, bounds.outHeight) / (inSampleSize * 2) >= ICON_PX) inSampleSize *= 2
|
||||
}
|
||||
BitmapFactory.decodeFile(file.path, options)
|
||||
}.getOrNull()
|
||||
|
||||
private fun square(bitmap: Bitmap): Bitmap {
|
||||
val side = minOf(bitmap.width, bitmap.height)
|
||||
return Bitmap.createBitmap(bitmap, (bitmap.width - side) / 2, (bitmap.height - side) / 2, side, side)
|
||||
}
|
||||
|
||||
fun pin(activity: Activity, game: Library.SteamGame) {
|
||||
val manager = activity.getSystemService(ShortcutManager::class.java)
|
||||
val requested = runCatching { manager.isRequestPinShortcutSupported &&
|
||||
@@ -44,4 +59,7 @@ object GameShortcuts {
|
||||
|
||||
fun result(activity: Activity, game: Library.SteamGame): Intent =
|
||||
activity.getSystemService(ShortcutManager::class.java).createShortcutResultIntent(info(activity, game))
|
||||
|
||||
private const val ICON_PX = 256
|
||||
private const val SHARP_ICON_PX = 128
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.droiddeck.launcher.frontend
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.annotation.GraphicsMode
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [33], manifest = Config.NONE)
|
||||
@GraphicsMode(GraphicsMode.Mode.NATIVE)
|
||||
class GameShortcutsIconTest {
|
||||
private lateinit var dir: File
|
||||
|
||||
@Before fun setUp() {
|
||||
dir = Files.createTempDirectory("shortcut-icons").toFile()
|
||||
}
|
||||
|
||||
@Test fun aSmallSteamIconGivesWayToTheMiddleOfTheCoverArt() {
|
||||
val icon = image("icon.png", 32, 32, Color.GREEN)
|
||||
val cover = image("cover.png", 300, 450, Color.BLUE, band = 75, bandColor = Color.RED)
|
||||
val bitmap = GameShortcuts.iconBitmap(game(icon = icon, art = cover))!!
|
||||
assertEquals(300, bitmap.width)
|
||||
assertEquals(300, bitmap.height)
|
||||
assertEquals(Color.BLUE, bitmap.getPixel(0, 0))
|
||||
assertEquals(Color.BLUE, bitmap.getPixel(299, 299))
|
||||
}
|
||||
|
||||
@Test fun anIconLargeEnoughForTheHomeScreenIsKept() {
|
||||
val icon = image("icon.png", 256, 256, Color.GREEN)
|
||||
val cover = image("cover.png", 300, 450, Color.BLUE)
|
||||
val bitmap = GameShortcuts.iconBitmap(game(icon = icon, art = cover))!!
|
||||
assertEquals(256, bitmap.width)
|
||||
assertEquals(Color.GREEN, bitmap.getPixel(128, 128))
|
||||
}
|
||||
|
||||
@Test fun theSmallIconIsStillUsedWhenThereIsNoArt() {
|
||||
val icon = image("icon.png", 32, 32, Color.GREEN)
|
||||
val bitmap = GameShortcuts.iconBitmap(game(icon = icon, art = null))!!
|
||||
assertEquals(32, bitmap.width)
|
||||
assertEquals(Color.GREEN, bitmap.getPixel(16, 16))
|
||||
}
|
||||
|
||||
private fun game(icon: File?, art: File?) =
|
||||
Library.SteamGame(appId = 17480, name = "Game", art = art, library = "test", icon = icon)
|
||||
|
||||
private fun image(name: String, width: Int, height: Int, color: Int, band: Int = 0, bandColor: Int = color): File {
|
||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bitmap)
|
||||
canvas.drawColor(color)
|
||||
if (band > 0) {
|
||||
val paint = Paint().apply { this.color = bandColor }
|
||||
canvas.drawRect(0f, 0f, width.toFloat(), band.toFloat(), paint)
|
||||
canvas.drawRect(0f, (height - band).toFloat(), width.toFloat(), height.toFloat(), paint)
|
||||
}
|
||||
return File(dir, name).also { file -> file.outputStream().use { bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) } }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user