From 4e0e7071868fa59d70e45c4bde58913e576ec1c5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:21:37 +0000 Subject: [PATCH 01/23] Initial plan From 841feaa662279946de49a9ef6d60ff57209c2082 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:36:57 +0000 Subject: [PATCH 02/23] Add core implementation for modify system settings actions Co-authored-by: sds100 <16245954+sds100@users.noreply.github.com> --- .../keymapper/base/actions/ActionData.kt | 45 ++++++++++++++++ .../base/actions/ActionDataEntityMapper.kt | 39 ++++++++++++++ .../sds100/keymapper/base/actions/ActionId.kt | 4 ++ .../keymapper/base/actions/ActionUtils.kt | 18 +++++++ .../base/actions/PerformActionsUseCase.kt | 12 +++++ base/src/main/res/values/strings.xml | 3 ++ .../keymapper/data/entities/ActionEntity.kt | 1 + .../keymapper/sysbridge/ISystemBridge.aidl | 4 ++ .../sysbridge/service/SystemBridge.kt | 36 +++++++++++++ .../system/display/AndroidDisplayAdapter.kt | 52 +++++++++++++++++++ .../system/display/DisplayAdapter.kt | 4 ++ 11 files changed, 218 insertions(+) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt index 35feedbca9..4ab5bda852 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt @@ -949,4 +949,49 @@ sealed class ActionData : Comparable { data object ClearRecentApp : ActionData() { override val id: ActionId = ActionId.CLEAR_RECENT_APP } + + @Serializable + sealed class ModifySetting : ActionData() { + abstract val settingKey: String + abstract val value: String + + @Serializable + data class System( + override val settingKey: String, + override val value: String, + ) : ModifySetting() { + override val id: ActionId = ActionId.MODIFY_SYSTEM_SETTING + + override fun compareTo(other: ActionData) = when (other) { + is System -> compareValuesBy(this, other, { it.settingKey }, { it.value }) + else -> super.compareTo(other) + } + } + + @Serializable + data class Secure( + override val settingKey: String, + override val value: String, + ) : ModifySetting() { + override val id: ActionId = ActionId.MODIFY_SECURE_SETTING + + override fun compareTo(other: ActionData) = when (other) { + is Secure -> compareValuesBy(this, other, { it.settingKey }, { it.value }) + else -> super.compareTo(other) + } + } + + @Serializable + data class Global( + override val settingKey: String, + override val value: String, + ) : ModifySetting() { + override val id: ActionId = ActionId.MODIFY_GLOBAL_SETTING + + override fun compareTo(other: ActionData) = when (other) { + is Global -> compareValuesBy(this, other, { it.settingKey }, { it.value }) + else -> super.compareTo(other) + } + } + } } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt index b13b0757e3..d4bb5ce0a6 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt @@ -723,6 +723,36 @@ object ActionDataEntityMapper { ActionId.FORCE_STOP_APP -> ActionData.ForceStopApp ActionId.CLEAR_RECENT_APP -> ActionData.ClearRecentApp + + ActionId.MODIFY_SYSTEM_SETTING -> { + val value = entity.extras.getData(ActionEntity.EXTRA_SETTING_VALUE) + .valueOrNull() ?: return null + + ActionData.ModifySetting.System( + settingKey = entity.data, + value = value, + ) + } + + ActionId.MODIFY_SECURE_SETTING -> { + val value = entity.extras.getData(ActionEntity.EXTRA_SETTING_VALUE) + .valueOrNull() ?: return null + + ActionData.ModifySetting.Secure( + settingKey = entity.data, + value = value, + ) + } + + ActionId.MODIFY_GLOBAL_SETTING -> { + val value = entity.extras.getData(ActionEntity.EXTRA_SETTING_VALUE) + .valueOrNull() ?: return null + + ActionData.ModifySetting.Global( + settingKey = entity.data, + value = value, + ) + } } } @@ -825,6 +855,7 @@ object ActionDataEntityMapper { is ActionData.ControlMedia.Rewind -> SYSTEM_ACTION_ID_MAP[data.id]!! is ActionData.ControlMedia.Stop -> SYSTEM_ACTION_ID_MAP[data.id]!! is ActionData.GoBack -> SYSTEM_ACTION_ID_MAP[data.id]!! + is ActionData.ModifySetting -> data.settingKey else -> SYSTEM_ACTION_ID_MAP[data.id]!! } @@ -1105,6 +1136,10 @@ object ActionDataEntityMapper { EntityExtra(ActionEntity.EXTRA_SHELL_COMMAND_TIMEOUT, data.timeoutMillis.toString()), ) + is ActionData.ModifySetting -> listOf( + EntityExtra(ActionEntity.EXTRA_SETTING_VALUE, data.value), + ) + else -> emptyList() } @@ -1279,5 +1314,9 @@ object ActionDataEntityMapper { ActionId.HTTP_REQUEST to "http_request", ActionId.FORCE_STOP_APP to "force_stop_app", ActionId.CLEAR_RECENT_APP to "clear_recent_app", + + ActionId.MODIFY_SYSTEM_SETTING to "modify_system_setting", + ActionId.MODIFY_SECURE_SETTING to "modify_secure_setting", + ActionId.MODIFY_GLOBAL_SETTING to "modify_global_setting", ) } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt index 481dc59c5b..fcd70dea63 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt @@ -147,4 +147,8 @@ enum class ActionId { FORCE_STOP_APP, CLEAR_RECENT_APP, + + MODIFY_SYSTEM_SETTING, + MODIFY_SECURE_SETTING, + MODIFY_GLOBAL_SETTING, } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt index 0902ef74db..14131285f3 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt @@ -254,6 +254,10 @@ object ActionUtils { ActionId.FORCE_STOP_APP -> ActionCategory.APPS ActionId.CLEAR_RECENT_APP -> ActionCategory.APPS + ActionId.MODIFY_SYSTEM_SETTING -> ActionCategory.DISPLAY + ActionId.MODIFY_SECURE_SETTING -> ActionCategory.DISPLAY + ActionId.MODIFY_GLOBAL_SETTING -> ActionCategory.DISPLAY + ActionId.CONSUME_KEY_EVENT -> ActionCategory.SPECIAL } @@ -383,6 +387,10 @@ object ActionUtils { ActionId.INTERACT_UI_ELEMENT -> R.string.action_interact_ui_element_title ActionId.FORCE_STOP_APP -> R.string.action_force_stop_app ActionId.CLEAR_RECENT_APP -> R.string.action_clear_recent_app + + ActionId.MODIFY_SYSTEM_SETTING -> R.string.action_modify_system_setting + ActionId.MODIFY_SECURE_SETTING -> R.string.action_modify_secure_setting + ActionId.MODIFY_GLOBAL_SETTING -> R.string.action_modify_global_setting } @DrawableRes @@ -760,6 +768,12 @@ object ActionUtils { return listOf(Permission.FIND_NEARBY_DEVICES) } + ActionId.MODIFY_SYSTEM_SETTING -> return listOf(Permission.WRITE_SETTINGS) + + ActionId.MODIFY_SECURE_SETTING, + ActionId.MODIFY_GLOBAL_SETTING, + -> return emptyList() // System bridge is required for these + else -> return emptyList() } @@ -890,6 +904,10 @@ object ActionUtils { ActionId.INTERACT_UI_ELEMENT -> KeyMapperIcons.JumpToElement ActionId.FORCE_STOP_APP -> Icons.Outlined.Dangerous ActionId.CLEAR_RECENT_APP -> Icons.Outlined.VerticalSplit + + ActionId.MODIFY_SYSTEM_SETTING -> Icons.Outlined.Settings + ActionId.MODIFY_SECURE_SETTING -> Icons.Outlined.Settings + ActionId.MODIFY_GLOBAL_SETTING -> Icons.Outlined.Settings } } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt index ab1fe35d3b..be71b8df87 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt @@ -1016,6 +1016,18 @@ class PerformActionsUseCaseImpl @AssistedInject constructor( result = SdkVersionTooLow(minSdk = Constants.SYSTEM_BRIDGE_MIN_API) } } + + is ActionData.ModifySetting.System -> { + result = displayAdapter.modifySystemSetting(action.settingKey, action.value) + } + + is ActionData.ModifySetting.Secure -> { + result = displayAdapter.modifySecureSetting(action.settingKey, action.value) + } + + is ActionData.ModifySetting.Global -> { + result = displayAdapter.modifyGlobalSetting(action.settingKey, action.value) + } } when (result) { diff --git a/base/src/main/res/values/strings.xml b/base/src/main/res/values/strings.xml index 188a2d3f59..f5c8cd0256 100644 --- a/base/src/main/res/values/strings.xml +++ b/base/src/main/res/values/strings.xml @@ -1160,6 +1160,9 @@ Force stop app Close and clear app from recents + Modify system setting + Modify secure setting + Modify global setting diff --git a/data/src/main/java/io/github/sds100/keymapper/data/entities/ActionEntity.kt b/data/src/main/java/io/github/sds100/keymapper/data/entities/ActionEntity.kt index 5d33801ee2..02a3ff8507 100644 --- a/data/src/main/java/io/github/sds100/keymapper/data/entities/ActionEntity.kt +++ b/data/src/main/java/io/github/sds100/keymapper/data/entities/ActionEntity.kt @@ -140,6 +140,7 @@ data class ActionEntity( const val EXTRA_DELAY_BEFORE_NEXT_ACTION = "extra_delay_before_next_action" const val EXTRA_HOLD_DOWN_DURATION = "extra_hold_down_duration" const val EXTRA_REPEAT_LIMIT = "extra_repeat_limit" + const val EXTRA_SETTING_VALUE = "extra_setting_value" val DESERIALIZER = jsonDeserializer { val typeString by it.json.byString(NAME_ACTION_TYPE) diff --git a/sysbridge/src/main/aidl/io/github/sds100/keymapper/sysbridge/ISystemBridge.aidl b/sysbridge/src/main/aidl/io/github/sds100/keymapper/sysbridge/ISystemBridge.aidl index 602310a084..d7fcc36c79 100644 --- a/sysbridge/src/main/aidl/io/github/sds100/keymapper/sysbridge/ISystemBridge.aidl +++ b/sysbridge/src/main/aidl/io/github/sds100/keymapper/sysbridge/ISystemBridge.aidl @@ -42,4 +42,8 @@ interface ISystemBridge { void removeTasks(String packageName) = 17; void setRingerMode(int ringerMode) = 18; + + boolean putSystemSetting(String key, String value) = 19; + boolean putSecureSetting(String key, String value) = 20; + boolean putGlobalSetting(String key, String value) = 21; } \ No newline at end of file diff --git a/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridge.kt b/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridge.kt index fdb9d4cc66..8d042ccd1f 100644 --- a/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridge.kt +++ b/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridge.kt @@ -667,4 +667,40 @@ internal class SystemBridge : ISystemBridge.Stub() { audioService.setRingerModeInternal(ringerMode, processPackageName) } + + override fun putSystemSetting(key: String?, value: String?): Boolean { + if (key == null || value == null) { + return false + } + + return try { + Settings.System.putString(contentResolver, key, value) + } catch (e: Exception) { + false + } + } + + override fun putSecureSetting(key: String?, value: String?): Boolean { + if (key == null || value == null) { + return false + } + + return try { + Settings.Secure.putString(contentResolver, key, value) + } catch (e: Exception) { + false + } + } + + override fun putGlobalSetting(key: String?, value: String?): Boolean { + if (key == null || value == null) { + return false + } + + return try { + Settings.Global.putString(contentResolver, key, value) + } catch (e: Exception) { + false + } + } } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/display/AndroidDisplayAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/display/AndroidDisplayAdapter.kt index 9067b25632..770894e74d 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/display/AndroidDisplayAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/display/AndroidDisplayAdapter.kt @@ -18,6 +18,7 @@ import io.github.sds100.keymapper.common.utils.SettingsUtils import io.github.sds100.keymapper.common.utils.SizeKM import io.github.sds100.keymapper.common.utils.Success import io.github.sds100.keymapper.common.utils.getRealDisplaySize +import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow @@ -32,6 +33,7 @@ import javax.inject.Singleton class AndroidDisplayAdapter @Inject constructor( @ApplicationContext private val context: Context, private val coroutineScope: CoroutineScope, + private val systemBridgeConnectionManager: SystemBridgeConnectionManager, ) : DisplayAdapter { companion object { @@ -253,4 +255,54 @@ class AndroidDisplayAdapter @Inject constructor( private fun isAodEnabled(): Boolean { return SettingsUtils.getSecureSetting(ctx, "doze_always_on") == 1 } + + override fun modifySystemSetting(key: String, value: String): KMResult<*> { + // Try to parse value as different types and use the appropriate method + val success = when { + value.toIntOrNull() != null -> SettingsUtils.putSystemSetting(ctx, key, value.toInt()) + value.toLongOrNull() != null -> SettingsUtils.putSystemSetting(ctx, key, value.toLong()) + value.toFloatOrNull() != null -> SettingsUtils.putSystemSetting(ctx, key, value.toFloat()) + else -> SettingsUtils.putSystemSetting(ctx, key, value) + } + + return if (success) { + Success(Unit) + } else { + KMError.FailedToModifySystemSetting(key) + } + } + + override fun modifySecureSetting(key: String, value: String): KMResult<*> { + return systemBridgeConnectionManager.run { bridge -> + val success = when { + value.toIntOrNull() != null -> bridge.putSecureSetting(key, value) + value.toLongOrNull() != null -> bridge.putSecureSetting(key, value) + value.toFloatOrNull() != null -> bridge.putSecureSetting(key, value) + else -> bridge.putSecureSetting(key, value) + } + + if (success) { + Success(Unit) + } else { + KMError.FailedToModifySystemSetting(key) + } + } + } + + override fun modifyGlobalSetting(key: String, value: String): KMResult<*> { + return systemBridgeConnectionManager.run { bridge -> + val success = when { + value.toIntOrNull() != null -> bridge.putGlobalSetting(key, value) + value.toLongOrNull() != null -> bridge.putGlobalSetting(key, value) + value.toFloatOrNull() != null -> bridge.putGlobalSetting(key, value) + else -> bridge.putGlobalSetting(key, value) + } + + if (success) { + Success(Unit) + } else { + KMError.FailedToModifySystemSetting(key) + } + } + } } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/display/DisplayAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/display/DisplayAdapter.kt index a38fe5a177..2881c423fd 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/display/DisplayAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/display/DisplayAdapter.kt @@ -27,4 +27,8 @@ interface DisplayAdapter { fun decreaseBrightness(): KMResult<*> fun enableAutoBrightness(): KMResult<*> fun disableAutoBrightness(): KMResult<*> + + fun modifySystemSetting(key: String, value: String): KMResult<*> + fun modifySecureSetting(key: String, value: String): KMResult<*> + fun modifyGlobalSetting(key: String, value: String): KMResult<*> } From d0d21d3190f708fd1c403b02dfaba847273ca8ca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:41:53 +0000 Subject: [PATCH 03/23] Add UI bottom sheet for modifying system settings Co-authored-by: sds100 <16245954+sds100@users.noreply.github.com> --- .../base/actions/ChooseActionScreen.kt | 1 + .../base/actions/CreateActionDelegate.kt | 63 +++++++ .../actions/ModifySettingActionBottomSheet.kt | 161 ++++++++++++++++++ base/src/main/res/values/strings.xml | 5 + 4 files changed, 230 insertions(+) create mode 100644 base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseActionScreen.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseActionScreen.kt index c3365db29f..a7c1a1868d 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseActionScreen.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseActionScreen.kt @@ -56,6 +56,7 @@ fun HandleActionBottomSheets(delegate: CreateActionDelegate) { HttpRequestBottomSheet(delegate) SmsActionBottomSheet(delegate) VolumeActionBottomSheet(delegate) + ModifySettingActionBottomSheet(delegate) } @Composable diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt index ad6ed0c4f3..c7d562a326 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt @@ -54,6 +54,7 @@ class CreateActionDelegate( var httpRequestBottomSheetState: ActionData.HttpRequest? by mutableStateOf(null) var smsActionBottomSheetState: SmsActionBottomSheetState? by mutableStateOf(null) var volumeActionState: VolumeActionBottomSheetState? by mutableStateOf(null) + var modifySettingActionBottomSheetState: ModifySettingActionBottomSheetState? by mutableStateOf(null) init { coroutineScope.launch { @@ -196,6 +197,11 @@ class CreateActionDelegate( } } + fun onDoneModifySettingClick(action: ActionData.ModifySetting) { + modifySettingActionBottomSheetState = null + actionResult.update { action } + } + suspend fun editAction(oldData: ActionData) { if (!oldData.isEditable()) { throw IllegalArgumentException("This action ${oldData.javaClass.name} can't be edited!") @@ -927,6 +933,63 @@ class CreateActionDelegate( ActionId.MOVE_CURSOR -> return createMoverCursorAction() ActionId.FORCE_STOP_APP -> return ActionData.ForceStopApp ActionId.CLEAR_RECENT_APP -> return ActionData.ClearRecentApp + + ActionId.MODIFY_SYSTEM_SETTING -> { + val settingKey = when (oldData) { + is ActionData.ModifySetting.System -> oldData.settingKey + else -> "" + } + + val value = when (oldData) { + is ActionData.ModifySetting.System -> oldData.value + else -> "" + } + + modifySettingActionBottomSheetState = ModifySettingActionBottomSheetState.System( + settingKey = settingKey, + value = value, + ) + + return null + } + + ActionId.MODIFY_SECURE_SETTING -> { + val settingKey = when (oldData) { + is ActionData.ModifySetting.Secure -> oldData.settingKey + else -> "" + } + + val value = when (oldData) { + is ActionData.ModifySetting.Secure -> oldData.value + else -> "" + } + + modifySettingActionBottomSheetState = ModifySettingActionBottomSheetState.Secure( + settingKey = settingKey, + value = value, + ) + + return null + } + + ActionId.MODIFY_GLOBAL_SETTING -> { + val settingKey = when (oldData) { + is ActionData.ModifySetting.Global -> oldData.settingKey + else -> "" + } + + val value = when (oldData) { + is ActionData.ModifySetting.Global -> oldData.value + else -> "" + } + + modifySettingActionBottomSheetState = ModifySettingActionBottomSheetState.Global( + settingKey = settingKey, + value = value, + ) + + return null + } } } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt new file mode 100644 index 0000000000..c6811719e3 --- /dev/null +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt @@ -0,0 +1,161 @@ +package io.github.sds100.keymapper.base.actions + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import io.github.sds100.keymapper.base.R +import io.github.sds100.keymapper.base.utils.ui.compose.BottomSheet +import io.github.sds100.keymapper.base.utils.ui.compose.BottomSheetDefaults +import kotlinx.coroutines.launch + +sealed class ModifySettingActionBottomSheetState( + open val settingKey: String, + open val value: String, +) { + data class System( + override val settingKey: String, + override val value: String, + ) : ModifySettingActionBottomSheetState(settingKey, value) + + data class Secure( + override val settingKey: String, + override val value: String, + ) : ModifySettingActionBottomSheetState(settingKey, value) + + data class Global( + override val settingKey: String, + override val value: String, + ) : ModifySettingActionBottomSheetState(settingKey, value) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ModifySettingActionBottomSheet(delegate: CreateActionDelegate) { + val scope = rememberCoroutineScope() + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + + if (delegate.modifySettingActionBottomSheetState != null) { + ModalBottomSheet( + onDismissRequest = { + scope.launch { + sheetState.hide() + }.invokeOnCompletion { + delegate.modifySettingActionBottomSheetState = null + } + }, + sheetState = sheetState, + ) { + ModifySettingActionBottomSheetContent( + state = delegate.modifySettingActionBottomSheetState!!, + onDismiss = { + scope.launch { + sheetState.hide() + }.invokeOnCompletion { + delegate.modifySettingActionBottomSheetState = null + } + }, + onComplete = { action -> + scope.launch { + sheetState.hide() + }.invokeOnCompletion { + delegate.onDoneModifySettingClick(action) + } + }, + ) + } + } +} + +@Composable +private fun ModifySettingActionBottomSheetContent( + state: ModifySettingActionBottomSheetState, + onDismiss: () -> Unit, + onComplete: (ActionData.ModifySetting) -> Unit, +) { + var settingKey by remember(state) { mutableStateOf(state.settingKey) } + var value by remember(state) { mutableStateOf(state.value) } + + val titleRes = when (state) { + is ModifySettingActionBottomSheetState.System -> R.string.action_modify_system_setting + is ModifySettingActionBottomSheetState.Secure -> R.string.action_modify_secure_setting + is ModifySettingActionBottomSheetState.Global -> R.string.action_modify_global_setting + } + + BottomSheet( + title = stringResource(titleRes), + onDismiss = onDismiss, + positiveButton = BottomSheetDefaults.OkButton { + val action = when (state) { + is ModifySettingActionBottomSheetState.System -> + ActionData.ModifySetting.System(settingKey, value) + is ModifySettingActionBottomSheetState.Secure -> + ActionData.ModifySetting.Secure(settingKey, value) + is ModifySettingActionBottomSheetState.Global -> + ActionData.ModifySetting.Global(settingKey, value) + } + onComplete(action) + }, + positiveButtonEnabled = settingKey.isNotBlank() && value.isNotBlank(), + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) { + Spacer(modifier = Modifier.height(16.dp)) + + OutlinedTextField( + value = settingKey, + onValueChange = { settingKey = it }, + label = { Text(stringResource(R.string.modify_setting_key_label)) }, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + OutlinedTextField( + value = value, + onValueChange = { value = it }, + label = { Text(stringResource(R.string.modify_setting_value_label)) }, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + val exampleText = when (state) { + is ModifySettingActionBottomSheetState.System -> + stringResource(R.string.modify_setting_example_system) + is ModifySettingActionBottomSheetState.Secure -> + stringResource(R.string.modify_setting_example_secure) + is ModifySettingActionBottomSheetState.Global -> + stringResource(R.string.modify_setting_example_global) + } + + Text( + text = exampleText, + style = androidx.compose.material3.MaterialTheme.typography.bodySmall, + color = androidx.compose.material3.MaterialTheme.colorScheme.onSurfaceVariant, + ) + + Spacer(modifier = Modifier.height(16.dp)) + } + } +} diff --git a/base/src/main/res/values/strings.xml b/base/src/main/res/values/strings.xml index f5c8cd0256..bde72c8fbe 100644 --- a/base/src/main/res/values/strings.xml +++ b/base/src/main/res/values/strings.xml @@ -1163,6 +1163,11 @@ Modify system setting Modify secure setting Modify global setting + Setting Key + Value + Example: screen_off_timeout = 2147483647 (System settings use WRITE_SETTINGS permission) + Example: accessibility_enabled = 1 (Secure settings require system bridge) + Example: airplane_mode_on = 1 (Global settings require system bridge) From 4cc6ed90ae4a4f99b33ce347f6639f88c05ba3bd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:44:38 +0000 Subject: [PATCH 04/23] Add action title and description display for modify settings Co-authored-by: sds100 <16245954+sds100@users.noreply.github.com> --- .../keymapper/base/actions/ActionUiHelper.kt | 15 +++++++++++++++ base/src/main/res/values/strings.xml | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt index 7659de15ef..be36a5c640 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt @@ -651,6 +651,21 @@ class ActionUiHelper( ActionData.Microphone.Mute -> getString(R.string.action_mute_microphone) ActionData.Microphone.Toggle -> getString(R.string.action_toggle_mute_microphone) ActionData.Microphone.Unmute -> getString(R.string.action_unmute_microphone) + + is ActionData.ModifySetting.System -> getString( + R.string.modify_setting_description, + arrayOf(action.settingKey, action.value, getString(R.string.modify_setting_type_system)), + ) + + is ActionData.ModifySetting.Secure -> getString( + R.string.modify_setting_description, + arrayOf(action.settingKey, action.value, getString(R.string.modify_setting_type_secure)), + ) + + is ActionData.ModifySetting.Global -> getString( + R.string.modify_setting_description, + arrayOf(action.settingKey, action.value, getString(R.string.modify_setting_type_global)), + ) } fun getIcon(action: ActionData): ComposeIconInfo = when (action) { diff --git a/base/src/main/res/values/strings.xml b/base/src/main/res/values/strings.xml index bde72c8fbe..fef9f998be 100644 --- a/base/src/main/res/values/strings.xml +++ b/base/src/main/res/values/strings.xml @@ -1079,6 +1079,10 @@ Send SMS: "%s"" to %s Compose SMS Compose SMS: "%s" to %s + Set %3$s: %1$s = %2$s + System Setting + Secure Setting + Global Setting Play sound Dismiss most recent notification Dismiss all notifications From 6237188b0212aa7a3173c6047cfbabbc6987e64b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:47:19 +0000 Subject: [PATCH 05/23] Fix SystemBridge settings implementation with proper ContentResolver access Co-authored-by: sds100 <16245954+sds100@users.noreply.github.com> --- .../keymapper/sysbridge/service/SystemBridge.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridge.kt b/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridge.kt index 8d042ccd1f..60baba88d2 100644 --- a/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridge.kt +++ b/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridge.kt @@ -26,6 +26,7 @@ import android.os.Process import android.os.ServiceManager import android.permission.IPermissionManager import android.permission.PermissionManagerApis +import android.provider.Settings import android.util.Log import android.view.InputEvent import com.android.internal.telephony.ITelephony @@ -674,8 +675,13 @@ internal class SystemBridge : ISystemBridge.Stub() { } return try { + // SystemBridge runs as shell/root user, so we can directly access Settings + // We need to use the system context's content resolver + val contentResolver = android.app.ActivityThread.systemMain() + .getSystemContext().contentResolver Settings.System.putString(contentResolver, key, value) } catch (e: Exception) { + Log.e(TAG, "Failed to put system setting: $key=$value", e) false } } @@ -686,8 +692,11 @@ internal class SystemBridge : ISystemBridge.Stub() { } return try { + val contentResolver = android.app.ActivityThread.systemMain() + .getSystemContext().contentResolver Settings.Secure.putString(contentResolver, key, value) } catch (e: Exception) { + Log.e(TAG, "Failed to put secure setting: $key=$value", e) false } } @@ -698,8 +707,11 @@ internal class SystemBridge : ISystemBridge.Stub() { } return try { + val contentResolver = android.app.ActivityThread.systemMain() + .getSystemContext().contentResolver Settings.Global.putString(contentResolver, key, value) } catch (e: Exception) { + Log.e(TAG, "Failed to put global setting: $key=$value", e) false } } From 927a3d6c2d4a024bd2e170dc59e2a96137f5e3f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:58:37 +0000 Subject: [PATCH 06/23] Refactor to use single ActionId with SettingType enum - Replace 3 separate ActionIds with single MODIFY_SETTING - Add SettingType enum (SYSTEM, SECURE, GLOBAL) - Update ActionData.ModifySetting to use enum instead of sealed class - Add dropdown for setting type in UI - Default to SYSTEM type (no PRO mode required) - Update all mappers and handlers accordingly Co-authored-by: sds100 <16245954+sds100@users.noreply.github.com> --- .../keymapper/base/actions/ActionData.kt | 55 +++------ .../base/actions/ActionDataEntityMapper.kt | 35 ++---- .../sds100/keymapper/base/actions/ActionId.kt | 4 +- .../keymapper/base/actions/ActionUiHelper.kt | 28 ++--- .../keymapper/base/actions/ActionUtils.kt | 18 +-- .../base/actions/CreateActionDelegate.kt | 48 ++------ .../actions/ModifySettingActionBottomSheet.kt | 116 ++++++++++++------ .../base/actions/PerformActionsUseCase.kt | 19 ++- base/src/main/res/values/strings.xml | 5 +- .../keymapper/data/entities/ActionEntity.kt | 1 + .../keymapper/system/settings/SettingType.kt | 10 ++ .../system/settings/SettingsAdapter.kt | 57 +++++++++ 12 files changed, 212 insertions(+), 184 deletions(-) create mode 100644 system/src/main/java/io/github/sds100/keymapper/system/settings/SettingType.kt create mode 100644 system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt index 4ab5bda852..52f8cbd9d9 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt @@ -951,47 +951,22 @@ sealed class ActionData : Comparable { } @Serializable - sealed class ModifySetting : ActionData() { - abstract val settingKey: String - abstract val value: String - - @Serializable - data class System( - override val settingKey: String, - override val value: String, - ) : ModifySetting() { - override val id: ActionId = ActionId.MODIFY_SYSTEM_SETTING - - override fun compareTo(other: ActionData) = when (other) { - is System -> compareValuesBy(this, other, { it.settingKey }, { it.value }) - else -> super.compareTo(other) - } - } - - @Serializable - data class Secure( - override val settingKey: String, - override val value: String, - ) : ModifySetting() { - override val id: ActionId = ActionId.MODIFY_SECURE_SETTING - - override fun compareTo(other: ActionData) = when (other) { - is Secure -> compareValuesBy(this, other, { it.settingKey }, { it.value }) - else -> super.compareTo(other) - } - } - - @Serializable - data class Global( - override val settingKey: String, - override val value: String, - ) : ModifySetting() { - override val id: ActionId = ActionId.MODIFY_GLOBAL_SETTING + data class ModifySetting( + val settingType: io.github.sds100.keymapper.system.settings.SettingType, + val settingKey: String, + val value: String, + ) : ActionData() { + override val id: ActionId = ActionId.MODIFY_SETTING - override fun compareTo(other: ActionData) = when (other) { - is Global -> compareValuesBy(this, other, { it.settingKey }, { it.value }) - else -> super.compareTo(other) - } + override fun compareTo(other: ActionData) = when (other) { + is ModifySetting -> compareValuesBy( + this, + other, + { it.settingType }, + { it.settingKey }, + { it.value }, + ) + else -> super.compareTo(other) } } } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt index d4bb5ce0a6..7e8bb512b7 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt @@ -724,31 +724,21 @@ object ActionDataEntityMapper { ActionId.FORCE_STOP_APP -> ActionData.ForceStopApp ActionId.CLEAR_RECENT_APP -> ActionData.ClearRecentApp - ActionId.MODIFY_SYSTEM_SETTING -> { + ActionId.MODIFY_SETTING -> { val value = entity.extras.getData(ActionEntity.EXTRA_SETTING_VALUE) .valueOrNull() ?: return null - ActionData.ModifySetting.System( - settingKey = entity.data, - value = value, - ) - } + val settingTypeString = entity.extras.getData(ActionEntity.EXTRA_SETTING_TYPE) + .valueOrNull() ?: "SYSTEM" // Default to SYSTEM for backward compatibility - ActionId.MODIFY_SECURE_SETTING -> { - val value = entity.extras.getData(ActionEntity.EXTRA_SETTING_VALUE) - .valueOrNull() ?: return null - - ActionData.ModifySetting.Secure( - settingKey = entity.data, - value = value, - ) - } - - ActionId.MODIFY_GLOBAL_SETTING -> { - val value = entity.extras.getData(ActionEntity.EXTRA_SETTING_VALUE) - .valueOrNull() ?: return null + val settingType = try { + io.github.sds100.keymapper.system.settings.SettingType.valueOf(settingTypeString) + } catch (e: IllegalArgumentException) { + io.github.sds100.keymapper.system.settings.SettingType.SYSTEM + } - ActionData.ModifySetting.Global( + ActionData.ModifySetting( + settingType = settingType, settingKey = entity.data, value = value, ) @@ -1138,6 +1128,7 @@ object ActionDataEntityMapper { is ActionData.ModifySetting -> listOf( EntityExtra(ActionEntity.EXTRA_SETTING_VALUE, data.value), + EntityExtra(ActionEntity.EXTRA_SETTING_TYPE, data.settingType.name), ) else -> emptyList() @@ -1315,8 +1306,6 @@ object ActionDataEntityMapper { ActionId.FORCE_STOP_APP to "force_stop_app", ActionId.CLEAR_RECENT_APP to "clear_recent_app", - ActionId.MODIFY_SYSTEM_SETTING to "modify_system_setting", - ActionId.MODIFY_SECURE_SETTING to "modify_secure_setting", - ActionId.MODIFY_GLOBAL_SETTING to "modify_global_setting", + ActionId.MODIFY_SETTING to "modify_setting", ) } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt index fcd70dea63..aa3b463135 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt @@ -148,7 +148,5 @@ enum class ActionId { FORCE_STOP_APP, CLEAR_RECENT_APP, - MODIFY_SYSTEM_SETTING, - MODIFY_SECURE_SETTING, - MODIFY_GLOBAL_SETTING, + MODIFY_SETTING, } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt index be36a5c640..6dbae2f8b6 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt @@ -652,20 +652,20 @@ class ActionUiHelper( ActionData.Microphone.Toggle -> getString(R.string.action_toggle_mute_microphone) ActionData.Microphone.Unmute -> getString(R.string.action_unmute_microphone) - is ActionData.ModifySetting.System -> getString( - R.string.modify_setting_description, - arrayOf(action.settingKey, action.value, getString(R.string.modify_setting_type_system)), - ) - - is ActionData.ModifySetting.Secure -> getString( - R.string.modify_setting_description, - arrayOf(action.settingKey, action.value, getString(R.string.modify_setting_type_secure)), - ) - - is ActionData.ModifySetting.Global -> getString( - R.string.modify_setting_description, - arrayOf(action.settingKey, action.value, getString(R.string.modify_setting_type_global)), - ) + is ActionData.ModifySetting -> { + val typeString = when (action.settingType) { + io.github.sds100.keymapper.system.settings.SettingType.SYSTEM -> + getString(R.string.modify_setting_type_system) + io.github.sds100.keymapper.system.settings.SettingType.SECURE -> + getString(R.string.modify_setting_type_secure) + io.github.sds100.keymapper.system.settings.SettingType.GLOBAL -> + getString(R.string.modify_setting_type_global) + } + getString( + R.string.modify_setting_description, + arrayOf(action.settingKey, action.value, typeString), + ) + } } fun getIcon(action: ActionData): ComposeIconInfo = when (action) { diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt index 14131285f3..5d7338e57f 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt @@ -254,9 +254,7 @@ object ActionUtils { ActionId.FORCE_STOP_APP -> ActionCategory.APPS ActionId.CLEAR_RECENT_APP -> ActionCategory.APPS - ActionId.MODIFY_SYSTEM_SETTING -> ActionCategory.DISPLAY - ActionId.MODIFY_SECURE_SETTING -> ActionCategory.DISPLAY - ActionId.MODIFY_GLOBAL_SETTING -> ActionCategory.DISPLAY + ActionId.MODIFY_SETTING -> ActionCategory.DISPLAY ActionId.CONSUME_KEY_EVENT -> ActionCategory.SPECIAL } @@ -388,9 +386,7 @@ object ActionUtils { ActionId.FORCE_STOP_APP -> R.string.action_force_stop_app ActionId.CLEAR_RECENT_APP -> R.string.action_clear_recent_app - ActionId.MODIFY_SYSTEM_SETTING -> R.string.action_modify_system_setting - ActionId.MODIFY_SECURE_SETTING -> R.string.action_modify_secure_setting - ActionId.MODIFY_GLOBAL_SETTING -> R.string.action_modify_global_setting + ActionId.MODIFY_SETTING -> R.string.action_modify_setting } @DrawableRes @@ -768,11 +764,7 @@ object ActionUtils { return listOf(Permission.FIND_NEARBY_DEVICES) } - ActionId.MODIFY_SYSTEM_SETTING -> return listOf(Permission.WRITE_SETTINGS) - - ActionId.MODIFY_SECURE_SETTING, - ActionId.MODIFY_GLOBAL_SETTING, - -> return emptyList() // System bridge is required for these + ActionId.MODIFY_SETTING -> return emptyList() // Permissions handled based on setting type at runtime else -> return emptyList() } @@ -905,9 +897,7 @@ object ActionUtils { ActionId.FORCE_STOP_APP -> Icons.Outlined.Dangerous ActionId.CLEAR_RECENT_APP -> Icons.Outlined.VerticalSplit - ActionId.MODIFY_SYSTEM_SETTING -> Icons.Outlined.Settings - ActionId.MODIFY_SECURE_SETTING -> Icons.Outlined.Settings - ActionId.MODIFY_GLOBAL_SETTING -> Icons.Outlined.Settings + ActionId.MODIFY_SETTING -> Icons.Outlined.Settings } } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt index c7d562a326..f82644e708 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt @@ -934,56 +934,24 @@ class CreateActionDelegate( ActionId.FORCE_STOP_APP -> return ActionData.ForceStopApp ActionId.CLEAR_RECENT_APP -> return ActionData.ClearRecentApp - ActionId.MODIFY_SYSTEM_SETTING -> { - val settingKey = when (oldData) { - is ActionData.ModifySetting.System -> oldData.settingKey - else -> "" - } - - val value = when (oldData) { - is ActionData.ModifySetting.System -> oldData.value - else -> "" - } - - modifySettingActionBottomSheetState = ModifySettingActionBottomSheetState.System( - settingKey = settingKey, - value = value, - ) - - return null - } - - ActionId.MODIFY_SECURE_SETTING -> { - val settingKey = when (oldData) { - is ActionData.ModifySetting.Secure -> oldData.settingKey - else -> "" - } - - val value = when (oldData) { - is ActionData.ModifySetting.Secure -> oldData.value - else -> "" + ActionId.MODIFY_SETTING -> { + val settingType = when (oldData) { + is ActionData.ModifySetting -> oldData.settingType + else -> io.github.sds100.keymapper.system.settings.SettingType.SYSTEM // Default to SYSTEM } - modifySettingActionBottomSheetState = ModifySettingActionBottomSheetState.Secure( - settingKey = settingKey, - value = value, - ) - - return null - } - - ActionId.MODIFY_GLOBAL_SETTING -> { val settingKey = when (oldData) { - is ActionData.ModifySetting.Global -> oldData.settingKey + is ActionData.ModifySetting -> oldData.settingKey else -> "" } val value = when (oldData) { - is ActionData.ModifySetting.Global -> oldData.value + is ActionData.ModifySetting -> oldData.value else -> "" } - modifySettingActionBottomSheetState = ModifySettingActionBottomSheetState.Global( + modifySettingActionBottomSheetState = ModifySettingActionBottomSheetState( + settingType = settingType, settingKey = settingKey, value = value, ) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt index c6811719e3..ca25f1be9e 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt @@ -5,7 +5,10 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text @@ -24,25 +27,11 @@ import io.github.sds100.keymapper.base.utils.ui.compose.BottomSheet import io.github.sds100.keymapper.base.utils.ui.compose.BottomSheetDefaults import kotlinx.coroutines.launch -sealed class ModifySettingActionBottomSheetState( - open val settingKey: String, - open val value: String, -) { - data class System( - override val settingKey: String, - override val value: String, - ) : ModifySettingActionBottomSheetState(settingKey, value) - - data class Secure( - override val settingKey: String, - override val value: String, - ) : ModifySettingActionBottomSheetState(settingKey, value) - - data class Global( - override val settingKey: String, - override val value: String, - ) : ModifySettingActionBottomSheetState(settingKey, value) -} +data class ModifySettingActionBottomSheetState( + val settingType: io.github.sds100.keymapper.system.settings.SettingType, + val settingKey: String, + val value: String, +) @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -88,27 +77,28 @@ private fun ModifySettingActionBottomSheetContent( onDismiss: () -> Unit, onComplete: (ActionData.ModifySetting) -> Unit, ) { + var settingType by remember(state) { mutableStateOf(state.settingType) } var settingKey by remember(state) { mutableStateOf(state.settingKey) } var value by remember(state) { mutableStateOf(state.value) } - - val titleRes = when (state) { - is ModifySettingActionBottomSheetState.System -> R.string.action_modify_system_setting - is ModifySettingActionBottomSheetState.Secure -> R.string.action_modify_secure_setting - is ModifySettingActionBottomSheetState.Global -> R.string.action_modify_global_setting + + var settingTypeExpanded by remember { mutableStateOf(false) } + var settingKeyExpanded by remember { mutableStateOf(false) } + + // Available setting keys based on selected type - placeholder, would need SettingsAdapter + val availableKeys = remember(settingType) { + // For now, return empty list. This will be populated via dependency injection + emptyList() } BottomSheet( - title = stringResource(titleRes), + title = stringResource(R.string.action_modify_setting), onDismiss = onDismiss, positiveButton = BottomSheetDefaults.OkButton { - val action = when (state) { - is ModifySettingActionBottomSheetState.System -> - ActionData.ModifySetting.System(settingKey, value) - is ModifySettingActionBottomSheetState.Secure -> - ActionData.ModifySetting.Secure(settingKey, value) - is ModifySettingActionBottomSheetState.Global -> - ActionData.ModifySetting.Global(settingKey, value) - } + val action = ActionData.ModifySetting( + settingType = settingType, + settingKey = settingKey, + value = value, + ) onComplete(action) }, positiveButtonEnabled = settingKey.isNotBlank() && value.isNotBlank(), @@ -120,6 +110,58 @@ private fun ModifySettingActionBottomSheetContent( ) { Spacer(modifier = Modifier.height(16.dp)) + // Setting Type Dropdown + ExposedDropdownMenuBox( + expanded = settingTypeExpanded, + onExpandedChange = { settingTypeExpanded = it }, + ) { + OutlinedTextField( + value = when (settingType) { + io.github.sds100.keymapper.system.settings.SettingType.SYSTEM -> + stringResource(R.string.modify_setting_type_system) + io.github.sds100.keymapper.system.settings.SettingType.SECURE -> + stringResource(R.string.modify_setting_type_secure) + io.github.sds100.keymapper.system.settings.SettingType.GLOBAL -> + stringResource(R.string.modify_setting_type_global) + }, + onValueChange = {}, + readOnly = true, + label = { Text(stringResource(R.string.modify_setting_type_label)) }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = settingTypeExpanded) }, + modifier = Modifier.fillMaxWidth().menuAnchor(), + singleLine = true, + ) + ExposedDropdownMenu( + expanded = settingTypeExpanded, + onDismissRequest = { settingTypeExpanded = false }, + ) { + DropdownMenuItem( + text = { Text(stringResource(R.string.modify_setting_type_system)) }, + onClick = { + settingType = io.github.sds100.keymapper.system.settings.SettingType.SYSTEM + settingTypeExpanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.modify_setting_type_secure)) }, + onClick = { + settingType = io.github.sds100.keymapper.system.settings.SettingType.SECURE + settingTypeExpanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.modify_setting_type_global)) }, + onClick = { + settingType = io.github.sds100.keymapper.system.settings.SettingType.GLOBAL + settingTypeExpanded = false + }, + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // Setting Key - allow both dropdown selection and manual entry OutlinedTextField( value = settingKey, onValueChange = { settingKey = it }, @@ -140,12 +182,12 @@ private fun ModifySettingActionBottomSheetContent( Spacer(modifier = Modifier.height(16.dp)) - val exampleText = when (state) { - is ModifySettingActionBottomSheetState.System -> + val exampleText = when (settingType) { + io.github.sds100.keymapper.system.settings.SettingType.SYSTEM -> stringResource(R.string.modify_setting_example_system) - is ModifySettingActionBottomSheetState.Secure -> + io.github.sds100.keymapper.system.settings.SettingType.SECURE -> stringResource(R.string.modify_setting_example_secure) - is ModifySettingActionBottomSheetState.Global -> + io.github.sds100.keymapper.system.settings.SettingType.GLOBAL -> stringResource(R.string.modify_setting_example_global) } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt index be71b8df87..47a984007f 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt @@ -1017,16 +1017,15 @@ class PerformActionsUseCaseImpl @AssistedInject constructor( } } - is ActionData.ModifySetting.System -> { - result = displayAdapter.modifySystemSetting(action.settingKey, action.value) - } - - is ActionData.ModifySetting.Secure -> { - result = displayAdapter.modifySecureSetting(action.settingKey, action.value) - } - - is ActionData.ModifySetting.Global -> { - result = displayAdapter.modifyGlobalSetting(action.settingKey, action.value) + is ActionData.ModifySetting -> { + result = when (action.settingType) { + io.github.sds100.keymapper.system.settings.SettingType.SYSTEM -> + displayAdapter.modifySystemSetting(action.settingKey, action.value) + io.github.sds100.keymapper.system.settings.SettingType.SECURE -> + displayAdapter.modifySecureSetting(action.settingKey, action.value) + io.github.sds100.keymapper.system.settings.SettingType.GLOBAL -> + displayAdapter.modifyGlobalSetting(action.settingKey, action.value) + } } } diff --git a/base/src/main/res/values/strings.xml b/base/src/main/res/values/strings.xml index fef9f998be..69a63fa14a 100644 --- a/base/src/main/res/values/strings.xml +++ b/base/src/main/res/values/strings.xml @@ -1164,11 +1164,10 @@ Force stop app Close and clear app from recents - Modify system setting - Modify secure setting - Modify global setting + Modify setting Setting Key Value + Setting Type Example: screen_off_timeout = 2147483647 (System settings use WRITE_SETTINGS permission) Example: accessibility_enabled = 1 (Secure settings require system bridge) Example: airplane_mode_on = 1 (Global settings require system bridge) diff --git a/data/src/main/java/io/github/sds100/keymapper/data/entities/ActionEntity.kt b/data/src/main/java/io/github/sds100/keymapper/data/entities/ActionEntity.kt index 02a3ff8507..20d60ac50a 100644 --- a/data/src/main/java/io/github/sds100/keymapper/data/entities/ActionEntity.kt +++ b/data/src/main/java/io/github/sds100/keymapper/data/entities/ActionEntity.kt @@ -141,6 +141,7 @@ data class ActionEntity( const val EXTRA_HOLD_DOWN_DURATION = "extra_hold_down_duration" const val EXTRA_REPEAT_LIMIT = "extra_repeat_limit" const val EXTRA_SETTING_VALUE = "extra_setting_value" + const val EXTRA_SETTING_TYPE = "extra_setting_type" val DESERIALIZER = jsonDeserializer { val typeString by it.json.byString(NAME_ACTION_TYPE) diff --git a/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingType.kt b/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingType.kt new file mode 100644 index 0000000000..8e0a5fd5e0 --- /dev/null +++ b/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingType.kt @@ -0,0 +1,10 @@ +package io.github.sds100.keymapper.system.settings + +import kotlinx.serialization.Serializable + +@Serializable +enum class SettingType { + SYSTEM, + SECURE, + GLOBAL, +} diff --git a/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt new file mode 100644 index 0000000000..fb822f3c42 --- /dev/null +++ b/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt @@ -0,0 +1,57 @@ +package io.github.sds100.keymapper.system.settings + +import android.content.Context +import android.database.Cursor +import android.net.Uri +import android.provider.Settings +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SettingsAdapter @Inject constructor( + @ApplicationContext private val context: Context, +) { + private val ctx = context.applicationContext + + fun getSystemSettingKeys(): List { + return getSettingKeys(Settings.System.CONTENT_URI) + } + + fun getSecureSettingKeys(): List { + return getSettingKeys(Settings.Secure.CONTENT_URI) + } + + fun getGlobalSettingKeys(): List { + return getSettingKeys(Settings.Global.CONTENT_URI) + } + + private fun getSettingKeys(uri: Uri): List { + val keys = mutableListOf() + var cursor: Cursor? = null + try { + cursor = ctx.contentResolver.query( + uri, + arrayOf("name"), + null, + null, + null, + ) + + cursor?.use { + val nameIndex = it.getColumnIndex("name") + if (nameIndex >= 0) { + while (it.moveToNext()) { + val name = it.getString(nameIndex) + if (!name.isNullOrBlank()) { + keys.add(name) + } + } + } + } + } catch (e: Exception) { + // Some devices may not allow querying all settings + } + return keys.sorted() + } +} From 51b18ebf1de28004673b8d1eff1a38591929c3cb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 14:11:55 +0000 Subject: [PATCH 07/23] Refactor settings modification to use SettingsAdapter and permissions - Move MODIFY_SETTING to APPS category - Create AndroidSettingsAdapter interface implementing SettingsAdapter - Use SettingsUtils for all setting modifications (no SystemBridge needed) - Remove SystemBridge methods for settings (putSystemSetting, etc) - Add permission checks: WRITE_SETTINGS for System, WRITE_SECURE_SETTINGS for Secure/Global - Remove setting methods from DisplayAdapter Co-authored-by: sds100 <16245954+sds100@users.noreply.github.com> --- .../base/actions/ActionErrorSnapshot.kt | 20 +++++ .../keymapper/base/actions/ActionUtils.kt | 3 +- .../base/actions/PerformActionsUseCase.kt | 7 +- .../keymapper/sysbridge/ISystemBridge.aidl | 4 - .../sysbridge/service/SystemBridge.kt | 47 ---------- .../system/display/AndroidDisplayAdapter.kt | 52 ----------- .../system/display/DisplayAdapter.kt | 4 - .../system/settings/SettingsAdapter.kt | 86 +++++++++++++++++-- 8 files changed, 106 insertions(+), 117 deletions(-) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionErrorSnapshot.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionErrorSnapshot.kt index a20a449237..6d453d93b5 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionErrorSnapshot.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionErrorSnapshot.kt @@ -231,6 +231,26 @@ class LazyActionErrorSnapshot( } } + is ActionData.ModifySetting -> { + return when (action.settingType) { + io.github.sds100.keymapper.system.settings.SettingType.SYSTEM -> { + if (!isPermissionGranted(Permission.WRITE_SETTINGS)) { + SystemError.PermissionDenied(Permission.WRITE_SETTINGS) + } else { + null + } + } + io.github.sds100.keymapper.system.settings.SettingType.SECURE, + io.github.sds100.keymapper.system.settings.SettingType.GLOBAL -> { + if (!isPermissionGranted(Permission.WRITE_SECURE_SETTINGS)) { + SystemError.PermissionDenied(Permission.WRITE_SECURE_SETTINGS) + } else { + null + } + } + } + } + else -> {} } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt index 5d7338e57f..81b8464d8c 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt @@ -253,8 +253,7 @@ object ActionUtils { ActionId.INTERACT_UI_ELEMENT -> ActionCategory.APPS ActionId.FORCE_STOP_APP -> ActionCategory.APPS ActionId.CLEAR_RECENT_APP -> ActionCategory.APPS - - ActionId.MODIFY_SETTING -> ActionCategory.DISPLAY + ActionId.MODIFY_SETTING -> ActionCategory.APPS ActionId.CONSUME_KEY_EVENT -> ActionCategory.SPECIAL } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt index 47a984007f..ec168ca2ca 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt @@ -120,6 +120,7 @@ class PerformActionsUseCaseImpl @AssistedInject constructor( private val settingsRepository: PreferenceRepository, private val inputEventHub: InputEventHub, private val systemBridgeConnectionManager: SystemBridgeConnectionManager, + private val settingsAdapter: io.github.sds100.keymapper.system.settings.SettingsAdapter, ) : PerformActionsUseCase { @AssistedFactory @@ -1020,11 +1021,11 @@ class PerformActionsUseCaseImpl @AssistedInject constructor( is ActionData.ModifySetting -> { result = when (action.settingType) { io.github.sds100.keymapper.system.settings.SettingType.SYSTEM -> - displayAdapter.modifySystemSetting(action.settingKey, action.value) + settingsAdapter.modifySystemSetting(action.settingKey, action.value) io.github.sds100.keymapper.system.settings.SettingType.SECURE -> - displayAdapter.modifySecureSetting(action.settingKey, action.value) + settingsAdapter.modifySecureSetting(action.settingKey, action.value) io.github.sds100.keymapper.system.settings.SettingType.GLOBAL -> - displayAdapter.modifyGlobalSetting(action.settingKey, action.value) + settingsAdapter.modifyGlobalSetting(action.settingKey, action.value) } } } diff --git a/sysbridge/src/main/aidl/io/github/sds100/keymapper/sysbridge/ISystemBridge.aidl b/sysbridge/src/main/aidl/io/github/sds100/keymapper/sysbridge/ISystemBridge.aidl index d7fcc36c79..602310a084 100644 --- a/sysbridge/src/main/aidl/io/github/sds100/keymapper/sysbridge/ISystemBridge.aidl +++ b/sysbridge/src/main/aidl/io/github/sds100/keymapper/sysbridge/ISystemBridge.aidl @@ -42,8 +42,4 @@ interface ISystemBridge { void removeTasks(String packageName) = 17; void setRingerMode(int ringerMode) = 18; - - boolean putSystemSetting(String key, String value) = 19; - boolean putSecureSetting(String key, String value) = 20; - boolean putGlobalSetting(String key, String value) = 21; } \ No newline at end of file diff --git a/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridge.kt b/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridge.kt index 60baba88d2..e89edb6930 100644 --- a/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridge.kt +++ b/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridge.kt @@ -668,51 +668,4 @@ internal class SystemBridge : ISystemBridge.Stub() { audioService.setRingerModeInternal(ringerMode, processPackageName) } - - override fun putSystemSetting(key: String?, value: String?): Boolean { - if (key == null || value == null) { - return false - } - - return try { - // SystemBridge runs as shell/root user, so we can directly access Settings - // We need to use the system context's content resolver - val contentResolver = android.app.ActivityThread.systemMain() - .getSystemContext().contentResolver - Settings.System.putString(contentResolver, key, value) - } catch (e: Exception) { - Log.e(TAG, "Failed to put system setting: $key=$value", e) - false - } - } - - override fun putSecureSetting(key: String?, value: String?): Boolean { - if (key == null || value == null) { - return false - } - - return try { - val contentResolver = android.app.ActivityThread.systemMain() - .getSystemContext().contentResolver - Settings.Secure.putString(contentResolver, key, value) - } catch (e: Exception) { - Log.e(TAG, "Failed to put secure setting: $key=$value", e) - false - } - } - - override fun putGlobalSetting(key: String?, value: String?): Boolean { - if (key == null || value == null) { - return false - } - - return try { - val contentResolver = android.app.ActivityThread.systemMain() - .getSystemContext().contentResolver - Settings.Global.putString(contentResolver, key, value) - } catch (e: Exception) { - Log.e(TAG, "Failed to put global setting: $key=$value", e) - false - } - } } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/display/AndroidDisplayAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/display/AndroidDisplayAdapter.kt index 770894e74d..9067b25632 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/display/AndroidDisplayAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/display/AndroidDisplayAdapter.kt @@ -18,7 +18,6 @@ import io.github.sds100.keymapper.common.utils.SettingsUtils import io.github.sds100.keymapper.common.utils.SizeKM import io.github.sds100.keymapper.common.utils.Success import io.github.sds100.keymapper.common.utils.getRealDisplaySize -import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow @@ -33,7 +32,6 @@ import javax.inject.Singleton class AndroidDisplayAdapter @Inject constructor( @ApplicationContext private val context: Context, private val coroutineScope: CoroutineScope, - private val systemBridgeConnectionManager: SystemBridgeConnectionManager, ) : DisplayAdapter { companion object { @@ -255,54 +253,4 @@ class AndroidDisplayAdapter @Inject constructor( private fun isAodEnabled(): Boolean { return SettingsUtils.getSecureSetting(ctx, "doze_always_on") == 1 } - - override fun modifySystemSetting(key: String, value: String): KMResult<*> { - // Try to parse value as different types and use the appropriate method - val success = when { - value.toIntOrNull() != null -> SettingsUtils.putSystemSetting(ctx, key, value.toInt()) - value.toLongOrNull() != null -> SettingsUtils.putSystemSetting(ctx, key, value.toLong()) - value.toFloatOrNull() != null -> SettingsUtils.putSystemSetting(ctx, key, value.toFloat()) - else -> SettingsUtils.putSystemSetting(ctx, key, value) - } - - return if (success) { - Success(Unit) - } else { - KMError.FailedToModifySystemSetting(key) - } - } - - override fun modifySecureSetting(key: String, value: String): KMResult<*> { - return systemBridgeConnectionManager.run { bridge -> - val success = when { - value.toIntOrNull() != null -> bridge.putSecureSetting(key, value) - value.toLongOrNull() != null -> bridge.putSecureSetting(key, value) - value.toFloatOrNull() != null -> bridge.putSecureSetting(key, value) - else -> bridge.putSecureSetting(key, value) - } - - if (success) { - Success(Unit) - } else { - KMError.FailedToModifySystemSetting(key) - } - } - } - - override fun modifyGlobalSetting(key: String, value: String): KMResult<*> { - return systemBridgeConnectionManager.run { bridge -> - val success = when { - value.toIntOrNull() != null -> bridge.putGlobalSetting(key, value) - value.toLongOrNull() != null -> bridge.putGlobalSetting(key, value) - value.toFloatOrNull() != null -> bridge.putGlobalSetting(key, value) - else -> bridge.putGlobalSetting(key, value) - } - - if (success) { - Success(Unit) - } else { - KMError.FailedToModifySystemSetting(key) - } - } - } } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/display/DisplayAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/display/DisplayAdapter.kt index 2881c423fd..a38fe5a177 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/display/DisplayAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/display/DisplayAdapter.kt @@ -27,8 +27,4 @@ interface DisplayAdapter { fun decreaseBrightness(): KMResult<*> fun enableAutoBrightness(): KMResult<*> fun disableAutoBrightness(): KMResult<*> - - fun modifySystemSetting(key: String, value: String): KMResult<*> - fun modifySecureSetting(key: String, value: String): KMResult<*> - fun modifyGlobalSetting(key: String, value: String): KMResult<*> } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt index fb822f3c42..032483db65 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt @@ -5,27 +5,89 @@ import android.database.Cursor import android.net.Uri import android.provider.Settings import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.sds100.keymapper.common.utils.KMError +import io.github.sds100.keymapper.common.utils.KMResult +import io.github.sds100.keymapper.common.utils.SettingsUtils +import io.github.sds100.keymapper.common.utils.Success import javax.inject.Inject import javax.inject.Singleton @Singleton -class SettingsAdapter @Inject constructor( +class AndroidSettingsAdapter @Inject constructor( @ApplicationContext private val context: Context, -) { +) : SettingsAdapter { private val ctx = context.applicationContext - fun getSystemSettingKeys(): List { + override fun getSystemSettingKeys(): List { return getSettingKeys(Settings.System.CONTENT_URI) } - fun getSecureSettingKeys(): List { + override fun getSecureSettingKeys(): List { return getSettingKeys(Settings.Secure.CONTENT_URI) } - fun getGlobalSettingKeys(): List { + override fun getGlobalSettingKeys(): List { return getSettingKeys(Settings.Global.CONTENT_URI) } + override fun getSystemSettingValue(key: String): String? { + return SettingsUtils.getSystemSetting(ctx, key) + } + + override fun getSecureSettingValue(key: String): String? { + return SettingsUtils.getSecureSetting(ctx, key) + } + + override fun getGlobalSettingValue(key: String): String? { + return SettingsUtils.getGlobalSetting(ctx, key) + } + + override fun modifySystemSetting(key: String, value: String): KMResult<*> { + // Try to parse value as different types and use the appropriate method + val success = when { + value.toIntOrNull() != null -> SettingsUtils.putSystemSetting(ctx, key, value.toInt()) + value.toLongOrNull() != null -> SettingsUtils.putSystemSetting(ctx, key, value.toLong()) + value.toFloatOrNull() != null -> SettingsUtils.putSystemSetting(ctx, key, value.toFloat()) + else -> SettingsUtils.putSystemSetting(ctx, key, value) + } + + return if (success) { + Success(Unit) + } else { + KMError.FailedToModifySystemSetting(key) + } + } + + override fun modifySecureSetting(key: String, value: String): KMResult<*> { + val success = when { + value.toIntOrNull() != null -> SettingsUtils.putSecureSetting(ctx, key, value.toInt()) + value.toLongOrNull() != null -> SettingsUtils.putSecureSetting(ctx, key, value.toLong()) + value.toFloatOrNull() != null -> SettingsUtils.putSecureSetting(ctx, key, value.toFloat()) + else -> SettingsUtils.putSecureSetting(ctx, key, value) + } + + return if (success) { + Success(Unit) + } else { + KMError.FailedToModifySystemSetting(key) + } + } + + override fun modifyGlobalSetting(key: String, value: String): KMResult<*> { + val success = when { + value.toIntOrNull() != null -> SettingsUtils.putGlobalSetting(ctx, key, value.toInt()) + value.toLongOrNull() != null -> SettingsUtils.putGlobalSetting(ctx, key, value.toLong()) + value.toFloatOrNull() != null -> SettingsUtils.putGlobalSetting(ctx, key, value.toFloat()) + else -> SettingsUtils.putGlobalSetting(ctx, key, value) + } + + return if (success) { + Success(Unit) + } else { + KMError.FailedToModifySystemSetting(key) + } + } + private fun getSettingKeys(uri: Uri): List { val keys = mutableListOf() var cursor: Cursor? = null @@ -55,3 +117,17 @@ class SettingsAdapter @Inject constructor( return keys.sorted() } } + +interface SettingsAdapter { + fun getSystemSettingKeys(): List + fun getSecureSettingKeys(): List + fun getGlobalSettingKeys(): List + + fun getSystemSettingValue(key: String): String? + fun getSecureSettingValue(key: String): String? + fun getGlobalSettingValue(key: String): String? + + fun modifySystemSetting(key: String, value: String): KMResult<*> + fun modifySecureSetting(key: String, value: String): KMResult<*> + fun modifyGlobalSetting(key: String, value: String): KMResult<*> +} From b57cdf842cd06e005545e35093a915434197c37d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 14:16:32 +0000 Subject: [PATCH 08/23] Add ChooseSettingScreen and ViewModel for setting selection - Created ChooseSettingViewModel with setting type selection and search - Created ChooseSettingScreen with dropdown for type and searchable list - Added ChooseSetting nav destination - Added string resources for choosesetting screen - Screen shows all available settings with current values - User can select from existing settings or enter custom key Next: Wire up navigation in CreateActionDelegate Co-authored-by: sds100 <16245954+sds100@users.noreply.github.com> --- .../base/actions/ChooseSettingScreen.kt | 198 ++++++++++++++++++ .../base/actions/ChooseSettingViewModel.kt | 96 +++++++++ .../base/utils/navigation/NavDestination.kt | 8 + base/src/main/res/values/strings.xml | 3 + 4 files changed, 305 insertions(+) create mode 100644 base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt create mode 100644 base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt new file mode 100644 index 0000000000..fd94fe562b --- /dev/null +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt @@ -0,0 +1,198 @@ +package io.github.sds100.keymapper.base.actions + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material3.BottomAppBar +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.ListItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import io.github.sds100.keymapper.base.R +import io.github.sds100.keymapper.base.utils.ui.compose.SearchAppBarActions +import io.github.sds100.keymapper.common.utils.State +import kotlinx.coroutines.flow.update + +@Composable +fun ChooseSettingScreen(modifier: Modifier = Modifier, viewModel: ChooseSettingViewModel) { + val state by viewModel.settings.collectAsStateWithLifecycle() + val query by viewModel.searchQuery.collectAsStateWithLifecycle() + val settingType by viewModel.selectedSettingType.collectAsStateWithLifecycle() + + ChooseSettingScreen( + modifier = modifier, + state = state, + query = query, + settingType = settingType, + onQueryChange = { newQuery -> viewModel.searchQuery.update { newQuery } }, + onCloseSearch = { viewModel.searchQuery.update { null } }, + onSettingTypeChange = { viewModel.selectedSettingType.value = it }, + onClickSetting = viewModel::onSettingClick, + onNavigateBack = viewModel::onNavigateBack, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun ChooseSettingScreen( + modifier: Modifier = Modifier, + state: State>, + query: String? = null, + settingType: io.github.sds100.keymapper.system.settings.SettingType, + onQueryChange: (String) -> Unit = {}, + onCloseSearch: () -> Unit = {}, + onSettingTypeChange: (io.github.sds100.keymapper.system.settings.SettingType) -> Unit = {}, + onClickSetting: (String, String?) -> Unit = { _, _ -> }, + onNavigateBack: () -> Unit = {}, +) { + Scaffold( + modifier = modifier.displayCutoutPadding(), + topBar = { + TopAppBar( + title = { Text(stringResource(R.string.choose_setting_title)) }, + ) + }, + bottomBar = { + BottomAppBar( + modifier = Modifier.imePadding(), + actions = { + SearchAppBarActions( + onCloseSearch = onCloseSearch, + onNavigateBack = onNavigateBack, + onQueryChange = onQueryChange, + enabled = state is State.Data, + query = query, + ) + }, + ) + }, + ) { innerPadding -> + Surface( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding), + ) { + Column(modifier = Modifier.fillMaxSize()) { + // Setting type dropdown + var expanded by remember { mutableStateOf(false) } + + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { expanded = it }, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) { + OutlinedTextField( + value = when (settingType) { + io.github.sds100.keymapper.system.settings.SettingType.SYSTEM -> + stringResource(R.string.modify_setting_type_system) + io.github.sds100.keymapper.system.settings.SettingType.SECURE -> + stringResource(R.string.modify_setting_type_secure) + io.github.sds100.keymapper.system.settings.SettingType.GLOBAL -> + stringResource(R.string.modify_setting_type_global) + }, + onValueChange = {}, + readOnly = true, + label = { Text(stringResource(R.string.modify_setting_type_label)) }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, + modifier = Modifier.fillMaxWidth().menuAnchor(), + singleLine = true, + ) + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + ) { + DropdownMenuItem( + text = { Text(stringResource(R.string.modify_setting_type_system)) }, + onClick = { + onSettingTypeChange(io.github.sds100.keymapper.system.settings.SettingType.SYSTEM) + expanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.modify_setting_type_secure)) }, + onClick = { + onSettingTypeChange(io.github.sds100.keymapper.system.settings.SettingType.SECURE) + expanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.modify_setting_type_global)) }, + onClick = { + onSettingTypeChange(io.github.sds100.keymapper.system.settings.SettingType.GLOBAL) + expanded = false + }, + ) + } + } + + HorizontalDivider() + + // Settings list + when (state) { + State.Loading -> { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } + } + is State.Data -> { + if (state.data.isEmpty()) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text( + text = stringResource(R.string.choose_setting_empty), + style = MaterialTheme.typography.bodyLarge, + ) + } + } else { + LazyColumn(modifier = Modifier.fillMaxSize()) { + items(state.data) { item -> + ListItem( + headlineContent = { Text(item.key) }, + supportingContent = item.value?.let { { Text(it) } }, + modifier = Modifier.clickable { + onClickSetting(item.key, item.value) + }, + ) + HorizontalDivider() + } + } + } + } + } + } + } + } +} diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt new file mode 100644 index 0000000000..06bf1e6a54 --- /dev/null +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt @@ -0,0 +1,96 @@ +package io.github.sds100.keymapper.base.actions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider +import io.github.sds100.keymapper.base.utils.ui.DialogProvider +import io.github.sds100.keymapper.base.utils.ui.ResourceProvider +import io.github.sds100.keymapper.common.utils.State +import io.github.sds100.keymapper.system.settings.SettingsAdapter +import javax.inject.Inject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +data class SettingItem( + val key: String, + val value: String?, +) + +@HiltViewModel +class ChooseSettingViewModel @Inject constructor( + private val settingsAdapter: SettingsAdapter, + resourceProvider: ResourceProvider, + navigationProvider: NavigationProvider, + dialogProvider: DialogProvider, +) : ViewModel(), + ResourceProvider by resourceProvider, + DialogProvider by dialogProvider, + NavigationProvider by navigationProvider { + + val searchQuery = MutableStateFlow(null) + val selectedSettingType = MutableStateFlow(io.github.sds100.keymapper.system.settings.SettingType.SYSTEM) + + val settings: StateFlow>> = + combine(selectedSettingType, searchQuery) { type, query -> + val keys = when (type) { + io.github.sds100.keymapper.system.settings.SettingType.SYSTEM -> + settingsAdapter.getSystemSettingKeys() + io.github.sds100.keymapper.system.settings.SettingType.SECURE -> + settingsAdapter.getSecureSettingKeys() + io.github.sds100.keymapper.system.settings.SettingType.GLOBAL -> + settingsAdapter.getGlobalSettingKeys() + } + + val items = keys + .filter { query == null || it.contains(query, ignoreCase = true) } + .map { key -> + val value = when (type) { + io.github.sds100.keymapper.system.settings.SettingType.SYSTEM -> + settingsAdapter.getSystemSettingValue(key) + io.github.sds100.keymapper.system.settings.SettingType.SECURE -> + settingsAdapter.getSecureSettingValue(key) + io.github.sds100.keymapper.system.settings.SettingType.GLOBAL -> + settingsAdapter.getGlobalSettingValue(key) + } + SettingItem(key, value) + } + + State.Data(items) + }.flowOn(Dispatchers.Default) + .stateIn(viewModelScope, SharingStarted.Eagerly, State.Loading) + + fun onNavigateBack() { + viewModelScope.launch { + popBackStack() + } + } + + fun onSettingClick(key: String, currentValue: String?) { + viewModelScope.launch { + popBackStackWithResult( + kotlinx.serialization.json.Json.encodeToString( + ChooseSettingResult.serializer(), + ChooseSettingResult( + settingType = selectedSettingType.value, + key = key, + currentValue = currentValue, + ), + ), + ) + } + } +} + +@kotlinx.serialization.Serializable +data class ChooseSettingResult( + val settingType: io.github.sds100.keymapper.system.settings.SettingType, + val key: String, + val currentValue: String?, +) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/utils/navigation/NavDestination.kt b/base/src/main/java/io/github/sds100/keymapper/base/utils/navigation/NavDestination.kt index fdbc390a85..3683bb9599 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/utils/navigation/NavDestination.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/utils/navigation/NavDestination.kt @@ -38,6 +38,7 @@ abstract class NavDestination(val isCompose: Boolean = false) { const val ID_CONFIG_KEY_MAP = "config_key_map" const val ID_INTERACT_UI_ELEMENT_ACTION = "interact_ui_element_action" const val ID_SHELL_COMMAND_ACTION = "shell_command_action" + const val ID_CHOOSE_SETTING = "choose_setting" const val ID_PRO_MODE = "pro_mode" const val ID_LOG = "log" const val ID_ADVANCED_TRIGGERS = "advanced_triggers" @@ -172,6 +173,13 @@ abstract class NavDestination(val isCompose: Boolean = false) { override val id: String = ID_SHELL_COMMAND_ACTION } + @Serializable + data class ChooseSetting( + val currentSettingType: io.github.sds100.keymapper.system.settings.SettingType?, + ) : NavDestination(isCompose = true) { + override val id: String = ID_CHOOSE_SETTING + } + @Serializable data object ProMode : NavDestination(isCompose = true) { override val id: String = ID_PRO_MODE diff --git a/base/src/main/res/values/strings.xml b/base/src/main/res/values/strings.xml index 69a63fa14a..9bcff9020f 100644 --- a/base/src/main/res/values/strings.xml +++ b/base/src/main/res/values/strings.xml @@ -1171,6 +1171,9 @@ Example: screen_off_timeout = 2147483647 (System settings use WRITE_SETTINGS permission) Example: accessibility_enabled = 1 (Secure settings require system bridge) Example: airplane_mode_on = 1 (Global settings require system bridge) + Choose Setting + No settings found + Choose Existing Setting From c1f999f3939a66e96da7a139f5c0ab6217ffadc6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 14:24:06 +0000 Subject: [PATCH 09/23] Fix code review feedback: remove fully qualified names and use KeyMapperDropdownMenu - Import SettingType instead of using fully qualified names - Import Json and Serializable for cleaner code - Replace custom dropdown with KeyMapperDropdownMenu in ChooseSettingScreen - Remove unused imports - Update NavDestination to use imported SettingType Co-authored-by: sds100 <16245954+sds100@users.noreply.github.com> --- .../base/actions/ChooseSettingScreen.kt | 75 ++++--------------- .../base/actions/ChooseSettingViewModel.kt | 23 +++--- .../base/utils/navigation/NavDestination.kt | 7 +- 3 files changed, 33 insertions(+), 72 deletions(-) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt index fd94fe562b..2d5bb9dffb 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt @@ -3,29 +3,19 @@ package io.github.sds100.keymapper.base.actions import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.displayCutoutPadding import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Settings import androidx.compose.material3.BottomAppBar import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -41,8 +31,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.sds100.keymapper.base.R +import io.github.sds100.keymapper.base.utils.ui.compose.KeyMapperDropdownMenu import io.github.sds100.keymapper.base.utils.ui.compose.SearchAppBarActions import io.github.sds100.keymapper.common.utils.State +import io.github.sds100.keymapper.system.settings.SettingType import kotlinx.coroutines.flow.update @Composable @@ -70,10 +62,10 @@ private fun ChooseSettingScreen( modifier: Modifier = Modifier, state: State>, query: String? = null, - settingType: io.github.sds100.keymapper.system.settings.SettingType, + settingType: SettingType, onQueryChange: (String) -> Unit = {}, onCloseSearch: () -> Unit = {}, - onSettingTypeChange: (io.github.sds100.keymapper.system.settings.SettingType) -> Unit = {}, + onSettingTypeChange: (SettingType) -> Unit = {}, onClickSetting: (String, String?) -> Unit = { _, _ -> }, onNavigateBack: () -> Unit = {}, ) { @@ -108,56 +100,21 @@ private fun ChooseSettingScreen( // Setting type dropdown var expanded by remember { mutableStateOf(false) } - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = { expanded = it }, + KeyMapperDropdownMenu( modifier = Modifier .fillMaxWidth() .padding(16.dp), - ) { - OutlinedTextField( - value = when (settingType) { - io.github.sds100.keymapper.system.settings.SettingType.SYSTEM -> - stringResource(R.string.modify_setting_type_system) - io.github.sds100.keymapper.system.settings.SettingType.SECURE -> - stringResource(R.string.modify_setting_type_secure) - io.github.sds100.keymapper.system.settings.SettingType.GLOBAL -> - stringResource(R.string.modify_setting_type_global) - }, - onValueChange = {}, - readOnly = true, - label = { Text(stringResource(R.string.modify_setting_type_label)) }, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, - modifier = Modifier.fillMaxWidth().menuAnchor(), - singleLine = true, - ) - ExposedDropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - ) { - DropdownMenuItem( - text = { Text(stringResource(R.string.modify_setting_type_system)) }, - onClick = { - onSettingTypeChange(io.github.sds100.keymapper.system.settings.SettingType.SYSTEM) - expanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.modify_setting_type_secure)) }, - onClick = { - onSettingTypeChange(io.github.sds100.keymapper.system.settings.SettingType.SECURE) - expanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.modify_setting_type_global)) }, - onClick = { - onSettingTypeChange(io.github.sds100.keymapper.system.settings.SettingType.GLOBAL) - expanded = false - }, - ) - } - } + expanded = expanded, + onExpandedChange = { expanded = it }, + label = { Text(stringResource(R.string.modify_setting_type_label)) }, + selectedValue = settingType, + values = listOf( + SettingType.SYSTEM to stringResource(R.string.modify_setting_type_system), + SettingType.SECURE to stringResource(R.string.modify_setting_type_secure), + SettingType.GLOBAL to stringResource(R.string.modify_setting_type_global), + ), + onValueChanged = onSettingTypeChange, + ) HorizontalDivider() diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt index 06bf1e6a54..6227002f4b 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt @@ -8,6 +8,7 @@ import io.github.sds100.keymapper.base.utils.ui.DialogProvider import io.github.sds100.keymapper.base.utils.ui.ResourceProvider import io.github.sds100.keymapper.common.utils.State import io.github.sds100.keymapper.system.settings.SettingsAdapter +import io.github.sds100.keymapper.system.settings.SettingType import javax.inject.Inject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -17,6 +18,8 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json data class SettingItem( val key: String, @@ -35,16 +38,16 @@ class ChooseSettingViewModel @Inject constructor( NavigationProvider by navigationProvider { val searchQuery = MutableStateFlow(null) - val selectedSettingType = MutableStateFlow(io.github.sds100.keymapper.system.settings.SettingType.SYSTEM) + val selectedSettingType = MutableStateFlow(SettingType.SYSTEM) val settings: StateFlow>> = combine(selectedSettingType, searchQuery) { type, query -> val keys = when (type) { - io.github.sds100.keymapper.system.settings.SettingType.SYSTEM -> + SettingType.SYSTEM -> settingsAdapter.getSystemSettingKeys() - io.github.sds100.keymapper.system.settings.SettingType.SECURE -> + SettingType.SECURE -> settingsAdapter.getSecureSettingKeys() - io.github.sds100.keymapper.system.settings.SettingType.GLOBAL -> + SettingType.GLOBAL -> settingsAdapter.getGlobalSettingKeys() } @@ -52,11 +55,11 @@ class ChooseSettingViewModel @Inject constructor( .filter { query == null || it.contains(query, ignoreCase = true) } .map { key -> val value = when (type) { - io.github.sds100.keymapper.system.settings.SettingType.SYSTEM -> + SettingType.SYSTEM -> settingsAdapter.getSystemSettingValue(key) - io.github.sds100.keymapper.system.settings.SettingType.SECURE -> + SettingType.SECURE -> settingsAdapter.getSecureSettingValue(key) - io.github.sds100.keymapper.system.settings.SettingType.GLOBAL -> + SettingType.GLOBAL -> settingsAdapter.getGlobalSettingValue(key) } SettingItem(key, value) @@ -75,7 +78,7 @@ class ChooseSettingViewModel @Inject constructor( fun onSettingClick(key: String, currentValue: String?) { viewModelScope.launch { popBackStackWithResult( - kotlinx.serialization.json.Json.encodeToString( + Json.encodeToString( ChooseSettingResult.serializer(), ChooseSettingResult( settingType = selectedSettingType.value, @@ -88,9 +91,9 @@ class ChooseSettingViewModel @Inject constructor( } } -@kotlinx.serialization.Serializable +@Serializable data class ChooseSettingResult( - val settingType: io.github.sds100.keymapper.system.settings.SettingType, + val settingType: SettingType, val key: String, val currentValue: String?, ) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/utils/navigation/NavDestination.kt b/base/src/main/java/io/github/sds100/keymapper/base/utils/navigation/NavDestination.kt index 3683bb9599..ef674f495d 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/utils/navigation/NavDestination.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/utils/navigation/NavDestination.kt @@ -1,6 +1,6 @@ -package io.github.sds100.keymapper.base.utils.navigation - +import io.github.sds100.keymapper.base.utils.navigation import io.github.sds100.keymapper.base.actions.ActionData +import io.github.sds100.keymapper.base.actions.ChooseSettingResult import io.github.sds100.keymapper.base.actions.pinchscreen.PinchPickCoordinateResult import io.github.sds100.keymapper.base.actions.swipescreen.SwipePickCoordinateResult import io.github.sds100.keymapper.base.actions.tapscreen.PickCoordinateResult @@ -10,6 +10,7 @@ import io.github.sds100.keymapper.base.system.intents.ConfigIntentResult import io.github.sds100.keymapper.base.trigger.TriggerSetupShortcut import io.github.sds100.keymapper.system.apps.ActivityInfo import io.github.sds100.keymapper.system.bluetooth.BluetoothDeviceInfo +import io.github.sds100.keymapper.system.settings.SettingType import kotlinx.serialization.Serializable @Serializable @@ -175,7 +176,7 @@ abstract class NavDestination(val isCompose: Boolean = false) { @Serializable data class ChooseSetting( - val currentSettingType: io.github.sds100.keymapper.system.settings.SettingType?, + val currentSettingType: SettingType?, ) : NavDestination(isCompose = true) { override val id: String = ID_CHOOSE_SETTING } From c351c610d97ad4232a3caede9651d4f51da8c518 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 14:39:02 +0000 Subject: [PATCH 10/23] Refactor SettingsAdapter and ModifySettingActionBottomSheet per review - Consolidate modify methods into single modifySetting(settingType, key, value) method - Import SettingType instead of using fully qualified name - Replace custom dropdown with KeyMapperDropdownMenu - Add onChooseSetting callback and Button to navigate to ChooseSettingScreen - Update PerformActionsUseCase to use new unified method - Add onChooseSettingClick method to CreateActionDelegate for navigation Co-authored-by: sds100 <16245954+sds100@users.noreply.github.com> --- .../base/actions/CreateActionDelegate.kt | 9 ++ .../actions/ModifySettingActionBottomSheet.kt | 89 +++++++------------ .../base/actions/PerformActionsUseCase.kt | 13 ++- .../system/settings/SettingsAdapter.kt | 61 +++++-------- 4 files changed, 66 insertions(+), 106 deletions(-) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt index f82644e708..9e9620dfbd 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt @@ -202,6 +202,15 @@ class CreateActionDelegate( actionResult.update { action } } + fun onChooseSettingClick(settingType: io.github.sds100.keymapper.system.settings.SettingType) { + coroutineScope.launch { + navigate( + "choose_setting", + NavDestination.ChooseSetting(currentSettingType = settingType), + ) + } + } + suspend fun editAction(oldData: ActionData) { if (!oldData.isEditable()) { throw IllegalArgumentException("This action ${oldData.javaClass.name} can't be edited!") diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt index ca25f1be9e..dfbcf47a0e 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt @@ -5,10 +5,8 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text @@ -25,10 +23,12 @@ import androidx.compose.ui.unit.dp import io.github.sds100.keymapper.base.R import io.github.sds100.keymapper.base.utils.ui.compose.BottomSheet import io.github.sds100.keymapper.base.utils.ui.compose.BottomSheetDefaults +import io.github.sds100.keymapper.base.utils.ui.compose.KeyMapperDropdownMenu +import io.github.sds100.keymapper.system.settings.SettingType import kotlinx.coroutines.launch data class ModifySettingActionBottomSheetState( - val settingType: io.github.sds100.keymapper.system.settings.SettingType, + val settingType: SettingType, val settingKey: String, val value: String, ) @@ -59,6 +59,9 @@ fun ModifySettingActionBottomSheet(delegate: CreateActionDelegate) { delegate.modifySettingActionBottomSheetState = null } }, + onChooseSetting = { settingType -> + delegate.onChooseSettingClick(settingType) + }, onComplete = { action -> scope.launch { sheetState.hide() @@ -75,6 +78,7 @@ fun ModifySettingActionBottomSheet(delegate: CreateActionDelegate) { private fun ModifySettingActionBottomSheetContent( state: ModifySettingActionBottomSheetState, onDismiss: () -> Unit, + onChooseSetting: (SettingType) -> Unit, onComplete: (ActionData.ModifySetting) -> Unit, ) { var settingType by remember(state) { mutableStateOf(state.settingType) } @@ -82,13 +86,6 @@ private fun ModifySettingActionBottomSheetContent( var value by remember(state) { mutableStateOf(state.value) } var settingTypeExpanded by remember { mutableStateOf(false) } - var settingKeyExpanded by remember { mutableStateOf(false) } - - // Available setting keys based on selected type - placeholder, would need SettingsAdapter - val availableKeys = remember(settingType) { - // For now, return empty list. This will be populated via dependency injection - emptyList() - } BottomSheet( title = stringResource(R.string.action_modify_setting), @@ -111,57 +108,33 @@ private fun ModifySettingActionBottomSheetContent( Spacer(modifier = Modifier.height(16.dp)) // Setting Type Dropdown - ExposedDropdownMenuBox( + KeyMapperDropdownMenu( + modifier = Modifier.fillMaxWidth(), expanded = settingTypeExpanded, onExpandedChange = { settingTypeExpanded = it }, + label = { Text(stringResource(R.string.modify_setting_type_label)) }, + selectedValue = settingType, + values = listOf( + SettingType.SYSTEM to stringResource(R.string.modify_setting_type_system), + SettingType.SECURE to stringResource(R.string.modify_setting_type_secure), + SettingType.GLOBAL to stringResource(R.string.modify_setting_type_global), + ), + onValueChanged = { settingType = it }, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Button to choose an existing setting + Button( + onClick = { onChooseSetting(settingType) }, + modifier = Modifier.fillMaxWidth(), ) { - OutlinedTextField( - value = when (settingType) { - io.github.sds100.keymapper.system.settings.SettingType.SYSTEM -> - stringResource(R.string.modify_setting_type_system) - io.github.sds100.keymapper.system.settings.SettingType.SECURE -> - stringResource(R.string.modify_setting_type_secure) - io.github.sds100.keymapper.system.settings.SettingType.GLOBAL -> - stringResource(R.string.modify_setting_type_global) - }, - onValueChange = {}, - readOnly = true, - label = { Text(stringResource(R.string.modify_setting_type_label)) }, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = settingTypeExpanded) }, - modifier = Modifier.fillMaxWidth().menuAnchor(), - singleLine = true, - ) - ExposedDropdownMenu( - expanded = settingTypeExpanded, - onDismissRequest = { settingTypeExpanded = false }, - ) { - DropdownMenuItem( - text = { Text(stringResource(R.string.modify_setting_type_system)) }, - onClick = { - settingType = io.github.sds100.keymapper.system.settings.SettingType.SYSTEM - settingTypeExpanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.modify_setting_type_secure)) }, - onClick = { - settingType = io.github.sds100.keymapper.system.settings.SettingType.SECURE - settingTypeExpanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.modify_setting_type_global)) }, - onClick = { - settingType = io.github.sds100.keymapper.system.settings.SettingType.GLOBAL - settingTypeExpanded = false - }, - ) - } + Text(stringResource(R.string.choose_existing_setting)) } Spacer(modifier = Modifier.height(16.dp)) - // Setting Key - allow both dropdown selection and manual entry + // Setting Key - manual entry OutlinedTextField( value = settingKey, onValueChange = { settingKey = it }, @@ -183,11 +156,11 @@ private fun ModifySettingActionBottomSheetContent( Spacer(modifier = Modifier.height(16.dp)) val exampleText = when (settingType) { - io.github.sds100.keymapper.system.settings.SettingType.SYSTEM -> + SettingType.SYSTEM -> stringResource(R.string.modify_setting_example_system) - io.github.sds100.keymapper.system.settings.SettingType.SECURE -> + SettingType.SECURE -> stringResource(R.string.modify_setting_example_secure) - io.github.sds100.keymapper.system.settings.SettingType.GLOBAL -> + SettingType.GLOBAL -> stringResource(R.string.modify_setting_example_global) } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt index ec168ca2ca..413d2272b6 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt @@ -1019,14 +1019,11 @@ class PerformActionsUseCaseImpl @AssistedInject constructor( } is ActionData.ModifySetting -> { - result = when (action.settingType) { - io.github.sds100.keymapper.system.settings.SettingType.SYSTEM -> - settingsAdapter.modifySystemSetting(action.settingKey, action.value) - io.github.sds100.keymapper.system.settings.SettingType.SECURE -> - settingsAdapter.modifySecureSetting(action.settingKey, action.value) - io.github.sds100.keymapper.system.settings.SettingType.GLOBAL -> - settingsAdapter.modifyGlobalSetting(action.settingKey, action.value) - } + result = settingsAdapter.modifySetting( + action.settingType, + action.settingKey, + action.value, + ) } } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt index 032483db65..047ad7e77e 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt @@ -42,43 +42,26 @@ class AndroidSettingsAdapter @Inject constructor( return SettingsUtils.getGlobalSetting(ctx, key) } - override fun modifySystemSetting(key: String, value: String): KMResult<*> { - // Try to parse value as different types and use the appropriate method - val success = when { - value.toIntOrNull() != null -> SettingsUtils.putSystemSetting(ctx, key, value.toInt()) - value.toLongOrNull() != null -> SettingsUtils.putSystemSetting(ctx, key, value.toLong()) - value.toFloatOrNull() != null -> SettingsUtils.putSystemSetting(ctx, key, value.toFloat()) - else -> SettingsUtils.putSystemSetting(ctx, key, value) - } - - return if (success) { - Success(Unit) - } else { - KMError.FailedToModifySystemSetting(key) - } - } - - override fun modifySecureSetting(key: String, value: String): KMResult<*> { - val success = when { - value.toIntOrNull() != null -> SettingsUtils.putSecureSetting(ctx, key, value.toInt()) - value.toLongOrNull() != null -> SettingsUtils.putSecureSetting(ctx, key, value.toLong()) - value.toFloatOrNull() != null -> SettingsUtils.putSecureSetting(ctx, key, value.toFloat()) - else -> SettingsUtils.putSecureSetting(ctx, key, value) - } - - return if (success) { - Success(Unit) - } else { - KMError.FailedToModifySystemSetting(key) - } - } - - override fun modifyGlobalSetting(key: String, value: String): KMResult<*> { - val success = when { - value.toIntOrNull() != null -> SettingsUtils.putGlobalSetting(ctx, key, value.toInt()) - value.toLongOrNull() != null -> SettingsUtils.putGlobalSetting(ctx, key, value.toLong()) - value.toFloatOrNull() != null -> SettingsUtils.putGlobalSetting(ctx, key, value.toFloat()) - else -> SettingsUtils.putGlobalSetting(ctx, key, value) + override fun modifySetting(settingType: SettingType, key: String, value: String): KMResult<*> { + val success = when (settingType) { + SettingType.SYSTEM -> when { + value.toIntOrNull() != null -> SettingsUtils.putSystemSetting(ctx, key, value.toInt()) + value.toLongOrNull() != null -> SettingsUtils.putSystemSetting(ctx, key, value.toLong()) + value.toFloatOrNull() != null -> SettingsUtils.putSystemSetting(ctx, key, value.toFloat()) + else -> SettingsUtils.putSystemSetting(ctx, key, value) + } + SettingType.SECURE -> when { + value.toIntOrNull() != null -> SettingsUtils.putSecureSetting(ctx, key, value.toInt()) + value.toLongOrNull() != null -> SettingsUtils.putSecureSetting(ctx, key, value.toLong()) + value.toFloatOrNull() != null -> SettingsUtils.putSecureSetting(ctx, key, value.toFloat()) + else -> SettingsUtils.putSecureSetting(ctx, key, value) + } + SettingType.GLOBAL -> when { + value.toIntOrNull() != null -> SettingsUtils.putGlobalSetting(ctx, key, value.toInt()) + value.toLongOrNull() != null -> SettingsUtils.putGlobalSetting(ctx, key, value.toLong()) + value.toFloatOrNull() != null -> SettingsUtils.putGlobalSetting(ctx, key, value.toFloat()) + else -> SettingsUtils.putGlobalSetting(ctx, key, value) + } } return if (success) { @@ -127,7 +110,5 @@ interface SettingsAdapter { fun getSecureSettingValue(key: String): String? fun getGlobalSettingValue(key: String): String? - fun modifySystemSetting(key: String, value: String): KMResult<*> - fun modifySecureSetting(key: String, value: String): KMResult<*> - fun modifyGlobalSetting(key: String, value: String): KMResult<*> + fun modifySetting(settingType: SettingType, key: String, value: String): KMResult<*> } From b149abf01203be0fdc3632d5e76d24126919a83e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 00:23:25 +0000 Subject: [PATCH 11/23] Consolidate SettingsAdapter methods and import SettingType in ActionErrorSnapshot - Refactored getKeys methods into single getAll(settingType) returning Map - Consolidated getValue methods into single getValue(settingType, key) method - Updated ChooseSettingViewModel to use new unified methods - Imported SettingType in ActionErrorSnapshot instead of using fully qualified name - Simplified interface with only 3 methods: getAll, getValue, modifySetting Co-authored-by: sds100 <16245954+sds100@users.noreply.github.com> --- .../base/actions/ActionErrorSnapshot.kt | 7 +- .../base/actions/ChooseSettingViewModel.kt | 27 +---- .../system/settings/SettingsAdapter.kt | 98 ++++++++----------- 3 files changed, 51 insertions(+), 81 deletions(-) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionErrorSnapshot.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionErrorSnapshot.kt index 6d453d93b5..4554ed268a 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionErrorSnapshot.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionErrorSnapshot.kt @@ -27,6 +27,7 @@ import io.github.sds100.keymapper.system.permissions.Permission import io.github.sds100.keymapper.system.permissions.PermissionAdapter import io.github.sds100.keymapper.system.permissions.SystemFeatureAdapter import io.github.sds100.keymapper.system.ringtones.RingtoneAdapter +import io.github.sds100.keymapper.system.settings.SettingType class LazyActionErrorSnapshot( private val packageManager: PackageManagerAdapter, @@ -233,15 +234,15 @@ class LazyActionErrorSnapshot( is ActionData.ModifySetting -> { return when (action.settingType) { - io.github.sds100.keymapper.system.settings.SettingType.SYSTEM -> { + SettingType.SYSTEM -> { if (!isPermissionGranted(Permission.WRITE_SETTINGS)) { SystemError.PermissionDenied(Permission.WRITE_SETTINGS) } else { null } } - io.github.sds100.keymapper.system.settings.SettingType.SECURE, - io.github.sds100.keymapper.system.settings.SettingType.GLOBAL -> { + SettingType.SECURE, + SettingType.GLOBAL -> { if (!isPermissionGranted(Permission.WRITE_SECURE_SETTINGS)) { SystemError.PermissionDenied(Permission.WRITE_SECURE_SETTINGS) } else { diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt index 6227002f4b..a512b455be 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt @@ -42,28 +42,11 @@ class ChooseSettingViewModel @Inject constructor( val settings: StateFlow>> = combine(selectedSettingType, searchQuery) { type, query -> - val keys = when (type) { - SettingType.SYSTEM -> - settingsAdapter.getSystemSettingKeys() - SettingType.SECURE -> - settingsAdapter.getSecureSettingKeys() - SettingType.GLOBAL -> - settingsAdapter.getGlobalSettingKeys() - } - - val items = keys - .filter { query == null || it.contains(query, ignoreCase = true) } - .map { key -> - val value = when (type) { - SettingType.SYSTEM -> - settingsAdapter.getSystemSettingValue(key) - SettingType.SECURE -> - settingsAdapter.getSecureSettingValue(key) - SettingType.GLOBAL -> - settingsAdapter.getGlobalSettingValue(key) - } - SettingItem(key, value) - } + val allSettings = settingsAdapter.getAll(type) + + val items = allSettings + .filter { (key, _) -> query == null || key.contains(query, ignoreCase = true) } + .map { (key, value) -> SettingItem(key, value) } State.Data(items) }.flowOn(Dispatchers.Default) diff --git a/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt index 047ad7e77e..58d53f8fec 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt @@ -18,28 +18,49 @@ class AndroidSettingsAdapter @Inject constructor( ) : SettingsAdapter { private val ctx = context.applicationContext - override fun getSystemSettingKeys(): List { - return getSettingKeys(Settings.System.CONTENT_URI) - } - - override fun getSecureSettingKeys(): List { - return getSettingKeys(Settings.Secure.CONTENT_URI) - } - - override fun getGlobalSettingKeys(): List { - return getSettingKeys(Settings.Global.CONTENT_URI) - } - - override fun getSystemSettingValue(key: String): String? { - return SettingsUtils.getSystemSetting(ctx, key) - } + override fun getAll(settingType: SettingType): Map { + val uri = when (settingType) { + SettingType.SYSTEM -> Settings.System.CONTENT_URI + SettingType.SECURE -> Settings.Secure.CONTENT_URI + SettingType.GLOBAL -> Settings.Global.CONTENT_URI + } + + val settings = mutableMapOf() + var cursor: Cursor? = null + try { + cursor = ctx.contentResolver.query( + uri, + arrayOf("name", "value"), + null, + null, + null, + ) - override fun getSecureSettingValue(key: String): String? { - return SettingsUtils.getSecureSetting(ctx, key) + cursor?.use { + val nameIndex = it.getColumnIndex("name") + val valueIndex = it.getColumnIndex("value") + if (nameIndex >= 0) { + while (it.moveToNext()) { + val name = it.getString(nameIndex) + if (!name.isNullOrBlank()) { + val value = if (valueIndex >= 0) it.getString(valueIndex) else null + settings[name] = value + } + } + } + } + } catch (e: Exception) { + // Some devices may not allow querying all settings + } + return settings.toSortedMap() } - override fun getGlobalSettingValue(key: String): String? { - return SettingsUtils.getGlobalSetting(ctx, key) + override fun getValue(settingType: SettingType, key: String): String? { + return when (settingType) { + SettingType.SYSTEM -> SettingsUtils.getSystemSetting(ctx, key) + SettingType.SECURE -> SettingsUtils.getSecureSetting(ctx, key) + SettingType.GLOBAL -> SettingsUtils.getGlobalSetting(ctx, key) + } } override fun modifySetting(settingType: SettingType, key: String, value: String): KMResult<*> { @@ -70,45 +91,10 @@ class AndroidSettingsAdapter @Inject constructor( KMError.FailedToModifySystemSetting(key) } } - - private fun getSettingKeys(uri: Uri): List { - val keys = mutableListOf() - var cursor: Cursor? = null - try { - cursor = ctx.contentResolver.query( - uri, - arrayOf("name"), - null, - null, - null, - ) - - cursor?.use { - val nameIndex = it.getColumnIndex("name") - if (nameIndex >= 0) { - while (it.moveToNext()) { - val name = it.getString(nameIndex) - if (!name.isNullOrBlank()) { - keys.add(name) - } - } - } - } - } catch (e: Exception) { - // Some devices may not allow querying all settings - } - return keys.sorted() - } } interface SettingsAdapter { - fun getSystemSettingKeys(): List - fun getSecureSettingKeys(): List - fun getGlobalSettingKeys(): List - - fun getSystemSettingValue(key: String): String? - fun getSecureSettingValue(key: String): String? - fun getGlobalSettingValue(key: String): String? - + fun getAll(settingType: SettingType): Map + fun getValue(settingType: SettingType, key: String): String? fun modifySetting(settingType: SettingType, key: String, value: String): KMResult<*> } From 162a959a4285f2f96fc72748360777d61e2a4fab Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 8 Nov 2025 00:43:10 +0100 Subject: [PATCH 12/23] #1871 clean up modify settings bottom sheet --- .../base/actions/ActionDataEntityMapper.kt | 4 +- .../base/actions/ActionErrorSnapshot.kt | 3 +- .../keymapper/base/actions/ActionUtils.kt | 3 +- .../base/actions/ChooseSettingScreen.kt | 12 +- .../base/actions/ChooseSettingViewModel.kt | 9 +- .../base/actions/CreateActionDelegate.kt | 57 +++--- .../actions/ModifySettingActionBottomSheet.kt | 183 +++++++++++------- .../base/utils/navigation/NavDestination.kt | 8 +- base/src/main/res/values/strings.xml | 11 +- .../sysbridge/service/SystemBridge.kt | 1 - .../keymapper/system/SystemHiltModule.kt | 6 + .../system/settings/SettingsAdapter.kt | 32 +-- 12 files changed, 187 insertions(+), 142 deletions(-) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt index 7e8bb512b7..7b0fd6626e 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt @@ -732,7 +732,9 @@ object ActionDataEntityMapper { .valueOrNull() ?: "SYSTEM" // Default to SYSTEM for backward compatibility val settingType = try { - io.github.sds100.keymapper.system.settings.SettingType.valueOf(settingTypeString) + io.github.sds100.keymapper.system.settings.SettingType.valueOf( + settingTypeString, + ) } catch (e: IllegalArgumentException) { io.github.sds100.keymapper.system.settings.SettingType.SYSTEM } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionErrorSnapshot.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionErrorSnapshot.kt index 4554ed268a..45d67f71cf 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionErrorSnapshot.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionErrorSnapshot.kt @@ -242,7 +242,8 @@ class LazyActionErrorSnapshot( } } SettingType.SECURE, - SettingType.GLOBAL -> { + SettingType.GLOBAL, + -> { if (!isPermissionGranted(Permission.WRITE_SECURE_SETTINGS)) { SystemError.PermissionDenied(Permission.WRITE_SECURE_SETTINGS) } else { diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt index 81b8464d8c..224c0a7108 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt @@ -763,7 +763,8 @@ object ActionUtils { return listOf(Permission.FIND_NEARBY_DEVICES) } - ActionId.MODIFY_SETTING -> return emptyList() // Permissions handled based on setting type at runtime + // Permissions handled based on setting type at runtime + ActionId.MODIFY_SETTING -> return emptyList() else -> return emptyList() } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt index 2d5bb9dffb..c9a9a78e9a 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt @@ -99,7 +99,7 @@ private fun ChooseSettingScreen( Column(modifier = Modifier.fillMaxSize()) { // Setting type dropdown var expanded by remember { mutableStateOf(false) } - + KeyMapperDropdownMenu( modifier = Modifier .fillMaxWidth() @@ -121,13 +121,19 @@ private fun ChooseSettingScreen( // Settings list when (state) { State.Loading -> { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { CircularProgressIndicator() } } is State.Data -> { if (state.data.isEmpty()) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { Text( text = stringResource(R.string.choose_setting_empty), style = MaterialTheme.typography.bodyLarge, diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt index a512b455be..1c834dbf85 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt @@ -7,8 +7,8 @@ import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider import io.github.sds100.keymapper.base.utils.ui.DialogProvider import io.github.sds100.keymapper.base.utils.ui.ResourceProvider import io.github.sds100.keymapper.common.utils.State -import io.github.sds100.keymapper.system.settings.SettingsAdapter import io.github.sds100.keymapper.system.settings.SettingType +import io.github.sds100.keymapper.system.settings.SettingsAdapter import javax.inject.Inject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -21,10 +21,7 @@ import kotlinx.coroutines.launch import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json -data class SettingItem( - val key: String, - val value: String?, -) +data class SettingItem(val key: String, val value: String?) @HiltViewModel class ChooseSettingViewModel @Inject constructor( @@ -43,7 +40,7 @@ class ChooseSettingViewModel @Inject constructor( val settings: StateFlow>> = combine(selectedSettingType, searchQuery) { type, query -> val allSettings = settingsAdapter.getAll(type) - + val items = allSettings .filter { (key, _) -> query == null || key.contains(query, ignoreCase = true) } .map { (key, value) -> SettingItem(key, value) } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt index 9e9620dfbd..234d313ed7 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt @@ -24,6 +24,7 @@ import io.github.sds100.keymapper.common.utils.State import io.github.sds100.keymapper.system.SystemError import io.github.sds100.keymapper.system.camera.CameraLens import io.github.sds100.keymapper.system.network.HttpMethod +import io.github.sds100.keymapper.system.settings.SettingType import io.github.sds100.keymapper.system.volume.DndMode import io.github.sds100.keymapper.system.volume.RingerMode import io.github.sds100.keymapper.system.volume.VolumeStream @@ -54,7 +55,9 @@ class CreateActionDelegate( var httpRequestBottomSheetState: ActionData.HttpRequest? by mutableStateOf(null) var smsActionBottomSheetState: SmsActionBottomSheetState? by mutableStateOf(null) var volumeActionState: VolumeActionBottomSheetState? by mutableStateOf(null) - var modifySettingActionBottomSheetState: ModifySettingActionBottomSheetState? by mutableStateOf(null) + var modifySettingActionBottomSheetState: ModifySettingActionBottomSheetState? by mutableStateOf( + null, + ) init { coroutineScope.launch { @@ -197,18 +200,31 @@ class CreateActionDelegate( } } - fun onDoneModifySettingClick(action: ActionData.ModifySetting) { + fun onDoneModifySettingClick() { + val state = modifySettingActionBottomSheetState ?: return + val result = ActionData.ModifySetting( + settingType = state.settingType, + settingKey = state.settingKey, + value = state.value, + ) + modifySettingActionBottomSheetState = null - actionResult.update { action } + actionResult.update { result } } - fun onChooseSettingClick(settingType: io.github.sds100.keymapper.system.settings.SettingType) { - coroutineScope.launch { - navigate( - "choose_setting", - NavDestination.ChooseSetting(currentSettingType = settingType), - ) - } + fun onSelectSettingType(settingType: SettingType) { + modifySettingActionBottomSheetState = + modifySettingActionBottomSheetState?.copy(settingType = settingType) + } + + fun onSettingKeyChange(key: String) { + modifySettingActionBottomSheetState = + modifySettingActionBottomSheetState?.copy(settingKey = key) + } + + fun onSettingValueChange(value: String) { + modifySettingActionBottomSheetState = + modifySettingActionBottomSheetState?.copy(value = value) } suspend fun editAction(oldData: ActionData) { @@ -944,25 +960,12 @@ class CreateActionDelegate( ActionId.CLEAR_RECENT_APP -> return ActionData.ClearRecentApp ActionId.MODIFY_SETTING -> { - val settingType = when (oldData) { - is ActionData.ModifySetting -> oldData.settingType - else -> io.github.sds100.keymapper.system.settings.SettingType.SYSTEM // Default to SYSTEM - } - - val settingKey = when (oldData) { - is ActionData.ModifySetting -> oldData.settingKey - else -> "" - } - - val value = when (oldData) { - is ActionData.ModifySetting -> oldData.value - else -> "" - } + val oldAction = oldData as? ActionData.ModifySetting modifySettingActionBottomSheetState = ModifySettingActionBottomSheetState( - settingType = settingType, - settingKey = settingKey, - value = value, + settingType = oldAction?.settingType ?: SettingType.SYSTEM, + settingKey = oldAction?.settingKey ?: "", + value = oldAction?.value ?: "", ) return null diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt index dfbcf47a0e..66236afc38 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt @@ -1,14 +1,22 @@ package io.github.sds100.keymapper.base.actions +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.SheetState +import androidx.compose.material3.SheetValue import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable @@ -17,12 +25,15 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.github.sds100.keymapper.base.R -import io.github.sds100.keymapper.base.utils.ui.compose.BottomSheet -import io.github.sds100.keymapper.base.utils.ui.compose.BottomSheetDefaults +import io.github.sds100.keymapper.base.compose.KeyMapperTheme import io.github.sds100.keymapper.base.utils.ui.compose.KeyMapperDropdownMenu import io.github.sds100.keymapper.system.settings.SettingType import kotlinx.coroutines.launch @@ -40,122 +51,102 @@ fun ModifySettingActionBottomSheet(delegate: CreateActionDelegate) { val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) if (delegate.modifySettingActionBottomSheetState != null) { - ModalBottomSheet( + ModifySettingActionBottomSheet( + sheetState = sheetState, + state = delegate.modifySettingActionBottomSheetState!!, onDismissRequest = { + delegate.modifySettingActionBottomSheetState = null + }, + onSelectSettingType = delegate::onSelectSettingType, + onSettingKeyChange = delegate::onSettingKeyChange, + onSettingValueChange = delegate::onSettingValueChange, + onDoneClick = { scope.launch { sheetState.hide() - }.invokeOnCompletion { - delegate.modifySettingActionBottomSheetState = null + delegate.onDoneModifySettingClick() } }, - sheetState = sheetState, - ) { - ModifySettingActionBottomSheetContent( - state = delegate.modifySettingActionBottomSheetState!!, - onDismiss = { - scope.launch { - sheetState.hide() - }.invokeOnCompletion { - delegate.modifySettingActionBottomSheetState = null - } - }, - onChooseSetting = { settingType -> - delegate.onChooseSettingClick(settingType) - }, - onComplete = { action -> - scope.launch { - sheetState.hide() - }.invokeOnCompletion { - delegate.onDoneModifySettingClick(action) - } - }, - ) - } + ) } } +@OptIn(ExperimentalMaterial3Api::class) @Composable -private fun ModifySettingActionBottomSheetContent( +private fun ModifySettingActionBottomSheet( + sheetState: SheetState, state: ModifySettingActionBottomSheetState, - onDismiss: () -> Unit, - onChooseSetting: (SettingType) -> Unit, - onComplete: (ActionData.ModifySetting) -> Unit, + onDismissRequest: () -> Unit = {}, + onSelectSettingType: (SettingType) -> Unit = {}, + onSettingKeyChange: (String) -> Unit = {}, + onSettingValueChange: (String) -> Unit = {}, + onDoneClick: () -> Unit = {}, ) { - var settingType by remember(state) { mutableStateOf(state.settingType) } - var settingKey by remember(state) { mutableStateOf(state.settingKey) } - var value by remember(state) { mutableStateOf(state.value) } - + val scope = rememberCoroutineScope() var settingTypeExpanded by remember { mutableStateOf(false) } - BottomSheet( - title = stringResource(R.string.action_modify_setting), - onDismiss = onDismiss, - positiveButton = BottomSheetDefaults.OkButton { - val action = ActionData.ModifySetting( - settingType = settingType, - settingKey = settingKey, - value = value, - ) - onComplete(action) + ModalBottomSheet( + onDismissRequest = { + scope.launch { + sheetState.hide() + } }, - positiveButtonEnabled = settingKey.isNotBlank() && value.isNotBlank(), + sheetState = sheetState, + dragHandle = null, ) { Column( modifier = Modifier + .verticalScroll(rememberScrollState()) .fillMaxWidth() .padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), ) { - Spacer(modifier = Modifier.height(16.dp)) + Text( + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp), + textAlign = TextAlign.Center, + text = stringResource(R.string.modify_setting_bottom_sheet_title), + style = MaterialTheme.typography.headlineMedium, + ) - // Setting Type Dropdown KeyMapperDropdownMenu( modifier = Modifier.fillMaxWidth(), expanded = settingTypeExpanded, onExpandedChange = { settingTypeExpanded = it }, label = { Text(stringResource(R.string.modify_setting_type_label)) }, - selectedValue = settingType, + selectedValue = state.settingType, values = listOf( SettingType.SYSTEM to stringResource(R.string.modify_setting_type_system), SettingType.SECURE to stringResource(R.string.modify_setting_type_secure), SettingType.GLOBAL to stringResource(R.string.modify_setting_type_global), ), - onValueChanged = { settingType = it }, + onValueChanged = onSelectSettingType, ) - Spacer(modifier = Modifier.height(16.dp)) - - // Button to choose an existing setting Button( - onClick = { onChooseSetting(settingType) }, + onClick = { onSelectSettingType(state.settingType) }, modifier = Modifier.fillMaxWidth(), ) { Text(stringResource(R.string.choose_existing_setting)) } - Spacer(modifier = Modifier.height(16.dp)) - - // Setting Key - manual entry OutlinedTextField( - value = settingKey, - onValueChange = { settingKey = it }, + value = state.settingKey, + onValueChange = onSettingKeyChange, label = { Text(stringResource(R.string.modify_setting_key_label)) }, modifier = Modifier.fillMaxWidth(), singleLine = true, ) - Spacer(modifier = Modifier.height(16.dp)) - OutlinedTextField( - value = value, - onValueChange = { value = it }, + value = state.value, + onValueChange = onSettingValueChange, label = { Text(stringResource(R.string.modify_setting_value_label)) }, modifier = Modifier.fillMaxWidth(), singleLine = true, ) - Spacer(modifier = Modifier.height(16.dp)) - - val exampleText = when (settingType) { + val exampleText = when (state.settingType) { SettingType.SYSTEM -> stringResource(R.string.modify_setting_example_system) SettingType.SECURE -> @@ -166,11 +157,61 @@ private fun ModifySettingActionBottomSheetContent( Text( text = exampleText, - style = androidx.compose.material3.MaterialTheme.typography.bodySmall, - color = androidx.compose.material3.MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, ) - Spacer(modifier = Modifier.height(16.dp)) + // TODO do not allow empty text fields + Row( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + OutlinedButton( + modifier = Modifier.weight(1f), + onClick = { + scope.launch { + sheetState.hide() + onDismissRequest() + } + }, + ) { + Text(stringResource(R.string.neg_cancel)) + } + + Spacer(modifier = Modifier.width(16.dp)) + + Button( + modifier = Modifier.weight(1f), + onClick = onDoneClick, + ) { + Text(stringResource(R.string.pos_done)) + } + } } } } + +@OptIn(ExperimentalMaterial3Api::class) +@Preview +@Composable +private fun Preview() { + KeyMapperTheme { + val sheetState = SheetState( + skipPartiallyExpanded = true, + density = LocalDensity.current, + initialValue = SheetValue.Expanded, + ) + + ModifySettingActionBottomSheet( + sheetState = sheetState, + state = ModifySettingActionBottomSheetState( + settingType = SettingType.GLOBAL, + settingKey = "adb_enabled", + value = "1", + ), + ) + } +} diff --git a/base/src/main/java/io/github/sds100/keymapper/base/utils/navigation/NavDestination.kt b/base/src/main/java/io/github/sds100/keymapper/base/utils/navigation/NavDestination.kt index ef674f495d..dfef10b955 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/utils/navigation/NavDestination.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/utils/navigation/NavDestination.kt @@ -1,4 +1,5 @@ -import io.github.sds100.keymapper.base.utils.navigation +package io.github.sds100.keymapper.base.utils.navigation + import io.github.sds100.keymapper.base.actions.ActionData import io.github.sds100.keymapper.base.actions.ChooseSettingResult import io.github.sds100.keymapper.base.actions.pinchscreen.PinchPickCoordinateResult @@ -175,9 +176,8 @@ abstract class NavDestination(val isCompose: Boolean = false) { } @Serializable - data class ChooseSetting( - val currentSettingType: SettingType?, - ) : NavDestination(isCompose = true) { + data class ChooseSetting(val currentSettingType: SettingType?) : + NavDestination(isCompose = true) { override val id: String = ID_CHOOSE_SETTING } diff --git a/base/src/main/res/values/strings.xml b/base/src/main/res/values/strings.xml index 6b40728739..8cea7d35f4 100644 --- a/base/src/main/res/values/strings.xml +++ b/base/src/main/res/values/strings.xml @@ -1093,9 +1093,10 @@ Compose SMS Compose SMS: "%s" to %s Set %3$s: %1$s = %2$s - System Setting - Secure Setting - Global Setting + System + Secure + Global + Modify setting Play sound Dismiss most recent notification Dismiss all notifications @@ -1178,7 +1179,7 @@ Force stop app Close and clear app from recents Modify setting - Setting Key + Key Value Setting Type Example: screen_off_timeout = 2147483647 (System settings use WRITE_SETTINGS permission) @@ -1186,7 +1187,7 @@ Example: airplane_mode_on = 1 (Global settings require system bridge) Choose Setting No settings found - Choose Existing Setting + Choose existing setting diff --git a/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridge.kt b/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridge.kt index e89edb6930..fdb9d4cc66 100644 --- a/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridge.kt +++ b/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridge.kt @@ -26,7 +26,6 @@ import android.os.Process import android.os.ServiceManager import android.permission.IPermissionManager import android.permission.PermissionManagerApis -import android.provider.Settings import android.util.Log import android.view.InputEvent import com.android.internal.telephony.ITelephony diff --git a/system/src/main/java/io/github/sds100/keymapper/system/SystemHiltModule.kt b/system/src/main/java/io/github/sds100/keymapper/system/SystemHiltModule.kt index a151730aa5..8909d67dcc 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/SystemHiltModule.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/SystemHiltModule.kt @@ -53,6 +53,8 @@ import io.github.sds100.keymapper.system.ringtones.AndroidRingtoneAdapter import io.github.sds100.keymapper.system.ringtones.RingtoneAdapter import io.github.sds100.keymapper.system.root.SuAdapter import io.github.sds100.keymapper.system.root.SuAdapterImpl +import io.github.sds100.keymapper.system.settings.AndroidSettingsAdapter +import io.github.sds100.keymapper.system.settings.SettingsAdapter import io.github.sds100.keymapper.system.shell.ShellAdapter import io.github.sds100.keymapper.system.shell.StandardShellAdapter import io.github.sds100.keymapper.system.shizuku.ShizukuAdapter @@ -193,4 +195,8 @@ abstract class SystemHiltModule { abstract fun provideSystemFeatureAdapter( impl: AndroidSystemFeatureAdapter, ): SystemFeatureAdapter + + @Singleton + @Binds + abstract fun provideSettingsAdapter(impl: AndroidSettingsAdapter): SettingsAdapter } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt index 58d53f8fec..493ba0f327 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt @@ -2,7 +2,6 @@ package io.github.sds100.keymapper.system.settings import android.content.Context import android.database.Cursor -import android.net.Uri import android.provider.Settings import dagger.hilt.android.qualifiers.ApplicationContext import io.github.sds100.keymapper.common.utils.KMError @@ -24,9 +23,9 @@ class AndroidSettingsAdapter @Inject constructor( SettingType.SECURE -> Settings.Secure.CONTENT_URI SettingType.GLOBAL -> Settings.Global.CONTENT_URI } - + val settings = mutableMapOf() - var cursor: Cursor? = null + var cursor: Cursor? try { cursor = ctx.contentResolver.query( uri, @@ -43,7 +42,11 @@ class AndroidSettingsAdapter @Inject constructor( while (it.moveToNext()) { val name = it.getString(nameIndex) if (!name.isNullOrBlank()) { - val value = if (valueIndex >= 0) it.getString(valueIndex) else null + val value = if (valueIndex >= 0) { + it.getString(valueIndex) + } else { + null + } settings[name] = value } } @@ -65,24 +68,9 @@ class AndroidSettingsAdapter @Inject constructor( override fun modifySetting(settingType: SettingType, key: String, value: String): KMResult<*> { val success = when (settingType) { - SettingType.SYSTEM -> when { - value.toIntOrNull() != null -> SettingsUtils.putSystemSetting(ctx, key, value.toInt()) - value.toLongOrNull() != null -> SettingsUtils.putSystemSetting(ctx, key, value.toLong()) - value.toFloatOrNull() != null -> SettingsUtils.putSystemSetting(ctx, key, value.toFloat()) - else -> SettingsUtils.putSystemSetting(ctx, key, value) - } - SettingType.SECURE -> when { - value.toIntOrNull() != null -> SettingsUtils.putSecureSetting(ctx, key, value.toInt()) - value.toLongOrNull() != null -> SettingsUtils.putSecureSetting(ctx, key, value.toLong()) - value.toFloatOrNull() != null -> SettingsUtils.putSecureSetting(ctx, key, value.toFloat()) - else -> SettingsUtils.putSecureSetting(ctx, key, value) - } - SettingType.GLOBAL -> when { - value.toIntOrNull() != null -> SettingsUtils.putGlobalSetting(ctx, key, value.toInt()) - value.toLongOrNull() != null -> SettingsUtils.putGlobalSetting(ctx, key, value.toLong()) - value.toFloatOrNull() != null -> SettingsUtils.putGlobalSetting(ctx, key, value.toFloat()) - else -> SettingsUtils.putGlobalSetting(ctx, key, value) - } + SettingType.SYSTEM -> SettingsUtils.putSystemSetting(ctx, key, value) + SettingType.SECURE -> SettingsUtils.putSecureSetting(ctx, key, value) + SettingType.GLOBAL -> SettingsUtils.putGlobalSetting(ctx, key, value) } return if (success) { From 16ef439777e5bf676f383e15f3aaf6de45429eb9 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 8 Nov 2025 00:49:37 +0100 Subject: [PATCH 13/23] #1871 use segmented buttons to switch setting type --- .../actions/ModifySettingActionBottomSheet.kt | 18 +++++------------- .../utils/ui/compose/KeyMapperDropdownMenu.kt | 3 ++- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt index 66236afc38..6338e99e5f 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt @@ -20,11 +20,7 @@ import androidx.compose.material3.SheetValue import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity @@ -34,7 +30,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.github.sds100.keymapper.base.R import io.github.sds100.keymapper.base.compose.KeyMapperTheme -import io.github.sds100.keymapper.base.utils.ui.compose.KeyMapperDropdownMenu +import io.github.sds100.keymapper.base.utils.ui.compose.KeyMapperSegmentedButtonRow import io.github.sds100.keymapper.system.settings.SettingType import kotlinx.coroutines.launch @@ -82,7 +78,6 @@ private fun ModifySettingActionBottomSheet( onDoneClick: () -> Unit = {}, ) { val scope = rememberCoroutineScope() - var settingTypeExpanded by remember { mutableStateOf(false) } ModalBottomSheet( onDismissRequest = { @@ -109,18 +104,15 @@ private fun ModifySettingActionBottomSheet( style = MaterialTheme.typography.headlineMedium, ) - KeyMapperDropdownMenu( + KeyMapperSegmentedButtonRow( modifier = Modifier.fillMaxWidth(), - expanded = settingTypeExpanded, - onExpandedChange = { settingTypeExpanded = it }, - label = { Text(stringResource(R.string.modify_setting_type_label)) }, - selectedValue = state.settingType, - values = listOf( + buttonStates = listOf( SettingType.SYSTEM to stringResource(R.string.modify_setting_type_system), SettingType.SECURE to stringResource(R.string.modify_setting_type_secure), SettingType.GLOBAL to stringResource(R.string.modify_setting_type_global), ), - onValueChanged = onSelectSettingType, + selectedState = state.settingType, + onStateSelected = onSelectSettingType, ) Button( diff --git a/base/src/main/java/io/github/sds100/keymapper/base/utils/ui/compose/KeyMapperDropdownMenu.kt b/base/src/main/java/io/github/sds100/keymapper/base/utils/ui/compose/KeyMapperDropdownMenu.kt index cd4629e72d..b18412f4c2 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/utils/ui/compose/KeyMapperDropdownMenu.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/utils/ui/compose/KeyMapperDropdownMenu.kt @@ -20,6 +20,7 @@ fun KeyMapperDropdownMenu( label: (@Composable () -> Unit)? = null, selectedValue: T, values: List>, + readOnly: Boolean = true, onValueChanged: (T) -> Unit = {}, ) { ExposedDropdownMenuBox( @@ -33,7 +34,7 @@ fun KeyMapperDropdownMenu( onValueChange = { newValue -> onValueChanged(values.single { it.second == newValue }.first) }, - readOnly = true, + readOnly = readOnly, label = label, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, colors = ExposedDropdownMenuDefaults.textFieldColors(), From 1862d3b5f07cfe67dc0cc8542439b7dedf9ca2c0 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 8 Nov 2025 01:00:06 +0100 Subject: [PATCH 14/23] #1871 clean up ChooseSettingScreen.kt and add previews --- .../sds100/keymapper/base/BaseMainNavHost.kt | 8 ++ .../base/actions/ChooseSettingScreen.kt | 83 +++++++++++++++---- .../base/actions/ChooseSettingViewModel.kt | 7 +- .../base/actions/CreateActionDelegate.kt | 15 ++++ .../actions/ModifySettingActionBottomSheet.kt | 26 +++--- .../base/utils/navigation/NavDestination.kt | 2 +- base/src/main/res/values/strings.xml | 6 +- 7 files changed, 106 insertions(+), 41 deletions(-) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/BaseMainNavHost.kt b/base/src/main/java/io/github/sds100/keymapper/base/BaseMainNavHost.kt index f57a042dbc..7dbe5c9a35 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/BaseMainNavHost.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/BaseMainNavHost.kt @@ -18,6 +18,7 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import io.github.sds100.keymapper.base.actions.ChooseActionScreen import io.github.sds100.keymapper.base.actions.ChooseActionViewModel +import io.github.sds100.keymapper.base.actions.ChooseSettingScreen import io.github.sds100.keymapper.base.actions.ConfigShellCommandViewModel import io.github.sds100.keymapper.base.actions.ShellCommandActionScreen import io.github.sds100.keymapper.base.actions.uielement.InteractUiElementScreen @@ -164,6 +165,13 @@ fun BaseMainNavHost( ) } + composable { + ChooseSettingScreen( + modifier = Modifier.fillMaxSize(), + viewModel = hiltViewModel(), + ) + } + composableDestinations() } } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt index c9a9a78e9a..d06d7ee21b 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt @@ -22,16 +22,15 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.sds100.keymapper.base.R -import io.github.sds100.keymapper.base.utils.ui.compose.KeyMapperDropdownMenu +import io.github.sds100.keymapper.base.compose.KeyMapperTheme +import io.github.sds100.keymapper.base.utils.ui.compose.KeyMapperSegmentedButtonRow import io.github.sds100.keymapper.base.utils.ui.compose.SearchAppBarActions import io.github.sds100.keymapper.common.utils.State import io.github.sds100.keymapper.system.settings.SettingType @@ -97,28 +96,21 @@ private fun ChooseSettingScreen( .padding(innerPadding), ) { Column(modifier = Modifier.fillMaxSize()) { - // Setting type dropdown - var expanded by remember { mutableStateOf(false) } - - KeyMapperDropdownMenu( + KeyMapperSegmentedButtonRow( modifier = Modifier .fillMaxWidth() .padding(16.dp), - expanded = expanded, - onExpandedChange = { expanded = it }, - label = { Text(stringResource(R.string.modify_setting_type_label)) }, - selectedValue = settingType, - values = listOf( + buttonStates = listOf( SettingType.SYSTEM to stringResource(R.string.modify_setting_type_system), SettingType.SECURE to stringResource(R.string.modify_setting_type_secure), SettingType.GLOBAL to stringResource(R.string.modify_setting_type_global), ), - onValueChanged = onSettingTypeChange, + selectedState = settingType, + onStateSelected = onSettingTypeChange, ) HorizontalDivider() - // Settings list when (state) { State.Loading -> { Box( @@ -159,3 +151,64 @@ private fun ChooseSettingScreen( } } } + +@OptIn(ExperimentalMaterial3Api::class) +@Preview +@Composable +private fun PreviewWithData() { + KeyMapperTheme { + ChooseSettingScreen( + state = State.Data( + listOf( + SettingItem("adb_enabled", "0"), + SettingItem("airplane_mode_on", "0"), + SettingItem("bluetooth_on", "1"), + SettingItem("screen_brightness", "128"), + SettingItem("wifi_on", "1"), + ), + ), + settingType = SettingType.GLOBAL, + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Preview +@Composable +private fun PreviewLoading() { + KeyMapperTheme { + ChooseSettingScreen( + state = State.Loading, + settingType = SettingType.SECURE, + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Preview +@Composable +private fun PreviewEmpty() { + KeyMapperTheme { + ChooseSettingScreen( + state = State.Data(emptyList()), + settingType = SettingType.SYSTEM, + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Preview +@Composable +private fun PreviewWithSearch() { + KeyMapperTheme { + ChooseSettingScreen( + state = State.Data( + listOf( + SettingItem("bluetooth_on", "1"), + ), + ), + query = "bluetooth", + settingType = SettingType.SECURE, + ) + } +} diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt index 1c834dbf85..2e3c343042 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingViewModel.kt @@ -21,8 +21,6 @@ import kotlinx.coroutines.launch import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json -data class SettingItem(val key: String, val value: String?) - @HiltViewModel class ChooseSettingViewModel @Inject constructor( private val settingsAdapter: SettingsAdapter, @@ -33,10 +31,9 @@ class ChooseSettingViewModel @Inject constructor( ResourceProvider by resourceProvider, DialogProvider by dialogProvider, NavigationProvider by navigationProvider { - val searchQuery = MutableStateFlow(null) - val selectedSettingType = MutableStateFlow(SettingType.SYSTEM) + val selectedSettingType = MutableStateFlow(SettingType.SYSTEM) val settings: StateFlow>> = combine(selectedSettingType, searchQuery) { type, query -> val allSettings = settingsAdapter.getAll(type) @@ -71,6 +68,8 @@ class ChooseSettingViewModel @Inject constructor( } } +data class SettingItem(val key: String, val value: String?) + @Serializable data class ChooseSettingResult( val settingType: SettingType, diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt index 234d313ed7..359546f2d0 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt @@ -222,6 +222,21 @@ class CreateActionDelegate( modifySettingActionBottomSheetState?.copy(settingKey = key) } + fun onChooseExistingSettingClick() { + val type = modifySettingActionBottomSheetState?.settingType ?: return + val destination = NavDestination.ChooseSetting(settingType = type) + + coroutineScope.launch { + val setting = navigate("choose_setting", destination) ?: return@launch + + modifySettingActionBottomSheetState = modifySettingActionBottomSheetState?.copy( + settingType = setting.settingType, + settingKey = setting.key, + value = setting.currentValue ?: "", + ) + } + } + fun onSettingValueChange(value: String) { modifySettingActionBottomSheetState = modifySettingActionBottomSheetState?.copy(value = value) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt index 6338e99e5f..c2a89aa7c5 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -56,6 +57,7 @@ fun ModifySettingActionBottomSheet(delegate: CreateActionDelegate) { onSelectSettingType = delegate::onSelectSettingType, onSettingKeyChange = delegate::onSettingKeyChange, onSettingValueChange = delegate::onSettingValueChange, + onChooseExistingClick = delegate::onChooseExistingSettingClick, onDoneClick = { scope.launch { sheetState.hide() @@ -75,6 +77,7 @@ private fun ModifySettingActionBottomSheet( onSelectSettingType: (SettingType) -> Unit = {}, onSettingKeyChange: (String) -> Unit = {}, onSettingValueChange: (String) -> Unit = {}, + onChooseExistingClick: () -> Unit = {}, onDoneClick: () -> Unit = {}, ) { val scope = rememberCoroutineScope() @@ -116,7 +119,7 @@ private fun ModifySettingActionBottomSheet( ) Button( - onClick = { onSelectSettingType(state.settingType) }, + onClick = onChooseExistingClick, modifier = Modifier.fillMaxWidth(), ) { Text(stringResource(R.string.choose_existing_setting)) @@ -128,6 +131,9 @@ private fun ModifySettingActionBottomSheet( label = { Text(stringResource(R.string.modify_setting_key_label)) }, modifier = Modifier.fillMaxWidth(), singleLine = true, + textStyle = MaterialTheme.typography.bodySmall.copy( + fontFamily = FontFamily.Monospace, + ), ) OutlinedTextField( @@ -136,21 +142,9 @@ private fun ModifySettingActionBottomSheet( label = { Text(stringResource(R.string.modify_setting_value_label)) }, modifier = Modifier.fillMaxWidth(), singleLine = true, - ) - - val exampleText = when (state.settingType) { - SettingType.SYSTEM -> - stringResource(R.string.modify_setting_example_system) - SettingType.SECURE -> - stringResource(R.string.modify_setting_example_secure) - SettingType.GLOBAL -> - stringResource(R.string.modify_setting_example_global) - } - - Text( - text = exampleText, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, + textStyle = MaterialTheme.typography.bodySmall.copy( + fontFamily = FontFamily.Monospace, + ), ) // TODO do not allow empty text fields diff --git a/base/src/main/java/io/github/sds100/keymapper/base/utils/navigation/NavDestination.kt b/base/src/main/java/io/github/sds100/keymapper/base/utils/navigation/NavDestination.kt index dfef10b955..3d0bf322eb 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/utils/navigation/NavDestination.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/utils/navigation/NavDestination.kt @@ -176,7 +176,7 @@ abstract class NavDestination(val isCompose: Boolean = false) { } @Serializable - data class ChooseSetting(val currentSettingType: SettingType?) : + data class ChooseSetting(val settingType: SettingType?) : NavDestination(isCompose = true) { override val id: String = ID_CHOOSE_SETTING } diff --git a/base/src/main/res/values/strings.xml b/base/src/main/res/values/strings.xml index 8cea7d35f4..6c72a161fa 100644 --- a/base/src/main/res/values/strings.xml +++ b/base/src/main/res/values/strings.xml @@ -1181,11 +1181,7 @@ Modify setting Key Value - Setting Type - Example: screen_off_timeout = 2147483647 (System settings use WRITE_SETTINGS permission) - Example: accessibility_enabled = 1 (Secure settings require system bridge) - Example: airplane_mode_on = 1 (Global settings require system bridge) - Choose Setting + Choose setting No settings found Choose existing setting From a75ea9f51a58c0d7a30b9e2187332e36af79cb6c Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 8 Nov 2025 01:19:59 +0100 Subject: [PATCH 15/23] #1871 ModifySetting action is now editable --- .../java/io/github/sds100/keymapper/base/actions/ActionUtils.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt index 224c0a7108..1e2ff04491 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt @@ -944,6 +944,7 @@ fun ActionData.isEditable(): Boolean = when (this) { is ActionData.ShellCommand, is ActionData.InteractUiElement, is ActionData.MoveCursor, + is ActionData.ModifySetting, -> true else -> false From 8149230c4a60912accf468021d9b3f33fed09edd Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 8 Nov 2025 01:35:35 +0100 Subject: [PATCH 16/23] #1871 add backhandler to ChooseSettingScreen --- .../sds100/keymapper/base/actions/ChooseSettingScreen.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt index d06d7ee21b..3e80ef9c00 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt @@ -1,5 +1,6 @@ package io.github.sds100.keymapper.base.actions +import androidx.activity.compose.BackHandler import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -68,6 +69,8 @@ private fun ChooseSettingScreen( onClickSetting: (String, String?) -> Unit = { _, _ -> }, onNavigateBack: () -> Unit = {}, ) { + BackHandler(onBack = onNavigateBack) + Scaffold( modifier = modifier.displayCutoutPadding(), topBar = { From 95d22d0f9eb36ad3c681cc1328e247bacb15c75c Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 8 Nov 2025 01:36:06 +0100 Subject: [PATCH 17/23] #1871 fix dismissing ModifySettingActionBottomSheet --- .../base/actions/ModifySettingActionBottomSheet.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt index c2a89aa7c5..daf53384ad 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt @@ -83,11 +83,7 @@ private fun ModifySettingActionBottomSheet( val scope = rememberCoroutineScope() ModalBottomSheet( - onDismissRequest = { - scope.launch { - sheetState.hide() - } - }, + onDismissRequest = onDismissRequest, sheetState = sheetState, dragHandle = null, ) { From 2b0efdce862b46ad7b77bc11c011ed9aa3cf62b6 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 8 Nov 2025 01:36:50 +0100 Subject: [PATCH 18/23] chore: upgrade compose BOM and navigation libraries --- .../base/actions/ActionOptionsBottomSheet.kt | 10 ++- .../actions/FlashlightActionBottomSheet.kt | 18 +++-- .../base/actions/HttpRequestBottomSheet.kt | 10 ++- .../actions/ModifySettingActionBottomSheet.kt | 6 +- .../base/actions/SmsActionBottomSheet.kt | 18 +++-- .../base/actions/VolumeActionBottomSheet.kt | 10 ++- .../keyevent/FixKeyEventActionBottomSheet.kt | 14 ++-- .../constraints/TimeConstraintBottomSheet.kt | 6 +- .../trigger/TriggerDiscoverBottomSheet.kt | 6 +- .../trigger/TriggerKeyOptionsBottomSheet.kt | 22 +++---- .../base/trigger/TriggerSetupBottomSheet.kt | 66 +++++++++---------- gradle/libs.versions.toml | 6 +- 12 files changed, 85 insertions(+), 107 deletions(-) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionOptionsBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionOptionsBottomSheet.kt index c9b11661c5..90cf7cba0c 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionOptionsBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionOptionsBottomSheet.kt @@ -24,14 +24,12 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedButton import androidx.compose.material3.SheetState -import androidx.compose.material3.SheetValue.Expanded import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -413,8 +411,8 @@ private fun Preview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) ActionOptionsBottomSheet( @@ -472,8 +470,8 @@ private fun PreviewNoEditButton() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) ActionOptionsBottomSheet( diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/FlashlightActionBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/FlashlightActionBottomSheet.kt index 60f4fa07c8..d7d058aa61 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/FlashlightActionBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/FlashlightActionBottomSheet.kt @@ -31,7 +31,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedButton import androidx.compose.material3.SheetState -import androidx.compose.material3.SheetValue import androidx.compose.material3.Slider import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -41,7 +40,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -461,8 +459,8 @@ private fun PreviewBothLenses() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) EnableFlashlightActionBottomSheet( @@ -497,8 +495,8 @@ private fun PreviewOnlyBackLens() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) EnableFlashlightActionBottomSheet( @@ -528,8 +526,8 @@ private fun PreviewOnlyBackLensChangeStrength() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) ChangeFlashlightStrengthActionBottomSheet( @@ -557,8 +555,8 @@ private fun PreviewUnsupportedAndroidVersion() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) EnableFlashlightActionBottomSheet( diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/HttpRequestBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/HttpRequestBottomSheet.kt index 696b6f2f9f..2ebe6e1d51 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/HttpRequestBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/HttpRequestBottomSheet.kt @@ -19,7 +19,6 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.SheetState -import androidx.compose.material3.SheetValue import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable @@ -30,7 +29,6 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType @@ -299,8 +297,8 @@ private fun PreviewEmpty() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) HttpRequestBottomSheet( sheetState = sheetState, @@ -323,8 +321,8 @@ private fun PreviewFilled() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) HttpRequestBottomSheet( sheetState = sheetState, diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt index daf53384ad..5b52e4385b 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt @@ -16,14 +16,12 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.SheetState -import androidx.compose.material3.SheetValue import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.style.TextAlign @@ -183,8 +181,8 @@ private fun Preview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) ModifySettingActionBottomSheet( diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/SmsActionBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/SmsActionBottomSheet.kt index a4ea1ee42f..b6ad399bb4 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/SmsActionBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/SmsActionBottomSheet.kt @@ -20,7 +20,6 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.SheetState -import androidx.compose.material3.SheetValue import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable @@ -32,7 +31,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType @@ -334,8 +332,8 @@ private fun Preview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) SmsActionBottomSheet( @@ -357,8 +355,8 @@ private fun PreviewTestError() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) SmsActionBottomSheet( @@ -380,8 +378,8 @@ private fun PreviewTestSuccess() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) SmsActionBottomSheet( @@ -403,8 +401,8 @@ private fun PreviewEmpty() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) SmsActionBottomSheet( diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/VolumeActionBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/VolumeActionBottomSheet.kt index c55738d03a..10b8b0ead8 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/VolumeActionBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/VolumeActionBottomSheet.kt @@ -19,7 +19,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedButton import androidx.compose.material3.SheetState -import androidx.compose.material3.SheetValue import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable @@ -30,7 +29,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -208,8 +206,8 @@ private fun PreviewVolumeActionBottomSheet() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) var state by remember { @@ -240,8 +238,8 @@ private fun PreviewVolumeActionBottomSheetDefaultStream() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) var state by remember { diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/FixKeyEventActionBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/FixKeyEventActionBottomSheet.kt index f3dc4e3b01..b890e9ac19 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/FixKeyEventActionBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/FixKeyEventActionBottomSheet.kt @@ -29,13 +29,11 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedCard import androidx.compose.material3.RadioButton import androidx.compose.material3.SheetState -import androidx.compose.material3.SheetValue import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.Placeholder import androidx.compose.ui.text.PlaceholderVerticalAlign @@ -307,8 +305,8 @@ private fun InputMethodPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) FixKeyEventActionBottomSheet( @@ -332,8 +330,8 @@ private fun ProModePreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) FixKeyEventActionBottomSheet( @@ -353,8 +351,8 @@ private fun ProModeUnsupportedPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) FixKeyEventActionBottomSheet( diff --git a/base/src/main/java/io/github/sds100/keymapper/base/constraints/TimeConstraintBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/constraints/TimeConstraintBottomSheet.kt index b8b1ac7370..e0a1415247 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/constraints/TimeConstraintBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/constraints/TimeConstraintBottomSheet.kt @@ -21,7 +21,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedButton import androidx.compose.material3.SheetState -import androidx.compose.material3.SheetValue import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TimePicker @@ -36,7 +35,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -275,8 +273,8 @@ private fun Preview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) TimeConstraintBottomSheet( diff --git a/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerDiscoverBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerDiscoverBottomSheet.kt index a358f590ad..cf17a0def7 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerDiscoverBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerDiscoverBottomSheet.kt @@ -9,12 +9,10 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedButton import androidx.compose.material3.SheetState -import androidx.compose.material3.SheetValue import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -68,8 +66,8 @@ private fun PreviewNoKeyRecordedComplete() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) TriggerDiscoverBottomSheet( diff --git a/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerKeyOptionsBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerKeyOptionsBottomSheet.kt index b56911fc7b..5a90be8d5c 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerKeyOptionsBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerKeyOptionsBottomSheet.kt @@ -21,7 +21,6 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.SheetState -import androidx.compose.material3.SheetValue.Expanded import androidx.compose.material3.Text import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable @@ -29,7 +28,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -411,8 +409,8 @@ private fun PreviewKeyEvent() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) TriggerKeyOptionsBottomSheet( @@ -449,8 +447,8 @@ private fun PreviewKeyEventTiny() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) TriggerKeyOptionsBottomSheet( @@ -487,8 +485,8 @@ private fun PreviewEvdev() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) TriggerKeyOptionsBottomSheet( @@ -513,8 +511,8 @@ private fun AssistantPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) TriggerKeyOptionsBottomSheet( @@ -534,8 +532,8 @@ private fun FloatingButtonPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) TriggerKeyOptionsBottomSheet( diff --git a/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerSetupBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerSetupBottomSheet.kt index 18a9d505ce..5aaf8bdefa 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerSetupBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerSetupBottomSheet.kt @@ -32,7 +32,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedButton import androidx.compose.material3.SheetState -import androidx.compose.material3.SheetValue import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState @@ -43,7 +42,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow @@ -904,8 +902,8 @@ private fun PowerButtonPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) PowerTriggerSetupBottomSheet( @@ -928,8 +926,8 @@ private fun PowerButtonDisabledPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) PowerTriggerSetupBottomSheet( @@ -952,8 +950,8 @@ private fun VolumeButtonPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) VolumeTriggerSetupBottomSheet( @@ -977,8 +975,8 @@ private fun VolumeButtonDisabledPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) VolumeTriggerSetupBottomSheet( @@ -1002,8 +1000,8 @@ private fun FingerprintGestureRequirementsMetPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) FingerprintGestureSetupBottomSheet( @@ -1024,8 +1022,8 @@ private fun FingerprintGestureRequirementsNotMetPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) FingerprintGestureSetupBottomSheet( @@ -1046,8 +1044,8 @@ private fun KeyboardButtonEnabledPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) KeyboardTriggerSetupBottomSheet( @@ -1071,8 +1069,8 @@ private fun KeyboardButtonDisabledPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) KeyboardTriggerSetupBottomSheet( @@ -1096,8 +1094,8 @@ private fun MouseButtonPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) MouseTriggerSetupBottomSheet( @@ -1120,8 +1118,8 @@ private fun MouseButtonDisabledPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) MouseTriggerSetupBottomSheet( @@ -1144,8 +1142,8 @@ private fun OtherButtonPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) OtherTriggerSetupBottomSheet( @@ -1169,8 +1167,8 @@ private fun OtherButtonDisabledPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) OtherTriggerSetupBottomSheet( @@ -1194,8 +1192,8 @@ private fun GamepadDpadPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) GamepadTriggerSetupBottomSheet( @@ -1219,8 +1217,8 @@ private fun GamepadDpadDisabledPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) GamepadTriggerSetupBottomSheet( @@ -1244,8 +1242,8 @@ private fun GamepadSimpleButtonsPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) GamepadTriggerSetupBottomSheet( @@ -1269,8 +1267,8 @@ private fun GamepadSimpleButtonsDisabledPreview() { KeyMapperTheme { val sheetState = SheetState( skipPartiallyExpanded = true, - density = LocalDensity.current, - initialValue = SheetValue.Expanded, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, ) GamepadTriggerSetupBottomSheet( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 67a55b1535..9b4fd25842 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,7 +19,7 @@ androidx-legacy-support-core-ui = "1.0.0" androidx-lifecycle = "2.9.0" androidx-lifecycle-extensions = "2.2.0" # Note: lifecycle-extensions is deprecated androidx-multidex = "2.0.1" -androidx-navigation = "2.9.0" # App level nav_version +androidx-navigation = "2.9.6" # App level nav_version androidx-navigation-safeargs-gradle-plugin = "2.6.0" # Project level nav_version androidx-preference-ktx = "1.2.1" androidx-recyclerview = "1.4.0" @@ -29,9 +29,9 @@ androidx-test-core = "1.6.1" androidx-viewpager2 = "1.1.0" dagger-hilt-android = "2.56.2" -hilt-navigation-compose = "1.2.0" +hilt-navigation-compose = "1.3.0" -compose-bom = "2025.05.01" +compose-bom = "2025.11.00" compose-compiler = "1.5.10" # kotlinCompilerExtensionVersion desugar-jdk-libs = "2.1.5" From 0f168ad6736a3e1c46f244a99c5811a9bd78b5d7 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 8 Nov 2025 01:40:58 +0100 Subject: [PATCH 19/23] #1871 use monospace font in ChooseSettingScreen.kt --- .../base/actions/ChooseSettingScreen.kt | 55 +++++++++++++++---- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt index 3e80ef9c00..bb8c2514ad 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseSettingScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.ListItem +import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface @@ -26,6 +27,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -135,18 +137,11 @@ private fun ChooseSettingScreen( ) } } else { - LazyColumn(modifier = Modifier.fillMaxSize()) { - items(state.data) { item -> - ListItem( - headlineContent = { Text(item.key) }, - supportingContent = item.value?.let { { Text(it) } }, - modifier = Modifier.clickable { - onClickSetting(item.key, item.value) - }, - ) - HorizontalDivider() - } - } + LoadedList( + modifier = Modifier.fillMaxSize(), + listItems = state.data, + onClick = onClickSetting, + ) } } } @@ -155,6 +150,42 @@ private fun ChooseSettingScreen( } } +@Composable +private fun LoadedList( + modifier: Modifier = Modifier, + listItems: List, + onClick: (String, String?) -> Unit, +) { + LazyColumn(modifier = modifier) { + items(listItems) { item -> + ListItem( + headlineContent = { + Text( + item.key, + style = LocalTextStyle.current.copy( + fontFamily = FontFamily.Monospace, + ), + ) + }, + supportingContent = item.value?.let { + { + Text( + it, + style = LocalTextStyle.current.copy( + fontFamily = FontFamily.Monospace, + ), + ) + } + }, + modifier = Modifier.clickable { + onClick(item.key, item.value) + }, + ) + HorizontalDivider() + } + } +} + @OptIn(ExperimentalMaterial3Api::class) @Preview @Composable From 94b77f2a3192d2fce6bc4142742b3cbd79703357 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 8 Nov 2025 01:48:35 +0100 Subject: [PATCH 20/23] #1871 fix saving ModifySettings action --- .../keymapper/base/actions/ActionDataEntityMapper.kt | 11 ++++++----- .../sds100/keymapper/data/entities/ActionEntity.kt | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt index 7b0fd6626e..f60f3eab11 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt @@ -22,6 +22,7 @@ import io.github.sds100.keymapper.system.camera.CameraLens import io.github.sds100.keymapper.system.intents.IntentExtraModel import io.github.sds100.keymapper.system.intents.IntentTarget import io.github.sds100.keymapper.system.network.HttpMethod +import io.github.sds100.keymapper.system.settings.SettingType import io.github.sds100.keymapper.system.volume.DndMode import io.github.sds100.keymapper.system.volume.RingerMode import io.github.sds100.keymapper.system.volume.VolumeStream @@ -50,6 +51,7 @@ object ActionDataEntityMapper { ActionEntity.Type.INTERACT_UI_ELEMENT -> ActionId.INTERACT_UI_ELEMENT ActionEntity.Type.SHELL_COMMAND -> ActionId.SHELL_COMMAND + ActionEntity.Type.MODIFY_SETTING -> ActionId.MODIFY_SETTING } return when (actionId) { @@ -732,11 +734,9 @@ object ActionDataEntityMapper { .valueOrNull() ?: "SYSTEM" // Default to SYSTEM for backward compatibility val settingType = try { - io.github.sds100.keymapper.system.settings.SettingType.valueOf( - settingTypeString, - ) - } catch (e: IllegalArgumentException) { - io.github.sds100.keymapper.system.settings.SettingType.SYSTEM + SettingType.valueOf(settingTypeString) + } catch (_: IllegalArgumentException) { + SettingType.SYSTEM } ActionData.ModifySetting( @@ -771,6 +771,7 @@ object ActionDataEntityMapper { is ActionData.Sound -> ActionEntity.Type.SOUND is ActionData.InteractUiElement -> ActionEntity.Type.INTERACT_UI_ELEMENT is ActionData.ShellCommand -> ActionEntity.Type.SHELL_COMMAND + is ActionData.ModifySetting -> ActionEntity.Type.MODIFY_SETTING else -> ActionEntity.Type.SYSTEM_ACTION } diff --git a/data/src/main/java/io/github/sds100/keymapper/data/entities/ActionEntity.kt b/data/src/main/java/io/github/sds100/keymapper/data/entities/ActionEntity.kt index 20d60ac50a..7961aa8b83 100644 --- a/data/src/main/java/io/github/sds100/keymapper/data/entities/ActionEntity.kt +++ b/data/src/main/java/io/github/sds100/keymapper/data/entities/ActionEntity.kt @@ -185,6 +185,7 @@ data class ActionEntity( SOUND, INTERACT_UI_ELEMENT, SHELL_COMMAND, + MODIFY_SETTING, } constructor( From f300bf8b8977fa54344aaacc1bd0b8f6b7e79534 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 8 Nov 2025 14:09:47 +0100 Subject: [PATCH 21/23] #1871 add TODO --- .../keymapper/base/actions/ModifySettingActionBottomSheet.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt index 5b52e4385b..e02e4fdbbb 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt @@ -142,6 +142,8 @@ private fun ModifySettingActionBottomSheet( ) // TODO do not allow empty text fields + // TODO add test button + // TODO add disclaimer that simply modifying setting values is not sufficient for the system to actually process the change Row( modifier = Modifier .fillMaxWidth() From 302095dbc5d00ceb74d2e9c0a0a33b6192372a08 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 9 Nov 2025 19:21:18 +0100 Subject: [PATCH 22/23] feat: WRITE_SECURE_SETTINGS permission dialog now directs the user to PRO mode --- .../sds100/keymapper/base/BaseMainActivity.kt | 14 +++--- .../shortcuts/CreateKeyMapShortcutActivity.kt | 16 ++++--- .../permissions/RequestPermissionDelegate.kt | 48 +++++++++++-------- base/src/main/res/values/strings.xml | 4 +- 4 files changed, 45 insertions(+), 37 deletions(-) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/BaseMainActivity.kt b/base/src/main/java/io/github/sds100/keymapper/base/BaseMainActivity.kt index 7b8f927f18..23e6cf7e58 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/BaseMainActivity.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/BaseMainActivity.kt @@ -21,7 +21,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.withStateAtLeast -import androidx.navigation.findNavController import com.anggrayudi.storage.extension.openInputStream import com.anggrayudi.storage.extension.openOutputStream import com.anggrayudi.storage.extension.toDocumentFile @@ -32,6 +31,7 @@ import io.github.sds100.keymapper.base.onboarding.OnboardingUseCase import io.github.sds100.keymapper.base.system.accessibility.AccessibilityServiceAdapterImpl import io.github.sds100.keymapper.base.system.permissions.RequestPermissionDelegate import io.github.sds100.keymapper.base.trigger.RecordTriggerControllerImpl +import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider import io.github.sds100.keymapper.base.utils.ui.ResourceProviderImpl import io.github.sds100.keymapper.common.BuildConfigProvider import io.github.sds100.keymapper.sysbridge.service.SystemBridgeSetupControllerImpl @@ -43,12 +43,12 @@ import io.github.sds100.keymapper.system.notifications.NotificationReceiverAdapt import io.github.sds100.keymapper.system.permissions.AndroidPermissionAdapter import io.github.sds100.keymapper.system.root.SuAdapterImpl import io.github.sds100.keymapper.system.shizuku.ShizukuAdapter -import javax.inject.Inject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import timber.log.Timber +import javax.inject.Inject abstract class BaseMainActivity : AppCompatActivity() { @@ -105,6 +105,9 @@ abstract class BaseMainActivity : AppCompatActivity() { @Inject lateinit var inputEventHub: InputEventHubImpl + @Inject + lateinit var navigationProvider: NavigationProvider + private lateinit var requestPermissionDelegate: RequestPermissionDelegate private val currentNightMode: Int @@ -162,15 +165,14 @@ abstract class BaseMainActivity : AppCompatActivity() { notificationReceiverAdapter = notificationReceiverAdapter, buildConfigProvider = buildConfigProvider, shizukuAdapter = shizukuAdapter, + navigationProvider = navigationProvider, + coroutineScope = lifecycleScope, ) permissionAdapter.request .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) .onEach { permission -> - requestPermissionDelegate.requestPermission( - permission, - findNavController(R.id.container), - ) + requestPermissionDelegate.requestPermission(permission) } .launchIn(lifecycleScope) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/shortcuts/CreateKeyMapShortcutActivity.kt b/base/src/main/java/io/github/sds100/keymapper/base/shortcuts/CreateKeyMapShortcutActivity.kt index 741ed4f789..d13b869230 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/shortcuts/CreateKeyMapShortcutActivity.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/shortcuts/CreateKeyMapShortcutActivity.kt @@ -8,23 +8,23 @@ import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.compose.ui.graphics.toArgb import androidx.lifecycle.Lifecycle -import androidx.navigation.findNavController +import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint -import io.github.sds100.keymapper.base.R import io.github.sds100.keymapper.base.compose.ComposeColors import io.github.sds100.keymapper.base.compose.KeyMapperTheme import io.github.sds100.keymapper.base.onboarding.OnboardingUseCase import io.github.sds100.keymapper.base.system.accessibility.AccessibilityServiceAdapterImpl import io.github.sds100.keymapper.base.system.permissions.RequestPermissionDelegate import io.github.sds100.keymapper.base.trigger.RecordTriggerControllerImpl +import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider import io.github.sds100.keymapper.base.utils.ui.ResourceProviderImpl import io.github.sds100.keymapper.base.utils.ui.launchRepeatOnLifecycle import io.github.sds100.keymapper.common.BuildConfigProvider import io.github.sds100.keymapper.system.notifications.NotificationReceiverAdapterImpl import io.github.sds100.keymapper.system.permissions.AndroidPermissionAdapter import io.github.sds100.keymapper.system.shizuku.ShizukuAdapter -import javax.inject.Inject import kotlinx.coroutines.flow.collectLatest +import javax.inject.Inject @AndroidEntryPoint class CreateKeyMapShortcutActivity : AppCompatActivity() { @@ -53,6 +53,9 @@ class CreateKeyMapShortcutActivity : AppCompatActivity() { @Inject lateinit var buildConfigProvider: BuildConfigProvider + @Inject + lateinit var navigationProvider: NavigationProvider + private lateinit var requestPermissionDelegate: RequestPermissionDelegate private val viewModel by viewModels() @@ -86,15 +89,14 @@ class CreateKeyMapShortcutActivity : AppCompatActivity() { notificationReceiverAdapter = notificationReceiverAdapter, buildConfigProvider = buildConfigProvider, shizukuAdapter = shizukuAdapter, + navigationProvider = navigationProvider, + coroutineScope = lifecycleScope, ) launchRepeatOnLifecycle(Lifecycle.State.STARTED) { permissionAdapter.request .collectLatest { permission -> - requestPermissionDelegate.requestPermission( - permission, - findNavController(R.id.container), - ) + requestPermissionDelegate.requestPermission(permission) } } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/system/permissions/RequestPermissionDelegate.kt b/base/src/main/java/io/github/sds100/keymapper/base/system/permissions/RequestPermissionDelegate.kt index 22067867e6..89f521832f 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/system/permissions/RequestPermissionDelegate.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/system/permissions/RequestPermissionDelegate.kt @@ -13,16 +13,21 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat -import androidx.navigation.NavController import io.github.sds100.keymapper.base.R +import io.github.sds100.keymapper.base.utils.navigation.NavDestination +import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider +import io.github.sds100.keymapper.base.utils.navigation.navigate import io.github.sds100.keymapper.base.utils.ui.str import io.github.sds100.keymapper.common.BuildConfigProvider +import io.github.sds100.keymapper.common.utils.onFailure import io.github.sds100.keymapper.system.DeviceAdmin import io.github.sds100.keymapper.system.notifications.NotificationReceiverAdapterImpl import io.github.sds100.keymapper.system.permissions.AndroidPermissionAdapter import io.github.sds100.keymapper.system.permissions.Permission import io.github.sds100.keymapper.system.shizuku.ShizukuAdapter import io.github.sds100.keymapper.system.url.UrlUtils +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import splitties.alertdialog.appcompat.messageResource import splitties.alertdialog.appcompat.negativeButton import splitties.alertdialog.appcompat.neutralButton @@ -38,6 +43,8 @@ class RequestPermissionDelegate( private val notificationReceiverAdapter: NotificationReceiverAdapterImpl, private val buildConfigProvider: BuildConfigProvider, private val shizukuAdapter: ShizukuAdapter, + private val navigationProvider: NavigationProvider, + private val coroutineScope: CoroutineScope, ) { private val startActivityForResultLauncher = @@ -58,7 +65,7 @@ class RequestPermissionDelegate( permissionAdapter.onPermissionsChanged() } - fun requestPermission(permission: Permission, navController: NavController?) { + fun requestPermission(permission: Permission) { when (permission) { Permission.WRITE_SETTINGS -> requestWriteSettings() Permission.CAMERA -> requestPermissionLauncher.launch(Manifest.permission.CAMERA) @@ -162,30 +169,29 @@ class RequestPermissionDelegate( } private fun requestWriteSecureSettings() { - if (permissionAdapter.isGranted(Permission.SHIZUKU) || - permissionAdapter.isGranted(Permission.ROOT) - ) { - permissionAdapter.grant(Manifest.permission.WRITE_SECURE_SETTINGS) + // Try granting with Shizuku, Root, or System Bridge + permissionAdapter.grant(Manifest.permission.WRITE_SECURE_SETTINGS).onFailure { error -> + activity.materialAlertDialog { + titleResource = R.string.dialog_title_write_secure_settings + messageResource = R.string.dialog_message_write_secure_settings - return - } + positiveButton(R.string.pos_proceed) { + val destination = NavDestination.ProMode - activity.materialAlertDialog { - titleResource = R.string.dialog_title_write_secure_settings - messageResource = R.string.dialog_message_write_secure_settings + coroutineScope.launch { + navigationProvider.navigate( + "grant_write_secure_settings_pro_mode", + destination, + ) + } + } - positiveButton(R.string.pos_grant_write_secure_settings_guide) { - UrlUtils.openUrl( - activity, - activity.str(R.string.url_grant_write_secure_settings_guide), - ) - } + negativeButton(R.string.neg_cancel) { + it.cancel() + } - negativeButton(R.string.neg_cancel) { - it.cancel() + show() } - - show() } } diff --git a/base/src/main/res/values/strings.xml b/base/src/main/res/values/strings.xml index 6c72a161fa..04d65e7fa7 100644 --- a/base/src/main/res/values/strings.xml +++ b/base/src/main/res/values/strings.xml @@ -328,7 +328,6 @@ https://docs.keymapper.club/redirects/fingerprint-map-options https://docs.keymapper.club/redirects/quick-start https://docs.keymapper.club/redirects/faq - https://docs.keymapper.club/redirects/grant-write-secure-settings https://dontkillmyapp.com https://docs.keymapper.club/redirects/keymap-action-options https://docs.keymapper.club/redirects/trigger-key-options @@ -427,7 +426,7 @@ Please grant Key Mapper root permission in your root management app, such as Magisk. Grant WRITE_SECURE_SETTINGS permission - A PC/Mac is required to grant this permission. Read the online guide. + You will need to use PRO mode to grant this permission. Your device doesn\'t seem to have an accessibility services settings page. Tap \"guide\" to read the online guide that explains how to fix this. You must hold down the keys in the order that they are listed. @@ -487,7 +486,6 @@ Done Kill - Guide Guide Change Fix partially From ca8951d6c3632fc3e8748fda7326287307db013f Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 9 Nov 2025 19:43:21 +0100 Subject: [PATCH 23/23] #1871 feat: complete modify system setting action --- CHANGELOG.md | 7 + base/src/main/assets/whats-new.txt | 1 + .../keymapper/base/actions/ActionUiHelper.kt | 10 +- .../base/actions/CreateActionDelegate.kt | 48 +++- .../base/actions/CreateActionUseCase.kt | 27 +- .../actions/ModifySettingActionBottomSheet.kt | 257 +++++++++++++++++- .../base/actions/PerformActionsUseCase.kt | 2 +- base/src/main/res/values/strings.xml | 8 +- .../system/settings/SettingsAdapter.kt | 35 ++- 9 files changed, 364 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38c136de1c..be03102384 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [4.0.0 Beta 3](https://github.com/sds100/KeyMapper/releases/tag/v4.0.0-beta.03) + +#### TO BE RELEASED + +## Added +- #1871 action to modify any system settings + ## [4.0.0 Beta 2](https://github.com/sds100/KeyMapper/releases/tag/v4.0.0-beta.02) #### 08 November 2025 diff --git a/base/src/main/assets/whats-new.txt b/base/src/main/assets/whats-new.txt index d170dd02d8..a9bdb53303 100644 --- a/base/src/main/assets/whats-new.txt +++ b/base/src/main/assets/whats-new.txt @@ -6,6 +6,7 @@ You can now remap ALL buttons when the screen is off (including the power button • Send SMS messages • Force stop current app or clear from recents • Mute/unmute microphone +• Modify any system setting 🆕 New Features • Redesigned Settings screen diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt index 6dbae2f8b6..579ed07356 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt @@ -653,17 +653,9 @@ class ActionUiHelper( ActionData.Microphone.Unmute -> getString(R.string.action_unmute_microphone) is ActionData.ModifySetting -> { - val typeString = when (action.settingType) { - io.github.sds100.keymapper.system.settings.SettingType.SYSTEM -> - getString(R.string.modify_setting_type_system) - io.github.sds100.keymapper.system.settings.SettingType.SECURE -> - getString(R.string.modify_setting_type_secure) - io.github.sds100.keymapper.system.settings.SettingType.GLOBAL -> - getString(R.string.modify_setting_type_global) - } getString( R.string.modify_setting_description, - arrayOf(action.settingKey, action.value, typeString), + arrayOf(action.settingKey, action.value), ) } } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt index 359546f2d0..85d31ee0e5 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt @@ -4,6 +4,7 @@ import android.text.InputType import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import io.github.sds100.keymapper.base.R import io.github.sds100.keymapper.base.actions.pinchscreen.PinchPickCoordinateResult import io.github.sds100.keymapper.base.actions.swipescreen.SwipePickCoordinateResult @@ -29,13 +30,17 @@ import io.github.sds100.keymapper.system.volume.DndMode import io.github.sds100.keymapper.system.volume.RingerMode import io.github.sds100.keymapper.system.volume.VolumeStream import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.serialization.json.Json +@OptIn(ExperimentalCoroutinesApi::class) class CreateActionDelegate( private val coroutineScope: CoroutineScope, private val useCase: CreateActionUseCase, @@ -67,6 +72,22 @@ class CreateActionDelegate( } } } + + coroutineScope.launch { + snapshotFlow { modifySettingActionBottomSheetState?.settingType } + .filterNotNull() + .flatMapLatest { settingType -> + val permission = useCase.getRequiredPermissionForSettingType(settingType) + useCase.isPermissionGrantedFlow(permission) + } + .collectLatest { isGranted -> + modifySettingActionBottomSheetState = + modifySettingActionBottomSheetState?.copy( + isPermissionGranted = isGranted, + testResult = null, + ) + } + } } fun onDoneConfigEnableFlashlightClick() { @@ -214,12 +235,18 @@ class CreateActionDelegate( fun onSelectSettingType(settingType: SettingType) { modifySettingActionBottomSheetState = - modifySettingActionBottomSheetState?.copy(settingType = settingType) + modifySettingActionBottomSheetState?.copy( + settingType = settingType, + testResult = null, + ) } fun onSettingKeyChange(key: String) { modifySettingActionBottomSheetState = - modifySettingActionBottomSheetState?.copy(settingKey = key) + modifySettingActionBottomSheetState?.copy( + settingKey = key, + testResult = null, + ) } fun onChooseExistingSettingClick() { @@ -233,6 +260,7 @@ class CreateActionDelegate( settingType = setting.settingType, settingKey = setting.key, value = setting.currentValue ?: "", + testResult = null, ) } } @@ -242,6 +270,22 @@ class CreateActionDelegate( modifySettingActionBottomSheetState?.copy(value = value) } + fun onTestModifySettingClick() { + val state = modifySettingActionBottomSheetState ?: return + + coroutineScope.launch { + val result = useCase.setSettingValue(state.settingType, state.settingKey, state.value) + modifySettingActionBottomSheetState = + modifySettingActionBottomSheetState?.copy(testResult = result) + } + } + + fun onRequestModifySettingPermission() { + val state = modifySettingActionBottomSheetState ?: return + val permission = useCase.getRequiredPermissionForSettingType(state.settingType) + useCase.requestPermission(permission) + } + suspend fun editAction(oldData: ActionData) { if (!oldData.isEditable()) { throw IllegalArgumentException("This action ${oldData.javaClass.name} can't be edited!") diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionUseCase.kt index 2e7071def9..8538a859b4 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionUseCase.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionUseCase.kt @@ -11,10 +11,12 @@ import io.github.sds100.keymapper.system.permissions.Permission import io.github.sds100.keymapper.system.permissions.PermissionAdapter import io.github.sds100.keymapper.system.permissions.SystemFeatureAdapter import io.github.sds100.keymapper.system.phone.PhoneAdapter -import javax.inject.Inject +import io.github.sds100.keymapper.system.settings.SettingType +import io.github.sds100.keymapper.system.settings.SettingsAdapter import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.merge +import javax.inject.Inject class CreateActionUseCaseImpl @Inject constructor( private val inputMethodAdapter: InputMethodAdapter, @@ -22,6 +24,7 @@ class CreateActionUseCaseImpl @Inject constructor( private val cameraAdapter: CameraAdapter, private val permissionAdapter: PermissionAdapter, private val phoneAdapter: PhoneAdapter, + private val settingsAdapter: SettingsAdapter, ) : CreateActionUseCase, IsActionSupportedUseCase by IsActionSupportedUseCaseImpl( systemFeatureAdapter, @@ -69,6 +72,25 @@ class CreateActionUseCaseImpl @Inject constructor( return phoneAdapter.sendSms(number, message) } + + override fun setSettingValue( + settingType: SettingType, + key: String, + value: String, + ): KMResult { + return settingsAdapter.setValue(settingType, key, value) + } + + override fun getRequiredPermissionForSettingType(settingType: SettingType): Permission { + return when (settingType) { + SettingType.SYSTEM -> Permission.WRITE_SETTINGS + SettingType.SECURE, SettingType.GLOBAL -> Permission.WRITE_SECURE_SETTINGS + } + } + + override fun isPermissionGrantedFlow(permission: Permission): Flow { + return permissionAdapter.isGrantedFlow(permission) + } } interface CreateActionUseCase : IsActionSupportedUseCase { @@ -83,4 +105,7 @@ interface CreateActionUseCase : IsActionSupportedUseCase { fun requestPermission(permission: Permission) suspend fun testSms(number: String, message: String): KMResult + fun setSettingValue(settingType: SettingType, key: String, value: String): KMResult + fun getRequiredPermissionForSettingType(settingType: SettingType): Permission + fun isPermissionGrantedFlow(permission: Permission): Flow } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt index e02e4fdbbb..8865110d47 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ModifySettingActionBottomSheet.kt @@ -10,7 +10,9 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedButton @@ -19,9 +21,15 @@ import androidx.compose.material3.SheetState import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.style.TextAlign @@ -29,7 +37,15 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.github.sds100.keymapper.base.R import io.github.sds100.keymapper.base.compose.KeyMapperTheme +import io.github.sds100.keymapper.base.compose.LocalCustomColorsPalette +import io.github.sds100.keymapper.base.utils.getFullMessage import io.github.sds100.keymapper.base.utils.ui.compose.KeyMapperSegmentedButtonRow +import io.github.sds100.keymapper.base.utils.ui.compose.filledTonalButtonColorsError +import io.github.sds100.keymapper.common.utils.KMError +import io.github.sds100.keymapper.common.utils.KMResult +import io.github.sds100.keymapper.common.utils.Success +import io.github.sds100.keymapper.system.SystemError +import io.github.sds100.keymapper.system.permissions.Permission import io.github.sds100.keymapper.system.settings.SettingType import kotlinx.coroutines.launch @@ -37,6 +53,8 @@ data class ModifySettingActionBottomSheetState( val settingType: SettingType, val settingKey: String, val value: String, + val testResult: KMResult? = null, + val isPermissionGranted: Boolean = false, ) @OptIn(ExperimentalMaterial3Api::class) @@ -56,6 +74,8 @@ fun ModifySettingActionBottomSheet(delegate: CreateActionDelegate) { onSettingKeyChange = delegate::onSettingKeyChange, onSettingValueChange = delegate::onSettingValueChange, onChooseExistingClick = delegate::onChooseExistingSettingClick, + onTestClick = delegate::onTestModifySettingClick, + onRequestPermissionClick = delegate::onRequestModifySettingPermission, onDoneClick = { scope.launch { sheetState.hide() @@ -76,10 +96,28 @@ private fun ModifySettingActionBottomSheet( onSettingKeyChange: (String) -> Unit = {}, onSettingValueChange: (String) -> Unit = {}, onChooseExistingClick: () -> Unit = {}, + onTestClick: () -> Unit = {}, + onRequestPermissionClick: () -> Unit = {}, onDoneClick: () -> Unit = {}, ) { val scope = rememberCoroutineScope() + val settingKeyEmptyErrorString = stringResource(R.string.modify_setting_key_empty_error) + val settingValueEmptyErrorString = stringResource(R.string.modify_setting_value_empty_error) + + var settingKeyError: String? by rememberSaveable { mutableStateOf(null) } + var settingValueError: String? by rememberSaveable { mutableStateOf(null) } + + LaunchedEffect(state) { + if (!state.settingKey.isBlank()){ + settingKeyError = null + } + + if (!state.value.isBlank()){ + settingValueError = null + } + } + ModalBottomSheet( onDismissRequest = onDismissRequest, sheetState = sheetState, @@ -112,6 +150,16 @@ private fun ModifySettingActionBottomSheet( onStateSelected = onSelectSettingType, ) + if (!state.isPermissionGranted) { + FilledTonalButton( + modifier = Modifier.fillMaxWidth(), + onClick = onRequestPermissionClick, + colors = ButtonDefaults.filledTonalButtonColorsError(), + ) { + Text(stringResource(R.string.modify_setting_grant_permission_button)) + } + } + Button( onClick = onChooseExistingClick, modifier = Modifier.fillMaxWidth(), @@ -125,9 +173,18 @@ private fun ModifySettingActionBottomSheet( label = { Text(stringResource(R.string.modify_setting_key_label)) }, modifier = Modifier.fillMaxWidth(), singleLine = true, - textStyle = MaterialTheme.typography.bodySmall.copy( + textStyle = MaterialTheme.typography.bodyMedium.copy( fontFamily = FontFamily.Monospace, ), + isError = settingKeyError != null, + supportingText = { + if (settingKeyError != null) { + Text( + text = settingKeyError!!, + color = MaterialTheme.colorScheme.error, + ) + } + }, ) OutlinedTextField( @@ -136,14 +193,76 @@ private fun ModifySettingActionBottomSheet( label = { Text(stringResource(R.string.modify_setting_value_label)) }, modifier = Modifier.fillMaxWidth(), singleLine = true, - textStyle = MaterialTheme.typography.bodySmall.copy( + textStyle = MaterialTheme.typography.bodyMedium.copy( fontFamily = FontFamily.Monospace, ), + isError = settingValueError != null, + supportingText = { + if (settingValueError != null) { + Text( + text = settingValueError!!, + color = MaterialTheme.colorScheme.error, + ) + } + }, + ) + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End, + ) { + if (state.testResult != null) { + val resultText: String = when (state.testResult) { + is Success -> stringResource(R.string.test_modify_setting_result_ok) + is KMError -> state.testResult.getFullMessage(LocalContext.current) + } + + val textColor = when (state.testResult) { + is Success -> LocalCustomColorsPalette.current.green + is KMError -> MaterialTheme.colorScheme.error + } + + Text( + modifier = Modifier.weight(1f), + text = resultText, + color = textColor, + style = MaterialTheme.typography.bodyMedium, + ) + } + + Spacer(modifier = Modifier.width(16.dp)) + + OutlinedButton( + onClick = { + var hasError = false + + if (state.settingKey.isBlank()) { + settingKeyError = settingKeyEmptyErrorString + hasError = true + } + + if (state.value.isBlank()) { + settingValueError = settingValueEmptyErrorString + hasError = true + } + + if (!hasError) { + onTestClick() + } + }, + ) { + Text(stringResource(R.string.button_test_modify_setting)) + } + } + + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.modify_setting_disclaimer), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, ) - // TODO do not allow empty text fields - // TODO add test button - // TODO add disclaimer that simply modifying setting values is not sufficient for the system to actually process the change Row( modifier = Modifier .fillMaxWidth() @@ -167,7 +286,19 @@ private fun ModifySettingActionBottomSheet( Button( modifier = Modifier.weight(1f), - onClick = onDoneClick, + onClick = { + if (state.settingKey.isBlank()) { + settingKeyError = settingKeyEmptyErrorString + } + + if (state.value.isBlank()) { + settingValueError = settingValueEmptyErrorString + } + + if (settingKeyError == null && settingValueError == null) { + onDoneClick() + } + }, ) { Text(stringResource(R.string.pos_done)) } @@ -197,3 +328,117 @@ private fun Preview() { ) } } + +@OptIn(ExperimentalMaterial3Api::class) +@Preview +@Composable +private fun PreviewEmpty() { + KeyMapperTheme { + val sheetState = SheetState( + skipPartiallyExpanded = true, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, + ) + + ModifySettingActionBottomSheet( + sheetState = sheetState, + state = ModifySettingActionBottomSheetState( + settingType = SettingType.SYSTEM, + settingKey = "", + value = "", + ), + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Preview +@Composable +private fun PreviewPermissionNotGranted() { + KeyMapperTheme { + val sheetState = SheetState( + skipPartiallyExpanded = true, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, + ) + + ModifySettingActionBottomSheet( + sheetState = sheetState, + state = ModifySettingActionBottomSheetState( + settingType = SettingType.SECURE, + settingKey = "airplane_mode_on", + value = "1", + isPermissionGranted = false, + ), + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Preview +@Composable +private fun PreviewTestLoading() { + KeyMapperTheme { + val sheetState = SheetState( + skipPartiallyExpanded = true, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, + ) + + ModifySettingActionBottomSheet( + sheetState = sheetState, + state = ModifySettingActionBottomSheetState( + settingType = SettingType.GLOBAL, + settingKey = "adb_enabled", + value = "1", + testResult = null, + ), + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Preview +@Composable +private fun PreviewTestSuccess() { + KeyMapperTheme { + val sheetState = SheetState( + skipPartiallyExpanded = true, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, + ) + + ModifySettingActionBottomSheet( + sheetState = sheetState, + state = ModifySettingActionBottomSheetState( + settingType = SettingType.GLOBAL, + settingKey = "adb_enabled", + value = "1", + testResult = Success(Unit), + ), + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Preview +@Composable +private fun PreviewTestError() { + KeyMapperTheme { + val sheetState = SheetState( + skipPartiallyExpanded = true, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, + ) + + ModifySettingActionBottomSheet( + sheetState = sheetState, + state = ModifySettingActionBottomSheetState( + settingType = SettingType.SECURE, + settingKey = "airplane_mode_on", + value = "1", + testResult = SystemError.PermissionDenied(Permission.WRITE_SECURE_SETTINGS), + ), + ) + } +} diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt index 413d2272b6..9d05d0f6bc 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt @@ -1019,7 +1019,7 @@ class PerformActionsUseCaseImpl @AssistedInject constructor( } is ActionData.ModifySetting -> { - result = settingsAdapter.modifySetting( + result = settingsAdapter.setValue( action.settingType, action.settingKey, action.value, diff --git a/base/src/main/res/values/strings.xml b/base/src/main/res/values/strings.xml index 04d65e7fa7..ebbdcbc3b1 100644 --- a/base/src/main/res/values/strings.xml +++ b/base/src/main/res/values/strings.xml @@ -1090,7 +1090,7 @@ Send SMS: "%s"" to %s Compose SMS Compose SMS: "%s" to %s - Set %3$s: %1$s = %2$s + Set setting: %1$s = %2$s System Secure Global @@ -1179,6 +1179,12 @@ Modify setting Key Value + Setting key cannot be empty + Setting value cannot be empty + Note: Simply modifying setting values may not be sufficient for the system to process the change. Some settings require additional actions or broadcasts to take effect. + Test + Setting modified successfully + Grant permission Choose setting No settings found Choose existing setting diff --git a/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt index 493ba0f327..3551501e68 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/settings/SettingsAdapter.kt @@ -8,6 +8,8 @@ import io.github.sds100.keymapper.common.utils.KMError import io.github.sds100.keymapper.common.utils.KMResult import io.github.sds100.keymapper.common.utils.SettingsUtils import io.github.sds100.keymapper.common.utils.Success +import io.github.sds100.keymapper.system.SystemError +import io.github.sds100.keymapper.system.permissions.Permission import javax.inject.Inject import javax.inject.Singleton @@ -66,17 +68,28 @@ class AndroidSettingsAdapter @Inject constructor( } } - override fun modifySetting(settingType: SettingType, key: String, value: String): KMResult<*> { - val success = when (settingType) { - SettingType.SYSTEM -> SettingsUtils.putSystemSetting(ctx, key, value) - SettingType.SECURE -> SettingsUtils.putSecureSetting(ctx, key, value) - SettingType.GLOBAL -> SettingsUtils.putGlobalSetting(ctx, key, value) - } + override fun setValue(settingType: SettingType, key: String, value: String): KMResult { + try { + val success = when (settingType) { + SettingType.SYSTEM -> SettingsUtils.putSystemSetting(ctx, key, value) + SettingType.SECURE -> SettingsUtils.putSecureSetting(ctx, key, value) + SettingType.GLOBAL -> SettingsUtils.putGlobalSetting(ctx, key, value) + } - return if (success) { - Success(Unit) - } else { - KMError.FailedToModifySystemSetting(key) + return if (success) { + Success(Unit) + } else { + KMError.FailedToModifySystemSetting(key) + } + } catch (_: IllegalArgumentException) { + return KMError.FailedToModifySystemSetting(key) + } catch (_: SecurityException) { + return when (settingType) { + SettingType.SYSTEM -> + SystemError.PermissionDenied(Permission.WRITE_SETTINGS) + SettingType.SECURE, SettingType.GLOBAL -> + SystemError.PermissionDenied(Permission.WRITE_SECURE_SETTINGS) + } } } } @@ -84,5 +97,5 @@ class AndroidSettingsAdapter @Inject constructor( interface SettingsAdapter { fun getAll(settingType: SettingType): Map fun getValue(settingType: SettingType, key: String): String? - fun modifySetting(settingType: SettingType, key: String, value: String): KMResult<*> + fun setValue(settingType: SettingType, key: String, value: String): KMResult }