UI: optimize AppContent's composing

This commit is contained in:
Han Yin 2025-04-13 18:26:41 -07:00
parent 0afd087f35
commit ea11ee3c94
2 changed files with 36 additions and 57 deletions

View File

@ -28,6 +28,7 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
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.AppNavigationDrawer
@ -65,61 +66,44 @@ class MainActivity : ComponentActivity() {
fun AppContent( fun AppContent(
mainVewModel: MainViewModel = hiltViewModel() mainVewModel: MainViewModel = hiltViewModel()
) { ) {
// Lifecycle and Coroutine scope
val lifecycleOwner = LocalLifecycleOwner.current
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
// Navigation
val navController = rememberNavController() val navController = rememberNavController()
val navigationActions = remember(navController) { NavigationActions(navController) } val navigationActions = remember(navController) { NavigationActions(navController) }
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val engineState by mainVewModel.engineState.collectAsState()
// TODO-han.yin: Also use delegate for `isModelLoaded`:
val isModelLoaded = remember(engineState) { mainVewModel.isModelLoaded() }
// Model unloading confirmation
var showUnloadDialog by remember { mutableStateOf(false) }
var pendingNavigation by remember { mutableStateOf<(() -> Unit)?>(null) }
// Get current route
val navBackStackEntry by navController.currentBackStackEntryAsState() val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute by remember { val currentRoute by remember {
derivedStateOf { navBackStackEntry?.destination?.route ?: "" } derivedStateOf { navBackStackEntry?.destination?.route ?: "" }
} }
var pendingNavigation by remember { mutableStateOf<(() -> Unit)?>(null) }
// Determine if drawer gestures should be enabled based on route // LLM Inference engine status
val drawerGesturesEnabled by remember(currentRoute, drawerState.currentValue) { val engineState by mainVewModel.engineState.collectAsState()
derivedStateOf { val isModelLoading = engineState is InferenceEngine.State.LoadingModel
// Always allow gesture dismissal when drawer is open || engineState is InferenceEngine.State.ProcessingSystemPrompt
if (drawerState.currentValue == DrawerValue.Open) { val isModelLoaded = engineState !is InferenceEngine.State.Uninitialized
true && engineState !is InferenceEngine.State.LibraryLoaded
} else {
// Only enable drawer opening by gesture on these screens
currentRoute == AppDestinations.MODEL_SELECTION_ROUTE ||
currentRoute == AppDestinations.SETTINGS_GENERAL_ROUTE ||
currentRoute == AppDestinations.MODELS_MANAGEMENT_ROUTE
}
}
}
// Determine if current route requires model unloading // Determine if current route requires model unloading
val routeNeedsModelUnloading by remember(currentRoute) { val routeNeedsModelUnloading by remember(currentRoute) {
derivedStateOf { derivedStateOf {
currentRoute == AppDestinations.CONVERSATION_ROUTE || currentRoute == AppDestinations.CONVERSATION_ROUTE
currentRoute == AppDestinations.BENCHMARK_ROUTE || || currentRoute == AppDestinations.BENCHMARK_ROUTE
currentRoute == AppDestinations.MODEL_LOADING_ROUTE || currentRoute == AppDestinations.MODEL_LOADING_ROUTE
} }
} }
// Get local back dispatcher // Model unloading confirmation
val backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher var showUnloadDialog by remember { mutableStateOf(false) }
val lifecycleOwner = LocalLifecycleOwner.current
// Helper function to handle back press with model unloading check // Helper function to handle back press with model unloading check
val handleBackWithModelCheck = { val handleBackWithModelCheck = {
if (mainVewModel.isModelLoading()) { if (isModelLoading) {
// If model is still loading, ignore the request // If model is still loading, ignore the request
true // Mark as handled true // Mark as handled
} else if (mainVewModel.isModelLoaded()) { } else if (isModelLoaded) {
showUnloadDialog = true showUnloadDialog = true
pendingNavigation = { navController.popBackStack() } pendingNavigation = { navController.popBackStack() }
true // Mark as handled true // Mark as handled
@ -130,6 +114,7 @@ fun AppContent(
} }
// Register a system back handler for screens that need unload confirmation // Register a system back handler for screens that need unload confirmation
val backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
DisposableEffect(lifecycleOwner, backDispatcher, currentRoute, isModelLoaded) { DisposableEffect(lifecycleOwner, backDispatcher, currentRoute, isModelLoaded) {
val callback = object : OnBackPressedCallback( val callback = object : OnBackPressedCallback(
// Only enable for screens that need model unloading confirmation // Only enable for screens that need model unloading confirmation
@ -148,6 +133,22 @@ fun AppContent(
} }
} }
// Determine if drawer gestures should be enabled based on route
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val drawerGesturesEnabled by remember(currentRoute, drawerState.currentValue) {
derivedStateOf {
// Always allow gesture dismissal when drawer is open
if (drawerState.currentValue == DrawerValue.Open) {
true
} else {
// Only enable drawer opening by gesture on these screens
currentRoute == AppDestinations.MODEL_SELECTION_ROUTE ||
currentRoute == AppDestinations.SETTINGS_GENERAL_ROUTE ||
currentRoute == AppDestinations.MODELS_MANAGEMENT_ROUTE
}
}
}
// Compose BackHandler for added protection (this handles Compose-based back navigation) // Compose BackHandler for added protection (this handles Compose-based back navigation)
BackHandler( BackHandler(
enabled = routeNeedsModelUnloading && enabled = routeNeedsModelUnloading &&
@ -166,11 +167,7 @@ fun AppContent(
} }
// Handle drawer state // Handle drawer state
val openDrawer: () -> Unit = { val openDrawer: () -> Unit = { coroutineScope.launch { drawerState.open() } }
coroutineScope.launch {
drawerState.open()
}
}
// Main Content with navigation drawer wrapper // Main Content with navigation drawer wrapper
AppNavigationDrawer( AppNavigationDrawer(

View File

@ -260,24 +260,6 @@ class MainViewModel @Inject constructor (
inferenceEngine.unloadModel() inferenceEngine.unloadModel()
} }
/**
* Checks if a model is currently being loaded.
*/
fun isModelLoading() =
engineState.value.let {
it is InferenceEngine.State.LoadingModel
|| it is InferenceEngine.State.ProcessingSystemPrompt
}
/**
* Checks if a model has already been loaded.
*/
fun isModelLoaded() =
engineState.value.let {
it !is InferenceEngine.State.Uninitialized
&& it !is InferenceEngine.State.LibraryLoaded
}
/** /**
* Clean up resources when ViewModel is cleared. * Clean up resources when ViewModel is cleared.
*/ */