diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b5d316e1..316a39f1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,7 +6,8 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/canopas/yourspace/domain/receiver/BatteryBroadcastReceiver.kt b/app/src/main/java/com/canopas/yourspace/domain/receiver/BatteryBroadcastReceiver.kt
new file mode 100644
index 00000000..8c174fc4
--- /dev/null
+++ b/app/src/main/java/com/canopas/yourspace/domain/receiver/BatteryBroadcastReceiver.kt
@@ -0,0 +1,34 @@
+package com.canopas.yourspace.domain.receiver
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.os.BatteryManager
+import com.canopas.yourspace.data.service.auth.AuthService
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import timber.log.Timber
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class BatteryBroadcastReceiver @Inject constructor(
+ private val authService: AuthService
+) : BroadcastReceiver() {
+
+ override fun onReceive(context: Context?, intent: Intent?) {
+ if (intent?.action == Intent.ACTION_BATTERY_OKAY || intent?.action == Intent.ACTION_BATTERY_LOW) {
+ CoroutineScope(Dispatchers.IO).launch {
+ try {
+ val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
+ val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
+ val batteryPct = level / scale.toFloat() * 100
+ authService.updateBatteryStatus(batteryPct)
+ } catch (e: Exception) {
+ Timber.e(e, "Failed to update battery status")
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/canopas/yourspace/domain/receiver/BootCompleteReceiver.kt b/app/src/main/java/com/canopas/yourspace/domain/receiver/BootCompleteReceiver.kt
new file mode 100644
index 00000000..2c1f6e28
--- /dev/null
+++ b/app/src/main/java/com/canopas/yourspace/domain/receiver/BootCompleteReceiver.kt
@@ -0,0 +1,25 @@
+package com.canopas.yourspace.domain.receiver
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import com.canopas.yourspace.data.service.auth.AuthService
+import com.canopas.yourspace.data.service.location.LocationManager
+import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class BootCompleteReceiver : BroadcastReceiver() {
+
+ @Inject
+ lateinit var locationManager: LocationManager
+
+ @Inject
+ lateinit var authService: AuthService
+
+ override fun onReceive(context: Context, intent: Intent) {
+ if (Intent.ACTION_BOOT_COMPLETED == intent.action && authService.currentUser != null) {
+ locationManager.startService()
+ }
+ }
+}
diff --git a/app/src/main/java/com/canopas/yourspace/ui/component/BackgroudPermissionCheck.kt b/app/src/main/java/com/canopas/yourspace/ui/component/BackgroudPermissionCheck.kt
index bd5320b3..ee15c56b 100644
--- a/app/src/main/java/com/canopas/yourspace/ui/component/BackgroudPermissionCheck.kt
+++ b/app/src/main/java/com/canopas/yourspace/ui/component/BackgroudPermissionCheck.kt
@@ -5,6 +5,7 @@ import android.app.Activity
import android.os.Build
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -14,6 +15,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
@@ -100,6 +102,8 @@ fun PermissionDialog(
title: String,
subTitle1: String,
subTitle2: String? = null,
+ dismissBtn: String? = null,
+ confirmBtn: String? = null,
onDismiss: () -> Unit,
goToSettings: () -> Unit
) {
@@ -120,7 +124,7 @@ fun PermissionDialog(
modifier = Modifier
.padding(top = 5.dp)
.fillMaxWidth(),
- style = AppTheme.appTypography.header1
+ style = AppTheme.appTypography.header3
)
Text(
text = subTitle1,
@@ -128,7 +132,7 @@ fun PermissionDialog(
modifier = Modifier
.padding(vertical = 10.dp)
.fillMaxWidth(),
- style = AppTheme.appTypography.body2
+ style = AppTheme.appTypography.body1
)
if (subTitle2 != null) {
@@ -142,12 +146,21 @@ fun PermissionDialog(
)
}
+ Spacer(modifier = Modifier.padding(10.dp))
+
PrimaryButton(
- modifier = Modifier
- .padding(vertical = 10.dp),
- label = stringResource(id = R.string.common_background_access_permission_btn),
+ label = if (!confirmBtn.isNullOrEmpty()) confirmBtn else stringResource(id = R.string.common_background_access_permission_btn),
onClick = goToSettings
)
+
+ dismissBtn?.let {
+ Spacer(modifier = Modifier.padding(4.dp))
+ PrimaryTextButton(
+ label = dismissBtn,
+ onClick = onDismiss,
+ containerColor = Color.Transparent
+ )
+ }
}
}
}
diff --git a/app/src/main/java/com/canopas/yourspace/ui/flow/home/home/HomeScreen.kt b/app/src/main/java/com/canopas/yourspace/ui/flow/home/home/HomeScreen.kt
index 139a7e47..484a1788 100644
--- a/app/src/main/java/com/canopas/yourspace/ui/flow/home/home/HomeScreen.kt
+++ b/app/src/main/java/com/canopas/yourspace/ui/flow/home/home/HomeScreen.kt
@@ -1,5 +1,8 @@
package com.canopas.yourspace.ui.flow.home.home
+import android.content.Intent
+import android.net.Uri
+import android.provider.Settings
import androidx.annotation.DrawableRes
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.animateFloatAsState
@@ -37,7 +40,9 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
import com.canopas.yourspace.R
import com.canopas.yourspace.data.utils.isBackgroundLocationPermissionGranted
+import com.canopas.yourspace.data.utils.isBatteryOptimizationEnabled
import com.canopas.yourspace.ui.component.AppProgressIndicator
+import com.canopas.yourspace.ui.component.PermissionDialog
import com.canopas.yourspace.ui.flow.home.activity.ActivityScreen
import com.canopas.yourspace.ui.flow.home.home.component.SpaceSelectionMenu
import com.canopas.yourspace.ui.flow.home.home.component.SpaceSelectionPopup
@@ -59,6 +64,12 @@ fun HomeScreen() {
}
}
+ LaunchedEffect(Unit) {
+ if (context.isBatteryOptimizationEnabled) {
+ viewModel.showBatteryOptimizationDialog()
+ }
+ }
+
Scaffold(
containerColor = AppTheme.colorScheme.surface,
content = {
@@ -144,6 +155,23 @@ fun HomeTopBar() {
}
}
}
+
+ if (state.showBatteryOptimizationPopup) {
+ val context = LocalContext.current
+ PermissionDialog(
+ title = stringResource(R.string.battery_optimization_dialog_title),
+ subTitle1 = stringResource(R.string.battery_optimization_dialog_message),
+ dismissBtn = stringResource(R.string.common_btn_cancel),
+ confirmBtn = stringResource(R.string.battery_optimization_dialog_btn_change_now),
+ onDismiss = { viewModel.dismissBatteryOptimizationDialog() },
+ goToSettings = {
+ val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
+ intent.data = Uri.parse("package:${context.packageName}")
+ context.startActivity(intent)
+ viewModel.dismissBatteryOptimizationDialog()
+ }
+ )
+ }
}
@Composable
diff --git a/app/src/main/java/com/canopas/yourspace/ui/flow/home/home/HomeScreenViewModel.kt b/app/src/main/java/com/canopas/yourspace/ui/flow/home/home/HomeScreenViewModel.kt
index 697276b3..e1ec3924 100644
--- a/app/src/main/java/com/canopas/yourspace/ui/flow/home/home/HomeScreenViewModel.kt
+++ b/app/src/main/java/com/canopas/yourspace/ui/flow/home/home/HomeScreenViewModel.kt
@@ -11,6 +11,7 @@ import com.canopas.yourspace.data.utils.AppDispatcher
import com.canopas.yourspace.ui.navigation.AppDestinations
import com.canopas.yourspace.ui.navigation.AppNavigator
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
@@ -169,6 +170,15 @@ class HomeScreenViewModel @Inject constructor(
if (spaceRepository.currentSpaceId.isEmpty()) return
navigator.navigateTo(AppDestinations.spaceThreads.path)
}
+
+ fun showBatteryOptimizationDialog() = viewModelScope.launch(appDispatcher.IO) {
+ delay(500)
+ _state.value = _state.value.copy(showBatteryOptimizationPopup = true)
+ }
+
+ fun dismissBatteryOptimizationDialog() {
+ _state.value = _state.value.copy(showBatteryOptimizationPopup = false)
+ }
}
data class HomeScreenState(
@@ -180,5 +190,6 @@ data class HomeScreenState(
val showSpaceSelectionPopup: Boolean = false,
val locationEnabled: Boolean = true,
val enablingLocation: Boolean = false,
+ val showBatteryOptimizationPopup: Boolean = false,
val error: Exception? = null
)
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2543d9f2..3b2d8f6b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -203,4 +203,8 @@
Thanks! Your feedback has been recorded.
Spaces
+ Turn off Battery Optimization for Location Sharing
+ For location sharing feature to work properly for your Spaces, turn off Battery Optimization in your phone settings for the YourSpace app. This won\'t affect your other apps.
+ Change now
+
\ No newline at end of file
diff --git a/data/src/main/java/com/canopas/yourspace/data/models/user/ApiUser.kt b/data/src/main/java/com/canopas/yourspace/data/models/user/ApiUser.kt
index 11aff737..1eb8a0d2 100644
--- a/data/src/main/java/com/canopas/yourspace/data/models/user/ApiUser.kt
+++ b/data/src/main/java/com/canopas/yourspace/data/models/user/ApiUser.kt
@@ -2,6 +2,7 @@ package com.canopas.yourspace.data.models.user
import androidx.annotation.Keep
import com.google.firebase.firestore.Exclude
+import com.squareup.moshi.JsonClass
import java.util.UUID
const val LOGIN_TYPE_GOOGLE = 1
@@ -31,6 +32,7 @@ data class ApiUser(
}
@Keep
+@JsonClass(generateAdapter = true)
data class ApiUserSession(
val id: String = UUID.randomUUID().toString(),
val user_id: String = "",
@@ -40,6 +42,6 @@ data class ApiUserSession(
val platform: Int = LOGIN_DEVICE_TYPE_ANDROID,
val session_active: Boolean = true,
val app_version: Long? = 0,
- val battery_status: String? = "",
+ val battery_pct: Float? = 0f,
val created_at: Long? = System.currentTimeMillis()
)
diff --git a/data/src/main/java/com/canopas/yourspace/data/service/auth/AuthService.kt b/data/src/main/java/com/canopas/yourspace/data/service/auth/AuthService.kt
index 8345978b..f3a2ab5b 100644
--- a/data/src/main/java/com/canopas/yourspace/data/service/auth/AuthService.kt
+++ b/data/src/main/java/com/canopas/yourspace/data/service/auth/AuthService.kt
@@ -2,6 +2,7 @@ package com.canopas.yourspace.data.service.auth
import com.canopas.yourspace.data.models.user.ApiUser
import com.canopas.yourspace.data.models.user.ApiUserSession
+import com.canopas.yourspace.data.service.location.LocationManager
import com.canopas.yourspace.data.service.user.ApiUserService
import com.canopas.yourspace.data.storage.UserPreferences
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
@@ -13,7 +14,8 @@ import javax.inject.Singleton
class AuthService @Inject constructor(
private val userPreferences: UserPreferences,
private val apiUserService: ApiUserService,
- private val firebaseAuth: FirebaseAuth
+ private val firebaseAuth: FirebaseAuth,
+ private val locationManager: LocationManager
) {
private val authStateChangeListeners = HashSet()
@@ -99,6 +101,7 @@ class AuthService @Inject constructor(
userPreferences.setOnboardShown(false)
userPreferences.currentSpace = ""
firebaseAuth.signOut()
+ locationManager.stopService()
}
suspend fun deleteAccount() {
@@ -109,6 +112,18 @@ class AuthService @Inject constructor(
suspend fun getUser(): ApiUser? = apiUserService.getUser(currentUser?.id ?: "")
suspend fun getUserFlow() = apiUserService.getUserFlow(currentUser?.id ?: "")
+
+ suspend fun updateBatteryStatus(batteryPercentage: Float) {
+ val currentUser = currentUser ?: return
+ val session = currentUserSession ?: return
+ apiUserService.updateBatteryPct(currentUser.id, session.id, batteryPercentage)
+ }
+
+ suspend fun updateUserSessionState(state: Int) {
+ val currentUser = currentUser ?: return
+ val session = currentUserSession ?: return
+ apiUserService.updateSessionState(currentUser.id, session.id, state)
+ }
}
interface AuthStateChangeListener {
diff --git a/data/src/main/java/com/canopas/yourspace/data/service/location/LocationManager.kt b/data/src/main/java/com/canopas/yourspace/data/service/location/LocationManager.kt
index 500ea262..f1d1b223 100644
--- a/data/src/main/java/com/canopas/yourspace/data/service/location/LocationManager.kt
+++ b/data/src/main/java/com/canopas/yourspace/data/service/location/LocationManager.kt
@@ -62,13 +62,13 @@ class LocationManager @Inject constructor(@ApplicationContext private val contex
setWaitForAccurateLocation(true)
}.build()
- fun startLocationTracking() {
+ internal fun startLocationTracking() {
if (context.hasFineLocationPermission) {
locationClient.requestLocationUpdates(request, locationUpdatePendingIntent)
}
}
- fun stopLocationTracking() {
+ internal fun stopLocationTracking() {
if (!context.isBackgroundLocationPermissionGranted) {
locationClient.flushLocations()
locationClient.removeLocationUpdates(locationUpdatePendingIntent)
@@ -78,4 +78,8 @@ class LocationManager @Inject constructor(@ApplicationContext private val contex
fun startService() {
context.startService(Intent(context, BackgroundLocationService::class.java))
}
+
+ fun stopService() {
+ context.stopService(Intent(context, BackgroundLocationService::class.java))
+ }
}
diff --git a/data/src/main/java/com/canopas/yourspace/data/service/user/ApiUserService.kt b/data/src/main/java/com/canopas/yourspace/data/service/user/ApiUserService.kt
index 37b7c54a..a6c4d43c 100644
--- a/data/src/main/java/com/canopas/yourspace/data/service/user/ApiUserService.kt
+++ b/data/src/main/java/com/canopas/yourspace/data/service/user/ApiUserService.kt
@@ -109,4 +109,22 @@ class ApiUserService @Inject constructor(
suspend fun addSpaceId(userId: String, spaceId: String) {
userRef.document(userId).update("space_ids", FieldValue.arrayUnion(spaceId)).await()
}
+
+ suspend fun updateBatteryPct(userId: String, sessionId: String, batteryPct: Float) {
+ sessionRef(userId).document(sessionId).update(
+ "battery_pct",
+ batteryPct,
+ "updated_at",
+ FieldValue.serverTimestamp()
+ ).await()
+ }
+
+ suspend fun updateSessionState(id: String, id1: String, state: Int) {
+ sessionRef(id).document(id1).update(
+ "user_state",
+ state,
+ "updated_at",
+ FieldValue.serverTimestamp()
+ ).await()
+ }
}
diff --git a/data/src/main/java/com/canopas/yourspace/data/utils/PermissionExts.kt b/data/src/main/java/com/canopas/yourspace/data/utils/PermissionExts.kt
index 66110f1c..78ff19a2 100644
--- a/data/src/main/java/com/canopas/yourspace/data/utils/PermissionExts.kt
+++ b/data/src/main/java/com/canopas/yourspace/data/utils/PermissionExts.kt
@@ -7,6 +7,7 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
+import android.os.PowerManager
import android.provider.Settings
import androidx.core.app.ActivityCompat
@@ -53,3 +54,13 @@ val Context.hasNotificationPermission
}
val Context.hasAllPermission get() = isLocationPermissionGranted
+
+val Context.isBatteryOptimizationEnabled: Boolean
+ get() {
+ val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ powerManager.isIgnoringBatteryOptimizations(packageName).not()
+ } else {
+ false
+ }
+ }