Skip to content

Commit 2226afd

Browse files
authored
Merge pull request #4 from sameerasw/develop
Develop
2 parents 4c5951c + 0eaeb6e commit 2226afd

29 files changed

+1878
-79
lines changed

app/build.gradle.kts

Lines changed: 5 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 = 2
18-
versionName = "2.0"
17+
versionCode = 3
18+
versionName = "3.0"
1919

2020
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2121
}
@@ -65,4 +65,7 @@ dependencies {
6565
// Shizuku
6666
implementation(libs.shizuku.api)
6767
implementation(libs.shizuku.provider)
68+
69+
// Gson for JSON serialization
70+
implementation("com.google.code.gson:gson:2.10.1")
6871
}

app/src/main/AndroidManifest.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
1313
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
1414
<uses-permission android:name="moe.shizuku.manager.permission.API_V23" />
15+
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
1516

1617
<application
18+
android:name=".EssentialsApp"
1719
android:allowBackup="true"
1820
android:dataExtractionRules="@xml/data_extraction_rules"
1921
android:fullBackupContent="@xml/backup_rules"
@@ -60,6 +62,15 @@
6062
android:resource="@xml/accessibility_service_config" />
6163
</service>
6264

65+
<service
66+
android:name=".services.NotificationListener"
67+
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
68+
android:exported="true">
69+
<intent-filter>
70+
<action android:name="android.service.notification.NotificationListenerService" />
71+
</intent-filter>
72+
</service>
73+
6374
<receiver
6475
android:name=".ScreenOffWidgetProvider"
6576
android:exported="false">
@@ -85,6 +96,10 @@
8596
android:name=".services.CaffeinateWakeLockService"
8697
android:exported="false"
8798
android:foregroundServiceType="specialUse" />
99+
<service
100+
android:name=".services.EdgeLightingService"
101+
android:exported="false"
102+
android:foregroundServiceType="specialUse" />
88103

89104
<provider
90105
android:name="rikka.shizuku.ShizukuProvider"
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.sameerasw.essentials
2+
3+
import android.app.Application
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.content.IntentFilter
7+
import android.os.Build
8+
import com.sameerasw.essentials.services.ScreenOffReceiver
9+
import com.sameerasw.essentials.utils.ShizukuUtils
10+
11+
class EssentialsApp : Application() {
12+
private val screenOffReceiver = ScreenOffReceiver()
13+
14+
override fun onCreate() {
15+
super.onCreate()
16+
ShizukuUtils.initialize()
17+
val intentFilter = IntentFilter(Intent.ACTION_SCREEN_OFF)
18+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
19+
registerReceiver(screenOffReceiver, intentFilter, Context.RECEIVER_EXPORTED)
20+
} else {
21+
registerReceiver(screenOffReceiver, intentFilter)
22+
}
23+
}
24+
25+
override fun onTerminate() {
26+
super.onTerminate()
27+
unregisterReceiver(screenOffReceiver)
28+
}
29+
}
30+

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

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
package com.sameerasw.essentials
22

3+
import android.content.ClipData
4+
import android.content.ClipboardManager
5+
import android.content.Context
6+
import android.content.Intent
7+
import android.net.Uri
38
import android.os.Bundle
49
import android.os.Vibrator
10+
import android.provider.Settings
511
import androidx.activity.ComponentActivity
612
import androidx.activity.compose.setContent
713
import androidx.activity.enableEdgeToEdge
@@ -31,9 +37,12 @@ import androidx.lifecycle.viewmodel.compose.viewModel
3137
import com.sameerasw.essentials.ui.composables.configs.StatusBarIconSettingsUI
3238
import com.sameerasw.essentials.ui.composables.configs.CaffeinateSettingsUI
3339
import com.sameerasw.essentials.ui.composables.configs.ScreenOffWidgetSettingsUI
40+
import com.sameerasw.essentials.ui.composables.configs.EdgeLightingSettingsUI
3441
import com.sameerasw.essentials.viewmodels.CaffeinateViewModel
3542
import com.sameerasw.essentials.viewmodels.MainViewModel
3643
import com.sameerasw.essentials.viewmodels.StatusBarIconViewModel
44+
import com.sameerasw.essentials.ui.components.sheets.PermissionItem
45+
import com.sameerasw.essentials.ui.components.sheets.PermissionsBottomSheet
3746

3847
@OptIn(ExperimentalMaterial3Api::class)
3948
class FeatureSettingsActivity : ComponentActivity() {
@@ -45,7 +54,8 @@ class FeatureSettingsActivity : ComponentActivity() {
4554
val featureDescriptions = mapOf(
4655
"Screen off widget" to "Invisible widget to turn the screen off",
4756
"Statusbar icons" to "Control the visibility of statusbar icons",
48-
"Caffeinate" to "Keep the screen awake"
57+
"Caffeinate" to "Keep the screen awake",
58+
"Edge lighting" to "Preview edge lighting effects on new notifications"
4959
)
5060
val description = featureDescriptions[feature] ?: ""
5161
setContent {
@@ -76,6 +86,109 @@ class FeatureSettingsActivity : ComponentActivity() {
7686
)
7787
}
7888

89+
// Permission sheet state
90+
var showPermissionSheet by remember { mutableStateOf(false) }
91+
val isAccessibilityEnabled by viewModel.isAccessibilityEnabled
92+
val isWriteSecureSettingsEnabled by viewModel.isWriteSecureSettingsEnabled
93+
val isOverlayPermissionGranted by viewModel.isOverlayPermissionGranted
94+
val isEdgeLightingAccessibilityEnabled by viewModel.isEdgeLightingAccessibilityEnabled
95+
val isNotificationListenerEnabled by viewModel.isNotificationListenerEnabled
96+
97+
// Show permission sheet if feature has missing permissions
98+
LaunchedEffect(feature, isAccessibilityEnabled, isWriteSecureSettingsEnabled, isOverlayPermissionGranted, isEdgeLightingAccessibilityEnabled, isNotificationListenerEnabled) {
99+
val hasMissingPermissions = when (feature) {
100+
"Screen off widget" -> !isAccessibilityEnabled
101+
"Statusbar icons" -> !isWriteSecureSettingsEnabled
102+
"Edge lighting" -> !isOverlayPermissionGranted || !isEdgeLightingAccessibilityEnabled || !isNotificationListenerEnabled
103+
else -> false
104+
}
105+
showPermissionSheet = hasMissingPermissions
106+
}
107+
108+
if (showPermissionSheet) {
109+
val permissionItems = when (feature) {
110+
"Screen off widget" -> listOf(
111+
PermissionItem(
112+
iconRes = R.drawable.rounded_settings_accessibility_24,
113+
title = "Accessibility",
114+
description = "Required to perform screen off actions via widget",
115+
dependentFeatures = PermissionRegistry.getFeatures("ACCESSIBILITY"),
116+
actionLabel = "Grant Permission",
117+
action = {
118+
context.startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
119+
},
120+
isGranted = isAccessibilityEnabled
121+
)
122+
)
123+
"Statusbar icons" -> listOf(
124+
PermissionItem(
125+
iconRes = R.drawable.rounded_security_24,
126+
title = "Write Secure Settings",
127+
description = "Required to change status bar icon visibility",
128+
dependentFeatures = PermissionRegistry.getFeatures("WRITE_SECURE_SETTINGS"),
129+
actionLabel = "Copy ADB",
130+
action = {
131+
val adbCommand = "adb shell pm grant com.sameerasw.essentials android.permission.WRITE_SECURE_SETTINGS"
132+
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
133+
val clip = ClipData.newPlainText("adb_command", adbCommand)
134+
clipboard.setPrimaryClip(clip)
135+
},
136+
secondaryActionLabel = "Check",
137+
secondaryAction = {
138+
viewModel.isWriteSecureSettingsEnabled.value = viewModel.canWriteSecureSettings(context)
139+
},
140+
isGranted = isWriteSecureSettingsEnabled
141+
)
142+
)
143+
"Edge lighting" -> listOf(
144+
PermissionItem(
145+
iconRes = R.drawable.rounded_magnify_fullscreen_24,
146+
title = "Overlay Permission",
147+
description = "Required to display the edge lighting overlay on the screen",
148+
dependentFeatures = PermissionRegistry.getFeatures("DRAW_OVERLAYS"),
149+
actionLabel = "Grant Permission",
150+
action = {
151+
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:${context.packageName}"))
152+
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
153+
context.startActivity(intent)
154+
},
155+
isGranted = isOverlayPermissionGranted
156+
),
157+
PermissionItem(
158+
iconRes = R.drawable.rounded_settings_accessibility_24,
159+
title = "Accessibility Service",
160+
description = "Required to trigger edge lighting on new notifications",
161+
dependentFeatures = PermissionRegistry.getFeatures("ACCESSIBILITY"),
162+
actionLabel = "Enable in Settings",
163+
action = {
164+
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
165+
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
166+
context.startActivity(intent)
167+
},
168+
isGranted = isEdgeLightingAccessibilityEnabled
169+
),
170+
PermissionItem(
171+
iconRes = R.drawable.rounded_notifications_unread_24,
172+
title = "Notification Listener",
173+
description = "Required to detect new notifications",
174+
dependentFeatures = PermissionRegistry.getFeatures("NOTIFICATION_LISTENER"),
175+
actionLabel = if (isNotificationListenerEnabled) "Permission granted" else "Grant listener",
176+
action = { viewModel.requestNotificationListenerPermission(context) },
177+
isGranted = isNotificationListenerEnabled
178+
)
179+
)
180+
else -> emptyList()
181+
}
182+
183+
if (permissionItems.isNotEmpty()) {
184+
PermissionsBottomSheet(
185+
onDismissRequest = { showPermissionSheet = false },
186+
featureTitle = feature,
187+
permissions = permissionItems
188+
)
189+
}
190+
}
191+
79192
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
80193
Scaffold(
81194
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
@@ -128,6 +241,9 @@ class FeatureSettingsActivity : ComponentActivity() {
128241
modifier = Modifier.padding(top = 16.dp)
129242
)
130243
}
244+
"Edge lighting" -> {
245+
EdgeLightingSettingsUI(viewModel = viewModel, modifier = Modifier.padding(top = 16.dp))
246+
}
131247
else -> {
132248
ScreenOffWidgetSettingsUI(
133249
viewModel = viewModel,
@@ -144,4 +260,4 @@ class FeatureSettingsActivity : ComponentActivity() {
144260
}
145261
}
146262
}
147-
}
263+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class MainActivity : ComponentActivity() {
5757
onSearchClick = { searchRequested = true },
5858
onSettingsClick = { startActivity(Intent(this, SettingsActivity::class.java)) },
5959
scrollBehavior = scrollBehavior,
60-
subtitle = "V$versionName"
60+
subtitle = "v$versionName"
6161
)
6262
}
6363
) { innerPadding ->
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.sameerasw.essentials
2+
3+
object MapsState {
4+
var hasNavigationNotification = false
5+
var isEnabled = false
6+
}
7+

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ fun initPermissionRegistry() {
1717
PermissionRegistry.register("ACCESSIBILITY", "Screen off widget")
1818
// Key for write secure settings
1919
PermissionRegistry.register("WRITE_SECURE_SETTINGS", "Statusbar icons")
20+
// Key for Shizuku (maps power saving)
21+
PermissionRegistry.register("SHIZUKU", "Maps power saving mode")
22+
// Key for notification listener permission
23+
PermissionRegistry.register("NOTIFICATION_LISTENER", "Maps power saving mode")
24+
// Key for draw over other apps permission (Edge lighting overlay)
25+
PermissionRegistry.register("DRAW_OVER_OTHER_APPS", "Edge lighting")
2026
// add other registrations here if needed in future
21-
}
22-
27+
}

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

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ fun SettingsContent(viewModel: MainViewModel, modifier: Modifier = Modifier) {
112112
val isReadPhoneStateEnabled by viewModel.isReadPhoneStateEnabled
113113
val isShizukuPermissionGranted by viewModel.isShizukuPermissionGranted
114114
val isShizukuAvailable by viewModel.isShizukuAvailable
115+
val isOverlayPermissionGranted by viewModel.isOverlayPermissionGranted
116+
val isNotificationListenerEnabled by viewModel.isNotificationListenerEnabled
115117
val context = LocalContext.current
116118

117119
Column(
@@ -136,7 +138,7 @@ fun SettingsContent(viewModel: MainViewModel, modifier: Modifier = Modifier) {
136138
)
137139

138140
PermissionCard(
139-
iconRes = R.drawable.rounded_chevron_right_24,
141+
iconRes = R.drawable.rounded_security_24,
140142
title = "Write Secure Settings",
141143
dependentFeatures = PermissionRegistry.getFeatures("WRITE_SECURE_SETTINGS"),
142144
actionLabel = if (isWriteSecureSettingsEnabled) "Granted" else "Copy ADB Command",
@@ -155,7 +157,7 @@ fun SettingsContent(viewModel: MainViewModel, modifier: Modifier = Modifier) {
155157

156158
if (isShizukuAvailable) {
157159
PermissionCard(
158-
iconRes = R.drawable.rounded_chevron_right_24,
160+
iconRes = R.drawable.rounded_adb_24,
159161
title = "Shizuku",
160162
dependentFeatures = listOf("Automatic Write Secure Settings Permission"),
161163
actionLabel = if (isShizukuPermissionGranted) "Granted" else "Request Permission",
@@ -198,6 +200,33 @@ fun SettingsContent(viewModel: MainViewModel, modifier: Modifier = Modifier) {
198200
)
199201
},
200202
)
203+
204+
PermissionCard(
205+
iconRes = R.drawable.rounded_magnify_fullscreen_24,
206+
title = "Draw Overlays",
207+
dependentFeatures = listOf("Edge Lighting"),
208+
actionLabel = if (isOverlayPermissionGranted) "Granted" else "Grant Permission",
209+
isGranted = isOverlayPermissionGranted,
210+
onActionClick = {
211+
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, android.net.Uri.parse("package:${context.packageName}"))
212+
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
213+
context.startActivity(intent)
214+
},
215+
)
216+
217+
PermissionCard(
218+
iconRes = R.drawable.rounded_notification_settings_24,
219+
title = "Notification Listener",
220+
dependentFeatures = listOf("Edge Lighting"),
221+
actionLabel = if (isNotificationListenerEnabled) "Granted" else "Enable listener",
222+
isGranted = isNotificationListenerEnabled,
223+
onActionClick = {
224+
val intent = Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS).apply {
225+
flags = Intent.FLAG_ACTIVITY_NEW_TASK
226+
}
227+
context.startActivity(intent)
228+
},
229+
)
201230
}
202231

203232
Spacer(modifier = Modifier.height(16.dp))
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.sameerasw.essentials.domain.model
2+
3+
data class AppSelection(
4+
val packageName: String,
5+
val isEnabled: Boolean
6+
)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.sameerasw.essentials.domain.model
2+
3+
import android.graphics.drawable.Drawable
4+
5+
data class NotificationApp(
6+
val packageName: String,
7+
val appName: String,
8+
val isEnabled: Boolean,
9+
val icon: Drawable,
10+
val isSystemApp: Boolean,
11+
val lastUpdated: Long
12+
)

0 commit comments

Comments
 (0)