Skip to content

Commit 249ca5f

Browse files
authored
Merge pull request #14 from sameerasw/develop
Develop
2 parents d83aea7 + 16f6472 commit 249ca5f

File tree

65 files changed

+2846
-843
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+2846
-843
lines changed

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ android {
1414
applicationId = "com.sameerasw.essentials"
1515
minSdk = 23
1616
targetSdk = 36
17-
versionCode = 7
18-
versionName = "5.1"
17+
versionCode = 8
18+
versionName = "6.0"
1919

2020
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2121
}

app/src/main/AndroidManifest.xml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
xmlns:tools="http://schemas.android.com/tools">
4+
<uses-permission android:name="android.permission.CAMERA" />
5+
<uses-feature android:name="android.hardware.camera.flashlight" android:required="false" />
46

57
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
68
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
@@ -151,6 +153,61 @@
151153
<action android:name="android.service.quicksettings.action.QS_TILE" />
152154
</intent-filter>
153155
</service>
156+
157+
<service
158+
android:name=".services.UiBlurTileService"
159+
android:exported="true"
160+
android:icon="@drawable/rounded_blur_on_24"
161+
android:label="UI Blur"
162+
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
163+
<intent-filter>
164+
<action android:name="android.service.quicksettings.action.QS_TILE" />
165+
</intent-filter>
166+
</service>
167+
168+
<service
169+
android:name=".services.BubblesTileService"
170+
android:exported="true"
171+
android:icon="@drawable/rounded_bubble_24"
172+
android:label="Bubbles"
173+
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
174+
<intent-filter>
175+
<action android:name="android.service.quicksettings.action.QS_TILE" />
176+
</intent-filter>
177+
</service>
178+
179+
<service
180+
android:name=".services.PrivateNotificationsTileService"
181+
android:exported="true"
182+
android:icon="@drawable/rounded_notifications_off_24"
183+
android:label="Sensitive Content"
184+
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
185+
<intent-filter>
186+
<action android:name="android.service.quicksettings.action.QS_TILE" />
187+
</intent-filter>
188+
</service>
189+
190+
<service
191+
android:name=".services.TapToWakeTileService"
192+
android:exported="true"
193+
android:icon="@drawable/rounded_touch_app_24"
194+
android:label="Tap to Wake"
195+
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
196+
<intent-filter>
197+
<action android:name="android.service.quicksettings.action.QS_TILE" />
198+
</intent-filter>
199+
</service>
200+
201+
<service
202+
android:name=".services.AlwaysOnDisplayTileService"
203+
android:exported="true"
204+
android:icon="@drawable/rounded_mobile_text_2_24"
205+
android:label="AOD"
206+
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
207+
<intent-filter>
208+
<action android:name="android.service.quicksettings.action.QS_TILE" />
209+
</intent-filter>
210+
</service>
154211
<service
155212
android:name=".services.EdgeLightingService"
156213
android:exported="false"

app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ import com.sameerasw.essentials.ui.composables.configs.CaffeinateSettingsUI
4141
import com.sameerasw.essentials.ui.composables.configs.ScreenOffWidgetSettingsUI
4242
import com.sameerasw.essentials.ui.composables.configs.EdgeLightingSettingsUI
4343
import com.sameerasw.essentials.ui.composables.configs.SoundModeTileSettingsUI
44+
import com.sameerasw.essentials.ui.composables.configs.QuickSettingsTilesSettingsUI
45+
import com.sameerasw.essentials.ui.composables.configs.FlashlightSettingsUI
46+
import com.sameerasw.essentials.ui.composables.configs.DynamicNightLightSettingsUI
47+
import com.sameerasw.essentials.ui.composables.configs.SnoozeNotificationsSettingsUI
4448
import com.sameerasw.essentials.viewmodels.CaffeinateViewModel
4549
import com.sameerasw.essentials.viewmodels.MainViewModel
4650
import com.sameerasw.essentials.viewmodels.StatusBarIconViewModel
@@ -60,7 +64,11 @@ class FeatureSettingsActivity : ComponentActivity() {
6064
"Caffeinate" to "Keep the screen awake",
6165
"Edge lighting" to "Preview edge lighting effects on new notifications",
6266
"Sound mode tile" to "QS tile to toggle sound mode",
63-
"Link actions" to "Handle links with multiple apps"
67+
"Link actions" to "Handle links with multiple apps",
68+
"Flashlight toggle" to "Toggle flashlight while screen off",
69+
"Dynamic night light" to "Toggle based on current app",
70+
"Snooze system notifications" to "Automatically snooze persistent notifications",
71+
"Quick Settings Tiles" to "All available QS tiles"
6472
)
6573
val description = featureDescriptions[feature] ?: ""
6674
setContent {
@@ -105,6 +113,9 @@ class FeatureSettingsActivity : ComponentActivity() {
105113
"Screen off widget" -> !isAccessibilityEnabled
106114
"Statusbar icons" -> !isWriteSecureSettingsEnabled
107115
"Edge lighting" -> !isOverlayPermissionGranted || !isEdgeLightingAccessibilityEnabled || !isNotificationListenerEnabled
116+
"Flashlight toggle" -> !isAccessibilityEnabled
117+
"Dynamic night light" -> !isAccessibilityEnabled || !isWriteSecureSettingsEnabled
118+
"Snooze system notifications" -> !isNotificationListenerEnabled
108119
else -> false
109120
}
110121
showPermissionSheet = hasMissingPermissions
@@ -182,6 +193,65 @@ class FeatureSettingsActivity : ComponentActivity() {
182193
isGranted = isNotificationListenerEnabled
183194
)
184195
)
196+
"Flashlight toggle" -> listOf(
197+
PermissionItem(
198+
iconRes = R.drawable.rounded_settings_accessibility_24,
199+
title = "Accessibility Service",
200+
description = "Required to intercept volume button presses when the screen is off",
201+
dependentFeatures = PermissionRegistry.getFeatures("ACCESSIBILITY"),
202+
actionLabel = "Enable in Settings",
203+
action = {
204+
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
205+
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
206+
context.startActivity(intent)
207+
},
208+
isGranted = isAccessibilityEnabled
209+
)
210+
)
211+
"Dynamic night light" -> listOf(
212+
PermissionItem(
213+
iconRes = R.drawable.rounded_settings_accessibility_24,
214+
title = "Accessibility Service",
215+
description = "Needed to monitor foreground applications.",
216+
dependentFeatures = PermissionRegistry.getFeatures("ACCESSIBILITY"),
217+
actionLabel = "Enable Service",
218+
action = {
219+
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
220+
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
221+
context.startActivity(intent)
222+
},
223+
isGranted = isAccessibilityEnabled
224+
),
225+
PermissionItem(
226+
iconRes = R.drawable.rounded_security_24,
227+
title = "Write Secure Settings",
228+
description = "Needed to toggle Night Light. Grant via ADB or root.",
229+
dependentFeatures = PermissionRegistry.getFeatures("WRITE_SECURE_SETTINGS"),
230+
actionLabel = "Copy ADB",
231+
action = {
232+
val adbCommand = "adb shell pm grant com.sameerasw.essentials android.permission.WRITE_SECURE_SETTINGS"
233+
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
234+
val clip = ClipData.newPlainText("adb_command", adbCommand)
235+
clipboard.setPrimaryClip(clip)
236+
},
237+
secondaryActionLabel = "Check",
238+
secondaryAction = {
239+
viewModel.isWriteSecureSettingsEnabled.value = viewModel.canWriteSecureSettings(context)
240+
},
241+
isGranted = isWriteSecureSettingsEnabled
242+
)
243+
)
244+
"Snooze system notifications" -> listOf(
245+
PermissionItem(
246+
iconRes = R.drawable.rounded_snooze_24,
247+
title = "Notification Listener",
248+
description = "Required to detect and snooze notifications",
249+
dependentFeatures = PermissionRegistry.getFeatures("NOTIFICATION_LISTENER"),
250+
actionLabel = if (isNotificationListenerEnabled) "Permission granted" else "Grant listener",
251+
action = { viewModel.requestNotificationListenerPermission(context) },
252+
isGranted = isNotificationListenerEnabled
253+
)
254+
)
185255
else -> emptyList()
186256
}
187257

@@ -253,6 +323,29 @@ class FeatureSettingsActivity : ComponentActivity() {
253323
"Sound mode tile" -> {
254324
SoundModeTileSettingsUI(modifier = Modifier.padding(top = 16.dp))
255325
}
326+
"Flashlight toggle" -> {
327+
FlashlightSettingsUI(
328+
viewModel = viewModel,
329+
modifier = Modifier.padding(top = 16.dp)
330+
)
331+
}
332+
"Dynamic night light" -> {
333+
DynamicNightLightSettingsUI(
334+
viewModel = viewModel,
335+
modifier = Modifier.padding(top = 16.dp)
336+
)
337+
}
338+
"Snooze system notifications" -> {
339+
SnoozeNotificationsSettingsUI(
340+
viewModel = viewModel,
341+
modifier = Modifier.padding(top = 16.dp)
342+
)
343+
}
344+
"Quick Settings Tiles" -> {
345+
QuickSettingsTilesSettingsUI(
346+
modifier = Modifier.padding(top = 16.dp)
347+
)
348+
}
256349
"Link actions" -> {
257350
setContent {
258351
EssentialsTheme {

app/src/main/java/com/sameerasw/essentials/MainActivity.kt

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import android.view.animation.AnticipateInterpolator
88
import androidx.activity.ComponentActivity
99
import androidx.activity.compose.setContent
1010
import androidx.activity.enableEdgeToEdge
11+
import androidx.core.view.WindowCompat
1112
import androidx.activity.viewModels
1213
import androidx.compose.foundation.layout.padding
1314
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -33,56 +34,77 @@ class MainActivity : ComponentActivity() {
3334
private var isAppReady = false
3435

3536
override fun onCreate(savedInstanceState: Bundle?) {
36-
super.onCreate(savedInstanceState)
37-
3837
// Install and configure the splash screen
3938
val splashScreen = installSplashScreen()
4039

40+
super.onCreate(savedInstanceState)
41+
4142
// Keep splash screen visible while app is loading
4243
splashScreen.setKeepOnScreenCondition { !isAppReady }
4344

4445
// Customize the exit animation - scale up and fade out
46+
// Safe implementation for OEM devices that may not provide iconView
4547
splashScreen.setOnExitAnimationListener { splashScreenViewProvider ->
46-
val splashScreenView = splashScreenViewProvider.view
47-
val splashIcon = splashScreenViewProvider.iconView
48+
try {
49+
val splashScreenView = splashScreenViewProvider.view
50+
val splashIcon = splashScreenViewProvider.iconView
4851

49-
// Fade out animation for the splash screen view
50-
val fadeOut = ObjectAnimator.ofFloat(splashScreenView, "alpha", 1f, 0f).apply {
51-
interpolator = AnticipateInterpolator()
52-
duration = 750
53-
}
54-
fadeOut.doOnEnd {
55-
splashScreenViewProvider.remove()
56-
}
57-
58-
// Only animate the icon if it exists (some OEM devices may not have it)
59-
if (splashIcon != null) {
60-
// Scale down animation
61-
val scaleUp = ObjectAnimator.ofFloat(splashIcon, "scaleX", 1f, 0.5f).apply {
52+
// Animate the splash screen view fade out
53+
val fadeOut = ObjectAnimator.ofFloat(splashScreenView, "alpha", 1f, 0f).apply {
6254
interpolator = AnticipateInterpolator()
6355
duration = 750
6456
}
65-
66-
val scaleUpY = ObjectAnimator.ofFloat(splashIcon, "scaleY", 1f, 0.5f).apply {
67-
interpolator = AnticipateInterpolator()
68-
duration = 750
57+
fadeOut.doOnEnd {
58+
splashScreenViewProvider.remove()
6959
}
7060

71-
// rotate
72-
val rotate360 = ObjectAnimator.ofFloat(splashIcon, "rotation", 0f,-90f).apply {
73-
interpolator = AnticipateInterpolator()
74-
duration = 750
61+
// Safely animate the icon if it exists
62+
// Known issue: Some OEM devices (Samsung One UI 8, Xiaomi on Android 16)
63+
// may not provide iconView, causing NullPointerException
64+
try {
65+
@Suppress("SENSELESS_COMPARISON")
66+
if (splashIcon != null) {
67+
// Scale down animation
68+
val scaleUp = ObjectAnimator.ofFloat(splashIcon, "scaleX", 1f, 0.5f).apply {
69+
interpolator = AnticipateInterpolator()
70+
duration = 750
71+
}
72+
73+
val scaleUpY = ObjectAnimator.ofFloat(splashIcon, "scaleY", 1f, 0.5f).apply {
74+
interpolator = AnticipateInterpolator()
75+
duration = 750
76+
}
77+
78+
// rotate
79+
val rotate360 = ObjectAnimator.ofFloat(splashIcon, "rotation", 0f, -90f).apply {
80+
interpolator = AnticipateInterpolator()
81+
duration = 750
82+
}
83+
84+
scaleUp.start()
85+
scaleUpY.start()
86+
rotate360.start()
87+
} else {
88+
Log.w("SplashScreen", "iconView is null - OEM device detected")
89+
}
90+
} catch (e: NullPointerException) {
91+
// Handle the edge case where iconView becomes null between check and animation
92+
Log.w("SplashScreen", "NullPointerException on iconView animation - likely OEM device", e)
7593
}
7694

77-
scaleUp.start()
78-
scaleUpY.start()
79-
rotate360.start()
95+
fadeOut.start()
96+
} catch (e: Exception) {
97+
// Fallback for any unexpected exceptions during animation
98+
Log.e("SplashScreen", "Exception during splash screen animation", e)
99+
try {
100+
splashScreenViewProvider.remove()
101+
} catch (e2: Exception) {
102+
Log.e("SplashScreen", "Exception during splash screen removal", e2)
103+
}
80104
}
81-
82-
fadeOut.start()
83105
}
84106

85-
enableEdgeToEdge()
107+
WindowCompat.setDecorFitsSystemWindows(window, false)
86108

87109
Log.d("MainActivity", "onCreate with action: ${intent?.action}")
88110

app/src/main/java/com/sameerasw/essentials/PermissionRegistry.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@ fun initPermissionRegistry() {
1616
// Accessibility permission
1717
PermissionRegistry.register("ACCESSIBILITY", "Screen off widget")
1818
PermissionRegistry.register("ACCESSIBILITY", "Edge lighting")
19+
PermissionRegistry.register("ACCESSIBILITY", "Flashlight toggle")
20+
PermissionRegistry.register("ACCESSIBILITY", "Dynamic night light")
1921

2022
// Write secure settings permission
2123
PermissionRegistry.register("WRITE_SECURE_SETTINGS", "Statusbar icons")
2224
PermissionRegistry.register("WRITE_SECURE_SETTINGS", "Sound Mode")
25+
PermissionRegistry.register("WRITE_SECURE_SETTINGS", "Dynamic night light")
2326

2427
// Shizuku permission
2528
PermissionRegistry.register("SHIZUKU", "Maps power saving mode")

0 commit comments

Comments
 (0)