diff --git a/app/src/main/java/chat/revolt/composables/screens/chat/ChannelIcon.kt b/app/src/main/java/chat/revolt/composables/screens/chat/ChannelIcon.kt index 59e00931..87dd0907 100644 --- a/app/src/main/java/chat/revolt/composables/screens/chat/ChannelIcon.kt +++ b/app/src/main/java/chat/revolt/composables/screens/chat/ChannelIcon.kt @@ -1,13 +1,21 @@ package chat.revolt.composables.screens.chat +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp import chat.revolt.R import chat.revolt.api.schemas.ChannelType @@ -31,11 +39,20 @@ fun ChannelIcon(channelType: ChannelType, modifier: Modifier = Modifier) { } ChannelType.SavedMessages -> { - Icon( - painter = painterResource(R.drawable.icn_note_stack_24dp), - contentDescription = stringResource(R.string.channel_notes), - modifier = modifier - ) + Box( + modifier = Modifier + .clip(CircleShape) + .size(32.dp) + .background(MaterialTheme.colorScheme.secondaryContainer) + ) { + Icon( + painter = painterResource(R.drawable.icn_note_stack_24dp), + contentDescription = stringResource(R.string.channel_notes), + modifier = modifier + .padding(6.dp) + + ) + } } ChannelType.DirectMessage -> { diff --git a/app/src/main/java/chat/revolt/composables/screens/chat/drawer/ChannelSideDrawer.kt b/app/src/main/java/chat/revolt/composables/screens/chat/drawer/ChannelSideDrawer.kt index 65bd40fc..02b44f11 100644 --- a/app/src/main/java/chat/revolt/composables/screens/chat/drawer/ChannelSideDrawer.kt +++ b/app/src/main/java/chat/revolt/composables/screens/chat/drawer/ChannelSideDrawer.kt @@ -7,6 +7,7 @@ import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable @@ -35,7 +36,10 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -67,13 +71,13 @@ 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.tooling.preview.Preview import androidx.compose.ui.unit.sp import chat.revolt.R import chat.revolt.api.RevoltAPI import chat.revolt.api.internals.CategorisedChannelList import chat.revolt.api.internals.ChannelUtils import chat.revolt.api.internals.DirectMessages -import chat.revolt.api.internals.FriendRequests import chat.revolt.api.schemas.Category import chat.revolt.api.schemas.Channel import chat.revolt.api.schemas.ChannelType @@ -93,13 +97,15 @@ import chat.revolt.composables.screens.chat.discover.DiscoverServersList import chat.revolt.screens.chat.ChatRouterDestination import chat.revolt.sheets.ChannelContextSheet -@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +@OptIn( + ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class, + ExperimentalMaterial3ExpressiveApi::class +) @Composable fun ChannelSideDrawer( currentServer: String?, currentDestination: ChatRouterDestination, onDestinationChanged: (ChatRouterDestination) -> Unit, - onLongPressAvatar: () -> Unit, navigateToServer: (String) -> Unit, onShowServerContextSheet: (String) -> Unit, showSettingsIcon: Boolean, @@ -183,377 +189,473 @@ fun ChannelSideDrawer( } } - Row(modifier.fillMaxSize()) { - LazyColumn( - Modifier.width(64.dp), - verticalArrangement = Arrangement.spacedBy(6.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - stickyHeader(key = "self") { - Column(Modifier.background(MaterialTheme.colorScheme.background)) { - UserAvatar( - username = RevoltAPI.userCache[RevoltAPI.selfId]?.let { - User.resolveDefaultName( - it + + + Box { + Row(modifier.fillMaxSize()) { + LazyColumn( + Modifier.width(64.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + stickyHeader(key = "self") { + Column(Modifier.background(MaterialTheme.colorScheme.background)) { + Box( + Modifier + .padding(8.dp) + .clip(RoundedCornerShape(16.dp)) + .clickable { + onDestinationChanged(ChatRouterDestination.Home) + } + .size(48.dp) + .background(MaterialTheme.colorScheme.primaryContainer), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(R.drawable.ic_direct_messages), + contentDescription = stringResource(R.string.discover_alt), + tint = MaterialTheme.colorScheme.onPrimaryContainer ) } - ?: "", - presence = presenceFromStatus( - RevoltAPI.userCache[RevoltAPI.selfId]?.status?.presence, - RevoltAPI.userCache[RevoltAPI.selfId]?.online ?: false - ), - userId = RevoltAPI.selfId ?: "", - avatar = RevoltAPI.userCache[RevoltAPI.selfId]?.avatar, - size = 48.dp, - presenceSize = 16.dp, - onClick = { - onDestinationChanged(ChatRouterDestination.Home) - }, - onLongClick = onLongPressAvatar, - modifier = Modifier - .padding(8.dp) - .size(48.dp) - ) + } } - } - items( - DirectMessages.unreadDMs().size, - key = { DirectMessages.unreadDMs()[it].id ?: it } - ) { - val dm = DirectMessages.unreadDMs()[it] - when (dm.channelType) { - ChannelType.Group -> GroupIcon( - name = dm.name ?: "?", - size = 48.dp, - onClick = { - dm.id?.let { id -> - onDestinationChanged(ChatRouterDestination.Channel(id)) - } - }, - icon = dm.icon, - modifier = Modifier - .padding(8.dp) - .size(48.dp) - ) - - else -> { - val partner = - if (dm.channelType == ChannelType.DirectMessage) { - RevoltAPI.userCache[ - ChannelUtils.resolveDMPartner( - dm - ) - ] - } else { - null - } - - UserAvatar( - username = partner?.let { p -> - User.resolveDefaultName( - p - ) - } ?: dm.name ?: "?", - presence = presenceFromStatus( - partner?.status?.presence, - partner?.online ?: false - ), - userId = partner?.id ?: dm.id ?: "", - avatar = partner?.avatar ?: dm.icon, + items( + count = DirectMessages.unreadDMs().size, + key = { DirectMessages.unreadDMs()[it].id ?: it } + ) { + val dm = DirectMessages.unreadDMs()[it] + when (dm.channelType) { + ChannelType.Group -> GroupIcon( + name = dm.name ?: "?", size = 48.dp, - presenceSize = 16.dp, onClick = { dm.id?.let { id -> onDestinationChanged(ChatRouterDestination.Channel(id)) } }, + icon = dm.icon, modifier = Modifier .padding(8.dp) .size(48.dp) ) - } - } - } - item(key = "divider") { - HorizontalDivider( - Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp) - ) - } + else -> { + val partner = + if (dm.channelType == ChannelType.DirectMessage) { + RevoltAPI.userCache[ + ChannelUtils.resolveDMPartner( + dm + ) + ] + } else { + null + } - - item(key = "discover") { - Box( - Modifier - .padding(8.dp) - .clip(RoundedCornerShape(16.dp)) - .clickable { - onDestinationChanged( - ChatRouterDestination.Discover - ) - } - .size(48.dp) - .background(MaterialTheme.colorScheme.onPrimary), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(R.drawable.icn_explore_24dp), - contentDescription = stringResource(R.string.discover_alt) - ) - } - } - - item(key = "add_server") { - Box( - Modifier - .padding(8.dp) - .clip(CircleShape) - .clickable { - onShowAddServerSheet() - } - .size(48.dp) - .background(MaterialTheme.colorScheme.onPrimary), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(R.drawable.icn_add_24dp), - contentDescription = stringResource(R.string.server_plus_alt) - ) - } - } - - items( - serverList.size, - key = { serverList[it].id ?: it } - ) { - val serverInList = serverList[it] - val serverHasUnread = - serverInList.id?.let { srvId -> RevoltAPI.unreads.serverHasUnread(srvId) } - ?: false - val leftIndicatorHeight = animateDpAsState( - targetValue = if (serverInList.id == currentServer) 32.dp - else if (serverHasUnread) 8.dp - else 0.dp, - animationSpec = spring( - dampingRatio = Spring.DampingRatioMediumBouncy, - stiffness = Spring.StiffnessLow - ), label = "Left indicator width" - ) - val leftIndicatorColour = animateColorAsState( - targetValue = - if (serverInList.id == currentServer) - MaterialTheme.colorScheme.primary - else if (serverHasUnread) - MaterialTheme.colorScheme.onSurfaceVariant - else - Color.Transparent, - animationSpec = spring( - dampingRatio = Spring.DampingRatioMediumBouncy, - stiffness = Spring.StiffnessLow - ), - label = "Left indicator colour" - ) - - Box(Modifier.fillMaxWidth()) { - Box( - Modifier - .padding(8.dp) - .clip(CircleShape) - .clickable { - serverInList.id?.let { srvId -> navigateToServer(srvId) } - }) { - val icon = serverInList.icon?.id?.let { iconId -> - "${RevoltAPI.getCurrentFilesUrl()}/icons/$iconId" - } - if (icon != null) { - RemoteImage( - url = icon, - allowAnimation = false, + UserAvatar( + username = partner?.let { p -> + User.resolveDefaultName( + p + ) + } ?: dm.name ?: "?", + presence = presenceFromStatus( + partner?.status?.presence, + partner?.online ?: false + ), + userId = partner?.id ?: dm.id ?: "", + avatar = partner?.avatar ?: dm.icon, + size = 48.dp, + presenceSize = 16.dp, + onClick = { + dm.id?.let { id -> + onDestinationChanged(ChatRouterDestination.Channel(id)) + } + }, modifier = Modifier + .padding(8.dp) .size(48.dp) - .clip(CircleShape), - description = serverInList.name ?: stringResource(R.string.unknown) - ) - } else { - IconPlaceholder( - name = serverInList.name ?: stringResource(R.string.unknown), - modifier = Modifier - .size(48.dp) - .clip(CircleShape) ) } } + } - Box( + item(key = "divider") { + HorizontalDivider( Modifier - .height(leftIndicatorHeight.value) - .width(8.dp) - .offset(x = (-4).dp) - .clip(CircleShape) - .background(leftIndicatorColour.value) - .align(Alignment.CenterStart) + .fillMaxWidth() + .padding(horizontal = 8.dp) ) } - } - if (showSettingsIcon) { - item(key = "settings") { + + item(key = "discover") { Box( Modifier .padding(8.dp) - .clip(CircleShape) + .clip(RoundedCornerShape(16.dp)) .clickable { - onOpenSettings() + onDestinationChanged( + ChatRouterDestination.Discover + ) } - .size(48.dp), + .size(48.dp) + .background(MaterialTheme.colorScheme.secondaryContainer), contentAlignment = Alignment.Center ) { Icon( - painter = painterResource(R.drawable.icn_settings_24dp), - contentDescription = stringResource(R.string.settings) + painter = painterResource(R.drawable.icn_explore_24dp), + contentDescription = stringResource(R.string.discover_alt) + ) + } + } + + item(key = "add_server") { + Box( + Modifier + .padding(8.dp) + .clip(CircleShape) + .clickable { + onShowAddServerSheet() + } + .size(48.dp) + .background(MaterialTheme.colorScheme.secondaryContainer), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(R.drawable.icn_add_24dp), + contentDescription = stringResource(R.string.server_plus_alt) + ) + } + } + + items( + serverList.size, + key = { serverList[it].id ?: it } + ) { + val serverInList = serverList[it] + val serverHasUnread = + serverInList.id?.let { srvId -> RevoltAPI.unreads.serverHasUnread(srvId) } + ?: false + val leftIndicatorHeight = animateDpAsState( + targetValue = if (serverInList.id == currentServer) 32.dp + else if (serverHasUnread) 8.dp + else 0.dp, + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow + ), label = "Left indicator width" + ) + val leftIndicatorColour = animateColorAsState( + targetValue = + if (serverInList.id == currentServer) + MaterialTheme.colorScheme.primary + else if (serverHasUnread) + MaterialTheme.colorScheme.onSurfaceVariant + else + Color.Transparent, + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow + ), + label = "Left indicator colour" + ) + + Box(Modifier.fillMaxWidth()) { + Box( + Modifier + .padding(8.dp) + .clip(CircleShape) + .clickable { + serverInList.id?.let { srvId -> navigateToServer(srvId) } + }) { + val icon = serverInList.icon?.id?.let { iconId -> + "${RevoltAPI.getCurrentFilesUrl()}/icons/$iconId" + } + if (icon != null) { + RemoteImage( + url = icon, + allowAnimation = false, + modifier = Modifier + .size(48.dp) + .clip(CircleShape), + description = serverInList.name + ?: stringResource(R.string.unknown) + ) + } else { + IconPlaceholder( + name = serverInList.name ?: stringResource(R.string.unknown), + modifier = Modifier + .size(48.dp) + .clip(CircleShape) + ) + } + } + + Box( + Modifier + .height(leftIndicatorHeight.value) + .width(8.dp) + .offset(x = (-4).dp) + .clip(CircleShape) + .background(leftIndicatorColour.value) + .align(Alignment.CenterStart) + ) + } + } + + if (showSettingsIcon) { + item(key = "settings") { + Box( + Modifier + .padding(8.dp) + .clip(CircleShape) + .clickable { + onOpenSettings() + } + .size(48.dp), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(R.drawable.icn_settings_24dp), + contentDescription = stringResource(R.string.settings) + ) + } + } + } + } + Column( + Modifier + .clip(shape = RoundedCornerShape(topStart = 24.dp)) + .background(color = MaterialTheme.colorScheme.surfaceContainer) + .weight(weight = 1f) + .fillMaxHeight() + ) { + Box( + Modifier + .height(serverBannerHeight) + ) { + if (server?.banner != null) { + RemoteImage( + url = "${RevoltAPI.getCurrentFilesUrl()}/banners/${server.banner.id}", + description = null, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxSize() + ) + + with(MaterialTheme.colorScheme) { + Box( + Modifier + .fillMaxSize() + .drawBehind { + drawRect( + Brush.linearGradient( + listOf( + Color.Black.copy(alpha = 0.6f), + Color.Transparent + ), + Offset.Zero, + Offset.Infinite.copy(x = 0f) + ), + ) + }) + } + } + + Row( + Modifier + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CompositionLocalProvider( + LocalContentColor provides + if (server?.banner != null) Color.White + else LocalContentColor.current + ) { + Row( + Modifier.weight(1f), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + if (server?.flags has ServerFlags.Official) { + Icon( + painter = painterResource( + id = R.drawable.ic_revolt_decagram_24dp + ), + contentDescription = stringResource( + R.string.server_flag_official + ), + tint = LocalContentColor.current, + modifier = Modifier + .size(24.dp) + ) + } + if (server?.flags has ServerFlags.Verified) { + Icon( + painter = painterResource( + id = R.drawable.ic_check_decagram_24dp + ), + contentDescription = stringResource( + R.string.server_flag_verified + ), + tint = LocalContentColor.current, + modifier = Modifier + .size(24.dp) + ) + } + + Text( + text = when (currentServer) { + null -> stringResource(if (currentDestination is ChatRouterDestination.Discover) R.string.discover_servers else R.string.direct_messages) + else -> server?.name ?: stringResource(R.string.unknown) + }, + style = MaterialTheme.typography.titleMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + + if (currentServer != null) { + IconButton(onClick = { + server?.id?.let { srvId -> onShowServerContextSheet(srvId) } + }) { + Icon( + painter = painterResource(R.drawable.icn_more_vert_24dp), + contentDescription = stringResource(R.string.menu), + tint = LocalContentColor.current + ) + } + } else { + Spacer(Modifier.height(64.dp)) + } + } + } + } + if (currentDestination is ChatRouterDestination.Discover) { + DiscoverServersList( + onJoinToServerSuccess = navigateToServer + ) + } else { + if (currentServer == null) { + DirectMessagesChannelListRenderer( + currentDestination, + onDestinationChanged, + channelListState, + onOpenChannelContextSheet = { channelContextSheetTarget = it } + ) + } else { + ServerChannelListRenderer( + categorisedChannels, + currentDestination, + onDestinationChanged, + channelListState, + onOpenChannelContextSheet = { channelContextSheetTarget = it }, + serverId = currentServer ) } } } } - Column( - Modifier - .clip(shape = RoundedCornerShape(topStart = 24.dp)) - .background(color = MaterialTheme.colorScheme.surfaceContainer) - .weight(weight = 1f) - .fillMaxHeight() - ) { - Box( - Modifier - .height(serverBannerHeight) + if (currentDestination is ChatRouterDestination.Home) { + FloatingActionButton( + modifier = Modifier + .padding(16.dp) + .align(Alignment.BottomEnd), + onClick = { + onDestinationChanged(ChatRouterDestination.Friends) + }, + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + shape = CircleShape ) { - if (server?.banner != null) { - RemoteImage( - url = "${RevoltAPI.getCurrentFilesUrl()}/banners/${server.banner.id}", - description = null, - contentScale = ContentScale.Crop, - modifier = Modifier - .fillMaxSize() - ) - - with(MaterialTheme.colorScheme) { - Box( - Modifier - .fillMaxSize() - .drawBehind { - drawRect( - Brush.linearGradient( - listOf( - Color.Black.copy(alpha = 0.6f), - Color.Transparent - ), - Offset.Zero, - Offset.Infinite.copy(x = 0f) - ), - ) - }) - } - } - - Row( - Modifier - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - CompositionLocalProvider( - LocalContentColor provides - if (server?.banner != null) Color.White - else LocalContentColor.current - ) { - Row( - Modifier.weight(1f), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - if (server?.flags has ServerFlags.Official) { - Icon( - painter = painterResource( - id = R.drawable.ic_revolt_decagram_24dp - ), - contentDescription = stringResource( - R.string.server_flag_official - ), - tint = LocalContentColor.current, - modifier = Modifier - .size(24.dp) - ) - } - if (server?.flags has ServerFlags.Verified) { - Icon( - painter = painterResource( - id = R.drawable.ic_check_decagram_24dp - ), - contentDescription = stringResource( - R.string.server_flag_verified - ), - tint = LocalContentColor.current, - modifier = Modifier - .size(24.dp) - ) - } - - Text( - text = when (currentServer) { - null -> stringResource(if (currentDestination is ChatRouterDestination.Discover) R.string.discover_servers else R.string.direct_messages) - else -> server?.name ?: stringResource(R.string.unknown) - }, - style = MaterialTheme.typography.titleMedium, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - - if (currentServer != null) { - IconButton(onClick = { - server?.id?.let { srvId -> onShowServerContextSheet(srvId) } - }) { - Icon( - painter = painterResource(R.drawable.icn_more_vert_24dp), - contentDescription = stringResource(R.string.menu), - tint = LocalContentColor.current - ) - } - } else { - Spacer(Modifier.height(64.dp)) - } - } - } - } - if (currentDestination is ChatRouterDestination.Discover) { - DiscoverServersList( - onJoinToServerSuccess = navigateToServer + Icon( + painter = painterResource(R.drawable.ic_new_message), + contentDescription = stringResource(R.string.message_friends), + tint = MaterialTheme.colorScheme.onPrimaryContainer ) - } else { - if (currentServer == null) { - DirectMessagesChannelListRenderer( - currentDestination, - onDestinationChanged, - channelListState, - onOpenChannelContextSheet = { channelContextSheetTarget = it } - ) - } else { - ServerChannelListRenderer( - categorisedChannels, - currentDestination, - onDestinationChanged, - channelListState, - onOpenChannelContextSheet = { channelContextSheetTarget = it }, - serverId = currentServer - ) - } } } } } +@Preview +@Composable +private fun DirectMessagesChannelListRendererPreview() { + val mockChannels = listOf( + Channel(id = "1", name = "Saved Messages", channelType = ChannelType.SavedMessages), + Channel( + id = "2", + name = "User One", + channelType = ChannelType.DirectMessage, + lastMessageID = "msg1" + ), + Channel( + id = "3", + name = "Group Chat Alpha", + channelType = ChannelType.Group, + icon = null, + lastMessageID = "msg2" + ), + Channel( + id = "4", + name = "User Two", + channelType = ChannelType.DirectMessage, + active = false + ), // inactive DM + Channel( + id = "5", + name = "Another Group", + channelType = ChannelType.Group, + icon = null, + lastMessageID = "msg3" + ) + ) + val mockUsers = mapOf( + "user1_id" to User(id = "user1_id", username = "User One", avatar = null, online = true), + "user2_id" to User(id = "user2_id", username = "User Two", avatar = null, online = false) + ) + + // Simulate RevoltAPI cache for preview + RevoltAPI.channelCache.clear() + mockChannels.forEach { RevoltAPI.channelCache[it.id!!] = it } + RevoltAPI.userCache.clear() + mockUsers.forEach { (id, user) -> RevoltAPI.userCache[id] = user } + + + //ColumnScope is required for DirectMessagesChannelListRenderer, let's provide a dummy one + Column(Modifier.background(MaterialTheme.colorScheme.surface)) { + DirectMessagesChannelListRenderer( + currentDestination = ChatRouterDestination.Home, + onDestinationChanged = {}, + channelListState = rememberLazyListState(), + onOpenChannelContextSheet = {} + ) + } +} + +@Preview +@Composable +private fun DirectMessagesChannelListRendererEmptyPreview() { + // Simulate RevoltAPI cache for preview - empty state + RevoltAPI.channelCache.clear() + RevoltAPI.channelCache["saved_notes_channel_id"] = Channel( + id = "saved_notes_channel_id", + name = "Saved Messages", + channelType = ChannelType.SavedMessages + ) + RevoltAPI.userCache.clear() + + //ColumnScope is required for DirectMessagesChannelListRenderer, let's provide a dummy one + Column(Modifier.background(MaterialTheme.colorScheme.surface)) { + DirectMessagesChannelListRenderer( + currentDestination = ChatRouterDestination.Home, + onDestinationChanged = {}, + channelListState = rememberLazyListState(), + onOpenChannelContextSheet = {} + ) + } +} + @Composable private fun ColumnScope.DirectMessagesChannelListRenderer( currentDestination: ChatRouterDestination, @@ -574,114 +676,125 @@ private fun ColumnScope.DirectMessagesChannelListRenderer( .fillMaxSize() .weight(1f) ) { - item(key = "overview") { - ChannelItem( - channel = Channel( - id = "overview", - name = stringResource(R.string.overview_screen_title), - channelType = ChannelType.TextChannel - ), - iconType = ChannelItemIconType.Painter(painterResource(R.drawable.icn_star_shine_24dp)), - isCurrent = currentDestination is ChatRouterDestination.Settings, - onDestinationChanged = { - onDestinationChanged(ChatRouterDestination.Settings) - }, - hasUnread = false, - onOpenChannelContextSheet = {} - ) - Spacer(Modifier.height(4.dp)) - } - - item(key = "friends") { - ChannelItem( - channel = Channel( - id = "friends", - name = stringResource(R.string.friends), - channelType = ChannelType.TextChannel - ), - iconType = ChannelItemIconType.Painter(painterResource(R.drawable.icn_group_24dp)), - isCurrent = currentDestination is ChatRouterDestination.Friends, - onDestinationChanged = { - onDestinationChanged(ChatRouterDestination.Friends) - }, - hasUnread = FriendRequests.getIncoming().isNotEmpty(), - onOpenChannelContextSheet = {}, - ) - Spacer(Modifier.height(4.dp)) - } - item(key = "saved_messages") { - val notesChannel = - RevoltAPI.channelCache.values.firstOrNull { it.channelType == ChannelType.SavedMessages } + val notesChannel = RevoltAPI.channelCache.values.firstOrNull { + it.channelType == ChannelType.SavedMessages + } if (notesChannel != null) { - ChannelItem( - channel = Channel( - id = notesChannel.id, - name = stringResource(R.string.channel_notes), - channelType = ChannelType.SavedMessages - ), - isCurrent = currentDestination is ChatRouterDestination.Channel && - currentDestination.channelId == notesChannel.id, - onDestinationChanged = { - onDestinationChanged(it) - }, - hasUnread = false, - onOpenChannelContextSheet = {}, + HorizontalDivider( + Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp) ) - Spacer(Modifier.height(4.dp)) + Column(Modifier.fillMaxWidth()) { + ChannelItem( + channel = Channel( + id = notesChannel.id, + name = stringResource(R.string.channel_notes), + channelType = ChannelType.SavedMessages + ), + isCurrent = currentDestination is ChatRouterDestination.Channel && + currentDestination.channelId == notesChannel.id, + onDestinationChanged = { + onDestinationChanged(it) + }, + hasUnread = false, + onOpenChannelContextSheet = {}, + ) + HorizontalDivider( + Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp) + ) + Spacer(Modifier.height(4.dp)) + } } } - item("divider") { - HorizontalDivider( - Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp, vertical = 4.dp) - ) - Spacer(Modifier.height(4.dp)) - } - - items( - dmAbleChannels.size, - key = { dmAbleChannels[it].id ?: it } - ) { - val channel = dmAbleChannels.getOrNull(it) ?: return@items - - val partner = - if (channel.channelType == ChannelType.DirectMessage) { - RevoltAPI.userCache[ - ChannelUtils.resolveDMPartner( - channel + if (dmAbleChannels.isEmpty()) { + item(key = "empty-list-state") { + Column( + Modifier.padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Image( + modifier = Modifier.size(100.dp), + painter = painterResource(R.drawable.empty_direct_messages_img), + contentDescription = stringResource(R.string.empty_messages_content_description) + ) + Spacer(Modifier.height(4.dp)) + Text( + text = stringResource(R.string.empty_messages_heading), + style = MaterialTheme.typography.labelLarge, + textAlign = TextAlign.Center, + fontSize = 24.sp + ) + Spacer(Modifier.height(4.dp)) + Text( + text = stringResource(R.string.empty_messages_body), + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center + ) + Spacer(Modifier.height(16.dp)) + Button( + onClick = { + onDestinationChanged(ChatRouterDestination.Friends) + }, + shape = MaterialTheme.shapes.small.copy( + topStart = CornerSize(8.dp), + topEnd = CornerSize(8.dp), + bottomStart = CornerSize(8.dp), + bottomEnd = CornerSize(8.dp) ) - ] - } else { - null + ) { + Text(text = stringResource(R.string.new_conversation)) + } } + } + } else { + items( + dmAbleChannels.size, + key = { dmAbleChannels[it].id ?: it } + ) { + val channel = dmAbleChannels.getOrNull(it) ?: return@items - DMOrGroupItem( - channel = channel, - partner = partner, - isCurrent = when (currentDestination) { - is ChatRouterDestination.Channel -> { - currentDestination.channelId == channel.id + val partner = + if (channel.channelType == ChannelType.DirectMessage) { + RevoltAPI.userCache[ + ChannelUtils.resolveDMPartner( + channel + ) + ] + } else { + null } - else -> false - }, - hasUnread = channel.lastMessageID?.let { lastMessageID -> - RevoltAPI.unreads.hasUnread( - channel.id!!, - lastMessageID, - serverId = null - ) - } ?: false, - isMuted = NotificationSettingsProvider.isChannelMuted(channel.id!!, null), - onDestinationChanged = { dest -> - onDestinationChanged(dest) - }, - onOpenChannelContextSheet = onOpenChannelContextSheet - ) + DMOrGroupItem( + channel = channel, + partner = partner, + isCurrent = when (currentDestination) { + is ChatRouterDestination.Channel -> { + currentDestination.channelId == channel.id + } + + else -> false + }, + hasUnread = channel.lastMessageID?.let { lastMessageID -> + RevoltAPI.unreads.hasUnread( + channel.id!!, + lastMessageID, + serverId = null + ) + } ?: false, + isMuted = NotificationSettingsProvider.isChannelMuted(channel.id!!, null), + onDestinationChanged = { dest -> + onDestinationChanged(dest) + }, + onOpenChannelContextSheet = onOpenChannelContextSheet + ) + } } item(key = "last") { diff --git a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt index e01f0eb2..ef660e5e 100644 --- a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt @@ -17,12 +17,9 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Face -import androidx.compose.material.icons.filled.Home -import androidx.compose.material.icons.filled.Person import androidx.compose.material3.AlertDialog import androidx.compose.material3.BottomAppBar import androidx.compose.material3.DrawerValue @@ -56,6 +53,7 @@ import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp import androidx.core.app.NotificationManagerCompat import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.Lifecycle @@ -851,8 +849,9 @@ fun ChannelNavigator( NavigationBarItem( icon = { Icon( - imageVector = Icons.Default.Home, + painter = painterResource(R.drawable.ic_bottom_navbar_home), contentDescription = "Home", + modifier = Modifier.size(32.dp) ) }, label = { @@ -873,8 +872,9 @@ fun ChannelNavigator( NavigationBarItem( icon = { Icon( - imageVector = Icons.Default.Person, + painter = painterResource(R.drawable.ic_bottom_navbar_friends), contentDescription = "Friends", + modifier = Modifier.size(32.dp) ) }, label = { @@ -889,8 +889,9 @@ fun ChannelNavigator( NavigationBarItem( icon = { Icon( - imageVector = Icons.Default.Face, + painter = painterResource(R.drawable.ic_bottom_navbar_you), contentDescription = "You", + modifier = Modifier.size(32.dp) ) }, label = { @@ -932,7 +933,6 @@ fun ChannelNavigator( else -> null }, navigateToServer = viewModel::navigateToServer, - onLongPressAvatar = onShowStatusSheet, onShowServerContextSheet = onShowServerContextSheet, showSettingsIcon = isTouchExplorationEnabled, onOpenSettings = { diff --git a/app/src/main/res/drawable/empty_direct_messages_img.xml b/app/src/main/res/drawable/empty_direct_messages_img.xml new file mode 100644 index 00000000..11971bf6 --- /dev/null +++ b/app/src/main/res/drawable/empty_direct_messages_img.xml @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_bottom_navbar_friends.xml b/app/src/main/res/drawable/ic_bottom_navbar_friends.xml new file mode 100644 index 00000000..c0be1b7d --- /dev/null +++ b/app/src/main/res/drawable/ic_bottom_navbar_friends.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_bottom_navbar_home.xml b/app/src/main/res/drawable/ic_bottom_navbar_home.xml new file mode 100644 index 00000000..a2bf4b8e --- /dev/null +++ b/app/src/main/res/drawable/ic_bottom_navbar_home.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_bottom_navbar_you.xml b/app/src/main/res/drawable/ic_bottom_navbar_you.xml new file mode 100644 index 00000000..0fde3ffb --- /dev/null +++ b/app/src/main/res/drawable/ic_bottom_navbar_you.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_direct_messages.xml b/app/src/main/res/drawable/ic_direct_messages.xml new file mode 100644 index 00000000..5efb937d --- /dev/null +++ b/app/src/main/res/drawable/ic_direct_messages.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_new_message.xml b/app/src/main/res/drawable/ic_new_message.xml new file mode 100644 index 00000000..24b2a720 --- /dev/null +++ b/app/src/main/res/drawable/ic_new_message.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/icn_message_24dp.xml b/app/src/main/res/drawable/icn_message_24dp.xml new file mode 100644 index 00000000..3329386f --- /dev/null +++ b/app/src/main/res/drawable/icn_message_24dp.xml @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 51939841..cee96118 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -16,6 +16,11 @@ Terms of Service Privacy Policy Community Guidelines + Message Friends + Empty Messages Sad Image + It\'s Quiet Here... + Find friends to chat with or create a group conversation. + New Conversation Welcome to PepChat! Join endless chats and connections; PepChat is your place to vibe with friends! @@ -233,7 +238,7 @@ Text Channel Voice Channel Group - Notes + Saved Notes This is the start of your conversation There may be some messages missing here.