parent
aa443d2acb
commit
fcdaf0759d
|
|
@ -63,10 +63,10 @@ import chat.revolt.api.settings.GlobalState
|
||||||
import chat.revolt.api.settings.SyncedSettings
|
import chat.revolt.api.settings.SyncedSettings
|
||||||
import chat.revolt.components.chat.NativeMessageField
|
import chat.revolt.components.chat.NativeMessageField
|
||||||
import chat.revolt.components.emoji.EmojiPicker
|
import chat.revolt.components.emoji.EmojiPicker
|
||||||
import chat.revolt.components.generic.presenceFromStatus
|
|
||||||
import chat.revolt.components.screens.chat.AttachmentManager
|
import chat.revolt.components.screens.chat.AttachmentManager
|
||||||
import chat.revolt.components.screens.chat.drawer.server.DrawerChannel
|
import chat.revolt.components.screens.chat.drawer.ChannelItem
|
||||||
import chat.revolt.components.screens.chat.drawer.server.DrawerChannelIconType
|
import chat.revolt.components.screens.chat.drawer.ChannelItemIconType
|
||||||
|
import chat.revolt.components.screens.chat.drawer.DMOrGroupItem
|
||||||
import chat.revolt.persistence.KVStorage
|
import chat.revolt.persistence.KVStorage
|
||||||
import chat.revolt.screens.chat.views.channel.ChannelScreenActivePane
|
import chat.revolt.screens.chat.views.channel.ChannelScreenActivePane
|
||||||
import chat.revolt.ui.theme.RevoltTheme
|
import chat.revolt.ui.theme.RevoltTheme
|
||||||
|
|
@ -342,39 +342,29 @@ fun ShareTargetScreen(
|
||||||
items(filteredChannels.count()) {
|
items(filteredChannels.count()) {
|
||||||
val channel = filteredChannels.elementAt(it)
|
val channel = filteredChannels.elementAt(it)
|
||||||
|
|
||||||
DrawerChannel(
|
when (channel.channelType) {
|
||||||
iconType = DrawerChannelIconType.Channel(
|
ChannelType.Group, ChannelType.DirectMessage -> DMOrGroupItem(
|
||||||
channel.channelType ?: ChannelType.TextChannel
|
channel = channel,
|
||||||
),
|
partner = ChannelUtils.resolveDMPartner(channel)?.let { u ->
|
||||||
name = (if (channel.server != null) "${channel.name} (${RevoltAPI.serverCache[channel.server]?.name})" else channel.name)
|
RevoltAPI.userCache[u]
|
||||||
?: ChannelUtils.resolveName(channel)
|
|
||||||
?: stringResource(R.string.unknown),
|
|
||||||
selected = selectedChannel == channel.id,
|
|
||||||
hasUnread = false,
|
|
||||||
onClick = {
|
|
||||||
selectedChannel = channel.id
|
|
||||||
},
|
|
||||||
dmPartnerIcon = ChannelUtils.resolveDMPartner(
|
|
||||||
channel
|
|
||||||
)?.let { u -> RevoltAPI.userCache[u] }?.avatar,
|
|
||||||
dmPartnerName = ChannelUtils.resolveName(
|
|
||||||
channel
|
|
||||||
),
|
|
||||||
dmPartnerStatus = ChannelUtils.resolveDMPartner(
|
|
||||||
channel
|
|
||||||
)
|
|
||||||
?.let { u -> RevoltAPI.userCache[u] }?.status?.presence?.let { p ->
|
|
||||||
presenceFromStatus(
|
|
||||||
p,
|
|
||||||
RevoltAPI.userCache[ChannelUtils.resolveDMPartner(
|
|
||||||
channel
|
|
||||||
)]?.online ?: false
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
dmPartnerId = ChannelUtils.resolveDMPartner(
|
isCurrent = selectedChannel == channel.id,
|
||||||
channel
|
hasUnread = false,
|
||||||
),
|
onDestinationChanged = { selectedChannel = channel.id },
|
||||||
)
|
onOpenChannelContextSheet = {}
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> ChannelItem(
|
||||||
|
iconType = ChannelItemIconType.Channel(
|
||||||
|
channel.channelType ?: ChannelType.TextChannel
|
||||||
|
),
|
||||||
|
channel = channel,
|
||||||
|
isCurrent = selectedChannel == channel.id,
|
||||||
|
onDestinationChanged = { selectedChannel = channel.id },
|
||||||
|
onOpenChannelContextSheet = {},
|
||||||
|
appendServerName = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
package chat.revolt.components.generic
|
package chat.revolt.components.generic
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
|
@ -15,31 +13,16 @@ import androidx.compose.ui.unit.TextUnit
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
private val NoopHandler = {}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun IconPlaceholder(
|
fun IconPlaceholder(
|
||||||
name: String,
|
name: String,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClick: () -> Unit = NoopHandler,
|
|
||||||
onLongClick: () -> Unit = NoopHandler,
|
|
||||||
fontSize: TextUnit = 20.sp
|
fontSize: TextUnit = 20.sp
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp))
|
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp))
|
||||||
.then(
|
|
||||||
if (onClick != NoopHandler || onLongClick != NoopHandler) {
|
|
||||||
Modifier.combinedClickable(
|
|
||||||
onClick = onClick,
|
|
||||||
onLongClick = onLongClick
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Modifier
|
|
||||||
}
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = name.first().uppercase(),
|
text = name.first().uppercase(),
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,623 +0,0 @@
|
||||||
package chat.revolt.components.screens.chat.drawer.channel
|
|
||||||
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.ImageView
|
|
||||||
import androidx.appcompat.widget.AppCompatImageView
|
|
||||||
import androidx.compose.animation.animateColorAsState
|
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.RowScope
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.LocalContentColor
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
|
||||||
import androidx.compose.material3.surfaceColorAtElevation
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
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.viewinterop.AndroidView
|
|
||||||
import chat.revolt.R
|
|
||||||
import chat.revolt.activities.RevoltTweenColour
|
|
||||||
import chat.revolt.activities.RevoltTweenDp
|
|
||||||
import chat.revolt.activities.RevoltTweenFloat
|
|
||||||
import chat.revolt.api.REVOLT_FILES
|
|
||||||
import chat.revolt.api.RevoltAPI
|
|
||||||
import chat.revolt.api.internals.CategorisedChannelList
|
|
||||||
import chat.revolt.api.internals.ChannelUtils
|
|
||||||
import chat.revolt.api.routes.user.openDM
|
|
||||||
import chat.revolt.api.schemas.ChannelType
|
|
||||||
import chat.revolt.api.schemas.ServerFlags
|
|
||||||
import chat.revolt.api.schemas.User
|
|
||||||
import chat.revolt.api.schemas.has
|
|
||||||
import chat.revolt.components.generic.presenceFromStatus
|
|
||||||
import chat.revolt.components.screens.chat.drawer.server.DrawerChannel
|
|
||||||
import chat.revolt.components.screens.chat.drawer.server.DrawerChannelIconType
|
|
||||||
import chat.revolt.screens.chat.ChatRouterDestination
|
|
||||||
import chat.revolt.sheets.ChannelContextSheet
|
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
const val BANNER_HEIGHT_COMPACT = 56
|
|
||||||
const val BANNER_HEIGHT_EXPANDED = 128
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
|
||||||
@Composable
|
|
||||||
fun RowScope.ChannelList(
|
|
||||||
serverId: String?,
|
|
||||||
currentDestination: ChatRouterDestination,
|
|
||||||
onDestinationChange: (ChatRouterDestination) -> Unit,
|
|
||||||
onServerSheetOpenFor: (String) -> Unit
|
|
||||||
) {
|
|
||||||
val lazyListState = rememberLazyListState()
|
|
||||||
val enableSmallBanner by remember {
|
|
||||||
derivedStateOf {
|
|
||||||
lazyListState.firstVisibleItemScrollOffset > 40 ||
|
|
||||||
lazyListState.firstVisibleItemIndex > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val bannerHeight by animateDpAsState(
|
|
||||||
targetValue = if (enableSmallBanner) BANNER_HEIGHT_COMPACT.dp else BANNER_HEIGHT_EXPANDED.dp,
|
|
||||||
animationSpec = RevoltTweenDp,
|
|
||||||
label = "Banner Height"
|
|
||||||
)
|
|
||||||
val bannerImageOpacity by animateFloatAsState(
|
|
||||||
targetValue = if (enableSmallBanner) 0f else 1f,
|
|
||||||
animationSpec = RevoltTweenFloat,
|
|
||||||
label = "Banner Image Opacity"
|
|
||||||
)
|
|
||||||
val bannerTextColour by animateColorAsState(
|
|
||||||
targetValue = if (enableSmallBanner) LocalContentColor.current else Color.White,
|
|
||||||
animationSpec = RevoltTweenColour,
|
|
||||||
label = "Banner Text Colour"
|
|
||||||
)
|
|
||||||
|
|
||||||
var channelContextSheetShown by remember { mutableStateOf(false) }
|
|
||||||
var channelContextSheetTarget by remember { mutableStateOf("") }
|
|
||||||
|
|
||||||
if (channelContextSheetShown) {
|
|
||||||
val channelContextSheetState = rememberModalBottomSheetState()
|
|
||||||
|
|
||||||
ModalBottomSheet(
|
|
||||||
sheetState = channelContextSheetState,
|
|
||||||
onDismissRequest = {
|
|
||||||
channelContextSheetShown = false
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
ChannelContextSheet(
|
|
||||||
channelId = channelContextSheetTarget,
|
|
||||||
onHideSheet = {
|
|
||||||
channelContextSheetState.hide()
|
|
||||||
channelContextSheetShown = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
val server = RevoltAPI.serverCache[serverId]
|
|
||||||
val categorisedChannels = server?.let {
|
|
||||||
ChannelUtils.categoriseServerFlat(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
Surface(
|
|
||||||
tonalElevation = 1.dp,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = 4.dp, top = 8.dp, bottom = 8.dp)
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
LazyColumn(
|
|
||||||
Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.fillMaxSize(),
|
|
||||||
state = lazyListState
|
|
||||||
) {
|
|
||||||
if (serverId == null) {
|
|
||||||
stickyHeader(
|
|
||||||
key = "header"
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = 8.dp, end = 8.dp, top = 0.dp, bottom = 8.dp)
|
|
||||||
.alpha(0.9f)
|
|
||||||
.height(BANNER_HEIGHT_COMPACT.dp + 8.dp) // due to padding in Text
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
|
|
||||||
.weight(1f)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.direct_messages),
|
|
||||||
style = MaterialTheme.typography.labelLarge,
|
|
||||||
fontSize = 16.sp,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = 16.dp, end = 16.dp, top = 24.dp, bottom = 16.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item(
|
|
||||||
key = "home"
|
|
||||||
) {
|
|
||||||
DrawerChannel(
|
|
||||||
name = stringResource(R.string.home),
|
|
||||||
iconType = DrawerChannelIconType.Painter(painterResource(R.drawable.ic_home_24dp)),
|
|
||||||
selected = currentDestination == ChatRouterDestination.Home,
|
|
||||||
hasUnread = false,
|
|
||||||
onClick = {
|
|
||||||
onDestinationChange(ChatRouterDestination.Home)
|
|
||||||
},
|
|
||||||
large = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
item(
|
|
||||||
key = "friends"
|
|
||||||
) {
|
|
||||||
DrawerChannel(
|
|
||||||
name = stringResource(R.string.friends),
|
|
||||||
iconType = DrawerChannelIconType.Painter(painterResource(R.drawable.ic_human_greeting_variant_24dp)),
|
|
||||||
selected = currentDestination == ChatRouterDestination.Friends,
|
|
||||||
hasUnread = false,
|
|
||||||
onClick = {
|
|
||||||
onDestinationChange(ChatRouterDestination.Friends)
|
|
||||||
},
|
|
||||||
large = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
item(
|
|
||||||
key = "notes"
|
|
||||||
) {
|
|
||||||
val notesChannelId =
|
|
||||||
RevoltAPI.channelCache.values.firstOrNull { it.channelType == ChannelType.SavedMessages }?.id
|
|
||||||
|
|
||||||
DrawerChannel(
|
|
||||||
name = stringResource(R.string.channel_notes),
|
|
||||||
iconType = DrawerChannelIconType.Channel(ChannelType.SavedMessages),
|
|
||||||
selected = currentDestination == ChatRouterDestination.Channel(
|
|
||||||
notesChannelId ?: ""
|
|
||||||
),
|
|
||||||
hasUnread = false,
|
|
||||||
onClick = {
|
|
||||||
if (notesChannelId != null) {
|
|
||||||
onDestinationChange(ChatRouterDestination.Channel(notesChannelId))
|
|
||||||
return@DrawerChannel
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.launch {
|
|
||||||
val notesChannel = openDM(RevoltAPI.selfId ?: return@launch)
|
|
||||||
if (notesChannel.id != null) {
|
|
||||||
if (RevoltAPI.channelCache[notesChannel.id] == null)
|
|
||||||
RevoltAPI.channelCache[notesChannel.id] = notesChannel
|
|
||||||
}
|
|
||||||
onDestinationChange(
|
|
||||||
ChatRouterDestination.Channel(
|
|
||||||
notesChannel.id ?: return@launch
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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?.let { p -> User.resolveDefaultName(p) } ?: channel.name
|
|
||||||
?: stringResource(R.string.unknown),
|
|
||||||
iconType = DrawerChannelIconType.Channel(
|
|
||||||
channel.channelType ?: ChannelType.TextChannel
|
|
||||||
),
|
|
||||||
selected = currentDestination == ChatRouterDestination.Channel(
|
|
||||||
channel.id ?: ""
|
|
||||||
),
|
|
||||||
hasUnread = channel.lastMessageID?.let { lastMessageID ->
|
|
||||||
RevoltAPI.unreads.hasUnread(
|
|
||||||
channel.id!!,
|
|
||||||
lastMessageID
|
|
||||||
)
|
|
||||||
} ?: false,
|
|
||||||
dmPartnerIcon = partner?.avatar ?: channel.icon,
|
|
||||||
dmPartnerId = partner?.id,
|
|
||||||
dmPartnerName = partner?.let { p -> User.resolveDefaultName(p) },
|
|
||||||
dmPartnerStatus = presenceFromStatus(
|
|
||||||
status = partner?.status?.presence,
|
|
||||||
online = partner?.online ?: false
|
|
||||||
),
|
|
||||||
onClick = {
|
|
||||||
onDestinationChange(
|
|
||||||
ChatRouterDestination.Channel(
|
|
||||||
channel.id ?: return@DrawerChannel
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onLongClick = {
|
|
||||||
channelContextSheetTarget = channel.id ?: return@DrawerChannel
|
|
||||||
channelContextSheetShown = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stickyHeader {
|
|
||||||
Box(
|
|
||||||
contentAlignment = Alignment.BottomStart,
|
|
||||||
modifier = Modifier
|
|
||||||
.then(
|
|
||||||
// if there is no banner, we change the design slightly.
|
|
||||||
// instead of there being a banner card we make a "classic"
|
|
||||||
// sticky header á la Google Messages
|
|
||||||
if (server?.banner != null) {
|
|
||||||
Modifier.padding(vertical = 8.dp, horizontal = 8.dp)
|
|
||||||
} else {
|
|
||||||
Modifier.padding(
|
|
||||||
start = 0.dp,
|
|
||||||
end = 8.dp,
|
|
||||||
top = 0.dp,
|
|
||||||
bottom = 0.dp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
if (server?.banner != null) {
|
|
||||||
Box(modifier = Modifier.height(bannerHeight)) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.alpha(max(0.95f, bannerImageOpacity))
|
|
||||||
.fillMaxSize()
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
|
||||||
.background(MaterialTheme.colorScheme.surface)
|
|
||||||
)
|
|
||||||
|
|
||||||
// *** ANDROIDVIEW RATIONALE ***
|
|
||||||
// Compose w/ Glide looks super laggy when resizing, because
|
|
||||||
// it tries to refetch the image every time. (luckily from cache)
|
|
||||||
// This is a temporary workaround until Glide can be resized
|
|
||||||
// without refetching in Compose.
|
|
||||||
AndroidView(
|
|
||||||
factory = { ctx ->
|
|
||||||
AppCompatImageView(ctx).apply {
|
|
||||||
scaleType = ImageView.ScaleType.CENTER_CROP
|
|
||||||
|
|
||||||
Glide.with(this)
|
|
||||||
.load("$REVOLT_FILES/banners/${server.banner.id}")
|
|
||||||
.transition(
|
|
||||||
DrawableTransitionOptions.withCrossFade()
|
|
||||||
)
|
|
||||||
.into(this)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
update = {
|
|
||||||
it.layoutParams = ViewGroup.LayoutParams(
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT
|
|
||||||
)
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.alpha(bannerImageOpacity)
|
|
||||||
.fillMaxSize()
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
|
||||||
)
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.alpha(bannerImageOpacity)
|
|
||||||
.fillMaxSize()
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
|
||||||
.background(
|
|
||||||
Brush.verticalGradient(
|
|
||||||
listOf(
|
|
||||||
Color.Transparent,
|
|
||||||
Color.Black.copy(alpha = 0.3f)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.alpha(0.9f)
|
|
||||||
.height(
|
|
||||||
BANNER_HEIGHT_COMPACT.dp + 8.dp
|
|
||||||
) // due to padding in Text
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(
|
|
||||||
MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Spacer(Modifier.width(16.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 = if (server?.banner != null) {
|
|
||||||
bannerTextColour
|
|
||||||
} else {
|
|
||||||
LocalContentColor.current
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(end = 8.dp)
|
|
||||||
.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 = if (server?.banner != null) {
|
|
||||||
bannerTextColour
|
|
||||||
} else {
|
|
||||||
LocalContentColor.current
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(end = 8.dp)
|
|
||||||
.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = (
|
|
||||||
server?.name
|
|
||||||
?: stringResource(R.string.unknown)
|
|
||||||
),
|
|
||||||
style = MaterialTheme.typography.labelLarge,
|
|
||||||
color = if (server?.banner != null) {
|
|
||||||
bannerTextColour
|
|
||||||
} else {
|
|
||||||
LocalContentColor.current
|
|
||||||
},
|
|
||||||
fontSize = 16.sp,
|
|
||||||
modifier = Modifier
|
|
||||||
.then(
|
|
||||||
if (server?.banner != null) {
|
|
||||||
Modifier.padding(
|
|
||||||
start = 0.dp,
|
|
||||||
end = 16.dp,
|
|
||||||
top = 16.dp,
|
|
||||||
bottom = 16.dp
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Modifier.padding(
|
|
||||||
start = 0.dp,
|
|
||||||
end = 24.dp,
|
|
||||||
top = 16.dp,
|
|
||||||
bottom = 16.dp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.weight(1f),
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
|
|
||||||
IconButton(onClick = {
|
|
||||||
onServerSheetOpenFor(serverId ?: return@IconButton)
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.MoreVert,
|
|
||||||
contentDescription = stringResource(
|
|
||||||
id = R.string.settings
|
|
||||||
),
|
|
||||||
tint = if (server?.banner != null) {
|
|
||||||
bannerTextColour
|
|
||||||
} else {
|
|
||||||
LocalContentColor.current
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (categorisedChannels.isNullOrEmpty()) {
|
|
||||||
item {
|
|
||||||
Column(
|
|
||||||
Modifier.padding(16.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.Center
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.no_channels_heading),
|
|
||||||
style = MaterialTheme.typography.labelLarge,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
fontSize = 24.sp,
|
|
||||||
modifier = Modifier.padding(bottom = 16.dp)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.no_channels_body),
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
items(
|
|
||||||
categorisedChannels.size,
|
|
||||||
key = { index ->
|
|
||||||
val channel = categorisedChannels.getOrNull(index)
|
|
||||||
channel?.let {
|
|
||||||
when (it) {
|
|
||||||
is CategorisedChannelList.Channel -> it.channel.id
|
|
||||||
is CategorisedChannelList.Category -> it.category.id
|
|
||||||
}
|
|
||||||
} ?: index
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
when (val item = categorisedChannels.getOrNull(it)) {
|
|
||||||
is CategorisedChannelList.Channel -> {
|
|
||||||
val channel = item.channel
|
|
||||||
|
|
||||||
val partner =
|
|
||||||
if (channel.channelType == ChannelType.DirectMessage) {
|
|
||||||
RevoltAPI.userCache[
|
|
||||||
ChannelUtils.resolveDMPartner(
|
|
||||||
channel
|
|
||||||
)
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawerChannel(
|
|
||||||
name = partner?.let { p -> User.resolveDefaultName(p) }
|
|
||||||
?: channel.name
|
|
||||||
?: stringResource(R.string.unknown),
|
|
||||||
iconType = DrawerChannelIconType.Channel(
|
|
||||||
channel.channelType ?: ChannelType.TextChannel
|
|
||||||
),
|
|
||||||
selected = currentDestination == ChatRouterDestination.Channel(
|
|
||||||
channel.id ?: ""
|
|
||||||
),
|
|
||||||
hasUnread = channel.lastMessageID?.let { lastMessageID ->
|
|
||||||
RevoltAPI.unreads.hasUnread(
|
|
||||||
channel.id!!,
|
|
||||||
lastMessageID
|
|
||||||
)
|
|
||||||
} ?: false,
|
|
||||||
dmPartnerIcon = partner?.avatar ?: channel.icon,
|
|
||||||
dmPartnerId = partner?.id,
|
|
||||||
dmPartnerName = partner?.let { p ->
|
|
||||||
User.resolveDefaultName(
|
|
||||||
p
|
|
||||||
)
|
|
||||||
},
|
|
||||||
dmPartnerStatus = presenceFromStatus(
|
|
||||||
status = partner?.status?.presence,
|
|
||||||
online = partner?.online ?: false
|
|
||||||
),
|
|
||||||
onClick = {
|
|
||||||
onDestinationChange(
|
|
||||||
ChatRouterDestination.Channel(
|
|
||||||
channel.id ?: return@DrawerChannel
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onLongClick = {
|
|
||||||
channelContextSheetTarget =
|
|
||||||
channel.id ?: return@DrawerChannel
|
|
||||||
channelContextSheetShown = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is CategorisedChannelList.Category -> {
|
|
||||||
val category = item.category
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = category.title ?: stringResource(R.string.unknown),
|
|
||||||
style = MaterialTheme.typography.labelLarge,
|
|
||||||
fontSize = 16.sp,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(
|
|
||||||
start = 16.dp,
|
|
||||||
end = 16.dp,
|
|
||||||
top = 24.dp,
|
|
||||||
bottom = 16.dp
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,169 +0,0 @@
|
||||||
package chat.revolt.components.screens.chat.drawer.server
|
|
||||||
|
|
||||||
import androidx.compose.animation.animateColorAsState
|
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
|
||||||
import androidx.compose.animation.core.spring
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
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.material3.Icon
|
|
||||||
import androidx.compose.material3.LocalContentColor
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import chat.revolt.api.schemas.AutumnResource
|
|
||||||
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
|
|
||||||
|
|
||||||
sealed class DrawerChannelIconType {
|
|
||||||
data class Channel(val type: ChannelType) : DrawerChannelIconType()
|
|
||||||
data class Painter(val painter: androidx.compose.ui.graphics.painter.Painter) :
|
|
||||||
DrawerChannelIconType()
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
@Composable
|
|
||||||
fun DrawerChannel(
|
|
||||||
iconType: DrawerChannelIconType,
|
|
||||||
name: String,
|
|
||||||
selected: Boolean,
|
|
||||||
hasUnread: Boolean,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
onLongClick: () -> Unit = {},
|
|
||||||
dmPartnerStatus: Presence? = null,
|
|
||||||
dmPartnerName: String? = null,
|
|
||||||
dmPartnerIcon: AutumnResource? = null,
|
|
||||||
dmPartnerId: String? = null,
|
|
||||||
large: Boolean = false
|
|
||||||
) {
|
|
||||||
val backgroundColor = animateColorAsState(
|
|
||||||
if (selected) {
|
|
||||||
MaterialTheme.colorScheme.background
|
|
||||||
} else {
|
|
||||||
Color.Transparent
|
|
||||||
},
|
|
||||||
animationSpec = spring(),
|
|
||||||
label = "Channel background colour"
|
|
||||||
)
|
|
||||||
|
|
||||||
val unreadDotOpacity = animateFloatAsState(
|
|
||||||
if (hasUnread) 1f else 0f,
|
|
||||||
animationSpec = spring(),
|
|
||||||
label = "Unread dot opacity"
|
|
||||||
)
|
|
||||||
|
|
||||||
val channelAlpha = animateFloatAsState(
|
|
||||||
if (hasUnread || selected) 1f else 0.8f,
|
|
||||||
animationSpec = spring(),
|
|
||||||
label = "Channel alpha"
|
|
||||||
)
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(vertical = 4.dp, horizontal = 8.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(MaterialTheme.shapes.medium)
|
|
||||||
.background(backgroundColor.value)
|
|
||||||
.alpha(channelAlpha.value)
|
|
||||||
.combinedClickable(
|
|
||||||
onClick = onClick,
|
|
||||||
onLongClick = onLongClick
|
|
||||||
)
|
|
||||||
.padding(vertical = 8.dp, horizontal = 16.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
when (iconType) {
|
|
||||||
is DrawerChannelIconType.Channel -> {
|
|
||||||
when (val channelType = iconType.type) {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is DrawerChannelIconType.Painter -> {
|
|
||||||
Icon(
|
|
||||||
painter = iconType.painter,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = LocalContentColor.current,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(end = 8.dp)
|
|
||||||
.size(32.dp)
|
|
||||||
.padding(4.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = name,
|
|
||||||
fontWeight = FontWeight.Medium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.padding(end = 8.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (hasUnread) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.offset(x = (-8).dp)
|
|
||||||
.clip(CircleShape)
|
|
||||||
.background(LocalContentColor.current)
|
|
||||||
.alpha(unreadDotOpacity.value)
|
|
||||||
.size(8.dp)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Spacer(modifier = Modifier.size(8.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
package chat.revolt.components.screens.chat.drawer.server
|
|
||||||
|
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
|
||||||
import androidx.compose.animation.core.spring
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
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.material3.LocalContentColor
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import chat.revolt.api.REVOLT_FILES
|
|
||||||
import chat.revolt.components.generic.IconPlaceholder
|
|
||||||
import chat.revolt.components.generic.RemoteImage
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
@Composable
|
|
||||||
fun DrawerServer(
|
|
||||||
iconId: String?,
|
|
||||||
serverName: String,
|
|
||||||
hasUnreads: Boolean,
|
|
||||||
onLongClick: () -> Unit,
|
|
||||||
onClick: () -> Unit
|
|
||||||
) {
|
|
||||||
val unreadIndicatorAlpha = animateFloatAsState(
|
|
||||||
if (hasUnreads) 1f else 0f,
|
|
||||||
animationSpec = spring(),
|
|
||||||
label = "Unread indicator alpha"
|
|
||||||
)
|
|
||||||
|
|
||||||
Box(
|
|
||||||
contentAlignment = Alignment.CenterStart
|
|
||||||
) {
|
|
||||||
if (iconId != null) {
|
|
||||||
RemoteImage(
|
|
||||||
url = "$REVOLT_FILES/icons/$iconId/server.png?max_side=256",
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.size(48.dp)
|
|
||||||
.clip(CircleShape)
|
|
||||||
.combinedClickable(
|
|
||||||
onClick = onClick,
|
|
||||||
onLongClick = onLongClick
|
|
||||||
),
|
|
||||||
description = serverName
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
IconPlaceholder(
|
|
||||||
name = serverName,
|
|
||||||
onClick = onClick,
|
|
||||||
onLongClick = onLongClick,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.size(48.dp)
|
|
||||||
.clip(CircleShape)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unread indicator
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.size(8.dp)
|
|
||||||
.offset(x = (-12).dp)
|
|
||||||
.clip(CircleShape)
|
|
||||||
.alpha(unreadIndicatorAlpha.value)
|
|
||||||
.background(LocalContentColor.current)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
package chat.revolt.components.screens.chat.drawer.server
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
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.unit.dp
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun DrawerServerlikeIcon(onClick: () -> Unit, content: @Composable () -> Unit) {
|
|
||||||
IconButton(
|
|
||||||
onClick = onClick,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.size(48.dp)
|
|
||||||
.clip(CircleShape)
|
|
||||||
.background(MaterialTheme.colorScheme.surface)
|
|
||||||
) {
|
|
||||||
content()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
package chat.revolt.components.screens.chat.drawer.server
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ServerDrawerSeparator() {
|
|
||||||
Box(
|
|
||||||
Modifier
|
|
||||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
|
||||||
.height(1.dp)
|
|
||||||
.width(48.dp)
|
|
||||||
.background(
|
|
||||||
MaterialTheme.colorScheme.onSurfaceVariant.copy(
|
|
||||||
alpha = 0.1f
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -56,7 +56,7 @@ fun ServerOverview(server: Server) {
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(166.dp)
|
.height(166.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
contentScale = ContentScale.FillWidth
|
contentScale = ContentScale.Crop
|
||||||
)
|
)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
|
|
|
||||||
|
|
@ -6,25 +6,15 @@ import android.view.accessibility.AccessibilityManager
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.Crossfade
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.statusBars
|
import androidx.compose.foundation.layout.statusBars
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Add
|
|
||||||
import androidx.compose.material.icons.filled.Settings
|
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.DismissibleDrawerSheet
|
import androidx.compose.material3.DismissibleDrawerSheet
|
||||||
import androidx.compose.material3.DismissibleNavigationDrawer
|
import androidx.compose.material3.DismissibleNavigationDrawer
|
||||||
|
|
@ -49,7 +39,6 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.snapshotFlow
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
@ -57,32 +46,23 @@ import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
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.internals.DirectMessages
|
import chat.revolt.api.internals.DirectMessages
|
||||||
import chat.revolt.api.realtime.DisconnectionState
|
import chat.revolt.api.realtime.DisconnectionState
|
||||||
import chat.revolt.api.realtime.RealtimeSocket
|
import chat.revolt.api.realtime.RealtimeSocket
|
||||||
import chat.revolt.api.schemas.ChannelType
|
|
||||||
import chat.revolt.api.schemas.User
|
|
||||||
import chat.revolt.api.settings.SyncedSettings
|
|
||||||
import chat.revolt.callbacks.Action
|
import chat.revolt.callbacks.Action
|
||||||
import chat.revolt.callbacks.ActionChannel
|
import chat.revolt.callbacks.ActionChannel
|
||||||
import chat.revolt.components.chat.DisconnectedNotice
|
import chat.revolt.components.chat.DisconnectedNotice
|
||||||
import chat.revolt.components.generic.GroupIcon
|
import chat.revolt.components.screens.chat.drawer.ChannelSideDrawer
|
||||||
import chat.revolt.components.generic.UserAvatar
|
|
||||||
import chat.revolt.components.generic.presenceFromStatus
|
|
||||||
import chat.revolt.components.screens.chat.drawer.channel.ChannelList
|
|
||||||
import chat.revolt.components.screens.chat.drawer.server.DrawerServer
|
|
||||||
import chat.revolt.components.screens.chat.drawer.server.DrawerServerlikeIcon
|
|
||||||
import chat.revolt.components.screens.chat.drawer.server.ServerDrawerSeparator
|
|
||||||
import chat.revolt.components.screens.voice.VoiceChannelOverlay
|
import chat.revolt.components.screens.voice.VoiceChannelOverlay
|
||||||
import chat.revolt.internals.Changelogs
|
import chat.revolt.internals.Changelogs
|
||||||
|
import chat.revolt.internals.extensions.BottomSheetInsets
|
||||||
|
import chat.revolt.internals.extensions.zero
|
||||||
import chat.revolt.persistence.KVStorage
|
import chat.revolt.persistence.KVStorage
|
||||||
import chat.revolt.screens.chat.dialogs.safety.ReportMessageDialog
|
import chat.revolt.screens.chat.dialogs.safety.ReportMessageDialog
|
||||||
import chat.revolt.screens.chat.dialogs.safety.ReportServerDialog
|
import chat.revolt.screens.chat.dialogs.safety.ReportServerDialog
|
||||||
|
|
@ -538,7 +518,8 @@ fun ChatRouterScreen(
|
||||||
sheetState = serverContextSheetState,
|
sheetState = serverContextSheetState,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
showServerContextSheet = false
|
showServerContextSheet = false
|
||||||
}
|
},
|
||||||
|
windowInsets = BottomSheetInsets
|
||||||
) {
|
) {
|
||||||
ServerContextSheet(
|
ServerContextSheet(
|
||||||
serverId = serverContextSheetTarget,
|
serverId = serverContextSheetTarget,
|
||||||
|
|
@ -718,7 +699,7 @@ fun ChatRouterScreen(
|
||||||
Row {
|
Row {
|
||||||
DismissibleDrawerSheet(
|
DismissibleDrawerSheet(
|
||||||
drawerContainerColor = Color.Transparent,
|
drawerContainerColor = Color.Transparent,
|
||||||
windowInsets = WindowInsets.navigationBars
|
windowInsets = WindowInsets.zero
|
||||||
) {
|
) {
|
||||||
Sidebar(
|
Sidebar(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
|
|
@ -755,7 +736,7 @@ fun ChatRouterScreen(
|
||||||
drawerContent = {
|
drawerContent = {
|
||||||
DismissibleDrawerSheet(
|
DismissibleDrawerSheet(
|
||||||
drawerContainerColor = Color.Transparent,
|
drawerContainerColor = Color.Transparent,
|
||||||
windowInsets = WindowInsets.navigationBars
|
windowInsets = WindowInsets.zero
|
||||||
) {
|
) {
|
||||||
Sidebar(
|
Sidebar(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
|
|
@ -809,192 +790,19 @@ fun Sidebar(
|
||||||
showSettingsButton: Boolean,
|
showSettingsButton: Boolean,
|
||||||
onOpenSettings: () -> Unit,
|
onOpenSettings: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
ChannelSideDrawer(
|
||||||
|
onDestinationChanged = viewModel::setSaveDestination,
|
||||||
Column(Modifier.fillMaxWidth()) {
|
currentDestination = viewModel.currentDestination,
|
||||||
Row {
|
currentServer = currentServer,
|
||||||
Column(
|
drawerState = drawerState,
|
||||||
modifier = Modifier
|
navigateToServer = viewModel::navigateToServer,
|
||||||
.fillMaxHeight()
|
onLongPressAvatar = onShowStatusSheet,
|
||||||
.verticalScroll(rememberScrollState()),
|
onShowServerContextSheet = onShowServerContextSheet,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
showSettingsIcon = showSettingsButton,
|
||||||
) {
|
onOpenSettings = onOpenSettings,
|
||||||
UserAvatar(
|
topNav = topNav,
|
||||||
username = RevoltAPI.userCache[RevoltAPI.selfId]?.let {
|
onShowAddServerSheet = onShowAddServerSheet
|
||||||
User.resolveDefaultName(
|
)
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
?: "",
|
|
||||||
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 = {
|
|
||||||
viewModel.setSaveDestination(ChatRouterDestination.defaultForDMList)
|
|
||||||
},
|
|
||||||
onLongClick = onShowStatusSheet,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.size(48.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
DirectMessages.unreadDMs().forEach {
|
|
||||||
when (it.channelType) {
|
|
||||||
ChannelType.Group -> GroupIcon(
|
|
||||||
name = it.name ?: "?",
|
|
||||||
size = 48.dp,
|
|
||||||
onClick = {
|
|
||||||
it.id?.let { id ->
|
|
||||||
viewModel.setSaveDestination(ChatRouterDestination.Channel(id))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon = it.icon,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.size(48.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
val partner =
|
|
||||||
if (it.channelType == ChannelType.DirectMessage) {
|
|
||||||
RevoltAPI.userCache[
|
|
||||||
ChannelUtils.resolveDMPartner(
|
|
||||||
it
|
|
||||||
)
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
UserAvatar(
|
|
||||||
username = partner?.let { p ->
|
|
||||||
User.resolveDefaultName(
|
|
||||||
p
|
|
||||||
)
|
|
||||||
} ?: it.name ?: "?",
|
|
||||||
presence = presenceFromStatus(
|
|
||||||
partner?.status?.presence,
|
|
||||||
partner?.online ?: false
|
|
||||||
),
|
|
||||||
userId = partner?.id ?: it.id ?: "",
|
|
||||||
avatar = partner?.avatar ?: it.icon,
|
|
||||||
size = 48.dp,
|
|
||||||
presenceSize = 16.dp,
|
|
||||||
onClick = {
|
|
||||||
it.id?.let { id ->
|
|
||||||
viewModel.setSaveDestination(
|
|
||||||
ChatRouterDestination.Channel(
|
|
||||||
id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.size(48.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerDrawerSeparator()
|
|
||||||
|
|
||||||
// This seems to confuse the formatter, here's what it does:
|
|
||||||
// - Take the list of servers and filter them by the ones that are in the ordering.
|
|
||||||
// - Sort the servers that are in the ordering using the ordering.
|
|
||||||
// - Add the servers that aren't in the ordering to the end of the list.
|
|
||||||
// - Sort the servers that aren't in the ordering by their ID (creation order).
|
|
||||||
(
|
|
||||||
(
|
|
||||||
RevoltAPI.serverCache.values.filter {
|
|
||||||
SyncedSettings.ordering.servers.contains(
|
|
||||||
it.id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.sortedBy { SyncedSettings.ordering.servers.indexOf(it.id) }
|
|
||||||
) + (
|
|
||||||
RevoltAPI.serverCache.values.filter {
|
|
||||||
!SyncedSettings.ordering.servers.contains(
|
|
||||||
it.id
|
|
||||||
)
|
|
||||||
}.sortedBy { it.id }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.forEach { server ->
|
|
||||||
if (server.id == null || server.name == null) return@forEach
|
|
||||||
|
|
||||||
DrawerServer(
|
|
||||||
iconId = server.icon?.id,
|
|
||||||
serverName = server.name,
|
|
||||||
hasUnreads = RevoltAPI.unreads.serverHasUnread(
|
|
||||||
server.id
|
|
||||||
),
|
|
||||||
onLongClick = {
|
|
||||||
onShowServerContextSheet(server.id)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
viewModel.navigateToServer(server.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawerServerlikeIcon(
|
|
||||||
onClick = onShowAddServerSheet
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.Add,
|
|
||||||
contentDescription = stringResource(id = R.string.server_plus_alt),
|
|
||||||
modifier = Modifier.padding(4.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawerServerlikeIcon(
|
|
||||||
onClick = { topNav.navigate("discover") }
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.ic_compass_24dp),
|
|
||||||
contentDescription = stringResource(id = R.string.discover_alt),
|
|
||||||
modifier = Modifier.padding(4.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showSettingsButton) {
|
|
||||||
DrawerServerlikeIcon(
|
|
||||||
onClick = { onOpenSettings() }
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Settings,
|
|
||||||
contentDescription = stringResource(id = R.string.settings),
|
|
||||||
modifier = Modifier.padding(4.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Crossfade(
|
|
||||||
targetState = currentServer,
|
|
||||||
label = "Channel List"
|
|
||||||
) {
|
|
||||||
ChannelList(
|
|
||||||
serverId = it,
|
|
||||||
currentDestination = viewModel.currentDestination,
|
|
||||||
onDestinationChange = { destination ->
|
|
||||||
viewModel.setSaveDestination(destination)
|
|
||||||
scope.launch {
|
|
||||||
drawerState?.close()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onServerSheetOpenFor = { target ->
|
|
||||||
onShowServerContextSheet(target)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -295,6 +295,7 @@
|
||||||
<string name="message_context_sheet_actions_delete_confirmation_yes">Delete</string>
|
<string name="message_context_sheet_actions_delete_confirmation_yes">Delete</string>
|
||||||
<string name="message_context_sheet_actions_delete_confirmation_no">Keep</string>
|
<string name="message_context_sheet_actions_delete_confirmation_no">Keep</string>
|
||||||
|
|
||||||
|
<string name="channel_context_sheet_open">Open channel actions</string>
|
||||||
<string name="channel_context_sheet_actions_copy_id">Copy ID</string>
|
<string name="channel_context_sheet_actions_copy_id">Copy ID</string>
|
||||||
<string name="channel_context_sheet_actions_copy_id_copied">Copied channel ID to clipboard</string>
|
<string name="channel_context_sheet_actions_copy_id_copied">Copied channel ID to clipboard</string>
|
||||||
<string name="channel_context_sheet_actions_mark_read">Mark as read</string>
|
<string name="channel_context_sheet_actions_mark_read">Mark as read</string>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue