mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-09-19 04:59:59 +00:00
Merge 27a13094de1d81c5c177b1ce2bd1a301ba2a6afc into c127eb83ab94c069c32d37530d2faecd381cd2a8
This commit is contained in:
commit
5193a8956a
@ -4,17 +4,9 @@ import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
@ -22,27 +14,19 @@ import androidx.lifecycle.Lifecycle
|
||||
import com.bitchat.android.mesh.BluetoothMeshService
|
||||
import com.bitchat.android.mesh.MeshService
|
||||
import com.bitchat.android.geohash.LocationChannelManager
|
||||
import com.bitchat.android.onboarding.BluetoothCheckScreen
|
||||
import com.bitchat.android.onboarding.BluetoothStatus
|
||||
import com.bitchat.android.onboarding.BluetoothStatusManager
|
||||
import com.bitchat.android.onboarding.BatteryOptimizationManager
|
||||
import com.bitchat.android.onboarding.BatteryOptimizationPreferenceManager
|
||||
import com.bitchat.android.onboarding.BatteryOptimizationScreen
|
||||
import com.bitchat.android.onboarding.BatteryOptimizationStatus
|
||||
import com.bitchat.android.onboarding.BackgroundLocationPermissionScreen
|
||||
import com.bitchat.android.onboarding.InitializationErrorScreen
|
||||
import com.bitchat.android.onboarding.InitializingScreen
|
||||
import com.bitchat.android.onboarding.LocationCheckScreen
|
||||
import com.bitchat.android.onboarding.LocationStatus
|
||||
import com.bitchat.android.onboarding.LocationStatusManager
|
||||
import com.bitchat.android.onboarding.OnboardingCoordinator
|
||||
import com.bitchat.android.onboarding.OnboardingState
|
||||
import com.bitchat.android.onboarding.PermissionExplanationScreen
|
||||
import com.bitchat.android.onboarding.PermissionManager
|
||||
import com.bitchat.android.ui.ChatScreen
|
||||
import com.bitchat.android.ui.BitchatApp
|
||||
import com.bitchat.android.ui.ChatViewModel
|
||||
import com.bitchat.android.ui.OrientationAwareActivity
|
||||
import com.bitchat.android.ui.theme.BitchatTheme
|
||||
import com.bitchat.android.wifiaware.WifiAwareController
|
||||
import com.bitchat.android.nostr.PoWPreferenceManager
|
||||
import com.bitchat.android.services.VerificationService
|
||||
@ -155,17 +139,20 @@ class MainActivity : OrientationAwareActivity() {
|
||||
)
|
||||
|
||||
setContent {
|
||||
BitchatTheme {
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
containerColor = MaterialTheme.colorScheme.background
|
||||
) { innerPadding ->
|
||||
OnboardingFlowScreen(modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
)
|
||||
}
|
||||
}
|
||||
BitchatApp(
|
||||
mainViewModel = mainViewModel,
|
||||
chatViewModel = chatViewModel,
|
||||
permissionManager = permissionManager,
|
||||
onboardingCoordinator = onboardingCoordinator,
|
||||
bluetoothStatusManager = bluetoothStatusManager,
|
||||
locationStatusManager = locationStatusManager,
|
||||
batteryOptimizationManager = batteryOptimizationManager,
|
||||
onCheckBluetoothAndProceed = ::checkBluetoothAndProceed,
|
||||
onCheckLocationAndProceed = ::checkLocationAndProceed,
|
||||
onCheckBatteryOptimizationAndProceed = ::checkBatteryOptimizationAndProceed,
|
||||
onProceedWithPermissionCheck = ::proceedWithPermissionCheck,
|
||||
onCheckOnboardingStatus = ::checkOnboardingStatus
|
||||
)
|
||||
}
|
||||
|
||||
// Collect state changes in a lifecycle-aware manner
|
||||
@ -195,159 +182,7 @@ class MainActivity : OrientationAwareActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OnboardingFlowScreen(modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
val onboardingState by mainViewModel.onboardingState.collectAsState()
|
||||
val bluetoothStatus by mainViewModel.bluetoothStatus.collectAsState()
|
||||
val locationStatus by mainViewModel.locationStatus.collectAsState()
|
||||
val batteryOptimizationStatus by mainViewModel.batteryOptimizationStatus.collectAsState()
|
||||
val errorMessage by mainViewModel.errorMessage.collectAsState()
|
||||
val isBluetoothLoading by mainViewModel.isBluetoothLoading.collectAsState()
|
||||
val isLocationLoading by mainViewModel.isLocationLoading.collectAsState()
|
||||
val isBatteryOptimizationLoading by mainViewModel.isBatteryOptimizationLoading.collectAsState()
|
||||
|
||||
DisposableEffect(context, bluetoothStatusManager) {
|
||||
|
||||
val receiver = bluetoothStatusManager.monitorBluetoothState(
|
||||
context = context,
|
||||
bluetoothStatusManager = bluetoothStatusManager,
|
||||
onBluetoothStateChanged = { status ->
|
||||
if (status == BluetoothStatus.ENABLED && onboardingState == OnboardingState.BLUETOOTH_CHECK) {
|
||||
checkBluetoothAndProceed()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onDispose {
|
||||
try {
|
||||
context.unregisterReceiver(receiver)
|
||||
} catch (e: IllegalStateException) {
|
||||
Log.w("BluetoothStatusUI", "Receiver was not registered")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (onboardingState) {
|
||||
OnboardingState.PERMISSION_REQUESTING -> {
|
||||
InitializingScreen(modifier)
|
||||
}
|
||||
|
||||
OnboardingState.BLUETOOTH_CHECK -> {
|
||||
BluetoothCheckScreen(
|
||||
modifier = modifier,
|
||||
status = bluetoothStatus,
|
||||
onEnableBluetooth = {
|
||||
mainViewModel.updateBluetoothLoading(true)
|
||||
bluetoothStatusManager.requestEnableBluetooth()
|
||||
},
|
||||
onRetry = {
|
||||
checkBluetoothAndProceed()
|
||||
},
|
||||
onSkip = {
|
||||
mainViewModel.skipBluetoothCheck()
|
||||
checkLocationAndProceed()
|
||||
},
|
||||
isLoading = isBluetoothLoading
|
||||
)
|
||||
}
|
||||
|
||||
OnboardingState.LOCATION_CHECK -> {
|
||||
LocationCheckScreen(
|
||||
modifier = modifier,
|
||||
status = locationStatus,
|
||||
onEnableLocation = {
|
||||
mainViewModel.updateLocationLoading(true)
|
||||
locationStatusManager.requestEnableLocation()
|
||||
},
|
||||
onRetry = {
|
||||
checkLocationAndProceed()
|
||||
},
|
||||
isLoading = isLocationLoading
|
||||
)
|
||||
}
|
||||
|
||||
OnboardingState.BATTERY_OPTIMIZATION_CHECK -> {
|
||||
BatteryOptimizationScreen(
|
||||
modifier = modifier,
|
||||
status = batteryOptimizationStatus,
|
||||
onDisableBatteryOptimization = {
|
||||
mainViewModel.updateBatteryOptimizationLoading(true)
|
||||
batteryOptimizationManager.requestDisableBatteryOptimization()
|
||||
},
|
||||
onRetry = {
|
||||
checkBatteryOptimizationAndProceed()
|
||||
},
|
||||
onSkip = {
|
||||
// Skip battery optimization and proceed
|
||||
proceedWithPermissionCheck()
|
||||
},
|
||||
isLoading = isBatteryOptimizationLoading
|
||||
)
|
||||
}
|
||||
|
||||
OnboardingState.PERMISSION_EXPLANATION -> {
|
||||
PermissionExplanationScreen(
|
||||
modifier = modifier,
|
||||
permissionCategories = permissionManager.getCategorizedPermissions(),
|
||||
onContinue = {
|
||||
mainViewModel.updateOnboardingState(OnboardingState.PERMISSION_REQUESTING)
|
||||
onboardingCoordinator.requestPermissions()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
OnboardingState.BACKGROUND_LOCATION_EXPLANATION -> {
|
||||
BackgroundLocationPermissionScreen(
|
||||
modifier = modifier,
|
||||
onContinue = {
|
||||
onboardingCoordinator.requestBackgroundLocation()
|
||||
},
|
||||
onRetry = {
|
||||
onboardingCoordinator.checkBackgroundLocationAndProceed()
|
||||
},
|
||||
onSkip = {
|
||||
onboardingCoordinator.skipBackgroundLocation()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
OnboardingState.CHECKING, OnboardingState.INITIALIZING, OnboardingState.COMPLETE -> {
|
||||
// Set up back navigation handling for the chat screen
|
||||
val backCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
// Let ChatViewModel handle navigation state
|
||||
val handled = chatViewModel.handleBackPressed()
|
||||
if (!handled) {
|
||||
// If ChatViewModel doesn't handle it, disable this callback
|
||||
// and let the system handle it (which will exit the app)
|
||||
this.isEnabled = false
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
this.isEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the callback - this will be automatically removed when the activity is destroyed
|
||||
onBackPressedDispatcher.addCallback(this, backCallback)
|
||||
ChatScreen(viewModel = chatViewModel)
|
||||
}
|
||||
|
||||
OnboardingState.ERROR -> {
|
||||
InitializationErrorScreen(
|
||||
modifier = modifier,
|
||||
errorMessage = errorMessage,
|
||||
onRetry = {
|
||||
mainViewModel.updateOnboardingState(OnboardingState.CHECKING)
|
||||
checkOnboardingStatus()
|
||||
},
|
||||
onOpenSettings = {
|
||||
onboardingCoordinator.openAppSettings()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOnboardingStateChange(state: OnboardingState) {
|
||||
|
||||
|
||||
234
app/src/main/java/com/bitchat/android/ui/BitchatApp.kt
Normal file
234
app/src/main/java/com/bitchat/android/ui/BitchatApp.kt
Normal file
@ -0,0 +1,234 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import android.util.Log
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import com.bitchat.android.MainViewModel
|
||||
import com.bitchat.android.onboarding.*
|
||||
import com.bitchat.android.ui.theme.BitchatTheme
|
||||
|
||||
@Composable
|
||||
internal fun BitchatApp(
|
||||
mainViewModel: MainViewModel,
|
||||
chatViewModel: ChatViewModel,
|
||||
permissionManager: PermissionManager,
|
||||
onboardingCoordinator: OnboardingCoordinator,
|
||||
bluetoothStatusManager: BluetoothStatusManager,
|
||||
locationStatusManager: LocationStatusManager,
|
||||
batteryOptimizationManager: BatteryOptimizationManager,
|
||||
onCheckBluetoothAndProceed: () -> Unit,
|
||||
onCheckLocationAndProceed: () -> Unit,
|
||||
onCheckBatteryOptimizationAndProceed: () -> Unit,
|
||||
onProceedWithPermissionCheck: () -> Unit,
|
||||
onCheckOnboardingStatus: () -> Unit
|
||||
) {
|
||||
BitchatTheme {
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
containerColor = MaterialTheme.colorScheme.background
|
||||
) { innerPadding ->
|
||||
OnboardingFlowScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding),
|
||||
mainViewModel = mainViewModel,
|
||||
chatViewModel = chatViewModel,
|
||||
permissionManager = permissionManager,
|
||||
onboardingCoordinator = onboardingCoordinator,
|
||||
bluetoothStatusManager = bluetoothStatusManager,
|
||||
locationStatusManager = locationStatusManager,
|
||||
batteryOptimizationManager = batteryOptimizationManager,
|
||||
onCheckBluetoothAndProceed = onCheckBluetoothAndProceed,
|
||||
onCheckLocationAndProceed = onCheckLocationAndProceed,
|
||||
onCheckBatteryOptimizationAndProceed = onCheckBatteryOptimizationAndProceed,
|
||||
onProceedWithPermissionCheck = onProceedWithPermissionCheck,
|
||||
onCheckOnboardingStatus = onCheckOnboardingStatus
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OnboardingFlowScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
mainViewModel: MainViewModel,
|
||||
chatViewModel: ChatViewModel,
|
||||
permissionManager: PermissionManager,
|
||||
onboardingCoordinator: OnboardingCoordinator,
|
||||
bluetoothStatusManager: BluetoothStatusManager,
|
||||
locationStatusManager: LocationStatusManager,
|
||||
batteryOptimizationManager: BatteryOptimizationManager,
|
||||
onCheckBluetoothAndProceed: () -> Unit,
|
||||
onCheckLocationAndProceed: () -> Unit,
|
||||
onCheckBatteryOptimizationAndProceed: () -> Unit,
|
||||
onProceedWithPermissionCheck: () -> Unit,
|
||||
onCheckOnboardingStatus: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val onboardingState by mainViewModel.onboardingState.collectAsState()
|
||||
val bluetoothStatus by mainViewModel.bluetoothStatus.collectAsState()
|
||||
val locationStatus by mainViewModel.locationStatus.collectAsState()
|
||||
val batteryOptimizationStatus by mainViewModel.batteryOptimizationStatus.collectAsState()
|
||||
val errorMessage by mainViewModel.errorMessage.collectAsState()
|
||||
val isBluetoothLoading by mainViewModel.isBluetoothLoading.collectAsState()
|
||||
val isLocationLoading by mainViewModel.isLocationLoading.collectAsState()
|
||||
val isBatteryOptimizationLoading by mainViewModel.isBatteryOptimizationLoading.collectAsState()
|
||||
|
||||
DisposableEffect(context, bluetoothStatusManager) {
|
||||
val receiver = bluetoothStatusManager.monitorBluetoothState(
|
||||
context = context,
|
||||
bluetoothStatusManager = bluetoothStatusManager,
|
||||
onBluetoothStateChanged = { status ->
|
||||
if (status == BluetoothStatus.ENABLED && onboardingState == OnboardingState.BLUETOOTH_CHECK) {
|
||||
onCheckBluetoothAndProceed()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onDispose {
|
||||
try {
|
||||
context.unregisterReceiver(receiver)
|
||||
} catch (e: IllegalStateException) {
|
||||
Log.w("BluetoothStatusUI", "Receiver was not registered")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val onBackPressedDispatcherOwner = LocalOnBackPressedDispatcherOwner.current
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
|
||||
when (onboardingState) {
|
||||
OnboardingState.PERMISSION_REQUESTING -> {
|
||||
InitializingScreen(modifier)
|
||||
}
|
||||
|
||||
OnboardingState.BLUETOOTH_CHECK -> {
|
||||
BluetoothCheckScreen(
|
||||
modifier = modifier,
|
||||
status = bluetoothStatus,
|
||||
onEnableBluetooth = {
|
||||
mainViewModel.updateBluetoothLoading(true)
|
||||
bluetoothStatusManager.requestEnableBluetooth()
|
||||
},
|
||||
onRetry = {
|
||||
onCheckBluetoothAndProceed()
|
||||
},
|
||||
onSkip = {
|
||||
mainViewModel.skipBluetoothCheck()
|
||||
onCheckLocationAndProceed()
|
||||
},
|
||||
isLoading = isBluetoothLoading
|
||||
)
|
||||
}
|
||||
|
||||
OnboardingState.LOCATION_CHECK -> {
|
||||
LocationCheckScreen(
|
||||
modifier = modifier,
|
||||
status = locationStatus,
|
||||
onEnableLocation = {
|
||||
mainViewModel.updateLocationLoading(true)
|
||||
locationStatusManager.requestEnableLocation()
|
||||
},
|
||||
onRetry = {
|
||||
onCheckLocationAndProceed()
|
||||
},
|
||||
isLoading = isLocationLoading
|
||||
)
|
||||
}
|
||||
|
||||
OnboardingState.BATTERY_OPTIMIZATION_CHECK -> {
|
||||
BatteryOptimizationScreen(
|
||||
modifier = modifier,
|
||||
status = batteryOptimizationStatus,
|
||||
onDisableBatteryOptimization = {
|
||||
mainViewModel.updateBatteryOptimizationLoading(true)
|
||||
batteryOptimizationManager.requestDisableBatteryOptimization()
|
||||
},
|
||||
onRetry = {
|
||||
onCheckBatteryOptimizationAndProceed()
|
||||
},
|
||||
onSkip = {
|
||||
// Skip battery optimization and proceed
|
||||
onProceedWithPermissionCheck()
|
||||
},
|
||||
isLoading = isBatteryOptimizationLoading
|
||||
)
|
||||
}
|
||||
|
||||
OnboardingState.PERMISSION_EXPLANATION -> {
|
||||
PermissionExplanationScreen(
|
||||
modifier = modifier,
|
||||
permissionCategories = permissionManager.getCategorizedPermissions(),
|
||||
onContinue = {
|
||||
mainViewModel.updateOnboardingState(OnboardingState.PERMISSION_REQUESTING)
|
||||
onboardingCoordinator.requestPermissions()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
OnboardingState.BACKGROUND_LOCATION_EXPLANATION -> {
|
||||
BackgroundLocationPermissionScreen(
|
||||
modifier = modifier,
|
||||
onContinue = {
|
||||
onboardingCoordinator.requestBackgroundLocation()
|
||||
},
|
||||
onRetry = {
|
||||
onboardingCoordinator.checkBackgroundLocationAndProceed()
|
||||
},
|
||||
onSkip = {
|
||||
onboardingCoordinator.skipBackgroundLocation()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
OnboardingState.CHECKING, OnboardingState.INITIALIZING, OnboardingState.COMPLETE -> {
|
||||
// Set up back navigation handling for the chat screen
|
||||
if (onBackPressedDispatcherOwner != null) {
|
||||
DisposableEffect(onBackPressedDispatcherOwner, lifecycleOwner) {
|
||||
val backCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
// Let ChatViewModel handle navigation state
|
||||
val handled = chatViewModel.handleBackPressed()
|
||||
if (!handled) {
|
||||
// If ChatViewModel doesn't handle it, disable this callback
|
||||
// and let the system handle it (which will exit the app)
|
||||
this.isEnabled = false
|
||||
onBackPressedDispatcherOwner.onBackPressedDispatcher.onBackPressed()
|
||||
this.isEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onBackPressedDispatcherOwner.onBackPressedDispatcher.addCallback(lifecycleOwner, backCallback)
|
||||
|
||||
onDispose {
|
||||
backCallback.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
ChatScreen(viewModel = chatViewModel)
|
||||
}
|
||||
|
||||
OnboardingState.ERROR -> {
|
||||
InitializationErrorScreen(
|
||||
modifier = modifier,
|
||||
errorMessage = errorMessage,
|
||||
onRetry = {
|
||||
mainViewModel.updateOnboardingState(OnboardingState.CHECKING)
|
||||
onCheckOnboardingStatus()
|
||||
},
|
||||
onOpenSettings = {
|
||||
onboardingCoordinator.openAppSettings()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user