diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..85ebb6a
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+Flip_2_DND
\ No newline at end of file
diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml
new file mode 100644
index 0000000..4a53bee
--- /dev/null
+++ b/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml
new file mode 100644
index 0000000..371f2e2
--- /dev/null
+++ b/.idea/appInsightsSettings.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..e58d3e4
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index 6d0ee1c..fdf8d99 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 74dd639..bbf9fc8 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,7 +1,6 @@
-
-
+
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 888bd00..53ec9fc 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -16,8 +16,8 @@ android {
applicationId = "dev.robin.flip_2_dnd"
minSdk = 23
targetSdk = 34
- versionCode = 7
- versionName = "1.0.7"
+ versionCode = 8
+ versionName = "1.0.8"
vectorDrawables {
useSupportLibrary = true
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index aae05f9..540377c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -8,6 +8,7 @@
+
@@ -40,5 +41,12 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/dev/robin/flip_2_dnd/MainActivity.kt b/app/src/main/java/dev/robin/flip_2_dnd/MainActivity.kt
index a43dede..b57f8be 100644
--- a/app/src/main/java/dev/robin/flip_2_dnd/MainActivity.kt
+++ b/app/src/main/java/dev/robin/flip_2_dnd/MainActivity.kt
@@ -12,8 +12,8 @@ import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
-import androidx.compose.runtime.*
-import androidx.compose.ui.platform.LocalContext
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
@@ -26,98 +26,98 @@ import dev.robin.flip_2_dnd.ui.theme.Flip_2_DNDTheme
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
- private val mainViewModel: MainViewModel by viewModels()
-
- private val dndPermissionLauncher = registerForActivityResult(
- ActivityResultContracts.StartActivityForResult()
- ) {
- checkAndStartService()
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- enableEdgeToEdge()
- checkAndStartService()
-
- setContent {
- Flip_2_DNDTheme {
- val navController = rememberNavController()
-
- NavHost(
- navController = navController,
- startDestination = "main"
- ) {
- composable("main") {
- val state by mainViewModel.state.collectAsState()
- MainScreen(
- state = state,
- onSettingsClick = {
- navController.navigate("settings")
- }
- )
- }
- composable("settings") {
- SettingsScreen(
- navController = navController
- )
- }
- }
- }
- }
- }
-
- private fun checkAndStartService() {
- val notificationPolicyGranted = isNotificationPolicyAccessGranted()
- val batteryOptimizationDisabled = isBatteryOptimizationDisabled()
-
- // Always start the service
- startFlipDetectorService()
-
- // If permissions are not granted, show a warning
- if (!notificationPolicyGranted || !batteryOptimizationDisabled) {
- // Optional: Add a toast or dialog to inform user about missing permissions
- Toast.makeText(
- this,
- "Please grant all permissions for full functionality",
- Toast.LENGTH_LONG
- ).show()
-
- if (!notificationPolicyGranted) {
- requestNotificationPolicyAccess()
- }
-
- if (!batteryOptimizationDisabled) {
- requestDisableBatteryOptimization()
- }
- }
- }
-
- private fun isNotificationPolicyAccessGranted(): Boolean {
- val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
- return notificationManager.isNotificationPolicyAccessGranted
- }
-
- private fun requestNotificationPolicyAccess() {
- val intent = Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS)
- dndPermissionLauncher.launch(intent)
- }
-
- private fun isBatteryOptimizationDisabled(): Boolean {
- val powerManager = getSystemService(POWER_SERVICE) as PowerManager
- return powerManager.isIgnoringBatteryOptimizations(packageName)
- }
-
- private fun requestDisableBatteryOptimization() {
- val intent = Intent().apply {
- action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
- data = Uri.parse("package:$packageName")
- }
- startActivity(intent)
- }
-
- private fun startFlipDetectorService() {
- Intent(this, FlipDetectorService::class.java).also { intent ->
- startForegroundService(intent)
- }
- }
+ private val mainViewModel: MainViewModel by viewModels()
+
+ private val dndPermissionLauncher = registerForActivityResult(
+ ActivityResultContracts.StartActivityForResult()
+ ) {
+ checkAndStartService()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ checkAndStartService()
+
+ setContent {
+ Flip_2_DNDTheme {
+ val navController = rememberNavController()
+
+ NavHost(
+ navController = navController,
+ startDestination = "main"
+ ) {
+ composable("main") {
+ val state by mainViewModel.state.collectAsState()
+ MainScreen(
+ state = state,
+ onSettingsClick = {
+ navController.navigate("settings")
+ }
+ )
+ }
+ composable("settings") {
+ SettingsScreen(
+ navController = navController
+ )
+ }
+ }
+ }
+ }
+ }
+
+ private fun checkAndStartService() {
+ val notificationPolicyGranted = isNotificationPolicyAccessGranted()
+ val batteryOptimizationDisabled = isBatteryOptimizationDisabled()
+
+ // Always start the service
+ startFlipDetectorService()
+
+ // If permissions are not granted, show a warning
+ if (!notificationPolicyGranted || !batteryOptimizationDisabled) {
+ // Optional: Add a toast or dialog to inform user about missing permissions
+ Toast.makeText(
+ this,
+ "Please grant all permissions for full functionality",
+ Toast.LENGTH_LONG
+ ).show()
+
+ if (!notificationPolicyGranted) {
+ requestNotificationPolicyAccess()
+ }
+
+ if (!batteryOptimizationDisabled) {
+ requestDisableBatteryOptimization()
+ }
+ }
+ }
+
+ private fun isNotificationPolicyAccessGranted(): Boolean {
+ val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+ return notificationManager.isNotificationPolicyAccessGranted
+ }
+
+ private fun requestNotificationPolicyAccess() {
+ val intent = Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS)
+ dndPermissionLauncher.launch(intent)
+ }
+
+ private fun isBatteryOptimizationDisabled(): Boolean {
+ val powerManager = getSystemService(POWER_SERVICE) as PowerManager
+ return powerManager.isIgnoringBatteryOptimizations(packageName)
+ }
+
+ private fun requestDisableBatteryOptimization() {
+ val intent = Intent().apply {
+ action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
+ data = Uri.parse("package:$packageName")
+ }
+ startActivity(intent)
+ }
+
+ private fun startFlipDetectorService() {
+ Intent(this, FlipDetectorService::class.java).also { intent ->
+ startForegroundService(intent)
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/dev/robin/flip_2_dnd/data/repository/OrientationRepositoryImpl.kt b/app/src/main/java/dev/robin/flip_2_dnd/data/repository/OrientationRepositoryImpl.kt
index 88c0398..32c07e0 100644
--- a/app/src/main/java/dev/robin/flip_2_dnd/data/repository/OrientationRepositoryImpl.kt
+++ b/app/src/main/java/dev/robin/flip_2_dnd/data/repository/OrientationRepositoryImpl.kt
@@ -5,9 +5,9 @@ import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
+import dagger.hilt.android.qualifiers.ApplicationContext
import dev.robin.flip_2_dnd.domain.model.PhoneOrientation
import dev.robin.flip_2_dnd.domain.repository.OrientationRepository
-import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import javax.inject.Inject
@@ -16,39 +16,39 @@ import kotlin.math.abs
@Singleton
class OrientationRepositoryImpl @Inject constructor(
- @ApplicationContext private val context: Context
+ @ApplicationContext private val context: Context
) : OrientationRepository, SensorEventListener {
- private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
- private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
- private val _orientation = MutableStateFlow(PhoneOrientation.UNKNOWN)
-
- override fun getOrientation(): Flow = _orientation
-
- override suspend fun startMonitoring() {
- sensorManager.registerListener(
- this,
- accelerometer,
- SensorManager.SENSOR_DELAY_NORMAL
- )
- }
-
- override suspend fun stopMonitoring() {
- sensorManager.unregisterListener(this)
- }
-
- override fun onSensorChanged(event: SensorEvent?) {
- if (event?.sensor?.type == Sensor.TYPE_ACCELEROMETER) {
- val z = event.values[2]
- _orientation.value = when {
- abs(z) > 8.0f && z < 0 -> PhoneOrientation.FACE_DOWN
- // abs(z) > 9.0f && z > 0 -> PhoneOrientation.FACE_UP
- else -> PhoneOrientation.FACE_UP
- }
- }
- }
-
- override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
- // Not needed for this implementation
- }
+ private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
+ private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
+ private val _orientation = MutableStateFlow(PhoneOrientation.UNKNOWN)
+
+ override fun getOrientation(): Flow = _orientation
+
+ override suspend fun startMonitoring() {
+ sensorManager.registerListener(
+ this,
+ accelerometer,
+ SensorManager.SENSOR_DELAY_NORMAL
+ )
+ }
+
+ override suspend fun stopMonitoring() {
+ sensorManager.unregisterListener(this)
+ }
+
+ override fun onSensorChanged(event: SensorEvent?) {
+ if (event?.sensor?.type == Sensor.TYPE_ACCELEROMETER) {
+ val z = event.values[2]
+ _orientation.value = when {
+ abs(z) > 9.5f && z < 0 -> PhoneOrientation.FACE_DOWN
+ // abs(z) > 9.0f && z > 0 -> PhoneOrientation.FACE_UP
+ else -> PhoneOrientation.FACE_UP
+ }
+ }
+ }
+
+ override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
+ // Not needed for this implementation
+ }
}
diff --git a/app/src/main/java/dev/robin/flip_2_dnd/services/AutoStartService.kt b/app/src/main/java/dev/robin/flip_2_dnd/services/AutoStartService.kt
new file mode 100644
index 0000000..7d51a10
--- /dev/null
+++ b/app/src/main/java/dev/robin/flip_2_dnd/services/AutoStartService.kt
@@ -0,0 +1,18 @@
+package dev.robin.flip_2_dnd.services
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import androidx.core.content.ContextCompat
+
+class AutoStartService : BroadcastReceiver() {
+
+ override fun onReceive(context: Context?, intent: Intent?) {
+ if (intent?.action == Intent.ACTION_BOOT_COMPLETED) {
+ context?.let {
+ val serviceIntent = Intent(it, FlipDetectorService::class.java)
+ ContextCompat.startForegroundService(it, serviceIntent)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/dev/robin/flip_2_dnd/services/DndService.kt b/app/src/main/java/dev/robin/flip_2_dnd/services/DndService.kt
index 6e9a956..3dd60cc 100644
--- a/app/src/main/java/dev/robin/flip_2_dnd/services/DndService.kt
+++ b/app/src/main/java/dev/robin/flip_2_dnd/services/DndService.kt
@@ -53,10 +53,9 @@ class DndService(private val context: Context) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- val timings = pattern
val amplitudes = IntArray(pattern.size) { VibrationEffect.DEFAULT_AMPLITUDE }
Log.d(TAG, "Creating waveform vibration for Android O+")
- vibrator.vibrate(VibrationEffect.createWaveform(timings, amplitudes, -1))
+ vibrator.vibrate(VibrationEffect.createWaveform(pattern, amplitudes, -1))
} else {
Log.d(TAG, "Using deprecated vibration method for older Android versions")
@Suppress("DEPRECATION")
@@ -86,13 +85,13 @@ class DndService(private val context: Context) {
}
}
- fun checkDndPermission(): Boolean {
+ private fun checkDndPermission(): Boolean {
val hasPermission = notificationManager.isNotificationPolicyAccessGranted
Log.d(TAG, "DND Permission check: $hasPermission")
return hasPermission
}
- fun openDndSettings() {
+ private fun openDndSettings() {
Log.d(TAG, "Opening DND settings")
val intent = Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
diff --git a/app/src/main/java/dev/robin/flip_2_dnd/services/SensorService.kt b/app/src/main/java/dev/robin/flip_2_dnd/services/SensorService.kt
index 5edb049..dbf3995 100644
--- a/app/src/main/java/dev/robin/flip_2_dnd/services/SensorService.kt
+++ b/app/src/main/java/dev/robin/flip_2_dnd/services/SensorService.kt
@@ -13,129 +13,135 @@ import kotlin.math.abs
private const val TAG = "SensorService"
class SensorService(context: Context) {
- private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
- private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
- private val gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
-
- private val _orientation = MutableStateFlow("Face up")
- val orientation: StateFlow = _orientation
-
- private val _accelerometerData = MutableStateFlow(FloatArray(3) { 0f })
- val accelerometerData: StateFlow = _accelerometerData
-
- private val _gyroscopeData = MutableStateFlow(FloatArray(3) { 0f })
- val gyroscopeData: StateFlow = _gyroscopeData
-
- private var lastAccelReading = FloatArray(3)
- private var lastGyroReading = FloatArray(3)
- private var isProcessing = false
- private var isRegistered = false
-
- init {
- if (accelerometer == null) {
- Log.e(TAG, "No accelerometer sensor found!")
- }
- if (gyroscope == null) {
- Log.e(TAG, "No gyroscope sensor found!")
- }
- }
-
- private val sensorListener = object : SensorEventListener {
- override fun onSensorChanged(event: SensorEvent) {
- when (event.sensor.type) {
- Sensor.TYPE_ACCELEROMETER -> {
- lastAccelReading = event.values.clone()
- _accelerometerData.value = event.values.clone()
- Log.d(TAG, "Accelerometer data: ${lastAccelReading.contentToString()}")
- processOrientation()
- }
- Sensor.TYPE_GYROSCOPE -> {
- lastGyroReading = event.values.clone()
- _gyroscopeData.value = event.values.clone()
- Log.d(TAG, "Gyroscope data: ${lastGyroReading.contentToString()}")
- }
- }
- }
-
- override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
- Log.d(TAG, "Sensor accuracy changed: ${sensor?.name}, accuracy: $accuracy")
- }
- }
-
- fun startMonitoring() {
- if (isRegistered) {
- Log.d(TAG, "Sensors already registered")
- return
- }
-
- if (accelerometer == null || gyroscope == null) {
- Log.e(TAG, "Required sensors not available - Accelerometer: ${accelerometer != null}, Gyroscope: ${gyroscope != null}")
- return
- }
-
- var success = true
-
- success = success && sensorManager.registerListener(
- sensorListener,
- accelerometer,
- SensorManager.SENSOR_DELAY_UI
- )
- if (!success) {
- Log.e(TAG, "Failed to register accelerometer")
- return
- }
-
- success = success && sensorManager.registerListener(
- sensorListener,
- gyroscope,
- SensorManager.SENSOR_DELAY_UI
- )
- if (!success) {
- Log.e(TAG, "Failed to register gyroscope")
- sensorManager.unregisterListener(sensorListener)
- return
- }
-
- isRegistered = true
- Log.d(TAG, "Successfully registered sensor listeners")
- }
-
- fun stopMonitoring() {
- if (!isRegistered) {
- Log.d(TAG, "Sensors not registered")
- return
- }
- sensorManager.unregisterListener(sensorListener)
- isRegistered = false
- Log.d(TAG, "Unregistered sensor listeners")
- }
-
- private fun processOrientation() {
- if (isProcessing) return
- isProcessing = true
-
- val x = lastAccelReading[0]
- val y = lastAccelReading[1]
- val z = lastAccelReading[2]
-
- // Check if the phone is relatively stable (not in motion)
- val isStable = abs(lastGyroReading[0]) < 0.02f &&
- abs(lastGyroReading[1]) < 0.02f &&
- abs(lastGyroReading[2]) < 0.02f
-
- // For face down, check stability. For other orientations, update immediately
- val orientation = when {
- abs(z) > 8.0f && z < 0 -> {
- // Only check stability for face down
- if (isStable) "Face down" else _orientation.value
- }
- else -> "Face up"
- }
-
- if (orientation != _orientation.value) {
- _orientation.value = orientation
- }
-
- isProcessing = false
- }
+ private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
+ private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
+ private val gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
+
+ private val _orientation = MutableStateFlow("Face up")
+ val orientation: StateFlow = _orientation
+
+ private val _accelerometerData = MutableStateFlow(FloatArray(3) { 0f })
+ val accelerometerData: StateFlow = _accelerometerData
+
+ private val _gyroscopeData = MutableStateFlow(FloatArray(3) { 0f })
+ val gyroscopeData: StateFlow = _gyroscopeData
+
+ private var lastAccelReading = FloatArray(3)
+ private var lastGyroReading = FloatArray(3)
+ private var isProcessing = false
+ private var isRegistered = false
+
+ init {
+ if (accelerometer == null) {
+ Log.e(TAG, "No accelerometer sensor found!")
+ }
+ if (gyroscope == null) {
+ Log.e(TAG, "No gyroscope sensor found!")
+ }
+ }
+
+ private val sensorListener = object : SensorEventListener {
+ override fun onSensorChanged(event: SensorEvent) {
+ when (event.sensor.type) {
+ Sensor.TYPE_ACCELEROMETER -> {
+ lastAccelReading = event.values.clone()
+ _accelerometerData.value = event.values.clone()
+ Log.d(TAG, "Accelerometer data: ${lastAccelReading.contentToString()}")
+ processOrientation()
+ }
+
+ Sensor.TYPE_GYROSCOPE -> {
+ lastGyroReading = event.values.clone()
+ _gyroscopeData.value = event.values.clone()
+ Log.d(TAG, "Gyroscope data: ${lastGyroReading.contentToString()}")
+ }
+ }
+ }
+
+ override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
+ Log.d(TAG, "Sensor accuracy changed: ${sensor?.name}, accuracy: $accuracy")
+ }
+ }
+
+ fun startMonitoring() {
+ if (isRegistered) {
+ Log.d(TAG, "Sensors already registered")
+ return
+ }
+
+ if (accelerometer == null || gyroscope == null) {
+ Log.e(
+ TAG,
+ "Required sensors not available - Accelerometer: ${accelerometer != null}, Gyroscope: ${gyroscope != null}"
+ )
+ return
+ }
+
+ var success = true
+
+ success = success && sensorManager.registerListener(
+ sensorListener,
+ accelerometer,
+ SensorManager.SENSOR_DELAY_UI
+ )
+ if (!success) {
+ Log.e(TAG, "Failed to register accelerometer")
+ return
+ }
+
+ success = success && sensorManager.registerListener(
+ sensorListener,
+ gyroscope,
+ SensorManager.SENSOR_DELAY_UI
+ )
+ if (!success) {
+ Log.e(TAG, "Failed to register gyroscope")
+ sensorManager.unregisterListener(sensorListener)
+ return
+ }
+
+ isRegistered = true
+ Log.d(TAG, "Successfully registered sensor listeners")
+ }
+
+ fun stopMonitoring() {
+ if (!isRegistered) {
+ Log.d(TAG, "Sensors not registered")
+ return
+ }
+ sensorManager.unregisterListener(sensorListener)
+ isRegistered = false
+ Log.d(TAG, "Unregistered sensor listeners")
+ }
+
+ private fun processOrientation() {
+ if (isProcessing) return
+ isProcessing = true
+
+ val x = lastAccelReading[0]
+ val y = lastAccelReading[1]
+ val z = lastAccelReading[2]
+
+ // Check if the phone is relatively stable (not in motion)
+ val isStable = abs(lastGyroReading[0]) < 0.02f &&
+ abs(lastGyroReading[1]) < 0.02f &&
+ abs(lastGyroReading[2]) < 0.02f
+
+ // For face down, check stability. For other orientations, update immediately
+ val orientation = when {
+ abs(z) >= 9.5f && z < 0 -> {
+ // Only check stability for face down
+ Log.d(TAG, "z value: " + abs(z) + " " + z)
+ if (isStable) "Face down" else _orientation.value
+ }
+
+ else -> "Face up"
+ }
+
+ if (orientation != _orientation.value) {
+ _orientation.value = orientation
+ }
+
+ isProcessing = false
+ }
}
diff --git a/metadata/en-US/changelogs/8.txt b/metadata/en-US/changelogs/8.txt
new file mode 100644
index 0000000..e69de29