Skip to content

Commit 6f70bd2

Browse files
authored
Add PC text editing shortcuts (#2576)
- Add Ctrl+A, Ctrl+C, and Ctrl+X shortcut keys to the PC shortcut enum - Add Select all, Copy, and Cut tiles to the PC Window controls surface - Cover shortcut ordering, realtime sending, and Bluetooth serialization 🤖 Auto-generated
1 parent 3765bc6 commit 6f70bd2

6 files changed

Lines changed: 140 additions & 1 deletion

File tree

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,9 @@ enum class PcKeyboardKey(
3737
}
3838

3939
enum class PcKeyboardShortcutKey(val protocolValue: String) {
40-
Meta("Meta")
40+
Meta("Meta"),
41+
Ctrl("Ctrl"),
42+
A("A"),
43+
C("C"),
44+
X("X")
4145
}

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,24 @@ fun PcWindowControlScreen(
6161
}
6262
}
6363
)
64+
Text(
65+
text = stringResource(R.string.pc_window_section_shortcuts),
66+
style = MaterialTheme.typography.titleMedium,
67+
color = MaterialTheme.colorScheme.onSurface
68+
)
69+
PcCompactCommandGrid(
70+
columns = 3,
71+
minTileHeightDp = 52,
72+
cells = pcWindowShortcutSpecs().map { spec ->
73+
PcCompactCommandCell(
74+
labelResId = spec.labelResId,
75+
enabled = enabled,
76+
onClick = { onCommandSelected(spec.command) },
77+
icon = spec.icon,
78+
tone = spec.tone
79+
)
80+
}
81+
)
6482
PcKeyboardNavigationCluster(
6583
enabled = enabled,
6684
onKeySelected = { key ->
@@ -74,6 +92,23 @@ fun pcWindowCompactControlSpecs(): List<PcWindowControlSpec?> {
7492
return pcWindowControlSpecs() + listOf(null)
7593
}
7694

95+
fun pcWindowShortcutSpecs(): List<PcWindowControlSpec> {
96+
return listOf(
97+
PcWindowControlSpec(
98+
R.string.pc_shortcut_select_all,
99+
PcControlCommand.KeyboardShortcut(listOf(PcKeyboardShortcutKey.Ctrl, PcKeyboardShortcutKey.A))
100+
),
101+
PcWindowControlSpec(
102+
R.string.pc_shortcut_copy,
103+
PcControlCommand.KeyboardShortcut(listOf(PcKeyboardShortcutKey.Ctrl, PcKeyboardShortcutKey.C))
104+
),
105+
PcWindowControlSpec(
106+
R.string.pc_shortcut_cut,
107+
PcControlCommand.KeyboardShortcut(listOf(PcKeyboardShortcutKey.Ctrl, PcKeyboardShortcutKey.X))
108+
)
109+
)
110+
}
111+
77112
fun pcWindowControlSpecs(): List<PcWindowControlSpec> {
78113
return listOf(
79114
PcWindowControlSpec(

app/src/main/res/values/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,10 @@
391391
<string name="pc_window_minimize">Minimize</string>
392392
<string name="pc_window_maximize">Maximize</string>
393393
<string name="pc_window_close">Close app</string>
394+
<string name="pc_window_section_shortcuts">Shortcuts</string>
395+
<string name="pc_shortcut_select_all">Select all</string>
396+
<string name="pc_shortcut_copy">Copy</string>
397+
<string name="pc_shortcut_cut">Cut</string>
394398
<string name="pc_key_start">Start</string>
395399
<string name="pc_key_backspace">Backspace</string>
396400
<string name="pc_key_delete">Delete</string>

app/src/test/java/com/enaboapps/switchify/pc/bluetooth/SwitchifyPcBleClientTest.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,49 @@ class SwitchifyPcBleClientTest {
273273
assertEquals("Meta", messages.single().getJSONObject("payload").getString("key"))
274274
}
275275

276+
@Test
277+
fun sendsEditingShortcutsAsKeyboardShortcutCommands() = runTest {
278+
val tokens = FakeTokenStore(mutableMapOf("desktop-1" to "token"), mutableMapOf("desktop-1" to "Switchify PC"))
279+
val messages = mutableListOf<JSONObject>()
280+
val transport = FakeTransportFactory { message ->
281+
val json = JSONObject(message)
282+
messages += json
283+
ack(json.getString("id"))
284+
}
285+
val client: PcConnector = client(tokens, transport)
286+
val session = PcAuthenticatedSession("desktop-1", "device-1", "AA:BB:CC:DD:EE:FF", PcTransport.Bluetooth)
287+
288+
assertEquals(
289+
PcCommandResult.Ack,
290+
client.sendCommand(
291+
session,
292+
PcControlCommand.KeyboardShortcut(listOf(PcKeyboardShortcutKey.Ctrl, PcKeyboardShortcutKey.A))
293+
)
294+
)
295+
assertEquals(
296+
PcCommandResult.Ack,
297+
client.sendCommand(
298+
session,
299+
PcControlCommand.KeyboardShortcut(listOf(PcKeyboardShortcutKey.Ctrl, PcKeyboardShortcutKey.C))
300+
)
301+
)
302+
assertEquals(
303+
PcCommandResult.Ack,
304+
client.sendCommand(
305+
session,
306+
PcControlCommand.KeyboardShortcut(listOf(PcKeyboardShortcutKey.Ctrl, PcKeyboardShortcutKey.X))
307+
)
308+
)
309+
310+
assertEquals(listOf("keyboard.shortcut", "keyboard.shortcut", "keyboard.shortcut"), messages.map { it.getString("type") })
311+
assertEquals("Ctrl", messages[0].getJSONObject("payload").getJSONArray("keys").getString(0))
312+
assertEquals("A", messages[0].getJSONObject("payload").getJSONArray("keys").getString(1))
313+
assertEquals("Ctrl", messages[1].getJSONObject("payload").getJSONArray("keys").getString(0))
314+
assertEquals("C", messages[1].getJSONObject("payload").getJSONArray("keys").getString(1))
315+
assertEquals("Ctrl", messages[2].getJSONObject("payload").getJSONArray("keys").getString(0))
316+
assertEquals("X", messages[2].getJSONObject("payload").getJSONArray("keys").getString(1))
317+
}
318+
276319
@Test
277320
fun realtimeMoveFallsBackWhenPointerProfileDoesNotAdvertiseNoAck() = runTest {
278321
val tokens = FakeTokenStore(mutableMapOf("desktop-1" to "token"), mutableMapOf("desktop-1" to "Switchify PC"))

app/src/test/java/com/enaboapps/switchify/screens/pc/PcMouseControlViewModelTest.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,24 @@ class PcMouseControlViewModelTest {
10241024
assertNull(viewModel.uiState.value.message)
10251025
}
10261026

1027+
@Test
1028+
fun windowSurfaceCanSendEditingShortcutCommand() = runTest(dispatcher) {
1029+
val connector = FakeConnector()
1030+
val controller = connectedController(connector = connector)
1031+
val viewModel = viewModel(controller)
1032+
val command = PcControlCommand.KeyboardShortcut(listOf(PcKeyboardShortcutKey.Ctrl, PcKeyboardShortcutKey.C))
1033+
1034+
viewModel.selectControlSurface(PcControlSurface.Window)
1035+
viewModel.send(command)
1036+
advanceUntilIdle()
1037+
1038+
assertEquals(listOf(command), connector.realtimeCommands)
1039+
assertTrue(connector.commands.isEmpty())
1040+
assertEquals(PcControlSurface.Window, viewModel.uiState.value.activeSurface)
1041+
assertFalse(viewModel.uiState.value.isBusy)
1042+
assertNull(viewModel.uiState.value.message)
1043+
}
1044+
10271045
@Test
10281046
fun controlCommandDoesNotSetBusy() = runTest(dispatcher) {
10291047
val connector = FakeConnector()

app/src/test/java/com/enaboapps/switchify/screens/pc/PcWindowControlScreenTest.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,41 @@ class PcWindowControlScreenTest {
6161
assertEquals(null, specs[8])
6262
}
6363

64+
@Test
65+
fun windowShortcutCommandsUseStableOrder() {
66+
val commands = pcWindowShortcutSpecs().map { it.command }
67+
68+
assertEquals(
69+
listOf(
70+
PcControlCommand.KeyboardShortcut(listOf(PcKeyboardShortcutKey.Ctrl, PcKeyboardShortcutKey.A)),
71+
PcControlCommand.KeyboardShortcut(listOf(PcKeyboardShortcutKey.Ctrl, PcKeyboardShortcutKey.C)),
72+
PcControlCommand.KeyboardShortcut(listOf(PcKeyboardShortcutKey.Ctrl, PcKeyboardShortcutKey.X))
73+
),
74+
commands
75+
)
76+
}
77+
78+
@Test
79+
fun windowShortcutLabelsMatchActions() {
80+
val specs = pcWindowShortcutSpecs()
81+
82+
assertEquals(R.string.pc_shortcut_select_all, specs[0].labelResId)
83+
assertEquals(
84+
PcControlCommand.KeyboardShortcut(listOf(PcKeyboardShortcutKey.Ctrl, PcKeyboardShortcutKey.A)),
85+
specs[0].command
86+
)
87+
assertEquals(R.string.pc_shortcut_copy, specs[1].labelResId)
88+
assertEquals(
89+
PcControlCommand.KeyboardShortcut(listOf(PcKeyboardShortcutKey.Ctrl, PcKeyboardShortcutKey.C)),
90+
specs[1].command
91+
)
92+
assertEquals(R.string.pc_shortcut_cut, specs[2].labelResId)
93+
assertEquals(
94+
PcControlCommand.KeyboardShortcut(listOf(PcKeyboardShortcutKey.Ctrl, PcKeyboardShortcutKey.X)),
95+
specs[2].command
96+
)
97+
}
98+
6499
@Test
65100
fun closeWindowCommandUsesDestructiveTone() {
66101
val specs = pcWindowControlSpecs()

0 commit comments

Comments
 (0)