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
9 changes: 6 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ android {
applicationId = "com.sameerasw.essentials"
minSdk = 24
targetSdk = 36
versionCode = 17
versionName = "8.3"
versionCode = 18
versionName = "8.4"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -69,9 +69,9 @@ dependencies {
implementation(libs.androidx.compose.foundation.layout)
implementation(libs.androidx.ui)
implementation(libs.androidx.compose.foundation)
implementation(libs.material)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
debugImplementation(libs.androidx.compose.ui.tooling)
Expand All @@ -94,4 +94,7 @@ dependencies {
// Volume Long Press
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1")
implementation("dev.rikka.shizuku:api:13.1.5")

// Google Maps & Location
implementation(libs.play.services.location)
}
33 changes: 32 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@
<uses-feature android:name="android.hardware.camera.flashlight" android:required="false" />

<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" tools:ignore="ProtectedPermissions" />
<uses-permission android:name="moe.shizuku.manager.permission.API_V23" />
Expand All @@ -22,6 +25,9 @@
<uses-permission android:name="android.permission.POST_PROMOTED_NOTIFICATIONS" tools:ignore="UnusedAttribute" />


<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

<application
android:name=".EssentialsApp"
Expand All @@ -44,6 +50,19 @@

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<!-- Handle shared locations -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="geo" />
</intent-filter>
<meta-data android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
Expand All @@ -62,6 +81,18 @@
android:theme="@style/Theme.Essentials">
</activity>

<activity
android:name=".ui.activities.LocationAlarmActivity"
android:exported="true"
android:launchMode="singleInstance"
android:theme="@style/Theme.Essentials.FullScreenAlarm" />

<service
android:name=".services.LocationReachedService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="location" />

<activity
android:name=".AppFreezingActivity"
android:exported="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import com.sameerasw.essentials.ui.composables.configs.QuickSettingsTilesSetting
import com.sameerasw.essentials.ui.composables.configs.ButtonRemapSettingsUI
import com.sameerasw.essentials.ui.composables.configs.DynamicNightLightSettingsUI
import com.sameerasw.essentials.ui.composables.configs.SnoozeNotificationsSettingsUI
import com.sameerasw.essentials.ui.composables.configs.LocationReachedSettingsUI
import com.sameerasw.essentials.viewmodels.CaffeinateViewModel
import com.sameerasw.essentials.viewmodels.MainViewModel
import com.sameerasw.essentials.viewmodels.StatusBarIconViewModel
Expand Down Expand Up @@ -189,6 +190,7 @@ class FeatureSettingsActivity : FragmentActivity() {
"Screen locked security" -> !isAccessibilityEnabled || !isWriteSecureSettingsEnabled || !viewModel.isDeviceAdminEnabled.value
"App lock" -> !isAccessibilityEnabled
"Freeze" -> !viewModel.isShizukuAvailable.value || !viewModel.isShizukuPermissionGranted.value
"Location reached" -> !viewModel.isLocationPermissionGranted.value || !viewModel.isBackgroundLocationPermissionGranted.value
else -> false
}
showPermissionSheet = hasMissingPermissions
Expand Down Expand Up @@ -385,6 +387,26 @@ class FeatureSettingsActivity : FragmentActivity() {
isGranted = viewModel.isShizukuPermissionGranted.value
)
)
"Location reached" -> listOf(
PermissionItem(
iconRes = R.drawable.rounded_navigation_24,
title = R.string.perm_location_title,
description = R.string.perm_location_desc,
dependentFeatures = PermissionRegistry.getFeatures("LOCATION"),
actionLabel = R.string.perm_action_grant,
action = { viewModel.requestLocationPermission(this) },
isGranted = viewModel.isLocationPermissionGranted.value
),
PermissionItem(
iconRes = R.drawable.rounded_navigation_24,
title = R.string.perm_bg_location_title,
description = R.string.perm_bg_location_desc,
dependentFeatures = PermissionRegistry.getFeatures("BACKGROUND_LOCATION"),
actionLabel = R.string.perm_action_grant,
action = { viewModel.requestBackgroundLocationPermission(this) },
isGranted = viewModel.isBackgroundLocationPermissionGranted.value
)
)
else -> emptyList()
}

Expand All @@ -409,7 +431,8 @@ class FeatureSettingsActivity : FragmentActivity() {
hasSearch = false,
onBackClick = { finish() },
scrollBehavior = scrollBehavior,
subtitle = if (featureObj != null) stringResource(featureObj.description) else ""
subtitle = if (featureObj != null) stringResource(featureObj.description) else "",
isBeta = featureObj?.isBeta ?: false
)
},
floatingActionButton = {
Expand Down Expand Up @@ -520,6 +543,13 @@ class FeatureSettingsActivity : FragmentActivity() {
highlightSetting = highlightSetting
)
}
"Location reached" -> {
LocationReachedSettingsUI(
mainViewModel = viewModel,
modifier = Modifier.padding(top = 16.dp),
highlightSetting = highlightSetting
)
}
// else -> default UI (optional cleanup)
}
}
Expand Down
18 changes: 17 additions & 1 deletion app/src/main/java/com/sameerasw/essentials/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import com.sameerasw.essentials.ui.composables.ComingSoonDIYScreen
import com.sameerasw.essentials.ui.theme.EssentialsTheme
import com.sameerasw.essentials.utils.HapticUtil
import com.sameerasw.essentials.viewmodels.MainViewModel
import com.sameerasw.essentials.viewmodels.LocationReachedViewModel
import com.sameerasw.essentials.ui.components.sheets.UpdateBottomSheet
import com.sameerasw.essentials.ui.components.sheets.InstructionsBottomSheet
import com.sameerasw.essentials.ui.composables.configs.FreezeSettingsUI
Expand All @@ -56,6 +57,7 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
class MainActivity : FragmentActivity() {
val viewModel: MainViewModel by viewModels()
val locationViewModel: LocationReachedViewModel by viewModels()
private var isAppReady = false

override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -78,7 +80,7 @@ class MainActivity : FragmentActivity() {
splashScreen.setOnExitAnimationListener { splashScreenViewProvider ->
try {
val splashScreenView = splashScreenViewProvider.view
val splashIcon = splashScreenViewProvider.iconView
val splashIcon = try { splashScreenViewProvider.iconView } catch (e: Exception) { null }

// Animate the splash screen view fade out
val fadeOut = ObjectAnimator.ofFloat(splashScreenView, "alpha", 1f, 0f).apply {
Expand Down Expand Up @@ -139,6 +141,7 @@ class MainActivity : FragmentActivity() {
}

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

// Initialize HapticUtil with saved preferences
HapticUtil.initialize(this)
Expand Down Expand Up @@ -301,6 +304,19 @@ class MainActivity : FragmentActivity() {

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
Log.d("MainActivity", "onNewIntent with action: ${intent.action}")
handleLocationIntent(intent)
}

private fun handleLocationIntent(intent: Intent?) {
intent?.let {
if (locationViewModel.handleIntent(it)) {
val settingsIntent = Intent(this, FeatureSettingsActivity::class.java).apply {
putExtra("feature", "Location reached")
}
startActivity(settingsIntent)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.sameerasw.essentials.data.repository

import android.content.Context
import android.content.SharedPreferences
import com.sameerasw.essentials.domain.model.LocationAlarm

import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow

class LocationReachedRepository(context: Context) {
private val prefs: SharedPreferences = context.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE)

companion object {
private val _isProcessing = MutableStateFlow(false)
val isProcessing = _isProcessing.asStateFlow()

private val _alarmFlow = MutableStateFlow<LocationAlarm?>(null)
val alarmFlow = _alarmFlow.asStateFlow()
}

init {
if (_alarmFlow.value == null) {
_alarmFlow.value = getAlarm()
}
}

fun setIsProcessing(processing: Boolean) {
_isProcessing.value = processing
}

fun saveAlarm(alarm: LocationAlarm) {
prefs.edit().apply {
putLong("location_reached_lat", java.lang.Double.doubleToRawLongBits(alarm.latitude))
putLong("location_reached_lng", java.lang.Double.doubleToRawLongBits(alarm.longitude))
putInt("location_reached_radius", alarm.radius)
putBoolean("location_reached_enabled", alarm.isEnabled)
apply()
}
_alarmFlow.value = alarm
}

fun getAlarm(): LocationAlarm {
val lat = java.lang.Double.longBitsToDouble(prefs.getLong("location_reached_lat", java.lang.Double.doubleToRawLongBits(0.0)))
val lng = java.lang.Double.longBitsToDouble(prefs.getLong("location_reached_lng", java.lang.Double.doubleToRawLongBits(0.0)))
val radius = prefs.getInt("location_reached_radius", 1000)
val enabled = prefs.getBoolean("location_reached_enabled", false)
return LocationAlarm(lat, lng, radius, enabled)
}

fun saveStartDistance(distance: Float) {
prefs.edit().putFloat("location_reached_start_dist", distance).apply()
}

fun getStartDistance(): Float {
return prefs.getFloat("location_reached_start_dist", 0f)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ abstract class Feature(
val permissionKeys: List<String> = emptyList(),
val searchableSettings: List<SearchSetting> = emptyList(),
val showToggle: Boolean = true,
val hasMoreSettings: Boolean = true
val hasMoreSettings: Boolean = true,
val isBeta: Boolean = false
) {
abstract fun isEnabled(viewModel: MainViewModel): Boolean

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.sameerasw.essentials.domain.model

data class LocationAlarm(
val latitude: Double = 0.0,
val longitude: Double = 0.0,
val radius: Int = 1000, // in meters
val isEnabled: Boolean = false
)
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ data class SearchableItem(
val parentFeature: String? = null,
val targetSettingHighlightKey: String? = null,
val titleRes: Int? = null,
val descriptionRes: Int? = null
val descriptionRes: Int? = null,
val isBeta: Boolean = false
)
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,20 @@ object FeatureRegistry {
override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) = viewModel.setAppLockEnabled(enabled, context)
},

object : Feature(
id = "Location reached",
title = R.string.feat_location_reached_title,
iconRes = R.drawable.rounded_navigation_24,
category = R.string.cat_tools,
description = R.string.feat_location_reached_desc,
permissionKeys = listOf("LOCATION", "BACKGROUND_LOCATION", "USE_FULL_SCREEN_INTENT"),
showToggle = false,
isBeta = true
) {
override fun isEnabled(viewModel: MainViewModel) = true
override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {}
},

object : Feature(
id = "Freeze",
title = R.string.feat_freeze_title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,9 @@ fun initPermissionRegistry() {

// Device Admin permission
PermissionRegistry.register("DEVICE_ADMIN", R.string.feat_screen_locked_security_title)

// Location permission
PermissionRegistry.register("LOCATION", R.string.feat_location_reached_title)
PermissionRegistry.register("BACKGROUND_LOCATION", R.string.feat_location_reached_title)
PermissionRegistry.register("USE_FULL_SCREEN_INTENT", R.string.feat_location_reached_title)
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ object SearchRegistry {
category = featureCategory,
icon = feature.iconRes,
featureKey = feature.id,
keywords = listOf(context.getString(R.string.keyword_feature), context.getString(R.string.keyword_settings))
keywords = listOf(context.getString(R.string.keyword_feature), context.getString(R.string.keyword_settings)),
isBeta = feature.isBeta
)
)

Expand All @@ -41,7 +42,8 @@ object SearchRegistry {
featureKey = feature.id,
parentFeature = featureTitle,
targetSettingHighlightKey = setting.targetSettingHighlightKey,
keywords = if (setting.keywordRes != 0) context.resources.getStringArray(setting.keywordRes).toList() else emptyList()
keywords = if (setting.keywordRes != 0) context.resources.getStringArray(setting.keywordRes).toList() else emptyList(),
isBeta = feature.isBeta
)
)
}
Expand Down
Loading