feat: double drawer is now simple drawer
members will go into bottom sheet Signed-off-by: Infi <wingit@geist.ga>
This commit is contained in:
parent
a8d70041cd
commit
440bf36ea2
|
|
@ -11,11 +11,24 @@ import android.widget.Toast
|
|||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
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
|
||||
|
|
@ -115,6 +128,8 @@ fun Message(
|
|||
truncate: Boolean = false,
|
||||
parse: (MessageSchema) -> SpannableStringBuilder = { SpannableStringBuilder(it.content) },
|
||||
onMessageContextMenu: () -> Unit = {},
|
||||
canReply: Boolean = false,
|
||||
onReply: () -> Unit = {},
|
||||
) {
|
||||
val author = RevoltAPI.userCache[message.author] ?: return CircularProgressIndicator()
|
||||
val context = LocalContext.current
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
package chat.revolt.components.screens.chat
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
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.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.revolt.R
|
||||
import chat.revolt.api.schemas.Channel
|
||||
|
||||
@Composable
|
||||
fun ChannelHeader(
|
||||
channel: Channel,
|
||||
onChannelClick: (String) -> Unit,
|
||||
onToggleDrawer: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
channel.id?.let { onChannelClick(it) }
|
||||
}
|
||||
.padding(vertical = 4.dp, horizontal = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
IconButton(onClick = {
|
||||
onToggleDrawer()
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Menu,
|
||||
contentDescription = stringResource(R.string.menu),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
|
||||
channel.channelType?.let {
|
||||
ChannelIcon(
|
||||
channelType = it,
|
||||
modifier = Modifier.alpha(0.6f)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = channel.name ?: "Ch #${channel.id}",
|
||||
fontWeight = FontWeight.Medium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Default.KeyboardArrowRight,
|
||||
contentDescription = stringResource(R.string.menu),
|
||||
modifier = Modifier
|
||||
.size(18.dp)
|
||||
.alpha(0.4f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,194 +0,0 @@
|
|||
package chat.revolt.components.screens.chat
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
enum class DoubleDrawerOpenState {
|
||||
Start,
|
||||
Center,
|
||||
End
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
class DoubleDrawerState(
|
||||
var initialValue: DoubleDrawerOpenState = DoubleDrawerOpenState.Center,
|
||||
val confirmStateChange: (DoubleDrawerOpenState) -> Boolean = { true }
|
||||
) {
|
||||
val swipeableState = SwipeableState<DoubleDrawerOpenState>(
|
||||
initialValue = initialValue,
|
||||
animationSpec = spring(),
|
||||
confirmStateChange = confirmStateChange
|
||||
)
|
||||
|
||||
suspend fun focusStart() {
|
||||
swipeableState.animateTo(DoubleDrawerOpenState.Start)
|
||||
}
|
||||
|
||||
suspend fun focusCenter() {
|
||||
swipeableState.animateTo(DoubleDrawerOpenState.Center)
|
||||
}
|
||||
|
||||
suspend fun focusEnd() {
|
||||
swipeableState.animateTo(DoubleDrawerOpenState.End)
|
||||
}
|
||||
|
||||
val isStart: Boolean
|
||||
get() = swipeableState.currentValue == DoubleDrawerOpenState.Start
|
||||
val isCenter: Boolean
|
||||
get() = swipeableState.currentValue == DoubleDrawerOpenState.Center
|
||||
val isEnd: Boolean
|
||||
get() = swipeableState.currentValue == DoubleDrawerOpenState.End
|
||||
|
||||
val currentValue: DoubleDrawerOpenState
|
||||
get() = swipeableState.currentValue
|
||||
|
||||
companion object {
|
||||
fun Saver(
|
||||
confirmStateChange: (DoubleDrawerOpenState) -> Boolean
|
||||
): Saver<DoubleDrawerState, DoubleDrawerOpenState> = Saver(
|
||||
save = { it.currentValue },
|
||||
restore = { DoubleDrawerState(it, confirmStateChange) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberDoubleDrawerState(
|
||||
initialValue: DoubleDrawerOpenState = DoubleDrawerOpenState.Center,
|
||||
confirmStateChange: (DoubleDrawerOpenState) -> Boolean = { true }
|
||||
): DoubleDrawerState = rememberSaveable(
|
||||
saver = DoubleDrawerState.Saver(confirmStateChange)
|
||||
) {
|
||||
DoubleDrawerState(initialValue, confirmStateChange)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun DoubleDrawer(
|
||||
state: DoubleDrawerState = rememberDoubleDrawerState(),
|
||||
startPanel: @Composable () -> Unit,
|
||||
endPanel: @Composable () -> Unit,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
|
||||
BoxWithConstraints(Modifier.fillMaxSize()) {
|
||||
val isPortrait =
|
||||
LocalContext.current.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
|
||||
val drawerWeight =
|
||||
if (isPortrait) 0.9f else 0.8f
|
||||
|
||||
val offsetValue =
|
||||
(constraints.maxWidth * drawerWeight) + (LocalDensity.current.run { 16.dp.toPx() })
|
||||
val isAtOffset = abs(state.swipeableState.offset.value) == abs(offsetValue)
|
||||
|
||||
val contentCornerRadius by animateDpAsState(
|
||||
targetValue = if (isAtOffset) 16.dp else 0.dp,
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.swipeable(
|
||||
state = state.swipeableState,
|
||||
orientation = Orientation.Horizontal,
|
||||
velocityThreshold = 500.dp,
|
||||
anchors = mapOf(
|
||||
offsetValue to DoubleDrawerOpenState.Start,
|
||||
0f to DoubleDrawerOpenState.Center,
|
||||
-offsetValue to DoubleDrawerOpenState.End
|
||||
),
|
||||
reverseDirection = layoutDirection == LayoutDirection.Rtl,
|
||||
resistance = ResistanceConfig(0.5f, 0.5f)
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.fillMaxWidth(drawerWeight)
|
||||
.align(Alignment.CenterStart)
|
||||
.offset {
|
||||
IntOffset(
|
||||
x = state.swipeableState.offset.value.roundToInt() - offsetValue.roundToInt(),
|
||||
y = 0
|
||||
)
|
||||
},
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(RoundedCornerShape(topEnd = 16.dp, bottomEnd = 16.dp))
|
||||
) {
|
||||
startPanel()
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.align(Alignment.Center)
|
||||
.offset {
|
||||
IntOffset(
|
||||
x = state.swipeableState.offset.value.roundToInt(),
|
||||
y = 0
|
||||
)
|
||||
},
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(RoundedCornerShape(contentCornerRadius))
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.fillMaxWidth(drawerWeight)
|
||||
.clip(
|
||||
RoundedCornerShape(
|
||||
topStart = 16.dp,
|
||||
bottomStart = 16.dp
|
||||
)
|
||||
)
|
||||
.align(Alignment.CenterEnd)
|
||||
.offset {
|
||||
IntOffset(
|
||||
x = state.swipeableState.offset.value.roundToInt() + offsetValue.roundToInt(),
|
||||
y = 0
|
||||
)
|
||||
},
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(RoundedCornerShape(topStart = 16.dp, bottomStart = 16.dp))
|
||||
) {
|
||||
endPanel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.rememberScrollState
|
||||
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.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
|
|
@ -23,14 +25,14 @@ import androidx.compose.ui.unit.sp
|
|||
import chat.revolt.R
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.schemas.ChannelType
|
||||
import chat.revolt.components.screens.chat.DoubleDrawerState
|
||||
import chat.revolt.components.screens.chat.drawer.server.DrawerChannel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun RowScope.ChannelList(
|
||||
serverId: String,
|
||||
drawerState: DoubleDrawerState,
|
||||
drawerState: DrawerState,
|
||||
currentChannel: String?,
|
||||
onChannelClick: (String) -> Unit,
|
||||
onChannelLongClick: (String) -> Unit,
|
||||
|
|
@ -69,7 +71,7 @@ fun RowScope.ChannelList(
|
|||
} ?: false,
|
||||
onClick = {
|
||||
onChannelClick(channel.id ?: return@DrawerChannel)
|
||||
coroutineScope.launch { drawerState.focusCenter() }
|
||||
coroutineScope.launch { drawerState.close() }
|
||||
},
|
||||
onLongClick = {
|
||||
onChannelLongClick(channel.id ?: return@DrawerChannel)
|
||||
|
|
@ -127,7 +129,7 @@ fun RowScope.ChannelList(
|
|||
} ?: true,
|
||||
onClick = {
|
||||
onChannelClick(ch.id ?: return@DrawerChannel)
|
||||
coroutineScope.launch { drawerState.focusCenter() }
|
||||
coroutineScope.launch { drawerState.close() }
|
||||
},
|
||||
onLongClick = {
|
||||
onChannelLongClick(ch.id ?: return@DrawerChannel)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
package chat.revolt.screens.chat
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
|
|
@ -18,11 +17,15 @@ import androidx.compose.foundation.verticalScroll
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.DismissibleDrawerSheet
|
||||
import androidx.compose.material3.DismissibleNavigationDrawer
|
||||
import androidx.compose.material3.DrawerValue
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.material3.rememberDrawerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -33,10 +36,10 @@ import androidx.compose.runtime.snapshotFlow
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
|
@ -52,13 +55,10 @@ import chat.revolt.api.realtime.RealtimeSocket
|
|||
import chat.revolt.components.chat.DisconnectedNotice
|
||||
import chat.revolt.components.generic.UserAvatar
|
||||
import chat.revolt.components.generic.presenceFromStatus
|
||||
import chat.revolt.components.screens.chat.DoubleDrawer
|
||||
import chat.revolt.components.screens.chat.DoubleDrawerOpenState
|
||||
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.chat.rememberDoubleDrawerState
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import chat.revolt.screens.chat.dialogs.safety.ReportMessageDialog
|
||||
import chat.revolt.screens.chat.sheets.AddServerSheet
|
||||
|
|
@ -168,10 +168,14 @@ class ChatRouterViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialNavigationApi::class, ExperimentalComposeUiApi::class)
|
||||
@OptIn(
|
||||
ExperimentalMaterialNavigationApi::class,
|
||||
ExperimentalComposeUiApi::class,
|
||||
ExperimentalMaterial3Api::class
|
||||
)
|
||||
@Composable
|
||||
fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hiltViewModel()) {
|
||||
val drawerState = rememberDoubleDrawerState()
|
||||
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
||||
val scope = rememberCoroutineScope()
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
|
||||
|
|
@ -184,11 +188,17 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil
|
|||
composition = sidebarSparkComposition,
|
||||
)
|
||||
|
||||
BackHandler(enabled = drawerState.isClosed) {
|
||||
scope.launch {
|
||||
drawerState.open()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(drawerState) {
|
||||
snapshotFlow { drawerState.currentValue }
|
||||
.distinctUntilChanged()
|
||||
.collect { state ->
|
||||
if (state != DoubleDrawerOpenState.Center) {
|
||||
if (state == DrawerValue.Closed) {
|
||||
keyboardController?.hide()
|
||||
}
|
||||
}
|
||||
|
|
@ -274,165 +284,167 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil
|
|||
})
|
||||
}
|
||||
|
||||
DoubleDrawer(
|
||||
state = drawerState,
|
||||
startPanel = {
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
Row {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
UserAvatar(
|
||||
username = RevoltAPI.userCache[RevoltAPI.selfId]?.username
|
||||
?: "",
|
||||
presence = presenceFromStatus(
|
||||
RevoltAPI.userCache[RevoltAPI.selfId]?.status?.presence
|
||||
?: ""
|
||||
),
|
||||
userId = RevoltAPI.selfId ?: "",
|
||||
avatar = RevoltAPI.userCache[RevoltAPI.selfId]?.avatar,
|
||||
size = 48.dp,
|
||||
presenceSize = 16.dp,
|
||||
onClick = {
|
||||
viewModel.navigateToServer("home", navController)
|
||||
},
|
||||
onLongClick = {
|
||||
navController.navigate("status")
|
||||
},
|
||||
DismissibleNavigationDrawer(
|
||||
drawerState = drawerState,
|
||||
drawerContent = {
|
||||
DismissibleDrawerSheet(
|
||||
drawerContainerColor = Color.Transparent,
|
||||
) {
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
Row {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.size(48.dp)
|
||||
)
|
||||
|
||||
ServerDrawerSeparator()
|
||||
|
||||
RevoltAPI.serverCache.values
|
||||
.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),
|
||||
) {
|
||||
viewModel.navigateToServer(
|
||||
server.id,
|
||||
navController
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DrawerServerlikeIcon(
|
||||
onClick = {
|
||||
navController.navigate("add_server")
|
||||
}
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Add,
|
||||
contentDescription = stringResource(id = R.string.server_plus_alt),
|
||||
modifier = Modifier.padding(4.dp)
|
||||
UserAvatar(
|
||||
username = RevoltAPI.userCache[RevoltAPI.selfId]?.username
|
||||
?: "",
|
||||
presence = presenceFromStatus(
|
||||
RevoltAPI.userCache[RevoltAPI.selfId]?.status?.presence
|
||||
?: ""
|
||||
),
|
||||
userId = RevoltAPI.selfId ?: "",
|
||||
avatar = RevoltAPI.userCache[RevoltAPI.selfId]?.avatar,
|
||||
size = 48.dp,
|
||||
presenceSize = 16.dp,
|
||||
onClick = {
|
||||
viewModel.navigateToServer("home", navController)
|
||||
},
|
||||
onLongClick = {
|
||||
navController.navigate("status")
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.size(48.dp)
|
||||
)
|
||||
|
||||
ServerDrawerSeparator()
|
||||
|
||||
RevoltAPI.serverCache.values
|
||||
.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
|
||||
),
|
||||
) {
|
||||
viewModel.navigateToServer(
|
||||
server.id,
|
||||
navController
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DrawerServerlikeIcon(
|
||||
onClick = {
|
||||
navController.navigate("add_server")
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Add,
|
||||
contentDescription = stringResource(id = R.string.server_plus_alt),
|
||||
modifier = Modifier.padding(4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Crossfade(
|
||||
targetState = viewModel.currentServer,
|
||||
label = "Channel List"
|
||||
) {
|
||||
ChannelList(
|
||||
serverId = it,
|
||||
drawerState = drawerState,
|
||||
currentChannel = viewModel.currentChannel,
|
||||
onChannelClick = { channelId ->
|
||||
viewModel.navigateToChannel(channelId, navController)
|
||||
},
|
||||
onChannelLongClick = { channelId ->
|
||||
navController.navigate("channel/$channelId/info")
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Crossfade(
|
||||
targetState = viewModel.currentServer,
|
||||
label = "Channel List"
|
||||
) {
|
||||
ChannelList(
|
||||
serverId = it,
|
||||
drawerState = drawerState,
|
||||
currentChannel = viewModel.currentChannel,
|
||||
onChannelClick = { channelId ->
|
||||
viewModel.navigateToChannel(channelId, navController)
|
||||
},
|
||||
onChannelLongClick = { channelId ->
|
||||
navController.navigate("channel/$channelId/info")
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
endPanel = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp))
|
||||
.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(text = "👋", fontSize = 64.sp)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
NavHost(navController = navController, startDestination = "home") {
|
||||
composable("home") {
|
||||
HomeScreen(navController = topNav)
|
||||
}
|
||||
composable("channel/{channelId}") { backStackEntry ->
|
||||
val channelId = backStackEntry.arguments?.getString("channelId")
|
||||
if (channelId != null) {
|
||||
ChannelScreen(
|
||||
navController = navController,
|
||||
channelId = channelId
|
||||
)
|
||||
content = {
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
NavHost(navController = navController, startDestination = "home") {
|
||||
composable("home") {
|
||||
HomeScreen(navController = topNav)
|
||||
}
|
||||
composable("channel/{channelId}") { backStackEntry ->
|
||||
val channelId = backStackEntry.arguments?.getString("channelId")
|
||||
if (channelId != null) {
|
||||
ChannelScreen(
|
||||
navController = navController,
|
||||
channelId = channelId,
|
||||
onToggleDrawer = {
|
||||
scope.launch {
|
||||
if (drawerState.isOpen) drawerState.close()
|
||||
else drawerState.open()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
composable("no_current_channel") {
|
||||
NoCurrentChannelScreen()
|
||||
}
|
||||
}
|
||||
composable("no_current_channel") {
|
||||
NoCurrentChannelScreen()
|
||||
}
|
||||
|
||||
bottomSheet("channel/{channelId}/info") { backStackEntry ->
|
||||
val channelId = backStackEntry.arguments?.getString("channelId")
|
||||
if (channelId != null) {
|
||||
ChannelInfoSheet(
|
||||
navController = navController,
|
||||
channelId = channelId
|
||||
)
|
||||
bottomSheet("channel/{channelId}/info") { backStackEntry ->
|
||||
val channelId = backStackEntry.arguments?.getString("channelId")
|
||||
if (channelId != null) {
|
||||
ChannelInfoSheet(
|
||||
navController = navController,
|
||||
channelId = channelId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
bottomSheet("channel/{channelId}/menu") { backStackEntry ->
|
||||
val channelId = backStackEntry.arguments?.getString("channelId")
|
||||
if (channelId != null) {
|
||||
ChannelContextSheet(
|
||||
navController = navController,
|
||||
channelId = channelId
|
||||
)
|
||||
bottomSheet("channel/{channelId}/menu") { backStackEntry ->
|
||||
val channelId = backStackEntry.arguments?.getString("channelId")
|
||||
if (channelId != null) {
|
||||
ChannelContextSheet(
|
||||
navController = navController,
|
||||
channelId = channelId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
bottomSheet("message/{messageId}/menu") { backStackEntry ->
|
||||
val messageId = backStackEntry.arguments?.getString("messageId")
|
||||
if (messageId != null) {
|
||||
MessageContextSheet(
|
||||
navController = navController,
|
||||
messageId = messageId
|
||||
)
|
||||
bottomSheet("message/{messageId}/menu") { backStackEntry ->
|
||||
val messageId = backStackEntry.arguments?.getString("messageId")
|
||||
if (messageId != null) {
|
||||
MessageContextSheet(
|
||||
navController = navController,
|
||||
messageId = messageId
|
||||
)
|
||||
}
|
||||
}
|
||||
bottomSheet("status") {
|
||||
StatusSheet(navController = navController, topNav = topNav)
|
||||
}
|
||||
bottomSheet("add_server") {
|
||||
AddServerSheet()
|
||||
}
|
||||
}
|
||||
bottomSheet("status") {
|
||||
StatusSheet(navController = navController, topNav = topNav)
|
||||
}
|
||||
bottomSheet("add_server") {
|
||||
AddServerSheet()
|
||||
}
|
||||
|
||||
dialog("report/message/{messageId}") { backStackEntry ->
|
||||
val messageId = backStackEntry.arguments?.getString("messageId")
|
||||
if (messageId != null) {
|
||||
ReportMessageDialog(
|
||||
navController = navController,
|
||||
messageId = messageId
|
||||
)
|
||||
dialog("report/message/{messageId}") { backStackEntry ->
|
||||
val messageId = backStackEntry.arguments?.getString("messageId")
|
||||
if (messageId != null) {
|
||||
ReportMessageDialog(
|
||||
navController = navController,
|
||||
messageId = messageId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,26 @@
|
|||
package chat.revolt.screens.chat.sheets
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
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.List
|
||||
import androidx.compose.material.icons.filled.Notifications
|
||||
import androidx.compose.material3.Icon
|
||||
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.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import chat.revolt.R
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.schemas.ChannelType
|
||||
import chat.revolt.components.generic.PageHeader
|
||||
import chat.revolt.components.screens.chat.ChannelIcon
|
||||
import chat.revolt.components.generic.SheetClickable
|
||||
|
||||
@Composable
|
||||
fun ChannelInfoSheet(
|
||||
|
|
@ -33,20 +38,8 @@ fun ChannelInfoSheet(
|
|||
.padding(horizontal = 16.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
ChannelIcon(
|
||||
channelType = channel.channelType ?: ChannelType.TextChannel,
|
||||
modifier = Modifier.size(32.dp)
|
||||
)
|
||||
PageHeader(
|
||||
text = channel.name ?: channel.id ?: "",
|
||||
modifier = Modifier.offset((-4).dp, 0.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.channel_info_sheet_description),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
|
|
@ -57,5 +50,69 @@ fun ChannelInfoSheet(
|
|||
?: stringResource(id = R.string.channel_info_sheet_description_empty),
|
||||
modifier = Modifier.padding(bottom = 10.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.channel_info_sheet_options),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(bottom = 4.dp)
|
||||
)
|
||||
|
||||
SheetClickable(
|
||||
icon = { modifier ->
|
||||
Icon(
|
||||
imageVector = Icons.Default.List,
|
||||
contentDescription = null,
|
||||
modifier = modifier
|
||||
)
|
||||
},
|
||||
label = { style ->
|
||||
Text(
|
||||
text = stringResource(id = R.string.channel_info_sheet_options_members),
|
||||
style = style
|
||||
)
|
||||
}
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
SheetClickable(
|
||||
icon = { modifier ->
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = null,
|
||||
modifier = modifier
|
||||
)
|
||||
},
|
||||
label = { style ->
|
||||
Text(
|
||||
text = stringResource(id = R.string.channel_info_sheet_options_invite),
|
||||
style = style
|
||||
)
|
||||
}
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
SheetClickable(
|
||||
icon = { modifier ->
|
||||
Icon(
|
||||
imageVector = Icons.Default.Notifications,
|
||||
contentDescription = null,
|
||||
modifier = modifier
|
||||
)
|
||||
},
|
||||
label = { style ->
|
||||
Text(
|
||||
text = stringResource(id = R.string.channel_info_sheet_options_notifications_manage),
|
||||
style = style
|
||||
)
|
||||
}
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
|
|
@ -4,13 +4,10 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
|||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material3.*
|
||||
|
|
@ -21,7 +18,6 @@ import androidx.compose.ui.draw.clip
|
|||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
|
|
@ -36,7 +32,7 @@ import chat.revolt.api.routes.microservices.autumn.FileArgs
|
|||
import chat.revolt.components.chat.Message
|
||||
import chat.revolt.components.chat.MessageField
|
||||
import chat.revolt.components.screens.chat.AttachmentManager
|
||||
import chat.revolt.components.screens.chat.ChannelIcon
|
||||
import chat.revolt.components.screens.chat.ChannelHeader
|
||||
import chat.revolt.components.screens.chat.ReplyManager
|
||||
import chat.revolt.components.screens.chat.TypingIndicator
|
||||
import chat.revolt.internals.markdown.ChannelMentionRule
|
||||
|
|
@ -58,6 +54,7 @@ import java.io.File
|
|||
fun ChannelScreen(
|
||||
navController: NavController,
|
||||
channelId: String,
|
||||
onToggleDrawer: () -> Unit,
|
||||
viewModel: ChannelScreenViewModel = viewModel()
|
||||
) {
|
||||
val channel = viewModel.channel
|
||||
|
|
@ -117,34 +114,14 @@ fun ChannelScreen(
|
|||
}
|
||||
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
navController.navigate("channel/${channel.id}/info")
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.clip(
|
||||
RoundedCornerShape(
|
||||
bottomStart = 16.dp,
|
||||
bottomEnd = 16.dp
|
||||
)
|
||||
)
|
||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
ChannelIcon(
|
||||
channelType = channel.channelType,
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
)
|
||||
Text(
|
||||
text = channel.name ?: channel.id!!,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
|
||||
ChannelHeader(
|
||||
channel = channel,
|
||||
onChannelClick = {
|
||||
navController.navigate("channel/${channel.id}/info")
|
||||
},
|
||||
onToggleDrawer = onToggleDrawer
|
||||
)
|
||||
|
||||
val isScrolledToBottom = remember(lazyListState) {
|
||||
derivedStateOf {
|
||||
lazyListState.firstVisibleItemIndex <= 6
|
||||
|
|
@ -189,41 +166,49 @@ fun ChannelScreen(
|
|||
items = viewModel.renderableMessages,
|
||||
key = { it.id!! }
|
||||
) { message ->
|
||||
Message(message, parse = {
|
||||
val parser = MarkdownParser()
|
||||
.addRules(
|
||||
SimpleMarkdownRules.createEscapeRule(),
|
||||
UserMentionRule(),
|
||||
ChannelMentionRule(),
|
||||
CustomEmoteRule(),
|
||||
)
|
||||
.addRules(
|
||||
createCodeRule(context, codeBlockColor.toArgb()),
|
||||
createInlineCodeRule(context, codeBlockColor.toArgb()),
|
||||
)
|
||||
.addRules(
|
||||
SimpleMarkdownRules.createSimpleMarkdownRules(
|
||||
includeEscapeRule = false
|
||||
Message(
|
||||
message,
|
||||
parse = {
|
||||
val parser = MarkdownParser()
|
||||
.addRules(
|
||||
SimpleMarkdownRules.createEscapeRule(),
|
||||
UserMentionRule(),
|
||||
ChannelMentionRule(),
|
||||
CustomEmoteRule(),
|
||||
)
|
||||
.addRules(
|
||||
createCodeRule(context, codeBlockColor.toArgb()),
|
||||
createInlineCodeRule(context, codeBlockColor.toArgb()),
|
||||
)
|
||||
.addRules(
|
||||
SimpleMarkdownRules.createSimpleMarkdownRules(
|
||||
includeEscapeRule = false
|
||||
)
|
||||
)
|
||||
|
||||
SimpleRenderer.render(
|
||||
source = it.content ?: "",
|
||||
parser = parser,
|
||||
initialState = MarkdownState(0),
|
||||
renderContext = MarkdownContext(
|
||||
memberMap = mapOf(),
|
||||
userMap = RevoltAPI.userCache.toMap(),
|
||||
channelMap = RevoltAPI.channelCache.mapValues { ch ->
|
||||
ch.value.name ?: ch.value.id ?: "#DeletedChannel"
|
||||
},
|
||||
emojiMap = RevoltAPI.emojiCache,
|
||||
serverId = channel.server ?: "",
|
||||
)
|
||||
)
|
||||
|
||||
SimpleRenderer.render(
|
||||
source = it.content ?: "",
|
||||
parser = parser,
|
||||
initialState = MarkdownState(0),
|
||||
renderContext = MarkdownContext(
|
||||
memberMap = mapOf(),
|
||||
userMap = RevoltAPI.userCache.toMap(),
|
||||
channelMap = RevoltAPI.channelCache.mapValues { ch ->
|
||||
ch.value.name ?: ch.value.id ?: "#DeletedChannel"
|
||||
},
|
||||
emojiMap = RevoltAPI.emojiCache,
|
||||
serverId = channel.server ?: "",
|
||||
)
|
||||
)
|
||||
}) {
|
||||
navController.navigate("message/${message.id}/menu")
|
||||
}
|
||||
},
|
||||
onMessageContextMenu = {
|
||||
navController.navigate("message/${message.id}/menu")
|
||||
},
|
||||
canReply = true,
|
||||
onReply = {
|
||||
viewModel.replyToMessage(message)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
|
|
|
|||
|
|
@ -358,4 +358,13 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
Log.d("ChannelScreen", "Acking channel")
|
||||
}
|
||||
}
|
||||
|
||||
fun replyToMessage(message: Message) {
|
||||
addInReplyTo(
|
||||
SendMessageReply(
|
||||
id = message.id!!,
|
||||
mention = false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,6 +149,10 @@
|
|||
|
||||
<string name="channel_info_sheet_description">Channel description</string>
|
||||
<string name="channel_info_sheet_description_empty">There hasn\'t been a description set for this channel yet.</string>
|
||||
<string name="channel_info_sheet_options">Options</string>
|
||||
<string name="channel_info_sheet_options_members">Members</string>
|
||||
<string name="channel_info_sheet_options_invite">Invite</string>
|
||||
<string name="channel_info_sheet_options_notifications_manage">Manage notifications</string>
|
||||
|
||||
<string name="message_context_sheet_actions_copy">Copy</string>
|
||||
<string name="message_context_sheet_actions_copy_failed_empty">Message is empty, nothing to copy</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue