ref #8,9: Replace arrow graphics

This commit is contained in:
Tim Kluge 2024-12-12 23:06:20 +01:00
parent 7d1715707e
commit 3f1ae6d37f
6 changed files with 52 additions and 43 deletions

View File

@ -1,6 +1,7 @@
package de.timklge.karooheadwind.datatypes package de.timklge.karooheadwind.datatypes
import android.content.Context import android.content.Context
import android.graphics.BitmapFactory
import android.util.Log import android.util.Log
import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.DpSize
import androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi import androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi
@ -54,6 +55,12 @@ class HeadwindDirectionDataType(
override fun startView(context: Context, config: ViewConfig, emitter: ViewEmitter) { override fun startView(context: Context, config: ViewConfig, emitter: ViewEmitter) {
Log.d(KarooHeadwindExtension.TAG, "Starting headwind direction view with $emitter") Log.d(KarooHeadwindExtension.TAG, "Starting headwind direction view with $emitter")
val baseBitmap = BitmapFactory.decodeResource(
context.resources,
de.timklge.karooheadwind.R.drawable.arrow
)
val configJob = CoroutineScope(Dispatchers.IO).launch { val configJob = CoroutineScope(Dispatchers.IO).launch {
emitter.onNext(UpdateGraphicConfig(showHeader = false)) emitter.onNext(UpdateGraphicConfig(showHeader = false))
awaitCancellation() awaitCancellation()
@ -76,10 +83,10 @@ class HeadwindDirectionDataType(
val windSpeed = streamData.data.current.windSpeed val windSpeed = streamData.data.current.windSpeed
val windDirection = streamData.value val windDirection = streamData.value
val headwindSpeed = cos( (windDirection + 180) * Math.PI / 180.0) * windSpeed val headwindSpeed = cos( (windDirection + 180) * Math.PI / 180.0) * windSpeed
val windSpeedText = if(streamData.settings.showWindspeedOverlay) "${headwindSpeed.roundToInt()}" else null val windSpeedText = "${headwindSpeed.roundToInt()}"
val result = glance.compose(context, DpSize.Unspecified) { val result = glance.compose(context, DpSize.Unspecified) {
HeadwindDirection(windDirection.roundToInt(), config.textSize, windSpeedText) HeadwindDirection(baseBitmap, windDirection.roundToInt(), config.textSize, windSpeedText)
} }
emitter.updateView(result.remoteViews) emitter.updateView(result.remoteViews)

View File

@ -1,23 +1,21 @@
package de.timklge.karooheadwind.datatypes package de.timklge.karooheadwind.datatypes
import android.R import android.content.Context
import android.content.res.Resources
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Paint import android.graphics.Paint
import android.graphics.Path import android.util.Log
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.glance.ColorFilter import androidx.glance.ColorFilter
import androidx.glance.GlanceModifier import androidx.glance.GlanceModifier
import androidx.glance.Image import androidx.glance.Image
import androidx.glance.ImageProvider import androidx.glance.ImageProvider
import androidx.glance.LocalContext import androidx.glance.appwidget.background
import androidx.glance.background import androidx.glance.background
import androidx.glance.color.ColorProvider import androidx.glance.color.ColorProvider
import androidx.glance.layout.Alignment import androidx.glance.layout.Alignment
@ -27,45 +25,44 @@ import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.padding import androidx.glance.layout.padding
import androidx.glance.preview.ExperimentalGlancePreviewApi import androidx.glance.preview.ExperimentalGlancePreviewApi
import androidx.glance.preview.Preview import androidx.glance.preview.Preview
import androidx.glance.text.FontFamily
import androidx.glance.text.Text import androidx.glance.text.Text
import androidx.glance.text.TextStyle import androidx.glance.text.TextStyle
import de.timklge.karooheadwind.KarooHeadwindExtension
import kotlin.math.roundToInt import kotlin.math.roundToInt
val bitmapsByBearing = mutableMapOf<Int, Bitmap>() data class BitmapWithBearing(val bitmap: Bitmap, val bearing: Int)
fun getArrowBitmapByBearing(bearing: Int): Bitmap { val bitmapsByBearing = mutableMapOf<BitmapWithBearing, Bitmap>()
fun getArrowBitmapByBearing(baseBitmap: Bitmap, bearing: Int): Bitmap {
synchronized(bitmapsByBearing) { synchronized(bitmapsByBearing) {
val bearingRounded = (((bearing + 360) / 5.0).roundToInt() * 5) % 360 val bearingRounded = (((bearing + 360) / 10.0).roundToInt() * 10) % 360
val storedBitmap = bitmapsByBearing[bearingRounded] val bitmapWithBearing = BitmapWithBearing(baseBitmap, bearingRounded)
val storedBitmap = bitmapsByBearing[bitmapWithBearing]
if (storedBitmap != null) return storedBitmap if (storedBitmap != null) return storedBitmap
val bitmap = Bitmap.createBitmap(128, 128, Bitmap.Config.ARGB_8888) val bitmap = Bitmap.createBitmap(128, 128, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap) val canvas = Canvas(bitmap)
val paint = Paint().apply { val paint = Paint().apply {
color = android.graphics.Color.WHITE color = android.graphics.Color.BLACK
style = Paint.Style.STROKE style = Paint.Style.STROKE
strokeWidth = 15f // strokeWidth = 15f
isAntiAlias = true isAntiAlias = true
} }
val path = Path().apply {
moveTo(64f, 0f) // Top point of the arrow
lineTo(128f, 128f) // Bottom right point of the arrow
lineTo(64f, 96f) // Middle bottom point of the arrow
lineTo(0f, 128f) // Bottom left point of the arrow
close() // Close the path to form the arrow shape
}
canvas.save() canvas.save()
canvas.rotate(bearing.toFloat(), 64f, 64f) // Rotate the canvas based on the bearing canvas.scale((bitmap.width / baseBitmap.width.toFloat()), (bitmap.height / baseBitmap.height.toFloat()), (bitmap.width / 2).toFloat(), (bitmap.height / 2).toFloat())
canvas.scale(0.75f, 0.75f, 64f, 64f) // Scale the arrow down to fit the canvas Log.d(KarooHeadwindExtension.TAG, "Drawing arrow at $bearingRounded")
canvas.drawPath(path, paint) canvas.rotate(bearing.toFloat(), (bitmap.width / 2).toFloat(), (bitmap.height / 2).toFloat())
canvas.drawBitmap(baseBitmap, ((bitmap.width - baseBitmap.width) / 2).toFloat(), ((bitmap.height - baseBitmap.height) / 2).toFloat(), paint)
canvas.restore() canvas.restore()
bitmapsByBearing[bearingRounded] = bitmap bitmapsByBearing[bitmapWithBearing] = bitmap
return bitmap return bitmap
} }
@ -74,7 +71,7 @@ fun getArrowBitmapByBearing(bearing: Int): Bitmap {
@OptIn(ExperimentalGlancePreviewApi::class) @OptIn(ExperimentalGlancePreviewApi::class)
@Preview(widthDp = 200, heightDp = 150) @Preview(widthDp = 200, heightDp = 150)
@Composable @Composable
fun HeadwindDirection(bearing: Int, fontSize: Int, overlayText: String? = null) { fun HeadwindDirection(baseBitmap: Bitmap, bearing: Int, fontSize: Int, overlayText: String) {
Box( Box(
modifier = GlanceModifier.fillMaxSize().padding(5.dp), modifier = GlanceModifier.fillMaxSize().padding(5.dp),
contentAlignment = Alignment( contentAlignment = Alignment(
@ -84,18 +81,16 @@ fun HeadwindDirection(bearing: Int, fontSize: Int, overlayText: String? = null)
) { ) {
Image( Image(
modifier = GlanceModifier.fillMaxSize(), modifier = GlanceModifier.fillMaxSize(),
provider = ImageProvider(getArrowBitmapByBearing(bearing)), provider = ImageProvider(getArrowBitmapByBearing(baseBitmap, bearing)),
contentDescription = "Relative wind direction indicator", contentDescription = "Relative wind direction indicator",
contentScale = ContentScale.Fit, contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White)) colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White))
) )
overlayText?.let {
Text( Text(
overlayText, overlayText,
style = TextStyle(ColorProvider(Color.White, Color.White), fontSize = TextUnit(fontSize.toFloat()*0.7f, TextUnitType.Sp)), style = TextStyle(ColorProvider(Color.Black, Color.White), fontSize = (0.5 * fontSize).sp, fontFamily = FontFamily.Monospace),
modifier = GlanceModifier.background(Color(0f, 0f, 0f, 0.5f)).padding(5.dp) modifier = GlanceModifier.background(Color(1f, 1f, 1f, 0.5f), Color(0f, 0f, 0f, 0.5f)).padding(2.dp)
) )
} }
} }
}

View File

@ -1,6 +1,7 @@
package de.timklge.karooheadwind.datatypes package de.timklge.karooheadwind.datatypes
import android.content.Context import android.content.Context
import android.graphics.BitmapFactory
import android.util.Log import android.util.Log
import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.DpSize
import androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi import androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi
@ -58,6 +59,11 @@ class WeatherDataType(
awaitCancellation() awaitCancellation()
} }
val baseBitmap = BitmapFactory.decodeResource(
context.resources,
de.timklge.karooheadwind.R.drawable.arrow_0
)
data class StreamData(val data: OpenMeteoCurrentWeatherResponse, val settings: HeadwindSettings) data class StreamData(val data: OpenMeteoCurrentWeatherResponse, val settings: HeadwindSettings)
val viewJob = CoroutineScope(Dispatchers.IO).launch { val viewJob = CoroutineScope(Dispatchers.IO).launch {
@ -73,7 +79,7 @@ class WeatherDataType(
val interpretation = WeatherInterpretation.fromWeatherCode(data.current.weatherCode) val interpretation = WeatherInterpretation.fromWeatherCode(data.current.weatherCode)
val result = glance.compose(context, DpSize.Unspecified) { val result = glance.compose(context, DpSize.Unspecified) {
Weather(interpretation, data.current.windDirection.roundToInt(), data.current.windSpeed.roundToInt(), data.current.windGusts.roundToInt()) Weather(baseBitmap, interpretation, data.current.windDirection.roundToInt(), data.current.windSpeed.roundToInt(), data.current.windGusts.roundToInt())
} }
emitter.updateView(result.remoteViews) emitter.updateView(result.remoteViews)

View File

@ -1,5 +1,6 @@
package de.timklge.karooheadwind.datatypes package de.timklge.karooheadwind.datatypes
import android.graphics.Bitmap
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnit
@ -41,7 +42,7 @@ fun getWeatherIcon(interpretation: WeatherInterpretation): Int {
@OptIn(ExperimentalGlancePreviewApi::class) @OptIn(ExperimentalGlancePreviewApi::class)
@Preview(widthDp = 200, heightDp = 150) @Preview(widthDp = 200, heightDp = 150)
@Composable @Composable
fun Weather(current: WeatherInterpretation, windBearing: Int, windSpeed: Int, windGusts: Int) { fun Weather(baseBitmap: Bitmap, current: WeatherInterpretation, windBearing: Int, windSpeed: Int, windGusts: Int) {
Column(modifier = GlanceModifier.fillMaxSize(), horizontalAlignment = Alignment.End) { Column(modifier = GlanceModifier.fillMaxSize(), horizontalAlignment = Alignment.End) {
Row(modifier = GlanceModifier.defaultWeight(), horizontalAlignment = Alignment.End) { Row(modifier = GlanceModifier.defaultWeight(), horizontalAlignment = Alignment.End) {
val imageW = 70 val imageW = 70
@ -58,7 +59,7 @@ fun Weather(current: WeatherInterpretation, windBearing: Int, windSpeed: Int, wi
Row(horizontalAlignment = Alignment.CenterHorizontally, verticalAlignment = Alignment.CenterVertically) { Row(horizontalAlignment = Alignment.CenterHorizontally, verticalAlignment = Alignment.CenterVertically) {
Image( Image(
modifier = GlanceModifier.height(20.dp).width(12.dp), modifier = GlanceModifier.height(20.dp).width(12.dp),
provider = ImageProvider(getArrowBitmapByBearing(windBearing)), provider = ImageProvider(getArrowBitmapByBearing(baseBitmap, windBearing)),
contentDescription = "Current wind direction", contentDescription = "Current wind direction",
contentScale = ContentScale.Fit, contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White)) colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White))

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B