From f334fff06be600bb835fb240d5b60be47f5a3bfe Mon Sep 17 00:00:00 2001 From: sameerasw Date: Mon, 2 Mar 2026 19:46:59 +0530 Subject: [PATCH 1/5] feat: Generalize root alternativves for shizuku --- app/build.gradle.kts | 20 +++++++++---------- .../tiles/AlwaysOnDisplayTileService.kt | 10 ++++++---- .../services/tiles/ChargeQuickTileService.kt | 5 ++--- .../services/tiles/MonoAudioTileService.kt | 8 +++----- .../services/tiles/NfcTileService.kt | 11 ++++++---- .../configs/QuickSettingsTilesSettingsUI.kt | 15 ++++++++++---- 6 files changed, 39 insertions(+), 30 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 088f73e6d..5909b3ffe 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -31,16 +31,16 @@ android { // optimized dev build -// debug { -// isMinifyEnabled = true -// isShrinkResources = true -// isDebuggable = false -// -// proguardFiles( -// getDefaultProguardFile("proguard-android-optimize.txt"), -// "proguard-rules.pro" -// ) -// } + debug { + isMinifyEnabled = true + isShrinkResources = true + isDebuggable = false + + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } // end diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/AlwaysOnDisplayTileService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/AlwaysOnDisplayTileService.kt index 84df15336..47d00e186 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/AlwaysOnDisplayTileService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/AlwaysOnDisplayTileService.kt @@ -9,6 +9,7 @@ import android.service.quicksettings.Tile import androidx.annotation.RequiresApi import com.sameerasw.essentials.R import com.sameerasw.essentials.data.repository.SettingsRepository +import com.sameerasw.essentials.utils.ShellUtils @RequiresApi(Build.VERSION_CODES.N) class AlwaysOnDisplayTileService : BaseTileService() { @@ -24,7 +25,8 @@ class AlwaysOnDisplayTileService : BaseTileService() { } override fun hasFeaturePermission(): Boolean { - return checkCallingOrSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) == PackageManager.PERMISSION_GRANTED + return com.sameerasw.essentials.utils.PermissionUtils.canWriteSecureSettings(this) || + (ShellUtils.isAvailable(this) && ShellUtils.hasPermission(this)) } override fun getTileIcon(): Icon? { @@ -60,11 +62,11 @@ class AlwaysOnDisplayTileService : BaseTileService() { } private fun isAodEnabled(): Boolean { - return Settings.Secure.getInt(contentResolver, "doze_always_on", 0) == 1 + return getSecureInt("doze_always_on", 0) == 1 } - + private fun setAodEnabled(enabled: Boolean) { - Settings.Secure.putInt(contentResolver, "doze_always_on", if (enabled) 1 else 0) + putSecureInt("doze_always_on", if (enabled) 1 else 0) } private fun isGlanceEnabled(): Boolean { diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/ChargeQuickTileService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/ChargeQuickTileService.kt index f89889309..3a9bd9a49 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/ChargeQuickTileService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/ChargeQuickTileService.kt @@ -9,6 +9,7 @@ import androidx.annotation.RequiresApi import com.sameerasw.essentials.FeatureSettingsActivity import com.sameerasw.essentials.R import com.sameerasw.essentials.utils.PermissionUtils +import com.sameerasw.essentials.utils.ShellUtils @RequiresApi(Build.VERSION_CODES.N) class ChargeQuickTileService : BaseTileService() { @@ -77,9 +78,7 @@ class ChargeQuickTileService : BaseTileService() { } override fun hasFeaturePermission(): Boolean { - return PermissionUtils.canWriteSecureSettings(this) && - com.sameerasw.essentials.utils.ShellUtils.hasPermission(this) && - com.sameerasw.essentials.utils.ShellUtils.isAvailable(this) + return ShellUtils.isAvailable(this) && ShellUtils.hasPermission(this) } diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/MonoAudioTileService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/MonoAudioTileService.kt index 9e67c126b..c8c236234 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/MonoAudioTileService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/MonoAudioTileService.kt @@ -14,11 +14,9 @@ class MonoAudioTileService : BaseTileService() { } override fun hasFeaturePermission(): Boolean { - // Private secure settings can only be modified by ADB, system apps, or - // apps with a target sdk of Android 5.1 and lower. - return com.sameerasw.essentials.utils.ShellUtils.hasPermission(this) && com.sameerasw.essentials.utils.ShellUtils.isAvailable( - this - ) + return com.sameerasw.essentials.utils.PermissionUtils.canWriteSecureSettings(this) || + (com.sameerasw.essentials.utils.ShellUtils.isAvailable(this) && + com.sameerasw.essentials.utils.ShellUtils.hasPermission(this)) } override fun getTileIcon(): Icon { diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/NfcTileService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/NfcTileService.kt index 9f0b56bb6..98e0406e8 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/NfcTileService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/NfcTileService.kt @@ -7,6 +7,7 @@ import android.os.Build import android.service.quicksettings.Tile import androidx.annotation.RequiresApi import com.sameerasw.essentials.R +import com.sameerasw.essentials.utils.ShellUtils import java.lang.reflect.Method @RequiresApi(Build.VERSION_CODES.N) @@ -55,8 +56,8 @@ class NfcTileService : BaseTileService() { } override fun hasFeaturePermission(): Boolean { - // We need WRITE_SECURE_SETTINGS to toggle NFC via reflection - return checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) == android.content.pm.PackageManager.PERMISSION_GRANTED + return com.sameerasw.essentials.utils.PermissionUtils.canWriteSecureSettings(this) || + (ShellUtils.isAvailable(this) && ShellUtils.hasPermission(this)) } override fun getTileIcon(): Icon { @@ -80,8 +81,10 @@ class NfcTileService : BaseTileService() { val method: Method = nfcAdapter.javaClass.getMethod(methodName) method.invoke(nfcAdapter) as Boolean } catch (e: Exception) { - e.printStackTrace() - false + // Fallback to shell if reflection fails + val command = if (enable) "svc nfc enable" else "svc nfc disable" + ShellUtils.runCommand(context, command) + true } } } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/QuickSettingsTilesSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/QuickSettingsTilesSettingsUI.kt index 91c925e06..a94dc3581 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/QuickSettingsTilesSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/QuickSettingsTilesSettingsUI.kt @@ -68,6 +68,7 @@ import com.sameerasw.essentials.services.tiles.UsbDebuggingTileService import com.sameerasw.essentials.ui.components.sheets.PermissionsBottomSheet import com.sameerasw.essentials.ui.modifiers.highlight import com.sameerasw.essentials.utils.PermissionUIHelper +import com.sameerasw.essentials.utils.PermissionUtils import com.sameerasw.essentials.utils.ShellUtils import com.sameerasw.essentials.viewmodels.MainViewModel @@ -128,7 +129,9 @@ fun QuickSettingsTilesSettingsUI( R.string.tile_aod, R.drawable.rounded_mobile_text_2_24, AlwaysOnDisplayTileService::class.java, - listOf("WRITE_SECURE_SETTINGS"), + if (ShellUtils.isRootEnabled(context)) listOf("ROOT") + else if (PermissionUtils.canWriteSecureSettings(context)) listOf("WRITE_SECURE_SETTINGS") + else listOf("SHIZUKU"), R.string.about_desc_aod ), QSTileInfo( @@ -177,7 +180,9 @@ fun QuickSettingsTilesSettingsUI( R.string.tile_mono_audio, R.drawable.rounded_headphones_24, MonoAudioTileService::class.java, - if (ShellUtils.isRootEnabled(context)) listOf("ROOT") else listOf("SHIZUKU"), + if (ShellUtils.isRootEnabled(context)) listOf("ROOT") + else if (PermissionUtils.canWriteSecureSettings(context)) listOf("WRITE_SECURE_SETTINGS") + else listOf("SHIZUKU"), R.string.about_desc_mono_audio ), QSTileInfo( @@ -216,7 +221,9 @@ fun QuickSettingsTilesSettingsUI( R.string.nfc_tile_label, R.drawable.rounded_nfc_24, NfcTileService::class.java, - if (ShellUtils.isRootEnabled(context)) listOf("ROOT") else listOf("SHIZUKU"), + if (ShellUtils.isRootEnabled(context)) listOf("ROOT") + else if (PermissionUtils.canWriteSecureSettings(context)) listOf("WRITE_SECURE_SETTINGS") + else listOf("SHIZUKU"), R.string.about_desc_nfc ), QSTileInfo( @@ -275,7 +282,7 @@ fun QuickSettingsTilesSettingsUI( R.string.tile_charge_optimization, R.drawable.rounded_battery_android_frame_shield_24, ChargeQuickTileService::class.java, - if (ShellUtils.isRootEnabled(context)) listOf("ROOT") else listOf("SHIZUKU", "WRITE_SECURE_SETTINGS"), + if (ShellUtils.isRootEnabled(context)) listOf("ROOT") else listOf("SHIZUKU"), R.string.about_desc_charge_optimization ) From f05ba34db8d8c4b8cfedfdfa9117024568a523d3 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Mon, 2 Mar 2026 19:51:18 +0530 Subject: [PATCH 2/5] fix: QS tiles screen edge-to-edge --- app/build.gradle.kts | 20 +++++++++---------- .../essentials/FeatureSettingsActivity.kt | 17 ++++++++++++---- .../configs/QuickSettingsTilesSettingsUI.kt | 17 ++++++++++++---- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5909b3ffe..088f73e6d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -31,16 +31,16 @@ android { // optimized dev build - debug { - isMinifyEnabled = true - isShrinkResources = true - isDebuggable = false - - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } +// debug { +// isMinifyEnabled = true +// isShrinkResources = true +// isDebuggable = false +// +// proguardFiles( +// getDefaultProguardFile("proguard-android-optimize.txt"), +// "proguard-rules.pro" +// ) +// } // end diff --git a/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt b/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt index 9dd183d19..640386ecd 100644 --- a/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt @@ -10,6 +10,7 @@ import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize @@ -336,7 +337,9 @@ class FeatureSettingsActivity : FragmentActivity() { .then(if (hasScroll) Modifier.verticalScroll(rememberScrollState()) else Modifier) ) { // Top padding for status bar - androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(statusBarHeight)) + if (featureId != "Quick settings tiles") { + androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(statusBarHeight)) + } if (featureId == "Watch") { WatchSettingsUI( @@ -533,8 +536,12 @@ class FeatureSettingsActivity : FragmentActivity() { "Quick settings tiles" -> { QuickSettingsTilesSettingsUI( - modifier = Modifier.padding(top = 16.dp), - highlightSetting = highlightSetting + modifier = Modifier.fillMaxSize(), + highlightSetting = highlightSetting, + contentPadding = PaddingValues( + top = statusBarHeight, + bottom = 150.dp + ) ) } @@ -620,7 +627,9 @@ class FeatureSettingsActivity : FragmentActivity() { } // Bottom padding for toolbar - androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(150.dp)) + if (featureId != "Quick settings tiles") { + androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(150.dp)) + } } SettingsFloatingToolbar( diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/QuickSettingsTilesSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/QuickSettingsTilesSettingsUI.kt index a94dc3581..ef0092d39 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/QuickSettingsTilesSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/QuickSettingsTilesSettingsUI.kt @@ -14,8 +14,12 @@ import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues 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.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape @@ -83,7 +87,8 @@ data class QSTileInfo( @Composable fun QuickSettingsTilesSettingsUI( modifier: Modifier = Modifier, - highlightSetting: String? = null + highlightSetting: String? = null, + contentPadding: PaddingValues = PaddingValues(0.dp) ) { val context = LocalContext.current LocalView.current @@ -339,11 +344,13 @@ fun QuickSettingsTilesSettingsUI( Column( modifier = modifier - .fillMaxWidth() + .fillMaxSize() .verticalScroll(rememberScrollState()) - .padding(16.dp), + .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { + Spacer(modifier = Modifier.height(contentPadding.calculateTopPadding())) + Spacer(modifier = Modifier.height(16.dp)) tiles.chunked(2).forEach { rowTiles -> Row( modifier = Modifier.fillMaxWidth(), @@ -422,8 +429,10 @@ fun QuickSettingsTilesSettingsUI( text = "Long press a tile to see what it does", style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(top = 8.dp, bottom = 48.dp) + modifier = Modifier.padding(top = 8.dp) ) + + Spacer(modifier = Modifier.height(contentPadding.calculateBottomPadding())) } } From b1b3178ee61e9615afd6c9c4e61937daaefee4a7 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Mon, 2 Mar 2026 19:55:17 +0530 Subject: [PATCH 3/5] feat: Blur toggle --- .../essentials/FeatureSettingsActivity.kt | 25 +++++++++----- .../com/sameerasw/essentials/MainActivity.kt | 25 +++++++++----- .../sameerasw/essentials/SettingsActivity.kt | 33 ++++++++++++++----- .../data/repository/SettingsRepository.kt | 1 + .../ui/activities/YourAndroidActivity.kt | 27 ++++++++++----- .../essentials/viewmodels/MainViewModel.kt | 9 +++++ 6 files changed, 88 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt b/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt index 640386ecd..ddea5f181 100644 --- a/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt @@ -165,6 +165,7 @@ class FeatureSettingsActivity : FragmentActivity() { remember(context) { viewModel.check(context) } val isPitchBlackThemeEnabled by viewModel.isPitchBlackThemeEnabled + val isBlurEnabled by viewModel.isBlurEnabled val pinnedFeatureKeys by viewModel.pinnedFeatureKeys EssentialsTheme(pitchBlackTheme = isPitchBlackThemeEnabled) { @@ -319,20 +320,28 @@ class FeatureSettingsActivity : FragmentActivity() { modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.surfaceContainer) - .progressiveBlur( - blurRadius = 40f, - height = statusBarHeightPx * 1.15f, - direction = BlurDirection.TOP + .then( + if (isBlurEnabled) { + Modifier.progressiveBlur( + blurRadius = 40f, + height = statusBarHeightPx * 1.15f, + direction = BlurDirection.TOP + ) + } else Modifier ) ) { val hasScroll = featureId != "Sound mode tile" && featureId != "Quick settings tiles" Column( modifier = Modifier .fillMaxSize() - .progressiveBlur( - blurRadius = 40f, - height = with(LocalDensity.current) { 150.dp.toPx() }, - direction = BlurDirection.BOTTOM + .then( + if (isBlurEnabled) { + Modifier.progressiveBlur( + blurRadius = 40f, + height = with(LocalDensity.current) { 150.dp.toPx() }, + direction = BlurDirection.BOTTOM + ) + } else Modifier ) .then(if (hasScroll) Modifier.verticalScroll(rememberScrollState()) else Modifier) ) { diff --git a/app/src/main/java/com/sameerasw/essentials/MainActivity.kt b/app/src/main/java/com/sameerasw/essentials/MainActivity.kt index 0f4139a01..97666f65f 100644 --- a/app/src/main/java/com/sameerasw/essentials/MainActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/MainActivity.kt @@ -241,6 +241,7 @@ class MainActivity : FragmentActivity() { viewModel.check(this) setContent { val isPitchBlackThemeEnabled by viewModel.isPitchBlackThemeEnabled + val isBlurEnabled by viewModel.isBlurEnabled EssentialsTheme(pitchBlackTheme = isPitchBlackThemeEnabled) { androidx.compose.runtime.CompositionLocalProvider( com.sameerasw.essentials.ui.state.LocalMenuStateManager provides remember { com.sameerasw.essentials.ui.state.MenuStateManager() } @@ -451,10 +452,14 @@ class MainActivity : FragmentActivity() { Box( modifier = Modifier .fillMaxSize() - .progressiveBlur( - blurRadius = 40f, - height = statusBarHeightPx * 1.15f, - direction = BlurDirection.TOP + .then( + if (isBlurEnabled) { + Modifier.progressiveBlur( + blurRadius = 40f, + height = statusBarHeightPx * 1.15f, + direction = BlurDirection.TOP + ) + } else Modifier ) ) { val currentTab = remember(tabs, currentPage) { @@ -609,10 +614,14 @@ class MainActivity : FragmentActivity() { modifier = Modifier .scale(1f - (backProgress.value * 0.05f)) .alpha(1f - (backProgress.value * 0.3f)) - .progressiveBlur( - blurRadius = 40f, - height = with(androidx.compose.ui.platform.LocalDensity.current) { 130.dp.toPx() }, - direction = BlurDirection.BOTTOM + .then( + if (isBlurEnabled) { + Modifier.progressiveBlur( + blurRadius = 40f, + height = with(androidx.compose.ui.platform.LocalDensity.current) { 130.dp.toPx() }, + direction = BlurDirection.BOTTOM + ) + } else Modifier ), label = "Tab Transition" ) { targetPage -> diff --git a/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt b/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt index 280f6fac0..4d69a8775 100644 --- a/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt @@ -131,14 +131,20 @@ class SettingsActivity : ComponentActivity() { } val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + val isBlurEnabled by viewModel.isBlurEnabled + Box( modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.surfaceContainer) - .progressiveBlur( - blurRadius = 40f, - height = statusBarHeightPx * 1.15f, - direction = BlurDirection.TOP + .then( + if (isBlurEnabled) { + Modifier.progressiveBlur( + blurRadius = 40f, + height = statusBarHeightPx * 1.15f, + direction = BlurDirection.TOP + ) + } else Modifier ) ) { val contentPadding = androidx.compose.foundation.layout.PaddingValues( @@ -152,10 +158,14 @@ class SettingsActivity : ComponentActivity() { viewModel = viewModel, contentPadding = contentPadding, modifier = Modifier - .progressiveBlur( - blurRadius = 40f, - height = with(LocalDensity.current) { 150.dp.toPx() }, - direction = BlurDirection.BOTTOM + .then( + if (isBlurEnabled) { + Modifier.progressiveBlur( + blurRadius = 40f, + height = with(LocalDensity.current) { 150.dp.toPx() }, + direction = BlurDirection.BOTTOM + ) + } else Modifier ) ) @@ -342,6 +352,13 @@ fun SettingsContent( isChecked = viewModel.isPitchBlackThemeEnabled.value, onCheckedChange = { viewModel.setPitchBlackThemeEnabled(it, context) } ) + IconToggleItem( + iconRes = R.drawable.rounded_blur_on_24, + title = "Use blur", + description = "Enable progressive blur elements across the UI", + isChecked = viewModel.isBlurEnabled.value, + onCheckedChange = { viewModel.setBlurEnabled(it, context) } + ) IconToggleItem( iconRes = R.drawable.rounded_numbers_24, title = stringResource(R.string.setting_use_root_title), diff --git a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt index 2f1a52011..dbf8fac04 100644 --- a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt +++ b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt @@ -163,6 +163,7 @@ class SettingsRepository(private val context: Context) { const val KEY_NOTIFICATION_GLANCE_SELECTED_APPS = "notification_glance_selected_apps" const val KEY_AOD_FORCE_TURN_OFF_ENABLED = "aod_force_turn_off_enabled" const val KEY_AUTO_ACCESSIBILITY_ENABLED = "auto_accessibility_enabled" + const val KEY_USE_BLUR = "use_blur" } // Observe changes diff --git a/app/src/main/java/com/sameerasw/essentials/ui/activities/YourAndroidActivity.kt b/app/src/main/java/com/sameerasw/essentials/ui/activities/YourAndroidActivity.kt index 924ae7a84..263797c3f 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/activities/YourAndroidActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/activities/YourAndroidActivity.kt @@ -122,6 +122,7 @@ class YourAndroidActivity : ComponentActivity() { setContent { val mainViewModel: com.sameerasw.essentials.viewmodels.MainViewModel = androidx.lifecycle.viewmodel.compose.viewModel() val isPitchBlackThemeEnabled by mainViewModel.isPitchBlackThemeEnabled + val isBlurEnabled by mainViewModel.isBlurEnabled val viewModel: YourAndroidViewModel = androidx.lifecycle.viewmodel.compose.viewModel() val deviceSpecs by viewModel.deviceSpecs.collectAsState() @@ -143,10 +144,14 @@ class YourAndroidActivity : ComponentActivity() { modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.surfaceContainer) - .progressiveBlur( - blurRadius = 40f, - height = statusBarHeightPx * 1.15f, - direction = BlurDirection.TOP + .then( + if (isBlurEnabled) { + Modifier.progressiveBlur( + blurRadius = 40f, + height = statusBarHeightPx * 1.15f, + direction = BlurDirection.TOP + ) + } else Modifier ) ) { YourAndroidContent( @@ -191,9 +196,11 @@ fun YourAndroidContent( } } + val mainViewModel: com.sameerasw.essentials.viewmodels.MainViewModel = androidx.lifecycle.viewmodel.compose.viewModel() val configuration = LocalConfiguration.current val screenHeight = configuration.screenHeightDp.dp val initialImageOffset = (screenHeight / 2) - 240.dp - 64.dp + val isBlurEnabled by mainViewModel.isBlurEnabled val imageOffsetState = animateDpAsState( targetValue = if (isStartupAnimationRunning) 0.dp else initialImageOffset, @@ -220,10 +227,14 @@ fun YourAndroidContent( Column( modifier = modifier .fillMaxSize() - .progressiveBlur( - blurRadius = 40f, - height = with(LocalDensity.current) { 150.dp.toPx() }, - direction = BlurDirection.BOTTOM + .then( + if (isBlurEnabled) { + Modifier.progressiveBlur( + blurRadius = 40f, + height = with(LocalDensity.current) { 150.dp.toPx() }, + direction = BlurDirection.BOTTOM + ) + } else Modifier ) .verticalScroll(rememberScrollState()) .padding( diff --git a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt index 3b43435d4..8fa7345ac 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt @@ -168,6 +168,7 @@ class MainViewModel : ViewModel() { val hasPendingUpdates = mutableStateOf(false) val isPitchBlackThemeEnabled = mutableStateOf(false) + val isBlurEnabled = mutableStateOf(true) // Keyboard Customization val keyboardHeight = mutableFloatStateOf(54f) @@ -407,6 +408,8 @@ class MainViewModel : ViewModel() { SettingsRepository.KEY_AOD_FORCE_TURN_OFF_ENABLED -> isAodForceTurnOffEnabled.value = settingsRepository.getBoolean(key) SettingsRepository.KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING -> isNotificationGlanceSameAsLightingEnabled.value = settingsRepository.getBoolean(key, true) SettingsRepository.KEY_AUTO_ACCESSIBILITY_ENABLED -> isAutoAccessibilityEnabled.value = settingsRepository.getBoolean(key) + + SettingsRepository.KEY_USE_BLUR -> isBlurEnabled.value = settingsRepository.getBoolean(key, true) } } } @@ -778,6 +781,7 @@ class MainViewModel : ViewModel() { isNotificationGlanceEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_ENABLED) isAodForceTurnOffEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_AOD_FORCE_TURN_OFF_ENABLED) isNotificationGlanceSameAsLightingEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING, true) + isBlurEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_USE_BLUR, true) refreshTrackedUpdates(context) if (isBatteryNotificationEnabled.value) { @@ -924,6 +928,11 @@ class MainViewModel : ViewModel() { settingsRepository.putBoolean(SettingsRepository.KEY_PITCH_BLACK_THEME_ENABLED, enabled) } + fun setBlurEnabled(enabled: Boolean, context: Context) { + isBlurEnabled.value = enabled + settingsRepository.putBoolean(SettingsRepository.KEY_USE_BLUR, enabled) + } + fun checkForUpdates(context: Context, manual: Boolean = false) { if (isCheckingUpdate.value) return From 69cc6440d6c8d0650f756465bf08655533de6c13 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Mon, 2 Mar 2026 19:59:46 +0530 Subject: [PATCH 4/5] feat: Optimized progressive blur --- .../essentials/ui/modifiers/ProgressiveBlurModifier.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/sameerasw/essentials/ui/modifiers/ProgressiveBlurModifier.kt b/app/src/main/java/com/sameerasw/essentials/ui/modifiers/ProgressiveBlurModifier.kt index 88e58a7e0..a2738e1d2 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/modifiers/ProgressiveBlurModifier.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/modifiers/ProgressiveBlurModifier.kt @@ -41,12 +41,17 @@ private val PROGRESSIVE_BLUR_SKSL = """ half4 accum = half4(0.0); float weightSum = 0.0; - const int SAMPLES = 5; + // Random value for dithering based on pixel coordinates + float dither = fract(sin(dot(fragCoord, float2(12.9898, 78.233))) * 43758.5453); + float2 jitter = float2(dither - 0.5, fract(dither * 1.618) - 0.5); + + const int SAMPLES = 4; float offsetScale = radius / float(SAMPLES); for (int x = -SAMPLES; x <= SAMPLES; x++) { for (int y = -SAMPLES; y <= SAMPLES; y++) { - float2 offset = float2(float(x), float(y)) * offsetScale; + // Apply jittered sampling with dither + float2 offset = (float2(float(x), float(y)) + jitter) * offsetScale; float distSq = dot(offset, offset); float radiusSq = radius * radius; From 36ffd91c4b6198c7bf62a8ad0d558b7cb960bb5b Mon Sep 17 00:00:00 2001 From: sameerasw Date: Mon, 2 Mar 2026 20:02:11 +0530 Subject: [PATCH 5/5] feat: Secondary toolbar haptics --- .../ui/components/SettingsFloatingToolbar.kt | 14 ++++++++++++-- .../ui/components/menus/SegmentedMenu.kt | 6 +++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/SettingsFloatingToolbar.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/SettingsFloatingToolbar.kt index 037998417..757c55d0b 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/SettingsFloatingToolbar.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/SettingsFloatingToolbar.kt @@ -32,6 +32,8 @@ import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenu import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenuItem import androidx.compose.foundation.layout.RowScope import androidx.compose.ui.Alignment +import androidx.compose.ui.platform.LocalView +import com.sameerasw.essentials.utils.HapticUtil @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -42,6 +44,7 @@ fun SettingsFloatingToolbar( menuContent: (@Composable SettingsMenuScope.() -> Unit)? = null ) { var menuExpanded by remember { mutableStateOf(false) } + val view = LocalView.current if (menuContent != null) { HorizontalFloatingToolbar( @@ -52,7 +55,10 @@ fun SettingsFloatingToolbar( floatingActionButton = { Box { FloatingActionButton( - onClick = { menuExpanded = true }, + onClick = { + HapticUtil.performVirtualKeyHaptic(view) + menuExpanded = true + }, containerColor = MaterialTheme.colorScheme.primaryContainer, contentColor = MaterialTheme.colorScheme.onPrimaryContainer, shape = MaterialTheme.shapes.large, @@ -104,8 +110,12 @@ private fun RowScope.ToolbarContent( title: String, onBackClick: () -> Unit ) { + val view = LocalView.current IconButton( - onClick = onBackClick, + onClick = { + HapticUtil.performVirtualKeyHaptic(view) + onBackClick() + }, modifier = Modifier.align(Alignment.CenterVertically), colors = IconButtonDefaults.filledIconButtonColors( contentColor = MaterialTheme.colorScheme.primary, diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/menus/SegmentedMenu.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/menus/SegmentedMenu.kt index 5ea57e202..d96a884c3 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/menus/SegmentedMenu.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/menus/SegmentedMenu.kt @@ -57,9 +57,13 @@ fun SegmentedDropdownMenuItem( disabledTrailingIconColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f), ) ) { + val view = androidx.compose.ui.platform.LocalView.current DropdownMenuItem( text = text, - onClick = onClick, + onClick = { + com.sameerasw.essentials.utils.HapticUtil.performUIHaptic(view) + onClick() + }, modifier = modifier .clip(MaterialTheme.shapes.extraSmall) .background(MaterialTheme.colorScheme.surfaceContainerHigh),