From 507242655da0f51bbe5fa48fc5f62013b8c59ad5 Mon Sep 17 00:00:00 2001 From: Hepolise Date: Wed, 10 Sep 2025 20:49:11 +0300 Subject: [PATCH 01/15] feat: add module status detection and UI overhaul - Replace deprecated ModuleIsNotEnabled component with enhanced status system - Add module initialization status tracking via broadcast receiver - Implement hook status detection using SharedPreferences - Add module info card showing active/inactive status and launch count - Improve error handling with specific messages for different failure states - Redesign UI with material cards and better organization - Add segmented buttons for app filter type selection - Implement debounced slider components for smoother preference updates - Fix navigation issues when module is not properly initialized - Migrate settings.gradle to Kotlin DSL - Add GitHub issue link for troubleshooting persistent problems Key changes: - Added HookBroadcastReceiver to handle module status updates - Implemented proper hook detection using lastInitHookTime - Enhanced SettingsScreen with module status information - Improved navigation flow based on module initialization state --- app/src/main/AndroidManifest.xml | 8 + .../module/VolumeControlModule.kt | 53 +-- .../module/VolumeKeyControlModuleHandlers.kt | 65 +++- .../receiver/HookBroadcastReceiver.kt | 30 ++ .../ui/SettingsActivity.kt | 18 +- .../hepolise/volumekeytrackcontrol/ui/Util.kt | 4 +- .../ui/component/AppFilterSetting.kt | 90 +---- .../ui/component/LongPressSetting.kt | 21 +- .../ui/component/ModuleIsNotEnabled.kt | 69 ---- .../ui/component/PrefsSlider.kt | 51 +++ .../ui/component/VibrationEffectSetting.kt | 37 +- .../ui/navigation/AppNavigation.kt | 55 +-- .../ui/screen/SettingsScreen.kt | 360 ++++++++++++++---- .../volumekeytrackcontrol/util/Constants.kt | 8 + .../util/SharedPreferencesUtil.kt | 27 +- app/src/main/res/values/strings.xml | 14 +- settings.gradle => settings.gradle.kts | 6 +- 17 files changed, 565 insertions(+), 351 deletions(-) create mode 100644 app/src/main/java/ru/hepolise/volumekeytrackcontrol/receiver/HookBroadcastReceiver.kt delete mode 100644 app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/ModuleIsNotEnabled.kt create mode 100644 app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/PrefsSlider.kt rename settings.gradle => settings.gradle.kts (83%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cd0262f..353c273 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,6 +26,14 @@ + + + + + + tryHookInitMethod(classLoader, params, logMessage) diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/module/VolumeKeyControlModuleHandlers.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/module/VolumeKeyControlModuleHandlers.kt index 3279c83..f34eb0b 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/module/VolumeKeyControlModuleHandlers.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/module/VolumeKeyControlModuleHandlers.kt @@ -1,6 +1,11 @@ package ru.hepolise.volumekeytrackcontrol.module +import android.content.BroadcastReceiver +import android.content.ComponentName import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.SharedPreferences import android.hardware.display.DisplayManager import android.media.AudioManager import android.media.session.MediaController @@ -14,6 +19,8 @@ import de.robv.android.xposed.XC_MethodHook import de.robv.android.xposed.XC_MethodHook.MethodHookParam import de.robv.android.xposed.XposedHelpers import ru.hepolise.volumekeytrackcontrol.module.util.LogHelper +import ru.hepolise.volumekeytrackcontrol.receiver.HookBroadcastReceiver +import ru.hepolise.volumekeytrackcontrol.util.Constants import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.getAppFilterType import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.getApps @@ -21,6 +28,7 @@ import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.getLongPress import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.isSwapButtons import ru.hepolise.volumekeytrackcontrol.util.VibratorUtil.getVibrator import ru.hepolise.volumekeytrackcontrol.util.VibratorUtil.triggerVibration +import ru.hepolise.volumekeytrackcontrolmodule.BuildConfig object VolumeKeyControlModuleHandlers { @@ -37,12 +45,15 @@ object VolumeKeyControlModuleHandlers { private lateinit var displayManager: DisplayManager private lateinit var vibrator: Vibrator + private var prefs: SharedPreferences? = null + private var mediaControllers: List? = null private fun log(text: String) = LogHelper.log(VolumeControlModule::class.java.simpleName, text) val handleInterceptKeyBeforeQueueing: XC_MethodHook = object : XC_MethodHook() { override fun beforeHookedMethod(param: MethodHookParam) { + initPrefs() with(param) { val event = args[0] as KeyEvent try { @@ -64,10 +75,22 @@ object VolumeKeyControlModuleHandlers { val handleConstructPhoneWindowManager: XC_MethodHook = object : XC_MethodHook() { override fun afterHookedMethod(param: MethodHookParam) { + log("handleConstructPhoneWindowManager: initialized") + val context = param.getContext() MediaEvent.entries.forEach { event -> - val runnable = Runnable { event.handle() } + val runnable = Runnable { event.handle(context) } XposedHelpers.setAdditionalInstanceField(param.thisObject, event.field, runnable) } + + val filter = IntentFilter(Intent.ACTION_USER_UNLOCKED) + context.registerReceiver(object : BroadcastReceiver() { + override fun onReceive(ctx: Context, intent: Intent) { + context.sendBroadcast { + putExtra(Constants.HOOKED, true) + } + ctx.unregisterReceiver(this) + } + }, filter) } } @@ -117,6 +140,10 @@ object VolumeKeyControlModuleHandlers { vibrator = getVibrator() } + private fun initPrefs() { + prefs = SharedPreferencesUtil.prefs() + } + private fun Context.initControllers() { val context = this val classLoader = javaClass.classLoader @@ -143,7 +170,7 @@ object VolumeKeyControlModuleHandlers { } private fun MethodHookParam.doHook(event: KeyEvent) { - val action = Action.entries.find { it.actionCode == event.action }!! + val action = Action.entries.single { it.actionCode == event.action } val keyHelper = KeyHelper(event.keyCode) keyHelper.updateFlags(action) when (action) { @@ -160,7 +187,6 @@ object VolumeKeyControlModuleHandlers { log("Aborting delayed skip") abortSkip() } else { - // only one button pressed if (getMediaController().isMusicActive()) { log("Music is active, creating delayed skip") delay(keyHelper.mediaEvent) @@ -194,10 +220,9 @@ object VolumeKeyControlModuleHandlers { } private fun getMediaController(): MediaController? { - val prefs = SharedPreferencesUtil.prefs() + val filterType = prefs.getAppFilterType() + val apps = prefs.getApps(filterType) return mediaControllers?.find { - val filterType = prefs.getAppFilterType() - val apps = prefs.getApps(filterType) when (filterType) { SharedPreferencesUtil.AppFilterType.DISABLED -> true SharedPreferencesUtil.AppFilterType.WHITE_LIST -> it.packageName in apps @@ -230,11 +255,26 @@ object VolumeKeyControlModuleHandlers { } } + private fun Context.sendBroadcast(block: Intent.() -> Unit) { + val modulePackage = BuildConfig.APPLICATION_ID + + val intent = Intent().apply { + component = ComponentName( + modulePackage, + HookBroadcastReceiver::class.java.name + ) + action = Constants.HOOK_UPDATE + block() + setPackage(modulePackage) + } + sendBroadcast(intent) + } + private fun MethodHookParam.delay(event: MediaEvent) { val handler = getHandler() handler.postDelayed( getRunnable(event.field), - SharedPreferencesUtil.prefs().getLongPressDuration().toLong() + prefs.getLongPressDuration().toLong() ) } @@ -267,16 +307,19 @@ object VolumeKeyControlModuleHandlers { private sealed class MediaEvent(val field: String) { - open fun handle() { + open fun handle(context: Context) { log("Sending ${this::class.simpleName}") isLongPress = true sendMediaButtonEventAndTriggerVibration(this) + context.sendBroadcast { + putExtra(Constants.INCREMENT_LAUNCH_COUNT, true) + } } object PlayPause : MediaEvent("mVolumeBothLongPress") { - override fun handle() { + override fun handle(context: Context) { if (isUpPressed && isDownPressed) { - super.handle() + super.handle(context) } else { log("Not sending ${this::class.simpleName}, down: $isDownPressed, up: $isUpPressed") } @@ -295,7 +338,7 @@ object VolumeKeyControlModuleHandlers { private class KeyHelper(keyCode: Int) { private val origKey = Key.entries.find { it.keyCode == keyCode }!! - private val isSwap = SharedPreferencesUtil.prefs().isSwapButtons() + private val isSwap = prefs.isSwapButtons() private val key = if (isSwap) { when (origKey) { Key.UP -> Key.DOWN diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/receiver/HookBroadcastReceiver.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/receiver/HookBroadcastReceiver.kt new file mode 100644 index 0000000..3ee9e2f --- /dev/null +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/receiver/HookBroadcastReceiver.kt @@ -0,0 +1,30 @@ +package ru.hepolise.volumekeytrackcontrol.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import androidx.core.content.edit +import ru.hepolise.volumekeytrackcontrol.util.Constants +import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.HOOK_PREFS_NAME +import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.LAST_INIT_HOOK_TIME +import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.LAUNCHED_COUNT +import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.getLaunchedCount + +class HookBroadcastReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == Constants.HOOK_UPDATE) { + val prefs = context.getSharedPreferences(HOOK_PREFS_NAME, Context.MODE_PRIVATE) + prefs.edit { + if (intent.getBooleanExtra(Constants.HOOKED, false)) { + putLong(LAST_INIT_HOOK_TIME, System.currentTimeMillis()) + } + if (intent.getBooleanExtra(Constants.INCREMENT_LAUNCH_COUNT, false)) { + val current = prefs.getLaunchedCount() + putInt(LAUNCHED_COUNT, current + 1) + } + } + } + } +} + diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/SettingsActivity.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/SettingsActivity.kt index fb7a371..8d38fcd 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/SettingsActivity.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/SettingsActivity.kt @@ -23,9 +23,10 @@ import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.core.animation.doOnEnd import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import ru.hepolise.volumekeytrackcontrol.ui.component.ModuleIsNotEnabled import ru.hepolise.volumekeytrackcontrol.ui.navigation.AppNavigation +import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.HOOK_PREFS_NAME import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.SETTINGS_PREFS_NAME +import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.isHooked import ru.hepolise.volumekeytrackcontrol.util.VibratorUtil.getVibrator import kotlin.system.exitProcess @@ -44,14 +45,9 @@ class SettingsActivity : ComponentActivity() { setUpSplashScreenAnimation() enableEdgeToEdge() setContent { - val prefs = tryLoadPrefs(this) - + val prefs = tryLoadPrefs() MaterialTheme(colorScheme = dynamicColorScheme(context = this)) { - if (prefs == null) { - ModuleIsNotEnabled() - } else { - AppNavigation(sharedPreferences = prefs, vibrator = getVibrator()) - } + AppNavigation(settingsPrefs = prefs, vibrator = getVibrator()) } } } @@ -63,10 +59,12 @@ class SettingsActivity : ComponentActivity() { } } - private fun tryLoadPrefs(context: Context): SharedPreferences? = try { + private fun Context.tryLoadPrefs(): SharedPreferences? = try { + isHooked = getSharedPreferences(HOOK_PREFS_NAME, MODE_PRIVATE).isHooked() + shouldRemoveFromRecents = !isHooked @SuppressLint("WorldReadableFiles") @Suppress("DEPRECATION") - context.getSharedPreferences(SETTINGS_PREFS_NAME, MODE_WORLD_READABLE) + getSharedPreferences(SETTINGS_PREFS_NAME, MODE_WORLD_READABLE) } catch (_: SecurityException) { shouldRemoveFromRecents = true null diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/Util.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/Util.kt index e6c3221..58f44dd 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/Util.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/Util.kt @@ -20,4 +20,6 @@ fun State.debounce( .collect { debouncedState.value = it } } return debouncedState -} \ No newline at end of file +} + +var isHooked = false \ No newline at end of file diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/AppFilterSetting.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/AppFilterSetting.kt index 7da11ae..072e880 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/AppFilterSetting.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/AppFilterSetting.kt @@ -1,35 +1,18 @@ package ru.hepolise.volumekeytrackcontrol.ui.component import android.content.SharedPreferences -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.expandVertically -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.shrinkVertically -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.Button -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.ExposedDropdownMenuDefaults -import androidx.compose.material3.MenuAnchorType +import androidx.compose.material3.SegmentedButton +import androidx.compose.material3.SegmentedButtonDefaults +import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.Text -import androidx.compose.material3.TextField 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.sp import androidx.core.content.edit import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.APP_FILTER_TYPE import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.AppFilterType -import ru.hepolise.volumekeytrackcontrolmodule.R @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -37,64 +20,25 @@ fun AppFilterSetting( value: AppFilterType, sharedPreferences: SharedPreferences, onValueChange: (AppFilterType) -> Unit, - onNavigateToAppFilter: () -> Unit ) { - Text(text = stringResource(R.string.app_filter), fontSize = 20.sp) - - var expanded by remember { mutableStateOf(false) } - - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = { expanded = !expanded } + SingleChoiceSegmentedButtonRow( + modifier = Modifier.fillMaxWidth() ) { - TextField( - value = stringResource(value.resourceId), - onValueChange = {}, - readOnly = true, - trailingIcon = { - ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) - }, - modifier = Modifier - .menuAnchor(MenuAnchorType.PrimaryNotEditable) - ) - ExposedDropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }) { - AppFilterType.entries.forEach { type -> - DropdownMenuItem( - text = { Text(stringResource(type.resourceId)) }, - onClick = { - onValueChange(type) - sharedPreferences.edit { - putString(APP_FILTER_TYPE, type.key) - } - expanded = false + AppFilterType.entries.forEachIndexed { index, type -> + SegmentedButton( + selected = value == type, + onClick = { + onValueChange(type) + sharedPreferences.edit { + putString(APP_FILTER_TYPE, type.key) } + }, + shape = SegmentedButtonDefaults.itemShape( + index = index, + count = AppFilterType.entries.size ) - } - } - } - - Box { - AnimatedVisibility( - visible = value == AppFilterType.WHITE_LIST || value == AppFilterType.BLACK_LIST, - enter = fadeIn() + expandVertically(expandFrom = Alignment.Top), - exit = fadeOut() + shrinkVertically(shrinkTowards = Alignment.Top), - modifier = Modifier.fillMaxWidth() - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, ) { - Button( - onClick = onNavigateToAppFilter, - ) { - Text( - text = stringResource( - R.string.manage_apps, - stringResource(value.resourceId) - ) - ) - } + Text(text = stringResource(type.resourceId)) } } } diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/LongPressSetting.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/LongPressSetting.kt index a9a8fba..b757d0c 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/LongPressSetting.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/LongPressSetting.kt @@ -3,12 +3,10 @@ package ru.hepolise.volumekeytrackcontrol.ui.component import android.content.SharedPreferences import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.widthIn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.Slider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -18,8 +16,6 @@ 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.compose.ui.unit.sp import androidx.core.content.edit import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.LONG_PRESS_DURATION import ru.hepolise.volumekeytrackcontrolmodule.R @@ -30,19 +26,12 @@ fun LongPressSetting( sharedPreferences: SharedPreferences, onValueChange: (Int) -> Unit ) { - - Text(text = stringResource(R.string.long_press_settings), fontSize = 20.sp) - - Slider( - value = longPressDuration.toFloat(), - onValueChange = { onValueChange(it.toInt()) }, + PrefsSlider( + value = longPressDuration, + onValueChange = { onValueChange(it) }, valueRange = 100f..1000f, - onValueChangeFinished = { - sharedPreferences.edit { - putInt(LONG_PRESS_DURATION, longPressDuration) - } - }, - modifier = Modifier.widthIn(max = 300.dp) + prefKey = LONG_PRESS_DURATION, + sharedPreferences = sharedPreferences ) var showLongPressTimeoutDialog by remember { mutableStateOf(false) } diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/ModuleIsNotEnabled.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/ModuleIsNotEnabled.kt deleted file mode 100644 index b72538f..0000000 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/ModuleIsNotEnabled.kt +++ /dev/null @@ -1,69 +0,0 @@ -package ru.hepolise.volumekeytrackcontrol.ui.component - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.LinkAnnotation -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.TextLinkStyles -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.style.TextDecoration -import androidx.compose.ui.text.withLink -import androidx.compose.ui.unit.dp -import ru.hepolise.volumekeytrackcontrol.util.Constants -import ru.hepolise.volumekeytrackcontrolmodule.R - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun ModuleIsNotEnabled() { - Scaffold( - topBar = { - TopAppBar(title = { Text(stringResource(R.string.app_name)) }) - } - ) { padding -> - Box( - modifier = Modifier - .fillMaxSize() - ) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(padding) - .padding(horizontal = 16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = buildAnnotatedString { - append(stringResource(id = R.string.module_is_not_enabled)) - append(" ") - withLink( - LinkAnnotation.Url( - url = Constants.LSPOSED_GITHUB_URL, - styles = TextLinkStyles( - style = SpanStyle( - color = MaterialTheme.colorScheme.primary, - textDecoration = TextDecoration.Underline - ) - ) - ) - ) { - append(stringResource(id = R.string.recommended_lsposed_version)) - } - } - ) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/PrefsSlider.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/PrefsSlider.kt new file mode 100644 index 0000000..a3a2ae5 --- /dev/null +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/PrefsSlider.kt @@ -0,0 +1,51 @@ +package ru.hepolise.volumekeytrackcontrol.ui.component + +import android.content.SharedPreferences +import androidx.compose.foundation.layout.widthIn +import androidx.compose.material3.Slider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.core.content.edit +import ru.hepolise.volumekeytrackcontrol.ui.debounce + +@Composable +fun PrefsSlider( + value: Int, + valueRange: ClosedFloatingPointRange, + prefKey: String, + sharedPreferences: SharedPreferences, + modifier: Modifier = Modifier, + onValueChange: (Int) -> Unit, + debounceMillis: Long = 50, +) { + val scope = rememberCoroutineScope() + var sliderValue by remember { mutableFloatStateOf(value.toFloat()) } + + val debouncedValue by remember { + derivedStateOf { sliderValue.toInt() } + }.debounce(debounceMillis, scope) + + LaunchedEffect(debouncedValue) { + sharedPreferences.edit { + putInt(prefKey, debouncedValue) + } + } + + Slider( + value = value.toFloat(), + onValueChange = { + sliderValue = it + onValueChange(it.toInt()) + }, + valueRange = valueRange, + modifier = modifier.widthIn(max = 300.dp) + ) +} diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/VibrationEffectSetting.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/VibrationEffectSetting.kt index a9adc33..bfc931e 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/VibrationEffectSetting.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/component/VibrationEffectSetting.kt @@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.widthIn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.Button @@ -24,7 +23,6 @@ import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MenuAnchorType -import androidx.compose.material3.Slider import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable @@ -35,8 +33,6 @@ 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.compose.ui.unit.sp import androidx.core.content.edit import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.EFFECT import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.VIBRATION_AMPLITUDE @@ -80,7 +76,6 @@ fun VibrationEffectSetting( onValueChange: (VibrationSettingData) -> Unit ) { val (vibrationType, vibrationLength, vibrationAmplitude) = value - Text(text = stringResource(R.string.vibration_settings), fontSize = 20.sp) var effectExpanded by remember { mutableStateOf(false) } ExposedDropdownMenuBox( @@ -131,18 +126,12 @@ fun VibrationEffectSetting( exit = fadeOut() + shrinkVertically(shrinkTowards = Alignment.Top) ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { - Slider( - value = vibrationLength.toFloat(), - onValueChange = { - onValueChange(value.copy(vibrationLength = it.toInt())) - }, + PrefsSlider( + value = vibrationLength, + onValueChange = { onValueChange(value.copy(vibrationLength = it)) }, valueRange = 10f..500f, - onValueChangeFinished = { - sharedPreferences.edit { - putInt(VIBRATION_LENGTH, vibrationLength) - } - }, - modifier = Modifier.widthIn(max = 300.dp) + prefKey = VIBRATION_LENGTH, + sharedPreferences = sharedPreferences ) var showManualVibrationLengthDialog by remember { mutableStateOf(false) } @@ -176,18 +165,12 @@ fun VibrationEffectSetting( ) } - Slider( - value = vibrationAmplitude.toFloat(), - onValueChange = { - onValueChange(value.copy(vibrationAmplitude = it.toInt())) - }, + PrefsSlider( + value = vibrationAmplitude, + onValueChange = { onValueChange(value.copy(vibrationAmplitude = it)) }, valueRange = 1f..255f, - onValueChangeFinished = { - sharedPreferences.edit { - putInt(VIBRATION_AMPLITUDE, vibrationAmplitude) - } - }, - modifier = Modifier.widthIn(max = 300.dp) + prefKey = VIBRATION_AMPLITUDE, + sharedPreferences = sharedPreferences ) var showVibrationAmplitudeDialog by remember { mutableStateOf(false) } diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/navigation/AppNavigation.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/navigation/AppNavigation.kt index 19def5a..335a86c 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/navigation/AppNavigation.kt @@ -17,7 +17,7 @@ import ru.hepolise.volumekeytrackcontrol.ui.screen.SettingsScreen import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil @Composable -fun AppNavigation(sharedPreferences: SharedPreferences, vibrator: Vibrator) { +fun AppNavigation(settingsPrefs: SharedPreferences?, vibrator: Vibrator) { val navController = rememberNavController() NavHost( @@ -28,40 +28,41 @@ fun AppNavigation(sharedPreferences: SharedPreferences, vibrator: Vibrator) { route = "main", ) { SettingsScreen( - sharedPreferences = sharedPreferences, + settingsPrefs = settingsPrefs, navController = navController, vibrator = vibrator ) } - composable( - route = "appFilter/{filterType}", - enterTransition = { - slideInHorizontally( - initialOffsetX = { fullWidth -> fullWidth }, - animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing) - ) + fadeIn( - animationSpec = tween(durationMillis = 300) + settingsPrefs?.also { sharedPreferences -> + composable( + route = "appFilter/{filterType}", + enterTransition = { + slideInHorizontally( + initialOffsetX = { fullWidth -> fullWidth }, + animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing) + ) + fadeIn( + animationSpec = tween(durationMillis = 300) + ) + }, + exitTransition = { + slideOutHorizontally( + targetOffsetX = { fullWidth -> fullWidth }, + animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing) + ) + fadeOut( + animationSpec = tween(durationMillis = 300) + ) + } + ) { backStackEntry -> + val filterType = SharedPreferencesUtil.AppFilterType.fromKey( + backStackEntry.arguments?.getString("filterType") ) - }, - exitTransition = { - slideOutHorizontally( - targetOffsetX = { fullWidth -> fullWidth }, - animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing) - ) + fadeOut( - animationSpec = tween(durationMillis = 300) + AppFilterScreen( + filterType = filterType, + sharedPreferences = sharedPreferences, + navController = navController ) } - ) { backStackEntry -> - val filterType = SharedPreferencesUtil.AppFilterType.fromKey( - backStackEntry.arguments?.getString("filterType") - ?: SharedPreferencesUtil.AppFilterType.DISABLED.key - ) - AppFilterScreen( - filterType = filterType, - sharedPreferences = sharedPreferences, - navController = navController - ) } } } \ No newline at end of file diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/screen/SettingsScreen.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/screen/SettingsScreen.kt index 61a7979..1f91714 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/screen/SettingsScreen.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/ui/screen/SettingsScreen.kt @@ -3,28 +3,50 @@ package ru.hepolise.volumekeytrackcontrol.ui.screen import android.content.Context import android.content.Intent import android.content.SharedPreferences +import android.content.res.Configuration import android.os.Vibrator import android.widget.Toast +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Done +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.Notifications +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.filled.Star +import androidx.compose.material.icons.filled.Warning import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton -import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf @@ -33,11 +55,20 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString.Builder +import androidx.compose.ui.text.LinkAnnotation +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLinkStyles +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.withLink import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.core.content.edit import androidx.core.net.toUri import androidx.navigation.NavController @@ -46,16 +77,19 @@ import ru.hepolise.volumekeytrackcontrol.ui.component.LongPressSetting import ru.hepolise.volumekeytrackcontrol.ui.component.SwapButtonsSetting import ru.hepolise.volumekeytrackcontrol.ui.component.VibrationEffectSetting import ru.hepolise.volumekeytrackcontrol.ui.component.VibrationSettingData +import ru.hepolise.volumekeytrackcontrol.ui.isHooked import ru.hepolise.volumekeytrackcontrol.util.Constants import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.APP_FILTER_TYPE_DEFAULT_VALUE import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.EFFECT_DEFAULT_VALUE +import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.HOOK_PREFS_NAME import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.IS_SWAP_BUTTONS_DEFAULT_VALUE import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.LONG_PRESS_DURATION_DEFAULT_VALUE import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.SETTINGS_PREFS_NAME import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.VIBRATION_AMPLITUDE_DEFAULT_VALUE import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.VIBRATION_LENGTH_DEFAULT_VALUE import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.getAppFilterType +import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.getLaunchedCount import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.getLongPressDuration import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.getVibrationAmplitude import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.getVibrationLength @@ -68,94 +102,144 @@ import ru.hepolise.volumekeytrackcontrolmodule.R @Composable fun SettingsScreen( navController: NavController?, - sharedPreferences: SharedPreferences, + settingsPrefs: SharedPreferences?, vibrator: Vibrator? ) { val context = LocalContext.current - var longPressDuration by remember { mutableIntStateOf(sharedPreferences.getLongPressDuration()) } - var vibrationType by remember { mutableStateOf(sharedPreferences.getVibrationType()) } - var vibrationLength by remember { mutableIntStateOf(sharedPreferences.getVibrationLength()) } - var vibrationAmplitude by remember { mutableIntStateOf(sharedPreferences.getVibrationAmplitude()) } - var isSwapButtons by remember { mutableStateOf(sharedPreferences.isSwapButtons()) } - var appFilterType by remember { mutableStateOf(sharedPreferences.getAppFilterType()) } + val hookPrefs = context.getSharedPreferences(HOOK_PREFS_NAME, Context.MODE_PRIVATE) + + var longPressDuration by remember { mutableIntStateOf(settingsPrefs.getLongPressDuration()) } + var vibrationType by remember { mutableStateOf(settingsPrefs.getVibrationType()) } + var vibrationLength by remember { mutableIntStateOf(settingsPrefs.getVibrationLength()) } + var vibrationAmplitude by remember { mutableIntStateOf(settingsPrefs.getVibrationAmplitude()) } + var isSwapButtons by remember { mutableStateOf(settingsPrefs.isSwapButtons()) } + var appFilterType by remember { mutableStateOf(settingsPrefs.getAppFilterType()) } + var showResetSettingsDialog by remember { mutableStateOf(false) } + + val isHooked = isHooked.takeIf { settingsPrefs != null } ?: false + var launchedCount by remember { mutableIntStateOf(hookPrefs.getLaunchedCount()) } + + val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> + launchedCount = hookPrefs.getLaunchedCount() + } + hookPrefs.registerOnSharedPreferenceChangeListener(listener) + + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() Scaffold( topBar = { - TopAppBar(title = { Text(stringResource(R.string.app_name)) }) - } + LargeTopAppBar( + title = { Text(stringResource(R.string.app_name)) }, + actions = { + if (settingsPrefs != null) { + IconButton(onClick = { showResetSettingsDialog = true }) { + Icon( + Icons.Default.Refresh, + contentDescription = stringResource(R.string.settings_reset) + ) + } + } + IconButton(onClick = { + val intent = Intent(Intent.ACTION_VIEW) + intent.data = Constants.GITHUB_URL.toUri() + context.startActivity(intent) + }) { + Icon( + Icons.Default.Info, + contentDescription = stringResource(R.string.about) + ) + } + }, + scrollBehavior = scrollBehavior + ) + }, ) { padding -> - Box( + Column( modifier = Modifier .fillMaxSize() .padding(padding) + .nestedScroll(scrollBehavior.nestedScrollConnection) + .verticalScroll(rememberScrollState()) + .padding(12.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(12.dp) - .verticalScroll(rememberScrollState()) - .padding(bottom = 48.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), - horizontalAlignment = Alignment.CenterHorizontally + SettingsCard( + icon = if (isHooked) Icons.Default.Done else Icons.Default.Warning, + title = stringResource(R.string.module_info), ) { - - LongPressSetting(longPressDuration, sharedPreferences) { - longPressDuration = it - } - - HorizontalDivider(modifier = Modifier.widthIn(max = 300.dp)) - - VibrationEffectSetting( - value = VibrationSettingData( - vibrationType, vibrationLength, vibrationAmplitude - ), - vibrator = vibrator, - sharedPreferences = sharedPreferences + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween ) { - vibrationType = it.vibrationType - vibrationLength = it.vibrationLength - vibrationAmplitude = it.vibrationAmplitude + Text( + stringResource(R.string.module_status), + style = MaterialTheme.typography.bodyMedium + ) + Text( + text = stringResource( + if (isHooked) R.string.module_status_active + else R.string.module_status_inactive + ), + color = if (isHooked) MaterialTheme.colorScheme.primary + else MaterialTheme.colorScheme.error, + fontWeight = FontWeight.Bold + ) } + when { + isHooked -> ModuleIsEnabled(launchedCount) + settingsPrefs == null -> ModuleIsNotEnabled() + else -> ModuleInitError() + } + } - HorizontalDivider(modifier = Modifier.widthIn(max = 300.dp)) - - AppFilterSetting( - value = appFilterType, - sharedPreferences = sharedPreferences, - onValueChange = { newAppFilterType -> appFilterType = newAppFilterType }, + if (settingsPrefs != null && isHooked) { + SettingsCard( + icon = Icons.Default.Settings, + title = stringResource(R.string.long_press_settings) ) { - navController?.navigate("appFilter/${appFilterType.key}") + LongPressSetting(longPressDuration, settingsPrefs) { + longPressDuration = it + } + SwapButtonsSetting( + isSwapButtons = isSwapButtons, + sharedPreferences = settingsPrefs + ) { + isSwapButtons = it + } } - HorizontalDivider(modifier = Modifier.widthIn(max = 300.dp)) - - Text(text = stringResource(R.string.other_settings), fontSize = 20.sp) - - SwapButtonsSetting( - isSwapButtons = isSwapButtons, - sharedPreferences = sharedPreferences + SettingsCard( + icon = Icons.Default.Notifications, + title = stringResource(R.string.vibration_settings) ) { - isSwapButtons = it + VibrationEffectSetting( + value = VibrationSettingData( + vibrationType, + vibrationLength, + vibrationAmplitude + ), + vibrator = vibrator, + sharedPreferences = settingsPrefs + ) { + vibrationType = it.vibrationType + vibrationLength = it.vibrationLength + vibrationAmplitude = it.vibrationAmplitude + } } - } - - Row( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.BottomCenter) - .padding(end = 16.dp, bottom = 8.dp) - ) { - Spacer(modifier = Modifier.weight(1f)) - - var showResetSettingsDialog by remember { mutableStateOf(false) } - Button(onClick = { - showResetSettingsDialog = true - }) { - Text(stringResource(R.string.settings_reset)) + SettingsCard( + icon = Icons.Default.Star, + title = stringResource(R.string.app_filter), + showAction = appFilterType != SharedPreferencesUtil.AppFilterType.DISABLED, + onActionClick = { navController?.navigate("appFilter/${appFilterType.key}") } + ) { + AppFilterSetting( + value = appFilterType, + sharedPreferences = settingsPrefs, + onValueChange = { appFilterType = it }, + ) } - if (showResetSettingsDialog) { AlertDialog( onDismissRequest = { showResetSettingsDialog = false }, @@ -164,7 +248,7 @@ fun SettingsScreen( confirmButton = { Button(onClick = { showResetSettingsDialog = false - sharedPreferences.edit { clear() } + settingsPrefs.edit { clear() } vibrationType = VibrationType.fromKey(EFFECT_DEFAULT_VALUE) vibrationLength = VIBRATION_LENGTH_DEFAULT_VALUE vibrationAmplitude = VIBRATION_AMPLITUDE_DEFAULT_VALUE @@ -189,29 +273,141 @@ fun SettingsScreen( } ) } + } + } + } +} - Spacer(modifier = Modifier.width(8.dp)) - - Button(onClick = { - val intent = Intent(Intent.ACTION_VIEW) - intent.data = Constants.GITHUB_URL.toUri() - context.startActivity(intent) - }) { - Text(stringResource(R.string.about)) +@Composable +fun SettingsCard( + icon: ImageVector, + title: String, + showAction: Boolean = false, + actionIcon: ImageVector? = null, + onActionClick: (() -> Unit)? = null, + content: @Composable ColumnScope.() -> Unit +) { + Card( + shape = RoundedCornerShape(24.dp), + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surface, + contentColor = MaterialTheme.colorScheme.onSurface + ), + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)), + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Icon(icon, contentDescription = null) + Spacer(Modifier.width(12.dp)) + Text(title, style = MaterialTheme.typography.titleMedium) + Spacer(Modifier.weight(1f)) + AnimatedVisibility( + visible = showAction, + enter = fadeIn() + slideInHorizontally(initialOffsetX = { it / 2 }), + exit = fadeOut() + slideOutHorizontally(targetOffsetX = { it / 2 }) + ) { + Icon( + imageVector = actionIcon ?: Icons.Default.Edit, + contentDescription = null, + modifier = Modifier.clickable(onClick = { onActionClick?.invoke() }) + ) } + } + content() + } + } +} - Spacer(modifier = Modifier.width(8.dp)) +@Composable +fun ModuleIsEnabled(launchedCount: Int) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + stringResource(R.string.module_launch_count), + style = MaterialTheme.typography.bodyMedium + ) + Text( + text = launchedCount.toString(), + fontWeight = FontWeight.Medium + ) + } +} + +@Composable +fun ModuleIsNotEnabled() { + Text( + text = buildAnnotatedString { + append(stringResource(id = R.string.module_is_not_enabled)) + RecommendedLsposedVersion() + } + ) +} + +@Composable +fun ModuleInitError() { + Text( + text = buildAnnotatedString { + append(stringResource(id = R.string.module_init_error)) + append("\n") + append("\n") + withLink( + LinkAnnotation.Url( + url = Constants.GITHUB_NEW_ISSUE_URL, + styles = TextLinkStyles( + style = SpanStyle( + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline + ) + ) + ) + ) { + append(stringResource(id = R.string.open_an_issue)) } + append(" ") + append(stringResource(id = R.string.if_the_problem_persists)) + RecommendedLsposedVersion() } + ) +} + +@Composable +private fun Builder.RecommendedLsposedVersion() { + append("\n") + append("\n") + append(stringResource(id = R.string.recommended_lsposed_version)) + append(" ") + withLink( + LinkAnnotation.Url( + url = Constants.LSPOSED_GITHUB_URL, + styles = TextLinkStyles( + style = SpanStyle( + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline + ) + ) + ) + ) { + append(stringResource(id = R.string.recommended_lsposed_version_url)) } } -@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable fun PreviewSettingsScreen() { SettingsScreen( navController = null, - sharedPreferences = LocalContext.current.getSharedPreferences( + settingsPrefs = LocalContext.current.getSharedPreferences( SETTINGS_PREFS_NAME, Context.MODE_PRIVATE ), diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/util/Constants.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/util/Constants.kt index 520e008..7079956 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/util/Constants.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/util/Constants.kt @@ -1,7 +1,15 @@ package ru.hepolise.volumekeytrackcontrol.util +import ru.hepolise.volumekeytrackcontrolmodule.BuildConfig + object Constants { const val GITHUB_URL = "https://github.com/Hepolise/VolumeKeyTrackControlModule" + const val GITHUB_NEW_ISSUE_URL = + "https://github.com/Hepolise/VolumeKeyTrackControlModule/issues/new/choose" const val LSPOSED_GITHUB_URL = "https://github.com/JingMatrix/LSPosed/actions/workflows/core.yml" + + const val HOOK_UPDATE = BuildConfig.APPLICATION_ID + ".HOOK_UPDATE" + const val HOOKED = "hooked" + const val INCREMENT_LAUNCH_COUNT = "incrementLaunchCount" } \ No newline at end of file diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/util/SharedPreferencesUtil.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/util/SharedPreferencesUtil.kt index 112ae3b..5596245 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrol/util/SharedPreferencesUtil.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrol/util/SharedPreferencesUtil.kt @@ -2,6 +2,7 @@ package ru.hepolise.volumekeytrackcontrol.util import android.content.SharedPreferences import android.os.Build +import android.os.SystemClock import android.view.ViewConfiguration import de.robv.android.xposed.XSharedPreferences import ru.hepolise.volumekeytrackcontrolmodule.BuildConfig @@ -9,6 +10,7 @@ import ru.hepolise.volumekeytrackcontrolmodule.R object SharedPreferencesUtil { const val SETTINGS_PREFS_NAME = "settings_prefs" + const val HOOK_PREFS_NAME = "hook_prefs" const val EFFECT = "selectedEffect" const val VIBRATION_LENGTH = "vibrationLength" @@ -19,6 +21,9 @@ object SharedPreferencesUtil { const val WHITE_LIST_APPS = "whiteListApps" const val BLACK_LIST_APPS = "blackListApps" + const val LAST_INIT_HOOK_TIME = "lastInitHookTime" + const val LAUNCHED_COUNT = "launchedCount" + val EFFECT_DEFAULT_VALUE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) VibrationType.Click.key else VibrationType.Manual.key const val VIBRATION_LENGTH_DEFAULT_VALUE = 50 @@ -27,6 +32,8 @@ object SharedPreferencesUtil { const val IS_SWAP_BUTTONS_DEFAULT_VALUE = false val APP_FILTER_TYPE_DEFAULT_VALUE = AppFilterType.DISABLED.key + const val LAUNCHED_COUNT_DEFAULT_VALUE = 0 + fun SharedPreferences?.getVibrationType(): VibrationType { val defaultValue = EFFECT_DEFAULT_VALUE return VibrationType.fromKey(this?.getString(EFFECT, defaultValue) ?: defaultValue) @@ -68,11 +75,21 @@ object SharedPreferencesUtil { } } + fun SharedPreferences.isHooked(): Boolean = + getLong( + LAST_INIT_HOOK_TIME, + 0L + ) >= (System.currentTimeMillis() - SystemClock.elapsedRealtime()) - fun prefs(): SharedPreferences? { - val pref = XSharedPreferences(BuildConfig.APPLICATION_ID, SETTINGS_PREFS_NAME) - return if (pref.file.canRead()) pref else null - } + fun SharedPreferences.getLaunchedCount(): Int = + this.getInt(LAUNCHED_COUNT, LAUNCHED_COUNT_DEFAULT_VALUE) + + private var _prefs: SharedPreferences? = null + + fun prefs(): SharedPreferences? = + XSharedPreferences(BuildConfig.APPLICATION_ID, SETTINGS_PREFS_NAME) + .takeIf { it.file.canRead() } + ?.also { _prefs = it } ?: _prefs enum class AppFilterType( val value: Int, @@ -84,7 +101,7 @@ object SharedPreferencesUtil { BLACK_LIST(2, "blacklist", R.string.app_filter_black_list); companion object { - fun fromKey(key: String) = entries.find { it.key == key } ?: DISABLED + fun fromKey(key: String?) = entries.find { it.key == key } ?: DISABLED } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e970697..2c82e94 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,12 @@ Volume Key Track Control + Module Info + Status: + Active + Inactive + Launch Count: + Vibration Settings Long Press Settings Other Settings @@ -50,8 +56,12 @@ Settings are reset to default About - Module is not enabled in LSPosed.\nIf the module is enabled and you still see this screen, try to restart the app.\nRecommended LSPosed version: - JingMatrix + Module is not enabled in LSPosed.\nIf the module is enabled and you still see this screen, try to restart the app. + Module initialization error.\nIf the module is enabled and you still see this screen, check if you have selected recommended apps and rebooted your device. + Open an issue + if the problem persists. + Recommended LSPosed version: + JingMatrix Back diff --git a/settings.gradle b/settings.gradle.kts similarity index 83% rename from settings.gradle rename to settings.gradle.kts index df7c36c..8cd08e7 100644 --- a/settings.gradle +++ b/settings.gradle.kts @@ -5,15 +5,17 @@ pluginManagement { mavenCentral() } } + dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { maven { - url "https://api.xposed.info/" + url = uri("https://api.xposed.info/") } google() mavenCentral() } } + rootProject.name = "VolumeKeyTrackControlModule" -include ':app' +include(":app") From 98ebf2387223afefb265714a0d845bac58cdc7f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 02:36:10 +0000 Subject: [PATCH 02/15] Build(deps): Bump com.android.application from 8.12.0 to 8.12.1 Bumps com.android.application from 8.12.0 to 8.12.1. --- updated-dependencies: - dependency-name: com.android.application dependency-version: 8.12.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index a61b71a..412a84d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.android.application") version "8.12.0" apply false + id("com.android.application") version "8.12.1" apply false id("com.android.library") version "8.12.0" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.2.10" apply false From 734aa0e1db61f759f722d39de407d20d7edb2232 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 05:07:58 +0000 Subject: [PATCH 03/15] Build(deps): Bump com.android.library from 8.12.0 to 8.12.1 Bumps com.android.library from 8.12.0 to 8.12.1. --- updated-dependencies: - dependency-name: com.android.library dependency-version: 8.12.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 412a84d..0c65417 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("com.android.application") version "8.12.1" apply false - id("com.android.library") version "8.12.0" apply false + id("com.android.library") version "8.12.1" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.2.10" apply false } From b36b4289bd325c9f013977df4ea22c00a908f5ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 20:01:10 +0000 Subject: [PATCH 04/15] Build(deps): Bump androidx.core:core-ktx from 1.16.0 to 1.17.0 Bumps androidx.core:core-ktx from 1.16.0 to 1.17.0. --- updated-dependencies: - dependency-name: androidx.core:core-ktx dependency-version: 1.17.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index da86b28..8abdfe6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -43,7 +43,7 @@ android { dependencies { implementation(platform("org.jetbrains.kotlin:kotlin-bom:2.2.10")) - implementation("androidx.core:core-ktx:1.16.0") + implementation("androidx.core:core-ktx:1.17.0") // Compose BOM (Bill of Materials) implementation(platform("androidx.compose:compose-bom:2025.08.00")) From e6a51d2ca04cddf0cdf93f6fe0e88e8f3528bc3d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 17:41:55 +0000 Subject: [PATCH 05/15] Build(deps): Bump actions/setup-java from 4 to 5 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c5e27e0..81091ee 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,7 +35,7 @@ jobs: - uses: actions/checkout@v5 - name: Set up JDK 21 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: '21' From 44ba7a3439297929ac94d1a2fc7fc00073d2a52f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 23:04:57 +0000 Subject: [PATCH 06/15] Build(deps): Bump androidx.compose:compose-bom Bumps androidx.compose:compose-bom from 2025.08.00 to 2025.08.01. --- updated-dependencies: - dependency-name: androidx.compose:compose-bom dependency-version: 2025.08.01 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8abdfe6..a419fe5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -46,7 +46,7 @@ dependencies { implementation("androidx.core:core-ktx:1.17.0") // Compose BOM (Bill of Materials) - implementation(platform("androidx.compose:compose-bom:2025.08.00")) + implementation(platform("androidx.compose:compose-bom:2025.08.01")) // Compose dependencies implementation("androidx.activity:activity-compose:1.10.1") From 8c40d8e07585ee771d764502d2fa1f6a40286abc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 17:35:21 +0000 Subject: [PATCH 07/15] Build(deps): Bump com.android.application from 8.12.1 to 8.12.2 Bumps com.android.application from 8.12.1 to 8.12.2. --- updated-dependencies: - dependency-name: com.android.application dependency-version: 8.12.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0c65417..dc5d430 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.android.application") version "8.12.1" apply false + id("com.android.application") version "8.12.2" apply false id("com.android.library") version "8.12.1" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.2.10" apply false From 0ae5949d24cf21eac8f547cf9d8d78047eccff6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 19:14:00 +0000 Subject: [PATCH 08/15] Build(deps): Bump com.android.library from 8.12.1 to 8.12.2 Bumps com.android.library from 8.12.1 to 8.12.2. --- updated-dependencies: - dependency-name: com.android.library dependency-version: 8.12.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index dc5d430..789dee5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("com.android.application") version "8.12.2" apply false - id("com.android.library") version "8.12.1" apply false + id("com.android.library") version "8.12.2" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.2.10" apply false } From f20fff73f0dd3691f474fe025a809d7cdb78bea1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:28:02 +0300 Subject: [PATCH 09/15] Build(deps): Bump com.android.application from 8.12.2 to 8.13.0 (#162) Bumps com.android.application from 8.12.2 to 8.13.0. --- updated-dependencies: - dependency-name: com.android.application dependency-version: 8.13.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 789dee5..6b50716 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.android.application") version "8.12.2" apply false + id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.12.2" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.2.10" apply false From 59842faebfaee9eaba746a1e894c746558937d4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 19:04:44 +0300 Subject: [PATCH 10/15] Build(deps): Bump com.android.library from 8.12.2 to 8.13.0 (#163) Bumps com.android.library from 8.12.2 to 8.13.0. --- updated-dependencies: - dependency-name: com.android.library dependency-version: 8.13.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 6b50716..403a83d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("com.android.application") version "8.13.0" apply false - id("com.android.library") version "8.12.2" apply false + id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.2.10" apply false } From 69de80377b8ae34a2e07ee448275a41d60f3a039 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 20:19:21 +0300 Subject: [PATCH 11/15] Build(deps): Bump org.jetbrains.kotlin.plugin.compose (#164) Bumps [org.jetbrains.kotlin.plugin.compose](https://github.com/JetBrains/kotlin) from 2.2.10 to 2.2.20. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v2.2.10...v2.2.20) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin.plugin.compose dependency-version: 2.2.20 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 403a83d..c1fb7cb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false - id("org.jetbrains.kotlin.plugin.compose") version "2.2.10" apply false + id("org.jetbrains.kotlin.plugin.compose") version "2.2.20" apply false } val versionName = "1.15.7" From 771b37163fe0bf7e25db5b3bc706cb328cb60cf7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 20:19:36 +0300 Subject: [PATCH 12/15] Build(deps): Bump androidx.compose:compose-bom (#165) Bumps androidx.compose:compose-bom from 2025.08.01 to 2025.09.00. --- updated-dependencies: - dependency-name: androidx.compose:compose-bom dependency-version: 2025.09.00 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a419fe5..c267aae 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -46,7 +46,7 @@ dependencies { implementation("androidx.core:core-ktx:1.17.0") // Compose BOM (Bill of Materials) - implementation(platform("androidx.compose:compose-bom:2025.08.01")) + implementation(platform("androidx.compose:compose-bom:2025.09.00")) // Compose dependencies implementation("androidx.activity:activity-compose:1.10.1") From 05c3644e5ab1786168b7c8ef7a58fd703388854f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 20:19:50 +0300 Subject: [PATCH 13/15] Build(deps): Bump org.jetbrains.kotlin:kotlin-bom from 2.2.10 to 2.2.20 (#166) Bumps [org.jetbrains.kotlin:kotlin-bom](https://github.com/JetBrains/kotlin) from 2.2.10 to 2.2.20. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v2.2.10...v2.2.20) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin:kotlin-bom dependency-version: 2.2.20 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c267aae..894ddaf 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -42,7 +42,7 @@ android { } dependencies { - implementation(platform("org.jetbrains.kotlin:kotlin-bom:2.2.10")) + implementation(platform("org.jetbrains.kotlin:kotlin-bom:2.2.20")) implementation("androidx.core:core-ktx:1.17.0") // Compose BOM (Bill of Materials) From 013b19e9043f66e641c511d09c66176d2a1c45e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 20:25:42 +0300 Subject: [PATCH 14/15] Build(deps): Bump androidx.activity:activity-compose (#167) Bumps androidx.activity:activity-compose from 1.10.1 to 1.11.0. --- updated-dependencies: - dependency-name: androidx.activity:activity-compose dependency-version: 1.11.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 894ddaf..ce42e9a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -49,7 +49,7 @@ dependencies { implementation(platform("androidx.compose:compose-bom:2025.09.00")) // Compose dependencies - implementation("androidx.activity:activity-compose:1.10.1") + implementation("androidx.activity:activity-compose:1.11.0") implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.material3:material3:1.3.2") From 343c73723c6e6d3bc337ccc4b7d1716e12c5f591 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 20:25:53 +0300 Subject: [PATCH 15/15] Build(deps): Bump org.jetbrains.kotlin.android from 2.2.10 to 2.2.20 (#168) Bumps [org.jetbrains.kotlin.android](https://github.com/JetBrains/kotlin) from 2.2.10 to 2.2.20. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v2.2.10...v2.2.20) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin.android dependency-version: 2.2.20 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index c1fb7cb..dd98523 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.10" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.2.20" apply false }