Skip to content

Commit e02ffbf

Browse files
committed
fix: do not show duplicate constraint shortcuts
1 parent bcdc075 commit e02ffbf

14 files changed

Lines changed: 390 additions & 403 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
## Fixed
2626

2727
- Restoring subgroups works and does not freeze Key Mapper
28+
- Do not show duplicate constraint shortcuts
2829

2930
## [3.2.1](https://github.com/sds100/KeyMapper/releases/tag/v3.2.1)
3031

base/src/main/java/io/github/sds100/keymapper/base/actions/ConfigActionsUseCase.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package io.github.sds100.keymapper.base.actions
22

33
import dagger.hilt.android.scopes.ViewModelScoped
44
import io.github.sds100.keymapper.base.constraints.ConfigConstraintsUseCase
5-
import io.github.sds100.keymapper.base.constraints.Constraint
5+
import io.github.sds100.keymapper.base.constraints.ConstraintData
66
import io.github.sds100.keymapper.base.keymaps.ConfigKeyMapState
77
import io.github.sds100.keymapper.base.keymaps.GetDefaultKeyMapOptionsUseCase
88
import io.github.sds100.keymapper.base.keymaps.KeyMap
@@ -221,11 +221,11 @@ class ConfigActionsUseCaseImpl @Inject constructor(
221221
}
222222

223223
if (data is ActionData.AnswerCall) {
224-
configConstraints.addConstraint(Constraint.PhoneRinging())
224+
configConstraints.addConstraint(ConstraintData.PhoneRinging)
225225
}
226226

227227
if (data is ActionData.EndCall) {
228-
configConstraints.addConstraint(Constraint.InPhoneCall())
228+
configConstraints.addConstraint(ConstraintData.InPhoneCall)
229229
}
230230

231231
return Action(

base/src/main/java/io/github/sds100/keymapper/base/constraints/ChooseConstraintViewModel.kt

Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class ChooseConstraintViewModel @Inject constructor(
9393
)
9494
}
9595

96-
private val returnResult = MutableSharedFlow<Constraint>()
96+
private val returnResult = MutableSharedFlow<ConstraintData>()
9797

9898
private val allListItems: List<SimpleListItemModel> by lazy { buildListItems() }
9999

@@ -105,20 +105,20 @@ class ChooseConstraintViewModel @Inject constructor(
105105
State.Data(filteredItems)
106106
}.flowOn(Dispatchers.Default).stateIn(viewModelScope, SharingStarted.Eagerly, State.Loading)
107107

108-
var timeConstraintState: Constraint.Time? by mutableStateOf(null)
108+
var timeConstraintState: ConstraintData.Time? by mutableStateOf(null)
109109

110110
init {
111111
viewModelScope.launch {
112-
returnResult.collect { constraint ->
113-
popBackStackWithResult(Json.encodeToString(constraint))
112+
returnResult.collect { constraintData ->
113+
popBackStackWithResult(Json.encodeToString(constraintData))
114114
}
115115
}
116116
}
117117

118118
fun onDoneConfigTimeConstraintClick() {
119-
timeConstraintState?.let { constraint ->
119+
timeConstraintState?.let { constraintData ->
120120
viewModelScope.launch {
121-
returnResult.emit(constraint)
121+
returnResult.emit(constraintData)
122122
timeConstraintState = null
123123
}
124124
}
@@ -139,49 +139,49 @@ class ChooseConstraintViewModel @Inject constructor(
139139
ConstraintId.APP_NOT_PLAYING_MEDIA,
140140
-> onSelectAppConstraint(constraintType)
141141

142-
ConstraintId.MEDIA_PLAYING -> returnResult.emit(Constraint.MediaPlaying())
143-
ConstraintId.MEDIA_NOT_PLAYING -> returnResult.emit(Constraint.NoMediaPlaying())
142+
ConstraintId.MEDIA_PLAYING -> returnResult.emit(ConstraintData.MediaPlaying)
143+
ConstraintId.MEDIA_NOT_PLAYING -> returnResult.emit(ConstraintData.NoMediaPlaying)
144144

145145
ConstraintId.BT_DEVICE_CONNECTED,
146146
ConstraintId.BT_DEVICE_DISCONNECTED,
147147
-> onSelectBluetoothConstraint(
148148
constraintType,
149149
)
150150

151-
ConstraintId.SCREEN_ON -> returnResult.emit(Constraint.ScreenOn())
151+
ConstraintId.SCREEN_ON -> returnResult.emit(ConstraintData.ScreenOn)
152152

153-
ConstraintId.SCREEN_OFF -> returnResult.emit(Constraint.ScreenOff())
153+
ConstraintId.SCREEN_OFF -> returnResult.emit(ConstraintData.ScreenOff)
154154

155155
ConstraintId.ORIENTATION_PORTRAIT ->
156-
returnResult.emit(Constraint.OrientationPortrait())
156+
returnResult.emit(ConstraintData.OrientationPortrait)
157157

158158
ConstraintId.ORIENTATION_LANDSCAPE ->
159-
returnResult.emit(Constraint.OrientationLandscape())
159+
returnResult.emit(ConstraintData.OrientationLandscape)
160160

161161
ConstraintId.ORIENTATION_0 ->
162-
returnResult.emit(Constraint.OrientationCustom(orientation = Orientation.ORIENTATION_0))
162+
returnResult.emit(ConstraintData.OrientationCustom(orientation = Orientation.ORIENTATION_0))
163163

164164
ConstraintId.ORIENTATION_90 ->
165-
returnResult.emit(Constraint.OrientationCustom(orientation = Orientation.ORIENTATION_90))
165+
returnResult.emit(ConstraintData.OrientationCustom(orientation = Orientation.ORIENTATION_90))
166166

167167
ConstraintId.ORIENTATION_180 ->
168-
returnResult.emit(Constraint.OrientationCustom(orientation = Orientation.ORIENTATION_180))
168+
returnResult.emit(ConstraintData.OrientationCustom(orientation = Orientation.ORIENTATION_180))
169169

170170
ConstraintId.ORIENTATION_270 ->
171-
returnResult.emit(Constraint.OrientationCustom(orientation = Orientation.ORIENTATION_270))
171+
returnResult.emit(ConstraintData.OrientationCustom(orientation = Orientation.ORIENTATION_270))
172172

173173
ConstraintId.FLASHLIGHT_ON -> {
174174
val lens = chooseFlashlightLens() ?: return@launch
175-
returnResult.emit(Constraint.FlashlightOn(lens = lens))
175+
returnResult.emit(ConstraintData.FlashlightOn(lens = lens))
176176
}
177177

178178
ConstraintId.FLASHLIGHT_OFF -> {
179179
val lens = chooseFlashlightLens() ?: return@launch
180-
returnResult.emit(Constraint.FlashlightOff(lens = lens))
180+
returnResult.emit(ConstraintData.FlashlightOff(lens = lens))
181181
}
182182

183-
ConstraintId.WIFI_ON -> returnResult.emit(Constraint.WifiOn())
184-
ConstraintId.WIFI_OFF -> returnResult.emit(Constraint.WifiOff())
183+
ConstraintId.WIFI_ON -> returnResult.emit(ConstraintData.WifiOn)
184+
ConstraintId.WIFI_OFF -> returnResult.emit(ConstraintData.WifiOff)
185185

186186
ConstraintId.WIFI_CONNECTED,
187187
ConstraintId.WIFI_DISCONNECTED,
@@ -194,34 +194,34 @@ class ChooseConstraintViewModel @Inject constructor(
194194
-> onSelectImeChosenConstraint(constraintType)
195195

196196
ConstraintId.DEVICE_IS_LOCKED ->
197-
returnResult.emit(Constraint.DeviceIsLocked())
197+
returnResult.emit(ConstraintData.DeviceIsLocked)
198198

199199
ConstraintId.DEVICE_IS_UNLOCKED ->
200-
returnResult.emit(Constraint.DeviceIsUnlocked())
200+
returnResult.emit(ConstraintData.DeviceIsUnlocked)
201201

202202
ConstraintId.IN_PHONE_CALL ->
203-
returnResult.emit(Constraint.InPhoneCall())
203+
returnResult.emit(ConstraintData.InPhoneCall)
204204

205205
ConstraintId.NOT_IN_PHONE_CALL ->
206-
returnResult.emit(Constraint.NotInPhoneCall())
206+
returnResult.emit(ConstraintData.NotInPhoneCall)
207207

208208
ConstraintId.PHONE_RINGING ->
209-
returnResult.emit(Constraint.PhoneRinging())
209+
returnResult.emit(ConstraintData.PhoneRinging)
210210

211211
ConstraintId.CHARGING ->
212-
returnResult.emit(Constraint.Charging())
212+
returnResult.emit(ConstraintData.Charging)
213213

214214
ConstraintId.DISCHARGING ->
215-
returnResult.emit(Constraint.Discharging())
215+
returnResult.emit(ConstraintData.Discharging)
216216

217217
ConstraintId.LOCK_SCREEN_SHOWING ->
218-
returnResult.emit(Constraint.LockScreenShowing())
218+
returnResult.emit(ConstraintData.LockScreenShowing)
219219

220220
ConstraintId.LOCK_SCREEN_NOT_SHOWING ->
221-
returnResult.emit(Constraint.LockScreenNotShowing())
221+
returnResult.emit(ConstraintData.LockScreenNotShowing)
222222

223223
ConstraintId.TIME -> {
224-
timeConstraintState = Constraint.Time(
224+
timeConstraintState = ConstraintData.Time(
225225
startHour = 0,
226226
startMinute = 0,
227227
endHour = 0,
@@ -315,10 +315,10 @@ class ChooseConstraintViewModel @Inject constructor(
315315

316316
when (type) {
317317
ConstraintId.WIFI_CONNECTED ->
318-
returnResult.emit(Constraint.WifiConnected(ssid = chosenSSID))
318+
returnResult.emit(ConstraintData.WifiConnected(ssid = chosenSSID))
319319

320320
ConstraintId.WIFI_DISCONNECTED ->
321-
returnResult.emit(Constraint.WifiDisconnected(ssid = chosenSSID))
321+
returnResult.emit(ConstraintData.WifiDisconnected(ssid = chosenSSID))
322322

323323
else -> Unit
324324
}
@@ -336,15 +336,15 @@ class ChooseConstraintViewModel @Inject constructor(
336336
when (type) {
337337
ConstraintId.IME_CHOSEN ->
338338
returnResult.emit(
339-
Constraint.ImeChosen(
339+
ConstraintData.ImeChosen(
340340
imeId = imeInfo.id,
341341
imeLabel = imeInfo.label,
342342
),
343343
)
344344

345345
ConstraintId.IME_NOT_CHOSEN ->
346346
returnResult.emit(
347-
Constraint.ImeNotChosen(
347+
ConstraintData.ImeNotChosen(
348348
imeId = imeInfo.id,
349349
imeLabel = imeInfo.label,
350350
),
@@ -367,21 +367,21 @@ class ChooseConstraintViewModel @Inject constructor(
367367
NavDestination.ChooseBluetoothDevice,
368368
) ?: return
369369

370-
val constraint = when (type) {
371-
ConstraintId.BT_DEVICE_CONNECTED -> Constraint.BtDeviceConnected(
370+
val constraintData = when (type) {
371+
ConstraintId.BT_DEVICE_CONNECTED -> ConstraintData.BtDeviceConnected(
372372
bluetoothAddress = device.address,
373373
deviceName = device.name,
374374
)
375375

376-
ConstraintId.BT_DEVICE_DISCONNECTED -> Constraint.BtDeviceDisconnected(
376+
ConstraintId.BT_DEVICE_DISCONNECTED -> ConstraintData.BtDeviceDisconnected(
377377
bluetoothAddress = device.address,
378378
deviceName = device.name,
379379
)
380380

381381
else -> throw IllegalArgumentException("Don't know how to create $type constraint after choosing app")
382382
}
383383

384-
returnResult.emit(constraint)
384+
returnResult.emit(constraintData)
385385
}
386386

387387
private suspend fun onSelectAppConstraint(type: ConstraintId) {
@@ -392,26 +392,26 @@ class ChooseConstraintViewModel @Inject constructor(
392392
)
393393
?: return
394394

395-
val constraint = when (type) {
396-
ConstraintId.APP_IN_FOREGROUND -> Constraint.AppInForeground(
395+
val constraintData = when (type) {
396+
ConstraintId.APP_IN_FOREGROUND -> ConstraintData.AppInForeground(
397397
packageName = packageName,
398398
)
399399

400-
ConstraintId.APP_NOT_IN_FOREGROUND -> Constraint.AppNotInForeground(
400+
ConstraintId.APP_NOT_IN_FOREGROUND -> ConstraintData.AppNotInForeground(
401401
packageName = packageName,
402402
)
403403

404-
ConstraintId.APP_PLAYING_MEDIA -> Constraint.AppPlayingMedia(
404+
ConstraintId.APP_PLAYING_MEDIA -> ConstraintData.AppPlayingMedia(
405405
packageName = packageName,
406406
)
407407

408-
ConstraintId.APP_NOT_PLAYING_MEDIA -> Constraint.AppNotPlayingMedia(
408+
ConstraintId.APP_NOT_PLAYING_MEDIA -> ConstraintData.AppNotPlayingMedia(
409409
packageName = packageName,
410410
)
411411

412412
else -> throw IllegalArgumentException("Don't know how to create $type constraint after choosing app")
413413
}
414414

415-
returnResult.emit(constraint)
415+
returnResult.emit(constraintData)
416416
}
417417
}

base/src/main/java/io/github/sds100/keymapper/base/constraints/ConfigConstraintsUseCase.kt

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,11 @@ import io.github.sds100.keymapper.base.keymaps.KeyMap
66
import io.github.sds100.keymapper.common.utils.State
77
import io.github.sds100.keymapper.data.Keys
88
import io.github.sds100.keymapper.data.repositories.PreferenceRepository
9-
import kotlinx.coroutines.Dispatchers
109
import kotlinx.coroutines.flow.Flow
1110
import kotlinx.coroutines.flow.StateFlow
1211
import kotlinx.coroutines.flow.combine
1312
import kotlinx.coroutines.flow.filterIsInstance
1413
import kotlinx.coroutines.flow.map
15-
import kotlinx.coroutines.withContext
1614
import kotlinx.serialization.json.Json
1715
import java.util.LinkedList
1816
import javax.inject.Inject
@@ -28,45 +26,44 @@ class ConfigConstraintsUseCaseImpl @Inject constructor(
2826
/**
2927
* The most recently used is first.
3028
*/
31-
override val recentlyUsedConstraints: Flow<List<Constraint>> =
29+
override val recentlyUsedConstraints: Flow<List<ConstraintData>> =
3230
combine(
3331
preferenceRepository.get(Keys.recentlyUsedConstraints).map(::getConstraintShortcuts),
3432
keyMap.filterIsInstance<State.Data<KeyMap>>(),
35-
) { shortcuts, keyMap ->
33+
) { shortcutData, keyMap ->
3634

3735
// Do not include constraints that the key map already contains.
38-
shortcuts
39-
.filter { !keyMap.data.constraintState.constraints.contains(it) }
36+
shortcutData
37+
.filter { constraintData ->
38+
!keyMap.data.constraintState.constraints.any { it.data == constraintData }
39+
}
4040
.take(5)
4141
}
4242

43-
override fun addConstraint(constraint: Constraint): Boolean {
43+
override fun addConstraint(constraintData: ConstraintData): Boolean {
4444
var containsConstraint = false
45+
val newConstraint = Constraint(data = constraintData)
4546

4647
updateConstraintState { oldState ->
47-
containsConstraint = oldState.constraints.contains(constraint)
48+
containsConstraint = oldState.constraints.any { it.data == constraintData }
4849

4950
if (containsConstraint) {
5051
oldState
5152
} else {
52-
oldState.copy(constraints = oldState.constraints.plus(constraint))
53+
oldState.copy(constraints = oldState.constraints.plus(newConstraint))
5354
}
5455
}
5556

5657
preferenceRepository.update(
5758
Keys.recentlyUsedConstraints,
5859
{ old ->
59-
val oldList: List<Constraint> = if (old == null) {
60-
emptyList()
61-
} else {
62-
Json.decodeFromString<List<Constraint>>(old)
63-
}
60+
val oldDataList = getConstraintShortcuts(old)
6461

65-
val newShortcuts = LinkedList(oldList)
66-
.also { it.addFirst(constraint) }
62+
val newDataList = LinkedList(oldDataList)
63+
.apply { addFirst(constraintData) }
6764
.distinct()
6865

69-
Json.encodeToString(newShortcuts)
66+
Json.encodeToString(newDataList)
7067
},
7168
)
7269

@@ -100,19 +97,14 @@ class ConfigConstraintsUseCaseImpl @Inject constructor(
10097
}
10198
}
10299

103-
private suspend fun getConstraintShortcuts(json: String?): List<Constraint> {
100+
private fun getConstraintShortcuts(json: String?): List<ConstraintData> {
104101
if (json == null) {
105102
return emptyList()
106103
}
107104

108105
try {
109-
return withContext(Dispatchers.Default) {
110-
val list = Json.decodeFromString<List<Constraint>>(json)
111-
112-
list.distinct()
113-
}
106+
return Json.decodeFromString<List<ConstraintData>>(json).distinct()
114107
} catch (_: Exception) {
115-
preferenceRepository.set(Keys.recentlyUsedConstraints, null)
116108
return emptyList()
117109
}
118110
}
@@ -121,8 +113,8 @@ class ConfigConstraintsUseCaseImpl @Inject constructor(
121113
interface ConfigConstraintsUseCase {
122114
val keyMap: StateFlow<State<KeyMap>>
123115

124-
val recentlyUsedConstraints: Flow<List<Constraint>>
125-
fun addConstraint(constraint: Constraint): Boolean
116+
val recentlyUsedConstraints: Flow<List<ConstraintData>>
117+
fun addConstraint(constraintData: ConstraintData): Boolean
126118
fun removeConstraint(id: String)
127119
fun setAndMode()
128120
fun setOrMode()

0 commit comments

Comments
 (0)