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