fix #1: Add ability to a second power bar to the top of the screen
This commit is contained in:
parent
7eed81a109
commit
dbaac0cec0
@ -14,6 +14,7 @@ class CustomProgressBar @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : View(context, attrs) {
|
||||
var progress: Double = 0.5
|
||||
var location: PowerbarLocation = PowerbarLocation.BOTTOM
|
||||
@ColorInt var progressColor: Int = 0xFF2b86e6.toInt()
|
||||
|
||||
override fun onDrawForeground(canvas: Canvas) {
|
||||
@ -48,21 +49,39 @@ class CustomProgressBar @JvmOverloads constructor(
|
||||
strokeWidth = 2f
|
||||
}
|
||||
|
||||
val rect = RectF(
|
||||
1f,
|
||||
1f + 4f,
|
||||
((canvas.width.toDouble() - 1f) * progress.coerceIn(0.0, 1.0)).toFloat(),
|
||||
canvas.height.toFloat() - 1f
|
||||
)
|
||||
when(location){
|
||||
PowerbarLocation.TOP -> {
|
||||
val rect = RectF(
|
||||
1f,
|
||||
1f,
|
||||
((canvas.width.toDouble() - 1f) * progress.coerceIn(0.0, 1.0)).toFloat(),
|
||||
canvas.height.toFloat() - 1f - 4f
|
||||
)
|
||||
|
||||
val corners = 2f
|
||||
canvas.drawRoundRect(0f, 2f + 4f, canvas.width.toFloat(), canvas.height.toFloat(), 2f, 2f, background)
|
||||
canvas.drawRoundRect(0f, 2f, canvas.width.toFloat(), canvas.height.toFloat() - 4f, 2f, 2f, background)
|
||||
|
||||
if (progress > 0.0) {
|
||||
canvas.drawRoundRect(rect, corners, corners, blurPaint)
|
||||
canvas.drawRoundRect(rect, corners, corners, linePaint)
|
||||
if (progress > 0.0) {
|
||||
canvas.drawRoundRect(rect, 2f, 2f, blurPaint)
|
||||
canvas.drawRoundRect(rect, 2f, 2f, linePaint)
|
||||
canvas.drawRoundRect(rect.right-4, rect.top, rect.right+4, rect.bottom, 2f, 2f, blurPaintHighlight)
|
||||
}
|
||||
}
|
||||
PowerbarLocation.BOTTOM -> {
|
||||
val rect = RectF(
|
||||
1f,
|
||||
1f + 4f,
|
||||
((canvas.width.toDouble() - 1f) * progress.coerceIn(0.0, 1.0)).toFloat(),
|
||||
canvas.height.toFloat() - 1f
|
||||
)
|
||||
|
||||
canvas.drawRoundRect(0f, 2f + 4f, canvas.width.toFloat(), canvas.height.toFloat(), 2f, 2f, background)
|
||||
|
||||
if (progress > 0.0) {
|
||||
canvas.drawRoundRect(rect, 2f, 2f, blurPaint)
|
||||
canvas.drawRoundRect(rect, 2f, 2f, linePaint)
|
||||
canvas.drawRoundRect(rect.right-4, rect.top, rect.right+4, rect.bottom, 2f, 2f, blurPaintHighlight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
canvas.drawRoundRect(rect.right-4, rect.top, rect.right+4, rect.bottom, 2f, 2f, blurPaintHighlight)
|
||||
}
|
||||
}
|
||||
@ -27,6 +27,7 @@ val settingsKey = stringPreferencesKey("settings")
|
||||
@Serializable
|
||||
data class PowerbarSettings(
|
||||
val source: SelectedSource = SelectedSource.POWER,
|
||||
val topBarSource: SelectedSource = SelectedSource.NONE,
|
||||
){
|
||||
companion object {
|
||||
val defaultSettings = Json.encodeToString(PowerbarSettings())
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package de.timklge.karoopowerbar
|
||||
|
||||
import android.R
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
@ -9,18 +8,46 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.NotificationCompat
|
||||
|
||||
import de.timklge.karoopowerbar.screens.SelectedSource
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ForegroundService : Service() {
|
||||
override fun onBind(intent: Intent?): IBinder {
|
||||
throw UnsupportedOperationException("Not yet implemented")
|
||||
}
|
||||
|
||||
private val windows = mutableSetOf<Window>()
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
setupForeground()
|
||||
val window = Window(this)
|
||||
window.open()
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
applicationContext.streamSettings()
|
||||
.collectLatest { settings ->
|
||||
windows.forEach { it.close() }
|
||||
windows.clear()
|
||||
|
||||
if (settings.source != SelectedSource.NONE) {
|
||||
Window(this@ForegroundService, PowerbarLocation.BOTTOM).apply {
|
||||
selectedSource = settings.source
|
||||
windows.add(this)
|
||||
open()
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.topBarSource != SelectedSource.NONE){
|
||||
Window(this@ForegroundService, PowerbarLocation.TOP).apply {
|
||||
selectedSource = settings.topBarSource
|
||||
open()
|
||||
windows.add(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
@ -45,7 +72,7 @@ class ForegroundService : Service() {
|
||||
val notification: Notification = notificationBuilder.setOngoing(true)
|
||||
.setContentTitle("Powerbar service running")
|
||||
.setContentText("Displaying on top of other apps")
|
||||
.setSmallIcon(R.drawable.ic_menu_add)
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setPriority(NotificationManager.IMPORTANCE_MIN)
|
||||
.setCategory(Notification.CATEGORY_SERVICE)
|
||||
.build()
|
||||
|
||||
@ -20,26 +20,34 @@ import io.hammerhead.karooext.models.UserProfile
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
fun remap(value: Double, fromMin: Double, fromMax: Double, toMin: Double, toMax: Double): Double {
|
||||
return (value - fromMin) * (toMax - toMin) / (fromMax - fromMin) + toMin
|
||||
}
|
||||
|
||||
enum class PowerbarLocation {
|
||||
TOP, BOTTOM
|
||||
}
|
||||
|
||||
class Window(
|
||||
private val context: Context
|
||||
private val context: Context,
|
||||
val powerbarLocation: PowerbarLocation = PowerbarLocation.BOTTOM
|
||||
) {
|
||||
private val rootView: View
|
||||
private var layoutParams: WindowManager.LayoutParams? = null
|
||||
private val windowManager: WindowManager
|
||||
private val layoutInflater: LayoutInflater
|
||||
|
||||
private val powerbar: CustomProgressBar
|
||||
|
||||
var selectedSource: SelectedSource = SelectedSource.POWER
|
||||
|
||||
init {
|
||||
layoutParams = WindowManager.LayoutParams(
|
||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||
@ -68,7 +76,15 @@ class Window(
|
||||
windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
}
|
||||
|
||||
layoutParams?.gravity = Gravity.BOTTOM
|
||||
layoutParams?.gravity = when (powerbarLocation) {
|
||||
PowerbarLocation.TOP -> Gravity.TOP
|
||||
PowerbarLocation.BOTTOM -> Gravity.BOTTOM
|
||||
}
|
||||
if (powerbarLocation == PowerbarLocation.TOP) {
|
||||
layoutParams?.y = 10
|
||||
} else {
|
||||
layoutParams?.y = 0
|
||||
}
|
||||
layoutParams?.width = displayMetrics.widthPixels
|
||||
layoutParams?.alpha = 1.0f
|
||||
}
|
||||
@ -79,36 +95,37 @@ class Window(
|
||||
|
||||
private var serviceJob: Job? = null
|
||||
|
||||
fun open() {
|
||||
serviceJob = CoroutineScope(Dispatchers.IO).launch {
|
||||
suspend fun open() {
|
||||
serviceJob = CoroutineScope(Dispatchers.Default).launch {
|
||||
karooSystem.connect { connected ->
|
||||
if (connected) {
|
||||
Log.i(KarooPowerbarExtension.TAG, "Connected")
|
||||
}
|
||||
}
|
||||
|
||||
context.streamSettings().distinctUntilChanged().collectLatest { settings ->
|
||||
powerbar.progressColor = context.resources.getColor(R.color.zoneAerobic)
|
||||
powerbar.progress = 0.0
|
||||
powerbar.invalidate()
|
||||
powerbar.progressColor = context.resources.getColor(R.color.zoneAerobic)
|
||||
powerbar.progress = 0.0
|
||||
powerbar.invalidate()
|
||||
|
||||
Log.i(KarooPowerbarExtension.TAG, "Streaming ${settings.source}")
|
||||
Log.i(KarooPowerbarExtension.TAG, "Streaming $selectedSource")
|
||||
|
||||
when (settings.source){
|
||||
SelectedSource.POWER -> streamPower(PowerStreamSmoothing.RAW)
|
||||
SelectedSource.POWER_3S -> streamPower(PowerStreamSmoothing.SMOOTHED_3S)
|
||||
SelectedSource.POWER_10S -> streamPower(PowerStreamSmoothing.SMOOTHED_10S)
|
||||
SelectedSource.HEART_RATE -> streamHeartrate()
|
||||
}
|
||||
when (selectedSource){
|
||||
SelectedSource.POWER -> streamPower(PowerStreamSmoothing.RAW)
|
||||
SelectedSource.POWER_3S -> streamPower(PowerStreamSmoothing.SMOOTHED_3S)
|
||||
SelectedSource.POWER_10S -> streamPower(PowerStreamSmoothing.SMOOTHED_10S)
|
||||
SelectedSource.HEART_RATE -> streamHeartrate()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (rootView.windowToken == null && rootView.parent == null) {
|
||||
windowManager.addView(rootView, layoutParams)
|
||||
withContext(Dispatchers.Main) {
|
||||
if (rootView.windowToken == null && rootView.parent == null) {
|
||||
windowManager.addView(rootView, layoutParams)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.d(KarooPowerbarExtension.TAG, e.toString())
|
||||
Log.e(KarooPowerbarExtension.TAG, e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -44,6 +44,7 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
enum class SelectedSource(val id: String, val label: String) {
|
||||
NONE("none", "None"),
|
||||
HEART_RATE("hr", "Heart Rate"),
|
||||
POWER("power", "Power"),
|
||||
POWER_3S("power_3s", "Power (3 second avg)"),
|
||||
@ -58,7 +59,9 @@ fun MainScreen() {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val karooSystem = remember { KarooSystemService(ctx) }
|
||||
|
||||
var selectedSource by remember { mutableStateOf(SelectedSource.POWER) }
|
||||
var bottomSelectedSource by remember { mutableStateOf(SelectedSource.POWER) }
|
||||
var topSelectedSource by remember { mutableStateOf(SelectedSource.NONE) }
|
||||
|
||||
var savedDialogVisible by remember { mutableStateOf(false) }
|
||||
var showAlerts by remember { mutableStateOf(false) }
|
||||
var givenPermissions by remember { mutableStateOf(false) }
|
||||
@ -67,7 +70,8 @@ fun MainScreen() {
|
||||
givenPermissions = Settings.canDrawOverlays(ctx)
|
||||
|
||||
ctx.streamSettings().collect { settings ->
|
||||
selectedSource = settings.source
|
||||
bottomSelectedSource = settings.source
|
||||
topSelectedSource = settings.topBarSource
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,18 +102,30 @@ fun MainScreen() {
|
||||
.verticalScroll(rememberScrollState())
|
||||
.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
|
||||
val powerSourceDropdownOptions = SelectedSource.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) }
|
||||
val powerSourceInitialSelection by remember(selectedSource) {
|
||||
mutableStateOf(powerSourceDropdownOptions.find { option -> option.id == selectedSource.id }!!)
|
||||
apply {
|
||||
val dropdownOptions = SelectedSource.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) }
|
||||
val dropdownInitialSelection by remember(bottomSelectedSource) {
|
||||
mutableStateOf(dropdownOptions.find { option -> option.id == bottomSelectedSource.id }!!)
|
||||
}
|
||||
Dropdown(label = "Bottom Bar", options = dropdownOptions, selected = dropdownInitialSelection) { selectedOption ->
|
||||
bottomSelectedSource = SelectedSource.entries.find { unit -> unit.id == selectedOption.id }!!
|
||||
}
|
||||
}
|
||||
Dropdown(label = "Data Source", options = powerSourceDropdownOptions, selected = powerSourceInitialSelection) { selectedOption ->
|
||||
selectedSource = SelectedSource.entries.find { unit -> unit.id == selectedOption.id }!!
|
||||
|
||||
apply {
|
||||
val dropdownOptions = SelectedSource.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) }
|
||||
val dropdownInitialSelection by remember(topSelectedSource) {
|
||||
mutableStateOf(dropdownOptions.find { option -> option.id == topSelectedSource.id }!!)
|
||||
}
|
||||
Dropdown(label = "Top Bar", options = dropdownOptions, selected = dropdownInitialSelection) { selectedOption ->
|
||||
topSelectedSource = SelectedSource.entries.find { unit -> unit.id == selectedOption.id }!!
|
||||
}
|
||||
}
|
||||
|
||||
FilledTonalButton(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(50.dp), onClick = {
|
||||
val newSettings = PowerbarSettings(source = selectedSource)
|
||||
val newSettings = PowerbarSettings(source = bottomSelectedSource, topBarSource = topSelectedSource)
|
||||
|
||||
coroutineScope.launch {
|
||||
saveSettings(ctx, newSettings)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user