mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-26 00:05:22 +00:00
add location services check
This commit is contained in:
@@ -0,0 +1,297 @@
|
||||
package com.bitchat.android.onboarding
|
||||
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Screen shown when checking location services status or requesting location services enable
|
||||
*/
|
||||
@Composable
|
||||
fun LocationCheckScreen(
|
||||
status: LocationStatus,
|
||||
onEnableLocation: () -> Unit,
|
||||
onRetry: () -> Unit,
|
||||
isLoading: Boolean = false
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(32.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
when (status) {
|
||||
LocationStatus.DISABLED -> {
|
||||
LocationDisabledContent(
|
||||
onEnableLocation = onEnableLocation,
|
||||
onRetry = onRetry,
|
||||
colorScheme = colorScheme,
|
||||
isLoading = isLoading
|
||||
)
|
||||
}
|
||||
LocationStatus.NOT_AVAILABLE -> {
|
||||
LocationNotAvailableContent(
|
||||
colorScheme = colorScheme
|
||||
)
|
||||
}
|
||||
LocationStatus.ENABLED -> {
|
||||
LocationCheckingContent(
|
||||
colorScheme = colorScheme
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LocationDisabledContent(
|
||||
onEnableLocation: () -> Unit,
|
||||
onRetry: () -> Unit,
|
||||
colorScheme: ColorScheme,
|
||||
isLoading: Boolean
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// Location icon - using LocationOn outlined icon in app's green color
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.LocationOn,
|
||||
contentDescription = "Location Services",
|
||||
modifier = Modifier.size(64.dp),
|
||||
tint = Color(0xFF00C851) // App's main green color
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "Location Services Required",
|
||||
style = MaterialTheme.typography.headlineSmall.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = colorScheme.primary
|
||||
),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = colorScheme.surfaceVariant.copy(alpha = 0.3f)
|
||||
),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
// Privacy assurance section
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Security,
|
||||
contentDescription = "Privacy",
|
||||
tint = Color(0xFF4CAF50),
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "Privacy First",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = colorScheme.onSurface
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "bitchat does NOT track your location or use GPS.\n\nLocation services are required by Android for Bluetooth scanning to work properly. This is an Android system requirement.",
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.8f)
|
||||
)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
Text(
|
||||
text = "bitchat needs location services for:",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = colorScheme.onSurface
|
||||
),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "• Bluetooth device scanning (Android requirement)\n" +
|
||||
"• Discovering nearby users on mesh network\n" +
|
||||
"• Creating connections without internet\n" +
|
||||
"• No GPS tracking or location collection",
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.8f)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
LocationLoadingIndicator()
|
||||
} else {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Button(
|
||||
onClick = onEnableLocation,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color(0xFF00C851) // App's main green color
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = "Open Location Settings",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold
|
||||
),
|
||||
modifier = Modifier.padding(vertical = 4.dp)
|
||||
)
|
||||
}
|
||||
|
||||
OutlinedButton(
|
||||
onClick = onRetry,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = "Check Again",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace
|
||||
),
|
||||
modifier = Modifier.padding(vertical = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LocationNotAvailableContent(
|
||||
colorScheme: ColorScheme
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// Error icon
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ErrorOutline,
|
||||
contentDescription = "Error",
|
||||
modifier = Modifier.size(64.dp),
|
||||
tint = colorScheme.error
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "Location Services Unavailable",
|
||||
style = MaterialTheme.typography.headlineSmall.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = colorScheme.error
|
||||
),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = colorScheme.errorContainer.copy(alpha = 0.1f)
|
||||
),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Location services are not available on this device. This is unusual as location services are standard on Android devices.\n\nbitchat needs location services for Bluetooth scanning to work properly (Android requirement). Without this, the app cannot discover nearby users.",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface
|
||||
),
|
||||
modifier = Modifier.padding(16.dp),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LocationCheckingContent(
|
||||
colorScheme: ColorScheme
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(32.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "bitchat*",
|
||||
style = MaterialTheme.typography.headlineLarge.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = colorScheme.primary
|
||||
),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
LocationLoadingIndicator()
|
||||
|
||||
Text(
|
||||
text = "Checking location services...",
|
||||
style = MaterialTheme.typography.bodyLarge.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.7f)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LocationLoadingIndicator() {
|
||||
// Animated rotation for the loading indicator
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "location_loading")
|
||||
val rotationAngle by infiniteTransition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 360f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(durationMillis = 2000, easing = LinearEasing),
|
||||
repeatMode = RepeatMode.Restart
|
||||
),
|
||||
label = "rotation"
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier.size(60.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.rotate(rotationAngle),
|
||||
color = Color(0xFF4CAF50), // Location green
|
||||
strokeWidth = 3.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
package com.bitchat.android.onboarding
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.location.LocationManager
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
|
||||
/**
|
||||
* Manages Location Services enable/disable state and user prompts
|
||||
* Checks location services status on every app startup
|
||||
* Note: This is for system location services, not location permissions
|
||||
*/
|
||||
class LocationStatusManager(
|
||||
private val activity: ComponentActivity,
|
||||
private val context: Context,
|
||||
private val onLocationEnabled: () -> Unit,
|
||||
private val onLocationDisabled: (String) -> Unit
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "LocationStatusManager"
|
||||
}
|
||||
|
||||
private var locationSettingsLauncher: ActivityResultLauncher<Intent>? = null
|
||||
private var locationManager: LocationManager? = null
|
||||
private var locationStateReceiver: BroadcastReceiver? = null
|
||||
|
||||
init {
|
||||
setupLocationManager()
|
||||
setupLocationSettingsLauncher()
|
||||
setupLocationStateReceiver()
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup LocationManager reference
|
||||
*/
|
||||
private fun setupLocationManager() {
|
||||
try {
|
||||
locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
Log.d(TAG, "LocationManager initialized: ${locationManager != null}")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to initialize LocationManager", e)
|
||||
locationManager = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup launcher for location settings request
|
||||
*/
|
||||
private fun setupLocationSettingsLauncher() {
|
||||
locationSettingsLauncher = activity.registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
val isEnabled = isLocationEnabled()
|
||||
Log.d(TAG, "Location settings request result: $isEnabled (result code: ${result.resultCode})")
|
||||
if (isEnabled) {
|
||||
onLocationEnabled()
|
||||
} else {
|
||||
onLocationDisabled("Location services are required for Bluetooth scanning on Android. Please enable location services to continue.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup broadcast receiver to listen for location settings changes
|
||||
*/
|
||||
private fun setupLocationStateReceiver() {
|
||||
locationStateReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action == LocationManager.MODE_CHANGED_ACTION ||
|
||||
intent.action == LocationManager.PROVIDERS_CHANGED_ACTION) {
|
||||
Log.d(TAG, "Location settings changed, checking status")
|
||||
val isEnabled = isLocationEnabled()
|
||||
if (isEnabled) {
|
||||
onLocationEnabled()
|
||||
} else {
|
||||
onLocationDisabled("Location services have been disabled.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register receiver for location changes
|
||||
val filter = IntentFilter().apply {
|
||||
addAction(LocationManager.MODE_CHANGED_ACTION)
|
||||
addAction(LocationManager.PROVIDERS_CHANGED_ACTION)
|
||||
}
|
||||
context.registerReceiver(locationStateReceiver, filter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if location services are enabled (system-wide setting)
|
||||
* Uses proper API depending on Android version
|
||||
*/
|
||||
fun isLocationEnabled(): Boolean {
|
||||
return try {
|
||||
locationManager?.let { lm ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
// API 28+ (Android 9) - Modern approach
|
||||
lm.isLocationEnabled
|
||||
} else {
|
||||
// Older devices - Check individual providers
|
||||
lm.isProviderEnabled(LocationManager.GPS_PROVIDER) ||
|
||||
lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
||||
}
|
||||
} ?: false
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Error checking location enabled state: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check location services status
|
||||
* This should be called on every app startup
|
||||
*/
|
||||
fun checkLocationStatus(): LocationStatus {
|
||||
Log.d(TAG, "Checking location services status")
|
||||
|
||||
return when {
|
||||
locationManager == null -> {
|
||||
Log.e(TAG, "LocationManager not available on this device")
|
||||
LocationStatus.NOT_AVAILABLE
|
||||
}
|
||||
!isLocationEnabled() -> {
|
||||
Log.w(TAG, "Location services are disabled")
|
||||
LocationStatus.DISABLED
|
||||
}
|
||||
else -> {
|
||||
Log.d(TAG, "Location services are enabled and ready")
|
||||
LocationStatus.ENABLED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request user to enable location services
|
||||
* Opens system location settings screen
|
||||
*/
|
||||
fun requestEnableLocation() {
|
||||
Log.d(TAG, "Requesting user to enable location services")
|
||||
|
||||
try {
|
||||
val enableLocationIntent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
|
||||
locationSettingsLauncher?.launch(enableLocationIntent)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to request location enable", e)
|
||||
onLocationDisabled("Failed to open location settings: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle location status check result
|
||||
*/
|
||||
fun handleLocationStatus(status: LocationStatus) {
|
||||
when (status) {
|
||||
LocationStatus.ENABLED -> {
|
||||
Log.d(TAG, "Location services enabled, proceeding")
|
||||
onLocationEnabled()
|
||||
}
|
||||
LocationStatus.DISABLED -> {
|
||||
Log.d(TAG, "Location services disabled, requesting enable")
|
||||
requestEnableLocation()
|
||||
}
|
||||
LocationStatus.NOT_AVAILABLE -> {
|
||||
Log.e(TAG, "Location services not available")
|
||||
onLocationDisabled("Location services are not available on this device.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user-friendly status message
|
||||
*/
|
||||
fun getStatusMessage(status: LocationStatus): String {
|
||||
return when (status) {
|
||||
LocationStatus.ENABLED -> "Location services are enabled and ready"
|
||||
LocationStatus.DISABLED -> "Location services are disabled. Please enable location services for Bluetooth scanning."
|
||||
LocationStatus.NOT_AVAILABLE -> "Location services are not available on this device."
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed diagnostics
|
||||
*/
|
||||
fun getDiagnostics(): String {
|
||||
return buildString {
|
||||
appendLine("Location Services Status Diagnostics:")
|
||||
appendLine("LocationManager available: ${locationManager != null}")
|
||||
appendLine("Location services enabled: ${isLocationEnabled()}")
|
||||
appendLine("Current status: ${checkLocationStatus()}")
|
||||
appendLine("Android version: ${Build.VERSION.SDK_INT}")
|
||||
|
||||
locationManager?.let { lm ->
|
||||
try {
|
||||
appendLine("GPS provider enabled: ${lm.isProviderEnabled(LocationManager.GPS_PROVIDER)}")
|
||||
appendLine("Network provider enabled: ${lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER)}")
|
||||
} catch (e: Exception) {
|
||||
appendLine("Provider details: [Error: ${e.message}]")
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
appendLine("Using modern isLocationEnabled() API")
|
||||
} else {
|
||||
appendLine("Using legacy provider check API")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log current location status for debugging
|
||||
*/
|
||||
fun logLocationStatus() {
|
||||
Log.d(TAG, getDiagnostics())
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup resources - call this when activity is destroyed
|
||||
*/
|
||||
fun cleanup() {
|
||||
locationStateReceiver?.let { receiver ->
|
||||
try {
|
||||
context.unregisterReceiver(receiver)
|
||||
Log.d(TAG, "Location state receiver unregistered")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Error unregistering location state receiver: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Location services status enum
|
||||
*/
|
||||
enum class LocationStatus {
|
||||
ENABLED,
|
||||
DISABLED,
|
||||
NOT_AVAILABLE
|
||||
}
|
||||
Reference in New Issue
Block a user