Skip to content

Commit 4bb5491

Browse files
authored
Merge pull request #2596 from switchifyapp/feature/pc-default-picker-last-connection-2595
Add PC default picker with last connection option
2 parents 8a312c7 + f951a54 commit 4bb5491

10 files changed

Lines changed: 505 additions & 62 deletions

File tree

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

Lines changed: 91 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ data class PcConnectionUiState(
2626
val permissionRequired: Boolean = false,
2727
val discoveryStatus: PcDiscoveryStatus = PcDiscoveryStatus.Empty,
2828
val discoveryStatusText: String = "Searching for Switchify PC...",
29+
val defaultPreference: PcDefaultPcPreference = PcDefaultPcPreference.LastConnection,
30+
val lastConnectedDesktopId: String? = null,
31+
val defaultPcChoices: List<PcDefaultPcChoice> = emptyList(),
2932
val pcRows: List<PcConnectionRowState> = emptyList(),
3033
val discoveredPcs: List<PcRowState> = emptyList(),
3134
val savedPairings: List<PcSavedPairingRowState> = emptyList(),
@@ -37,6 +40,12 @@ data class PcConnectionUiState(
3740
val pendingUnpair: PcUnpairConfirmationState? = null
3841
)
3942

43+
data class PcDefaultPcChoice(
44+
val preference: PcDefaultPcPreference,
45+
val title: String,
46+
val description: String
47+
)
48+
4049
data class PcRowState(
4150
val pc: DiscoveredPc,
4251
val title: String,
@@ -129,11 +138,18 @@ class PcConnectionViewModel(
129138
val hasTokenByDesktopId = withContext(backgroundDispatcher) {
130139
inputs.pcs.associate { pc -> pc.desktopId to !tokenStore.getToken(pc.desktopId).isNullOrBlank() }
131140
}
141+
val allPairings = withContext(backgroundDispatcher) {
142+
tokenStore.listPairings()
143+
}
132144
val savedPairings = withContext(backgroundDispatcher) {
133-
savedPairings(discoveredDesktopIds)
145+
savedPairings(allPairings, discoveredDesktopIds)
146+
}
147+
val defaultPreference = withContext(backgroundDispatcher) {
148+
tokenStore.getDefaultPcPreference()
134149
}
135-
val defaultDesktopId = withContext(backgroundDispatcher) {
136-
tokenStore.getDefaultDesktopId()
150+
val defaultDesktopId = (defaultPreference as? PcDefaultPcPreference.SpecificPc)?.desktopId
151+
val lastConnectedDesktopId = withContext(backgroundDispatcher) {
152+
tokenStore.getLastConnectedDesktopId()
137153
}
138154
val pcRows = buildPcRows(
139155
pcs = inputs.pcs,
@@ -148,6 +164,9 @@ class PcConnectionViewModel(
148164
current.copy(
149165
discoveryStatus = inputs.status,
150166
discoveryStatusText = discoveryStatusText(inputs.status, inputs.pcs.isEmpty()),
167+
defaultPreference = defaultPreference,
168+
lastConnectedDesktopId = lastConnectedDesktopId,
169+
defaultPcChoices = buildDefaultPcChoices(allPairings, inputs.pcs, lastConnectedDesktopId),
151170
pcRows = pcRows,
152171
discoveredPcs = inputs.pcs.map { pc ->
153172
rowState(
@@ -252,17 +271,38 @@ class PcConnectionViewModel(
252271
}
253272

254273
fun setDefaultPc(desktopId: String, displayName: String) {
255-
if (tokenStore.getToken(desktopId).isNullOrBlank()) return
256-
tokenStore.setDefaultDesktopId(desktopId)
257-
tokenRevision.update { it + 1 }
258-
_uiState.update {
259-
it.copy(message = "Default PC set to $displayName.")
260-
}
274+
setDefaultPcPreference(PcDefaultPcPreference.SpecificPc(desktopId), displayName)
261275
}
262276

263277
fun clearDefaultPc() {
264-
tokenStore.clearDefaultDesktopId()
278+
setDefaultPcPreference(PcDefaultPcPreference.LastConnection)
279+
}
280+
281+
fun setDefaultPcPreference(preference: PcDefaultPcPreference) {
282+
val displayName = (preference as? PcDefaultPcPreference.SpecificPc)?.desktopId?.let { desktopId ->
283+
_uiState.value.pcRows.firstOrNull { it.desktopId == desktopId }?.title
284+
?: tokenStore.getServiceName(desktopId)
285+
?: desktopId
286+
}
287+
setDefaultPcPreference(preference, displayName)
288+
}
289+
290+
private fun setDefaultPcPreference(
291+
preference: PcDefaultPcPreference,
292+
displayName: String? = null
293+
) {
294+
if (preference is PcDefaultPcPreference.SpecificPc && tokenStore.getToken(preference.desktopId).isNullOrBlank()) {
295+
return
296+
}
297+
tokenStore.setDefaultPcPreference(preference)
265298
tokenRevision.update { it + 1 }
299+
_uiState.update {
300+
val message = when (preference) {
301+
PcDefaultPcPreference.LastConnection -> "Default set to last connection."
302+
is PcDefaultPcPreference.SpecificPc -> "Default PC set to ${displayName ?: preference.desktopId}."
303+
}
304+
it.copy(message = message)
305+
}
266306
}
267307

268308
fun stopPcBluetooth() {
@@ -309,7 +349,7 @@ class PcConnectionViewModel(
309349
}
310350
return PcRowState(
311351
pc = pc,
312-
title = pc.displayName,
352+
title = pc.controlDeviceName,
313353
summary = summary,
314354
actionText = actionText,
315355
enabled = !connected && !isBusy,
@@ -320,8 +360,11 @@ class PcConnectionViewModel(
320360
)
321361
}
322362

323-
private fun savedPairings(discoveredDesktopIds: Set<String>): List<PcSavedPairingRowState> {
324-
return tokenStore.listPairings()
363+
private fun savedPairings(
364+
pairings: List<PcStoredPairing>,
365+
discoveredDesktopIds: Set<String>
366+
): List<PcSavedPairingRowState> {
367+
return pairings
325368
.filterNot { it.desktopId in discoveredDesktopIds }
326369
.map { pairing ->
327370
PcSavedPairingRowState(
@@ -334,6 +377,33 @@ class PcConnectionViewModel(
334377
}
335378
}
336379

380+
private fun buildDefaultPcChoices(
381+
pairings: List<PcStoredPairing>,
382+
discoveredPcs: List<DiscoveredPc>,
383+
lastConnectedDesktopId: String?
384+
): List<PcDefaultPcChoice> {
385+
val discoveredNames = discoveredPcs.associate { it.desktopId to it.controlDeviceName }
386+
val lastConnectionDisplayName = pairings.firstOrNull {
387+
it.desktopId == lastConnectedDesktopId
388+
}?.let { pairing -> discoveredNames[pairing.desktopId] ?: pairing.serviceName ?: pairing.desktopId }
389+
val lastConnectionDescription = lastConnectionDisplayName?.let {
390+
"Currently: $it"
391+
} ?: "Switchify will use the most recently connected PC."
392+
return listOf(
393+
PcDefaultPcChoice(
394+
preference = PcDefaultPcPreference.LastConnection,
395+
title = "Use last connection",
396+
description = lastConnectionDescription
397+
)
398+
) + pairings.map { pairing ->
399+
PcDefaultPcChoice(
400+
preference = PcDefaultPcPreference.SpecificPc(pairing.desktopId),
401+
title = discoveredNames[pairing.desktopId] ?: pairing.serviceName ?: pairing.desktopId,
402+
description = "Always connect to this PC when it is available."
403+
)
404+
}
405+
}
406+
337407
private fun buildPcRows(
338408
pcs: List<DiscoveredPc>,
339409
statuses: Map<String, PcRowStatus>,
@@ -383,7 +453,7 @@ class PcConnectionViewModel(
383453
}
384454
return PcConnectionRowState(
385455
desktopId = pc.desktopId,
386-
title = pc.displayName,
456+
title = pc.controlDeviceName,
387457
summary = summary,
388458
source = PcConnectionRowSource.Discovered,
389459
status = rowStatus,
@@ -440,7 +510,7 @@ class PcConnectionViewModel(
440510
desktopId = pc.desktopId,
441511
status = PcRowStatus.WaitingApproval,
442512
message = null,
443-
approvalCode = PcApprovalCodeState(pc.displayName, verificationCode)
513+
approvalCode = PcApprovalCodeState(pc.controlDeviceName, verificationCode)
444514
)
445515
when (val pairing = connector.requestApproval(pc, requestNonce)) {
446516
is PcPairingResult.Paired -> {
@@ -449,11 +519,12 @@ class PcConnectionViewModel(
449519
isAuthFailure = { it is PcPingResult.AuthFailed }
450520
)) {
451521
is PcPingResult.Connected -> {
452-
tokenStore.saveToken(pc.desktopId, pairing.token, ping.endpointId, pc.displayName)
522+
tokenStore.saveToken(pc.desktopId, pairing.token, ping.endpointId, pc.controlDeviceName)
523+
tokenStore.recordSuccessfulConnection(pc.desktopId)
453524
tokenRevision.update { it + 1 }
454525
PcConnectionStateHolder.setConnected(
455526
PcAuthenticatedSession(pc.desktopId, identityRepository.getDeviceId(), ping.endpointId),
456-
pc.displayName
527+
pc.controlDeviceName
457528
)
458529
setIdle(pc.desktopId, PcRowStatus.Connected, null)
459530
}
@@ -480,11 +551,12 @@ class PcConnectionViewModel(
480551
isAuthFailure = { it is PcPingResult.AuthFailed }
481552
)) {
482553
is PcPingResult.Connected -> {
483-
tokenStore.saveToken(pc.desktopId, token, result.endpointId, pc.displayName)
554+
tokenStore.saveToken(pc.desktopId, token, result.endpointId, pc.controlDeviceName)
555+
tokenStore.recordSuccessfulConnection(pc.desktopId)
484556
tokenRevision.update { it + 1 }
485557
PcConnectionStateHolder.setConnected(
486558
PcAuthenticatedSession(pc.desktopId, identityRepository.getDeviceId(), result.endpointId),
487-
pc.displayName
559+
pc.controlDeviceName
488560
)
489561
setIdle(pc.desktopId, PcRowStatus.Connected, null)
490562
}

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -253,12 +253,12 @@ class PcServiceConnectionController(
253253
deviceId = identityRepository.getDeviceId(),
254254
requestNonce = requestNonce
255255
)
256-
onWaitingForApproval(PcApprovalCodeState(pc.displayName, verificationCode))
256+
onWaitingForApproval(PcApprovalCodeState(pc.controlDeviceName, verificationCode))
257257
return when (val result = connector.requestApproval(pc, requestNonce)) {
258258
is PcPairingResult.Paired -> {
259-
tokenStore.saveToken(result.desktopId, result.token, result.endpointId, pc.displayName)
259+
tokenStore.saveToken(result.desktopId, result.token, result.endpointId, pc.controlDeviceName)
260260
val session = PcAuthenticatedSession(result.desktopId, identityRepository.getDeviceId(), result.endpointId)
261-
openLiveControlSession(session, pc.displayName, pc.controlDeviceName)
261+
openLiveControlSession(session, pc.controlDeviceName, pc.controlDeviceName)
262262
}
263263
is PcPairingResult.Failed -> PcServiceConnectResult.Failed(result.reason, result.message)
264264
}
@@ -274,9 +274,9 @@ class PcServiceConnectionController(
274274
isAuthFailure = { it is PcPingResult.AuthFailed }
275275
)) {
276276
is PcPingResult.Connected -> {
277-
tokenStore.saveToken(pc.desktopId, token, result.endpointId, pc.displayName)
277+
tokenStore.saveToken(pc.desktopId, token, result.endpointId, pc.controlDeviceName)
278278
val session = PcAuthenticatedSession(pc.desktopId, identityRepository.getDeviceId(), result.endpointId)
279-
openLiveControlSession(session, pc.displayName, pc.controlDeviceName)
279+
openLiveControlSession(session, pc.controlDeviceName, pc.controlDeviceName)
280280
}
281281
is PcPingResult.AuthFailed -> {
282282
PcConnectionStateHolder.setDisconnected()
@@ -335,6 +335,7 @@ class PcServiceConnectionController(
335335
liveDisplayName = displayName
336336
liveControlDeviceName = controlDeviceName
337337
pointerProfile = connection.pointerProfile
338+
tokenStore.recordSuccessfulConnection(session.desktopId)
338339
observeLiveConnection(connection, session)
339340
startLiveHeartbeatIfNeeded()
340341
_state.value = PcServiceConnectionState.Connected(session, displayName, pointerProfile)

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

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,28 @@ interface PcPairingTokenStore {
1010
fun listPairings(): List<PcStoredPairing>
1111
fun getLastEndpointId(desktopId: String): String?
1212
fun getServiceName(desktopId: String): String?
13+
fun getDefaultPcPreference(): PcDefaultPcPreference {
14+
return getDefaultDesktopId()?.let { PcDefaultPcPreference.SpecificPc(it) }
15+
?: PcDefaultPcPreference.LastConnection
16+
}
17+
fun setDefaultPcPreference(preference: PcDefaultPcPreference) {
18+
when (preference) {
19+
PcDefaultPcPreference.LastConnection -> clearDefaultDesktopId()
20+
is PcDefaultPcPreference.SpecificPc -> setDefaultDesktopId(preference.desktopId)
21+
}
22+
}
23+
fun getLastConnectedDesktopId(): String? = null
24+
fun recordSuccessfulConnection(desktopId: String) = Unit
1325
fun getDefaultDesktopId(): String?
1426
fun setDefaultDesktopId(desktopId: String)
1527
fun clearDefaultDesktopId()
1628
}
1729

30+
sealed class PcDefaultPcPreference {
31+
data object LastConnection : PcDefaultPcPreference()
32+
data class SpecificPc(val desktopId: String) : PcDefaultPcPreference()
33+
}
34+
1835
data class PcStoredPairing(
1936
val desktopId: String,
2037
val serviceName: String?,
@@ -55,6 +72,10 @@ class PcTokenStore(context: Context) : PcPairingTokenStore {
5572
remove(serviceNameKey(desktopId))
5673
if (preferences.getString(defaultDesktopIdKey, null) == desktopId) {
5774
remove(defaultDesktopIdKey)
75+
putString(defaultPreferenceModeKey, defaultPreferenceModeLastConnection)
76+
}
77+
if (preferences.getString(lastConnectedDesktopIdKey, null) == desktopId) {
78+
remove(lastConnectedDesktopIdKey)
5879
}
5980
}
6081
}
@@ -80,24 +101,68 @@ class PcTokenStore(context: Context) : PcPairingTokenStore {
80101
return preferences.getString(serviceNameKey(desktopId), null)?.takeIf { it.isNotBlank() }
81102
}
82103

83-
override fun getDefaultDesktopId(): String? {
84-
val desktopId = preferences.getString(defaultDesktopIdKey, null)?.takeIf { it.isNotBlank() } ?: return null
104+
override fun getDefaultPcPreference(): PcDefaultPcPreference {
105+
val mode = preferences.getString(defaultPreferenceModeKey, null)
106+
val desktopId = preferences.getString(defaultDesktopIdKey, null)?.takeIf { it.isNotBlank() }
107+
if (mode == null) {
108+
if (desktopId != null && getToken(desktopId) != null) {
109+
return PcDefaultPcPreference.SpecificPc(desktopId)
110+
}
111+
setDefaultPcPreference(PcDefaultPcPreference.LastConnection)
112+
return PcDefaultPcPreference.LastConnection
113+
}
114+
if (mode == defaultPreferenceModeSpecificPc) {
115+
if (desktopId != null && getToken(desktopId) != null) {
116+
return PcDefaultPcPreference.SpecificPc(desktopId)
117+
}
118+
setDefaultPcPreference(PcDefaultPcPreference.LastConnection)
119+
return PcDefaultPcPreference.LastConnection
120+
}
121+
return PcDefaultPcPreference.LastConnection
122+
}
123+
124+
override fun setDefaultPcPreference(preference: PcDefaultPcPreference) {
125+
when (preference) {
126+
PcDefaultPcPreference.LastConnection -> preferences.edit {
127+
putString(defaultPreferenceModeKey, defaultPreferenceModeLastConnection)
128+
remove(defaultDesktopIdKey)
129+
}
130+
is PcDefaultPcPreference.SpecificPc -> {
131+
if (getToken(preference.desktopId) == null) return
132+
preferences.edit {
133+
putString(defaultPreferenceModeKey, defaultPreferenceModeSpecificPc)
134+
putString(defaultDesktopIdKey, preference.desktopId)
135+
}
136+
}
137+
}
138+
}
139+
140+
override fun getLastConnectedDesktopId(): String? {
141+
val desktopId = preferences.getString(lastConnectedDesktopIdKey, null)?.takeIf { it.isNotBlank() } ?: return null
85142
if (getToken(desktopId) != null) return desktopId
86-
clearDefaultDesktopId()
143+
preferences.edit {
144+
remove(lastConnectedDesktopIdKey)
145+
}
87146
return null
88147
}
89148

90-
override fun setDefaultDesktopId(desktopId: String) {
149+
override fun recordSuccessfulConnection(desktopId: String) {
91150
if (getToken(desktopId) == null) return
92151
preferences.edit {
93-
putString(defaultDesktopIdKey, desktopId)
152+
putString(lastConnectedDesktopIdKey, desktopId)
94153
}
95154
}
96155

156+
override fun getDefaultDesktopId(): String? {
157+
return (getDefaultPcPreference() as? PcDefaultPcPreference.SpecificPc)?.desktopId
158+
}
159+
160+
override fun setDefaultDesktopId(desktopId: String) {
161+
setDefaultPcPreference(PcDefaultPcPreference.SpecificPc(desktopId))
162+
}
163+
97164
override fun clearDefaultDesktopId() {
98-
preferences.edit {
99-
remove(defaultDesktopIdKey)
100-
}
165+
setDefaultPcPreference(PcDefaultPcPreference.LastConnection)
101166
}
102167

103168
private fun tokenKey(desktopId: String) = "token:$desktopId"
@@ -117,7 +182,11 @@ class PcTokenStore(context: Context) : PcPairingTokenStore {
117182

118183
private companion object {
119184
const val pairingIdsKey = "paired_desktop_ids"
185+
const val defaultPreferenceModeKey = "default_preference_mode"
120186
const val defaultDesktopIdKey = "default_desktop_id"
187+
const val lastConnectedDesktopIdKey = "last_connected_desktop_id"
188+
const val defaultPreferenceModeLastConnection = "last_connection"
189+
const val defaultPreferenceModeSpecificPc = "specific_pc"
121190
const val tokenPrefix = "token:"
122191
}
123192
}

0 commit comments

Comments
 (0)