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 android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
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.activity.compose.setContent
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material3.DrawerState
|
||||||
import androidx.compose.material3.DrawerValue
|
import androidx.compose.material3.DrawerValue
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.rememberDrawerState
|
import androidx.compose.material3.rememberDrawerState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavType
|
import androidx.navigation.NavType
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
import com.example.llama.revamp.engine.InferenceEngine
|
import com.example.llama.revamp.engine.InferenceEngine
|
||||||
import com.example.llama.revamp.navigation.AppDestinations
|
import com.example.llama.revamp.navigation.AppDestinations
|
||||||
import com.example.llama.revamp.navigation.NavigationActions
|
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.components.UnloadModelConfirmationDialog
|
||||||
import com.example.llama.revamp.ui.screens.BenchmarkScreen
|
import com.example.llama.revamp.ui.screens.BenchmarkScreen
|
||||||
import com.example.llama.revamp.ui.screens.ConversationScreen
|
import com.example.llama.revamp.ui.screens.ConversationScreen
|
||||||
|
|
@ -71,6 +81,11 @@ fun AppContent() {
|
||||||
|
|
||||||
val engineState by viewModel.engineState.collectAsState()
|
val engineState by viewModel.engineState.collectAsState()
|
||||||
|
|
||||||
|
// Track if model is loaded for gesture handling
|
||||||
|
val isModelLoaded = remember(engineState) {
|
||||||
|
viewModel.isModelLoaded()
|
||||||
|
}
|
||||||
|
|
||||||
val navigationActions = remember(navController) {
|
val navigationActions = remember(navController) {
|
||||||
NavigationActions(navController)
|
NavigationActions(navController)
|
||||||
}
|
}
|
||||||
|
|
@ -79,6 +94,65 @@ fun AppContent() {
|
||||||
var showUnloadDialog by remember { mutableStateOf(false) }
|
var showUnloadDialog by remember { mutableStateOf(false) }
|
||||||
var pendingNavigation by remember { mutableStateOf<(() -> Unit)?>(null) }
|
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
|
// Observe back button
|
||||||
LaunchedEffect(navController) {
|
LaunchedEffect(navController) {
|
||||||
navController.addOnDestinationChangedListener { _, destination, _ ->
|
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(
|
NavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = AppDestinations.MODEL_SELECTION_ROUTE
|
startDestination = AppDestinations.MODEL_SELECTION_ROUTE
|
||||||
|
|
@ -129,12 +208,7 @@ fun AppContent() {
|
||||||
},
|
},
|
||||||
onBackPressed = {
|
onBackPressed = {
|
||||||
// Need to unload model before going back
|
// Need to unload model before going back
|
||||||
if (viewModel.isModelLoaded()) {
|
handleBackWithModelCheck()
|
||||||
showUnloadDialog = true
|
|
||||||
pendingNavigation = { navController.popBackStack() }
|
|
||||||
} else {
|
|
||||||
navController.popBackStack()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
drawerState = drawerState,
|
drawerState = drawerState,
|
||||||
navigationActions = navigationActions
|
navigationActions = navigationActions
|
||||||
|
|
@ -146,12 +220,7 @@ fun AppContent() {
|
||||||
ConversationScreen(
|
ConversationScreen(
|
||||||
onBackPressed = {
|
onBackPressed = {
|
||||||
// Need to unload model before going back
|
// Need to unload model before going back
|
||||||
if (viewModel.isModelLoaded()) {
|
handleBackWithModelCheck()
|
||||||
showUnloadDialog = true
|
|
||||||
pendingNavigation = { navController.popBackStack() }
|
|
||||||
} else {
|
|
||||||
navController.popBackStack()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
drawerState = drawerState,
|
drawerState = drawerState,
|
||||||
navigationActions = navigationActions,
|
navigationActions = navigationActions,
|
||||||
|
|
@ -164,12 +233,7 @@ fun AppContent() {
|
||||||
BenchmarkScreen(
|
BenchmarkScreen(
|
||||||
onBackPressed = {
|
onBackPressed = {
|
||||||
// Need to unload model before going back
|
// Need to unload model before going back
|
||||||
if (viewModel.isModelLoaded()) {
|
handleBackWithModelCheck()
|
||||||
showUnloadDialog = true
|
|
||||||
pendingNavigation = { navController.popBackStack() }
|
|
||||||
} else {
|
|
||||||
navController.popBackStack()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onRerunPressed = {
|
onRerunPressed = {
|
||||||
viewModel.rerunBenchmark()
|
viewModel.rerunBenchmark()
|
||||||
|
|
@ -208,6 +272,7 @@ fun AppContent() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Model unload confirmation dialog
|
// Model unload confirmation dialog
|
||||||
if (showUnloadDialog) {
|
if (showUnloadDialog) {
|
||||||
|
|
|
||||||
|
|
@ -62,10 +62,6 @@ fun AppScaffold(
|
||||||
// Formatted memory usage
|
// Formatted memory usage
|
||||||
val memoryText = "${memoryUsage.availableGb}GB available"
|
val memoryText = "${memoryUsage.availableGb}GB available"
|
||||||
|
|
||||||
AppNavigationDrawer(
|
|
||||||
drawerState = drawerState,
|
|
||||||
navigationActions = navigationActions
|
|
||||||
) {
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
SystemStatusTopBar(
|
SystemStatusTopBar(
|
||||||
|
|
@ -85,5 +81,4 @@ fun AppScaffold(
|
||||||
},
|
},
|
||||||
content = content
|
content = content
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.example.llama.revamp.ui.components
|
package com.example.llama.revamp.ui.components
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
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.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Home
|
import androidx.compose.material.icons.filled.Home
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
import androidx.compose.material3.DrawerState
|
import androidx.compose.material3.DrawerState
|
||||||
|
import androidx.compose.material3.DrawerValue
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -19,9 +23,14 @@ import androidx.compose.material3.ModalNavigationDrawer
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
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.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.example.llama.revamp.navigation.NavigationActions
|
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.
|
* 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
|
@Composable
|
||||||
fun AppNavigationDrawer(
|
fun AppNavigationDrawer(
|
||||||
drawerState: DrawerState,
|
drawerState: DrawerState,
|
||||||
navigationActions: NavigationActions,
|
navigationActions: NavigationActions,
|
||||||
|
modelLoaded: Boolean = false,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
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(
|
ModalNavigationDrawer(
|
||||||
drawerState = drawerState,
|
drawerState = drawerState,
|
||||||
|
gesturesEnabled = gesturesEnabled,
|
||||||
drawerContent = {
|
drawerContent = {
|
||||||
ModalDrawerSheet {
|
ModalDrawerSheet(
|
||||||
|
modifier = Modifier.width(drawerWidth)
|
||||||
|
) {
|
||||||
DrawerContent(
|
DrawerContent(
|
||||||
onHomeClicked = {
|
onHomeClicked = {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue