Merge 27a13094de1d81c5c177b1ce2bd1a301ba2a6afc into c127eb83ab94c069c32d37530d2faecd381cd2a8

This commit is contained in:
Ruslan 2026-09-15 10:07:26 +09:00 committed by GitHub
commit 5193a8956a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 249 additions and 180 deletions

View File

@ -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) {

View 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()
}
)
}
}
}