Skip to content

Commit eda1d53

Browse files
committed
#1947 feat: show tip to use expert mode where the old option for screen off remapping used to be
1 parent 618a01f commit eda1d53

File tree

6 files changed

+77
-19
lines changed

6 files changed

+77
-19
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
#### TO BE RELEASED
44

5+
## Added
6+
- #1947 show tip to use expert mode where the old option for screen off remapping used to be
7+
58
## Bug fixes
69

710
- #1955 step forward and step backward media actions support more apps.

base/src/main/java/io/github/sds100/keymapper/base/keymaps/ConfigKeyMapOptionsViewModel.kt

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import io.github.sds100.keymapper.base.R
66
import io.github.sds100.keymapper.base.actions.ActionUiHelper
77
import io.github.sds100.keymapper.base.shortcuts.CreateKeyMapShortcutUseCase
88
import io.github.sds100.keymapper.base.trigger.ConfigTriggerUseCase
9+
import io.github.sds100.keymapper.base.trigger.EvdevTriggerKey
910
import io.github.sds100.keymapper.base.utils.getFullMessage
11+
import io.github.sds100.keymapper.base.utils.navigation.NavDestination
12+
import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider
13+
import io.github.sds100.keymapper.base.utils.navigation.navigate
1014
import io.github.sds100.keymapper.base.utils.ui.DialogModel
1115
import io.github.sds100.keymapper.base.utils.ui.DialogProvider
1216
import io.github.sds100.keymapper.base.utils.ui.ResourceProvider
@@ -16,12 +20,14 @@ import io.github.sds100.keymapper.common.utils.State
1620
import io.github.sds100.keymapper.common.utils.dataOrNull
1721
import io.github.sds100.keymapper.common.utils.mapData
1822
import io.github.sds100.keymapper.common.utils.onFailure
23+
import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManager
24+
import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionState
1925
import kotlinx.coroutines.CoroutineScope
2026
import kotlinx.coroutines.flow.SharingStarted
2127
import kotlinx.coroutines.flow.StateFlow
28+
import kotlinx.coroutines.flow.combine
2229
import kotlinx.coroutines.flow.first
2330
import kotlinx.coroutines.flow.firstOrNull
24-
import kotlinx.coroutines.flow.map
2531
import kotlinx.coroutines.flow.stateIn
2632
import kotlinx.coroutines.launch
2733

@@ -30,16 +36,22 @@ class ConfigKeyMapOptionsViewModel(
3036
private val config: ConfigTriggerUseCase,
3137
private val displayUseCase: DisplayKeyMapUseCase,
3238
private val createKeyMapShortcut: CreateKeyMapShortcutUseCase,
39+
private val systemBridgeConnectionManager: SystemBridgeConnectionManager,
3340
private val dialogProvider: DialogProvider,
41+
navigationProvider: NavigationProvider,
3442
resourceProvider: ResourceProvider,
3543
) : ResourceProvider by resourceProvider,
3644
DialogProvider by dialogProvider,
45+
NavigationProvider by navigationProvider,
3746
KeyMapOptionsCallback {
3847

3948
private val actionUiHelper = ActionUiHelper(displayUseCase, resourceProvider)
4049

41-
val state: StateFlow<State<KeyMapOptionsState>> = config.keyMap.map { keyMapState ->
42-
keyMapState.mapData { keyMap -> buildState(keyMap) }
50+
val state: StateFlow<State<KeyMapOptionsState>> = combine(
51+
config.keyMap,
52+
systemBridgeConnectionManager.connectionState,
53+
) { keyMapState, systemBridgeConnectionState ->
54+
keyMapState.mapData { keyMap -> buildState(keyMap, systemBridgeConnectionState) }
4355
}.stateIn(coroutineScope, SharingStarted.Eagerly, State.Loading)
4456

4557
override fun onLongPressDelayChanged(delay: Int) {
@@ -74,6 +86,12 @@ class ConfigKeyMapOptionsViewModel(
7486
config.setTriggerFromOtherAppsEnabled(checked)
7587
}
7688

89+
override fun onOpenExpertModeSettings() {
90+
coroutineScope.launch {
91+
navigate("screen_off_trigger_tip", NavDestination.ExpertMode)
92+
}
93+
}
94+
7795
override fun onCreateShortcutClick() {
7896
coroutineScope.launch {
7997
val mapping = config.keyMap.firstOrNull()?.dataOrNull() ?: return@launch
@@ -100,7 +118,9 @@ class ConfigKeyMapOptionsViewModel(
100118
// background is white. Also, getting the colorOnSurface attribute
101119
// from the application context doesn't seem to work correctly.
102120
TintType.OnSurface -> iconInfo.drawable.setTint(Color.BLACK)
121+
103122
is TintType.Color -> iconInfo.drawable.setTint(iconInfo.tintType.color)
123+
104124
else -> {}
105125
}
106126

@@ -132,7 +152,10 @@ class ConfigKeyMapOptionsViewModel(
132152
}
133153
}
134154

135-
private suspend fun buildState(keyMap: KeyMap): KeyMapOptionsState {
155+
private suspend fun buildState(
156+
keyMap: KeyMap,
157+
systemBridgeConnectionState: SystemBridgeConnectionState,
158+
): KeyMapOptionsState {
136159
val defaultLongPressDelay = config.defaultLongPressDelay.first()
137160
val defaultDoublePressDelay = config.defaultDoublePressDelay.first()
138161
val defaultSequenceTriggerTimeout = config.defaultSequenceTriggerTimeout.first()
@@ -167,6 +190,9 @@ class ConfigKeyMapOptionsViewModel(
167190
isLauncherShortcutButtonEnabled = createKeyMapShortcut.isSupported,
168191

169192
showToast = keyMap.trigger.showToast,
193+
showScreenOffTip = keyMap.trigger.keys.none { it is EvdevTriggerKey },
194+
isExpertModeStarted =
195+
systemBridgeConnectionState is SystemBridgeConnectionState.Connected,
170196
)
171197
}
172198
}
@@ -199,4 +225,7 @@ data class KeyMapOptionsState(
199225
val isLauncherShortcutButtonEnabled: Boolean,
200226

201227
val showToast: Boolean,
228+
229+
val showScreenOffTip: Boolean,
230+
val isExpertModeStarted: Boolean,
202231
)

base/src/main/java/io/github/sds100/keymapper/base/keymaps/KeyMapOptionsScreen.kt

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import androidx.compose.ui.unit.dp
4343
import androidx.lifecycle.compose.collectAsStateWithLifecycle
4444
import io.github.sds100.keymapper.base.R
4545
import io.github.sds100.keymapper.base.compose.KeyMapperTheme
46+
import io.github.sds100.keymapper.base.onboarding.TipCard
4647
import io.github.sds100.keymapper.base.utils.ui.SliderMaximums
4748
import io.github.sds100.keymapper.base.utils.ui.SliderMinimums
4849
import io.github.sds100.keymapper.base.utils.ui.SliderStepSizes
@@ -98,9 +99,27 @@ private fun Loaded(
9899
modifier = modifier
99100
.verticalScroll(rememberScrollState())
100101
.fillMaxWidth(),
102+
verticalArrangement = Arrangement.spacedBy(8.dp),
101103
) {
102104
Spacer(modifier = Modifier.height(8.dp))
103105

106+
if (state.showScreenOffTip) {
107+
TipCard(
108+
modifier = Modifier
109+
.fillMaxWidth()
110+
.padding(horizontal = 8.dp),
111+
title = stringResource(R.string.tip_screen_off_trigger_title),
112+
message = stringResource(R.string.tip_screen_off_trigger_message),
113+
isDismissable = false,
114+
buttonText = if (state.isExpertModeStarted) {
115+
null
116+
} else {
117+
stringResource(R.string.button_enable_expert_mode)
118+
},
119+
onButtonClick = callback::onOpenExpertModeSettings,
120+
)
121+
}
122+
104123
TriggerFromOtherAppsSection(
105124
modifier = Modifier
106125
.fillMaxWidth()
@@ -112,8 +131,6 @@ private fun Loaded(
112131
onCreateShortcutClick = callback::onCreateShortcutClick,
113132
)
114133

115-
Spacer(Modifier.height(8.dp))
116-
117134
CheckBoxText(
118135
modifier = Modifier
119136
.padding(horizontal = 8.dp)
@@ -122,7 +139,6 @@ private fun Loaded(
122139
isChecked = state.showToast,
123140
onCheckedChange = callback::onShowToastChanged,
124141
)
125-
Spacer(Modifier.height(8.dp))
126142

127143
if (state.showVibrate) {
128144
CheckBoxText(
@@ -133,7 +149,6 @@ private fun Loaded(
133149
isChecked = state.vibrate,
134150
onCheckedChange = callback::onVibrateChanged,
135151
)
136-
Spacer(Modifier.height(8.dp))
137152
}
138153

139154
if (state.showVibrateDuration) {
@@ -151,7 +166,6 @@ private fun Loaded(
151166
valueRange = vibrateDurationMin.toFloat()..vibrateDurationMax.toFloat(),
152167
stepSize = SliderStepSizes.VIBRATION_DURATION,
153168
)
154-
Spacer(Modifier.height(8.dp))
155169
}
156170

157171
if (state.showLongPressDoubleVibration) {
@@ -163,7 +177,6 @@ private fun Loaded(
163177
isChecked = state.longPressDoubleVibration,
164178
onCheckedChange = callback::onLongPressDoubleVibrationChanged,
165179
)
166-
Spacer(Modifier.height(8.dp))
167180
}
168181

169182
if (state.showLongPressDelay) {
@@ -181,7 +194,6 @@ private fun Loaded(
181194
valueRange = longPressDelayMin.toFloat()..longPressDelayMax.toFloat(),
182195
stepSize = SliderStepSizes.TRIGGER_LONG_PRESS_DELAY,
183196
)
184-
Spacer(Modifier.height(8.dp))
185197
}
186198

187199
if (state.showDoublePressDelay) {
@@ -199,7 +211,6 @@ private fun Loaded(
199211
valueRange = doublePressDelayMin.toFloat()..doublePressDelayMax.toFloat(),
200212
stepSize = SliderStepSizes.TRIGGER_DOUBLE_PRESS_DELAY,
201213
)
202-
Spacer(Modifier.height(8.dp))
203214
}
204215

205216
if (state.showSequenceTriggerTimeout) {
@@ -219,7 +230,6 @@ private fun Loaded(
219230
valueRange = sequenceTriggerTimeoutMin..sequenceTriggerTimeoutMax,
220231
stepSize = SliderStepSizes.TRIGGER_SEQUENCE_TRIGGER_TIMEOUT,
221232
)
222-
Spacer(Modifier.height(8.dp))
223233
}
224234

225235
Spacer(Modifier.height(8.dp))
@@ -359,6 +369,7 @@ interface KeyMapOptionsCallback {
359369
fun onShowToastChanged(checked: Boolean) = run { }
360370
fun onTriggerFromOtherAppsChanged(checked: Boolean) = run {}
361371
fun onCreateShortcutClick() = run { }
372+
fun onOpenExpertModeSettings() = run {}
362373
}
363374

364375
@Preview
@@ -396,6 +407,8 @@ private fun Preview() {
396407
isLauncherShortcutButtonEnabled = false,
397408

398409
showToast = true,
410+
showScreenOffTip = true,
411+
isExpertModeStarted = false,
399412
),
400413
),
401414
callback = object : KeyMapOptionsCallback {},

base/src/main/java/io/github/sds100/keymapper/base/trigger/BaseConfigTriggerViewModel.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ abstract class BaseConfigTriggerViewModel(
9898
config,
9999
displayKeyMap,
100100
createKeyMapShortcut,
101+
systemBridgeConnectionManager,
101102
dialogProvider,
103+
navigationProvider,
102104
resourceProvider,
103105
)
104106

@@ -256,11 +258,9 @@ abstract class BaseConfigTriggerViewModel(
256258

257259
val clickTypeButtons = mutableSetOf<ClickType>()
258260

259-
/**
260-
* The click type radio buttons are only visible if there is one key
261-
* or there are only key code keys in the trigger. It is not possible to do a long press of
262-
* non-key code keys in a parallel trigger.
263-
*/
261+
// The click type radio buttons are only visible if there is one key
262+
// or there are only key code keys in the trigger. It is not possible to do a long press of
263+
// non-key code keys in a parallel trigger.
264264
if (trigger.keys.size == 1 && trigger.keys.all { it.allowedDoublePress }) {
265265
clickTypeButtons.add(ClickType.SHORT_PRESS)
266266
clickTypeButtons.add(ClickType.DOUBLE_PRESS)

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@
408408
<string name="button_add_action">Add action</string>
409409
<string name="button_record_trigger">Tap to record trigger</string>
410410
<string name="button_record_trigger_expert_mode">Record with Expert Mode</string>
411+
<string name="button_enable_expert_mode">Enable Expert Mode</string>
411412
<string name="button_advanced_triggers_badge">NEW!</string>
412413
<string name="button_done">Done</string>
413414
<string name="button_fix">Fix</string>
@@ -444,6 +445,8 @@
444445
<string name="dialog_message_sequence_trigger_explanation">There is a timeout to input this trigger. You can change this timeout in the "Options" tab.</string>
445446
<string name="tip_parallel_trigger_title">How to use this trigger</string>
446447
<string name="tip_sequence_trigger_title">Sequence triggers</string>
448+
<string name="tip_screen_off_trigger_title">Trigger when screen is off</string>
449+
<string name="tip_screen_off_trigger_message">This key map cannot be detected when the screen is off because the trigger was not recorded with Expert Mode. Re-record the trigger with Expert Mode to enable screen-off remapping.</string>
447450
<string name="dialog_message_bt_constraint_limitation">Android doesn\'t allow apps to get a list of connected (not paired) Bluetooth devices. Apps can only detect when they are connected and disconnected. So if your Bluetooth device is already connected to your device when the accessibility service starts, you will have to reconnect it for the app to know it is connected.</string>
448451
<string name="dialog_title_change_location_or_disable">Automatic backup</string>
449452
<string name="dialog_message_change_location_or_disable">Change location or turn off automatic back up?</string>

sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/manager/SystemBridgeConnectionManager.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,16 +257,26 @@ class SystemBridgeConnectionManagerImpl @Inject constructor(
257257
}
258258

259259
@SuppressLint("ObsoleteSdkInt")
260-
@RequiresApi(Constants.SYSTEM_BRIDGE_MIN_API)
261260
interface SystemBridgeConnectionManager {
261+
// Do not require min API to check the state.
262262
val connectionState: StateFlow<SystemBridgeConnectionState>
263263

264+
@RequiresApi(Constants.SYSTEM_BRIDGE_MIN_API)
264265
fun <T> run(block: (ISystemBridge) -> T): KMResult<T>
266+
267+
@RequiresApi(Constants.SYSTEM_BRIDGE_MIN_API)
265268
fun stopSystemBridge()
269+
270+
@RequiresApi(Constants.SYSTEM_BRIDGE_MIN_API)
266271
fun restartSystemBridge()
267272

273+
@RequiresApi(Constants.SYSTEM_BRIDGE_MIN_API)
268274
suspend fun startWithRoot()
275+
276+
@RequiresApi(Constants.SYSTEM_BRIDGE_MIN_API)
269277
fun startWithShizuku()
278+
279+
@RequiresApi(Constants.SYSTEM_BRIDGE_MIN_API)
270280
suspend fun startWithAdb()
271281
}
272282

0 commit comments

Comments
 (0)