From 8298a3b5a24d77b497c34f57ec80b72b47d0a3d2 Mon Sep 17 00:00:00 2001 From: KR Developer Date: Mon, 6 Jul 2026 00:25:14 +0530 Subject: [PATCH 01/13] refactor(ui): fixed inconsistent ui of repo tracking card --- .../ui/activities/YourAndroidActivity.kt | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) 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 0de9c864f..e77ade9c3 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 @@ -16,15 +16,15 @@ 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.Spacer +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues 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.layout.size import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll @@ -527,20 +527,17 @@ fun YourAndroidContent( .padding(32.dp) ) } else if (trackedRepos.isEmpty()) { - androidx.compose.material3.Card( - modifier = Modifier - .background( - MaterialTheme.colorScheme.surfaceBright - ) - .fillMaxWidth() - .graphicsLayer { - alpha = contentAlphaState.value - translationY = contentOffsetState.value.toPx() - }, - shape = MaterialTheme.shapes.large + RoundedCardContainer( + modifier = Modifier.graphicsLayer { + alpha = contentAlphaState.value + translationY = contentOffsetState.value.toPx() + } ) { Column( - modifier = Modifier.padding(24.dp), + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surfaceBright) + .padding(32.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { @@ -549,17 +546,26 @@ fun YourAndroidContent( style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant ) - Spacer(modifier = Modifier.height(16.dp)) - Button(onClick = onAddRepoClick) { - Text(stringResource(R.string.action_add_repository)) - } - Spacer(modifier = Modifier.height(24.dp)) + } + + Row( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surfaceBright) + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { ImportExportButtons( + modifier = Modifier.wrapContentWidth().weight(1f), view = view, exportLauncher = exportLauncher, importLauncher = importLauncher, showExport = false ) + Button(onClick = onAddRepoClick, modifier = Modifier.weight(1f)) { + Text(stringResource(R.string.action_add_repository)) + } } } } else { From 6dd698663d736847b865befd2b0b56390afb031d Mon Sep 17 00:00:00 2001 From: KR Developer Date: Mon, 6 Jul 2026 16:33:51 +0530 Subject: [PATCH 02/13] refactor(ui): added small rounded corner shape to cars --- .../essentials/ui/activities/YourAndroidActivity.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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 e77ade9c3..6bf4a8e8e 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 @@ -74,6 +74,7 @@ import com.sameerasw.essentials.ui.components.sheets.UpdateBottomSheet import com.sameerasw.essentials.ui.modifiers.BlurDirection import com.sameerasw.essentials.ui.modifiers.progressiveBlur import com.sameerasw.essentials.ui.theme.EssentialsTheme +import com.sameerasw.essentials.ui.theme.Shapes import com.sameerasw.essentials.utils.DeviceInfo import com.sameerasw.essentials.utils.DeviceUtils import com.sameerasw.essentials.utils.HapticUtil @@ -536,7 +537,10 @@ fun YourAndroidContent( Column( modifier = Modifier .fillMaxWidth() - .background(MaterialTheme.colorScheme.surfaceBright) + .background( + MaterialTheme.colorScheme.surfaceBright, + shape = Shapes.extraSmall + ) .padding(32.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center @@ -551,7 +555,10 @@ fun YourAndroidContent( Row( modifier = Modifier .fillMaxWidth() - .background(MaterialTheme.colorScheme.surfaceBright) + .background( + MaterialTheme.colorScheme.surfaceBright, + shape = Shapes.extraSmall + ) .padding(12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp) From 26720c82d7ea17eca0f68146381a701a7a34da40 Mon Sep 17 00:00:00 2001 From: KR Developer Date: Tue, 7 Jul 2026 11:58:07 +0530 Subject: [PATCH 03/13] feat(wallpaper): show and update prev and next wallpaper auto update time --- .../data/repository/SettingsRepository.kt | 2 + .../services/DailyWallpaperWorker.kt | 8 ++ .../composables/wallpaper/WallpaperScreen.kt | 76 ++++++++++++++----- .../essentials/viewmodels/MainViewModel.kt | 23 ++++++ 4 files changed, 92 insertions(+), 17 deletions(-) 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 255d2446a..cfd3950d4 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 @@ -52,6 +52,8 @@ class SettingsRepository(private val context: Context) { const val KEY_DAILY_WALLPAPER_PHOTO_LINK = "daily_wallpaper_photo_link" const val KEY_DAILY_WALLPAPER_UPDATED_AT = "daily_wallpaper_updated_at" const val KEY_DAILY_WALLPAPER_AUTO_UPDATE = "daily_wallpaper_auto_update" + const val KEY_DAILY_WALLPAPER_AUTO_UPDATE_TIME = "daily_wallpaper_auto_update_time" + const val KEY_DAILY_WALLPAPER_SHOW_LAST_TIME = "daily_wallpaper_show_last_time" const val KEY_DAILY_WALLPAPER_APPLY_HOME = "daily_wallpaper_apply_home" const val KEY_DAILY_WALLPAPER_APPLY_LOCK = "daily_wallpaper_apply_lock" diff --git a/app/src/main/java/com/sameerasw/essentials/services/DailyWallpaperWorker.kt b/app/src/main/java/com/sameerasw/essentials/services/DailyWallpaperWorker.kt index eff3528b7..3f5ac267b 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/DailyWallpaperWorker.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/DailyWallpaperWorker.kt @@ -6,6 +6,7 @@ import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.sameerasw.essentials.data.repository.SettingsRepository import com.sameerasw.essentials.data.repository.WallpaperRepository +import java.time.LocalDateTime class DailyWallpaperWorker( appContext: Context, @@ -77,6 +78,13 @@ class DailyWallpaperWorker( SettingsRepository.KEY_DAILY_WALLPAPER_UPDATED_AT, todayWallpaper.updatedAt ) + + val currentTime = LocalDateTime.now().toString() + settingsRepository.putString( + SettingsRepository.KEY_DAILY_WALLPAPER_AUTO_UPDATE_TIME, + currentTime + ) + Log.d( "DailyWallpaperWorker", "Successfully auto-applied wallpaper (force=$force, flags=$flags): ${todayWallpaper.id}" diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/wallpaper/WallpaperScreen.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/wallpaper/WallpaperScreen.kt index 6633e91cd..aea363c84 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/wallpaper/WallpaperScreen.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/wallpaper/WallpaperScreen.kt @@ -70,6 +70,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -107,6 +108,9 @@ import com.sameerasw.essentials.viewmodels.MainViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.time.Duration +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -124,6 +128,8 @@ fun WallpaperScreen( val isLoading by viewModel.isWallpaperLoading val isBlurEnabled by viewModel.isBlurEnabled val isAutoUpdateEnabled by viewModel.isDailyWallpaperAutoUpdateEnabled + val dailyWallpaperAutoUpdateTime by viewModel.dailyWallpaperAutoUpdateTime + val isDailyWallpaperShowLastTime by viewModel.isDailyWallpaperShowLastTime // Live Wallpaper Settings State var availableVideos by remember { mutableStateOf(repository.getLiveWallpaperAvailableVideos()) } @@ -212,6 +218,19 @@ fun WallpaperScreen( } } + var dailyWallpaperNextAutoUpdateTime by remember { mutableLongStateOf(0L) } + LaunchedEffect(dailyWallpaperAutoUpdateTime) { + if (!dailyWallpaperAutoUpdateTime.isNullOrEmpty()){ + val prevTime = LocalDateTime.parse(dailyWallpaperAutoUpdateTime) + val passedTime = Duration.between( + prevTime, + LocalDateTime.now() + ).toHours() + val hoursLeft = (12L - passedTime).coerceAtLeast(0) + dailyWallpaperNextAutoUpdateTime = hoursLeft + } + } + Box( modifier = Modifier .fillMaxSize() @@ -473,24 +492,47 @@ fun WallpaperScreen( .padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = stringResource(R.string.label_wallpaper_auto_update), - style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold), - color = MaterialTheme.colorScheme.onSurface - ) - Switch( - checked = isAutoUpdateEnabled, - onCheckedChange = { checked -> - HapticUtil.performUIHaptic(view) - viewModel.setDailyWallpaperAutoUpdate(checked, context) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column( + verticalArrangement = Arrangement.spacedBy(2.dp) + ) { + Text( + text = stringResource(R.string.label_wallpaper_auto_update), + style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold), + color = MaterialTheme.colorScheme.onSurface + ) + if (isAutoUpdateEnabled && !dailyWallpaperAutoUpdateTime.isNullOrEmpty()){ + val timeText = if(isDailyWallpaperShowLastTime) { + val showTime = LocalDateTime.parse(dailyWallpaperAutoUpdateTime) + .format(DateTimeFormatter.ofPattern("hh:mm a")) + "Last checked at $showTime" + } else{ + val showTime = if(dailyWallpaperNextAutoUpdateTime > 0L) "$dailyWallpaperNextAutoUpdateTime hours" else "a few minutes" + "Next check in $showTime" + } + Text( + text = timeText, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.clickable(enabled = true, onClick = { + HapticUtil.performUIHaptic(view) + viewModel.setDailyWallpaperShowLastTime() + }) + ) + } } - ) - } + Switch( + checked = isAutoUpdateEnabled, + onCheckedChange = { checked -> + HapticUtil.performUIHaptic(view) + viewModel.setDailyWallpaperAutoUpdate(checked, context) + } + ) + } Row( modifier = Modifier 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 30de864cb..3d56292ab 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt @@ -69,6 +69,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File import java.io.FileOutputStream +import java.time.LocalDateTime class MainViewModel : ViewModel() { val isAccessibilityEnabled = mutableStateOf(false) @@ -937,6 +938,8 @@ class MainViewModel : ViewModel() { if (isDailyWallpaperAutoUpdateEnabled.value) { schedulePeriodicWallpaperCheck(context) } + dailyWallpaperAutoUpdateTime.value = settingsRepository.getString(SettingsRepository.KEY_DAILY_WALLPAPER_AUTO_UPDATE_TIME) + isDailyWallpaperShowLastTime.value = settingsRepository.getBoolean(SettingsRepository.KEY_DAILY_WALLPAPER_SHOW_LAST_TIME) if (isHideGestureBarEnabled.value) { applyHideGestureBar(context, true) @@ -4239,10 +4242,19 @@ class MainViewModel : ViewModel() { } val isDailyWallpaperAutoUpdateEnabled = mutableStateOf(false) + val dailyWallpaperAutoUpdateTime = mutableStateOf(null) + val isDailyWallpaperShowLastTime = mutableStateOf(false) + + fun setDailyWallpaperShowLastTime() { + val status = !isDailyWallpaperShowLastTime.value + isDailyWallpaperShowLastTime.value = status + settingsRepository.putBoolean(SettingsRepository.KEY_DAILY_WALLPAPER_SHOW_LAST_TIME, status) + } fun setDailyWallpaperAutoUpdate(enabled: Boolean, context: Context) { isDailyWallpaperAutoUpdateEnabled.value = enabled settingsRepository.putBoolean(SettingsRepository.KEY_DAILY_WALLPAPER_AUTO_UPDATE, enabled) + updateDailyWallpaperAutoUpdateTime(enabled) if (enabled) { schedulePeriodicWallpaperCheck(context) triggerInstantWallpaperUpdate(context) @@ -4279,4 +4291,15 @@ class MainViewModel : ViewModel() { androidx.work.WorkManager.getInstance(context) .cancelUniqueWork("daily_wallpaper_check_work") } + + private fun updateDailyWallpaperAutoUpdateTime(enabled: Boolean) { + if (enabled){ + val currentTime = LocalDateTime.now().toString() + dailyWallpaperAutoUpdateTime.value = currentTime + settingsRepository.putString(SettingsRepository.KEY_DAILY_WALLPAPER_AUTO_UPDATE_TIME, currentTime) + } else { + dailyWallpaperAutoUpdateTime.value = null + settingsRepository.remove(SettingsRepository.KEY_DAILY_WALLPAPER_AUTO_UPDATE_TIME) + } + } } From 51c38063d1975a02e587158d1d7c582758e1e68c Mon Sep 17 00:00:00 2001 From: KR Developer Date: Tue, 7 Jul 2026 14:09:15 +0530 Subject: [PATCH 04/13] feat(developer): added setting for import config, merge config with current settings --- .../java/com/sameerasw/essentials/SettingsActivity.kt | 8 ++++++++ .../essentials/data/repository/SettingsRepository.kt | 5 +++-- .../sameerasw/essentials/viewmodels/MainViewModel.kt | 11 ++++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt b/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt index 2db18abd9..b9efa3cff 100644 --- a/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt @@ -952,6 +952,14 @@ fun SettingsContent( ) RoundedCardContainer { + IconToggleItem( + iconRes = R.drawable.rounded_front_hand_24, + title = "Keep new settings", + description = "Don't reset existing settings after import", + isChecked = viewModel.isKeepPrefs.value, + onCheckedChange = { viewModel.toggleKeepPrefs(it) } + ) + Row( modifier = Modifier .fillMaxWidth() 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 cfd3950d4..8a5aec8b9 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 @@ -288,6 +288,7 @@ class SettingsRepository(private val context: Context) { const val KEY_POCKET_MODE_EXCLUDED_APPS = "pocket_mode_excluded_apps" const val KEY_POCKET_MODE_TRIGGER_DELAY = "pocket_mode_trigger_delay" const val KEY_POCKET_MODE_LOCK_SCREEN_ONLY = "pocket_mode_lock_screen_only" + const val KEY_KEEP_PREFS = "keep_prefs" } // Observe changes @@ -769,7 +770,7 @@ class SettingsRepository(private val context: Context) { } } - fun importConfigs(inputStream: java.io.InputStream): Boolean { + fun importConfigs(inputStream: java.io.InputStream, keepPrefs: Boolean): Boolean { return try { val json = inputStream.bufferedReader().use { it.readText() } val allConfigs: Map>> = @@ -797,7 +798,7 @@ class SettingsRepository(private val context: Context) { } p.edit().apply { - clear() + if(!keepPrefs) clear() // Restore preserved values preservedValues.forEach { (key, value) -> 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 3d56292ab..1c50619fe 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt @@ -332,6 +332,8 @@ class MainViewModel : ViewModel() { private var workflowPollingJob: kotlinx.coroutines.Job? = null val gitHubUser = mutableStateOf(null) + val isKeepPrefs = mutableStateOf(false) + private val contentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) { override fun onChange(selfChange: Boolean, uri: Uri?) { uri?.let { @@ -1498,6 +1500,8 @@ class MainViewModel : ViewModel() { if (isBatteryNotificationEnabled.value) { startBatteryNotificationService(context) } + + isKeepPrefs.value = settingsRepository.getBoolean(SettingsRepository.KEY_KEEP_PREFS) } private fun startBatteryNotificationService(context: Context) { @@ -3935,7 +3939,7 @@ class MainViewModel : ViewModel() { } fun importConfigs(context: Context, inputStream: java.io.InputStream): Boolean { - val success = settingsRepository.importConfigs(inputStream) + val success = settingsRepository.importConfigs(inputStream, isKeepPrefs.value) if (success) { settingsRepository.syncSystemSettingsWithSaved() com.sameerasw.essentials.domain.diy.DIYRepository.reloadAutomations() @@ -4302,4 +4306,9 @@ class MainViewModel : ViewModel() { settingsRepository.remove(SettingsRepository.KEY_DAILY_WALLPAPER_AUTO_UPDATE_TIME) } } + + fun toggleKeepPrefs(enabled: Boolean) { + isKeepPrefs.value = enabled + settingsRepository.putBoolean(SettingsRepository.KEY_KEEP_PREFS, enabled) + } } From 574e021d75704b16a2419d78fca7ebfdccddb008 Mon Sep 17 00:00:00 2001 From: KR Developer Date: Tue, 7 Jul 2026 14:26:12 +0530 Subject: [PATCH 05/13] refactor(ui/settings): nothing, just added shape --- .../java/com/sameerasw/essentials/SettingsActivity.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt b/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt index b9efa3cff..228936bc2 100644 --- a/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt @@ -96,6 +96,7 @@ import com.sameerasw.essentials.ui.components.sheets.UpdateBottomSheet import com.sameerasw.essentials.ui.modifiers.BlurDirection import com.sameerasw.essentials.ui.modifiers.progressiveBlur import com.sameerasw.essentials.ui.theme.EssentialsTheme +import com.sameerasw.essentials.ui.theme.Shapes import com.sameerasw.essentials.utils.DeviceUtils import com.sameerasw.essentials.utils.HapticUtil import com.sameerasw.essentials.utils.PermissionUtils @@ -964,7 +965,8 @@ fun SettingsContent( modifier = Modifier .fillMaxWidth() .background( - color = MaterialTheme.colorScheme.surfaceBright + color = MaterialTheme.colorScheme.surfaceBright, + shape = Shapes.extraSmall ) .padding(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp) @@ -995,7 +997,8 @@ fun SettingsContent( modifier = Modifier .fillMaxWidth() .background( - color = MaterialTheme.colorScheme.surfaceBright + color = MaterialTheme.colorScheme.surfaceBright, + shape = Shapes.extraSmall ) .padding(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp) @@ -1034,7 +1037,8 @@ fun SettingsContent( modifier = Modifier .fillMaxWidth() .background( - color = MaterialTheme.colorScheme.surfaceBright + color = MaterialTheme.colorScheme.surfaceBright, + shape = Shapes.extraSmall ) .padding(start = 12.dp, end = 12.dp, top = 4.dp, bottom = 12.dp), horizontalArrangement = Arrangement.spacedBy(8.dp) From 4fb94b6fde593fbc36984be74b23b2f8e362aaf0 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Tue, 7 Jul 2026 18:34:53 +0530 Subject: [PATCH 06/13] refactor: localize settings labels and add safe date formatting to wallpaper update display --- .../sameerasw/essentials/SettingsActivity.kt | 4 +-- .../composables/wallpaper/WallpaperScreen.kt | 31 ++++++++++++------- app/src/main/res/values/strings.xml | 8 +++++ 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt b/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt index 228936bc2..eccf90995 100644 --- a/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt @@ -955,8 +955,8 @@ fun SettingsContent( RoundedCardContainer { IconToggleItem( iconRes = R.drawable.rounded_front_hand_24, - title = "Keep new settings", - description = "Don't reset existing settings after import", + title = stringResource(R.string.label_keep_new_settings), + description = stringResource(R.string.desc_keep_new_settings), isChecked = viewModel.isKeepPrefs.value, onCheckedChange = { viewModel.toggleKeepPrefs(it) } ) diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/wallpaper/WallpaperScreen.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/wallpaper/WallpaperScreen.kt index aea363c84..19ba61264 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/wallpaper/WallpaperScreen.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/wallpaper/WallpaperScreen.kt @@ -219,15 +219,20 @@ fun WallpaperScreen( } var dailyWallpaperNextAutoUpdateTime by remember { mutableLongStateOf(0L) } + var dailyWallpaperLastCheckedFormatted by remember { mutableStateOf("") } LaunchedEffect(dailyWallpaperAutoUpdateTime) { if (!dailyWallpaperAutoUpdateTime.isNullOrEmpty()){ - val prevTime = LocalDateTime.parse(dailyWallpaperAutoUpdateTime) - val passedTime = Duration.between( - prevTime, - LocalDateTime.now() - ).toHours() - val hoursLeft = (12L - passedTime).coerceAtLeast(0) - dailyWallpaperNextAutoUpdateTime = hoursLeft + val prevTime = runCatching { LocalDateTime.parse(dailyWallpaperAutoUpdateTime) }.getOrNull() + if (prevTime != null) { + val passedTime = Duration.between( + prevTime, + LocalDateTime.now() + ).toHours() + dailyWallpaperNextAutoUpdateTime = (12L - passedTime).coerceAtLeast(0) + dailyWallpaperLastCheckedFormatted = runCatching { + prevTime.format(DateTimeFormatter.ofPattern("hh:mm a")) + }.getOrDefault("") + } } } @@ -507,12 +512,14 @@ fun WallpaperScreen( ) if (isAutoUpdateEnabled && !dailyWallpaperAutoUpdateTime.isNullOrEmpty()){ val timeText = if(isDailyWallpaperShowLastTime) { - val showTime = LocalDateTime.parse(dailyWallpaperAutoUpdateTime) - .format(DateTimeFormatter.ofPattern("hh:mm a")) - "Last checked at $showTime" + stringResource(R.string.label_wallpaper_last_checked, dailyWallpaperLastCheckedFormatted) } else{ - val showTime = if(dailyWallpaperNextAutoUpdateTime > 0L) "$dailyWallpaperNextAutoUpdateTime hours" else "a few minutes" - "Next check in $showTime" + val showTime = if(dailyWallpaperNextAutoUpdateTime > 0L) { + stringResource(R.string.label_wallpaper_hours, dailyWallpaperNextAutoUpdateTime.toInt()) + } else { + stringResource(R.string.label_wallpaper_a_few_minutes) + } + stringResource(R.string.label_wallpaper_next_check, showTime) } Text( text = timeText, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2e5fb0df6..2bcb5c683 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1665,4 +1665,12 @@ Trigger delay Lock screen only Only trigger pocket mode when the device is locked + + + Keep new settings + Don\'t reset existing settings after import + Last checked at %1$s + Next check in %1$s + %1$d hours + a few minutes From 49d8ff48a0da55e1eb4a88f3de23ad24d8e22f7f Mon Sep 17 00:00:00 2001 From: sameerasw Date: Tue, 7 Jul 2026 19:10:28 +0530 Subject: [PATCH 07/13] feat: replace date import options with a dialog with warnings --- .../sameerasw/essentials/SettingsActivity.kt | 64 ++++++---- .../sheets/ImportConfigConfirmationSheet.kt | 119 ++++++++++++++++++ .../essentials/viewmodels/MainViewModel.kt | 10 +- app/src/main/res/values/strings.xml | 7 +- 4 files changed, 168 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/com/sameerasw/essentials/ui/components/sheets/ImportConfigConfirmationSheet.kt diff --git a/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt b/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt index eccf90995..eba275919 100644 --- a/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt @@ -92,6 +92,7 @@ import com.sameerasw.essentials.ui.components.pickers.DefaultTabPicker import com.sameerasw.essentials.ui.components.pickers.LanguagePicker import com.sameerasw.essentials.ui.components.sheets.InstructionsBottomSheet import com.sameerasw.essentials.ui.components.sheets.UnsupportedFeaturesConfirmationSheet +import com.sameerasw.essentials.ui.components.sheets.ImportConfigConfirmationSheet import com.sameerasw.essentials.ui.components.sheets.UpdateBottomSheet import com.sameerasw.essentials.ui.modifiers.BlurDirection import com.sameerasw.essentials.ui.modifiers.progressiveBlur @@ -262,6 +263,30 @@ fun SettingsContent( var showInstructionsSheet by remember { mutableStateOf(false) } var showShizukuHelpBottomSheet by remember { mutableStateOf(false) } var showUnsupportedFeaturesSheet by remember { mutableStateOf(false) } + var showImportConfirmSheet by remember { mutableStateOf(false) } + var selectedImportUri by remember { mutableStateOf(null) } + + val onImportConfig: (Boolean) -> Unit = { keepPrefs -> + selectedImportUri?.let { uri -> + try { + context.contentResolver.openInputStream(uri)?.use { inputStream -> + if (viewModel.importConfigs(context, inputStream, keepPrefs)) { + Toast.makeText(context, "Config imported successfully", Toast.LENGTH_SHORT) + .show() + } else { + Toast.makeText(context, "Failed to import config", Toast.LENGTH_SHORT) + .show() + } + } + } catch (e: Exception) { + Toast.makeText(context, "Failed to import config", Toast.LENGTH_SHORT).show() + e.printStackTrace() + } finally { + selectedImportUri = null + showImportConfirmSheet = false + } + } + } val exportLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.CreateDocument("application/json") @@ -284,20 +309,8 @@ fun SettingsContent( contract = ActivityResultContracts.OpenDocument() ) { uri -> uri?.let { - try { - context.contentResolver.openInputStream(it)?.use { inputStream -> - if (viewModel.importConfigs(context, inputStream)) { - Toast.makeText(context, "Config imported successfully", Toast.LENGTH_SHORT) - .show() - } else { - Toast.makeText(context, "Failed to import config", Toast.LENGTH_SHORT) - .show() - } - } - } catch (e: Exception) { - Toast.makeText(context, "Failed to import config", Toast.LENGTH_SHORT).show() - e.printStackTrace() - } + selectedImportUri = it + showImportConfirmSheet = true } } @@ -326,6 +339,21 @@ fun SettingsContent( ) } + if (showImportConfirmSheet) { + ImportConfigConfirmationSheet( + onDismissRequest = { + showImportConfirmSheet = false + selectedImportUri = null + }, + onConfirmOverride = { + onImportConfig(false) + }, + onConfirmMerge = { + onImportConfig(true) + } + ) + } + if (showShizukuHelpBottomSheet) { val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) ModalBottomSheet( @@ -953,14 +981,6 @@ fun SettingsContent( ) RoundedCardContainer { - IconToggleItem( - iconRes = R.drawable.rounded_front_hand_24, - title = stringResource(R.string.label_keep_new_settings), - description = stringResource(R.string.desc_keep_new_settings), - isChecked = viewModel.isKeepPrefs.value, - onCheckedChange = { viewModel.toggleKeepPrefs(it) } - ) - Row( modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/ImportConfigConfirmationSheet.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/ImportConfigConfirmationSheet.kt new file mode 100644 index 000000000..526e86477 --- /dev/null +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/ImportConfigConfirmationSheet.kt @@ -0,0 +1,119 @@ +package com.sameerasw.essentials.ui.components.sheets + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.sameerasw.essentials.R +import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer +import com.sameerasw.essentials.utils.HapticUtil + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ImportConfigConfirmationSheet( + onDismissRequest: () -> Unit, + onConfirmOverride: () -> Unit, + onConfirmMerge: () -> Unit +) { + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + val view = LocalView.current + + ModalBottomSheet( + onDismissRequest = onDismissRequest, + sheetState = sheetState, + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + .padding(bottom = 32.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(R.string.import_config_sheet_title), + style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.Bold), + color = MaterialTheme.colorScheme.onSurface, + textAlign = TextAlign.Center + ) + + Text( + text = stringResource(R.string.import_config_sheet_desc), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center + ) + + RoundedCardContainer( + modifier = Modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(R.string.import_config_merge_warning), + style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Medium), + color = MaterialTheme.colorScheme.error, + textAlign = TextAlign.Center + ) + } + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Button( + onClick = { + HapticUtil.performUIHaptic(view) + onConfirmOverride() + }, + modifier = Modifier.weight(1f) + ) { + Text(stringResource(R.string.action_override)) + } + + OutlinedButton( + onClick = { + HapticUtil.performUIHaptic(view) + onConfirmMerge() + }, + modifier = Modifier.weight(1f) + ) { + Text(stringResource(R.string.action_merge)) + } + } + + OutlinedButton( + onClick = { + HapticUtil.performUIHaptic(view) + onDismissRequest() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.action_abort)) + } + } + } +} 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 1c50619fe..ce31a4be6 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt @@ -332,7 +332,6 @@ class MainViewModel : ViewModel() { private var workflowPollingJob: kotlinx.coroutines.Job? = null val gitHubUser = mutableStateOf(null) - val isKeepPrefs = mutableStateOf(false) private val contentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) { override fun onChange(selfChange: Boolean, uri: Uri?) { @@ -1501,7 +1500,6 @@ class MainViewModel : ViewModel() { startBatteryNotificationService(context) } - isKeepPrefs.value = settingsRepository.getBoolean(SettingsRepository.KEY_KEEP_PREFS) } private fun startBatteryNotificationService(context: Context) { @@ -3938,8 +3936,8 @@ class MainViewModel : ViewModel() { settingsRepository.exportConfigs(outputStream) } - fun importConfigs(context: Context, inputStream: java.io.InputStream): Boolean { - val success = settingsRepository.importConfigs(inputStream, isKeepPrefs.value) + fun importConfigs(context: Context, inputStream: java.io.InputStream, keepPrefs: Boolean): Boolean { + val success = settingsRepository.importConfigs(inputStream, keepPrefs) if (success) { settingsRepository.syncSystemSettingsWithSaved() com.sameerasw.essentials.domain.diy.DIYRepository.reloadAutomations() @@ -4307,8 +4305,4 @@ class MainViewModel : ViewModel() { } } - fun toggleKeepPrefs(enabled: Boolean) { - isKeepPrefs.value = enabled - settingsRepository.putBoolean(SettingsRepository.KEY_KEEP_PREFS, enabled) - } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2bcb5c683..f6dca362c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1667,8 +1667,11 @@ Only trigger pocket mode when the device is locked - Keep new settings - Don\'t reset existing settings after import + Restore Configuration + Choose how you want to restore the configuration. + Merging might cause data corruption. Please proceed with caution! + Override + Merge Last checked at %1$s Next check in %1$s %1$d hours From c1ada04b08e40f08edeab86aa7d7029be7f6cac9 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Tue, 7 Jul 2026 19:45:33 +0530 Subject: [PATCH 08/13] feat: add support for granting WRITE_SECURE_SETTINGS permission via root access #548 --- .../essentials/utils/PermissionUIHelper.kt | 14 ++++++++++---- app/src/main/res/values/strings.xml | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/sameerasw/essentials/utils/PermissionUIHelper.kt b/app/src/main/java/com/sameerasw/essentials/utils/PermissionUIHelper.kt index abc65df74..241ed33e9 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/PermissionUIHelper.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/PermissionUIHelper.kt @@ -55,11 +55,17 @@ object PermissionUIHelper { viewModel.check(context) }, isGranted = viewModel.isWriteSecureSettingsEnabled.value, - shizukuActionLabel = R.string.perm_action_grant_shizuku, - shizukuActionEnabled = ShizukuUtils.hasPermission(), + shizukuActionLabel = if (viewModel.isRootEnabled.value) R.string.perm_action_grant_root else R.string.perm_action_grant_shizuku, + shizukuActionEnabled = if (viewModel.isRootEnabled.value) viewModel.isRootAvailable.value else ShizukuUtils.hasPermission(), shizukuAction = { - if (ShizukuUtils.grantWriteSecureSettingsPermission()) { - viewModel.check(context) + if (viewModel.isRootEnabled.value) { + if (RootUtils.runCommand("pm grant ${context.packageName} android.permission.WRITE_SECURE_SETTINGS")) { + viewModel.check(context) + } + } else { + if (ShizukuUtils.grantWriteSecureSettingsPermission()) { + viewModel.check(context) + } } }, instructions = R.string.perm_write_secure_instructions diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f6dca362c..a974629b5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -671,6 +671,7 @@ How to grant Granted Grant using Shizuku + Grant using Root This permission can be granted either via a PC using ADB. Copy the ADB command and run on the shell.\n\nOr you can easily grant it using Shizuku from the below button.\n\nThis permission only needs to be granted once. Battery Optimization Ensure the service is not killed by the system to save power. From db1779112883903e6c13b45ae8d3ad54d63b03ad Mon Sep 17 00:00:00 2001 From: sameerasw Date: Tue, 7 Jul 2026 19:47:53 +0530 Subject: [PATCH 09/13] feat: Removed hardcoded permission check in refresh rate settings in favor of common util --- .../essentials/FeatureSettingsActivity.kt | 8 +++++-- .../configs/RefreshRateSettingsUI.kt | 13 ++++++++--- .../essentials/utils/PermissionUIHelper.kt | 22 ++++++++++++++----- app/src/main/res/values/strings.xml | 1 + 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt b/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt index b624484d4..5df6ff98f 100644 --- a/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt @@ -304,7 +304,9 @@ class FeatureSettingsActivity : AppCompatActivity() { "Location reached" -> !viewModel.isLocationPermissionGranted.value || !viewModel.isBackgroundLocationPermissionGranted.value "Quick settings tiles" -> !viewModel.isWriteSettingsEnabled.value - "Screen refresh rate" -> !viewModel.isShizukuPermissionGranted.value + "Screen refresh rate" -> !com.sameerasw.essentials.utils.ShellUtils.hasPermission( + context + ) // Top level checks for other features (rarely hit if they are children, but safe to add) "Essentials On Display" -> !isAccessibilityEnabled || !isNotificationListenerEnabled "Call vibrations" -> !isReadPhoneStateEnabled || !isNotificationListenerEnabled @@ -539,7 +541,9 @@ class FeatureSettingsActivity : AppCompatActivity() { "Battery notification" -> !viewModel.isPostNotificationsEnabled.value || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !viewModel.isBluetoothPermissionGranted.value) "Text and animations" -> !viewModel.isWriteSettingsEnabled.value || !isWriteSecureSettingsEnabled "Lock screen clock" -> !isWriteSecureSettingsEnabled - "Screen refresh rate" -> !viewModel.isShizukuPermissionGranted.value + "Screen refresh rate" -> !com.sameerasw.essentials.utils.ShellUtils.hasPermission( + context + ) "Shut-Up!" -> !isWriteSecureSettingsEnabled || !viewModel.isUsageStatsPermissionGranted.value else -> false } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/RefreshRateSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/RefreshRateSettingsUI.kt index 2f4e73ef2..35a79e30a 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/RefreshRateSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/RefreshRateSettingsUI.kt @@ -38,7 +38,7 @@ fun RefreshRateSettingsUI( ) { val context = LocalContext.current val view = LocalView.current - val isEnabled = viewModel.isShizukuPermissionGranted.value + val isEnabled = if (viewModel.isRootEnabled.value) viewModel.isRootPermissionGranted.value else viewModel.isShizukuPermissionGranted.value val isFixedMode = viewModel.refreshRateMode.value == RefreshRateUtils.MODE_FIXED val systemLabel = stringResource(R.string.refresh_rate_system_default) @@ -172,7 +172,10 @@ fun RefreshRateSettingsUI( } } else { Text( - text = stringResource(R.string.msg_refresh_rate_permission_required), + text = stringResource( + if (viewModel.isRootEnabled.value) R.string.msg_refresh_rate_root_permission_required + else R.string.msg_refresh_rate_permission_required + ), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.error, modifier = Modifier @@ -181,7 +184,11 @@ fun RefreshRateSettingsUI( ) Button( onClick = { - viewModel.requestShizukuPermission() + if (viewModel.isRootEnabled.value) { + viewModel.check(context) + } else { + viewModel.requestShizukuPermission() + } HapticUtil.performSliderHaptic(view) }, colors = ButtonDefaults.filledTonalButtonColors(), diff --git a/app/src/main/java/com/sameerasw/essentials/utils/PermissionUIHelper.kt b/app/src/main/java/com/sameerasw/essentials/utils/PermissionUIHelper.kt index 241ed33e9..442b45793 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/PermissionUIHelper.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/PermissionUIHelper.kt @@ -209,13 +209,23 @@ object PermissionUIHelper { ) "SHIZUKU" -> PermissionItem( - iconRes = R.drawable.rounded_mode_cool_24, - title = R.string.perm_shizuku_title, - description = R.string.perm_shizuku_desc, + iconRes = if (viewModel.isRootEnabled.value) R.drawable.rounded_numbers_24 else R.drawable.rounded_mode_cool_24, + title = if (viewModel.isRootEnabled.value) R.string.perm_root_title else R.string.perm_shizuku_title, + description = if (viewModel.isRootEnabled.value) R.string.perm_root_desc else R.string.perm_shizuku_desc, dependentFeatures = PermissionRegistry.getFeatures("SHIZUKU"), - actionLabel = if (viewModel.isShizukuPermissionGranted.value) R.string.perm_action_granted else R.string.perm_action_grant, - action = { viewModel.requestShizukuPermission() }, - isGranted = viewModel.isShizukuPermissionGranted.value + actionLabel = if (viewModel.isRootEnabled.value) { + if (viewModel.isRootPermissionGranted.value) R.string.perm_action_granted else R.string.perm_action_grant + } else { + if (viewModel.isShizukuPermissionGranted.value) R.string.perm_action_granted else R.string.perm_action_grant + }, + action = { + if (viewModel.isRootEnabled.value) { + viewModel.check(context) + } else { + viewModel.requestShizukuPermission() + } + }, + isGranted = if (viewModel.isRootEnabled.value) viewModel.isRootPermissionGranted.value else viewModel.isShizukuPermissionGranted.value ) "READ_CALENDAR" -> PermissionItem( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a974629b5..e37916c60 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1485,6 +1485,7 @@ System Remove the custom override and return to the system default refresh behavior Shizuku permission required to adjust refresh rate + Root permission required to adjust refresh rate Refresh Rate Mode Switch between fixed and ranged refresh rate control Fixed Refresh Rate From 2124987abff6d83ed2962874878157d1b72af0dd Mon Sep 17 00:00:00 2001 From: sameerasw Date: Tue, 7 Jul 2026 19:58:27 +0530 Subject: [PATCH 10/13] feat: increase flashlight pulse intensity granularity to 1% with improved rounding #442 --- .../ui/composables/configs/FlashlightPulseSettingsUI.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FlashlightPulseSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FlashlightPulseSettingsUI.kt index a9841d491..6163cb206 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FlashlightPulseSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FlashlightPulseSettingsUI.kt @@ -85,9 +85,9 @@ fun FlashlightPulseSettingsUI( title = stringResource(R.string.flashlight_pulse_max_brightness), value = viewModel.flashlightPulseMaxIntensity.floatValue, onValueChange = { viewModel.setFlashlightPulseMaxIntensity(it) }, - valueRange = 0.05f..1f, - valueFormatter = { "${(it * 100).toInt()}%" }, - increment = 0.05f, + valueRange = 0.01f..1f, + valueFormatter = { "${Math.round(it * 100)}%" }, + increment = 0.01f, modifier = Modifier.highlight(highlightSetting == "flashlight_pulse_max_intensity") ) } From 6f0ebd20c5e610971f2662ec325b25291d905009 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Tue, 7 Jul 2026 20:08:18 +0530 Subject: [PATCH 11/13] feat: add flashlight overheating prevention #404 --- .../data/repository/SettingsRepository.kt | 1 + .../services/handlers/FlashlightHandler.kt | 37 +++++++++++++++++++ .../configs/FlashlightSettingsUI.kt | 7 ++++ .../essentials/viewmodels/MainViewModel.kt | 15 ++++++++ app/src/main/res/values/strings.xml | 2 + 5 files changed, 62 insertions(+) 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 8a5aec8b9..de997eca6 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 @@ -123,6 +123,7 @@ class SettingsRepository(private val context: Context) { const val KEY_FLASHLIGHT_PULSE_MAX_INTENSITY = "flashlight_pulse_max_intensity" const val KEY_FLASHLIGHT_PULSE_DISABLE_ON_DND = "flashlight_pulse_disable_on_dnd" const val KEY_FLASHLIGHT_POCKET_TURN_OFF_ENABLED = "flashlight_pocket_turn_off_enabled" + const val KEY_FLASHLIGHT_OVERHEAT_PREVENTION_ENABLED = "flashlight_overheat_prevention_enabled" const val KEY_SCREEN_LOCKED_SECURITY_ENABLED = "screen_locked_security_enabled" const val KEY_HIDE_SYSTEM_ICONS = "hide_system_icons" diff --git a/app/src/main/java/com/sameerasw/essentials/services/handlers/FlashlightHandler.kt b/app/src/main/java/com/sameerasw/essentials/services/handlers/FlashlightHandler.kt index 80f4fdc69..8fe150c45 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/handlers/FlashlightHandler.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/handlers/FlashlightHandler.kt @@ -51,6 +51,7 @@ class FlashlightHandler( private var primaryCameraId: String? = null private var currentIntensityLevel: Int = 1 private var flashlightJob: Job? = null + private var overheatPreventionJob: Job? = null private var isInternalToggle = false private val NOTIFICATION_ID_FLASHLIGHT = 1010 @@ -105,11 +106,13 @@ class FlashlightHandler( currentIntensityLevel = FlashlightUtil.getDefaultLevel(service, cameraId) updateFlashlightNotification(currentIntensityLevel) } + startOverheatPrevention(cameraId) } else { // Flashlight turned OFF flashlightJob?.cancel() // Stop any ongoing fade-in or fade-out isInternalToggle = false // Reset cancelFlashlightNotification() + stopOverheatPrevention() } } } @@ -121,6 +124,7 @@ class FlashlightHandler( fun unregister() { torchCallback.let { cameraManager.unregisterTorchCallback(it) } primaryCameraId = null + stopOverheatPrevention() } fun handleIntent(intent: Intent) { @@ -657,4 +661,37 @@ class FlashlightHandler( false } } + + private fun startOverheatPrevention(cameraId: String) { + overheatPreventionJob?.cancel() + overheatPreventionJob = scope.launch { + while (isTorchOn) { + kotlinx.coroutines.delay(120000L) + val prefs = service.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE) + val isOverheatEnabled = prefs.getBoolean("flashlight_overheat_prevention_enabled", true) + if (isOverheatEnabled && FlashlightUtil.isIntensitySupported(service, cameraId)) { + val currentLevel = FlashlightUtil.getCurrentLevel(service, cameraId) + val maxLevel = FlashlightUtil.getMaxLevel(service, cameraId) + val limitLevel = (maxLevel * 0.75f).toInt().coerceAtLeast(1) + if (currentLevel > limitLevel) { + FlashlightUtil.fadeFlashlight( + service, + cameraId, + fromLevel = currentLevel, + toLevel = limitLevel, + durationMs = 1000L, + steps = 15 + ) + currentIntensityLevel = limitLevel + updateFlashlightNotification(limitLevel) + } + } + } + } + } + + private fun stopOverheatPrevention() { + overheatPreventionJob?.cancel() + overheatPreventionJob = null + } } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FlashlightSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FlashlightSettingsUI.kt index 3ccea7595..c818dbaa8 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FlashlightSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FlashlightSettingsUI.kt @@ -71,6 +71,13 @@ fun FlashlightSettingsUI( isChecked = viewModel.isFlashlightPocketTurnOffEnabled.value, onCheckedChange = { viewModel.setFlashlightPocketTurnOffEnabled(it, context) } ) + IconToggleItem( + iconRes = R.drawable.rounded_snowflake_24, + title = stringResource(R.string.flashlight_overheat_title), + description = stringResource(R.string.flashlight_overheat_desc), + isChecked = viewModel.isFlashlightOverheatEnabled.value, + onCheckedChange = { viewModel.setFlashlightOverheatEnabled(it, context) } + ) } } 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 ce31a4be6..f47245904 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt @@ -119,6 +119,7 @@ class MainViewModel : ViewModel() { val flashlightPulseMaxIntensity = mutableFloatStateOf(0.5f) val isFlashlightPulseDisableOnDnd = mutableStateOf(true) val isFlashlightPocketTurnOffEnabled = mutableStateOf(false) + val isFlashlightOverheatEnabled = mutableStateOf(true) val isLocationPermissionGranted = mutableStateOf(false) val isBackgroundLocationPermissionGranted = mutableStateOf(false) val isFullScreenIntentPermissionGranted = mutableStateOf(false) @@ -654,6 +655,10 @@ class MainViewModel : ViewModel() { isFlashlightPocketTurnOffEnabled.value = settingsRepository.getBoolean(key) } + SettingsRepository.KEY_FLASHLIGHT_OVERHEAT_PREVENTION_ENABLED -> { + isFlashlightOverheatEnabled.value = settingsRepository.getBoolean(key, true) + } + SettingsRepository.KEY_CIRCLE_TO_SEARCH_GESTURE_ENABLED -> { isCircleToSearchGestureEnabled.value = settingsRepository.getBoolean(key) } @@ -1316,6 +1321,8 @@ class MainViewModel : ViewModel() { ) isFlashlightPocketTurnOffEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_FLASHLIGHT_POCKET_TURN_OFF_ENABLED) + isFlashlightOverheatEnabled.value = + settingsRepository.getBoolean(SettingsRepository.KEY_FLASHLIGHT_OVERHEAT_PREVENTION_ENABLED, true) isPitchBlackThemeEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_PITCH_BLACK_THEME_ENABLED) isEnableUnsupportedFeatures.value = settingsRepository.isEnableUnsupportedFeatures() @@ -3839,6 +3846,14 @@ class MainViewModel : ViewModel() { ) } + fun setFlashlightOverheatEnabled(enabled: Boolean, context: Context) { + isFlashlightOverheatEnabled.value = enabled + settingsRepository.putBoolean( + SettingsRepository.KEY_FLASHLIGHT_OVERHEAT_PREVENTION_ENABLED, + enabled + ) + } + fun setFlashlightFadeEnabled(enabled: Boolean, context: Context) { isFlashlightFadeEnabled.value = enabled settingsRepository.putBoolean(SettingsRepository.KEY_FLASHLIGHT_FADE_ENABLED, enabled) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e37916c60..67f6aa73a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -104,6 +104,8 @@ Even while display is on Pocket detection Turn off flashlight when phone is in your pocket + Overheating prevention + Lower brightness on longer period of use Settings From e3652ef0a929f9eb2ac0a652b941d94382489e77 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Tue, 7 Jul 2026 20:09:37 +0530 Subject: [PATCH 12/13] feat: update flashlight overheat icon to thermometer alert --- .../configs/FlashlightSettingsUI.kt | 2 +- .../drawable/rounded_thermometer_alert_24.xml | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/rounded_thermometer_alert_24.xml diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FlashlightSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FlashlightSettingsUI.kt index c818dbaa8..cb707df60 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FlashlightSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FlashlightSettingsUI.kt @@ -72,7 +72,7 @@ fun FlashlightSettingsUI( onCheckedChange = { viewModel.setFlashlightPocketTurnOffEnabled(it, context) } ) IconToggleItem( - iconRes = R.drawable.rounded_snowflake_24, + iconRes = R.drawable.rounded_thermometer_alert_24, title = stringResource(R.string.flashlight_overheat_title), description = stringResource(R.string.flashlight_overheat_desc), isChecked = viewModel.isFlashlightOverheatEnabled.value, diff --git a/app/src/main/res/drawable/rounded_thermometer_alert_24.xml b/app/src/main/res/drawable/rounded_thermometer_alert_24.xml new file mode 100644 index 000000000..e4783a634 --- /dev/null +++ b/app/src/main/res/drawable/rounded_thermometer_alert_24.xml @@ -0,0 +1,20 @@ + + + + + + From 207e36d0e28ee7152abd5473ce8a43eedd723b28 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Tue, 7 Jul 2026 20:19:29 +0530 Subject: [PATCH 13/13] chore: bump app version to 15.7 (build 51) --- app/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3368ba747..827be7f42 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -29,8 +29,8 @@ android { applicationId = "com.sameerasw.essentials" minSdk = 26 targetSdk = 37 - versionCode = 50 - versionName = "15.6" + versionCode = 51 + versionName = "15.7" val whatsNewCounter = 2 buildConfigField("int", "WHATS_NEW_COUNTER", whatsNewCounter.toString())