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
64 changes: 37 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Adaptive Theme: Auto Dark Mode by Ambient Light

Adaptive Theme automatically switches between Light and **Dark mode**
Adaptive Theme automatically switches between light and **dark mode**
using the **ambient light sensor** — not a fixed schedule.

It adapts to real lighting conditions to optimize **readability**, **eye comfort**, and **battery
Expand All @@ -19,16 +19,16 @@ life**.
## 🚀 Quick Start (2 minutes)

1. **Install** Adaptive Theme.
2. **Grant the permission** with a [web-tool](https://lexip.dev/setup), Shizuku, or other methods
2. **Grant the permission** with the [web-tool](https://lexip.dev/setup), Shizuku, or other methods
below.
3. **Pick your lux threshold** and you’re done. ✅

## 📋 Table of Contents

- [✨ Features & Highlights](#-features--highlights)
- [🛠️ One-Time Setup](#%EF%B8%8F-one-time-setup)
- [⚙️ How it Works](#%EF%B8%8F-how-it-works)
- [✅ Safety](#-safety)
- [⚙️ How it Works](#%EF%B8%8F-how-it-works)
- [❓ FAQ](#-faq)
- [❤️ Support the Project](#%EF%B8%8F-support-the-project)
- [🏗️ Architecture & Tech Stack](#%EF%B8%8F-architecture--tech-stack)
Expand All @@ -37,15 +37,17 @@ life**.

* 🌤️ **Smart Detection:** Uses your devices physical light sensor to switch the system
theme.
* ⚙️ **Custom brightness threshold:** Choose exactly when Light ↔ Dark should flip.
* ⚙️ **Custom brightness threshold:** Choose exactly when the theme should flip or use a preset (
indoor, outdoor, sunlight, etc.).
* 🔋 **Battery Friendly:** The app is passive. Its event-driven architecture only checks the sensor
when you turn on the screen — zero battery drain in the background.
* 🗝️ **No Root Required:** Root access is not required (but is supported as an alternative setup
* 🗝️ **No Root Required:** Root access is not required (but supported as an alternative setup
method).
* 🐱 **Shizuku Support:** One of multiple setup options is
using [Shizuku](https://github.com/RikkaApps/Shizuku).
* 🚀 **Modern & Native:** Built with best-practices using Kotlin, Jetpack Compose and Material You
for a smooth and solid experience.
* 🌍 **50+ Languages:** Applied globalization at its best.
* 🔒 **Transparent:** Free, open-source, no-ads.

## 🛠️ One-Time Setup
Expand All @@ -56,44 +58,49 @@ permission (`WRITE_SECURE_SETTINGS`) has to be granted.
The app comes with an easy step-by-step setup process, that lets you choose one of the following
methods to do so:

* **Web Tool (Recommended)** – Use our browser-based setup tool on a secondary device (Computer,
Tablet,
or Phone). No code or ADB
installation required (WebADB).
👉 **[lexip.dev/setup](https://lexip.dev/setup)**
* **Web Tool (Recommended)** – A browser-based setup tool on a secondary device (Computer,
Tablet,
or Phone). No code or ADB
installation required (WebADB).
👉 **[lexip.dev/setup](https://lexip.dev/setup)**

* **Shizuku** – If you have **[Shizuku](https://github.com/RikkaApps/Shizuku)** installed and
configured, you can
grant the permission directly within the Adaptive Theme app.
grant the permission directly within Adaptive Theme.

* **Root** – If your device is rooted, you can grant the permission directly in Adaptive Theme as
well.

* **Manual ADB** – If you have ADB installed on your computer, you can simply run the ADB command
manually:
```adb shell pm grant dev.lexip.hecate android.permission.WRITE_SECURE_SETTINGS```

* **Root** – If your device is rooted, you can grant the permission with one tap inside the app.
## ✅ Safety

* **Manual ADB** – If you have ADB installed on your computer, you can run the ADB command manually.
The required permission only allows the app to change system settings such as the dark mode. This is
absolutely safe and
completely reversible by uninstalling the app. It does **not** grant root access or read any user
data.

## ⚙️ How it Works

**Wondering why the theme didn't change immediately?**

To avoid screen flicker and unnecessary background work, Adaptive Theme follows strict rules:

- **Event-driven:** It checks the light sensor only immediately after the screen turns on.
- **Event-driven:** It checks the light sensor only right after the screen turns on. Combined with
hysteresis, this prevents flicker, avoids interruptions while you’re using the phone, and saves
battery.
- **Validity check:** It verifies that the sensor is not obstructed (e.g., by a hand or pocket).
- **Seamless switch:** It switches the theme instantly, ensuring the UI is ready before you start
interacting with it.

## ✅ Safety

The required permission only allows the app to change system settings such as the dark mode. This is
absolutely safe and
completely reversible by uninstalling the app. It does **not** grant root access or read any user
data.

## ❓ FAQ

**Does this require root?**

* No. It works on stock devices. However, if you have Root, it can optionally be used to set up the
service faster.
* No. It works on stock devices. However, if you have Root, it can be used as an alternative setup
method.

**Does it work with custom Android skins (Xiaomi MIUI, Samsung OneUI, etc.)?**

Expand All @@ -107,11 +114,12 @@ data.
your device usage.
- Check that your sensor isn’t covered when you turn the screen on.
- Adjust your lux threshold and test in clearly bright/dim conditions.
- Check if the current lux value is shown correctly in the Adaptive Theme app.

### Support & Feedback

If Adaptive Theme doesn’t work for you — or if you have any questions or ideas — please open an
issue here or send feedback via the app.
If Adaptive Theme doesn’t work for you — or if you have any questions or ideas — please [open an
issue](https://github.com/xLexip/Adaptive-Theme/issues/new) here or send feedback via the app.

## ❤️ Support the Project

Expand Down Expand Up @@ -142,7 +150,8 @@ also [buy me a coffee](https://buymeacoffee.com/lexip).
Adaptive Theme is built with modern Android engineering standards to ensure a lightweight,
maintainable, and production-ready codebase.

**Modern Codebase:** Written entirely in Kotlin with Jetpack Compose and Material 3 (Material You).
**Modern Codebase:** Written entirely in Kotlin with Jetpack Compose and Material 3 (Material You),
including haptic feedback.

**Architecture:** Follows the MVVM pattern with a Single-Activity architecture.

Expand All @@ -155,7 +164,8 @@ broadcasts – ensuring zero unnecessary battery drain in the background.

### **Made with 🥨 in Germany.**

> ~~> Keywords: theme switcher · android automation · night mode · dark sense · automatic dark
> ~~> Keywords: theme switcher · android automation · night mode · dark sense · automatic android
dark
mode ·
brightness-based ·
light-based · based on lux · google pixel · auto dark theme · shizuku apps · android 14 · android
Expand Down
6 changes: 4 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ android {
applicationId = "dev.lexip.hecate"
minSdk = 34
targetSdk = 35
versionCode = 97
versionName = "1.0.0"
versionCode = 103
versionName = "1.1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

Expand Down Expand Up @@ -123,6 +123,8 @@ dependencies {
"playImplementation"(libs.firebase.analytics)
"playImplementation"(libs.firebase.crashlytics)
"playImplementation"(libs.app.update.ktx)
"playImplementation"(libs.review)
"playImplementation"(libs.review.ktx)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down
27 changes: 27 additions & 0 deletions app/src/foss/kotlin/dev/lexip/hecate/util/InAppReviewHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2025 xLexip <https://lexip.dev>
*
* Licensed under the GNU General Public License, Version 3.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/gpl-3.0
*
* Please see the License for specific terms regarding permissions and limitations.
*/

package dev.lexip.hecate.util

import android.app.Activity

object InAppReviewHandler {

fun setReviewPending() {
// No-op for FOSS flavor
}

fun checkAndTriggerReview(activity: Activity) {
// No-op for FOSS flavor
}
}

3 changes: 3 additions & 0 deletions app/src/main/kotlin/dev/lexip/hecate/ui/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import dev.lexip.hecate.services.BroadcastReceiverService
import dev.lexip.hecate.ui.navigation.NavigationManager
import dev.lexip.hecate.ui.theme.HecateTheme
import dev.lexip.hecate.util.DarkThemeHandler
import dev.lexip.hecate.util.InAppReviewHandler
import dev.lexip.hecate.util.InAppUpdateManager
import dev.lexip.hecate.util.InstallSourceChecker

Expand Down Expand Up @@ -74,6 +75,8 @@ class MainActivity : ComponentActivity() {
override fun onResume() {
super.onResume()

InAppReviewHandler.checkAndTriggerReview(this)

inAppUpdateManager?.resumeImmediateUpdateIfNeeded()
inAppUpdateManager?.resumeFlexibleUpdateIfNeeded()

Expand Down
9 changes: 8 additions & 1 deletion app/src/main/kotlin/dev/lexip/hecate/ui/MainScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
Expand Down Expand Up @@ -97,6 +98,8 @@ fun MainScreen(

val internalUiState by mainViewModel.uiState.collectAsState()

val isSystemDark = isSystemInDarkTheme()

LaunchedEffect(Unit) {
val installed = ShizukuAvailability.isShizukuInstalled(context)
mainViewModel.setShizukuInstalled(installed)
Expand Down Expand Up @@ -271,7 +274,11 @@ fun MainScreen(
onValueChange = { index ->
mainViewModel.setPendingCustomSliderLux(lux[index])
mainViewModel.onSliderValueCommitted(index)
textShakeKey.intValue += 1

// Shake the description text when the user could expect an immediate theme switch
if ((currentLux > lux[index]) == isSystemDark) {
textShakeKey.intValue += 1
}
},
enabled = uiState.adaptiveThemeEnabled,
firstCard = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ import androidx.core.net.toUri
import dev.lexip.hecate.BuildConfig
import dev.lexip.hecate.R
import dev.lexip.hecate.logging.Logger
import dev.lexip.hecate.util.InAppReviewHandler
import dev.lexip.hecate.util.InstallSourceChecker
import java.net.URLEncoder
import java.nio.charset.StandardCharsets


const val FEEDBACK_SUBJECT = "Adaptive Theme Feedback (v${BuildConfig.VERSION_NAME})"

@Composable
Expand Down Expand Up @@ -150,6 +150,11 @@ fun ThreeDotMenu(
context,
"support_project"
)

if (isAdaptiveThemeEnabled) {
InAppReviewHandler.setReviewPending()
}

val supportUri =
"https://github.com/xLexip/Adaptive-Theme?tab=readme-ov-file#%EF%B8%8F-support-the-project".toUri()
val supportIntent = Intent(Intent.ACTION_VIEW, supportUri)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ class SetupViewModel(

fun onGrantViaRootRequested() {
val context = application.applicationContext
Toast.makeText(context, R.string.setup_root_grant_starting, Toast.LENGTH_SHORT).show()
Toast.makeText(context, R.string.setup_root_request, Toast.LENGTH_SHORT).show()

viewModelScope.launch(ioDispatcher) {
val result = tryGrantViaRoot()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ internal fun ForExpertsSectionCard(
Column(modifier = Modifier.fillMaxWidth()) {
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(id = R.string.setup_manual_command),
text = stringResource(id = R.string.setup_alternatives),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Expand Down
6 changes: 3 additions & 3 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
<string name="setup_alternative_methods">Alternative Methoden</string>
<string name="setup_grant_title">Schritt 3: Berechtigung mit dem anderen Gerät erteilen</string>
<string name="setup_grant_body">Öffne den folgenden Link auf deinem anderen Gerät und folge den Anweisungen:</string>
<string name="setup_manual_command">Alternativ kannst du diesen ADB-Command ausführen oder Root benutzen. Shizuku wird ebenfalls unterstützt.</string>
<string name="setup_alternatives">Alternativ kannst du einen ADB-Command ausführen oder Root benutzen. Shizuku wird ebenfalls unterstützt.</string>
<string name="setup_permission_granted">Berechtigung erteilt.</string>
<string name="setup_permission_not_granted">Berechtigung noch nicht erteilt. Bitte schließe die Einrichtung mit dem anderen Gerät ab.</string>
<string name="setup_usb_debugging_disabled">USB-Debugging aktivieren</string>
Expand All @@ -73,12 +73,12 @@
<string name="setup_why_other_device_title">Warum ist ein anderes Gerät nötig?</string>
<string name="setup_why_other_device">Android verhindert, dass Apps sich die benötigte Berechtigung selbst erteilen. Es wird ein anderes Gerät mit einem Webbrowser oder ADB benötigt.</string>
<string name="setup_is_this_safe_title">Ist das sicher?</string>
<string name="setup_is_this_safe_body">Ja. Es erlaubt der App nur, Einstellungen wie den Dunkelmodus zu ändern. Das ist absolut sicher und vollständig rückgängig zu machen.</string>
<string name="setup_is_this_safe_body">Ja. Es erlaubt der App nur, Einstellungen wie den Dark Mode zu ändern. Das ist absolut sicher und vollständig rückgängig zu machen. Außerdem ist der Quellcode öffentlich auf GitHub einsehbar.</string>
<string name="setup_shizuku_title">Alternative: Shizuku</string>
<string name="setup_shizuku_body">Wenn du Shizuku bereits verwendest, benötigst du weder ein zweites Gerät noch ADB.</string>
<string name="setup_shizuku_action">Shizuku verwenden</string>
<string name="setup_action_use_root">Root verwenden</string>
<string name="setup_root_grant_starting">Root-Zugriff angefragt… (beta)</string>
<string name="setup_root_request">Root-Zugriff angefragt…</string>
<string name="setup_root_grant_failed">Root-Vergabe fehlgeschlagen.</string>
<string name="setup_root_question">Ist das Gerät gerooted?</string>

Expand Down
6 changes: 3 additions & 3 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
<string name="setup_alternative_methods">Alternative methods</string>
<string name="setup_grant_title">Step 3: Grant permission with the other device</string>
<string name="setup_grant_body">Open the following link on your other device and follow the instructions:</string>
<string name="setup_manual_command">Alternatively, you can execute this ADB command or use root to grant the permission. Shizuku is also supported.</string>
<string name="setup_alternatives">Alternatively, you can execute an ADB command yourself or use root to grant the permission. Shizuku is also supported.</string>
<string name="setup_permission_granted">Permission granted.</string>
<string name="setup_permission_not_granted">Permission not yet granted. Please complete the setup with the other device.</string>
<string name="setup_usb_debugging_disabled">Enable USB Debugging</string>
Expand All @@ -78,12 +78,12 @@
<string name="setup_why_other_device_title">Why is another device required?</string>
<string name="setup_why_other_device">Android prevents apps from granting the required permission themselves. It requires another device with either a web browser or ADB installed. </string>
<string name="setup_is_this_safe_title">Is this safe?</string>
<string name="setup_is_this_safe_body">Yes. It just allows the app to modify settings like the dark mode. This is absolutely safe and completely reversible.</string>
<string name="setup_is_this_safe_body">Yes. It just allows the app to modify settings like the dark mode. This is absolutely safe and completely reversible. Furthermore, the source code is publicly available on GitHub.</string>
<string name="setup_shizuku_title">Alternative: Shizuku</string>
<string name="setup_shizuku_body">If you already use Shizuku, you won’t need a second device or ADB.</string>
<string name="setup_shizuku_action">Use Shizuku instead</string>
<string name="setup_action_use_root">Use Root</string>
<string name="setup_root_grant_starting">Requesting root access… (beta)</string>
<string name="setup_root_request">Requesting root access…</string>
<string name="setup_root_grant_failed">Root grant failed.</string>
<string name="setup_root_question">Is the device rooted?</string>

Expand Down
7 changes: 7 additions & 0 deletions app/src/play/kotlin/dev/lexip/hecate/logging/Logger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,11 @@ object Logger {
}
}
}

fun logInAppReviewFlowCompleted(context: Context) {
ifAllowed {
analytics(context).logEvent("review_flow_completed") { }
}
}

}
Loading
Loading