diff --git a/app/src/main/java/com/brittytino/patchwork/FeatureSettingsActivity.kt b/app/src/main/java/com/brittytino/patchwork/FeatureSettingsActivity.kt
index e3a91e3..dca2866 100644
--- a/app/src/main/java/com/brittytino/patchwork/FeatureSettingsActivity.kt
+++ b/app/src/main/java/com/brittytino/patchwork/FeatureSettingsActivity.kt
@@ -74,6 +74,11 @@ import com.brittytino.patchwork.ui.composables.configs.ScreenLockedSecuritySetti
import com.brittytino.patchwork.ui.composables.configs.KeyboardSettingsUI
import com.brittytino.patchwork.utils.HapticUtil
import com.brittytino.patchwork.domain.registry.FeatureRegistry
+import com.brittytino.patchwork.ui.screens.AppBehaviorScreen
+import com.brittytino.patchwork.ui.screens.AppCooldownScreen
+import com.brittytino.patchwork.ui.screens.IdleAppScreen
+import com.brittytino.patchwork.ui.screens.ActionHistoryScreen
+import com.brittytino.patchwork.ui.screens.SystemSnapshotsScreen
@OptIn(ExperimentalMaterial3Api::class)
class FeatureSettingsActivity : FragmentActivity() {
@@ -178,6 +183,7 @@ class FeatureSettingsActivity : FragmentActivity() {
val isOverlayPermissionGranted by viewModel.isOverlayPermissionGranted
val isNotificationLightingAccessibilityEnabled by viewModel.isNotificationLightingAccessibilityEnabled
val isNotificationListenerEnabled by viewModel.isNotificationListenerEnabled
+ val isUsageStatsPermissionGranted by viewModel.isUsageStatsPermissionGranted
// FAB State for Notification Lighting
var fabExpanded by remember { mutableStateOf(true) }
@@ -190,7 +196,7 @@ class FeatureSettingsActivity : FragmentActivity() {
}
// Show permission sheet if feature has missing permissions
- LaunchedEffect(featureId, isAccessibilityEnabled, isWriteSecureSettingsEnabled, isOverlayPermissionGranted, isNotificationLightingAccessibilityEnabled, isNotificationListenerEnabled) {
+ LaunchedEffect(featureId, isAccessibilityEnabled, isWriteSecureSettingsEnabled, isOverlayPermissionGranted, isNotificationLightingAccessibilityEnabled, isNotificationListenerEnabled, isUsageStatsPermissionGranted) {
val hasMissingPermissions = when (featureId) {
"Screen off widget" -> !isAccessibilityEnabled
"Statusbar icons" -> !isWriteSecureSettingsEnabled
@@ -203,6 +209,13 @@ class FeatureSettingsActivity : FragmentActivity() {
"Freeze" -> !com.brittytino.patchwork.utils.ShellUtils.hasPermission(context)
"Location reached" -> !viewModel.isLocationPermissionGranted.value || !viewModel.isBackgroundLocationPermissionGranted.value
"Quick settings tiles" -> !viewModel.isWriteSettingsEnabled.value
+
+ // New Features Check
+ "App Behavior Controller" -> !isAccessibilityEnabled
+ "Smart App Cooldown" -> !isAccessibilityEnabled || !isOverlayPermissionGranted
+ "Idle App Auto-Action" -> !isUsageStatsPermissionGranted
+ "System State Snapshots" -> !isWriteSecureSettingsEnabled
+
else -> false
}
showPermissionSheet = hasMissingPermissions
@@ -290,9 +303,95 @@ class FeatureSettingsActivity : FragmentActivity() {
action = {
context.startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
},
+ isGranted = isAccessibilityEnabled ),
+ PermissionItem(
+ iconRes = R.drawable.rounded_security_24,
+ title = R.string.perm_write_secure_title,
+ description = R.string.perm_write_secure_desc_night_light,
+ dependentFeatures = PermissionRegistry.getFeatures("WRITE_SECURE_SETTINGS"),
+ actionLabel = R.string.perm_action_copy_adb,
+ action = {
+ val adbCommand = "adb shell pm grant com.brittytino.patchwork android.permission.WRITE_SECURE_SETTINGS"
+ val clipboard = context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
+ val clip = ClipData.newPlainText("adb_command", adbCommand)
+ clipboard.setPrimaryClip(clip)
+ },
+ secondaryActionLabel = R.string.perm_action_check,
+ secondaryAction = {
+ viewModel.isWriteSecureSettingsEnabled.value = viewModel.canWriteSecureSettings(context)
+ },
+ isGranted = isWriteSecureSettingsEnabled
+ )
+ )
+ "App Behavior Controller" -> listOf(
+ PermissionItem(
+ iconRes = R.drawable.rounded_settings_accessibility_24,
+ title = R.string.perm_accessibility_title,
+ description = R.string.perm_accessibility_desc_common,
+ dependentFeatures = PermissionRegistry.getFeatures("ACCESSIBILITY"),
+ actionLabel = R.string.perm_action_enable,
+ action = { context.startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)) },
isGranted = isAccessibilityEnabled
)
)
+ "Smart App Cooldown" -> listOf(
+ PermissionItem(
+ iconRes = R.drawable.rounded_settings_accessibility_24,
+ title = R.string.perm_accessibility_title,
+ description = R.string.perm_accessibility_desc_common,
+ dependentFeatures = PermissionRegistry.getFeatures("ACCESSIBILITY"),
+ actionLabel = R.string.perm_action_enable,
+ action = { context.startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)) },
+ isGranted = isAccessibilityEnabled
+ ),
+ PermissionItem(
+ iconRes = R.drawable.rounded_magnify_fullscreen_24,
+ title = R.string.perm_overlay_title,
+ description = R.string.perm_overlay_desc,
+ dependentFeatures = PermissionRegistry.getFeatures("DRAW_OVERLAYS"),
+ actionLabel = R.string.perm_action_grant,
+ action = {
+ val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:${context.packageName}"))
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ context.startActivity(intent)
+ },
+ isGranted = isOverlayPermissionGranted
+ )
+ )
+ "Idle App Auto-Action" -> listOf(
+ PermissionItem(
+ iconRes = R.drawable.rounded_info_24,
+ title = R.string.feat_idle_app_title,
+ description = R.string.feat_idle_app_desc,
+ dependentFeatures = PermissionRegistry.getFeatures("USAGE_STATS"),
+ actionLabel = R.string.perm_action_grant,
+ action = {
+ val intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ context.startActivity(intent)
+ },
+ isGranted = isUsageStatsPermissionGranted
+ )
+ )
+ "System State Snapshots" -> listOf(
+ PermissionItem(
+ iconRes = R.drawable.rounded_security_24,
+ title = R.string.perm_write_secure_title,
+ description = R.string.perm_write_secure_desc_common,
+ dependentFeatures = PermissionRegistry.getFeatures("WRITE_SECURE_SETTINGS"),
+ actionLabel = R.string.perm_action_copy_adb,
+ action = {
+ val adbCommand = "adb shell pm grant com.brittytino.patchwork android.permission.WRITE_SECURE_SETTINGS"
+ val clipboard = context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
+ val clip = ClipData.newPlainText("adb_command", adbCommand)
+ clipboard.setPrimaryClip(clip)
+ },
+ secondaryActionLabel = R.string.perm_action_check,
+ secondaryAction = {
+ viewModel.isWriteSecureSettingsEnabled.value = viewModel.canWriteSecureSettings(context)
+ },
+ isGranted = isWriteSecureSettingsEnabled )
+ )
"Dynamic night light" -> listOf(
PermissionItem(
iconRes = R.drawable.rounded_settings_accessibility_24,
@@ -462,150 +561,168 @@ class FeatureSettingsActivity : FragmentActivity() {
}
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
- Scaffold(
- contentWindowInsets = androidx.compose.foundation.layout.WindowInsets(0, 0, 0, 0),
- modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
- containerColor = MaterialTheme.colorScheme.surfaceContainer,
- topBar = {
- ReusableTopAppBar(
- title = if (featureObj != null) stringResource(featureObj.title) else featureId,
- hasBack = true,
- hasSearch = false,
- onBackClick = { finish() },
- scrollBehavior = scrollBehavior,
- subtitle = if (featureObj != null) stringResource(featureObj.description) else "",
- isBeta = featureObj?.isBeta ?: false
- )
- },
- floatingActionButton = {
- if (featureId == "Notification lighting") {
- ExtendedFloatingActionButton(
- onClick = {
- HapticUtil.performVirtualKeyHaptic(view)
- viewModel.triggerNotificationLighting(context)
- },
- expanded = fabExpanded,
- icon = { Icon(painter = painterResource(id = R.drawable.rounded_play_arrow_24), contentDescription = null) },
- text = { Text(stringResource(R.string.action_preview)) },
- modifier = Modifier.height(64.dp)
- )
- }
+
+ // Check if this is a full-screen feature that manages its own Scaffold
+ val isFullScreenFeature = featureId == "App Behavior Controller" ||
+ featureId == "Smart App Cooldown" ||
+ featureId == "Idle App Auto-Action" ||
+ featureId == "Action History Timeline" ||
+ featureId == "System State Snapshots"
+
+ if (isFullScreenFeature) {
+ when (featureId) {
+ "App Behavior Controller" -> AppBehaviorScreen()
+ "Smart App Cooldown" -> AppCooldownScreen()
+ "Idle App Auto-Action" -> IdleAppScreen()
+ "Action History Timeline" -> ActionHistoryScreen(onNavigateBack = { finish() })
+ "System State Snapshots" -> SystemSnapshotsScreen(onNavigateBack = { finish() })
}
- ) { innerPadding ->
- val hasScroll = featureId != "Sound mode tile"
- Column(
- modifier = Modifier
- .padding(innerPadding)
- .fillMaxSize()
- .then(if (hasScroll) Modifier.verticalScroll(rememberScrollState()) else Modifier)
- ) {
- when (featureId) {
- "Screen off widget" -> {
- ScreenOffWidgetSettingsUI(
- viewModel = viewModel,
- selectedHaptic = selectedHaptic,
- onHapticSelected = { type -> selectedHaptic = type },
- vibrator = vibrator,
- prefs = prefs,
- modifier = Modifier.padding(top = 16.dp),
- highlightSetting = highlightSetting
- )
- }
- "Statusbar icons" -> {
- StatusBarIconSettingsUI(
- viewModel = statusBarViewModel,
- modifier = Modifier.padding(top = 16.dp),
- highlightSetting = highlightSetting
- )
- }
- "Caffeinate" -> {
- CaffeinateSettingsUI(
- viewModel = caffeinateViewModel,
- modifier = Modifier.padding(top = 16.dp),
- highlightSetting = highlightSetting
- )
- }
- "Notification lighting" -> {
- NotificationLightingSettingsUI(
- viewModel = viewModel,
- modifier = Modifier.padding(top = 16.dp),
- highlightSetting = highlightSetting
- )
- }
- "Sound mode tile" -> {
- SoundModeTileSettingsUI(
- modifier = Modifier.padding(top = 16.dp),
- highlightSetting = highlightSetting
- )
- }
- "Button remap" -> {
- ButtonRemapSettingsUI(
- viewModel = viewModel,
- modifier = Modifier.padding(top = 16.dp),
- highlightSetting = highlightSetting
- )
- }
- "Dynamic night light" -> {
- DynamicNightLightSettingsUI(
- viewModel = viewModel,
- modifier = Modifier.padding(top = 16.dp),
- highlightSetting = highlightSetting
- )
- }
- "Snooze system notifications" -> {
- SnoozeNotificationsSettingsUI(
- viewModel = viewModel,
- modifier = Modifier.padding(top = 16.dp),
- highlightSetting = highlightSetting
- )
- }
- "Screen locked security" -> {
- ScreenLockedSecuritySettingsUI(
- viewModel = viewModel,
- modifier = Modifier.padding(top = 16.dp),
- highlightSetting = highlightSetting
- )
- }
- "App lock" -> {
- AppLockSettingsUI(
- viewModel = viewModel,
- highlightKey = highlightSetting
- )
- }
- "Freeze" -> {
- com.brittytino.patchwork.ui.composables.configs.FreezeSettingsUI(
- viewModel = viewModel,
- modifier = Modifier.padding(top = 16.dp),
- highlightKey = highlightSetting
- )
- }
- "Quick settings tiles" -> {
- QuickSettingsTilesSettingsUI(
- modifier = Modifier.padding(top = 16.dp),
- highlightSetting = highlightSetting
- )
- }
- "Location reached" -> {
- LocationReachedSettingsUI(
- mainViewModel = viewModel,
- modifier = Modifier.padding(top = 16.dp),
- highlightSetting = highlightSetting
- )
- }
- "System Keyboard" -> {
- KeyboardSettingsUI(
- viewModel = viewModel,
- modifier = Modifier.padding(top = 16.dp),
- highlightSetting = highlightSetting
+ } else {
+ Scaffold(
+ contentWindowInsets = androidx.compose.foundation.layout.WindowInsets(0, 0, 0, 0),
+ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+ containerColor = MaterialTheme.colorScheme.surfaceContainer,
+ topBar = {
+ ReusableTopAppBar(
+ title = if (featureObj != null) stringResource(featureObj.title) else featureId,
+ hasBack = true,
+ hasSearch = false,
+ onBackClick = { finish() },
+ scrollBehavior = scrollBehavior,
+ subtitle = if (featureObj != null) stringResource(featureObj.description) else "",
+ isBeta = featureObj?.isBeta ?: false
+ )
+ },
+ floatingActionButton = {
+ if (featureId == "Notification lighting") {
+ ExtendedFloatingActionButton(
+ onClick = {
+ HapticUtil.performVirtualKeyHaptic(view)
+ viewModel.triggerNotificationLighting(context)
+ },
+ expanded = fabExpanded,
+ icon = { Icon(painter = painterResource(id = R.drawable.rounded_play_arrow_24), contentDescription = null) },
+ text = { Text(stringResource(R.string.action_preview)) },
+ modifier = Modifier.height(64.dp)
)
}
- "Batteries" -> {
- BatteriesSettingsUI(
- viewModel = viewModel,
- modifier = Modifier.padding(top = 16.dp)
- )
+ }
+ ) { innerPadding ->
+ val hasScroll = featureId != "Sound mode tile"
+ Column(
+ modifier = Modifier
+ .padding(innerPadding)
+ .fillMaxSize()
+ .then(if (hasScroll) Modifier.verticalScroll(rememberScrollState()) else Modifier)
+ ) {
+ when (featureId) {
+ "Screen off widget" -> {
+ ScreenOffWidgetSettingsUI(
+ viewModel = viewModel,
+ selectedHaptic = selectedHaptic,
+ onHapticSelected = { type -> selectedHaptic = type },
+ vibrator = vibrator,
+ prefs = prefs,
+ modifier = Modifier.padding(top = 16.dp),
+ highlightSetting = highlightSetting
+ )
+ }
+ "Statusbar icons" -> {
+ StatusBarIconSettingsUI(
+ viewModel = statusBarViewModel,
+ modifier = Modifier.padding(top = 16.dp),
+ highlightSetting = highlightSetting
+ )
+ }
+ "Caffeinate" -> {
+ CaffeinateSettingsUI(
+ viewModel = caffeinateViewModel,
+ modifier = Modifier.padding(top = 16.dp),
+ highlightSetting = highlightSetting
+ )
+ }
+ "Notification lighting" -> {
+ NotificationLightingSettingsUI(
+ viewModel = viewModel,
+ modifier = Modifier.padding(top = 16.dp),
+ highlightSetting = highlightSetting
+ )
+ }
+ "Sound mode tile" -> {
+ SoundModeTileSettingsUI(
+ modifier = Modifier.padding(top = 16.dp),
+ highlightSetting = highlightSetting
+ )
+ }
+ "Button remap" -> {
+ ButtonRemapSettingsUI(
+ viewModel = viewModel,
+ modifier = Modifier.padding(top = 16.dp),
+ highlightSetting = highlightSetting
+ )
+ }
+ "Dynamic night light" -> {
+ DynamicNightLightSettingsUI(
+ viewModel = viewModel,
+ modifier = Modifier.padding(top = 16.dp),
+ highlightSetting = highlightSetting
+ )
+ }
+ "Snooze system notifications" -> {
+ SnoozeNotificationsSettingsUI(
+ viewModel = viewModel,
+ modifier = Modifier.padding(top = 16.dp),
+ highlightSetting = highlightSetting
+ )
+ }
+ "Screen locked security" -> {
+ ScreenLockedSecuritySettingsUI(
+ viewModel = viewModel,
+ modifier = Modifier.padding(top = 16.dp),
+ highlightSetting = highlightSetting
+ )
+ }
+ "App lock" -> {
+ AppLockSettingsUI(
+ viewModel = viewModel,
+ highlightKey = highlightSetting
+ )
+ }
+ "Freeze" -> {
+ com.brittytino.patchwork.ui.composables.configs.FreezeSettingsUI(
+ viewModel = viewModel,
+ modifier = Modifier.padding(top = 16.dp),
+ highlightKey = highlightSetting
+ )
+ }
+ "Quick settings tiles" -> {
+ QuickSettingsTilesSettingsUI(
+ modifier = Modifier.padding(top = 16.dp),
+ highlightSetting = highlightSetting
+ )
+ }
+ "Location reached" -> {
+ LocationReachedSettingsUI(
+ mainViewModel = viewModel,
+ modifier = Modifier.padding(top = 16.dp),
+ highlightSetting = highlightSetting
+ )
+ }
+ "System Keyboard" -> {
+ KeyboardSettingsUI(
+ viewModel = viewModel,
+ modifier = Modifier.padding(top = 16.dp),
+ highlightSetting = highlightSetting
+ )
+ }
+ "Batteries" -> {
+ BatteriesSettingsUI(
+ viewModel = viewModel,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ }
+ // else -> default UI (optional cleanup)
}
- // else -> default UI (optional cleanup)
}
}
}
diff --git a/app/src/main/java/com/brittytino/patchwork/MainActivity.kt b/app/src/main/java/com/brittytino/patchwork/MainActivity.kt
index 3357659..27ed6b5 100644
--- a/app/src/main/java/com/brittytino/patchwork/MainActivity.kt
+++ b/app/src/main/java/com/brittytino/patchwork/MainActivity.kt
@@ -64,6 +64,11 @@ class MainActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Install and configure the splash screen
val splashScreen = installSplashScreen()
+
+ // Force splash screen to dismiss after 2 seconds no matter what
+ // to prevent getting stuck if Compose has an issue on this device/OS
+ window.decorView.postDelayed({ isAppReady = true }, 2000)
+ splashScreen.setKeepOnScreenCondition { !isAppReady }
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
@@ -73,74 +78,6 @@ class MainActivity : FragmentActivity() {
window.isNavigationBarContrastEnforced = false
}
- // Keep splash screen visible while app is loading
- splashScreen.setKeepOnScreenCondition { !isAppReady }
-
- // Customize the exit animation - scale up and fade out
- // Safe implementation for OEM devices that may not provide iconView
- splashScreen.setOnExitAnimationListener { splashScreenViewProvider ->
- try {
- val splashScreenView = splashScreenViewProvider.view
- val splashIcon = try { splashScreenViewProvider.iconView } catch (e: Exception) { null }
-
- // Animate the splash screen view fade out
- val fadeOut = ObjectAnimator.ofFloat(splashScreenView, "alpha", 1f, 0f).apply {
- interpolator = AnticipateInterpolator()
- duration = 750
- }
- fadeOut.doOnEnd {
- splashScreenViewProvider.remove()
- // Re-apply edge to edge AFTER the splash screen view is removed
- // to ensure it's not overridden by splash screen cleanup
- enableEdgeToEdge()
- }
-
- // Safely animate the icon if it exists
- // Known issue: Some OEM devices (Samsung One UI 8, Xiaomi on Android 16)
- // may not provide iconView, causing NullPointerException
- try {
- @Suppress("SENSELESS_COMPARISON")
- if (splashIcon != null) {
- // Scale down animation
- val scaleUp = ObjectAnimator.ofFloat(splashIcon, "scaleX", 1f, 0.5f).apply {
- interpolator = AnticipateInterpolator()
- duration = 750
- }
-
- val scaleUpY = ObjectAnimator.ofFloat(splashIcon, "scaleY", 1f, 0.5f).apply {
- interpolator = AnticipateInterpolator()
- duration = 750
- }
-
- // rotate
- val rotate360 = ObjectAnimator.ofFloat(splashIcon, "rotation", 0f, -90f).apply {
- interpolator = AnticipateInterpolator()
- duration = 750
- }
-
- scaleUp.start()
- scaleUpY.start()
- rotate360.start()
- } else {
- Log.w("SplashScreen", "iconView is null - OEM device detected")
- }
- } catch (e: NullPointerException) {
- // Handle the edge case where iconView becomes null between check and animation
- Log.w("SplashScreen", "NullPointerException on iconView animation - likely OEM device", e)
- }
-
- fadeOut.start()
- } catch (e: Exception) {
- // Fallback for any unexpected exceptions during animation
- Log.e("SplashScreen", "Exception during splash screen animation", e)
- try {
- splashScreenViewProvider.remove()
- } catch (e2: Exception) {
- Log.e("SplashScreen", "Exception during splash screen removal", e2)
- }
- }
- }
-
Log.d("MainActivity", "onCreate with action: ${intent?.action}")
handleLocationIntent(intent)
@@ -148,9 +85,17 @@ class MainActivity : FragmentActivity() {
HapticUtil.initialize(this)
// initialize permission registry
initPermissionRegistry()
- // Initialize viewModel state early for correct initial composition
+
+ // viewModel.check is also called in LaunchedEffect inside setContent.
viewModel.check(this)
+
setContent {
+ // Confirm composition started and mark app as ready
+ LaunchedEffect(Unit) {
+ isAppReady = true
+ Log.d("MainActivity", "Composition started")
+ }
+
val isPitchBlackThemeEnabled by viewModel.isPitchBlackThemeEnabled
PatchworkTheme(pitchBlackTheme = isPitchBlackThemeEnabled) {
val context = LocalContext.current
@@ -286,12 +231,6 @@ class MainActivity : FragmentActivity() {
}
}
}
-
-
- // Mark app as ready after composing (happens very quickly)
- LaunchedEffect(Unit) {
- isAppReady = true
- }
}
}
}
diff --git a/app/src/main/java/com/brittytino/patchwork/domain/registry/FeatureRegistry.kt b/app/src/main/java/com/brittytino/patchwork/domain/registry/FeatureRegistry.kt
index eee2256..5b709f0 100644
--- a/app/src/main/java/com/brittytino/patchwork/domain/registry/FeatureRegistry.kt
+++ b/app/src/main/java/com/brittytino/patchwork/domain/registry/FeatureRegistry.kt
@@ -520,6 +520,70 @@ object FeatureRegistry {
override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {}
},
+ object : Feature(
+ id = "App Behavior Controller",
+ title = R.string.feat_app_behavior_title,
+ iconRes = R.drawable.rounded_settings_accessibility_24,
+ category = R.string.cat_tools,
+ description = R.string.feat_app_behavior_desc,
+ permissionKeys = listOf("ACCESSIBILITY"),
+ showToggle = false
+ ) {
+ override fun isEnabled(viewModel: MainViewModel) = true
+ override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {}
+ },
+
+ object : Feature(
+ id = "Smart App Cooldown",
+ title = R.string.feat_app_cooldown_title,
+ iconRes = R.drawable.rounded_timer_24,
+ category = R.string.cat_tools,
+ description = R.string.feat_app_cooldown_desc,
+ permissionKeys = listOf("ACCESSIBILITY", "DRAW_OVERLAYS"),
+ showToggle = false
+ ) {
+ override fun isEnabled(viewModel: MainViewModel) = true
+ override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {}
+ },
+
+ object : Feature(
+ id = "Idle App Auto-Action",
+ title = R.string.feat_idle_app_title,
+ iconRes = R.drawable.rounded_av_timer_24,
+ category = R.string.cat_tools,
+ description = R.string.feat_idle_app_desc,
+ permissionKeys = listOf("USAGE_STATS"),
+ showToggle = false
+ ) {
+ override fun isEnabled(viewModel: MainViewModel) = true
+ override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {}
+ },
+
+ object : Feature(
+ id = "Action History Timeline",
+ title = R.string.feat_action_history_title,
+ iconRes = R.drawable.rounded_fiber_smart_record_24,
+ category = R.string.cat_tools,
+ description = R.string.feat_action_history_desc,
+ showToggle = false
+ ) {
+ override fun isEnabled(viewModel: MainViewModel) = true
+ override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {}
+ },
+
+ object : Feature(
+ id = "System State Snapshots",
+ title = R.string.feat_system_snapshots_title,
+ iconRes = R.drawable.rounded_save_24,
+ category = R.string.cat_tools,
+ description = R.string.feat_system_snapshots_desc,
+ permissionKeys = listOf("WRITE_SECURE_SETTINGS"),
+ showToggle = false
+ ) {
+ override fun isEnabled(viewModel: MainViewModel) = true
+ override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {}
+ },
+
object : Feature(
id = "Watermarks",
title = R.string.feat_watermark_title,
diff --git a/app/src/main/java/com/brittytino/patchwork/domain/registry/PermissionRegistry.kt b/app/src/main/java/com/brittytino/patchwork/domain/registry/PermissionRegistry.kt
index f89ffb9..a85992b 100644
--- a/app/src/main/java/com/brittytino/patchwork/domain/registry/PermissionRegistry.kt
+++ b/app/src/main/java/com/brittytino/patchwork/domain/registry/PermissionRegistry.kt
@@ -68,4 +68,11 @@ fun initPermissionRegistry() {
// Modify system settings permission
PermissionRegistry.register("WRITE_SETTINGS", R.string.feat_qs_tiles_title)
+
+ // New Features
+ PermissionRegistry.register("USAGE_STATS", R.string.feat_idle_app_title)
+ PermissionRegistry.register("ACCESSIBILITY", R.string.feat_app_behavior_title)
+ PermissionRegistry.register("ACCESSIBILITY", R.string.feat_app_cooldown_title)
+ PermissionRegistry.register("DRAW_OVERLAYS", R.string.feat_app_cooldown_title)
+ PermissionRegistry.register("WRITE_SECURE_SETTINGS", R.string.feat_system_snapshots_title)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/brittytino/patchwork/ui/components/dialogs/AboutSection.kt b/app/src/main/java/com/brittytino/patchwork/ui/components/dialogs/AboutSection.kt
index 167b1d9..8026ccc 100644
--- a/app/src/main/java/com/brittytino/patchwork/ui/components/dialogs/AboutSection.kt
+++ b/app/src/main/java/com/brittytino/patchwork/ui/components/dialogs/AboutSection.kt
@@ -119,7 +119,7 @@ fun AboutSection(
modifier = Modifier.padding(horizontal = 4.dp)
) {
Icon(
- painter = painterResource(id = R.drawable.brand_github),
+ painter = painterResource(id = R.drawable.rounded_globe_24),
contentDescription = null,
modifier = Modifier.size(18.dp)
)
@@ -161,7 +161,7 @@ fun AboutSection(
modifier = Modifier.padding(horizontal = 4.dp)
) {
Icon(
- painter = painterResource(id = R.drawable.brand_telegram),
+ painter = painterResource(id = R.drawable.rounded_globe_24),
contentDescription = null,
modifier = Modifier.size(18.dp)
)
diff --git a/app/src/main/java/com/brittytino/patchwork/ui/components/sheets/BugReportBottomSheet.kt b/app/src/main/java/com/brittytino/patchwork/ui/components/sheets/BugReportBottomSheet.kt
index 8034bc1..4a35357 100644
--- a/app/src/main/java/com/brittytino/patchwork/ui/components/sheets/BugReportBottomSheet.kt
+++ b/app/src/main/java/com/brittytino/patchwork/ui/components/sheets/BugReportBottomSheet.kt
@@ -224,7 +224,7 @@ fun BugReportBottomSheet(
},
modifier = Modifier.fillMaxWidth()
) {
- Icon(painter = painterResource(R.drawable.brand_github), contentDescription = null, modifier = Modifier.size(18.dp))
+ Icon(painter = painterResource(R.drawable.rounded_globe_24), contentDescription = null, modifier = Modifier.size(18.dp))
Spacer(modifier = Modifier.width(8.dp))
Text(stringResource(R.string.action_report_github))
}
diff --git a/app/src/main/java/com/brittytino/patchwork/ui/components/sheets/InstructionsBottomSheet.kt b/app/src/main/java/com/brittytino/patchwork/ui/components/sheets/InstructionsBottomSheet.kt
index 71caf58..90fd06e 100644
--- a/app/src/main/java/com/brittytino/patchwork/ui/components/sheets/InstructionsBottomSheet.kt
+++ b/app/src/main/java/com/brittytino/patchwork/ui/components/sheets/InstructionsBottomSheet.kt
@@ -182,7 +182,7 @@ fun InstructionsBottomSheet(
modifier = Modifier.padding(horizontal = 4.dp)
) {
Icon(
- painter = painterResource(id = R.drawable.brand_github),
+ painter = painterResource(id = R.drawable.rounded_globe_24),
contentDescription = null,
modifier = Modifier.size(18.dp)
)
@@ -222,7 +222,7 @@ fun InstructionsBottomSheet(
modifier = Modifier.padding(horizontal = 4.dp)
) {
Icon(
- painter = painterResource(id = R.drawable.brand_telegram),
+ painter = painterResource(id = R.drawable.rounded_globe_24),
contentDescription = null,
modifier = Modifier.size(18.dp)
)
@@ -336,7 +336,7 @@ fun ExpandableGuideSection(section: InstructionSection) {
shape = RoundedCornerShape(12.dp)
) {
Icon(
- painter = painterResource(id = R.drawable.brand_github),
+ painter = painterResource(id = R.drawable.rounded_globe_24),
contentDescription = null,
modifier = Modifier.size(18.dp)
)
diff --git a/app/src/main/java/com/brittytino/patchwork/ui/components/sheets/UpdateBottomSheet.kt b/app/src/main/java/com/brittytino/patchwork/ui/components/sheets/UpdateBottomSheet.kt
index f2f95cf..0d33b83 100644
--- a/app/src/main/java/com/brittytino/patchwork/ui/components/sheets/UpdateBottomSheet.kt
+++ b/app/src/main/java/com/brittytino/patchwork/ui/components/sheets/UpdateBottomSheet.kt
@@ -157,7 +157,7 @@ fun UpdateBottomSheet(
modifier = Modifier.fillMaxWidth()
) {
Icon(
- painter = painterResource(id = R.drawable.brand_github),
+ painter = painterResource(id = R.drawable.rounded_globe_24),
contentDescription = null,
modifier = Modifier.size(18.dp)
)
diff --git a/app/src/main/java/com/brittytino/patchwork/ui/composables/watermark/WatermarkScreen.kt b/app/src/main/java/com/brittytino/patchwork/ui/composables/watermark/WatermarkScreen.kt
index dd29d80..57bdc13 100644
--- a/app/src/main/java/com/brittytino/patchwork/ui/composables/watermark/WatermarkScreen.kt
+++ b/app/src/main/java/com/brittytino/patchwork/ui/composables/watermark/WatermarkScreen.kt
@@ -967,16 +967,7 @@ private fun LogoCarouselPicker(
modifier: Modifier = Modifier
) {
val logos = listOf(
- R.drawable.apple,
- R.drawable.cmf,
- R.drawable.google,
- R.drawable.moto,
- R.drawable.nothing,
- R.drawable.oppo,
- R.drawable.samsung,
- R.drawable.sony,
- R.drawable.vivo,
- R.drawable.xiaomi
+ R.mipmap.ic_launcher
)
val carouselState = androidx.compose.material3.carousel.rememberCarouselState { logos.size }
diff --git a/app/src/main/java/com/brittytino/patchwork/ui/ime/KeyboardInputView.kt b/app/src/main/java/com/brittytino/patchwork/ui/ime/KeyboardInputView.kt
index 666d132..eb5f417 100644
--- a/app/src/main/java/com/brittytino/patchwork/ui/ime/KeyboardInputView.kt
+++ b/app/src/main/java/com/brittytino/patchwork/ui/ime/KeyboardInputView.kt
@@ -402,11 +402,11 @@ fun KeyboardInputView(
content = {
val functions = remember(isClipboardEnabled) {
val list = mutableListOf(
- R.drawable.ic_emoji to "Emoji",
- R.drawable.ic_undo to "Undo"
+ R.drawable.rounded_heart_smile_24 to "Emoji",
+ R.drawable.rounded_arrow_back_24 to "Undo"
)
if (isClipboardEnabled) {
- list.add(1, R.drawable.ic_clipboard to "Clipboard")
+ list.add(1, R.drawable.rounded_content_paste_24 to "Clipboard")
}
list
}
@@ -644,7 +644,7 @@ fun KeyboardInputView(
.fillMaxHeight()
) {
Icon(
- painter = painterResource(id = R.drawable.key_shift),
+ painter = painterResource(id = R.drawable.rounded_keyboard_arrow_up_24),
contentDescription = "Shift",
modifier = Modifier.size(24.dp),
tint = if (shiftState != ShiftState.OFF) MaterialTheme.colorScheme.onPrimary
diff --git a/app/src/main/java/com/brittytino/patchwork/utils/PermissionUtils.kt b/app/src/main/java/com/brittytino/patchwork/utils/PermissionUtils.kt
index 23ca9cd..4d62cd8 100644
--- a/app/src/main/java/com/brittytino/patchwork/utils/PermissionUtils.kt
+++ b/app/src/main/java/com/brittytino/patchwork/utils/PermissionUtils.kt
@@ -51,6 +51,16 @@ object PermissionUtils {
}
}
+ fun hasUsageStatsPermission(context: Context): Boolean {
+ val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as android.app.AppOpsManager
+ val mode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ appOps.unsafeCheckOpNoThrow(android.app.AppOpsManager.OPSTR_GET_USAGE_STATS, android.os.Process.myUid(), context.packageName)
+ } else {
+ appOps.checkOpNoThrow(android.app.AppOpsManager.OPSTR_GET_USAGE_STATS, android.os.Process.myUid(), context.packageName)
+ }
+ return mode == android.app.AppOpsManager.MODE_ALLOWED
+ }
+
fun isDeviceAdminActive(context: Context): Boolean {
val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val adminComponent = ComponentName(context, SecurityDeviceAdminReceiver::class.java)
diff --git a/app/src/main/java/com/brittytino/patchwork/utils/RootUtils.kt b/app/src/main/java/com/brittytino/patchwork/utils/RootUtils.kt
index fdb8578..6c374e8 100644
--- a/app/src/main/java/com/brittytino/patchwork/utils/RootUtils.kt
+++ b/app/src/main/java/com/brittytino/patchwork/utils/RootUtils.kt
@@ -8,8 +8,18 @@ object RootUtils {
fun isRootAvailable(): Boolean {
return try {
val process = Runtime.getRuntime().exec(arrayOf("sh", "-c", "which su"))
- val exitCode = process.waitFor()
- exitCode == 0
+ // Add a short timeout to prevent hanging the app on problematic devices
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ if (!process.waitFor(2, java.util.concurrent.TimeUnit.SECONDS)) {
+ process.destroyForcibly()
+ return false
+ }
+ } else {
+ // Fallback for older versions (unlikely to be used here but for safety)
+ val exitCode = process.waitFor()
+ return exitCode == 0
+ }
+ process.exitValue() == 0
} catch (e: Exception) {
false
}
@@ -19,8 +29,16 @@ object RootUtils {
// In many root managers, 'su -c id' will return 0 if granted
return try {
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "id"))
- val exitCode = process.waitFor()
- exitCode == 0
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ if (!process.waitFor(2, java.util.concurrent.TimeUnit.SECONDS)) {
+ process.destroyForcibly()
+ return false
+ }
+ } else {
+ val exitCode = process.waitFor()
+ return exitCode == 0
+ }
+ process.exitValue() == 0
} catch (e: Exception) {
false
}
diff --git a/app/src/main/java/com/brittytino/patchwork/viewmodels/MainViewModel.kt b/app/src/main/java/com/brittytino/patchwork/viewmodels/MainViewModel.kt
index dc7141b..4516a5f 100644
--- a/app/src/main/java/com/brittytino/patchwork/viewmodels/MainViewModel.kt
+++ b/app/src/main/java/com/brittytino/patchwork/viewmodels/MainViewModel.kt
@@ -86,6 +86,7 @@ class MainViewModel : ViewModel() {
val isBackgroundLocationPermissionGranted = mutableStateOf(false)
val isFullScreenIntentPermissionGranted = mutableStateOf(false)
val isBluetoothPermissionGranted = mutableStateOf(false)
+ val isUsageStatsPermissionGranted = mutableStateOf(false)
val isBluetoothDevicesEnabled = mutableStateOf(false)
@@ -241,6 +242,7 @@ class MainViewModel : ViewModel() {
isWriteSettingsEnabled.value = PermissionUtils.canWriteSystemSettings(context)
isBluetoothPermissionGranted.value = PermissionUtils.hasBluetoothPermission(context)
+ isUsageStatsPermissionGranted.value = PermissionUtils.hasUsageStatsPermission(context)
settingsRepository.registerOnSharedPreferenceChangeListener(preferenceChangeListener)
@@ -263,12 +265,20 @@ class MainViewModel : ViewModel() {
notificationLightingIndicatorY.value = settingsRepository.getFloat(SettingsRepository.KEY_EDGE_LIGHTING_INDICATOR_Y, 2f)
isRootEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_USE_ROOT)
- if (isRootEnabled.value) {
- isRootAvailable.value = com.brittytino.patchwork.utils.RootUtils.isRootAvailable()
- isRootPermissionGranted.value = com.brittytino.patchwork.utils.RootUtils.isRootPermissionGranted()
- } else {
- isRootAvailable.value = false
- isRootPermissionGranted.value = false
+ viewModelScope.launch(Dispatchers.IO) {
+ if (isRootEnabled.value) {
+ val available = com.brittytino.patchwork.utils.RootUtils.isRootAvailable()
+ val granted = com.brittytino.patchwork.utils.RootUtils.isRootPermissionGranted()
+ withContext(Dispatchers.Main) {
+ isRootAvailable.value = available
+ isRootPermissionGranted.value = granted
+ }
+ } else {
+ withContext(Dispatchers.Main) {
+ isRootAvailable.value = false
+ isRootPermissionGranted.value = false
+ }
+ }
}
notificationLightingIndicatorScale.value = settingsRepository.getFloat(SettingsRepository.KEY_EDGE_LIGHTING_INDICATOR_SCALE, 1.0f)
diff --git a/app/src/main/java/com/brittytino/patchwork/viewmodels/WatermarkViewModel.kt b/app/src/main/java/com/brittytino/patchwork/viewmodels/WatermarkViewModel.kt
index e7e2181..7a62624 100644
--- a/app/src/main/java/com/brittytino/patchwork/viewmodels/WatermarkViewModel.kt
+++ b/app/src/main/java/com/brittytino/patchwork/viewmodels/WatermarkViewModel.kt
@@ -80,22 +80,8 @@ class WatermarkViewModel(
}
private fun detectOemLogo(exif: ExifData): Int? {
- val make = exif.make?.lowercase() ?: ""
- val model = exif.model?.lowercase() ?: ""
-
- return when {
- make.contains("apple") || model.contains("iphone") -> R.drawable.apple
- make.contains("google") || model.contains("pixel") -> R.drawable.google
- make.contains("samsung") -> R.drawable.samsung
- make.contains("xiaomi") || make.contains("redmi") || make.contains("poco") -> R.drawable.xiaomi
- make.contains("oppo") -> R.drawable.oppo
- make.contains("vivo") -> R.drawable.vivo
- make.contains("sony") -> R.drawable.sony
- make.contains("nothing") -> R.drawable.nothing
- make.contains("cmf") -> R.drawable.cmf
- make.contains("motorola") || make.contains("moto") -> R.drawable.moto
- else -> null
- }
+ // Updated to use the new app logo for all watermarks as requested
+ return R.mipmap.ic_launcher
}
fun loadPreview(uri: Uri) {
diff --git a/app/src/main/res/drawable/apple.xml b/app/src/main/res/drawable/apple.xml
deleted file mode 100644
index 1c20b80..0000000
--- a/app/src/main/res/drawable/apple.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/brand_github.xml b/app/src/main/res/drawable/brand_github.xml
deleted file mode 100644
index e26e25c..0000000
--- a/app/src/main/res/drawable/brand_github.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/brand_telegram.xml b/app/src/main/res/drawable/brand_telegram.xml
deleted file mode 100644
index 6c3318e..0000000
--- a/app/src/main/res/drawable/brand_telegram.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/cmf.xml b/app/src/main/res/drawable/cmf.xml
deleted file mode 100644
index 464772b..0000000
--- a/app/src/main/res/drawable/cmf.xml
+++ /dev/null
@@ -1,145 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/google.xml b/app/src/main/res/drawable/google.xml
deleted file mode 100644
index c0496e4..0000000
--- a/app/src/main/res/drawable/google.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_clipboard.xml b/app/src/main/res/drawable/ic_clipboard.xml
deleted file mode 100644
index 2fc6497..0000000
--- a/app/src/main/res/drawable/ic_clipboard.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_emoji.xml b/app/src/main/res/drawable/ic_emoji.xml
deleted file mode 100644
index 7c66b44..0000000
--- a/app/src/main/res/drawable/ic_emoji.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_undo.xml b/app/src/main/res/drawable/ic_undo.xml
deleted file mode 100644
index c7e9edd..0000000
--- a/app/src/main/res/drawable/ic_undo.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/key_shift.xml b/app/src/main/res/drawable/key_shift.xml
deleted file mode 100644
index 0294fd9..0000000
--- a/app/src/main/res/drawable/key_shift.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/moto.xml b/app/src/main/res/drawable/moto.xml
deleted file mode 100644
index e568364..0000000
--- a/app/src/main/res/drawable/moto.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/drawable/nothing.xml b/app/src/main/res/drawable/nothing.xml
deleted file mode 100644
index b4153c1..0000000
--- a/app/src/main/res/drawable/nothing.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/oppo.xml b/app/src/main/res/drawable/oppo.xml
deleted file mode 100644
index 1875ee4..0000000
--- a/app/src/main/res/drawable/oppo.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/samsung.xml b/app/src/main/res/drawable/samsung.xml
deleted file mode 100644
index 7fc874f..0000000
--- a/app/src/main/res/drawable/samsung.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/sony.xml b/app/src/main/res/drawable/sony.xml
deleted file mode 100644
index cded39a..0000000
--- a/app/src/main/res/drawable/sony.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/vivo.xml b/app/src/main/res/drawable/vivo.xml
deleted file mode 100644
index 8d6174f..0000000
--- a/app/src/main/res/drawable/vivo.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/drawable/xiaomi.xml b/app/src/main/res/drawable/xiaomi.xml
deleted file mode 100644
index 95cd9fc..0000000
--- a/app/src/main/res/drawable/xiaomi.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
deleted file mode 100644
index 50ec886..0000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
deleted file mode 100644
index 50ec886..0000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.jpg b/app/src/main/res/mipmap-hdpi/ic_launcher.jpg
new file mode 100644
index 0000000..1d5815e
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.jpg differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
deleted file mode 100644
index f2874b5..0000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.jpg b/app/src/main/res/mipmap-hdpi/ic_launcher_round.jpg
new file mode 100644
index 0000000..1d5815e
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.jpg differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
deleted file mode 100644
index 18eec59..0000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.jpg b/app/src/main/res/mipmap-mdpi/ic_launcher.jpg
new file mode 100644
index 0000000..d68b1b6
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.jpg differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
deleted file mode 100644
index 48acbd6..0000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.jpg b/app/src/main/res/mipmap-mdpi/ic_launcher_round.jpg
new file mode 100644
index 0000000..d68b1b6
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.jpg differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
deleted file mode 100644
index 0a0da75..0000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.jpg b/app/src/main/res/mipmap-xhdpi/ic_launcher.jpg
new file mode 100644
index 0000000..aeecb36
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.jpg differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
deleted file mode 100644
index 4413e81..0000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.jpg b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.jpg
new file mode 100644
index 0000000..aeecb36
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.jpg differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
deleted file mode 100644
index 3d814dd..0000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.jpg b/app/src/main/res/mipmap-xxhdpi/ic_launcher.jpg
new file mode 100644
index 0000000..ec9b73e
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.jpg differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
deleted file mode 100644
index b03744e..0000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.jpg b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.jpg
new file mode 100644
index 0000000..ec9b73e
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.jpg differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 5fbbcff..0000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.jpg b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.jpg
new file mode 100644
index 0000000..4bdb92c
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.jpg differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
deleted file mode 100644
index dd7c598..0000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.jpg b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.jpg
new file mode 100644
index 0000000..4bdb92c
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.jpg differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
deleted file mode 100644
index f9eec84..0000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/values-night/splash.xml b/app/src/main/res/values-night/splash.xml
index f97eb2e..b6ce686 100644
--- a/app/src/main/res/values-night/splash.xml
+++ b/app/src/main/res/values-night/splash.xml
@@ -7,7 +7,7 @@
- false
- @color/app_window_background
- @mipmap/ic_launcher
- - 2000
+ - 1000
- @style/Theme.Patchwork
diff --git a/app/src/main/res/values-v31/splash.xml b/app/src/main/res/values-v31/splash.xml
index b23ad4b..5400b74 100644
--- a/app/src/main/res/values-v31/splash.xml
+++ b/app/src/main/res/values-v31/splash.xml
@@ -5,8 +5,8 @@
- false
- true
- false
- - @drawable/ic_launcher_foreground_light
- - 3000
+ - @mipmap/ic_launcher
+ - 1000
- @style/Theme.Patchwork
diff --git a/app/src/main/res/values/splash.xml b/app/src/main/res/values/splash.xml
index b0c1e72..b6ce686 100644
--- a/app/src/main/res/values/splash.xml
+++ b/app/src/main/res/values/splash.xml
@@ -6,8 +6,8 @@
- true
- false
- @color/app_window_background
- - @drawable/ic_launcher_foreground_light
- - 3000
+ - @mipmap/ic_launcher
+ - 1000
- @style/Theme.Patchwork
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8a03472..0cd3a7d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -319,6 +319,23 @@
View all
Button remap
Remap hardware button actions
+
+
+ App Behavior Controller
+ Define per-app rules for volume, screen, etc.
+
+ Smart App Cooldown
+ Prevent compulsive app reopening with delays
+
+ Idle App Auto-Action
+ Auto-freeze or notify for unused apps
+
+ Action History Timeline
+ View log of all Patchwork actions
+
+ System State Snapshots
+ Save and restore system profiles
+
Dynamic night light
Toggle night light based on app
Screen locked security