UI: split a nested parent settings screen into separate child settings screens

This commit is contained in:
Han Yin 2025-04-11 21:55:04 -07:00
parent 65c09b2b32
commit a7ee3d305f
6 changed files with 533 additions and 318 deletions

View File

@ -3,6 +3,7 @@ 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.OnBackPressedCallback
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
@ -24,12 +25,10 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner 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.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.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
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
@ -37,10 +36,10 @@ 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
import com.example.llama.revamp.ui.screens.ModeSelectionScreen
import com.example.llama.revamp.ui.screens.ModelSelectionScreen import com.example.llama.revamp.ui.screens.ModelSelectionScreen
import com.example.llama.revamp.ui.screens.SettingsScreen import com.example.llama.revamp.ui.screens.ModelsManagementScreen
import com.example.llama.revamp.ui.screens.SettingsTab import com.example.llama.revamp.ui.screens.ModeSelectionScreen
import com.example.llama.revamp.ui.screens.SettingsGeneralScreen
import com.example.llama.revamp.ui.theme.LlamaTheme import com.example.llama.revamp.ui.theme.LlamaTheme
import com.example.llama.revamp.util.ViewModelFactoryProvider import com.example.llama.revamp.util.ViewModelFactoryProvider
import com.example.llama.revamp.viewmodel.MainViewModel import com.example.llama.revamp.viewmodel.MainViewModel
@ -103,7 +102,8 @@ fun AppContent() {
} else { } else {
// Only enable drawer opening by gesture on these screens // Only enable drawer opening by gesture on these screens
currentRoute == AppDestinations.MODEL_SELECTION_ROUTE || currentRoute == AppDestinations.MODEL_SELECTION_ROUTE ||
currentRoute.startsWith(AppDestinations.SETTINGS_ROUTE) currentRoute == AppDestinations.SETTINGS_GENERAL_ROUTE ||
currentRoute == AppDestinations.MODELS_MANAGEMENT_ROUTE
} }
} }
} }
@ -180,7 +180,8 @@ fun AppContent() {
AppNavigationDrawer( AppNavigationDrawer(
drawerState = drawerState, drawerState = drawerState,
navigationActions = navigationActions, navigationActions = navigationActions,
gesturesEnabled = drawerGesturesEnabled gesturesEnabled = drawerGesturesEnabled,
currentRoute = currentRoute
) { ) {
NavHost( NavHost(
navController = navController, navController = navController,
@ -194,7 +195,7 @@ fun AppContent() {
navigationActions.navigateToModeSelection() navigationActions.navigateToModeSelection()
}, },
onManageModelsClicked = { onManageModelsClicked = {
navigationActions.navigateToSettings(SettingsTab.MODEL_MANAGEMENT.name) navigationActions.navigateToModelsManagement()
}, },
onMenuClicked = openDrawer, onMenuClicked = openDrawer,
drawerState = drawerState, drawerState = drawerState,
@ -255,28 +256,23 @@ fun AppContent() {
) )
} }
// Settings Screen // Settings General Screen
composable( composable(AppDestinations.SETTINGS_GENERAL_ROUTE) {
route = "${AppDestinations.SETTINGS_ROUTE}/{tab}", SettingsGeneralScreen(
arguments = listOf(
navArgument("tab") {
type = NavType.StringType
defaultValue = SettingsTab.GENERAL.name
}
)
) { backStackEntry ->
val tabName = backStackEntry.arguments?.getString("tab") ?: SettingsTab.GENERAL.name
val tab = try {
SettingsTab.valueOf(tabName)
} catch (e: IllegalArgumentException) {
SettingsTab.GENERAL
}
SettingsScreen(
selectedTab = tab,
onBackPressed = { navController.popBackStack() }, onBackPressed = { navController.popBackStack() },
drawerState = drawerState, drawerState = drawerState,
navigationActions = navigationActions navigationActions = navigationActions,
onMenuClicked = openDrawer
)
}
// Models Management Screen
composable(AppDestinations.MODELS_MANAGEMENT_ROUTE) {
ModelsManagementScreen(
onBackPressed = { navController.popBackStack() },
drawerState = drawerState,
navigationActions = navigationActions,
onMenuClicked = openDrawer
) )
} }
} }

View File

@ -6,11 +6,15 @@ import androidx.navigation.NavController
* Navigation destinations for the app * Navigation destinations for the app
*/ */
object AppDestinations { object AppDestinations {
// Primary navigation destinations
const val MODEL_SELECTION_ROUTE = "model_selection" const val MODEL_SELECTION_ROUTE = "model_selection"
const val MODE_SELECTION_ROUTE = "mode_selection" const val MODE_SELECTION_ROUTE = "mode_selection"
const val CONVERSATION_ROUTE = "conversation" const val CONVERSATION_ROUTE = "conversation"
const val BENCHMARK_ROUTE = "benchmark" const val BENCHMARK_ROUTE = "benchmark"
const val SETTINGS_ROUTE = "settings"
// Settings destinations (moved from tabs to separate routes)
const val SETTINGS_GENERAL_ROUTE = "settings_general"
const val MODELS_MANAGEMENT_ROUTE = "models_management"
} }
/** /**
@ -37,8 +41,12 @@ class NavigationActions(private val navController: NavController) {
navController.navigate(AppDestinations.BENCHMARK_ROUTE) navController.navigate(AppDestinations.BENCHMARK_ROUTE)
} }
fun navigateToSettings(tab: String = "GENERAL") { fun navigateToSettingsGeneral() {
navController.navigate("${AppDestinations.SETTINGS_ROUTE}/$tab") navController.navigate(AppDestinations.SETTINGS_GENERAL_ROUTE)
}
fun navigateToModelsManagement() {
navController.navigate(AppDestinations.MODELS_MANAGEMENT_ROUTE)
} }
fun navigateUp() { fun navigateUp() {

View File

@ -3,18 +3,21 @@ package com.example.llama.revamp.ui.components
import androidx.activity.compose.BackHandler 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.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize 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.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Folder
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.HorizontalDivider
import androidx.compose.material3.DrawerState import androidx.compose.material3.DrawerState
import androidx.compose.material3.DrawerValue import androidx.compose.material3.DrawerValue
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
import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.ModalDrawerSheet
@ -23,6 +26,7 @@ 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.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
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.platform.LocalConfiguration
@ -40,6 +44,7 @@ fun AppNavigationDrawer(
drawerState: DrawerState, drawerState: DrawerState,
navigationActions: NavigationActions, navigationActions: NavigationActions,
gesturesEnabled: Boolean, gesturesEnabled: Boolean,
currentRoute: String,
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
@ -63,18 +68,14 @@ fun AppNavigationDrawer(
modifier = Modifier.width(drawerWidth) modifier = Modifier.width(drawerWidth)
) { ) {
DrawerContent( DrawerContent(
onHomeClicked = { currentRoute = currentRoute,
onNavigate = { destination ->
coroutineScope.launch { coroutineScope.launch {
drawerState.close() drawerState.close()
navigationActions.navigateToModelSelection() destination()
} }
}, },
onSettingsClicked = { navigationActions = navigationActions
coroutineScope.launch {
drawerState.close()
navigationActions.navigateToSettings()
}
}
) )
} }
}, },
@ -84,39 +85,78 @@ fun AppNavigationDrawer(
@Composable @Composable
private fun DrawerContent( private fun DrawerContent(
onHomeClicked: () -> Unit, currentRoute: String,
onSettingsClicked: () -> Unit, onNavigate: ((Function0<Unit>)) -> Unit,
navigationActions: NavigationActions,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Column( Column(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.padding(16.dp) .padding(16.dp)
) {
// App Header
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Text( Text(
text = "Local LLM", text = "Local LLM",
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
textAlign = TextAlign.Center, textAlign = TextAlign.Center
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp)
) )
Text(
text = "v1.0.0",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 4.dp)
)
}
HorizontalDivider() HorizontalDivider()
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// Navigation Items // Main Navigation Items
Text(
text = "Navigation",
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 8.dp, bottom = 8.dp)
)
DrawerNavigationItem( DrawerNavigationItem(
icon = Icons.Default.Home, icon = Icons.Default.Home,
label = "Home", label = "Home",
onClick = onHomeClicked isSelected = currentRoute == com.example.llama.revamp.navigation.AppDestinations.MODEL_SELECTION_ROUTE,
onClick = { onNavigate { navigationActions.navigateToModelSelection() } }
)
Spacer(modifier = Modifier.height(24.dp))
// Settings Group
Text(
text = "Settings",
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 8.dp, bottom = 8.dp)
) )
DrawerNavigationItem( DrawerNavigationItem(
icon = Icons.Default.Settings, icon = Icons.Default.Settings,
label = "Settings", label = "General Settings",
onClick = onSettingsClicked isSelected = currentRoute == com.example.llama.revamp.navigation.AppDestinations.SETTINGS_GENERAL_ROUTE,
onClick = { onNavigate { navigationActions.navigateToSettingsGeneral() } }
)
DrawerNavigationItem(
icon = Icons.Default.Folder,
label = "Models Management",
isSelected = currentRoute == com.example.llama.revamp.navigation.AppDestinations.MODELS_MANAGEMENT_ROUTE,
onClick = { onNavigate { navigationActions.navigateToModelsManagement() } }
) )
} }
} }
@ -125,28 +165,47 @@ private fun DrawerContent(
private fun DrawerNavigationItem( private fun DrawerNavigationItem(
icon: ImageVector, icon: ImageVector,
label: String, label: String,
isSelected: Boolean,
onClick: () -> Unit onClick: () -> Unit
) { ) {
val backgroundColor = if (isSelected) {
MaterialTheme.colorScheme.primaryContainer
} else {
MaterialTheme.colorScheme.surface
}
val contentColor = if (isSelected) {
MaterialTheme.colorScheme.onPrimaryContainer
} else {
MaterialTheme.colorScheme.onSurface
}
Surface( Surface(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable(onClick = onClick) .clickable(onClick = onClick)
.padding(vertical = 8.dp), .padding(vertical = 4.dp),
color = MaterialTheme.colorScheme.surface color = backgroundColor,
shape = MaterialTheme.shapes.small
) { ) {
Column( Row(
modifier = Modifier.padding(8.dp) modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp, horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) { ) {
Icon( Icon(
imageVector = icon, imageVector = icon,
contentDescription = label, contentDescription = label,
tint = MaterialTheme.colorScheme.primary tint = contentColor,
modifier = Modifier.size(24.dp)
) )
Text( Text(
text = label, text = label,
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(start = 8.dp, top = 4.dp) color = contentColor,
modifier = Modifier.padding(start = 16.dp)
) )
} }
} }

View File

@ -0,0 +1,216 @@
package com.example.llama.revamp.ui.screens
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Info
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DrawerState
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.example.llama.revamp.data.model.ModelInfo
import com.example.llama.revamp.navigation.NavigationActions
import com.example.llama.revamp.ui.components.AppScaffold
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/**
* Screen for managing LLM models (view, download, delete)
*/
@Composable
fun ModelsManagementScreen(
onBackPressed: () -> Unit,
drawerState: DrawerState,
navigationActions: NavigationActions,
onMenuClicked: () -> Unit
) {
// For demo purposes, we'll use sample models
val installedModels = remember { ModelInfo.getSampleModels() }
AppScaffold(
title = "Models Management",
navigationActions = navigationActions,
onBackPressed = onBackPressed,
onMenuPressed = onMenuClicked
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(16.dp)
) {
// Summary card
Card(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = "Models Storage",
style = MaterialTheme.typography.titleMedium
)
Spacer(modifier = Modifier.height(8.dp))
Row(
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = "Storage Used: 14.6GB / 32GB",
style = MaterialTheme.typography.bodyMedium
)
Spacer(modifier = Modifier.height(4.dp))
LinearProgressIndicator(
progress = { 0.45f },
modifier = Modifier.fillMaxWidth()
)
}
OutlinedButton(
onClick = { /* Download new model */ },
modifier = Modifier.padding(start = 16.dp)
) {
Text("Add Model")
}
}
}
}
// Installed models list
Text(
text = "Installed Models",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(vertical = 8.dp)
)
LazyColumn {
items(installedModels) { model ->
ModelManagementItem(
model = model,
onInfoClick = { /* Show model details */ },
onDeleteClick = { /* Delete model */ }
)
Spacer(modifier = Modifier.height(8.dp))
}
}
}
}
}
@Composable
fun ModelManagementItem(
model: ModelInfo,
onInfoClick: () -> Unit,
onDeleteClick: () -> Unit
) {
Card(
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = model.name,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
Text(
text = "${model.parameters}${model.quantization}${model.formattedSize}",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
IconButton(onClick = onInfoClick) {
Icon(
imageVector = Icons.Default.Info,
contentDescription = "Model details",
tint = MaterialTheme.colorScheme.primary
)
}
IconButton(onClick = onDeleteClick) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Delete model",
tint = MaterialTheme.colorScheme.error
)
}
}
HorizontalDivider(
modifier = Modifier.padding(vertical = 8.dp)
)
Row(
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = "Location: ${model.path}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
model.lastUsed?.let { lastUsed ->
val dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.getDefault())
Text(
text = "Last used: ${dateFormat.format(Date(lastUsed))}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
}
}

View File

@ -0,0 +1,195 @@
package com.example.llama.revamp.ui.screens
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Card
import androidx.compose.material3.DrawerState
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.llama.revamp.data.preferences.UserPreferences
import com.example.llama.revamp.monitoring.PerformanceMonitor
import com.example.llama.revamp.navigation.NavigationActions
import com.example.llama.revamp.ui.components.AppScaffold
import com.example.llama.revamp.util.ViewModelFactoryProvider
import com.example.llama.revamp.viewmodel.PerformanceViewModel
/**
* Screen for general app settings
*/
@Composable
fun SettingsGeneralScreen(
onBackPressed: () -> Unit,
drawerState: DrawerState,
navigationActions: NavigationActions,
onMenuClicked: () -> Unit
) {
// Create dependencies for PerformanceViewModel
val context = LocalContext.current
val performanceMonitor = remember { PerformanceMonitor(context) }
val userPreferences = remember { UserPreferences(context) }
// Create factory for PerformanceViewModel
val factory = remember { ViewModelFactoryProvider.getPerformanceViewModelFactory(performanceMonitor, userPreferences) }
// Get ViewModel instance with factory
val performanceViewModel: PerformanceViewModel = viewModel(factory = factory)
// Collect state from ViewModel
val isMonitoringEnabled by performanceViewModel.isMonitoringEnabled.collectAsState()
val useFahrenheit by performanceViewModel.useFahrenheitUnit.collectAsState()
AppScaffold(
title = "Settings",
navigationActions = navigationActions,
onBackPressed = onBackPressed,
onMenuPressed = onMenuClicked
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(16.dp)
.verticalScroll(rememberScrollState())
) {
SettingsCategory(title = "Performance Monitoring") {
SettingsSwitch(
title = "Enable Monitoring",
description = "Display memory, battery and temperature information",
checked = isMonitoringEnabled,
onCheckedChange = { performanceViewModel.setMonitoringEnabled(it) }
)
SettingsSwitch(
title = "Use Fahrenheit",
description = "Display temperature in Fahrenheit instead of Celsius",
checked = useFahrenheit,
onCheckedChange = { performanceViewModel.setUseFahrenheitUnit(it) }
)
}
SettingsCategory(title = "Theme") {
SettingsSwitch(
title = "Dark Theme",
description = "Use dark theme throughout the app",
checked = true, // This would be connected to theme state in a real app
onCheckedChange = { /* TODO: Implement theme switching */ }
)
}
SettingsCategory(title = "About") {
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = "Local LLM",
style = MaterialTheme.typography.titleLarge
)
Text(
text = "Version 1.0.0",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Local inference for LLM models on your device.",
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
}
}
@Composable
fun SettingsCategory(
title: String,
content: @Composable () -> Unit
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 8.dp)
)
Card(
modifier = Modifier.fillMaxWidth()
) {
Column {
content()
}
}
Spacer(modifier = Modifier.height(16.dp))
}
}
@Composable
fun SettingsSwitch(
title: String,
description: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium
)
Text(
text = description,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Switch(
checked = checked,
onCheckedChange = onCheckedChange
)
}
}
HorizontalDivider()
}

View File

@ -1,259 +0,0 @@
package com.example.llama.revamp.ui.screens
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Card
import androidx.compose.material3.Divider
import androidx.compose.material3.DrawerState
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.llama.revamp.data.preferences.UserPreferences
import com.example.llama.revamp.monitoring.PerformanceMonitor
import com.example.llama.revamp.navigation.NavigationActions
import com.example.llama.revamp.ui.components.AppScaffold
import com.example.llama.revamp.util.ViewModelFactoryProvider
import com.example.llama.revamp.viewmodel.PerformanceViewModel
/**
* Tabs for the settings screen.
*/
enum class SettingsTab {
GENERAL,
MODEL_MANAGEMENT,
ADVANCED
}
@Composable
fun SettingsScreen(
selectedTab: SettingsTab = SettingsTab.GENERAL,
onBackPressed: () -> Unit,
drawerState: DrawerState,
navigationActions: NavigationActions
) {
// State for tab selection
var currentTab by remember { mutableStateOf(selectedTab) }
// Create dependencies for PerformanceViewModel
val context = LocalContext.current
val performanceMonitor = remember { PerformanceMonitor(context) }
val userPreferences = remember { UserPreferences(context) }
// Create factory for PerformanceViewModel
val factory = remember { ViewModelFactoryProvider.getPerformanceViewModelFactory(performanceMonitor, userPreferences) }
// Get ViewModel instance with factory
val performanceViewModel: PerformanceViewModel = viewModel(factory = factory)
AppScaffold(
title = "Settings",
drawerState = drawerState,
navigationActions = navigationActions,
onBackPressed = onBackPressed
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
// Tabs for different settings categories
TabRow(selectedTabIndex = currentTab.ordinal) {
Tab(
selected = currentTab == SettingsTab.GENERAL,
onClick = { currentTab = SettingsTab.GENERAL },
text = { Text("General") }
)
Tab(
selected = currentTab == SettingsTab.MODEL_MANAGEMENT,
onClick = { currentTab = SettingsTab.MODEL_MANAGEMENT },
text = { Text("Models") }
)
Tab(
selected = currentTab == SettingsTab.ADVANCED,
onClick = { currentTab = SettingsTab.ADVANCED },
text = { Text("Advanced") }
)
}
// Content for the selected tab
when (currentTab) {
SettingsTab.GENERAL -> GeneralSettings(performanceViewModel)
SettingsTab.MODEL_MANAGEMENT -> ModelManagementSettings()
SettingsTab.ADVANCED -> AdvancedSettings()
}
}
}
}
@Composable
fun GeneralSettings(performanceViewModel: PerformanceViewModel) {
val isMonitoringEnabled by performanceViewModel.isMonitoringEnabled.collectAsState()
val useFahrenheit by performanceViewModel.useFahrenheitUnit.collectAsState()
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.verticalScroll(rememberScrollState())
) {
SettingsCategory(title = "Performance Monitoring") {
SettingsSwitch(
title = "Enable Monitoring",
description = "Display memory, battery and temperature information",
checked = isMonitoringEnabled,
onCheckedChange = { performanceViewModel.setMonitoringEnabled(it) }
)
SettingsSwitch(
title = "Use Fahrenheit",
description = "Display temperature in Fahrenheit instead of Celsius",
checked = useFahrenheit,
onCheckedChange = { performanceViewModel.setUseFahrenheitUnit(it) }
)
}
SettingsCategory(title = "Theme") {
SettingsSwitch(
title = "Dark Theme",
description = "Use dark theme throughout the app",
checked = true, // This would be connected to theme state in a real app
onCheckedChange = { /* TODO: Implement theme switching */ }
)
}
}
}
@Composable
fun ModelManagementSettings() {
// This would be populated with actual functionality in a real implementation
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.verticalScroll(rememberScrollState())
) {
Text(
text = "Model Management",
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(bottom = 16.dp)
)
Text(
text = "This section will allow you to download, delete, and manage LLM models.",
style = MaterialTheme.typography.bodyMedium
)
}
}
@Composable
fun AdvancedSettings() {
// This would be populated with actual functionality in a real implementation
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.verticalScroll(rememberScrollState())
) {
Text(
text = "Advanced Settings",
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(bottom = 16.dp)
)
Text(
text = "This section will contain advanced settings such as memory management, cache configuration, and debugging options.",
style = MaterialTheme.typography.bodyMedium
)
}
}
@Composable
fun SettingsCategory(
title: String,
content: @Composable () -> Unit
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 8.dp)
)
Card(
modifier = Modifier.fillMaxWidth()
) {
Column {
content()
}
}
Spacer(modifier = Modifier.height(16.dp))
}
}
@Composable
fun SettingsSwitch(
title: String,
description: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium
)
Text(
text = description,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Switch(
checked = checked,
onCheckedChange = onCheckedChange
)
}
}
Divider()
}