1 file changed
+169
-62
@@ -18,6 +18,7 @@ import androidx.compose.material3.AlertDialog
|
|||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -45,6 +46,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
import com.m57.hermescontrol.theme.LocalSpacing
|
import com.m57.hermescontrol.theme.LocalSpacing
|
||||||
import com.m57.hermescontrol.ui.common.EmptyState
|
import com.m57.hermescontrol.ui.common.EmptyState
|
||||||
import com.m57.hermescontrol.ui.common.ErrorState
|
import com.m57.hermescontrol.ui.common.ErrorState
|
||||||
|
import com.m57.hermescontrol.ui.common.FilterChipRow
|
||||||
import com.m57.hermescontrol.ui.common.HermesScaffold
|
import com.m57.hermescontrol.ui.common.HermesScaffold
|
||||||
import com.m57.hermescontrol.ui.common.LoadingState
|
import com.m57.hermescontrol.ui.common.LoadingState
|
||||||
import com.m57.hermescontrol.ui.common.SearchBar
|
import com.m57.hermescontrol.ui.common.SearchBar
|
||||||
@@ -61,6 +63,13 @@ fun SkillsScreen(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val spacing = LocalSpacing.current
|
val spacing = LocalSpacing.current
|
||||||
var query by remember { mutableStateOf("") }
|
var query by remember { mutableStateOf("") }
|
||||||
|
val statuses = listOf("All Statuses", "Enabled", "Disabled")
|
||||||
|
var selectedStatus by remember { mutableStateOf("All Statuses") }
|
||||||
|
val categories =
|
||||||
|
remember(state.skills) {
|
||||||
|
listOf("All") + state.skills.mapNotNull { it.category }.distinct().sorted()
|
||||||
|
}
|
||||||
|
var selectedCategory by remember { mutableStateOf("All") }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.loadSkills()
|
viewModel.loadSkills()
|
||||||
@@ -74,13 +83,32 @@ fun SkillsScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val filtered =
|
val filtered =
|
||||||
if (query.isBlank()) {
|
remember(state.skills, query, selectedStatus, selectedCategory) {
|
||||||
state.skills
|
state.skills.filter { skill ->
|
||||||
} else {
|
val matchesQuery =
|
||||||
state.skills.filter {
|
if (query.isBlank()) {
|
||||||
it.name.contains(query, ignoreCase = true) ||
|
true
|
||||||
it.category?.contains(query, ignoreCase = true) == true ||
|
} else {
|
||||||
it.description?.contains(query, ignoreCase = true) == true
|
skill.name.contains(query, ignoreCase = true) ||
|
||||||
|
skill.category?.contains(query, ignoreCase = true) == true ||
|
||||||
|
skill.description?.contains(query, ignoreCase = true) == true
|
||||||
|
}
|
||||||
|
|
||||||
|
val matchesStatus =
|
||||||
|
when (selectedStatus) {
|
||||||
|
"Enabled" -> skill.enabled
|
||||||
|
"Disabled" -> !skill.enabled
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
|
||||||
|
val matchesCategory =
|
||||||
|
if (selectedCategory == "All") {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
skill.category == selectedCategory
|
||||||
|
}
|
||||||
|
|
||||||
|
matchesQuery && matchesStatus && matchesCategory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,74 +151,153 @@ fun SkillsScreen(
|
|||||||
placeholder = "Search skills…",
|
placeholder = "Search skills…",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(filtered) { skill ->
|
item {
|
||||||
Card(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier =
|
||||||
colors =
|
Modifier
|
||||||
CardDefaults.cardColors(
|
.fillMaxWidth()
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
.padding(horizontal = spacing.md),
|
||||||
),
|
verticalArrangement = Arrangement.spacedBy(spacing.xs),
|
||||||
) {
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Status",
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
Row(
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(spacing.xs),
|
||||||
|
) {
|
||||||
|
statuses.forEach { status ->
|
||||||
|
FilterChip(
|
||||||
|
selected = selectedStatus == status,
|
||||||
|
onClick = { selectedStatus = status },
|
||||||
|
label = { Text(status) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (categories.size > 2) {
|
||||||
|
item {
|
||||||
|
Column(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(spacing.md),
|
.padding(vertical = spacing.xs),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
verticalArrangement = Arrangement.spacedBy(spacing.xs),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Category",
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(horizontal = spacing.md),
|
||||||
|
)
|
||||||
|
FilterChipRow(
|
||||||
|
chips = categories,
|
||||||
|
selectedChip = selectedCategory,
|
||||||
|
onChipSelected = { selectedCategory = it },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (filtered.isEmpty()) {
|
||||||
|
item {
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = spacing.lg),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(spacing.xs),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "No matching skills",
|
||||||
|
style =
|
||||||
|
MaterialTheme.typography.titleMedium.copy(
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Try adjusting your search or filters.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
items(filtered) { skill ->
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors =
|
||||||
|
CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.weight(1f),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(spacing.md),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Row(
|
||||||
imageVector = Icons.Filled.Extension,
|
modifier = Modifier.weight(1f),
|
||||||
contentDescription = null,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
)
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
|
||||||
Text(
|
|
||||||
text = skill.name,
|
|
||||||
style =
|
|
||||||
MaterialTheme.typography.titleMedium.copy(
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
skill.category?.let { cat ->
|
|
||||||
Text(
|
|
||||||
text = cat,
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
skill.description?.let { desc ->
|
|
||||||
Text(
|
|
||||||
text = desc,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
maxLines = 2,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
) {
|
|
||||||
IconButton(
|
|
||||||
onClick = { viewModel.loadSkillContent(skill.name) },
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Edit,
|
imageVector = Icons.Filled.Extension,
|
||||||
contentDescription = "Edit Skill File",
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = skill.name,
|
||||||
|
style =
|
||||||
|
MaterialTheme.typography.titleMedium.copy(
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
skill.category?.let { cat ->
|
||||||
|
Text(
|
||||||
|
text = cat,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
skill.description?.let { desc ->
|
||||||
|
Text(
|
||||||
|
text = desc,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
maxLines = 2,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = { viewModel.loadSkillContent(skill.name) },
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Edit,
|
||||||
|
contentDescription = "Edit Skill File",
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Switch(
|
||||||
|
checked = skill.enabled,
|
||||||
|
onCheckedChange = { viewModel.toggleSkill(skill) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Switch(
|
|
||||||
checked = skill.enabled,
|
|
||||||
onCheckedChange = { viewModel.toggleSkill(skill) },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user