diff --git a/app/src/main/java/chat/stoat/composables/screens/chat/drawer/ChannelSideDrawer.kt b/app/src/main/java/chat/stoat/composables/screens/chat/drawer/ChannelSideDrawer.kt index dbaf6e57..9fc0b517 100644 --- a/app/src/main/java/chat/stoat/composables/screens/chat/drawer/ChannelSideDrawer.kt +++ b/app/src/main/java/chat/stoat/composables/screens/chat/drawer/ChannelSideDrawer.kt @@ -37,6 +37,9 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CornerSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.material3.DrawerState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider @@ -52,6 +55,7 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -762,6 +766,29 @@ fun ColumnScope.ServerChannelListRenderer( serverId: String ) { val scope = rememberCoroutineScope() + val collapsedCategories = remember { mutableStateMapOf() } + + val visibleItems by remember(categorisedChannels) { + derivedStateOf { + if (categorisedChannels.isNullOrEmpty()) return@derivedStateOf emptyList() + val result = mutableListOf() + var currentCategoryId: String? = null + for (item in categorisedChannels) { + when (item) { + is CategorisedChannelList.Category -> { + currentCategoryId = item.category.id + result.add(item) + } + is CategorisedChannelList.Channel -> { + if (currentCategoryId == null || collapsedCategories[currentCategoryId] != true) { + result.add(item) + } + } + } + } + result + } + } LazyColumn( state = channelListState, @@ -794,8 +821,8 @@ fun ColumnScope.ServerChannelListRenderer( } } - items(categorisedChannels?.size ?: 0) { - when (val channelOrCat = categorisedChannels?.get(it)) { + items(visibleItems.size) { + when (val channelOrCat = visibleItems[it]) { is CategorisedChannelList.Channel -> { ChannelItem( channel = channelOrCat.channel, @@ -828,7 +855,16 @@ fun ColumnScope.ServerChannelListRenderer( } is CategorisedChannelList.Category -> { - CategoryItem(category = channelOrCat.category) + val categoryId = channelOrCat.category.id + CategoryItem( + category = channelOrCat.category, + isCollapsed = categoryId != null && collapsedCategories[categoryId] == true, + onToggleCollapse = { + if (categoryId != null) { + collapsedCategories[categoryId] = collapsedCategories[categoryId] != true + } + } + ) } else -> {} @@ -958,16 +994,30 @@ fun ChannelItem( @Composable fun CategoryItem( - category: Category + category: Category, + isCollapsed: Boolean = false, + onToggleCollapse: () -> Unit = {} ) { - Text( - text = category.title ?: stringResource(R.string.unknown), - style = MaterialTheme.typography.labelLarge, - fontSize = 16.sp, - modifier = Modifier.padding( - start = 24.dp, end = 24.dp, top = 24.dp, bottom = 16.dp + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .clickable(onClick = onToggleCollapse) + .padding(start = 24.dp, end = 16.dp, top = 24.dp, bottom = 16.dp) + .fillMaxWidth() + ) { + Text( + text = category.title ?: stringResource(R.string.unknown), + style = MaterialTheme.typography.labelLarge, + fontSize = 16.sp, + modifier = Modifier.weight(1f) ) - ) + Icon( + imageVector = if (isCollapsed) Icons.Filled.KeyboardArrowDown else Icons.Filled.KeyboardArrowUp, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(20.dp) + ) + } } @OptIn(ExperimentalFoundationApi::class)