Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: xLexip/Hecate
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 7c3291f68e56ea7b083b4a0915617451b2dff910
Choose a base ref
..
head repository: xLexip/Hecate
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 56922975db7b82a94fb420d297ead42c7fcaeab9
Choose a head ref
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -49,10 +49,12 @@ android {
}

dependencies {
implementation(libs.androidx.localbroadcastmanager)
implementation(libs.androidx.core.splashscreen.v100)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.datastore.preferences)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.material3)
64 changes: 49 additions & 15 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,26 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2024 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.
-->

<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
tools:ignore="ProtectedPermissions">

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.App.Starting"
android:usesCleartextTraffic="false"
android:windowSoftInputMode="adjustResize">
android:name="dev.lexip.hecate.HecateApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.App.Starting"
android:usesCleartextTraffic="false"
android:windowSoftInputMode="adjustResize">

<activity
android:name="MainActivity"
android:exported="true"
android:theme="@style/Theme.App.Starting">
android:name=".ui.MainActivity"
android:exported="true"
android:theme="@style/Theme.App.Starting">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<service
android:name=".services.BroadcastReceiverService"
android:foregroundServiceType="specialUse" />

<receiver
android:name=".broadcasts.BootCompletedReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>

</application>

</manifest>
31 changes: 31 additions & 0 deletions app/src/main/java/dev/lexip/hecate/HecateApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2024 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

import android.app.Application
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore

const val USER_PREFERENCES_NAME = "user_preferences"
private val Context.dataStore by preferencesDataStore(USER_PREFERENCES_NAME)

class HecateApplication : Application() {
/**
* Top level data store to ensure it is maintained as a singleton
* but still accessible in both the app and service.
*/
val userPreferencesDataStore: DataStore<Preferences>
get() = this.dataStore
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2024 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.broadcasts

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import dev.lexip.hecate.services.BroadcastReceiverService

private const val TAG = "BootCompletedReceiver"

class BootCompletedReceiver : BroadcastReceiver() {

override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
Log.i(TAG, "Boot completed, starting broadcast receiver service...")
val serviceIntent = Intent(context, BroadcastReceiverService::class.java)
context.startService(serviceIntent)
}
}

}
55 changes: 55 additions & 0 deletions app/src/main/java/dev/lexip/hecate/broadcasts/ScreenOnReceiver.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2024-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.broadcasts

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import dev.lexip.hecate.util.DarkThemeHandler
import dev.lexip.hecate.util.LightSensorManager
import dev.lexip.hecate.util.ProximitySensorManager

private const val TAG = "ScreenOnReceiver"

/**
* Adaptive theme switching logic. Executes when the screen is turned on.
* The theme is switched based on the environment brightness and proximity sensor values.
*/
class ScreenOnReceiver(
private val proximitySensorManager: ProximitySensorManager,
private val lightSensorManager: LightSensorManager,
private val darkThemeHandler: DarkThemeHandler,
private val adaptiveThemeThresholdLux: Float
) : BroadcastReceiver() {

override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_SCREEN_ON) {
Log.d(TAG, "Screen turned on, checking adaptive theme conditions...")

// Check if the device is covered using the proximity sensor
proximitySensorManager.startListening { distance ->
proximitySensorManager.stopListening()

// If the device is not covered, change the device theme based on the environment
if (distance >= 5f) {
lightSensorManager.startListening { lightValue ->
lightSensorManager.stopListening()
darkThemeHandler.setDarkTheme(lightValue < adaptiveThemeThresholdLux)
}
} else Log.d(TAG, "Device is covered, skipping adaptive theme checks.")

}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2024 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.data

import android.util.Log
import androidx.datastore.core.DataStore
import androidx.datastore.core.IOException
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.floatPreferencesKey
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map

private const val TAG = "UserPreferencesRepository"

data class UserPreferences(
val adaptiveThemeEnabled: Boolean,
val adaptiveThemeThresholdLux: Float
)

class UserPreferencesRepository(private val dataStore: DataStore<Preferences>) {

private object PreferencesKeys {
val ADAPTIVE_THEME_ENABLED = booleanPreferencesKey("adaptive_theme_enabled")
val ADAPTIVE_THEME_THRESHOLD_LUX = floatPreferencesKey("adaptive_theme_threshold_lux")
}

val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
.catch { exception ->
// dataStore.data throws an IOException when an error is encountered when reading data
if (exception is IOException) {
Log.e(TAG, "Error reading user preferences.", exception)
emit(emptyPreferences())
} else {
throw exception
}
}.map { preferences ->
mapUserPreferences(preferences)
}

suspend fun fetchInitialPreferences() =
mapUserPreferences(dataStore.data.first().toPreferences())

private fun mapUserPreferences(preferences: Preferences): UserPreferences {
// Get our show completed value, defaulting to false if not set:
preferences[PreferencesKeys.ADAPTIVE_THEME_ENABLED] == true
val adaptiveThemeEnabled = preferences[PreferencesKeys.ADAPTIVE_THEME_ENABLED] == true
val adaptiveThemeThresholdLux =
preferences[PreferencesKeys.ADAPTIVE_THEME_THRESHOLD_LUX] ?: 100f
return UserPreferences(adaptiveThemeEnabled, adaptiveThemeThresholdLux)
}

suspend fun updateAdaptiveThemeEnabled(enabled: Boolean) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.ADAPTIVE_THEME_ENABLED] = enabled
}
}

suspend fun updateAdaptiveThemeThresholdLux(lux: Float) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.ADAPTIVE_THEME_THRESHOLD_LUX] = lux
}
}

}
Loading