экран очков: единый тумблер на «Пульте» + очки шлют реальное состояние экрана
- shared: новое сообщение ScreenState(on) от очков телефону - glasses: слушаем ACTION_SCREEN_ON/OFF (PowerManager.isInteractive), шлём состояние при подключении телефона и на каждое изменение; после ScreenControl — актуализация - phone: VmPhoneController.screenOn (StateFlow<Boolean?>), оптимистичное обновление в setScreen - phone: на экране «Пульт» одна кнопка-тумблер «Погасить/Разбудить экран очков», подпись отражает реальное состояние, disabled без Канала 1
This commit is contained in:
@@ -28,6 +28,7 @@ import pw.binom.rayneovm.shared.KeyEvent as VmKeyEvent
|
||||
import pw.binom.rayneovm.shared.Pong
|
||||
import pw.binom.rayneovm.shared.RequestShizukuStatus
|
||||
import pw.binom.rayneovm.shared.ScreenControl
|
||||
import pw.binom.rayneovm.shared.ScreenState
|
||||
import pw.binom.rayneovm.shared.SetAudioMode
|
||||
import pw.binom.rayneovm.shared.SetText
|
||||
import pw.binom.rayneovm.shared.ShizukuStatus
|
||||
@@ -56,6 +57,10 @@ class VmPhoneController(
|
||||
private val _glassesShizuku = MutableStateFlow<ShizukuStatus?>(null)
|
||||
val glassesShizuku: StateFlow<ShizukuStatus?> = _glassesShizuku.asStateFlow()
|
||||
|
||||
/** Реальное состояние экрана очков (шлют очки); null — пока неизвестно. */
|
||||
private val _screenOn = MutableStateFlow<Boolean?>(null)
|
||||
val screenOn: StateFlow<Boolean?> = _screenOn.asStateFlow()
|
||||
|
||||
private var currentTransport: StringTransport? = null
|
||||
private val audioManager by lazy { AudioPlaybackManager(context) }
|
||||
|
||||
@@ -122,6 +127,7 @@ class VmPhoneController(
|
||||
onDisconnected = {
|
||||
currentTransport = null
|
||||
_glassesShizuku.value = null
|
||||
_screenOn.value = null
|
||||
// Очки отвалились — аудио-поток всё равно мёртв, глушим трек.
|
||||
audioManager.stop()
|
||||
_state.value = ConnectionState.Disconnected
|
||||
@@ -159,6 +165,9 @@ class VmPhoneController(
|
||||
is ShizukuStatus -> {
|
||||
_glassesShizuku.value = msg
|
||||
}
|
||||
is ScreenState -> {
|
||||
_screenOn.value = msg.on
|
||||
}
|
||||
is SetAudioMode -> {
|
||||
// Аудио режим — отдельный поток, не контролируется этим контроллером напрямую.
|
||||
// AudioCaptureService на очках стартует/останавливается.
|
||||
@@ -226,6 +235,8 @@ class VmPhoneController(
|
||||
suspend fun setScreen(sleep: Boolean): Boolean {
|
||||
if (_state.value !is ConnectionState.Connected) return false
|
||||
send(ScreenControl(sleep))
|
||||
// Оптимистично: точное состояние очки пришлют сами (ScreenState) — возможно с задержкой.
|
||||
_screenOn.value = !sleep
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,12 @@ import androidx.compose.material.icons.filled.FastRewind
|
||||
import androidx.compose.material.icons.filled.Keyboard
|
||||
import androidx.compose.material.icons.filled.Pause
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material.icons.filled.PowerSettingsNew
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -42,6 +44,7 @@ import androidx.compose.ui.unit.sp
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import pw.binom.rayneovm.phone.protocol.ConnectionState
|
||||
import pw.binom.rayneovm.phone.protocol.VmPhoneController
|
||||
import pw.binom.rayneovm.shared.VmDisplay
|
||||
|
||||
@@ -71,6 +74,9 @@ fun RemoteScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
// Реальное состояние экрана очков (шлют сами очки) — питает кнопку-тумблер ниже.
|
||||
val screenOn by controller.screenOn.collectAsState()
|
||||
val connected = controller.state.collectAsState().value is ConnectionState.Connected
|
||||
// Позиция курсора в координатах VirtualDisplay гостя.
|
||||
var cursor by remember { mutableStateOf(Offset(VmDisplay.WIDTH / 2f, VmDisplay.HEIGHT / 2f)) }
|
||||
// Отложенный клик: два пальца кладут не одновременно, и первый палец успевает «щёлкнуть»
|
||||
@@ -203,6 +209,23 @@ fun RemoteScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// Одна кнопка-тумблер: гасит/будит экран очков. Символ отражает текущее состояние
|
||||
// (его шлют очки), поэтому нажатие всегда чередует вкл/выкл корректно.
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
RemoteButton(
|
||||
icon = Icons.Default.PowerSettingsNew,
|
||||
label = when (screenOn) {
|
||||
true -> "Погасить экран очков"
|
||||
false -> "Разбудить экран очков"
|
||||
null -> "Экран очков: вкл/выкл"
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
enabled = connected,
|
||||
) {
|
||||
scope.launch { controller.setScreen(sleep = screenOn != false) }
|
||||
}
|
||||
}
|
||||
|
||||
if (showKeyboard) {
|
||||
KeyboardDialog(
|
||||
onDismiss = { showKeyboard = false },
|
||||
|
||||
Reference in New Issue
Block a user