UI: disable triggering drawer via gesture; enable alert dialog on back navigation inside conversation and benchmark
This commit is contained in:
parent
a7ae8b7ce0
commit
648b97818e
|
|
@ -2,30 +2,40 @@ package com.example.llama.revamp
|
|||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.OnBackPressedDispatcher
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.DrawerState
|
||||
import androidx.compose.material3.DrawerValue
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.rememberDrawerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.navArgument
|
||||
import com.example.llama.revamp.engine.InferenceEngine
|
||||
import com.example.llama.revamp.navigation.AppDestinations
|
||||
import com.example.llama.revamp.navigation.NavigationActions
|
||||
import com.example.llama.revamp.ui.components.AppNavigationDrawer
|
||||
import com.example.llama.revamp.ui.components.UnloadModelConfirmationDialog
|
||||
import com.example.llama.revamp.ui.screens.BenchmarkScreen
|
||||
import com.example.llama.revamp.ui.screens.ConversationScreen
|
||||
|
|
@ -71,6 +81,11 @@ fun AppContent() {
|
|||
|
||||
val engineState by viewModel.engineState.collectAsState()
|
||||
|
||||
// Track if model is loaded for gesture handling
|
||||
val isModelLoaded = remember(engineState) {
|
||||
viewModel.isModelLoaded()
|
||||
}
|
||||
|
||||
val navigationActions = remember(navController) {
|
||||
NavigationActions(navController)
|
||||
}
|
||||
|
|
@ -79,6 +94,65 @@ fun AppContent() {
|
|||
var showUnloadDialog by remember { mutableStateOf(false) }
|
||||
var pendingNavigation by remember { mutableStateOf<(() -> Unit)?>(null) }
|
||||
|
||||
// Get current route
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentRoute by remember {
|
||||
derivedStateOf { navBackStackEntry?.destination?.route }
|
||||
}
|
||||
|
||||
// Determine if current route requires model unloading
|
||||
val routeNeedsModelUnloading by remember(currentRoute) {
|
||||
derivedStateOf {
|
||||
currentRoute == AppDestinations.CONVERSATION_ROUTE ||
|
||||
currentRoute == AppDestinations.BENCHMARK_ROUTE ||
|
||||
currentRoute == AppDestinations.MODE_SELECTION_ROUTE
|
||||
}
|
||||
}
|
||||
|
||||
// Get local back dispatcher
|
||||
val backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
|
||||
// Helper function to handle back press with model unloading check
|
||||
val handleBackWithModelCheck = {
|
||||
if (viewModel.isModelLoaded()) {
|
||||
showUnloadDialog = true
|
||||
pendingNavigation = { navController.popBackStack() }
|
||||
true // Mark as handled
|
||||
} else {
|
||||
navController.popBackStack()
|
||||
true // Mark as handled
|
||||
}
|
||||
}
|
||||
|
||||
// Register a system back handler for screens that need unload confirmation
|
||||
DisposableEffect(lifecycleOwner, backDispatcher, currentRoute, isModelLoaded) {
|
||||
val callback = object : OnBackPressedCallback(
|
||||
// Only enable for screens that need model unloading confirmation
|
||||
routeNeedsModelUnloading && isModelLoaded
|
||||
) {
|
||||
override fun handleOnBackPressed() {
|
||||
handleBackWithModelCheck()
|
||||
}
|
||||
}
|
||||
|
||||
backDispatcher?.addCallback(lifecycleOwner, callback)
|
||||
|
||||
// Remove the callback when the effect leaves the composition
|
||||
onDispose {
|
||||
callback.remove()
|
||||
}
|
||||
}
|
||||
|
||||
// Compose BackHandler for added protection (this handles Compose-based back navigation)
|
||||
BackHandler(
|
||||
enabled = routeNeedsModelUnloading &&
|
||||
isModelLoaded &&
|
||||
drawerState.currentValue == DrawerValue.Closed
|
||||
) {
|
||||
handleBackWithModelCheck()
|
||||
}
|
||||
|
||||
// Observe back button
|
||||
LaunchedEffect(navController) {
|
||||
navController.addOnDestinationChangedListener { _, destination, _ ->
|
||||
|
|
@ -94,7 +168,12 @@ fun AppContent() {
|
|||
}
|
||||
}
|
||||
|
||||
// Main Content
|
||||
// Main Content with navigation drawer wrapper
|
||||
AppNavigationDrawer(
|
||||
drawerState = drawerState,
|
||||
navigationActions = navigationActions,
|
||||
modelLoaded = isModelLoaded
|
||||
) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = AppDestinations.MODEL_SELECTION_ROUTE
|
||||
|
|
@ -129,12 +208,7 @@ fun AppContent() {
|
|||
},
|
||||
onBackPressed = {
|
||||
// Need to unload model before going back
|
||||
if (viewModel.isModelLoaded()) {
|
||||
showUnloadDialog = true
|
||||
pendingNavigation = { navController.popBackStack() }
|
||||
} else {
|
||||
navController.popBackStack()
|
||||
}
|
||||
handleBackWithModelCheck()
|
||||
},
|
||||
drawerState = drawerState,
|
||||
navigationActions = navigationActions
|
||||
|
|
@ -146,12 +220,7 @@ fun AppContent() {
|
|||
ConversationScreen(
|
||||
onBackPressed = {
|
||||
// Need to unload model before going back
|
||||
if (viewModel.isModelLoaded()) {
|
||||
showUnloadDialog = true
|
||||
pendingNavigation = { navController.popBackStack() }
|
||||
} else {
|
||||
navController.popBackStack()
|
||||
}
|
||||
handleBackWithModelCheck()
|
||||
},
|
||||
drawerState = drawerState,
|
||||
navigationActions = navigationActions,
|
||||
|
|
@ -164,12 +233,7 @@ fun AppContent() {
|
|||
BenchmarkScreen(
|
||||
onBackPressed = {
|
||||
// Need to unload model before going back
|
||||
if (viewModel.isModelLoaded()) {
|
||||
showUnloadDialog = true
|
||||
pendingNavigation = { navController.popBackStack() }
|
||||
} else {
|
||||
navController.popBackStack()
|
||||
}
|
||||
handleBackWithModelCheck()
|
||||
},
|
||||
onRerunPressed = {
|
||||
viewModel.rerunBenchmark()
|
||||
|
|
@ -208,6 +272,7 @@ fun AppContent() {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Model unload confirmation dialog
|
||||
if (showUnloadDialog) {
|
||||
|
|
|
|||
|
|
@ -62,10 +62,6 @@ fun AppScaffold(
|
|||
// Formatted memory usage
|
||||
val memoryText = "${memoryUsage.availableGb}GB available"
|
||||
|
||||
AppNavigationDrawer(
|
||||
drawerState = drawerState,
|
||||
navigationActions = navigationActions
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
SystemStatusTopBar(
|
||||
|
|
@ -85,5 +81,4 @@ fun AppScaffold(
|
|||
},
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.example.llama.revamp.ui.components
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
|
|
@ -7,10 +8,13 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.DrawerState
|
||||
import androidx.compose.material3.DrawerValue
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -19,9 +23,14 @@ import androidx.compose.material3.ModalNavigationDrawer
|
|||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.llama.revamp.navigation.NavigationActions
|
||||
|
|
@ -29,19 +38,45 @@ import kotlinx.coroutines.launch
|
|||
|
||||
/**
|
||||
* App navigation drawer that provides access to different sections of the app.
|
||||
* Gesture opening is disabled when a model is loaded to prevent accidental navigation,
|
||||
* but gesture dismissal is always enabled.
|
||||
*/
|
||||
@Composable
|
||||
fun AppNavigationDrawer(
|
||||
drawerState: DrawerState,
|
||||
navigationActions: NavigationActions,
|
||||
modelLoaded: Boolean = false,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val configuration = LocalConfiguration.current
|
||||
|
||||
// Calculate drawer width (60% of screen width)
|
||||
val drawerWidth = (configuration.screenWidthDp * 0.6).dp
|
||||
|
||||
// Determine if gestures should be enabled
|
||||
// Always enable when drawer is open (to allow dismissal)
|
||||
// Only enable when model is not loaded (to prevent accidental opening)
|
||||
val gesturesEnabled by remember(drawerState.currentValue, modelLoaded) {
|
||||
derivedStateOf {
|
||||
drawerState.currentValue == DrawerValue.Open || !modelLoaded
|
||||
}
|
||||
}
|
||||
|
||||
// Handle back button to close drawer if open
|
||||
BackHandler(enabled = drawerState.currentValue == DrawerValue.Open) {
|
||||
coroutineScope.launch {
|
||||
drawerState.close()
|
||||
}
|
||||
}
|
||||
|
||||
ModalNavigationDrawer(
|
||||
drawerState = drawerState,
|
||||
gesturesEnabled = gesturesEnabled,
|
||||
drawerContent = {
|
||||
ModalDrawerSheet {
|
||||
ModalDrawerSheet(
|
||||
modifier = Modifier.width(drawerWidth)
|
||||
) {
|
||||
DrawerContent(
|
||||
onHomeClicked = {
|
||||
coroutineScope.launch {
|
||||
|
|
|
|||
Loading…
Reference in New Issue