mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 16:05:20 +00:00
Refactor: Migrate MainViewModel to use StateFlow
This commit refactors the `MainViewModel` to use `StateFlow` instead of `mutableStateOf` for managing its observable properties. This change improves the way UI state is handled and observed, aligning with modern Android development best practices. Additionally, `OnboardingState.kt` has been moved to the `onboarding` package for better organization.
This commit is contained in:
@@ -10,9 +10,13 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import com.bitchat.android.mesh.BluetoothMeshService
|
import com.bitchat.android.mesh.BluetoothMeshService
|
||||||
import com.bitchat.android.onboarding.BluetoothCheckScreen
|
import com.bitchat.android.onboarding.BluetoothCheckScreen
|
||||||
import com.bitchat.android.onboarding.BluetoothStatus
|
import com.bitchat.android.onboarding.BluetoothStatus
|
||||||
@@ -23,6 +27,7 @@ import com.bitchat.android.onboarding.LocationCheckScreen
|
|||||||
import com.bitchat.android.onboarding.LocationStatus
|
import com.bitchat.android.onboarding.LocationStatus
|
||||||
import com.bitchat.android.onboarding.LocationStatusManager
|
import com.bitchat.android.onboarding.LocationStatusManager
|
||||||
import com.bitchat.android.onboarding.OnboardingCoordinator
|
import com.bitchat.android.onboarding.OnboardingCoordinator
|
||||||
|
import com.bitchat.android.onboarding.OnboardingState
|
||||||
import com.bitchat.android.onboarding.PermissionExplanationScreen
|
import com.bitchat.android.onboarding.PermissionExplanationScreen
|
||||||
import com.bitchat.android.onboarding.PermissionManager
|
import com.bitchat.android.onboarding.PermissionManager
|
||||||
import com.bitchat.android.ui.ChatScreen
|
import com.bitchat.android.ui.ChatScreen
|
||||||
@@ -88,23 +93,39 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collect state changes in a lifecycle-aware manner
|
||||||
|
lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
mainViewModel.onboardingState.collect { state ->
|
||||||
|
handleOnboardingStateChange(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Only start onboarding process if we're in the initial CHECKING state
|
// Only start onboarding process if we're in the initial CHECKING state
|
||||||
// This prevents restarting onboarding on configuration changes
|
// This prevents restarting onboarding on configuration changes
|
||||||
if (mainViewModel.onboardingState == OnboardingState.CHECKING) {
|
if (mainViewModel.onboardingState.value == OnboardingState.CHECKING) {
|
||||||
checkOnboardingStatus()
|
checkOnboardingStatus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun OnboardingFlowScreen() {
|
private fun OnboardingFlowScreen() {
|
||||||
when (mainViewModel.onboardingState) {
|
val onboardingState by mainViewModel.onboardingState.collectAsState()
|
||||||
|
val bluetoothStatus by mainViewModel.bluetoothStatus.collectAsState()
|
||||||
|
val locationStatus by mainViewModel.locationStatus.collectAsState()
|
||||||
|
val errorMessage by mainViewModel.errorMessage.collectAsState()
|
||||||
|
val isBluetoothLoading by mainViewModel.isBluetoothLoading.collectAsState()
|
||||||
|
val isLocationLoading by mainViewModel.isLocationLoading.collectAsState()
|
||||||
|
|
||||||
|
when (onboardingState) {
|
||||||
OnboardingState.CHECKING -> {
|
OnboardingState.CHECKING -> {
|
||||||
InitializingScreen()
|
InitializingScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
OnboardingState.BLUETOOTH_CHECK -> {
|
OnboardingState.BLUETOOTH_CHECK -> {
|
||||||
BluetoothCheckScreen(
|
BluetoothCheckScreen(
|
||||||
status = mainViewModel.bluetoothStatus,
|
status = bluetoothStatus,
|
||||||
onEnableBluetooth = {
|
onEnableBluetooth = {
|
||||||
mainViewModel.updateBluetoothLoading(true)
|
mainViewModel.updateBluetoothLoading(true)
|
||||||
bluetoothStatusManager.requestEnableBluetooth()
|
bluetoothStatusManager.requestEnableBluetooth()
|
||||||
@@ -112,13 +133,13 @@ class MainActivity : ComponentActivity() {
|
|||||||
onRetry = {
|
onRetry = {
|
||||||
checkBluetoothAndProceed()
|
checkBluetoothAndProceed()
|
||||||
},
|
},
|
||||||
isLoading = mainViewModel.isBluetoothLoading
|
isLoading = isBluetoothLoading
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
OnboardingState.LOCATION_CHECK -> {
|
OnboardingState.LOCATION_CHECK -> {
|
||||||
LocationCheckScreen(
|
LocationCheckScreen(
|
||||||
status = mainViewModel.locationStatus,
|
status = locationStatus,
|
||||||
onEnableLocation = {
|
onEnableLocation = {
|
||||||
mainViewModel.updateLocationLoading(true)
|
mainViewModel.updateLocationLoading(true)
|
||||||
locationStatusManager.requestEnableLocation()
|
locationStatusManager.requestEnableLocation()
|
||||||
@@ -126,7 +147,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
onRetry = {
|
onRetry = {
|
||||||
checkLocationAndProceed()
|
checkLocationAndProceed()
|
||||||
},
|
},
|
||||||
isLoading = mainViewModel.isLocationLoading
|
isLoading = isLocationLoading
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +193,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
OnboardingState.ERROR -> {
|
OnboardingState.ERROR -> {
|
||||||
InitializationErrorScreen(
|
InitializationErrorScreen(
|
||||||
errorMessage = mainViewModel.errorMessage,
|
errorMessage = errorMessage,
|
||||||
onRetry = {
|
onRetry = {
|
||||||
mainViewModel.updateOnboardingState(OnboardingState.CHECKING)
|
mainViewModel.updateOnboardingState(OnboardingState.CHECKING)
|
||||||
checkOnboardingStatus()
|
checkOnboardingStatus()
|
||||||
@@ -185,6 +206,20 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleOnboardingStateChange(state: OnboardingState) {
|
||||||
|
|
||||||
|
when (state) {
|
||||||
|
OnboardingState.COMPLETE -> {
|
||||||
|
// App is fully initialized, mesh service is running
|
||||||
|
android.util.Log.d("MainActivity", "Onboarding completed - app ready")
|
||||||
|
}
|
||||||
|
OnboardingState.ERROR -> {
|
||||||
|
android.util.Log.e("MainActivity", "Onboarding error state reached")
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun checkOnboardingStatus() {
|
private fun checkOnboardingStatus() {
|
||||||
android.util.Log.d("MainActivity", "Checking onboarding status")
|
android.util.Log.d("MainActivity", "Checking onboarding status")
|
||||||
|
|
||||||
@@ -215,7 +250,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
bluetoothStatusManager.logBluetoothStatus()
|
bluetoothStatusManager.logBluetoothStatus()
|
||||||
mainViewModel.updateBluetoothStatus(bluetoothStatusManager.checkBluetoothStatus())
|
mainViewModel.updateBluetoothStatus(bluetoothStatusManager.checkBluetoothStatus())
|
||||||
|
|
||||||
when (mainViewModel.bluetoothStatus) {
|
when (mainViewModel.bluetoothStatus.value) {
|
||||||
BluetoothStatus.ENABLED -> {
|
BluetoothStatus.ENABLED -> {
|
||||||
// Bluetooth is enabled, check location services next
|
// Bluetooth is enabled, check location services next
|
||||||
checkLocationAndProceed()
|
checkLocationAndProceed()
|
||||||
@@ -286,7 +321,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
locationStatusManager.logLocationStatus()
|
locationStatusManager.logLocationStatus()
|
||||||
mainViewModel.updateLocationStatus(locationStatusManager.checkLocationStatus())
|
mainViewModel.updateLocationStatus(locationStatusManager.checkLocationStatus())
|
||||||
|
|
||||||
when (mainViewModel.locationStatus) {
|
when (mainViewModel.locationStatus.value) {
|
||||||
LocationStatus.ENABLED -> {
|
LocationStatus.ENABLED -> {
|
||||||
// Location services enabled, proceed with permission/onboarding check
|
// Location services enabled, proceed with permission/onboarding check
|
||||||
proceedWithPermissionCheck()
|
proceedWithPermissionCheck()
|
||||||
@@ -325,7 +360,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
mainViewModel.updateLocationStatus(locationStatusManager.checkLocationStatus())
|
mainViewModel.updateLocationStatus(locationStatusManager.checkLocationStatus())
|
||||||
|
|
||||||
when {
|
when {
|
||||||
mainViewModel.locationStatus == LocationStatus.NOT_AVAILABLE -> {
|
mainViewModel.locationStatus.value == LocationStatus.NOT_AVAILABLE -> {
|
||||||
// Show permanent error for devices without location services
|
// Show permanent error for devices without location services
|
||||||
mainViewModel.updateErrorMessage(message)
|
mainViewModel.updateErrorMessage(message)
|
||||||
mainViewModel.updateOnboardingState(OnboardingState.ERROR)
|
mainViewModel.updateOnboardingState(OnboardingState.ERROR)
|
||||||
@@ -346,7 +381,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
mainViewModel.updateBluetoothStatus(bluetoothStatusManager.checkBluetoothStatus())
|
mainViewModel.updateBluetoothStatus(bluetoothStatusManager.checkBluetoothStatus())
|
||||||
|
|
||||||
when {
|
when {
|
||||||
mainViewModel.bluetoothStatus == BluetoothStatus.NOT_SUPPORTED -> {
|
mainViewModel.bluetoothStatus.value == BluetoothStatus.NOT_SUPPORTED -> {
|
||||||
// Show permanent error for unsupported devices
|
// Show permanent error for unsupported devices
|
||||||
mainViewModel.updateErrorMessage(message)
|
mainViewModel.updateErrorMessage(message)
|
||||||
mainViewModel.updateOnboardingState(OnboardingState.ERROR)
|
mainViewModel.updateOnboardingState(OnboardingState.ERROR)
|
||||||
@@ -450,7 +485,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
// Handle notification intents when app is already running
|
// Handle notification intents when app is already running
|
||||||
if (mainViewModel.onboardingState == OnboardingState.COMPLETE) {
|
if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) {
|
||||||
handleNotificationIntent(intent)
|
handleNotificationIntent(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -458,7 +493,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
// Check Bluetooth and Location status on resume and handle accordingly
|
// Check Bluetooth and Location status on resume and handle accordingly
|
||||||
if (mainViewModel.onboardingState == OnboardingState.COMPLETE) {
|
if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) {
|
||||||
// Set app foreground state
|
// Set app foreground state
|
||||||
meshService.connectionManager.setAppBackgroundState(false)
|
meshService.connectionManager.setAppBackgroundState(false)
|
||||||
chatViewModel.setAppBackgroundState(false)
|
chatViewModel.setAppBackgroundState(false)
|
||||||
@@ -487,7 +522,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
// Only set background state if app is fully initialized
|
// Only set background state if app is fully initialized
|
||||||
if (mainViewModel.onboardingState == OnboardingState.COMPLETE) {
|
if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) {
|
||||||
// Set app background state
|
// Set app background state
|
||||||
meshService.connectionManager.setAppBackgroundState(true)
|
meshService.connectionManager.setAppBackgroundState(true)
|
||||||
chatViewModel.setAppBackgroundState(true)
|
chatViewModel.setAppBackgroundState(true)
|
||||||
@@ -523,7 +558,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
* Restart mesh services (for debugging/troubleshooting)
|
* Restart mesh services (for debugging/troubleshooting)
|
||||||
*/
|
*/
|
||||||
fun restartMeshServices() {
|
fun restartMeshServices() {
|
||||||
if (mainViewModel.onboardingState == OnboardingState.COMPLETE) {
|
if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
android.util.Log.d("MainActivity", "Restarting mesh services")
|
android.util.Log.d("MainActivity", "Restarting mesh services")
|
||||||
@@ -550,7 +585,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stop mesh services if app was fully initialized
|
// Stop mesh services if app was fully initialized
|
||||||
if (mainViewModel.onboardingState == OnboardingState.COMPLETE) {
|
if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) {
|
||||||
try {
|
try {
|
||||||
meshService.stopServices()
|
meshService.stopServices()
|
||||||
android.util.Log.d("MainActivity", "Mesh services stopped successfully")
|
android.util.Log.d("MainActivity", "Mesh services stopped successfully")
|
||||||
|
|||||||
@@ -1,52 +1,55 @@
|
|||||||
package com.bitchat.android
|
package com.bitchat.android
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.bitchat.android.onboarding.BluetoothStatus
|
import com.bitchat.android.onboarding.BluetoothStatus
|
||||||
import com.bitchat.android.onboarding.LocationStatus
|
import com.bitchat.android.onboarding.LocationStatus
|
||||||
|
import com.bitchat.android.onboarding.OnboardingState
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
class MainViewModel : ViewModel() {
|
class MainViewModel : ViewModel() {
|
||||||
|
|
||||||
private var _onboardingState by mutableStateOf(OnboardingState.CHECKING)
|
private val _onboardingState = MutableStateFlow(OnboardingState.CHECKING)
|
||||||
val onboardingState: OnboardingState get() = _onboardingState
|
val onboardingState: StateFlow<OnboardingState> = _onboardingState.asStateFlow()
|
||||||
|
|
||||||
private var _bluetoothStatus by mutableStateOf(BluetoothStatus.ENABLED)
|
private val _bluetoothStatus = MutableStateFlow(BluetoothStatus.ENABLED)
|
||||||
val bluetoothStatus: BluetoothStatus get() = _bluetoothStatus
|
val bluetoothStatus: StateFlow<BluetoothStatus> = _bluetoothStatus.asStateFlow()
|
||||||
|
|
||||||
private var _locationStatus by mutableStateOf(LocationStatus.ENABLED)
|
private val _locationStatus = MutableStateFlow(LocationStatus.ENABLED)
|
||||||
val locationStatus: LocationStatus get() = _locationStatus
|
val locationStatus: StateFlow<LocationStatus> = _locationStatus.asStateFlow()
|
||||||
|
|
||||||
private var _errorMessage by mutableStateOf("")
|
private val _errorMessage = MutableStateFlow("")
|
||||||
val errorMessage: String get() = _errorMessage
|
val errorMessage: StateFlow<String> = _errorMessage.asStateFlow()
|
||||||
|
|
||||||
private var _isBluetoothLoading by mutableStateOf(false)
|
private val _isBluetoothLoading = MutableStateFlow(false)
|
||||||
val isBluetoothLoading: Boolean get() = _isBluetoothLoading
|
val isBluetoothLoading: StateFlow<Boolean> = _isBluetoothLoading.asStateFlow()
|
||||||
|
|
||||||
private var _isLocationLoading by mutableStateOf(false)
|
private val _isLocationLoading = MutableStateFlow(false)
|
||||||
val isLocationLoading: Boolean get() = _isLocationLoading
|
val isLocationLoading: StateFlow<Boolean> = _isLocationLoading.asStateFlow()
|
||||||
|
|
||||||
// Public update functions for MainActivity
|
// Public update functions for MainActivity
|
||||||
fun updateOnboardingState(state: OnboardingState) {
|
fun updateOnboardingState(state: OnboardingState) {
|
||||||
_onboardingState = state
|
_onboardingState.value = state
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateBluetoothStatus(status: BluetoothStatus) {
|
fun updateBluetoothStatus(status: BluetoothStatus) {
|
||||||
_bluetoothStatus = status
|
_bluetoothStatus.value = status
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateLocationStatus(status: LocationStatus) {
|
fun updateLocationStatus(status: LocationStatus) {
|
||||||
_locationStatus = status
|
_locationStatus.value = status
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateErrorMessage(message: String) {
|
fun updateErrorMessage(message: String) {
|
||||||
_errorMessage = message
|
_errorMessage.value = message
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateBluetoothLoading(loading: Boolean) {
|
fun updateBluetoothLoading(loading: Boolean) {
|
||||||
_isBluetoothLoading = loading
|
_isBluetoothLoading.value = loading
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateLocationLoading(loading: Boolean) {
|
fun updateLocationLoading(loading: Boolean) {
|
||||||
_isLocationLoading = loading
|
_isLocationLoading.value = loading
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.bitchat.android
|
package com.bitchat.android.onboarding
|
||||||
|
|
||||||
enum class OnboardingState {
|
enum class OnboardingState {
|
||||||
CHECKING,
|
CHECKING,
|
||||||
Reference in New Issue
Block a user