diff --git a/app/schemas/de.langerhans.odintools.data.AppDatabase/3.json b/app/schemas/de.langerhans.odintools.data.AppDatabase/3.json new file mode 100644 index 0000000..9c3ddad --- /dev/null +++ b/app/schemas/de.langerhans.odintools.data.AppDatabase/3.json @@ -0,0 +1,64 @@ +{ + "formatVersion": 1, + "database": { + "version": 3, + "identityHash": "83c89ea9294a5f18c12f79b54aecff49", + "entities": [ + { + "tableName": "appoverride", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `controllerStyle` TEXT, `l2R2Style` TEXT, `perfMode` TEXT, `fanMode` TEXT, `vibrationStrength` INTEGER, PRIMARY KEY(`packageName`))", + "fields": [ + { + "fieldPath": "packageName", + "columnName": "packageName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "controllerStyle", + "columnName": "controllerStyle", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "l2R2Style", + "columnName": "l2R2Style", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "perfMode", + "columnName": "perfMode", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "fanMode", + "columnName": "fanMode", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vibrationStrength", + "columnName": "vibrationStrength", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "packageName" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '83c89ea9294a5f18c12f79b54aecff49')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/de/langerhans/odintools/appsettings/AppOverrideListUiModel.kt b/app/src/main/java/de/langerhans/odintools/appsettings/AppOverrideListUiModel.kt index 3c90262..2cd8537 100644 --- a/app/src/main/java/de/langerhans/odintools/appsettings/AppOverrideListUiModel.kt +++ b/app/src/main/java/de/langerhans/odintools/appsettings/AppOverrideListUiModel.kt @@ -33,4 +33,5 @@ data class AppUiModel( val l2r2Style: L2R2Style? = null, val fanMode: FanMode? = null, val perfMode: PerfMode? = null, + val vibrationStrength: Int? = null, ) diff --git a/app/src/main/java/de/langerhans/odintools/appsettings/AppOverrideMapper.kt b/app/src/main/java/de/langerhans/odintools/appsettings/AppOverrideMapper.kt index 2c5cb41..3d4adc5 100644 --- a/app/src/main/java/de/langerhans/odintools/appsettings/AppOverrideMapper.kt +++ b/app/src/main/java/de/langerhans/odintools/appsettings/AppOverrideMapper.kt @@ -44,16 +44,18 @@ class AppOverrideMapper @Inject constructor( val l2R2Style = L2R2Style.getById(app.l2R2Style) val perfMode = PerfMode.getById(app.perfMode) val fanMode = FanMode.getById(app.fanMode) + val vibrationStrength = app.vibrationStrength return AppUiModel( packageName = app.packageName, appName = context.packageManager.getApplicationLabel(appInfo).toString(), appIcon = context.packageManager.getApplicationIcon(appInfo), - subtitle = getSubtitle(controllerStyle, l2R2Style, perfMode, fanMode), + subtitle = getSubtitle(controllerStyle, l2R2Style, perfMode, fanMode, vibrationStrength), controllerStyle = controllerStyle, l2r2Style = l2R2Style, perfMode = perfMode, fanMode = fanMode, + vibrationStrength = vibrationStrength, ) } @@ -68,7 +70,13 @@ class AppOverrideMapper @Inject constructor( ) } - private fun getSubtitle(controllerStyle: ControllerStyle, l2R2Style: L2R2Style, perfMode: PerfMode, fanMode: FanMode): String? { + private fun getSubtitle( + controllerStyle: ControllerStyle, + l2R2Style: L2R2Style, + perfMode: PerfMode, + fanMode: FanMode, + vibrationStrength: Int?, + ): String? { return buildString { if (controllerStyle != ControllerStyle.Unknown) { append(context.getString(R.string.controllerStyle)) @@ -88,6 +96,12 @@ class AppOverrideMapper @Inject constructor( append(context.getString(perfMode.textRes)) append(" | ") } + if (vibrationStrength != null) { + append(context.getString(R.string.vibrationStrength)) + append(": ") + append(vibrationStrength.toString()) + append(" | ") + } if (fanMode != FanMode.Unknown) { append(context.getString(R.string.fanMode)) append(": ") diff --git a/app/src/main/java/de/langerhans/odintools/appsettings/AppOverridesScreen.kt b/app/src/main/java/de/langerhans/odintools/appsettings/AppOverridesScreen.kt index 689b94a..b17b007 100644 --- a/app/src/main/java/de/langerhans/odintools/appsettings/AppOverridesScreen.kt +++ b/app/src/main/java/de/langerhans/odintools/appsettings/AppOverridesScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -49,9 +50,11 @@ import de.langerhans.odintools.models.PerfMode import de.langerhans.odintools.models.PerfMode.HighPerformance import de.langerhans.odintools.models.PerfMode.Performance import de.langerhans.odintools.models.PerfMode.Standard +import de.langerhans.odintools.tools.DeviceType import de.langerhans.odintools.ui.composables.DeleteConfirmDialog import de.langerhans.odintools.ui.composables.LargeDropdownMenu import de.langerhans.odintools.ui.composables.OdinTopAppBar +import de.langerhans.odintools.ui.composables.VibrationPreferenceDialog import de.langerhans.odintools.ui.theme.Typography @Composable @@ -181,6 +184,17 @@ fun AppOverridesScreen(viewModel: AppOverridesViewModel = hiltViewModel(), navig onSelectionChanged = { viewModel.perfModeSelected(it) }, modifier = Modifier.padding(bottom = 16.dp), ) + if (viewModel.getDeviceType() == DeviceType.ODIN2) { + Column { + VibrationPreferenceRow( + label = stringResource(id = R.string.vibrationStrength), + initialValue = uiState.app?.vibrationStrength ?: 0, + onSave = { newValue -> + viewModel.vibrationStrengthSelected(newValue) + }, + ) + } + } if (uiState.app?.perfMode != null && uiState.app?.perfMode != PerfMode.Unknown) { OverrideSpinnerRow( label = R.string.fanMode, @@ -267,3 +281,37 @@ fun OverrideSpinnerRow( ) } } + +@Composable +fun VibrationPreferenceRow(label: String, initialValue: Int, onSave: (newValue: Int) -> Unit, modifier: Modifier = Modifier) { + var showDialog by remember { mutableStateOf(false) } + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + .padding(8.dp), + ) { + Text( + text = label, + modifier = Modifier + .weight(1f) + .padding(end = 16.dp), + ) + OutlinedButton( + onClick = { showDialog = true }, + ) { + Text(text = "$initialValue") + } + } + + if (showDialog) { + VibrationPreferenceDialog( + initialValue = initialValue, + onCancel = { showDialog = false }, + onSave = { newValue -> + onSave(newValue) + showDialog = false + }, + ) + } +} diff --git a/app/src/main/java/de/langerhans/odintools/appsettings/AppOverridesViewModel.kt b/app/src/main/java/de/langerhans/odintools/appsettings/AppOverridesViewModel.kt index 6751dfb..ddc8bd1 100644 --- a/app/src/main/java/de/langerhans/odintools/appsettings/AppOverridesViewModel.kt +++ b/app/src/main/java/de/langerhans/odintools/appsettings/AppOverridesViewModel.kt @@ -12,6 +12,7 @@ import de.langerhans.odintools.models.FanMode.Companion.getDisabledFanModes import de.langerhans.odintools.models.L2R2Style import de.langerhans.odintools.models.NoChange import de.langerhans.odintools.models.PerfMode +import de.langerhans.odintools.tools.DeviceType import de.langerhans.odintools.tools.DeviceUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -37,6 +38,7 @@ class AppOverridesViewModel @Inject constructor( private var initialControllerStyle = NoChange.KEY private var initialL2R2Style = NoChange.KEY private var initialPerfMode = NoChange.KEY + private var initialVibrationStrength: Int? = null private var initialFanMode = NoChange.KEY init { @@ -51,6 +53,7 @@ class AppOverridesViewModel @Inject constructor( initialControllerStyle = app.controllerStyle ?: NoChange.KEY initialL2R2Style = app.l2R2Style ?: NoChange.KEY initialPerfMode = app.perfMode ?: NoChange.KEY + initialVibrationStrength = app.vibrationStrength ?: null initialFanMode = app.fanMode ?: NoChange.KEY appOverrideMapper.mapAppOverride(app) @@ -77,6 +80,7 @@ class AppOverridesViewModel @Inject constructor( l2R2Style = _uiState.value.app?.l2r2Style?.id, perfMode = _uiState.value.app?.perfMode?.id, fanMode = _uiState.value.app?.fanMode?.id, + vibrationStrength = _uiState.value.app?.vibrationStrength, ), ) } @@ -161,17 +165,32 @@ class AppOverridesViewModel @Inject constructor( } } + fun vibrationStrengthSelected(strength: Int) { + _uiState.update { + it.copy( + app = it.app?.copy(vibrationStrength = strength), + hasUnsavedChanges = hasUnsavedChanges(vibrationStrength = strength), + ) + } + } + + fun getDeviceType(): DeviceType { + return deviceUtils.getDeviceType() + } + private fun hasUnsavedChanges( controllerStyle: String? = null, l2R2Style: String? = null, perfMode: String? = null, fanMode: String? = null, + vibrationStrength: Int? = null, ): Boolean { return listOf( (controllerStyle ?: _uiState.value.app?.controllerStyle?.id) != initialControllerStyle, (l2R2Style ?: _uiState.value.app?.l2r2Style?.id) != initialL2R2Style, (perfMode ?: _uiState.value.app?.perfMode?.id) != initialPerfMode, (fanMode ?: _uiState.value.app?.fanMode?.id) != initialFanMode, + (vibrationStrength ?: _uiState.value.app?.vibrationStrength) != initialVibrationStrength, ).any { it } } diff --git a/app/src/main/java/de/langerhans/odintools/data/AppDatabase.kt b/app/src/main/java/de/langerhans/odintools/data/AppDatabase.kt index c0cd89e..dba3b3b 100644 --- a/app/src/main/java/de/langerhans/odintools/data/AppDatabase.kt +++ b/app/src/main/java/de/langerhans/odintools/data/AppDatabase.kt @@ -6,9 +6,10 @@ import androidx.room.RoomDatabase @Database( entities = [AppOverrideEntity::class], - version = 2, + version = 3, autoMigrations = [ AutoMigration(from = 1, to = 2), + AutoMigration(from = 2, to = 3), ], exportSchema = true, ) diff --git a/app/src/main/java/de/langerhans/odintools/data/AppOverrideEntity.kt b/app/src/main/java/de/langerhans/odintools/data/AppOverrideEntity.kt index 057f7f7..06cf45b 100644 --- a/app/src/main/java/de/langerhans/odintools/data/AppOverrideEntity.kt +++ b/app/src/main/java/de/langerhans/odintools/data/AppOverrideEntity.kt @@ -11,4 +11,5 @@ data class AppOverrideEntity( val l2R2Style: String?, val perfMode: String?, val fanMode: String?, + val vibrationStrength: Int?, ) diff --git a/app/src/main/java/de/langerhans/odintools/service/ForegroundAppWatcherService.kt b/app/src/main/java/de/langerhans/odintools/service/ForegroundAppWatcherService.kt index 1225c3d..cd14f68 100644 --- a/app/src/main/java/de/langerhans/odintools/service/ForegroundAppWatcherService.kt +++ b/app/src/main/java/de/langerhans/odintools/service/ForegroundAppWatcherService.kt @@ -52,6 +52,7 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService() private var savedL2R2Style: L2R2Style? = null private var savedPerfMode: PerfMode? = null private var savedFanMode: FanMode? = null + private var savedVibrationStrength: Int? = null private var currentIme = "" private val imeObserver = object : ContentObserver(Handler(Looper.getMainLooper())) { @@ -100,6 +101,7 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService() savedL2R2Style = L2R2Style.getStyle(executor) savedPerfMode = PerfMode.getMode(executor) savedFanMode = FanMode.getMode(executor) + savedVibrationStrength = executor.getVibrationStrength().getOrNull() } ControllerStyle.getById(override.controllerStyle).takeIf { @@ -130,6 +132,12 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService() savedFanMode?.enable(executor) } + if (override.vibrationStrength != null) { + executor.setVibrationStrength(override.vibrationStrength) + } else { + executor.setVibrationStrength(savedVibrationStrength) + } + hasSetOverride = true } @@ -148,6 +156,9 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService() savedFanMode?.enable(executor) savedFanMode = null + executor.setVibrationStrength(savedVibrationStrength) + savedVibrationStrength = null + hasSetOverride = false } diff --git a/app/src/main/java/de/langerhans/odintools/tools/ShellExecutor.kt b/app/src/main/java/de/langerhans/odintools/tools/ShellExecutor.kt index df8d489..863d636 100644 --- a/app/src/main/java/de/langerhans/odintools/tools/ShellExecutor.kt +++ b/app/src/main/java/de/langerhans/odintools/tools/ShellExecutor.kt @@ -146,4 +146,15 @@ class ShellExecutor @Inject constructor() { fun setBooleanValue(file: String, value: Boolean) { setIntValue(file, if (value) 1 else 0) } + + fun getVibrationStrength(): Result { + val defaultValue = 0 + return executeAsRoot("cat /d/haptics/user_vmax_mv") + .mapCatching { it?.toInt() ?: defaultValue } // This throws on RP4 because it has no rumble + } + + fun setVibrationStrength(newValue: Int?) { + if (newValue == null) return + executeAsRoot("echo $newValue > /d/haptics/user_vmax_mv") + } }