@file:OptIn( androidx.compose.material3.ExperimentalMaterial3Api::class, androidx.compose.foundation.layout.ExperimentalLayoutApi::class ) package me.eternal.purrfect.ui.manager.pages.tracker import androidx.compose.animation.animateContentSize import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.DeleteOutline import androidx.compose.material.icons.filled.ExpandLess import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Save import androidx.compose.material.icons.filled.Warning import androidx.activity.compose.BackHandler import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.material3.LocalTextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.navigation.NavBackStackEntry import kotlinx.coroutines.launch import me.eternal.purrfect.common.data.* import me.eternal.purrfect.common.ui.rememberAsyncMutableState import me.eternal.purrfect.common.ui.rememberAsyncMutableStateList import me.eternal.purrfect.storage.* import me.eternal.purrfect.ui.manager.Routes import me.eternal.purrfect.ui.manager.components.AestheticDialog import me.eternal.purrfect.common.ui.theme.LocalPurrfectSkin import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.graphics.SolidColor import me.eternal.purrfect.ui.manager.pages.social.AddFriendDialog class EditRule : Routes.Route() { override val translation by lazy { context.translation.getCategory("manager.friend_tracker") } private data class RuleSnapshot( val name: String, val author: String, val scopes: List, val events: List, val scopeType: TrackerScopeType ) @Composable private fun RuleCard( modifier: Modifier = Modifier, contentPadding: PaddingValues = PaddingValues(16.dp), content: @Composable ColumnScope.() -> Unit ) { val shape = RoundedCornerShape(22.dp) val glowPrimary = TrackerSkinPalette.glowPrimary val glowSecondary = TrackerSkinPalette.glowSecondary val border = remember(glowPrimary, glowSecondary) { Brush.linearGradient( listOf( glowPrimary.copy(alpha = 0.5f), glowSecondary.copy(alpha = 0.42f) ) ) } Surface( modifier = modifier, shape = shape, color = Color.White.copy(alpha = 0.04f), tonalElevation = 0.dp, shadowElevation = 14.dp, border = BorderStroke(1.dp, border) ) { Box( modifier = Modifier .background(TrackerSkinPalette.cardOverlay, shape) .padding(contentPadding) ) { Column(content = content) } } } @Composable fun ActionCheckbox( text: String, checked: MutableState, onChanged: (Boolean) -> Unit = {} ) { Row( modifier = Modifier.clickable { checked.value = !checked.value onChanged(checked.value) }, horizontalArrangement = Arrangement.spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically ) { Checkbox( modifier = Modifier.height(30.dp), checked = checked.value, onCheckedChange = { checked.value = it; onChanged(it) }, colors = CheckboxDefaults.colors( checkedColor = TrackerSkinPalette.glowPrimary, uncheckedColor = Color.White.copy(alpha = 0.8f), checkmarkColor = Color.Black ) ) Text(text, fontSize = 12.sp, color = Color.White) } } @Composable fun ConditionCheckboxes( params: TrackerRuleActionParams ) { ActionCheckbox( text = translation["condition_only_inside_conversation"], checked = remember { mutableStateOf(params.onlyInsideConversation) }, onChanged = { params.onlyInsideConversation = it } ) ActionCheckbox( text = translation["condition_only_outside_conversation"], checked = remember { mutableStateOf(params.onlyOutsideConversation) }, onChanged = { params.onlyOutsideConversation = it } ) ActionCheckbox( text = translation["condition_only_when_app_active"], checked = remember { mutableStateOf(params.onlyWhenAppActive) }, onChanged = { params.onlyWhenAppActive = it } ) ActionCheckbox( text = translation["condition_only_when_app_inactive"], checked = remember { mutableStateOf(params.onlyWhenAppInactive) }, onChanged = { params.onlyWhenAppInactive = it } ) ActionCheckbox( text = translation["condition_no_push_notification_when_app_active"], checked = remember { mutableStateOf(params.noPushNotificationWhenAppActive) }, onChanged = { params.noPushNotificationWhenAppActive = it } ) } @Composable fun AddEventDialog( onDismissRequest: () -> Unit, onEventAdd: (TrackerRuleEvent) -> Unit ) { val expanded = remember { mutableStateOf(false) } val currentEventType = remember { mutableStateOf(TrackerEventType.CONVERSATION_ENTER.key) } val addEventActions = remember { mutableStateOf(emptySet()) } val addEventActionParams = remember { TrackerRuleActionParams() } Dialog( onDismissRequest = onDismissRequest, properties = DialogProperties(dismissOnClickOutside = true, usePlatformDefaultWidth = false) ) { RuleCard( modifier = Modifier .padding(horizontal = 10.dp) .widthIn(max = 420.dp), contentPadding = PaddingValues(18.dp) ) { Column( horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.spacedBy(12.dp) ) { Text( translation["add_event_dialog_title"], style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.ExtraBold), color = Color.White, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() ) ExposedDropdownMenuBox( expanded = expanded.value, onExpandedChange = { expanded.value = !expanded.value }, modifier = Modifier.fillMaxWidth() ) { val eventLabel = context.translation["tracker_events.${currentEventType.value}"] Surface( onClick = { expanded.value = true }, modifier = Modifier .fillMaxWidth() .menuAnchor(MenuAnchorType.PrimaryNotEditable), shape = RoundedCornerShape(12.dp), color = Color.White.copy(alpha = 0.08f) ) { Row(modifier = Modifier.padding(12.dp), horizontalArrangement = Arrangement.SpaceBetween) { Text(eventLabel, color = Color.White) ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded.value) } } ExposedDropdownMenu( expanded = expanded.value, onDismissRequest = { expanded.value = false } ) { TrackerEventType.entries.forEach { type -> DropdownMenuItem( text = { Text(context.translation["tracker_events.${type.key}"]) }, onClick = { currentEventType.value = type.key expanded.value = false } ) } } } Text(translation["actions_title"], color = Color.White, fontWeight = FontWeight.Bold) FlowRow( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { TrackerRuleAction.entries.forEach { action -> val selected = action in addEventActions.value FilterChip( selected = selected, onClick = { addEventActions.value = if (selected) addEventActions.value - action else addEventActions.value + action }, label = { Text(context.translation["tracker_actions.${action.key}"]) }, colors = FilterChipDefaults.filterChipColors( selectedContainerColor = TrackerSkinPalette.glowPrimary.copy(alpha = 0.4f), selectedLabelColor = Color.White, labelColor = Color.White.copy(alpha = 0.7f) ) ) } } Text( translation["conditions_title"], style = MaterialTheme.typography.titleMedium, color = Color.White, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() ) ConditionCheckboxes(addEventActionParams) Button( onClick = { onEventAdd( TrackerRuleEvent( id = -1, enabled = true, eventType = currentEventType.value, params = addEventActionParams.copy(), actions = addEventActions.value.toList() ) ) }, modifier = Modifier.align(Alignment.CenterHorizontally), colors = ButtonDefaults.buttonColors( containerColor = TrackerSkinPalette.glowPrimary.copy(alpha = 0.35f), contentColor = Color.White ) ) { Text(translation["add_button"]) } } } } } override val title: @Composable () -> Unit = {} @OptIn(ExperimentalFoundationApi::class) override val content: @Composable (NavBackStackEntry) -> Unit = { navBackStackEntry -> val coroutineScope = rememberCoroutineScope() val currentRuleId = navBackStackEntry.arguments?.getString("rule_id")?.toIntOrNull() val events = rememberAsyncMutableStateList(defaultValue = emptyList()) { currentRuleId?.let { ruleId -> context.database.getTrackerEvents(ruleId) } ?: emptyList() } val eventsToDelete = remember { mutableStateListOf() } var currentScopeType by remember { mutableStateOf(TrackerScopeType.BLACKLIST) } val scopes = rememberAsyncMutableStateList(defaultValue = emptyList()) { currentRuleId?.let { ruleId -> context.database.getRuleTrackerScopes(ruleId).also { map -> currentScopeType = if (map.isEmpty()) TrackerScopeType.WHITELIST else map.values.first() }.map { entry -> entry.key } } ?: emptyList() } val ruleName = rememberAsyncMutableState(defaultValue = "", keys = arrayOf(currentRuleId)) { currentRuleId?.let { ruleId -> context.database.getTrackerRule(ruleId)?.name ?: translation["default_rule_name"] } ?: translation["default_rule_name"] } val authorName = rememberAsyncMutableState(defaultValue = "", keys = arrayOf(currentRuleId)) { currentRuleId?.let { ruleId -> context.database.getTrackerRule(ruleId)?.author ?: "" } ?: "" } fun snapshotEvents() = events.map { event -> event.copy(params = event.params.copy(), actions = event.actions.toList()) } fun buildSnapshot() = RuleSnapshot( name = ruleName.value, author = authorName.value, scopes = scopes.toList(), events = snapshotEvents(), scopeType = currentScopeType ) val initialSnapshot = remember(currentRuleId) { mutableStateOf(if (currentRuleId == null) buildSnapshot() else null) } LaunchedEffect( currentRuleId, ruleName.value, authorName.value, events.size, scopes.size, currentScopeType ) { if (initialSnapshot.value == null) { val hasLoaded = ruleName.value.isNotBlank() || authorName.value.isNotBlank() || events.isNotEmpty() || scopes.isNotEmpty() if (hasLoaded) { initialSnapshot.value = buildSnapshot() } } } val isDirty by remember { derivedStateOf { val snapshot = initialSnapshot.value ?: return@derivedStateOf false snapshot.name != ruleName.value || snapshot.author != authorName.value || snapshot.scopes != scopes.toList() || snapshot.events != snapshotEvents() || snapshot.scopeType != currentScopeType } } var deleteConfirmation by remember { mutableStateOf(false) } var showDuplicateNameDialog by remember { mutableStateOf(false) } var showEventsEmptyDialog by remember { mutableStateOf(false) } var showDiscardDialog by remember { mutableStateOf(false) } var addFriendDialog by remember { mutableStateOf(null) } var addEventDialogVisible by remember { mutableStateOf(false) } if (showDiscardDialog) { AestheticDialog( onDismissRequest = { showDiscardDialog = false }, title = translation["discard_changes_dialog_title"], text = translation["discard_changes_dialog_text"], icon = Icons.Default.Warning, confirmButtonText = translation["discard_button"], onConfirm = { showDiscardDialog = false routes.navController.popBackStack() }, dismissButtonText = translation["button.cancel"], onDismiss = { showDiscardDialog = false }, opaque = true, showCloseButton = false ) } BackHandler(enabled = isDirty) { showDiscardDialog = true } if (showEventsEmptyDialog) { AestheticDialog( onDismissRequest = { showEventsEmptyDialog = false }, title = translation["cannot_save_rule_dialog_title"], text = translation["cannot_save_rule_dialog_text"], icon = Icons.Default.Info, confirmButtonText = translation["button.ok"], onConfirm = { showEventsEmptyDialog = false }, opaque = true, showCloseButton = false ) } if (showDuplicateNameDialog) { AestheticDialog( onDismissRequest = { showDuplicateNameDialog = false }, title = translation["duplicate_rule_name_dialog_title"], text = translation["duplicate_rule_name_dialog_text"], icon = Icons.Default.Warning, confirmButtonText = translation["button.ok"], onConfirm = { showDuplicateNameDialog = false } ) } if (deleteConfirmation) { AestheticDialog( onDismissRequest = { deleteConfirmation = false }, title = translation["delete_rule_dialog_title"], text = translation["delete_rule_dialog_text"], icon = Icons.Default.DeleteOutline, confirmButtonText = translation["delete_button"], onConfirm = { if (currentRuleId != null) context.database.deleteTrackerRule(currentRuleId) routes.navController.popBackStack() }, dismissButtonText = translation["button.cancel"], onDismiss = { deleteConfirmation = false }, opaque = true, showCloseButton = false ) } Scaffold( modifier = Modifier .fillMaxSize() .systemBarsPadding() .navigationBarsPadding(), containerColor = Color.Transparent, topBar = { val topShape = RoundedCornerShape(26.dp) Surface( modifier = Modifier .fillMaxWidth() .padding(horizontal = 14.dp, vertical = 12.dp), shape = topShape, color = TrackerSkinPalette.cardOverlayColor, shadowElevation = 12.dp, border = BorderStroke(1.dp, Color.White.copy(alpha = 0.12f)) ) { Row( modifier = Modifier .fillMaxWidth() .padding(horizontal = 14.dp, vertical = 12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp) ) { IconButton(onClick = { if (isDirty) { showDiscardDialog = true } else { routes.navController.popBackStack() } }) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = translation["back_button_description"], tint = Color.White) } Column(modifier = Modifier.weight(1f)) { Text( translation[if (currentRuleId == null) "new_rule_title" else "edit_rule_title"], color = Color.White, fontWeight = FontWeight.ExtraBold, fontSize = 18.sp ) Text( translation["rule_subtitle"] ?: "", color = TrackerSkinPalette.textSecondary, fontSize = 12.sp ) } if (currentRuleId != null) { IconButton(onClick = { deleteConfirmation = true }) { Icon(Icons.Default.DeleteOutline, contentDescription = translation["delete_button_description"], tint = Color.White) } Spacer(modifier = Modifier.width(6.dp)) } IconButton(onClick = { if (events.isEmpty()) { showEventsEmptyDialog = true return@IconButton } if (currentRuleId == null && context.database.getTrackerRuleByName(ruleName.value.trim()) != null) { showDuplicateNameDialog = true return@IconButton } val ruleId = currentRuleId ?: context.database.newTrackerRule() eventsToDelete.forEach { event -> context.database.deleteTrackerRuleEvent(event.id.takeIf { it > -1 } ?: return@forEach) } events.forEach { event -> context.database.addOrUpdateTrackerRuleEvent( event.id.takeIf { it > -1 }, ruleId, event.eventType, event.params, event.actions ) } context.database.setTrackerRuleName(ruleId, ruleName.value.trim()) context.database.setTrackerRuleAuthor(ruleId, authorName.value.trim()) context.database.setRuleTrackerScopes(ruleId, currentScopeType, scopes) routes.navController.popBackStack() }) { Icon(Icons.Filled.Save, contentDescription = translation["save_button_description"], tint = Color.White) } } } } ) { padding -> Box( modifier = Modifier .fillMaxSize() .background(TrackerSkinPalette.backgroundGradient) .padding(padding) ) { val contentBottomPadding = routes.bottomPadding + 12.dp Column( modifier = Modifier .fillMaxSize() .padding(bottom = contentBottomPadding) .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(16.dp) ) { RuleCard( modifier = Modifier .fillMaxWidth() .padding(horizontal = 12.dp, vertical = 4.dp) ) { val textFieldShape = RoundedCornerShape(16.dp) Text( translation["general_section_title"], style = MaterialTheme.typography.titleMedium, color = Color.White, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() ) TextField( value = ruleName.value, onValueChange = { ruleName.value = it }, label = { Text(translation["rule_name_label"], color = TrackerSkinPalette.textSecondary) }, modifier = Modifier .fillMaxWidth() .padding(horizontal = 2.dp), singleLine = true, shape = textFieldShape, colors = TextFieldDefaults.colors( focusedContainerColor = Color.White.copy(alpha = 0.05f), unfocusedContainerColor = Color.White.copy(alpha = 0.04f), focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, cursorColor = TrackerSkinPalette.glowPrimary, focusedLabelColor = Color.White, unfocusedLabelColor = TrackerSkinPalette.textSecondary, focusedTextColor = Color.White, unfocusedTextColor = Color.White ), textStyle = LocalTextStyle.current.copy(color = Color.White) ) Spacer(modifier = Modifier.height(8.dp)) TextField( value = authorName.value, onValueChange = { authorName.value = it }, label = { Text(translation["author_name_label"], color = TrackerSkinPalette.textSecondary) }, modifier = Modifier .fillMaxWidth() .padding(horizontal = 2.dp), singleLine = true, shape = textFieldShape, colors = TextFieldDefaults.colors( focusedContainerColor = Color.White.copy(alpha = 0.05f), unfocusedContainerColor = Color.White.copy(alpha = 0.04f), focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, cursorColor = TrackerSkinPalette.glowPrimary, focusedLabelColor = Color.White, unfocusedLabelColor = TrackerSkinPalette.textSecondary, focusedTextColor = Color.White, unfocusedTextColor = Color.White ), textStyle = LocalTextStyle.current.copy(color = Color.White) ) } RuleCard( modifier = Modifier .fillMaxWidth() .padding(horizontal = 12.dp, vertical = 4.dp) ) { Text( translation["scope_section_title"], style = MaterialTheme.typography.titleMedium, color = Color.White, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() ) val friendDialogActions = remember { AddFriendDialog.Actions( onFriendState = { friend, state -> if (state) scopes.add(friend.userId) else scopes.remove(friend.userId) }, onGroupState = { group, state -> if (state) scopes.add(group.conversationId) else scopes.remove(group.conversationId) }, getFriendState = { friend -> friend.userId in scopes }, getGroupState = { group -> group.conversationId in scopes } ) } val scopeOptions = listOf( 0 to translation["scope_all"], 1 to translation["scope_whitelist"], 2 to translation["scope_blacklist"] ) val selectedScopeIndex = when { scopes.isEmpty() -> 0 currentScopeType == TrackerScopeType.WHITELIST -> 1 else -> 2 } Row( modifier = Modifier .fillMaxWidth() .padding(top = 10.dp, bottom = 12.dp), horizontalArrangement = Arrangement.spacedBy(10.dp) ) { scopeOptions.forEach { (index, label) -> val selected = selectedScopeIndex == index val optionShape = RoundedCornerShape(16.dp) val glowPrimary = TrackerSkinPalette.glowPrimary val glowSecondary = TrackerSkinPalette.glowSecondary val optionBrush = remember(glowPrimary, glowSecondary) { Brush.linearGradient( listOf( glowPrimary.copy(alpha = if (selected) 0.22f else 0.12f), glowSecondary.copy(alpha = if (selected) 0.18f else 0.1f) ) ) } Surface( modifier = Modifier .weight(1f) .heightIn(min = 46.dp), shape = optionShape, color = Color.Transparent, border = BorderStroke( 1.dp, if (selected) TrackerSkinPalette.glowPrimary.copy(alpha = 0.5f) else Color.White.copy(alpha = 0.12f) ), tonalElevation = 0.dp, shadowElevation = if (selected) 10.dp else 0.dp ) { Box( modifier = Modifier .fillMaxSize() .background(optionBrush, optionShape) .clickable( indication = null, interactionSource = remember { MutableInteractionSource() } ) { when (index) { 0 -> scopes.clear() 1 -> { currentScopeType = TrackerScopeType.WHITELIST if (scopes.isEmpty()) { addFriendDialog = AddFriendDialog(context, friendDialogActions, pinnedIds = scopes) } } 2 -> { currentScopeType = TrackerScopeType.BLACKLIST if (scopes.isEmpty()) { addFriendDialog = AddFriendDialog(context, friendDialogActions, pinnedIds = scopes) } } } }, contentAlignment = Alignment.Center ) { Text( text = label, color = Color.White, fontWeight = FontWeight.SemiBold, textAlign = TextAlign.Center ) } } } } if (scopes.isNotEmpty()) { val selectorShape = RoundedCornerShape(18.dp) val glowPrimary = TrackerSkinPalette.glowPrimary val glowSecondary = TrackerSkinPalette.glowSecondary val selectorBrush = remember(glowPrimary, glowSecondary) { Brush.linearGradient( listOf( glowPrimary.copy(alpha = 0.32f), glowSecondary.copy(alpha = 0.28f) ) ) } val borderBrush = remember(glowPrimary, glowSecondary) { Brush.linearGradient( listOf( glowPrimary.copy(alpha = 0.45f), glowSecondary.copy(alpha = 0.38f) ) ) } Surface( onClick = { addFriendDialog = AddFriendDialog(context, friendDialogActions, pinnedIds = scopes) }, modifier = Modifier .fillMaxWidth() .padding(vertical = 4.dp), shape = selectorShape, color = Color.Transparent, tonalElevation = 0.dp, shadowElevation = 0.dp, border = BorderStroke(1.dp, borderBrush) ) { Row( modifier = Modifier .clip(selectorShape) .background(selectorBrush) .padding(horizontal = 16.dp, vertical = 12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Text( translation.format("select_friends_groups_button", "count" to scopes.size.toString()), color = Color.White, fontWeight = FontWeight.SemiBold ) } } } addFriendDialog?.Content { addFriendDialog = null } } RuleCard( modifier = Modifier .fillMaxWidth() .padding(horizontal = 12.dp, vertical = 4.dp) ) { Column(Modifier.animateContentSize()) { Box( modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center ) { Text( translation["events_section_title"], fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Color.White, textAlign = TextAlign.Center ) IconButton( onClick = { addEventDialogVisible = true }, modifier = Modifier.align(Alignment.CenterEnd) ) { Icon(Icons.Default.Add, contentDescription = translation["add_event_button"], modifier = Modifier.size(28.dp), tint = Color.White) } } if (addEventDialogVisible) { AddEventDialog( onDismissRequest = { addEventDialogVisible = false }, onEventAdd = { event -> events.add(0, event) addEventDialogVisible = false } ) } if (events.isEmpty()) { Text( translation["no_events_text"], fontSize = 12.sp, fontWeight = FontWeight.Light, modifier = Modifier .padding(10.dp) .fillMaxWidth(), textAlign = TextAlign.Center, color = TrackerSkinPalette.textSecondary ) } events.forEach { event -> var expanded by remember { mutableStateOf(false) } Surface( modifier = Modifier .fillMaxWidth() .animateContentSize() .padding(vertical = 4.dp) .clickable { expanded = !expanded }, shape = RoundedCornerShape(18.dp), color = Color.White.copy(alpha = 0.04f), border = BorderStroke(1.dp, Color.White.copy(alpha = 0.1f)) ) { Column(Modifier.padding(8.dp)) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Row( modifier = Modifier.weight(1f, fill = false), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically ) { Icon( if (expanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore, contentDescription = null, tint = Color.White ) Column { Text( context.translation["tracker_events.${event.eventType}"], lineHeight = 20.sp, fontSize = 18.sp, fontWeight = FontWeight.Bold, color = Color.White ) Text( text = event.actions.joinToString(", ") { context.translation["tracker_actions.${it.key}"] }, fontSize = 10.sp, fontWeight = FontWeight.Light, overflow = TextOverflow.Ellipsis, maxLines = 1, lineHeight = 14.sp, color = TrackerSkinPalette.textSecondary ) } } OutlinedIconButton( onClick = { if (event.id > -1) { eventsToDelete.add(event) } events.remove(event) } ) { Icon(Icons.Default.DeleteOutline, contentDescription = translation["delete_button_description"], tint = Color.White) } } if (expanded) { Column(modifier = Modifier.padding(top = 8.dp)) { ConditionCheckboxes(event.params) } } } } } } } } } } } }