UI: implement core flow's screens
This commit is contained in:
parent
5ad65919e9
commit
7e5c80cee9
|
|
@ -0,0 +1,143 @@
|
|||
package com.example.llama.revamp.ui.screens
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DrawerState
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.example.llama.revamp.engine.InferenceEngine
|
||||
import com.example.llama.revamp.navigation.NavigationActions
|
||||
import com.example.llama.revamp.ui.components.AppScaffold
|
||||
import com.example.llama.revamp.ui.theme.MonospacedTextStyle
|
||||
import com.example.llama.revamp.viewmodel.MainViewModel
|
||||
|
||||
@Composable
|
||||
fun BenchmarkScreen(
|
||||
onBackPressed: () -> Unit,
|
||||
onRerunPressed: () -> Unit,
|
||||
onSharePressed: () -> Unit,
|
||||
drawerState: DrawerState,
|
||||
navigationActions: NavigationActions,
|
||||
viewModel: MainViewModel = viewModel()
|
||||
) {
|
||||
val engineState by viewModel.engineState.collectAsState()
|
||||
val benchmarkResults by viewModel.benchmarkResults.collectAsState()
|
||||
val selectedModel by viewModel.selectedModel.collectAsState()
|
||||
|
||||
AppScaffold(
|
||||
title = "Benchmark Results",
|
||||
drawerState = drawerState,
|
||||
navigationActions = navigationActions,
|
||||
onBackPressed = onBackPressed,
|
||||
onRerunPressed = onRerunPressed,
|
||||
onSharePressed = onSharePressed
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
.padding(16.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
// Model info
|
||||
selectedModel?.let { model ->
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = model.name,
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "${model.parameters} [${model.quantization}]",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark results or loading indicator
|
||||
when {
|
||||
engineState is InferenceEngine.State.Benchmarking -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(200.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
CircularProgressIndicator()
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = "Running benchmark...",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
benchmarkResults != null -> {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = benchmarkResults ?: "",
|
||||
style = MonospacedTextStyle,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(200.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "Benchmark results will appear here",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,354 @@
|
|||
package com.example.llama.revamp.ui.screens
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
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.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ExpandLess
|
||||
import androidx.compose.material.icons.filled.ExpandMore
|
||||
import androidx.compose.material.icons.filled.Send
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DrawerState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.example.llama.revamp.engine.InferenceEngine
|
||||
import com.example.llama.revamp.navigation.NavigationActions
|
||||
import com.example.llama.revamp.ui.components.AppScaffold
|
||||
import com.example.llama.revamp.viewmodel.MainViewModel
|
||||
import com.example.llama.revamp.viewmodel.Message
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ConversationScreen(
|
||||
onBackPressed: () -> Unit,
|
||||
drawerState: DrawerState,
|
||||
navigationActions: NavigationActions,
|
||||
viewModel: MainViewModel = viewModel()
|
||||
) {
|
||||
val engineState by viewModel.engineState.collectAsState()
|
||||
val messages by viewModel.messages.collectAsState()
|
||||
val systemPrompt by viewModel.systemPrompt.collectAsState()
|
||||
val selectedModel by viewModel.selectedModel.collectAsState()
|
||||
|
||||
val isProcessing = engineState is InferenceEngine.State.ProcessingUserPrompt
|
||||
val isGenerating = engineState is InferenceEngine.State.Generating
|
||||
|
||||
val lazyListState = rememberLazyListState()
|
||||
var inputText by remember { mutableStateOf("") }
|
||||
|
||||
// Auto-scroll to bottom when messages change
|
||||
LaunchedEffect(messages.size) {
|
||||
if (messages.isNotEmpty()) {
|
||||
lazyListState.animateScrollToItem(messages.size - 1)
|
||||
}
|
||||
}
|
||||
|
||||
AppScaffold(
|
||||
title = selectedModel?.name ?: "Conversation",
|
||||
drawerState = drawerState,
|
||||
navigationActions = navigationActions,
|
||||
onBackPressed = onBackPressed
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
// System prompt display (collapsible)
|
||||
systemPrompt?.let { prompt ->
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(8.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "System Prompt",
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
IconButton(onClick = { expanded = !expanded }) {
|
||||
Icon(
|
||||
imageVector = if (expanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore,
|
||||
contentDescription = if (expanded) "Collapse" else "Expand"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(visible = expanded) {
|
||||
Text(
|
||||
text = prompt,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Messages
|
||||
LazyColumn(
|
||||
state = lazyListState,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
items(messages) { message ->
|
||||
MessageBubble(message = message)
|
||||
}
|
||||
|
||||
// Show thinking indicator when processing
|
||||
item {
|
||||
if (isProcessing) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// Assistant avatar
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.primary),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "AI",
|
||||
color = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(16.dp),
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
Text(
|
||||
text = "Thinking...",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Input area
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = inputText,
|
||||
onValueChange = { inputText = it },
|
||||
modifier = Modifier.weight(1f),
|
||||
placeholder = { Text("Type your message...") },
|
||||
singleLine = false,
|
||||
maxLines = 5,
|
||||
enabled = !isProcessing && !isGenerating
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (inputText.isNotBlank()) {
|
||||
viewModel.sendMessage(inputText)
|
||||
inputText = ""
|
||||
}
|
||||
},
|
||||
enabled = inputText.isNotBlank() && !isProcessing && !isGenerating
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Send,
|
||||
contentDescription = "Send",
|
||||
tint = if (inputText.isNotBlank() && !isProcessing && !isGenerating)
|
||||
MaterialTheme.colorScheme.primary
|
||||
else
|
||||
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MessageBubble(message: Message) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp),
|
||||
verticalAlignment = Alignment.Top
|
||||
) {
|
||||
when (message) {
|
||||
is Message.User -> {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(40.dp)
|
||||
.height(40.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.secondaryContainer),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "You",
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
style = MaterialTheme.typography.labelMedium
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(5f)
|
||||
.clip(
|
||||
RoundedCornerShape(
|
||||
topStart = 16.dp,
|
||||
topEnd = 16.dp,
|
||||
bottomStart = 16.dp,
|
||||
bottomEnd = 4.dp
|
||||
)
|
||||
)
|
||||
.background(MaterialTheme.colorScheme.primaryContainer)
|
||||
.padding(12.dp)
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = message.content,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
|
||||
Text(
|
||||
text = message.formattedTime,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.7f),
|
||||
modifier = Modifier.align(Alignment.End)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is Message.Assistant -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.primary),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "AI",
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
style = MaterialTheme.typography.labelMedium
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(5f)
|
||||
.clip(
|
||||
RoundedCornerShape(
|
||||
topStart = 16.dp,
|
||||
topEnd = 16.dp,
|
||||
bottomStart = 4.dp,
|
||||
bottomEnd = 16.dp
|
||||
)
|
||||
)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.padding(12.dp)
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = message.content,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
if (message.isComplete) {
|
||||
Text(
|
||||
text = message.formattedTime,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
|
||||
modifier = Modifier.align(Alignment.End)
|
||||
)
|
||||
} else {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.size(12.dp)
|
||||
.align(Alignment.End),
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,393 @@
|
|||
package com.example.llama.revamp.ui.screens
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
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.selection.selectable
|
||||
import androidx.compose.foundation.selection.selectableGroup
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ExpandLess
|
||||
import androidx.compose.material.icons.filled.ExpandMore
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.DrawerState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.llama.revamp.data.model.SystemPrompt
|
||||
import com.example.llama.revamp.engine.InferenceEngine
|
||||
import com.example.llama.revamp.navigation.NavigationActions
|
||||
import com.example.llama.revamp.ui.components.AppScaffold
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ModeSelectionScreen(
|
||||
engineState: InferenceEngine.State,
|
||||
onBenchmarkSelected: () -> Unit,
|
||||
onConversationSelected: (String?) -> Unit,
|
||||
onBackPressed: () -> Unit,
|
||||
drawerState: DrawerState,
|
||||
navigationActions: NavigationActions
|
||||
) {
|
||||
val staffPickedPrompts = remember { SystemPrompt.getStaffPickedPrompts() }
|
||||
val recentPrompts = remember { SystemPrompt.getRecentPrompts() }
|
||||
|
||||
var selectedMode by remember { mutableStateOf<Mode?>(null) }
|
||||
var useSystemPrompt by remember { mutableStateOf(false) }
|
||||
var selectedPrompt by remember { mutableStateOf<SystemPrompt?>(null) }
|
||||
var tabIndex by remember { mutableStateOf(0) }
|
||||
|
||||
// Custom prompt sheet state
|
||||
val sheetState = rememberModalBottomSheetState()
|
||||
var showCustomPromptSheet by remember { mutableStateOf(false) }
|
||||
var customPromptText by remember { mutableStateOf("") }
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
// Check if we're in a loading state
|
||||
val isLoading = engineState !is InferenceEngine.State.Uninitialized &&
|
||||
engineState !is InferenceEngine.State.LibraryLoaded &&
|
||||
engineState !is InferenceEngine.State.AwaitingUserPrompt
|
||||
|
||||
AppScaffold(
|
||||
title = "Select Mode",
|
||||
drawerState = drawerState,
|
||||
navigationActions = navigationActions,
|
||||
onBackPressed = onBackPressed
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
// Mode selection cards
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 8.dp)
|
||||
.selectable(
|
||||
selected = selectedMode == Mode.BENCHMARK,
|
||||
onClick = { selectedMode = Mode.BENCHMARK },
|
||||
enabled = !isLoading,
|
||||
role = Role.RadioButton
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
RadioButton(
|
||||
selected = selectedMode == Mode.BENCHMARK,
|
||||
onClick = null // handled by parent selectable
|
||||
)
|
||||
Text(
|
||||
text = "Benchmark",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 8.dp)
|
||||
.selectable(
|
||||
selected = selectedMode == Mode.CONVERSATION,
|
||||
onClick = {
|
||||
selectedMode = Mode.CONVERSATION
|
||||
},
|
||||
enabled = !isLoading,
|
||||
role = Role.RadioButton
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
RadioButton(
|
||||
selected = selectedMode == Mode.CONVERSATION,
|
||||
onClick = null // handled by parent selectable
|
||||
)
|
||||
Text(
|
||||
text = "Conversation",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// System prompt section (only visible when conversation mode is selected)
|
||||
AnimatedVisibility(visible = selectedMode == Mode.CONVERSATION) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 8.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.selectableGroup(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "Use system prompt",
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
TextButton(
|
||||
onClick = { useSystemPrompt = !useSystemPrompt }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (useSystemPrompt) Icons.Default.ExpandLess else Icons.Default.ExpandMore,
|
||||
contentDescription = if (useSystemPrompt) "Collapse" else "Expand"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(visible = useSystemPrompt) {
|
||||
Column {
|
||||
TabRow(selectedTabIndex = tabIndex) {
|
||||
Tab(
|
||||
selected = tabIndex == 0,
|
||||
onClick = { tabIndex = 0 },
|
||||
text = { Text("Staff Picks") }
|
||||
)
|
||||
|
||||
Tab(
|
||||
selected = tabIndex == 1,
|
||||
onClick = { tabIndex = 1 },
|
||||
text = { Text("Recent") }
|
||||
)
|
||||
}
|
||||
|
||||
// Tab content
|
||||
when (tabIndex) {
|
||||
0 -> PromptList(
|
||||
prompts = staffPickedPrompts,
|
||||
selectedPrompt = selectedPrompt,
|
||||
onPromptSelected = { selectedPrompt = it }
|
||||
)
|
||||
1 -> PromptList(
|
||||
prompts = recentPrompts,
|
||||
selectedPrompt = selectedPrompt,
|
||||
onPromptSelected = { selectedPrompt = it }
|
||||
)
|
||||
}
|
||||
|
||||
// Custom prompt button
|
||||
OutlinedButton(
|
||||
onClick = { showCustomPromptSheet = true },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp)
|
||||
) {
|
||||
Text("Custom prompt...")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
// Start button
|
||||
Button(
|
||||
onClick = {
|
||||
when (selectedMode) {
|
||||
Mode.BENCHMARK -> onBenchmarkSelected()
|
||||
Mode.CONVERSATION -> {
|
||||
val systemPrompt = if (useSystemPrompt) {
|
||||
selectedPrompt?.content ?: customPromptText.takeIf { it.isNotBlank() }
|
||||
} else null
|
||||
onConversationSelected(systemPrompt)
|
||||
}
|
||||
null -> { /* No mode selected */ }
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp),
|
||||
enabled = selectedMode != null && !isLoading
|
||||
) {
|
||||
if (isLoading) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.height(24.dp)
|
||||
.width(24.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = when(engineState) {
|
||||
is InferenceEngine.State.LoadingModel -> "Loading model..."
|
||||
is InferenceEngine.State.ProcessingSystemPrompt -> "Processing system prompt..."
|
||||
is InferenceEngine.State.ModelLoaded -> "Preparing conversation..."
|
||||
else -> "Processing..."
|
||||
}
|
||||
)
|
||||
} else {
|
||||
Text("Start")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom prompt bottom sheet
|
||||
if (showCustomPromptSheet) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = { showCustomPromptSheet = false },
|
||||
sheetState = sheetState
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Custom System Prompt",
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
value = customPromptText,
|
||||
onValueChange = { customPromptText = it },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(200.dp),
|
||||
label = { Text("Enter system prompt") },
|
||||
placeholder = { Text("You are a helpful assistant...") }
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
TextButton(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
sheetState.hide()
|
||||
showCustomPromptSheet = false
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text("Cancel")
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
selectedPrompt = null
|
||||
coroutineScope.launch {
|
||||
sheetState.hide()
|
||||
showCustomPromptSheet = false
|
||||
}
|
||||
},
|
||||
enabled = customPromptText.isNotBlank()
|
||||
) {
|
||||
Text("Use Custom Prompt")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PromptList(
|
||||
prompts: List<SystemPrompt>,
|
||||
selectedPrompt: SystemPrompt?,
|
||||
onPromptSelected: (SystemPrompt) -> Unit
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(200.dp)
|
||||
.padding(vertical = 8.dp)
|
||||
) {
|
||||
items(prompts) { prompt ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.selectable(
|
||||
selected = selectedPrompt?.id == prompt.id,
|
||||
onClick = { onPromptSelected(prompt) },
|
||||
role = Role.RadioButton
|
||||
)
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
RadioButton(
|
||||
selected = selectedPrompt?.id == prompt.id,
|
||||
onClick = null // handled by parent selectable
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = prompt.name,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
|
||||
Text(
|
||||
text = prompt.content,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Divider(modifier = Modifier.padding(horizontal = 16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
BENCHMARK,
|
||||
CONVERSATION
|
||||
}
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
package com.example.llama.revamp.ui.screens
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
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.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.DrawerState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
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.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
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ModelSelectionScreen(
|
||||
onModelSelected: (ModelInfo) -> Unit,
|
||||
onManageModelsClicked: () -> Unit,
|
||||
onMenuClicked: () -> Unit,
|
||||
drawerState: DrawerState,
|
||||
navigationActions: NavigationActions
|
||||
) {
|
||||
// For demo purposes, we'll use sample models
|
||||
val models = remember { ModelInfo.getSampleModels() }
|
||||
|
||||
AppScaffold(
|
||||
title = "Models",
|
||||
drawerState = drawerState,
|
||||
navigationActions = navigationActions,
|
||||
onMenuPressed = onMenuClicked
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
TextButton(
|
||||
onClick = onManageModelsClicked,
|
||||
modifier = Modifier
|
||||
.align(Alignment.End)
|
||||
.padding(top = 8.dp, bottom = 8.dp)
|
||||
) {
|
||||
Text("Manage Models")
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "Downloaded Models",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(vertical = 8.dp)
|
||||
)
|
||||
|
||||
LazyColumn {
|
||||
items(models) { model ->
|
||||
ModelCard(
|
||||
model = model,
|
||||
onClick = { onModelSelected(model) }
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ModelCard(
|
||||
model: ModelInfo,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick),
|
||||
elevation = CardDefaults.cardElevation(
|
||||
defaultElevation = 2.dp
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = model.name,
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
Row {
|
||||
Text(
|
||||
text = model.parameters,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
Text(
|
||||
text = " • ${model.quantization}",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
Text(
|
||||
text = " • ${model.formattedSize}",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
Text(
|
||||
text = "Context Length: ${model.contextLength}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
IconButton(
|
||||
onClick = onClick
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.PlayArrow,
|
||||
contentDescription = "Select model",
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,259 @@
|
|||
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()
|
||||
}
|
||||
Loading…
Reference in New Issue