feat: initial direct messages and better group chats
Signed-off-by: Infi <wingit@geist.ga>
This commit is contained in:
parent
557a8957f2
commit
1db1ffcb95
|
|
@ -101,6 +101,7 @@ android {
|
||||||
}
|
}
|
||||||
lintOptions {
|
lintOptions {
|
||||||
abortOnError false
|
abortOnError false
|
||||||
|
disable 'MissingTranslation'
|
||||||
}
|
}
|
||||||
namespace 'chat.revolt'
|
namespace 'chat.revolt'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package chat.revolt.api.internals
|
||||||
|
|
||||||
|
import chat.revolt.api.RevoltAPI
|
||||||
|
import chat.revolt.api.schemas.Channel
|
||||||
|
|
||||||
|
object ChannelUtils {
|
||||||
|
fun resolveDMName(channel: Channel): String? {
|
||||||
|
return channel.name
|
||||||
|
?: RevoltAPI.userCache[channel.recipients?.first { u -> u != RevoltAPI.selfId }]?.username
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resolveDMPartner(channel: Channel): String? {
|
||||||
|
return channel.recipients?.first { u -> u != RevoltAPI.selfId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,17 @@
|
||||||
package chat.revolt.components.chat
|
package chat.revolt.components.chat
|
||||||
|
|
||||||
import androidx.compose.animation.*
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
|
@ -12,7 +19,13 @@ import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Send
|
import androidx.compose.material.icons.filled.Send
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -107,7 +120,7 @@ fun MessageField(
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Add,
|
Icons.Default.Add,
|
||||||
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f),
|
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f),
|
||||||
contentDescription = stringResource(id = R.string.unknown),
|
contentDescription = stringResource(id = R.string.add_attachment_alt),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.size(32.dp)
|
.size(32.dp)
|
||||||
|
|
@ -132,7 +145,7 @@ fun MessageField(
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Send,
|
Icons.Default.Send,
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
contentDescription = stringResource(id = R.string.unknown),
|
contentDescription = stringResource(id = R.string.send_alt),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.size(32.dp)
|
.size(32.dp)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -123,6 +124,65 @@ fun UserAvatar(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@Composable
|
||||||
|
fun GroupIcon(
|
||||||
|
name: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
icon: AutumnResource? = null,
|
||||||
|
rawUrl: String? = null,
|
||||||
|
size: Dp = 40.dp,
|
||||||
|
onLongClick: (() -> Unit)? = null,
|
||||||
|
onClick: (() -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.size(size),
|
||||||
|
contentAlignment = Alignment.BottomEnd
|
||||||
|
) {
|
||||||
|
if (icon != null) {
|
||||||
|
RemoteImage(
|
||||||
|
url = rawUrl ?: "$REVOLT_FILES/icons/${icon.id!!}/group.png",
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
description = stringResource(id = R.string.avatar_alt, name),
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(MaterialTheme.shapes.small)
|
||||||
|
.size(size)
|
||||||
|
.then(
|
||||||
|
if (onLongClick != null || onClick != null) Modifier
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = { onClick?.invoke() },
|
||||||
|
onLongClick = { onLongClick?.invoke() }
|
||||||
|
)
|
||||||
|
else Modifier
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(size)
|
||||||
|
.then(
|
||||||
|
if (onLongClick != null || onClick != null) Modifier
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = { onClick?.invoke() },
|
||||||
|
onLongClick = { onLongClick?.invoke() }
|
||||||
|
)
|
||||||
|
else Modifier
|
||||||
|
)
|
||||||
|
.clip(MaterialTheme.shapes.small)
|
||||||
|
.background(MaterialTheme.colorScheme.primary)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = name.first().toString(),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
modifier = Modifier.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UserAvatarWidthPlaceholder(
|
fun UserAvatarWidthPlaceholder(
|
||||||
size: Dp = 40.dp,
|
size: Dp = 40.dp,
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,9 @@ import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.revolt.R
|
import chat.revolt.R
|
||||||
|
import chat.revolt.api.internals.ChannelUtils
|
||||||
import chat.revolt.api.schemas.Channel
|
import chat.revolt.api.schemas.Channel
|
||||||
|
import chat.revolt.api.schemas.ChannelType
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChannelHeader(
|
fun ChannelHeader(
|
||||||
|
|
@ -64,7 +66,13 @@ fun ChannelHeader(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = channel.name ?: "Ch #${channel.id}",
|
text = channel.name
|
||||||
|
?: ChannelUtils.resolveDMName(channel)
|
||||||
|
?: if (channel.channelType == ChannelType.SavedMessages) {
|
||||||
|
stringResource(R.string.channel_notes)
|
||||||
|
} else {
|
||||||
|
stringResource(R.string.unknown)
|
||||||
|
},
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ package chat.revolt.components.screens.chat.drawer.channel
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material3.DrawerState
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
|
|
@ -19,7 +19,6 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -30,21 +29,21 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import chat.revolt.R
|
import chat.revolt.R
|
||||||
import chat.revolt.api.RevoltAPI
|
import chat.revolt.api.RevoltAPI
|
||||||
|
import chat.revolt.api.internals.ChannelUtils
|
||||||
import chat.revolt.api.schemas.ChannelType
|
import chat.revolt.api.schemas.ChannelType
|
||||||
|
import chat.revolt.components.generic.presenceFromStatus
|
||||||
import chat.revolt.components.screens.chat.drawer.server.DrawerChannel
|
import chat.revolt.components.screens.chat.drawer.server.DrawerChannel
|
||||||
import chat.revolt.sheets.ChannelContextSheet
|
import chat.revolt.sheets.ChannelContextSheet
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun RowScope.ChannelList(
|
fun RowScope.ChannelList(
|
||||||
serverId: String,
|
serverId: String,
|
||||||
drawerState: DrawerState,
|
currentDestination: String?,
|
||||||
currentChannel: String?,
|
currentChannel: String?,
|
||||||
onChannelClick: (String) -> Unit,
|
onChannelClick: (String) -> Unit,
|
||||||
|
onSpecialClick: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
var channelContextSheetShown by remember { mutableStateOf(false) }
|
var channelContextSheetShown by remember { mutableStateOf(false) }
|
||||||
var channelContextSheetTarget by remember { mutableStateOf("") }
|
var channelContextSheetTarget by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
|
@ -67,6 +66,13 @@ fun RowScope.ChannelList(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val dmAbleChannels =
|
||||||
|
RevoltAPI.channelCache.values
|
||||||
|
.filter { it.channelType == ChannelType.DirectMessage || it.channelType == ChannelType.Group }
|
||||||
|
.filter { if (it.channelType == ChannelType.DirectMessage) it.active == true else true }
|
||||||
|
.sortedBy { it.lastMessageID ?: it.id }
|
||||||
|
.reversed()
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
tonalElevation = 1.dp,
|
tonalElevation = 1.dp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -74,82 +80,153 @@ fun RowScope.ChannelList(
|
||||||
.clip(RoundedCornerShape(16.dp))
|
.clip(RoundedCornerShape(16.dp))
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
Column(
|
LazyColumn(
|
||||||
Modifier
|
Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
|
.fillMaxSize(),
|
||||||
) {
|
) {
|
||||||
if (serverId == "home") {
|
if (serverId == "home") {
|
||||||
Column(
|
item(
|
||||||
Modifier
|
key = "header"
|
||||||
.weight(1f)
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
) {
|
) {
|
||||||
RevoltAPI.channelCache.values.filter { it.channelType == ChannelType.Group }
|
Text(
|
||||||
.forEach { channel ->
|
text = stringResource(R.string.direct_messages),
|
||||||
DrawerChannel(
|
style = MaterialTheme.typography.labelLarge,
|
||||||
name = channel.name
|
fontSize = 24.sp,
|
||||||
?: "GDM #${channel.id}",
|
modifier = Modifier.padding(16.dp)
|
||||||
channelType = ChannelType.Group,
|
)
|
||||||
selected = currentChannel == channel.id,
|
}
|
||||||
hasUnread = channel.lastMessageID?.let { lastMessageID ->
|
|
||||||
RevoltAPI.unreads.hasUnread(
|
item(
|
||||||
channel.id!!,
|
key = "home"
|
||||||
lastMessageID
|
) {
|
||||||
)
|
DrawerChannel(
|
||||||
} ?: false,
|
name = stringResource(R.string.home),
|
||||||
onClick = {
|
channelType = ChannelType.TextChannel,
|
||||||
onChannelClick(channel.id ?: return@DrawerChannel)
|
selected = currentDestination == "home",
|
||||||
coroutineScope.launch { drawerState.close() }
|
hasUnread = false,
|
||||||
},
|
onClick = {
|
||||||
onLongClick = {
|
onSpecialClick("home")
|
||||||
channelContextSheetTarget = channel.id ?: return@DrawerChannel
|
},
|
||||||
channelContextSheetShown = true
|
large = true,
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item(
|
||||||
|
key = "notes"
|
||||||
|
) {
|
||||||
|
val notesChannelId =
|
||||||
|
RevoltAPI.channelCache.values.firstOrNull { it.channelType == ChannelType.SavedMessages }?.id
|
||||||
|
|
||||||
|
DrawerChannel(
|
||||||
|
name = stringResource(R.string.channel_notes),
|
||||||
|
channelType = ChannelType.SavedMessages,
|
||||||
|
selected = currentDestination == "channel/{channelId}" && currentChannel == notesChannelId,
|
||||||
|
hasUnread = false,
|
||||||
|
onClick = {
|
||||||
|
onChannelClick(notesChannelId ?: return@DrawerChannel)
|
||||||
|
},
|
||||||
|
large = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item(
|
||||||
|
key = "divider"
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
Modifier
|
||||||
|
.padding(vertical = 8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(1.dp),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
items(
|
||||||
|
dmAbleChannels.size,
|
||||||
|
key = { index ->
|
||||||
|
val channel = dmAbleChannels.getOrNull(index)
|
||||||
|
channel?.id ?: index
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
val channel = dmAbleChannels.getOrNull(it) ?: return@items
|
||||||
|
|
||||||
|
val partner =
|
||||||
|
if (channel.channelType == ChannelType.DirectMessage) RevoltAPI.userCache[ChannelUtils.resolveDMPartner(
|
||||||
|
channel
|
||||||
|
)] else null
|
||||||
|
|
||||||
|
DrawerChannel(
|
||||||
|
name = partner?.username ?: channel.name
|
||||||
|
?: stringResource(R.string.unknown),
|
||||||
|
channelType = channel.channelType ?: ChannelType.TextChannel,
|
||||||
|
selected = currentDestination == "channel/{channelId}" && currentChannel == channel.id,
|
||||||
|
hasUnread = channel.lastMessageID?.let { lastMessageID ->
|
||||||
|
RevoltAPI.unreads.hasUnread(
|
||||||
|
channel.id!!,
|
||||||
|
lastMessageID
|
||||||
)
|
)
|
||||||
|
} ?: false,
|
||||||
|
dmPartnerIcon = partner?.avatar ?: channel.icon,
|
||||||
|
dmPartnerId = partner?.id,
|
||||||
|
dmPartnerName = partner?.username,
|
||||||
|
dmPartnerStatus = presenceFromStatus(
|
||||||
|
partner?.status?.presence ?: "Offline"
|
||||||
|
),
|
||||||
|
onClick = {
|
||||||
|
onChannelClick(channel.id ?: return@DrawerChannel)
|
||||||
|
},
|
||||||
|
onLongClick = {
|
||||||
|
channelContextSheetTarget = channel.id ?: return@DrawerChannel
|
||||||
|
channelContextSheetShown = true
|
||||||
}
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val server = RevoltAPI.serverCache[serverId]
|
val server = RevoltAPI.serverCache[serverId]
|
||||||
|
|
||||||
Text(
|
item {
|
||||||
text = server?.name
|
Text(
|
||||||
?: stringResource(R.string.unknown),
|
text = server?.name
|
||||||
style = MaterialTheme.typography.labelLarge,
|
?: stringResource(R.string.unknown),
|
||||||
fontSize = 24.sp,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
modifier = Modifier.padding(16.dp)
|
fontSize = 24.sp,
|
||||||
)
|
modifier = Modifier.padding(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (server?.channels?.isEmpty() == true) {
|
if (server?.channels?.isEmpty() == true) {
|
||||||
Column(
|
item {
|
||||||
Modifier.weight(1f),
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
Modifier.weight(1f),
|
||||||
verticalArrangement = Arrangement.Center
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
verticalArrangement = Arrangement.Center
|
||||||
Text(
|
) {
|
||||||
text = stringResource(R.string.no_channels_heading),
|
Text(
|
||||||
style = MaterialTheme.typography.labelLarge,
|
text = stringResource(R.string.no_channels_heading),
|
||||||
textAlign = TextAlign.Center,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
fontSize = 24.sp,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.padding(bottom = 16.dp)
|
fontSize = 24.sp,
|
||||||
)
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
Text(
|
)
|
||||||
text = stringResource(R.string.no_channels_body),
|
Text(
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
text = stringResource(R.string.no_channels_body),
|
||||||
textAlign = TextAlign.Center,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
)
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Column(
|
items(
|
||||||
Modifier
|
server?.channels?.size ?: 0,
|
||||||
.weight(1f)
|
key = { server?.channels?.get(it) ?: "" }
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
) {
|
) {
|
||||||
server?.channels?.forEach { channelId ->
|
server?.channels?.get(it)?.let { channelId ->
|
||||||
RevoltAPI.channelCache[channelId]?.let { ch ->
|
RevoltAPI.channelCache[channelId]?.let { ch ->
|
||||||
DrawerChannel(
|
DrawerChannel(
|
||||||
name = ch.name!!,
|
name = ch.name!!,
|
||||||
channelType = ch.channelType!!,
|
channelType = ch.channelType!!,
|
||||||
selected = currentChannel == ch.id,
|
selected = currentDestination == "channel/{channelId}" && currentChannel == ch.id,
|
||||||
hasUnread = ch.lastMessageID?.let { lastMessageID ->
|
hasUnread = ch.lastMessageID?.let { lastMessageID ->
|
||||||
RevoltAPI.unreads.hasUnread(
|
RevoltAPI.unreads.hasUnread(
|
||||||
ch.id!!,
|
ch.id!!,
|
||||||
|
|
@ -158,7 +235,6 @@ fun RowScope.ChannelList(
|
||||||
} ?: true,
|
} ?: true,
|
||||||
onClick = {
|
onClick = {
|
||||||
onChannelClick(ch.id ?: return@DrawerChannel)
|
onChannelClick(ch.id ?: return@DrawerChannel)
|
||||||
coroutineScope.launch { drawerState.close() }
|
|
||||||
},
|
},
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
channelContextSheetTarget = ch.id ?: return@DrawerChannel
|
channelContextSheetTarget = ch.id ?: return@DrawerChannel
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,13 @@ import androidx.compose.animation.core.spring
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -20,7 +26,11 @@ import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import chat.revolt.api.schemas.AutumnResource
|
||||||
import chat.revolt.api.schemas.ChannelType
|
import chat.revolt.api.schemas.ChannelType
|
||||||
|
import chat.revolt.components.generic.GroupIcon
|
||||||
|
import chat.revolt.components.generic.Presence
|
||||||
|
import chat.revolt.components.generic.UserAvatar
|
||||||
import chat.revolt.components.screens.chat.ChannelIcon
|
import chat.revolt.components.screens.chat.ChannelIcon
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
|
@ -32,6 +42,11 @@ fun DrawerChannel(
|
||||||
hasUnread: Boolean,
|
hasUnread: Boolean,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit = {},
|
onLongClick: () -> Unit = {},
|
||||||
|
dmPartnerStatus: Presence? = null,
|
||||||
|
dmPartnerName: String? = null,
|
||||||
|
dmPartnerIcon: AutumnResource? = null,
|
||||||
|
dmPartnerId: String? = null,
|
||||||
|
large: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val backgroundColor = animateColorAsState(
|
val backgroundColor = animateColorAsState(
|
||||||
if (selected) MaterialTheme.colorScheme.background
|
if (selected) MaterialTheme.colorScheme.background
|
||||||
|
|
@ -66,7 +81,37 @@ fun DrawerChannel(
|
||||||
.padding(vertical = 8.dp, horizontal = 16.dp),
|
.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
ChannelIcon(channelType = channelType, modifier = Modifier.padding(end = 8.dp))
|
when (channelType) {
|
||||||
|
ChannelType.DirectMessage -> UserAvatar(
|
||||||
|
username = dmPartnerName ?: "",
|
||||||
|
avatar = dmPartnerIcon,
|
||||||
|
userId = dmPartnerId ?: "",
|
||||||
|
presence = dmPartnerStatus,
|
||||||
|
size = 32.dp,
|
||||||
|
presenceSize = 16.dp,
|
||||||
|
modifier = Modifier.padding(end = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
ChannelType.Group -> GroupIcon(
|
||||||
|
name = name,
|
||||||
|
icon = dmPartnerIcon,
|
||||||
|
size = 32.dp,
|
||||||
|
modifier = Modifier.padding(end = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> ChannelIcon(
|
||||||
|
channelType = channelType,
|
||||||
|
modifier = Modifier.then(
|
||||||
|
if (large) Modifier.padding(
|
||||||
|
end = 12.dp,
|
||||||
|
start = 4.dp,
|
||||||
|
top = 4.dp,
|
||||||
|
bottom = 4.dp
|
||||||
|
) else Modifier.padding(end = 8.dp)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = name,
|
text = name,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,14 @@ class ChatRouterViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun navigateToSpecial(destination: String, navController: NavController) {
|
||||||
|
navController.navigate(destination) {
|
||||||
|
navController.graph.startDestinationRoute?.let { route ->
|
||||||
|
popUpTo(route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
|
|
@ -379,11 +387,16 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil
|
||||||
) {
|
) {
|
||||||
ChannelList(
|
ChannelList(
|
||||||
serverId = it,
|
serverId = it,
|
||||||
drawerState = drawerState,
|
currentDestination = navController.currentDestination?.route,
|
||||||
currentChannel = viewModel.currentChannel,
|
currentChannel = viewModel.currentChannel,
|
||||||
onChannelClick = { channelId ->
|
onChannelClick = { channelId ->
|
||||||
viewModel.navigateToChannel(channelId, navController)
|
viewModel.navigateToChannel(channelId, navController)
|
||||||
}
|
scope.launch { drawerState.close() }
|
||||||
|
},
|
||||||
|
onSpecialClick = { destination ->
|
||||||
|
viewModel.navigateToSpecial(destination, navController)
|
||||||
|
scope.launch { drawerState.close() }
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ import chat.revolt.activities.RevoltTweenDp
|
||||||
import chat.revolt.activities.RevoltTweenFloat
|
import chat.revolt.activities.RevoltTweenFloat
|
||||||
import chat.revolt.activities.RevoltTweenInt
|
import chat.revolt.activities.RevoltTweenInt
|
||||||
import chat.revolt.api.RevoltAPI
|
import chat.revolt.api.RevoltAPI
|
||||||
|
import chat.revolt.api.internals.ChannelUtils
|
||||||
import chat.revolt.api.routes.microservices.autumn.FileArgs
|
import chat.revolt.api.routes.microservices.autumn.FileArgs
|
||||||
import chat.revolt.components.chat.Message
|
import chat.revolt.components.chat.Message
|
||||||
import chat.revolt.components.chat.MessageField
|
import chat.revolt.components.chat.MessageField
|
||||||
|
|
@ -390,7 +391,9 @@ fun ChannelScreen(
|
||||||
pickFileLauncher.launch(arrayOf("*/*"))
|
pickFileLauncher.launch(arrayOf("*/*"))
|
||||||
},
|
},
|
||||||
channelType = channel.channelType,
|
channelType = channel.channelType,
|
||||||
channelName = channel.name ?: channel.id!!,
|
channelName = channel.name ?: ChannelUtils.resolveDMName(channel) ?: stringResource(
|
||||||
|
R.string.unknown
|
||||||
|
),
|
||||||
forceSendButton = viewModel.attachments.isNotEmpty(),
|
forceSendButton = viewModel.attachments.isNotEmpty(),
|
||||||
disabled = viewModel.attachments.isNotEmpty() && viewModel.sendingMessage
|
disabled = viewModel.attachments.isNotEmpty() && viewModel.sendingMessage
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,8 @@
|
||||||
|
|
||||||
<string name="avatar_alt">%1$s\'s avatar</string>
|
<string name="avatar_alt">%1$s\'s avatar</string>
|
||||||
|
|
||||||
|
<string name="direct_messages">Direct Messages</string>
|
||||||
|
|
||||||
<string name="channel_dm">Direct Message</string>
|
<string name="channel_dm">Direct Message</string>
|
||||||
<string name="channel_text">Text Channel</string>
|
<string name="channel_text">Text Channel</string>
|
||||||
<string name="channel_voice">Voice Channel</string>
|
<string name="channel_voice">Voice Channel</string>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue