Skip to content

Commit e27ab4a

Browse files
authored
Merge pull request #2353 from switchifyapp/feature/dedicated-pc-typing-screen-2352
Add dedicated scanned PC typing screen
2 parents 7a38231 + 6c13fa9 commit e27ab4a

16 files changed

Lines changed: 806 additions & 226 deletions
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.enaboapps.switchify.pc
2+
3+
import androidx.annotation.StringRes
4+
import com.enaboapps.switchify.R
5+
6+
enum class PcKeyboardKey(
7+
val protocolValue: String,
8+
@param:StringRes val labelResId: Int
9+
) {
10+
Backspace("Backspace", R.string.pc_key_backspace),
11+
Delete("Delete", R.string.pc_key_delete),
12+
Enter("Enter", R.string.pc_key_enter),
13+
Escape("Escape", R.string.pc_key_escape),
14+
Space("Space", R.string.pc_key_space),
15+
Tab("Tab", R.string.pc_key_tab),
16+
ArrowUp("ArrowUp", R.string.pc_key_arrow_up),
17+
ArrowDown("ArrowDown", R.string.pc_key_arrow_down),
18+
ArrowLeft("ArrowLeft", R.string.pc_key_arrow_left),
19+
ArrowRight("ArrowRight", R.string.pc_key_arrow_right),
20+
Home("Home", R.string.pc_key_home),
21+
End("End", R.string.pc_key_end),
22+
PageUp("PageUp", R.string.pc_key_page_up),
23+
PageDown("PageDown", R.string.pc_key_page_down)
24+
}

app/src/main/java/com/enaboapps/switchify/pc/PcProtocol.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,17 @@ object PcProtocol {
104104
return authenticatedCommand(id, deviceId, token, timestamp, "keyboard.typeText", JSONObject().put("text", text))
105105
}
106106

107+
fun keyboardKey(id: String, deviceId: String, token: String, timestamp: Long, key: PcKeyboardKey): String {
108+
return authenticatedCommand(
109+
id,
110+
deviceId,
111+
token,
112+
timestamp,
113+
"keyboard.key",
114+
JSONObject().put("key", key.protocolValue)
115+
)
116+
}
117+
107118
fun parseResponse(raw: String): PcProtocolResponse {
108119
return runCatching {
109120
val json = JSONObject(raw)

app/src/main/java/com/enaboapps/switchify/pc/PcServiceConnectionController.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ class PcServiceConnectionController(
8080
return PcServiceConnectResult.Failed(message)
8181
}
8282

83-
suspend fun sendMouseCommand(command: PcMouseCommand): PcCommandResult {
83+
suspend fun sendCommand(command: PcControlCommand): PcCommandResult {
8484
val connected = PcConnectionStateHolder.connectionState.value as? PcConnectionState.Connected
8585
?: return PcCommandResult.AuthFailed()
86-
val result = connector.sendMouseCommand(connected.session, command)
86+
val result = connector.sendCommand(connected.session, command)
8787
if (result is PcCommandResult.AuthFailed) {
8888
tokenStore.clearToken(connected.session.desktopId)
8989
PcConnectionStateHolder.setDisconnected()

app/src/main/java/com/enaboapps/switchify/pc/SwitchifyPcClient.kt

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@ sealed class PcPingResult {
2525
data class Failed(val message: String) : PcPingResult()
2626
}
2727

28-
sealed class PcMouseCommand {
29-
data class Move(val dx: Int, val dy: Int) : PcMouseCommand()
30-
data class Scroll(val dx: Int, val dy: Int) : PcMouseCommand()
31-
data class TypeText(val text: String) : PcMouseCommand()
32-
data object LeftClick : PcMouseCommand()
33-
data object DoubleClick : PcMouseCommand()
34-
data object RightClick : PcMouseCommand()
28+
sealed class PcControlCommand {
29+
data class Move(val dx: Int, val dy: Int) : PcControlCommand()
30+
data class Scroll(val dx: Int, val dy: Int) : PcControlCommand()
31+
data class TypeText(val text: String) : PcControlCommand()
32+
data class PressKey(val key: PcKeyboardKey) : PcControlCommand()
33+
data object LeftClick : PcControlCommand()
34+
data object DoubleClick : PcControlCommand()
35+
data object RightClick : PcControlCommand()
3536
}
3637

3738
sealed class PcCommandResult {
@@ -41,22 +42,22 @@ sealed class PcCommandResult {
4142
}
4243

4344
sealed class PcLiveControlResult {
44-
data class Connected(val connection: PcMouseControlConnection) : PcLiveControlResult()
45+
data class Connected(val connection: PcControlConnection) : PcLiveControlResult()
4546
data class AuthFailed(val message: String = "Connection expired. Connect to PC from Switchify first.") : PcLiveControlResult()
4647
data class Failed(val message: String = "Could not connect to PC.") : PcLiveControlResult()
4748
}
4849

49-
interface PcMouseControlConnection {
50+
interface PcControlConnection {
5051
val pointerProfile: PcPointerMovementProfile?
51-
suspend fun sendMouseCommand(command: PcMouseCommand): PcCommandResult
52+
suspend fun sendCommand(command: PcControlCommand): PcCommandResult
5253
fun close()
5354
}
5455

5556
interface PcConnector {
5657
suspend fun requestApproval(pc: DiscoveredPc, requestNonce: String): PcPairingResult
5758
suspend fun authenticatedPing(pc: DiscoveredPc, token: String): PcPingResult
58-
suspend fun openMouseControlSession(session: PcAuthenticatedSession): PcLiveControlResult
59-
suspend fun sendMouseCommand(session: PcAuthenticatedSession, command: PcMouseCommand): PcCommandResult
59+
suspend fun openControlSession(session: PcAuthenticatedSession): PcLiveControlResult
60+
suspend fun sendCommand(session: PcAuthenticatedSession, command: PcControlCommand): PcCommandResult
6061
fun close()
6162
}
6263

@@ -146,7 +147,7 @@ class SwitchifyPcClient(
146147
return PcPingResult.Failed("Found PC, but could not connect.")
147148
}
148149

149-
override suspend fun sendMouseCommand(session: PcAuthenticatedSession, command: PcMouseCommand): PcCommandResult {
150+
override suspend fun sendCommand(session: PcAuthenticatedSession, command: PcControlCommand): PcCommandResult {
150151
val token = tokenStore.getToken(session.desktopId) ?: return PcCommandResult.AuthFailed()
151152
return runCatching {
152153
val webSocketSession = client.webSocketSession { url { takeFrom(session.websocketUrl) } }
@@ -174,7 +175,7 @@ class SwitchifyPcClient(
174175
}.getOrDefault(PcCommandResult.Failed())
175176
}
176177

177-
override suspend fun openMouseControlSession(session: PcAuthenticatedSession): PcLiveControlResult {
178+
override suspend fun openControlSession(session: PcAuthenticatedSession): PcLiveControlResult {
178179
val token = tokenStore.getToken(session.desktopId) ?: return PcLiveControlResult.AuthFailed()
179180
var webSocketSession: WebSocketSession? = null
180181
return try {
@@ -192,7 +193,7 @@ class SwitchifyPcClient(
192193
)
193194
when (val response = withTimeout(10_000) { readExpectedResponse(webSocketSession, requestId) }) {
194195
is PcProtocolResponse.Ack -> PcLiveControlResult.Connected(
195-
LiveMouseControlConnection(
196+
LiveControlConnection(
196197
webSocketSession = webSocketSession,
197198
authenticatedSession = session,
198199
token = token,
@@ -275,24 +276,25 @@ class SwitchifyPcClient(
275276

276277
private fun nextRequestId(): String = "android-${UUID.randomUUID()}"
277278

278-
private fun PcMouseCommand.toMessage(id: String, deviceId: String, token: String, timestamp: Long): String {
279+
private fun PcControlCommand.toMessage(id: String, deviceId: String, token: String, timestamp: Long): String {
279280
return when (this) {
280-
is PcMouseCommand.Move -> PcProtocol.mouseMove(id, deviceId, token, timestamp, dx, dy)
281-
is PcMouseCommand.Scroll -> PcProtocol.mouseScroll(id, deviceId, token, timestamp, dx, dy)
282-
PcMouseCommand.LeftClick -> PcProtocol.mouseClick(id, deviceId, token, timestamp)
283-
PcMouseCommand.DoubleClick -> PcProtocol.mouseDoubleClick(id, deviceId, token, timestamp)
284-
PcMouseCommand.RightClick -> PcProtocol.mouseRightClick(id, deviceId, token, timestamp)
285-
is PcMouseCommand.TypeText -> PcProtocol.keyboardTypeText(id, deviceId, token, timestamp, text)
281+
is PcControlCommand.Move -> PcProtocol.mouseMove(id, deviceId, token, timestamp, dx, dy)
282+
is PcControlCommand.Scroll -> PcProtocol.mouseScroll(id, deviceId, token, timestamp, dx, dy)
283+
PcControlCommand.LeftClick -> PcProtocol.mouseClick(id, deviceId, token, timestamp)
284+
PcControlCommand.DoubleClick -> PcProtocol.mouseDoubleClick(id, deviceId, token, timestamp)
285+
PcControlCommand.RightClick -> PcProtocol.mouseRightClick(id, deviceId, token, timestamp)
286+
is PcControlCommand.TypeText -> PcProtocol.keyboardTypeText(id, deviceId, token, timestamp, text)
287+
is PcControlCommand.PressKey -> PcProtocol.keyboardKey(id, deviceId, token, timestamp, key)
286288
}
287289
}
288290

289-
private inner class LiveMouseControlConnection(
291+
private inner class LiveControlConnection(
290292
private val webSocketSession: WebSocketSession,
291293
private val authenticatedSession: PcAuthenticatedSession,
292294
private val token: String,
293295
override val pointerProfile: PcPointerMovementProfile?
294-
) : PcMouseControlConnection {
295-
override suspend fun sendMouseCommand(command: PcMouseCommand): PcCommandResult {
296+
) : PcControlConnection {
297+
override suspend fun sendCommand(command: PcControlCommand): PcCommandResult {
296298
return runCatching {
297299
val requestId = nextRequestId()
298300
webSocketSession.send(
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.enaboapps.switchify.screens.pc
2+
3+
enum class PcControlSurface {
4+
Mouse,
5+
Typing
6+
}

app/src/main/java/com/enaboapps/switchify/screens/pc/PcMouseCommandGrid.kt

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,21 @@ import androidx.compose.ui.text.style.TextAlign
3333
import androidx.compose.ui.text.style.TextOverflow
3434
import androidx.compose.ui.unit.dp
3535
import com.enaboapps.switchify.R
36-
import com.enaboapps.switchify.pc.PcMouseCommand
36+
import com.enaboapps.switchify.pc.PcControlCommand
3737

3838
data class PcMouseControlSpec(
3939
@param:StringRes val labelResId: Int,
40-
val command: PcMouseCommand
40+
val command: PcControlCommand
4141
)
4242

4343
@Composable
44-
fun PcMouseCommandGrid(
44+
fun PcControlCommandGrid(
4545
connected: Boolean,
4646
movementStep: Int,
47-
onCommandSelected: (PcMouseCommand) -> Unit,
47+
onCommandSelected: (PcControlCommand) -> Unit,
4848
modifier: Modifier = Modifier
4949
) {
50-
PcMouseCommandSections(
50+
PcControlCommandSections(
5151
connected = connected,
5252
movementStep = movementStep,
5353
onCommandSelected = onCommandSelected,
@@ -101,10 +101,10 @@ fun PcMovementSizeSection(
101101
}
102102

103103
@Composable
104-
fun PcMouseCommandSections(
104+
fun PcControlCommandSections(
105105
connected: Boolean,
106106
movementStep: Int,
107-
onCommandSelected: (PcMouseCommand) -> Unit,
107+
onCommandSelected: (PcControlCommand) -> Unit,
108108
modifier: Modifier = Modifier
109109
) {
110110
Column(
@@ -146,9 +146,9 @@ fun PcTypingCommandSection(
146146
modifier = Modifier.fillMaxWidth(),
147147
horizontalArrangement = Arrangement.spacedBy(10.dp)
148148
) {
149-
PcCommandTile(
149+
PcScannedCommandTile(
150150
labelResId = R.string.pc_typing_type_text,
151-
connected = connected,
151+
enabled = connected,
152152
onClick = onOpenTyping,
153153
minHeightDp = 72,
154154
modifier = Modifier.weight(1f)
@@ -163,7 +163,7 @@ fun PcTypingCommandSection(
163163
private fun PcMovementCommandSection(
164164
connected: Boolean,
165165
movementStep: Int,
166-
onCommandSelected: (PcMouseCommand) -> Unit
166+
onCommandSelected: (PcControlCommand) -> Unit
167167
) {
168168
val controls = pcMovementControlSpecs(movementStep)
169169
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
@@ -208,7 +208,7 @@ private fun PcButtonCommandSection(
208208
@StringRes titleResId: Int,
209209
specs: List<PcMouseControlSpec>,
210210
connected: Boolean,
211-
onCommandSelected: (PcMouseCommand) -> Unit
211+
onCommandSelected: (PcControlCommand) -> Unit
212212
) {
213213
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
214214
PcCommandSectionTitle(titleResId)
@@ -234,7 +234,7 @@ private fun PcCommandSectionTitle(@StringRes titleResId: Int) {
234234
private fun PcCommandButtonRow(
235235
specs: List<PcMouseControlSpec>,
236236
connected: Boolean,
237-
onCommandSelected: (PcMouseCommand) -> Unit,
237+
onCommandSelected: (PcControlCommand) -> Unit,
238238
minHeightDp: Int
239239
) {
240240
Row(
@@ -257,52 +257,60 @@ private fun PcCommandButtonRow(
257257
private fun PcCommandButton(
258258
spec: PcMouseControlSpec,
259259
connected: Boolean,
260-
onCommandSelected: (PcMouseCommand) -> Unit,
260+
onCommandSelected: (PcControlCommand) -> Unit,
261261
minHeightDp: Int,
262262
modifier: Modifier = Modifier
263263
) {
264-
PcCommandTile(
264+
PcScannedCommandTile(
265265
labelResId = spec.labelResId,
266-
connected = connected,
266+
enabled = connected,
267267
onClick = { onCommandSelected(spec.command) },
268268
minHeightDp = minHeightDp,
269269
modifier = modifier
270270
)
271271
}
272272

273273
@Composable
274-
private fun PcCommandTile(
274+
fun PcScannedCommandTile(
275275
@StringRes labelResId: Int,
276-
connected: Boolean,
276+
enabled: Boolean,
277277
onClick: () -> Unit,
278-
minHeightDp: Int,
279-
modifier: Modifier = Modifier
278+
modifier: Modifier = Modifier,
279+
minHeightDp: Int = 72,
280+
square: Boolean = true
280281
) {
281282
val interactionSource = remember { MutableInteractionSource() }
282283
val pressed by interactionSource.collectIsPressedAsState()
283284
val backgroundColor = when {
284-
!connected -> MaterialTheme.colorScheme.surfaceVariant
285+
!enabled -> MaterialTheme.colorScheme.surfaceVariant
285286
pressed -> MaterialTheme.colorScheme.primaryContainer
286287
else -> MaterialTheme.colorScheme.surface
287288
}
288-
val borderColor = if (connected) {
289+
val borderColor = if (enabled) {
289290
MaterialTheme.colorScheme.outline
290291
} else {
291292
MaterialTheme.colorScheme.outlineVariant
292293
}
293-
val contentColor = if (connected) {
294+
val contentColor = if (enabled) {
294295
MaterialTheme.colorScheme.onSurface
295296
} else {
296297
MaterialTheme.colorScheme.onSurfaceVariant
297298
}
298-
299-
Surface(
300-
modifier = modifier
299+
val tileModifier = if (square) {
300+
modifier
301301
.fillMaxWidth()
302302
.aspectRatio(1f)
303303
.heightIn(min = minHeightDp.dp)
304+
} else {
305+
modifier
306+
.fillMaxWidth()
307+
.heightIn(min = minHeightDp.dp)
308+
}
309+
310+
Surface(
311+
modifier = tileModifier
304312
.clickable(
305-
enabled = connected,
313+
enabled = enabled,
306314
role = Role.Button,
307315
interactionSource = interactionSource,
308316
indication = null,
@@ -367,29 +375,29 @@ fun pcMouseControlSpecs(moveStep: Int): List<PcMouseControlSpec> {
367375
fun pcMovementControlSpecs(moveStep: Int): List<PcMouseControlSpec> {
368376
val step = moveStep.coerceAtLeast(1)
369377
return listOf(
370-
PcMouseControlSpec(R.string.pc_mouse_up_left, PcMouseCommand.Move(-step, -step)),
371-
PcMouseControlSpec(R.string.pc_mouse_up, PcMouseCommand.Move(0, -step)),
372-
PcMouseControlSpec(R.string.pc_mouse_up_right, PcMouseCommand.Move(step, -step)),
373-
PcMouseControlSpec(R.string.pc_mouse_left, PcMouseCommand.Move(-step, 0)),
374-
PcMouseControlSpec(R.string.pc_mouse_right, PcMouseCommand.Move(step, 0)),
375-
PcMouseControlSpec(R.string.pc_mouse_down_left, PcMouseCommand.Move(-step, step)),
376-
PcMouseControlSpec(R.string.pc_mouse_down, PcMouseCommand.Move(0, step)),
377-
PcMouseControlSpec(R.string.pc_mouse_down_right, PcMouseCommand.Move(step, step))
378+
PcMouseControlSpec(R.string.pc_mouse_up_left, PcControlCommand.Move(-step, -step)),
379+
PcMouseControlSpec(R.string.pc_mouse_up, PcControlCommand.Move(0, -step)),
380+
PcMouseControlSpec(R.string.pc_mouse_up_right, PcControlCommand.Move(step, -step)),
381+
PcMouseControlSpec(R.string.pc_mouse_left, PcControlCommand.Move(-step, 0)),
382+
PcMouseControlSpec(R.string.pc_mouse_right, PcControlCommand.Move(step, 0)),
383+
PcMouseControlSpec(R.string.pc_mouse_down_left, PcControlCommand.Move(-step, step)),
384+
PcMouseControlSpec(R.string.pc_mouse_down, PcControlCommand.Move(0, step)),
385+
PcMouseControlSpec(R.string.pc_mouse_down_right, PcControlCommand.Move(step, step))
378386
)
379387
}
380388

381389
fun pcClickControlSpecs(): List<PcMouseControlSpec> {
382390
return listOf(
383-
PcMouseControlSpec(R.string.pc_mouse_click, PcMouseCommand.LeftClick),
384-
PcMouseControlSpec(R.string.pc_mouse_double_click, PcMouseCommand.DoubleClick),
385-
PcMouseControlSpec(R.string.pc_mouse_right_click, PcMouseCommand.RightClick)
391+
PcMouseControlSpec(R.string.pc_mouse_click, PcControlCommand.LeftClick),
392+
PcMouseControlSpec(R.string.pc_mouse_double_click, PcControlCommand.DoubleClick),
393+
PcMouseControlSpec(R.string.pc_mouse_right_click, PcControlCommand.RightClick)
386394
)
387395
}
388396

389397
fun pcScrollControlSpecs(): List<PcMouseControlSpec> {
390398
val scrollStep = 5
391399
return listOf(
392-
PcMouseControlSpec(R.string.pc_mouse_scroll_up, PcMouseCommand.Scroll(0, scrollStep)),
393-
PcMouseControlSpec(R.string.pc_mouse_scroll_down, PcMouseCommand.Scroll(0, -scrollStep))
400+
PcMouseControlSpec(R.string.pc_mouse_scroll_up, PcControlCommand.Scroll(0, scrollStep)),
401+
PcMouseControlSpec(R.string.pc_mouse_scroll_down, PcControlCommand.Scroll(0, -scrollStep))
394402
)
395403
}

0 commit comments

Comments
 (0)