diff --git a/app/src/main/java/com/bitchat/android/MainActivity.kt b/app/src/main/java/com/bitchat/android/MainActivity.kt index 66084427..335cb6ed 100644 --- a/app/src/main/java/com/bitchat/android/MainActivity.kt +++ b/app/src/main/java/com/bitchat/android/MainActivity.kt @@ -1,28 +1,36 @@ package com.bitchat.android -import android.Manifest import android.content.Intent import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.activity.result.contract.ActivityResultContracts -import androidx.activity.viewModels -import androidx.compose.foundation.isSystemInDarkTheme -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.material3.Surface -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.activity.OnBackPressedCallback -import androidx.activity.addCallback +import androidx.activity.compose.setContent +import androidx.activity.viewModels +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.lifecycle.Lifecycle import com.bitchat.android.mesh.BluetoothMeshService -import com.bitchat.android.onboarding.* +import com.bitchat.android.onboarding.BluetoothCheckScreen +import com.bitchat.android.onboarding.BluetoothStatus +import com.bitchat.android.onboarding.BluetoothStatusManager +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.ChatViewModel import com.bitchat.android.ui.theme.BitchatTheme @@ -38,6 +46,7 @@ class MainActivity : ComponentActivity() { // Core mesh service - managed at app level private lateinit var meshService: BluetoothMeshService + private val mainViewModel: MainViewModel by viewModels() private val chatViewModel: ChatViewModel by viewModels { object : ViewModelProvider.Factory { override fun create(modelClass: Class): T { @@ -47,25 +56,6 @@ class MainActivity : ComponentActivity() { } } - // UI state for onboarding flow - private var onboardingState by mutableStateOf(OnboardingState.CHECKING) - private var bluetoothStatus by mutableStateOf(BluetoothStatus.ENABLED) - private var locationStatus by mutableStateOf(LocationStatus.ENABLED) - private var errorMessage by mutableStateOf("") - private var isBluetoothLoading by mutableStateOf(false) - private var isLocationLoading by mutableStateOf(false) - - enum class OnboardingState { - CHECKING, - BLUETOOTH_CHECK, - LOCATION_CHECK, - PERMISSION_EXPLANATION, - PERMISSION_REQUESTING, - INITIALIZING, - COMPLETE, - ERROR - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -104,12 +94,31 @@ class MainActivity : ComponentActivity() { } } - // Start the onboarding process - checkOnboardingStatus() + // 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 + // This prevents restarting onboarding on configuration changes + if (mainViewModel.onboardingState.value == OnboardingState.CHECKING) { + checkOnboardingStatus() + } } @Composable private fun OnboardingFlowScreen() { + 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 -> { InitializingScreen() @@ -119,7 +128,7 @@ class MainActivity : ComponentActivity() { BluetoothCheckScreen( status = bluetoothStatus, onEnableBluetooth = { - isBluetoothLoading = true + mainViewModel.updateBluetoothLoading(true) bluetoothStatusManager.requestEnableBluetooth() }, onRetry = { @@ -133,7 +142,7 @@ class MainActivity : ComponentActivity() { LocationCheckScreen( status = locationStatus, onEnableLocation = { - isLocationLoading = true + mainViewModel.updateLocationLoading(true) locationStatusManager.requestEnableLocation() }, onRetry = { @@ -147,7 +156,7 @@ class MainActivity : ComponentActivity() { PermissionExplanationScreen( permissionCategories = permissionManager.getCategorizedPermissions(), onContinue = { - onboardingState = OnboardingState.PERMISSION_REQUESTING + mainViewModel.updateOnboardingState(OnboardingState.PERMISSION_REQUESTING) onboardingCoordinator.requestPermissions() } ) @@ -187,7 +196,7 @@ class MainActivity : ComponentActivity() { InitializationErrorScreen( errorMessage = errorMessage, onRetry = { - onboardingState = OnboardingState.CHECKING + mainViewModel.updateOnboardingState(OnboardingState.CHECKING) checkOnboardingStatus() }, onOpenSettings = { @@ -198,6 +207,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() { Log.d("MainActivity", "Checking onboarding status") @@ -226,9 +249,9 @@ class MainActivity : ComponentActivity() { // For existing users, check Bluetooth status first bluetoothStatusManager.logBluetoothStatus() - bluetoothStatus = bluetoothStatusManager.checkBluetoothStatus() + mainViewModel.updateBluetoothStatus(bluetoothStatusManager.checkBluetoothStatus()) - when (bluetoothStatus) { + when (mainViewModel.bluetoothStatus.value) { BluetoothStatus.ENABLED -> { // Bluetooth is enabled, check location services next checkLocationAndProceed() @@ -236,14 +259,14 @@ class MainActivity : ComponentActivity() { BluetoothStatus.DISABLED -> { // Show Bluetooth enable screen (should have permissions as existing user) Log.d("MainActivity", "Bluetooth disabled, showing enable screen") - onboardingState = OnboardingState.BLUETOOTH_CHECK - isBluetoothLoading = false + mainViewModel.updateOnboardingState(OnboardingState.BLUETOOTH_CHECK) + mainViewModel.updateBluetoothLoading(false) } BluetoothStatus.NOT_SUPPORTED -> { // Device doesn't support Bluetooth - Log.e("MainActivity", "Bluetooth not supported") - onboardingState = OnboardingState.BLUETOOTH_CHECK - isBluetoothLoading = false + android.util.Log.e("MainActivity", "Bluetooth not supported") + mainViewModel.updateOnboardingState(OnboardingState.BLUETOOTH_CHECK) + mainViewModel.updateBluetoothLoading(false) } } } @@ -259,14 +282,14 @@ class MainActivity : ComponentActivity() { if (permissionManager.isFirstTimeLaunch()) { Log.d("MainActivity", "First time launch, showing permission explanation") - onboardingState = OnboardingState.PERMISSION_EXPLANATION + mainViewModel.updateOnboardingState(OnboardingState.PERMISSION_EXPLANATION) } else if (permissionManager.areAllPermissionsGranted()) { Log.d("MainActivity", "Existing user with permissions, initializing app") - onboardingState = OnboardingState.INITIALIZING + mainViewModel.updateOnboardingState(OnboardingState.INITIALIZING) initializeApp() } else { Log.d("MainActivity", "Existing user missing permissions, showing explanation") - onboardingState = OnboardingState.PERMISSION_EXPLANATION + mainViewModel.updateOnboardingState(OnboardingState.PERMISSION_EXPLANATION) } } } @@ -276,8 +299,8 @@ class MainActivity : ComponentActivity() { */ private fun handleBluetoothEnabled() { Log.d("MainActivity", "Bluetooth enabled by user") - isBluetoothLoading = false - bluetoothStatus = BluetoothStatus.ENABLED + mainViewModel.updateBluetoothLoading(false) + mainViewModel.updateBluetoothStatus(BluetoothStatus.ENABLED) checkLocationAndProceed() } @@ -297,9 +320,9 @@ class MainActivity : ComponentActivity() { // For existing users, check location status locationStatusManager.logLocationStatus() - locationStatus = locationStatusManager.checkLocationStatus() + mainViewModel.updateLocationStatus(locationStatusManager.checkLocationStatus()) - when (locationStatus) { + when (mainViewModel.locationStatus.value) { LocationStatus.ENABLED -> { // Location services enabled, proceed with permission/onboarding check proceedWithPermissionCheck() @@ -307,14 +330,14 @@ class MainActivity : ComponentActivity() { LocationStatus.DISABLED -> { // Show location enable screen (should have permissions as existing user) Log.d("MainActivity", "Location services disabled, showing enable screen") - onboardingState = OnboardingState.LOCATION_CHECK - isLocationLoading = false + mainViewModel.updateOnboardingState(OnboardingState.LOCATION_CHECK) + mainViewModel.updateLocationLoading(false) } LocationStatus.NOT_AVAILABLE -> { // Device doesn't support location services (very unusual) Log.e("MainActivity", "Location services not available") - onboardingState = OnboardingState.LOCATION_CHECK - isLocationLoading = false + mainViewModel.updateOnboardingState(OnboardingState.LOCATION_CHECK) + mainViewModel.updateLocationLoading(false) } } } @@ -324,8 +347,8 @@ class MainActivity : ComponentActivity() { */ private fun handleLocationEnabled() { Log.d("MainActivity", "Location services enabled by user") - isLocationLoading = false - locationStatus = LocationStatus.ENABLED + mainViewModel.updateLocationLoading(false) + mainViewModel.updateLocationStatus(LocationStatus.ENABLED) proceedWithPermissionCheck() } @@ -334,18 +357,18 @@ class MainActivity : ComponentActivity() { */ private fun handleLocationDisabled(message: String) { Log.w("MainActivity", "Location services disabled or failed: $message") - isLocationLoading = false - locationStatus = locationStatusManager.checkLocationStatus() - + mainViewModel.updateLocationLoading(false) + mainViewModel.updateLocationStatus(locationStatusManager.checkLocationStatus()) + when { - locationStatus == LocationStatus.NOT_AVAILABLE -> { + mainViewModel.locationStatus.value == LocationStatus.NOT_AVAILABLE -> { // Show permanent error for devices without location services - errorMessage = message - onboardingState = OnboardingState.ERROR + mainViewModel.updateErrorMessage(message) + mainViewModel.updateOnboardingState(OnboardingState.ERROR) } else -> { // Stay on location check screen for retry - onboardingState = OnboardingState.LOCATION_CHECK + mainViewModel.updateOnboardingState(OnboardingState.LOCATION_CHECK) } } } @@ -355,14 +378,14 @@ class MainActivity : ComponentActivity() { */ private fun handleBluetoothDisabled(message: String) { Log.w("MainActivity", "Bluetooth disabled or failed: $message") - isBluetoothLoading = false - bluetoothStatus = bluetoothStatusManager.checkBluetoothStatus() + mainViewModel.updateBluetoothLoading(false) + mainViewModel.updateBluetoothStatus(bluetoothStatusManager.checkBluetoothStatus()) when { - bluetoothStatus == BluetoothStatus.NOT_SUPPORTED -> { + mainViewModel.bluetoothStatus.value == BluetoothStatus.NOT_SUPPORTED -> { // Show permanent error for unsupported devices - errorMessage = message - onboardingState = OnboardingState.ERROR + mainViewModel.updateErrorMessage(message) + mainViewModel.updateOnboardingState(OnboardingState.ERROR) } message.contains("Permission") && permissionManager.isFirstTimeLaunch() -> { // During first-time onboarding, if Bluetooth enable fails due to permissions, @@ -373,11 +396,11 @@ class MainActivity : ComponentActivity() { message.contains("Permission") -> { // For existing users, redirect to permission explanation to grant missing permissions Log.d("MainActivity", "Bluetooth enable requires permissions, showing permission explanation") - onboardingState = OnboardingState.PERMISSION_EXPLANATION + mainViewModel.updateOnboardingState(OnboardingState.PERMISSION_EXPLANATION) } else -> { // Stay on Bluetooth check screen for retry - onboardingState = OnboardingState.BLUETOOTH_CHECK + mainViewModel.updateOnboardingState(OnboardingState.BLUETOOTH_CHECK) } } } @@ -393,21 +416,21 @@ class MainActivity : ComponentActivity() { currentBluetoothStatus != BluetoothStatus.ENABLED -> { // Bluetooth still disabled, but now we have permissions to enable it Log.d("MainActivity", "Permissions granted, but Bluetooth still disabled. Showing Bluetooth enable screen.") - bluetoothStatus = currentBluetoothStatus - onboardingState = OnboardingState.BLUETOOTH_CHECK - isBluetoothLoading = false + mainViewModel.updateBluetoothStatus(currentBluetoothStatus) + mainViewModel.updateOnboardingState(OnboardingState.BLUETOOTH_CHECK) + mainViewModel.updateBluetoothLoading(false) } currentLocationStatus != LocationStatus.ENABLED -> { // Location services still disabled, but now we have permissions to enable it Log.d("MainActivity", "Permissions granted, but Location services still disabled. Showing Location enable screen.") - locationStatus = currentLocationStatus - onboardingState = OnboardingState.LOCATION_CHECK - isLocationLoading = false + mainViewModel.updateLocationStatus(currentLocationStatus) + mainViewModel.updateOnboardingState(OnboardingState.LOCATION_CHECK) + mainViewModel.updateLocationLoading(false) } else -> { // Both are enabled, proceed to app initialization Log.d("MainActivity", "Both Bluetooth and Location services are enabled, proceeding to initialization") - onboardingState = OnboardingState.INITIALIZING + mainViewModel.updateOnboardingState(OnboardingState.INITIALIZING) initializeApp() } } @@ -415,8 +438,8 @@ class MainActivity : ComponentActivity() { private fun handleOnboardingFailed(message: String) { Log.e("MainActivity", "Onboarding failed: $message") - errorMessage = message - onboardingState = OnboardingState.ERROR + mainViewModel.updateErrorMessage(message) + mainViewModel.updateOnboardingState(OnboardingState.ERROR) } private fun initializeApp() { @@ -449,10 +472,8 @@ class MainActivity : ComponentActivity() { // Small delay to ensure mesh service is fully initialized delay(500) - Log.d("MainActivity", "App initialization complete") - onboardingState = OnboardingState.COMPLETE - + mainViewModel.updateOnboardingState(OnboardingState.COMPLETE) } catch (e: Exception) { Log.e("MainActivity", "Failed to initialize app", e) handleOnboardingFailed("Failed to initialize the app: ${e.message}") @@ -463,7 +484,7 @@ class MainActivity : ComponentActivity() { override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) // Handle notification intents when app is already running - if (onboardingState == OnboardingState.COMPLETE) { + if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) { handleNotificationIntent(intent) } } @@ -471,7 +492,7 @@ class MainActivity : ComponentActivity() { override fun onResume() { super.onResume() // Check Bluetooth and Location status on resume and handle accordingly - if (onboardingState == OnboardingState.COMPLETE) { + if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) { // Set app foreground state meshService.connectionManager.setAppBackgroundState(false) chatViewModel.setAppBackgroundState(false) @@ -480,9 +501,9 @@ class MainActivity : ComponentActivity() { val currentBluetoothStatus = bluetoothStatusManager.checkBluetoothStatus() if (currentBluetoothStatus != BluetoothStatus.ENABLED) { Log.w("MainActivity", "Bluetooth disabled while app was backgrounded") - bluetoothStatus = currentBluetoothStatus - onboardingState = OnboardingState.BLUETOOTH_CHECK - isBluetoothLoading = false + mainViewModel.updateBluetoothStatus(currentBluetoothStatus) + mainViewModel.updateOnboardingState(OnboardingState.BLUETOOTH_CHECK) + mainViewModel.updateBluetoothLoading(false) return } @@ -490,9 +511,9 @@ class MainActivity : ComponentActivity() { val currentLocationStatus = locationStatusManager.checkLocationStatus() if (currentLocationStatus != LocationStatus.ENABLED) { Log.w("MainActivity", "Location services disabled while app was backgrounded") - locationStatus = currentLocationStatus - onboardingState = OnboardingState.LOCATION_CHECK - isLocationLoading = false + mainViewModel.updateLocationStatus(currentLocationStatus) + mainViewModel.updateOnboardingState(OnboardingState.LOCATION_CHECK) + mainViewModel.updateLocationLoading(false) } } } @@ -500,7 +521,7 @@ class MainActivity : ComponentActivity() { override fun onPause() { super.onPause() // Only set background state if app is fully initialized - if (onboardingState == OnboardingState.COMPLETE) { + if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) { // Set app background state meshService.connectionManager.setAppBackgroundState(true) chatViewModel.setAppBackgroundState(true) @@ -544,7 +565,7 @@ class MainActivity : ComponentActivity() { } // Stop mesh services if app was fully initialized - if (onboardingState == OnboardingState.COMPLETE) { + if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) { try { meshService.stopServices() Log.d("MainActivity", "Mesh services stopped successfully") diff --git a/app/src/main/java/com/bitchat/android/MainViewModel.kt b/app/src/main/java/com/bitchat/android/MainViewModel.kt new file mode 100644 index 00000000..62177c18 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/MainViewModel.kt @@ -0,0 +1,55 @@ +package com.bitchat.android + +import androidx.lifecycle.ViewModel +import com.bitchat.android.onboarding.BluetoothStatus +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() { + + private val _onboardingState = MutableStateFlow(OnboardingState.CHECKING) + val onboardingState: StateFlow = _onboardingState.asStateFlow() + + private val _bluetoothStatus = MutableStateFlow(BluetoothStatus.ENABLED) + val bluetoothStatus: StateFlow = _bluetoothStatus.asStateFlow() + + private val _locationStatus = MutableStateFlow(LocationStatus.ENABLED) + val locationStatus: StateFlow = _locationStatus.asStateFlow() + + private val _errorMessage = MutableStateFlow("") + val errorMessage: StateFlow = _errorMessage.asStateFlow() + + private val _isBluetoothLoading = MutableStateFlow(false) + val isBluetoothLoading: StateFlow = _isBluetoothLoading.asStateFlow() + + private val _isLocationLoading = MutableStateFlow(false) + val isLocationLoading: StateFlow = _isLocationLoading.asStateFlow() + + // Public update functions for MainActivity + fun updateOnboardingState(state: OnboardingState) { + _onboardingState.value = state + } + + fun updateBluetoothStatus(status: BluetoothStatus) { + _bluetoothStatus.value = status + } + + fun updateLocationStatus(status: LocationStatus) { + _locationStatus.value = status + } + + fun updateErrorMessage(message: String) { + _errorMessage.value = message + } + + fun updateBluetoothLoading(loading: Boolean) { + _isBluetoothLoading.value = loading + } + + fun updateLocationLoading(loading: Boolean) { + _isLocationLoading.value = loading + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bitchat/android/core/ui/utils/ModifierExt.kt b/app/src/main/java/com/bitchat/android/core/ui/utils/ModifierExt.kt new file mode 100644 index 00000000..19dabb57 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/core/ui/utils/ModifierExt.kt @@ -0,0 +1,57 @@ +package com.bitchat.android.core.ui.utils + +import androidx.compose.foundation.clickable +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +fun Modifier.singleOrTripleClickable( + onSingleClick: () -> Unit, + onTripleClick: () -> Unit, + clickTimeThreshold: Long = 300L +): Modifier = composed { + var tapCount by remember { mutableIntStateOf(0) } + var lastTapTime by remember { mutableLongStateOf(0L) } + var singleClickJob by remember { mutableStateOf(null) } + val coroutineScope = rememberCoroutineScope() + + this.clickable { + val currentTime = System.currentTimeMillis() + + if (currentTime - lastTapTime < clickTimeThreshold) { + tapCount++ + } else { + tapCount = 1 + } + + lastTapTime = currentTime + + // Cancel any pending single click action + singleClickJob?.cancel() + singleClickJob = null + + when (tapCount) { + 1 -> { + // Wait to see if more taps come + singleClickJob = coroutineScope.launch { + delay(clickTimeThreshold) + if (tapCount == 1) { + onSingleClick() + } + } + } + 3 -> { + // Triple click detected - execute immediately + onTripleClick() + tapCount = 0 + } + } + + // Reset after threshold if no triple click + if (tapCount > 3) { + tapCount = 0 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bitchat/android/onboarding/OnboardingState.kt b/app/src/main/java/com/bitchat/android/onboarding/OnboardingState.kt new file mode 100644 index 00000000..cb8e73b5 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/onboarding/OnboardingState.kt @@ -0,0 +1,12 @@ +package com.bitchat.android.onboarding + +enum class OnboardingState { + CHECKING, + BLUETOOTH_CHECK, + LOCATION_CHECK, + PERMISSION_EXPLANATION, + PERMISSION_REQUESTING, + INITIALIZING, + COMPLETE, + ERROR +} \ No newline at end of file diff --git a/app/src/main/java/com/bitchat/android/ui/ChatHeader.kt b/app/src/main/java/com/bitchat/android/ui/ChatHeader.kt index 65e2ee8e..50662361 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatHeader.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatHeader.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.bitchat.android.core.ui.utils.singleOrTripleClickable /** * Header components for ChatScreen @@ -148,8 +149,7 @@ fun ChatHeaderContent( onShowAppInfo: () -> Unit ) { val colorScheme = MaterialTheme.colorScheme - var tripleClickCount by remember { mutableStateOf(0) } - + when { selectedPrivatePeer != null -> { // Private chat header - ensure state synchronization @@ -181,15 +181,8 @@ fun ChatHeaderContent( MainHeader( nickname = nickname, onNicknameChange = viewModel::setNickname, - onTitleClick = { - tripleClickCount++ - if (tripleClickCount >= 3) { - tripleClickCount = 0 - onTripleClick() - } else { - onShowAppInfo() - } - }, + onTitleClick = onShowAppInfo, + onTripleTitleClick = onTripleClick, onSidebarClick = onSidebarClick, viewModel = viewModel ) @@ -345,6 +338,7 @@ private fun MainHeader( nickname: String, onNicknameChange: (String) -> Unit, onTitleClick: () -> Unit, + onTripleTitleClick: () -> Unit, onSidebarClick: () -> Unit, viewModel: ChatViewModel ) { @@ -360,12 +354,18 @@ private fun MainHeader( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - Row(verticalAlignment = Alignment.CenterVertically) { + Row( + modifier = Modifier.fillMaxHeight(), + verticalAlignment = Alignment.CenterVertically + ) { Text( text = "bitchat*", style = MaterialTheme.typography.headlineSmall, color = colorScheme.primary, - modifier = Modifier.clickable { onTitleClick() } + modifier = Modifier.singleOrTripleClickable( + onSingleClick = onTitleClick, + onTripleClick = onTripleTitleClick + ) ) Spacer(modifier = Modifier.width(8.dp)) diff --git a/app/src/main/java/com/bitchat/android/ui/SidebarComponents.kt b/app/src/main/java/com/bitchat/android/ui/SidebarComponents.kt index 27e6927f..f34ed992 100644 --- a/app/src/main/java/com/bitchat/android/ui/SidebarComponents.kt +++ b/app/src/main/java/com/bitchat/android/ui/SidebarComponents.kt @@ -1,8 +1,7 @@ package com.bitchat.android.ui +import com.bitchat.android.R import android.util.Log -import androidx.compose.animation.* -import androidx.compose.animation.core.* import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn @@ -16,11 +15,12 @@ import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.zIndex + /** * Sidebar components for ChatScreen @@ -140,7 +140,7 @@ private fun SidebarHeader() { verticalAlignment = Alignment.CenterVertically ) { Text( - text = "YOUR NETWORK", + text = stringResource(id = R.string.your_network).uppercase(), style = MaterialTheme.typography.titleMedium.copy( fontWeight = FontWeight.Bold, fontFamily = FontFamily.Monospace @@ -175,7 +175,7 @@ fun ChannelsSection( ) Spacer(modifier = Modifier.width(6.dp)) Text( - text = "CHANNELS", + text = stringResource(id = R.string.channels).uppercase(), style = MaterialTheme.typography.labelSmall, color = colorScheme.onSurface.copy(alpha = 0.6f), fontWeight = FontWeight.Bold @@ -255,7 +255,7 @@ fun PeopleSection( ) Spacer(modifier = Modifier.width(6.dp)) Text( - text = "PEOPLE", + text = stringResource(id = R.string.people).uppercase(), style = MaterialTheme.typography.labelSmall, color = colorScheme.onSurface.copy(alpha = 0.6f), fontWeight = FontWeight.Bold @@ -264,7 +264,7 @@ fun PeopleSection( if (connectedPeers.isEmpty()) { Text( - text = "No one connected", + text = stringResource(id = R.string.no_one_connected), style = MaterialTheme.typography.bodyMedium, color = colorScheme.onSurface.copy(alpha = 0.5f), modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 51c0a712..82b84804 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -16,4 +16,5 @@ Online Users No one connected Triple tap to clear all data + Your Network