Skip to content

Commit 4fb2fda

Browse files
Add PC switching to control screen (#2559)
- Add a compact bottom strip for switching PCs from the control screen - Discover nearby paired PCs and connect selected PCs in-place - Stop mouse repeat and reset drag state before changing PCs - Cover chooser discovery, switching, failure, approval, and cleanup behavior 🤖 Auto-generated Co-authored-by: Owen McGirr <o.a.mcgirr@gmail.com>
1 parent f0b0ead commit 4fb2fda

5 files changed

Lines changed: 663 additions & 11 deletions

File tree

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
package com.enaboapps.switchify.screens.pc
2+
3+
import androidx.compose.foundation.BorderStroke
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.Row
7+
import androidx.compose.foundation.layout.Spacer
8+
import androidx.compose.foundation.layout.fillMaxWidth
9+
import androidx.compose.foundation.layout.heightIn
10+
import androidx.compose.foundation.layout.navigationBarsPadding
11+
import androidx.compose.foundation.layout.padding
12+
import androidx.compose.foundation.layout.size
13+
import androidx.compose.foundation.layout.width
14+
import androidx.compose.foundation.rememberScrollState
15+
import androidx.compose.foundation.verticalScroll
16+
import androidx.compose.material.icons.Icons
17+
import androidx.compose.material.icons.rounded.Computer
18+
import androidx.compose.material3.AlertDialog
19+
import androidx.compose.material3.Button
20+
import androidx.compose.material3.Icon
21+
import androidx.compose.material3.LinearProgressIndicator
22+
import androidx.compose.material3.MaterialTheme
23+
import androidx.compose.material3.OutlinedButton
24+
import androidx.compose.material3.Surface
25+
import androidx.compose.material3.Text
26+
import androidx.compose.material3.TextButton
27+
import androidx.compose.runtime.Composable
28+
import androidx.compose.ui.Alignment
29+
import androidx.compose.ui.Modifier
30+
import androidx.compose.ui.res.stringResource
31+
import androidx.compose.ui.text.font.FontFamily
32+
import androidx.compose.ui.text.style.TextAlign
33+
import androidx.compose.ui.text.style.TextOverflow
34+
import androidx.compose.ui.unit.dp
35+
import com.enaboapps.switchify.R
36+
37+
@Composable
38+
fun PcControlPcSwitchStrip(
39+
connectedDisplayName: String?,
40+
enabled: Boolean,
41+
isDiscovering: Boolean,
42+
switching: Boolean,
43+
onSwitchClick: () -> Unit
44+
) {
45+
Surface(
46+
modifier = Modifier
47+
.fillMaxWidth()
48+
.navigationBarsPadding(),
49+
color = MaterialTheme.colorScheme.surface,
50+
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant)
51+
) {
52+
Row(
53+
modifier = Modifier
54+
.fillMaxWidth()
55+
.heightIn(min = 64.dp)
56+
.padding(horizontal = 16.dp, vertical = 8.dp),
57+
verticalAlignment = Alignment.CenterVertically
58+
) {
59+
Icon(
60+
imageVector = Icons.Rounded.Computer,
61+
contentDescription = null,
62+
modifier = Modifier.size(24.dp),
63+
tint = MaterialTheme.colorScheme.primary
64+
)
65+
Spacer(modifier = Modifier.width(12.dp))
66+
Column(
67+
modifier = Modifier.weight(1f),
68+
verticalArrangement = Arrangement.Center
69+
) {
70+
Text(
71+
text = stringResource(R.string.pc_control_current_pc),
72+
style = MaterialTheme.typography.labelMedium,
73+
color = MaterialTheme.colorScheme.onSurfaceVariant,
74+
maxLines = 1,
75+
overflow = TextOverflow.Ellipsis
76+
)
77+
Text(
78+
text = connectedDisplayName ?: stringResource(R.string.pc_control_no_pc_connected),
79+
style = MaterialTheme.typography.bodyMedium,
80+
color = MaterialTheme.colorScheme.onSurface,
81+
maxLines = 1,
82+
overflow = TextOverflow.Ellipsis
83+
)
84+
if (isDiscovering || switching) {
85+
Text(
86+
text = stringResource(R.string.pc_control_switch_pc_searching),
87+
style = MaterialTheme.typography.labelSmall,
88+
color = MaterialTheme.colorScheme.onSurfaceVariant,
89+
maxLines = 1,
90+
overflow = TextOverflow.Ellipsis
91+
)
92+
}
93+
}
94+
Spacer(modifier = Modifier.width(12.dp))
95+
Button(
96+
onClick = onSwitchClick,
97+
enabled = enabled && !switching
98+
) {
99+
Text(stringResource(R.string.pc_control_switch_pc))
100+
}
101+
}
102+
}
103+
}
104+
105+
@Composable
106+
fun PcSwitchPcDialog(
107+
rows: List<PcSwitchRowState>,
108+
isDiscovering: Boolean,
109+
switchingDesktopId: String?,
110+
onDismiss: () -> Unit,
111+
onRefresh: () -> Unit,
112+
onPcSelected: (String) -> Unit
113+
) {
114+
AlertDialog(
115+
onDismissRequest = onDismiss,
116+
confirmButton = {
117+
TextButton(onClick = onRefresh, enabled = !isDiscovering && switchingDesktopId == null) {
118+
Text(stringResource(R.string.pc_control_switch_pc_refresh))
119+
}
120+
},
121+
dismissButton = {
122+
TextButton(onClick = onDismiss) {
123+
Text(stringResource(R.string.cancel))
124+
}
125+
},
126+
title = { Text(stringResource(R.string.pc_control_switch_pc_title)) },
127+
text = {
128+
Column(
129+
modifier = Modifier
130+
.fillMaxWidth()
131+
.verticalScroll(rememberScrollState()),
132+
verticalArrangement = Arrangement.spacedBy(8.dp)
133+
) {
134+
if (isDiscovering) {
135+
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
136+
Text(
137+
text = stringResource(R.string.pc_control_switch_pc_searching),
138+
style = MaterialTheme.typography.bodyMedium,
139+
color = MaterialTheme.colorScheme.onSurfaceVariant
140+
)
141+
} else if (rows.isEmpty()) {
142+
Text(
143+
text = stringResource(R.string.pc_control_switch_pc_empty),
144+
style = MaterialTheme.typography.bodyMedium,
145+
color = MaterialTheme.colorScheme.onSurfaceVariant
146+
)
147+
} else {
148+
rows.forEach { row ->
149+
PcSwitchPcRow(
150+
row = row,
151+
switching = switchingDesktopId == row.desktopId,
152+
onPcSelected = onPcSelected
153+
)
154+
}
155+
}
156+
}
157+
}
158+
)
159+
}
160+
161+
@Composable
162+
private fun PcSwitchPcRow(
163+
row: PcSwitchRowState,
164+
switching: Boolean,
165+
onPcSelected: (String) -> Unit
166+
) {
167+
Surface(
168+
modifier = Modifier.fillMaxWidth(),
169+
shape = MaterialTheme.shapes.small,
170+
color = if (row.connected) {
171+
MaterialTheme.colorScheme.primaryContainer
172+
} else {
173+
MaterialTheme.colorScheme.surface
174+
},
175+
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant)
176+
) {
177+
Row(
178+
modifier = Modifier
179+
.fillMaxWidth()
180+
.padding(12.dp),
181+
verticalAlignment = Alignment.CenterVertically
182+
) {
183+
Column(modifier = Modifier.weight(1f)) {
184+
Text(
185+
text = row.displayName,
186+
style = MaterialTheme.typography.bodyLarge,
187+
color = MaterialTheme.colorScheme.onSurface,
188+
maxLines = 1,
189+
overflow = TextOverflow.Ellipsis
190+
)
191+
if (row.summary.isNotBlank()) {
192+
Text(
193+
text = row.summary,
194+
style = MaterialTheme.typography.bodySmall,
195+
color = MaterialTheme.colorScheme.onSurfaceVariant,
196+
maxLines = 1,
197+
overflow = TextOverflow.Ellipsis
198+
)
199+
}
200+
}
201+
Spacer(modifier = Modifier.width(12.dp))
202+
if (row.connected) {
203+
Text(
204+
text = stringResource(R.string.pc_control_switch_pc_connected),
205+
style = MaterialTheme.typography.labelLarge,
206+
color = MaterialTheme.colorScheme.primary
207+
)
208+
} else {
209+
OutlinedButton(
210+
onClick = { onPcSelected(row.desktopId) },
211+
enabled = row.enabled && !switching
212+
) {
213+
Text(stringResource(R.string.pc_connection_connect))
214+
}
215+
}
216+
}
217+
}
218+
}
219+
220+
@Composable
221+
fun PcSwitchPcApprovalDialog(approvalCode: com.enaboapps.switchify.pc.PcApprovalCodeState) {
222+
AlertDialog(
223+
onDismissRequest = {},
224+
confirmButton = {},
225+
title = { Text(stringResource(R.string.pc_pairing_code_title)) },
226+
text = {
227+
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
228+
Text(
229+
text = stringResource(R.string.pc_pairing_code_pc_name, approvalCode.pcName),
230+
style = MaterialTheme.typography.bodyMedium,
231+
color = MaterialTheme.colorScheme.onSurfaceVariant
232+
)
233+
Text(
234+
text = stringResource(R.string.pc_pairing_code_message),
235+
style = MaterialTheme.typography.bodyMedium
236+
)
237+
Text(
238+
text = approvalCode.verificationCode,
239+
modifier = Modifier
240+
.fillMaxWidth()
241+
.padding(vertical = 8.dp),
242+
style = MaterialTheme.typography.headlineLarge,
243+
fontFamily = FontFamily.Monospace,
244+
textAlign = TextAlign.Center
245+
)
246+
Text(
247+
text = stringResource(R.string.pc_pairing_code_waiting),
248+
style = MaterialTheme.typography.bodyMedium,
249+
color = MaterialTheme.colorScheme.onSurfaceVariant
250+
)
251+
}
252+
}
253+
)
254+
}

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,15 @@ private fun PcMouseControlScreen(
116116
showBackButton = true,
117117
onBackPressed = onClose
118118
)
119+
},
120+
bottomBar = {
121+
PcControlPcSwitchStrip(
122+
connectedDisplayName = uiState.connectedDisplayName,
123+
enabled = !uiState.isBusy,
124+
isDiscovering = uiState.isDiscoveringSwitchPcs,
125+
switching = uiState.switchingDesktopId != null,
126+
onSwitchClick = viewModel::openSwitchPcChooser
127+
)
119128
}
120129
) { paddingValues ->
121130
Surface(
@@ -179,4 +188,19 @@ private fun PcMouseControlScreen(
179188
}
180189
}
181190
}
191+
192+
if (uiState.switchPcChooserVisible) {
193+
PcSwitchPcDialog(
194+
rows = uiState.switchPcRows,
195+
isDiscovering = uiState.isDiscoveringSwitchPcs,
196+
switchingDesktopId = uiState.switchingDesktopId,
197+
onDismiss = viewModel::dismissSwitchPcChooser,
198+
onRefresh = viewModel::refreshSwitchPcChoices,
199+
onPcSelected = viewModel::switchToPc
200+
)
201+
}
202+
203+
uiState.switchPcApprovalCode?.let { approvalCode ->
204+
PcSwitchPcApprovalDialog(approvalCode)
205+
}
182206
}

0 commit comments

Comments
 (0)