Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
66 changes: 49 additions & 17 deletions app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,12 @@ 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
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
Expand Down Expand Up @@ -261,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<Uri?>(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")
Expand All @@ -283,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
}
}

Expand Down Expand Up @@ -325,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(
Expand Down Expand Up @@ -956,7 +985,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)
Expand Down Expand Up @@ -987,7 +1017,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)
Expand Down Expand Up @@ -1026,7 +1057,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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -121,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"
Expand Down Expand Up @@ -286,6 +289,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
Expand Down Expand Up @@ -767,7 +771,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<String, Map<String, Map<String, Any>>> =
Expand Down Expand Up @@ -795,7 +799,7 @@ class SettingsRepository(private val context: Context) {
}

p.edit().apply {
clear()
if(!keepPrefs) clear()

// Restore preserved values
preservedValues.forEach { (key, value) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
}
}
Expand All @@ -121,6 +124,7 @@ class FlashlightHandler(
fun unregister() {
torchCallback.let { cameraManager.unregisterTorchCallback(it) }
primaryCameraId = null
stopOverheatPrevention()
}

fun handleIntent(intent: Intent) {
Expand Down Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -527,20 +528,20 @@ 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,
shape = Shapes.extraSmall
)
.padding(32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Expand All @@ -549,17 +550,29 @@ 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,
shape = Shapes.extraSmall
)
.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 {
Expand Down
Loading