очки: HUD (время+заряд) сверху по центру обоих глаз, всегда поверх
Панель перенесена из view-mate (StatusPanel) как часть активити, без виджета: StatusHud + rememberStatusHudData в :app-glasses-vm/ui. Слой добавляется в GlassesScreen поверх when(state) внутри Binocular, поэтому виден в любом состоянии — ожидание/нет связи/запущенный гость — и дублируется в обе половины (левый и правый глаз).
This commit is contained in:
@@ -46,6 +46,7 @@ fun GlassesScreen(
|
||||
val state by controller.state.collectAsState()
|
||||
val controllerFrameSource by controller.frameSource.collectAsState()
|
||||
val cursor by controller.cursor.collectAsState()
|
||||
val hud = rememberStatusHudData()
|
||||
val noOp = remember { NoOpFrameSource() }
|
||||
val frameSource = controllerFrameSource ?: noOp
|
||||
|
||||
@@ -95,6 +96,17 @@ fun GlassesScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// HUD (время + заряд) — всегда поверх, в обеих половинах (на обоих глазах),
|
||||
// сверху по центру. Виден в любом состоянии: и в ожидании связи, и поверх гостя.
|
||||
Binocular {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.TopCenter,
|
||||
) {
|
||||
StatusHud(hud, modifier = Modifier.padding(top = 6.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package pw.binom.rayneovm.glasses.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.BatteryManager
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import kotlin.math.roundToInt
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/** Данные HUD: время + заряд + цвет индикатора заряда. */
|
||||
data class StatusHudData(
|
||||
val time: String,
|
||||
val percent: Int,
|
||||
val chargeColorArgb: Int,
|
||||
)
|
||||
|
||||
private const val CHARGE_COLOR_CHARGING = 0xFF4CAF50.toInt() // зелёный — идёт заряд
|
||||
private const val CHARGE_COLOR_DISCHARGING = 0xFFFFFFFF.toInt() // белый — разряд
|
||||
private const val CHARGE_COLOR_PLUGGED_NOT_CHARGING = 0xFFE53935.toInt() // красный — подключён, но не заряжается
|
||||
|
||||
/** Цвет индикатора заряда: не подключён=белый, CHARGING/FULL=зелёный, иначе красный. */
|
||||
private fun chargeColorArgb(plugged: Boolean, status: Int): Int = when {
|
||||
!plugged -> CHARGE_COLOR_DISCHARGING
|
||||
status == BatteryManager.BATTERY_STATUS_CHARGING ||
|
||||
status == BatteryManager.BATTERY_STATUS_FULL -> CHARGE_COLOR_CHARGING
|
||||
else -> CHARGE_COLOR_PLUGGED_NOT_CHARGING
|
||||
}
|
||||
|
||||
/**
|
||||
* HUD очков: время + заряд, компактной «пилюлей» на полупрозрачном тёмном фоне.
|
||||
*
|
||||
* Панель вызывается внутри [pw.binom.mercury.binocular.Binocular] (см. `GlassesScreen`),
|
||||
* поэтому автоматически дублируется в обеих половинах — левом и правом глазу.
|
||||
*/
|
||||
@Composable
|
||||
fun StatusHud(data: StatusHudData, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.background(Color(0x99000000))
|
||||
.padding(horizontal = 10.dp, vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(data.time, color = Color.White, fontSize = 14.sp)
|
||||
Spacer(Modifier.width(6.dp))
|
||||
// Процент — цветом состояния заряда, без отдельного квадратика.
|
||||
Text("${data.percent}%", color = Color(data.chargeColorArgb), fontSize = 14.sp)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Провайдер данных HUD: время обновляется раз в секунду, заряд — из sticky
|
||||
* `ACTION_BATTERY_CHANGED`. Держится один на весь экран; обе половины читают одни данные.
|
||||
*/
|
||||
@Composable
|
||||
fun rememberStatusHudData(): StatusHudData {
|
||||
val context = LocalContext.current
|
||||
var data by remember {
|
||||
mutableStateOf(
|
||||
StatusHudData(
|
||||
time = "--:--",
|
||||
percent = 0,
|
||||
chargeColorArgb = CHARGE_COLOR_DISCHARGING,
|
||||
)
|
||||
)
|
||||
}
|
||||
DisposableEffect(Unit) {
|
||||
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
val timeFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
|
||||
var percent = 0
|
||||
var plugged = false
|
||||
var status = BatteryManager.BATTERY_STATUS_UNKNOWN
|
||||
|
||||
fun readBattery() {
|
||||
val intent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
||||
?: return
|
||||
val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
|
||||
val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
|
||||
status = intent.getIntExtra(
|
||||
BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN
|
||||
)
|
||||
plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0
|
||||
percent = if (scale > 0 && level >= 0) {
|
||||
(level * 100f / scale).roundToInt().coerceIn(0, 100)
|
||||
} else {
|
||||
level.coerceIn(0, 100)
|
||||
}
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
while (isActive) {
|
||||
readBattery()
|
||||
data = StatusHudData(timeFormat.format(Date()), percent, chargeColorArgb(plugged, status))
|
||||
delay(1_000)
|
||||
}
|
||||
}
|
||||
onDispose { scope.cancel() }
|
||||
}
|
||||
return data
|
||||
}
|
||||
Reference in New Issue
Block a user