Skip to content

Commit 664e140

Browse files
authored
Merge pull request #70 from xLexip/develop
v0.9.0-beta
2 parents 51c7bd5 + bcabb10 commit 664e140

34 files changed

+2215
-1055
lines changed

README.md

Lines changed: 123 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,144 @@
1-
[![GooglePlay](https://upload.wikimedia.org/wikipedia/commons/7/78/Google_Play_Store_badge_EN.svg)](https://lexip.dev/hecate/play)
1+
# Adaptive Theme: Smart Dark Mode
22

3-
![feature_graphic](https://i.ibb.co/G38P9b9V/adaptive-theme.jpg)
3+
Adaptive Theme intelligently automates your device's theme settings, switching between **Light and
4+
Dark mode** based on your environment's **ambient light** — not just the time of day.
45

5-
## Adaptive Theme
6+
Get the readability of Light mode in bright daylight and the eye-comfort of Dark mode in low light.
7+
This allows for a true auto dark mode experience that native Android doesn't offer.
68

7-
Adaptive Theme intelligently switches your device between Light and Dark mode based on your
8-
environment.
9+
## Closed Beta
910

10-
Get the readability of Light mode in bright daylight and the comfort of Dark mode in low light —
11-
going easy on your eyes and your battery.
11+
Join this [Google Group](https://groups.google.com/g/apphive-testers) before clicking on
12+
this [Google Play](https://play.google.com/apps/testing/dev.lexip.hecate) link to install the beta.
13+
14+
15+
---
16+
17+
## 📋 Table of Contents
18+
19+
- [💡 Why use Adaptive Theme?](#-why-use-adaptive-theme)
20+
- [✨ Key Highlights](#-key-highlights)
21+
- [🛠️ One-Time Setup](#%EF%B8%8F-one-time-setup)
22+
- [⚙️ How it Works](#%EF%B8%8F-how-it-works)
23+
- [✅ Safety](#-safety)
24+
- [❓ FAQ](#-faq)
25+
- [❤️ Support the Project](#%EF%B8%8F-support-the-project)
26+
- [📱 Screenshots](#-screenshots)
27+
28+
---
29+
30+
## 💡 Why use Adaptive Theme?
31+
32+
Most Android phones only switch themes at sunset or based on a fixed schedule. Adaptive Theme uses
33+
your **light sensor** to switch intelligently, optimizing both **eye comfort** and **battery life**.
34+
35+
## ✨ Key Highlights
36+
37+
* 🌤️ **Smart Ambient Detection:** Uses your device's physical light sensor to toggle the system
38+
theme.
39+
* ⚙️ **Full Customization:** Set your specific lux threshold (brightness level) and use the Quick
40+
Settings tile to quickly pause/resume the service.
41+
* 🚀 **Modern & Native:** Built with **Jetpack Compose** and **Material You** for a smooth,
42+
crash-free experience.
43+
* 🔋 **Battery Friendly:** The app is passive. It only checks the sensor when you turn the screen
44+
on — zero battery drain in the background.
45+
* 🔒 **Privacy First:** Open Source, completely free, and no ads at all.
46+
* 🗝️ **No Root Required:** Root access is not required (but is supported as an alternative setup
47+
method).
48+
* 🐱 **Optional Shizuku Support:** One of multiple setup options is
49+
using [Shizuku](https://github.com/RikkaApps/Shizuku).
1250

1351
---
1452

15-
### Highlights
53+
## 🛠️ One-Time Setup
54+
55+
Android restricts apps from changing system themes by default. To unlock this feature, a specific
56+
permission (`WRITE_SECURE_SETTINGS`) is needed. After installing the app, you can choose any of the
57+
following methods:
58+
59+
#### Method 1: Web Tool (Recommended)
60+
61+
Use our browser-based setup tool on a secondary device (Computer, Tablet, or Phone). No code or ADB
62+
installation required (WebADB).
63+
👉 **[lexip.dev/setup](https://lexip.dev/setup)**
64+
65+
#### Method 2: Shizuku (No PC)
1666

17-
🌤️ **Smart Detection**: Uses your ambient light sensor to switch themes automatically.
67+
If you have **Shizuku** installed and configured (via Wireless Debugging or Root), you can grant the
68+
permission directly within the Adaptive Theme app.
1869

19-
⚙️ **Full Control**: Fully customizable brightness threshold and a Quick Settings tile to
20-
pause/resume the service.
70+
#### Method 3: Root
2171

22-
🔒 **Free & Open**: Free to use, no ads and open source.
72+
If your device is rooted, you can grant the permission with one click inside the app.
2373

24-
🚀 **Native Design**: Modern architecture, built with Jetpack Compose and Material You for a seamless
25-
Android experience.
74+
#### Method 4: Manual ADB
2675

27-
🚫 **No Flickering**: The theme only changes when you turn on screen and the device is uncovered.
76+
If you have ADB installed on your computer, you can run the ADB grant command manually via your
77+
terminal.
2878

2979
---
3080

31-
### One-Time Setup
81+
## ⚙️ How it Works
82+
83+
**Why didn't the theme change immediately?**
84+
85+
To prevent unnecessary battery drain and screen flickering, Adaptive Theme obeys the following
86+
rules:
87+
88+
1. It checks the light sensor only **immediately after the screen turns on**.
89+
2. It verifies that the light sensor is **not covered**.
90+
3. It switches the theme **instantly** before you start interacting with the UI.
91+
92+
---
93+
94+
## ✅ Safety
95+
96+
The required permission does **not** grant root access or read any user data. It only allows the app
97+
to change settings such as "Dark Mode" in the system settings. This is absolutely safe and
98+
completely reversible by uninstalling the app.
99+
100+
---
101+
102+
## ❓ FAQ
103+
104+
**1. Does this require Root?**
105+
No. It works on stock devices. However, if you have Root, it can optionally be used to set up the
106+
service faster.
107+
108+
**2. Does it work with custom skins (MIUI, OneUI)?**
109+
In most cases, yes. It works with any system that respects the native Android Dark Mode
110+
implementation.
111+
112+
**Support & Feedback:** If Adaptive Theme not work for you or if you have any questions, please
113+
create an Issue or send feedback via the app.
114+
115+
---
116+
117+
## ❤️ Support the Project
118+
119+
Adaptive Theme is **completely free**, **ad-free**, **open source**, and developed in my free time.
120+
121+
If you enjoy using the app, there are three simple ways you can support the project:
122+
123+
**Star on GitHub:** Give this repository a star to help others find it.
124+
125+
🌟 **Rate on Google Play:**
126+
A [5-star rating](https://play.google.com/store/apps/details?id=dev.lexip.hecate)
127+
is the best way to boost the ranking.
128+
129+
**Buy me a Coffee:** If you are feeling generous, you can
130+
also [buy me a coffee](https://buymeacoffee.com/lexip).
131+
132+
📣 **Spread the Word:** Share the app to help the project grow.
133+
134+
---
32135

33-
To toggle the system theme, Android requires the permission `WRITE_SECURE_SETTINGS`. This is safe,
34-
transparent and fully reversible. The app will guide you through the setup process.
136+
**🇩🇪 Made in Germany** – Engineered with precision (and 🥨 🍺).
35137

36138
---
37139

38-
That’s it! Set your preference, and never worry about your light/dark mode again.
140+
## 📱 Screenshots
39141

40-
🇩🇪 Made with 🥨 🍺 in Germany.
142+
[![Adaptive Theme Screenshot](https://i.ibb.co/6cngXDnx/Adaptive-Theme-Screenshot.webp)](https://ibb.co/gbjz4tjp)
41143

42-
[![SonarCloud](https://sonarcloud.io/api/project_badges/quality_gate?project=xLexip_Hecate)](https://sonarcloud.io/summary/new_code?id=xLexip_Hecate)
144+
#### [More Screenshots](https://play.google.com/store/apps/details?id=dev.lexip.hecate)

app/build.gradle.kts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ android {
1515
applicationId = "dev.lexip.hecate"
1616
minSdk = 31
1717
targetSdk = 36
18-
versionCode = 36
19-
versionName = "0.7.0"
18+
versionCode = 46
19+
versionName = "0.9.0"
2020
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2121
}
2222

@@ -100,6 +100,8 @@ dependencies {
100100
implementation(platform(libs.androidx.compose.bom))
101101
implementation(libs.androidx.compose.material.icons.extended)
102102
implementation(libs.app.update.ktx)
103+
implementation(libs.shizuku.api)
104+
implementation(libs.shizuku.provider)
103105
testImplementation(libs.junit)
104106
androidTestImplementation(libs.androidx.junit)
105107
androidTestImplementation(libs.androidx.espresso.core)

app/proguard-rules.pro

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,12 @@
1818

1919
# If you keep the line number information, uncomment this to
2020
# hide the original source file name.
21-
#-renamesourcefileattribute SourceFile
21+
#-renamesourcefileattribute SourceFile
22+
23+
# --- Shizuku integration ---
24+
# Keep all Shizuku library classes used for binder communication
25+
-keep class moe.shizuku.** { *; }
26+
-keep class rikka.shizuku.** { *; }
27+
28+
# Keep Shizuku user service implementation and its members
29+
-keep class dev.lexip.hecate.util.shizuku.GrantService { *; }

app/src/main/AndroidManifest.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
xmlns:android="http://schemas.android.com/apk/res/android"
1515
tools:ignore="ProtectedPermissions">
1616

17+
<queries>
18+
<package android:name="moe.shizuku.privileged.api" />
19+
</queries>
20+
1721
<uses-permission
1822
android:name="android.permission.FOREGROUND_SERVICE"
1923
tools:ignore="ForegroundServicesPolicy" />
@@ -52,6 +56,15 @@
5256
android:localeConfig="@xml/locales_config"
5357
tools:targetApi="33">
5458

59+
<!-- Shizuku provider to acquire binder from Shizuku/Sui -->
60+
<provider
61+
android:name="rikka.shizuku.ShizukuProvider"
62+
android:authorities="${applicationId}.shizuku"
63+
android:multiprocess="false"
64+
android:enabled="true"
65+
android:exported="true"
66+
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
67+
5568
<!-- Explicitly disable AdID collection for Firebase Analytics -->
5669
<meta-data
5770
android:name="google_analytics_adid_collection_enabled"

app/src/main/java/dev/lexip/hecate/analytics/AnalyticsLogger.kt

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,11 @@ object AnalyticsLogger {
125125
}
126126
}
127127

128-
fun logSetupFinished(context: Context) {
128+
fun logSetupComplete(context: Context, source: String? = null) {
129129
ifAllowed {
130-
analytics(context).logEvent("setup_finished") { }
130+
analytics(context).logEvent("setup_finished") {
131+
if (source != null) param("source", source)
132+
}
131133
}
132134
}
133135

@@ -136,4 +138,45 @@ object AnalyticsLogger {
136138
analytics(context).logEvent("in_app_update_installed") { }
137139
}
138140
}
141+
142+
fun logUnexpectedShizukuError(
143+
context: Context,
144+
operation: String,
145+
stage: String,
146+
throwable: Throwable,
147+
binderReady: Boolean,
148+
packageName: String? = null
149+
) {
150+
ifAllowed {
151+
analytics(context).logEvent("shizuku_unexpected_error") {
152+
param("operation", operation)
153+
param("stage", stage)
154+
param("exception_type", throwable.javaClass.simpleName)
155+
param("message", throwable.message ?: "no_message")
156+
param("binder_ready", if (binderReady) 1L else 0L)
157+
if (packageName != null) param("package_name", packageName)
158+
}
159+
}
160+
}
161+
162+
fun logShizukuGrantResult(
163+
context: Context,
164+
result: dev.lexip.hecate.util.shizuku.ShizukuManager.GrantResult,
165+
packageName: String
166+
) {
167+
ifAllowed {
168+
analytics(context).logEvent("shizuku_grant_result") {
169+
val (resultType, exitCode) = when (result) {
170+
is dev.lexip.hecate.util.shizuku.ShizukuManager.GrantResult.Success -> "success" to null
171+
is dev.lexip.hecate.util.shizuku.ShizukuManager.GrantResult.ServiceNotRunning -> "service_not_running" to null
172+
is dev.lexip.hecate.util.shizuku.ShizukuManager.GrantResult.NotAuthorized -> "not_authorized" to null
173+
is dev.lexip.hecate.util.shizuku.ShizukuManager.GrantResult.ShellCommandFailed -> "shell_command_failed" to result.exitCode
174+
is dev.lexip.hecate.util.shizuku.ShizukuManager.GrantResult.Unexpected -> "unexpected" to null
175+
}
176+
param("result_type", resultType)
177+
exitCode?.let { param("exit_code", it.toLong()) }
178+
param("package_name", packageName)
179+
}
180+
}
181+
}
139182
}

app/src/main/java/dev/lexip/hecate/services/BroadcastReceiverService.kt

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import android.util.Log
2525
import androidx.core.app.NotificationCompat
2626
import dev.lexip.hecate.HecateApplication
2727
import dev.lexip.hecate.R
28-
import dev.lexip.hecate.analytics.AnalyticsLogger
2928
import dev.lexip.hecate.broadcasts.ScreenOnReceiver
3029
import dev.lexip.hecate.data.UserPreferencesRepository
3130
import dev.lexip.hecate.util.DarkThemeHandler
@@ -148,22 +147,6 @@ class BroadcastReceiverService : Service() {
148147
pendingIntent
149148
).build()
150149

151-
// Create action to pause/kill the service. The service will start again on next boot or app open.
152-
val stopIntent = Intent(this, BroadcastReceiverService::class.java).apply {
153-
action = ACTION_PAUSE_SERVICE
154-
}
155-
val pausePendingIntent = PendingIntent.getService(
156-
this,
157-
0,
158-
stopIntent,
159-
PendingIntent.FLAG_IMMUTABLE
160-
)
161-
val pauseAction = NotificationCompat.Action.Builder(
162-
0,
163-
getString(R.string.action_pause_service),
164-
pausePendingIntent
165-
).build()
166-
167150
// Build notification
168151
val builder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
169152
.setContentTitle(getString(R.string.app_name))
@@ -173,7 +156,6 @@ class BroadcastReceiverService : Service() {
173156
.setOnlyAlertOnce(true)
174157
.setContentIntent(pendingIntent)
175158
.addAction(disableAction)
176-
.addAction(pauseAction)
177159
.setOngoing(true)
178160

179161

0 commit comments

Comments
 (0)