ref #8,9: Replace arrow graphics
This commit is contained in:
parent
7d1715707e
commit
3f1ae6d37f
@ -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)
|
||||||
|
|||||||
@ -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(
|
||||||
@ -83,19 +80,17 @@ 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.Black, Color.White), fontSize = (0.5 * fontSize).sp, fontFamily = FontFamily.Monospace),
|
||||||
style = TextStyle(ColorProvider(Color.White, Color.White), fontSize = TextUnit(fontSize.toFloat()*0.7f, TextUnitType.Sp)),
|
modifier = GlanceModifier.background(Color(1f, 1f, 1f, 0.5f), Color(0f, 0f, 0f, 0.5f)).padding(2.dp)
|
||||||
modifier = GlanceModifier.background(Color(0f, 0f, 0f, 0.5f)).padding(5.dp)
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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)
|
||||||
|
|||||||
@ -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))
|
||||||
|
|||||||
BIN
app/src/main/res/drawable/arrow.png
Executable file
BIN
app/src/main/res/drawable/arrow.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
BIN
app/src/main/res/drawable/arrow_0.png
Normal file
BIN
app/src/main/res/drawable/arrow_0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 980 B |
Loading…
x
Reference in New Issue
Block a user