feat: revamp message field, colours, typing, attachments
This commit is contained in:
parent
e1abc4fff5
commit
fcddb8a968
|
|
@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import chat.revolt.api.settings.GlobalState
|
import chat.revolt.api.settings.GlobalState
|
||||||
|
|
@ -43,6 +44,7 @@ class MainActivity : ComponentActivity() {
|
||||||
val RevoltTweenInt: FiniteAnimationSpec<IntOffset> = tween(400, easing = EaseInOutExpo)
|
val RevoltTweenInt: FiniteAnimationSpec<IntOffset> = tween(400, easing = EaseInOutExpo)
|
||||||
val RevoltTweenIntSize: FiniteAnimationSpec<IntSize> = tween(400, easing = EaseInOutExpo)
|
val RevoltTweenIntSize: FiniteAnimationSpec<IntSize> = tween(400, easing = EaseInOutExpo)
|
||||||
val RevoltTweenFloat: FiniteAnimationSpec<Float> = tween(400, easing = EaseInOutExpo)
|
val RevoltTweenFloat: FiniteAnimationSpec<Float> = tween(400, easing = EaseInOutExpo)
|
||||||
|
val RevoltTweenDp: FiniteAnimationSpec<Dp> = tween(400, easing = EaseInOutExpo)
|
||||||
|
|
||||||
@OptIn(ExperimentalAnimationApi::class)
|
@OptIn(ExperimentalAnimationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,37 @@
|
||||||
package chat.revolt.components.chat
|
package chat.revolt.components.chat
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.*
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
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.KeyboardArrowRight
|
|
||||||
import androidx.compose.material.icons.filled.Send
|
import androidx.compose.material.icons.filled.Send
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.revolt.R
|
import chat.revolt.R
|
||||||
|
import chat.revolt.RevoltTweenFloat
|
||||||
|
import chat.revolt.RevoltTweenInt
|
||||||
import chat.revolt.api.schemas.ChannelType
|
import chat.revolt.api.schemas.ChannelType
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MessageField(
|
fun MessageField(
|
||||||
showButtons: Boolean,
|
|
||||||
onToggleButtons: (Boolean) -> Unit,
|
|
||||||
messageContent: String,
|
messageContent: String,
|
||||||
onMessageContentChange: (String) -> Unit,
|
onMessageContentChange: (String) -> Unit,
|
||||||
onAddAttachment: () -> Unit,
|
onAddAttachment: () -> Unit,
|
||||||
|
|
@ -46,90 +51,85 @@ fun MessageField(
|
||||||
ChannelType.SavedMessages -> R.string.message_field_placeholder_notes
|
ChannelType.SavedMessages -> R.string.message_field_placeholder_notes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val sendButtonVisible = (messageContent.isNotBlank() || forceSendButton) && !disabled
|
||||||
|
|
||||||
Row(modifier) {
|
Row(modifier) {
|
||||||
// Additional buttons (currently adding an attachment)
|
BasicTextField(
|
||||||
AnimatedVisibility(visible = showButtons) {
|
|
||||||
Row(Modifier.weight(1f)) {
|
|
||||||
ElevatedButton(
|
|
||||||
onClick = {
|
|
||||||
focusRequester.freeFocus() // hide keyboard because it's annoying
|
|
||||||
onAddAttachment()
|
|
||||||
},
|
|
||||||
modifier = Modifier.size(56.dp),
|
|
||||||
contentPadding = PaddingValues(0.dp),
|
|
||||||
shape = CircleShape
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.Add,
|
|
||||||
contentDescription = stringResource(id = R.string.add_attachment_alt)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The small chevron you see when the buttons are hidden
|
|
||||||
AnimatedVisibility(visible = !showButtons) {
|
|
||||||
Column(
|
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
modifier = Modifier.height(56.dp)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.KeyboardArrowRight,
|
|
||||||
contentDescription = stringResource(id = R.string.show_more_alt),
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(onClick = {
|
|
||||||
onToggleButtons(true)
|
|
||||||
})
|
|
||||||
.size(24.dp + 8.dp)
|
|
||||||
.padding(vertical = 4.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextField(
|
|
||||||
value = messageContent,
|
value = messageContent,
|
||||||
onValueChange = onMessageContentChange,
|
onValueChange = onMessageContentChange,
|
||||||
singleLine = false,
|
singleLine = false,
|
||||||
shape = MaterialTheme.shapes.extraLarge,
|
|
||||||
enabled = !disabled,
|
enabled = !disabled,
|
||||||
placeholder = {
|
textStyle = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.onSurface),
|
||||||
Text(
|
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
|
||||||
stringResource(
|
|
||||||
id = placeholderResource,
|
|
||||||
channelName
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
colors = TextFieldDefaults.textFieldColors(
|
|
||||||
focusedIndicatorColor = Color.Transparent,
|
|
||||||
unfocusedIndicatorColor = Color.Transparent,
|
|
||||||
disabledIndicatorColor = Color.Transparent,
|
|
||||||
errorIndicatorColor = Color.Transparent,
|
|
||||||
placeholderColor = Color.Gray,
|
|
||||||
),
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(start = 8.dp)
|
.focusRequester(focusRequester),
|
||||||
.focusRequester(focusRequester)
|
keyboardOptions = KeyboardOptions.Default,
|
||||||
)
|
keyboardActions = KeyboardActions.Default,
|
||||||
|
decorationBox = @Composable { innerTextField ->
|
||||||
// Send button (visible when text is entered or when forceSendButton is true)
|
TextFieldDefaults.TextFieldDecorationBox(
|
||||||
AnimatedVisibility(visible = (messageContent.isNotBlank() || forceSendButton) && !disabled) {
|
value = messageContent,
|
||||||
Button(
|
innerTextField = innerTextField,
|
||||||
onClick = onSendMessage,
|
enabled = !disabled,
|
||||||
modifier = Modifier
|
singleLine = false,
|
||||||
.padding(start = 8.dp)
|
visualTransformation = VisualTransformation.None,
|
||||||
.size(56.dp),
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
contentPadding = PaddingValues(0.dp),
|
placeholder = {
|
||||||
shape = CircleShape
|
Text(
|
||||||
) {
|
stringResource(
|
||||||
Icon(
|
id = placeholderResource,
|
||||||
Icons.Default.Send,
|
channelName
|
||||||
contentDescription = stringResource(id = R.string.send_alt)
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
colors = TextFieldDefaults.textFieldColors(
|
||||||
|
focusedIndicatorColor = Color.Transparent,
|
||||||
|
unfocusedIndicatorColor = Color.Transparent,
|
||||||
|
disabledIndicatorColor = Color.Transparent,
|
||||||
|
errorIndicatorColor = Color.Transparent,
|
||||||
|
placeholderColor = Color.Gray
|
||||||
|
),
|
||||||
|
contentPadding = PaddingValues(16.dp),
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Add,
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f),
|
||||||
|
contentDescription = stringResource(id = R.string.unknown),
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.size(32.dp)
|
||||||
|
.clickable {
|
||||||
|
focusRequester.freeFocus() // hide keyboard because it's annoying
|
||||||
|
onAddAttachment()
|
||||||
|
}
|
||||||
|
.padding(4.dp)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailingIcon = {
|
||||||
|
AnimatedVisibility(sendButtonVisible,
|
||||||
|
enter = slideInVertically(
|
||||||
|
animationSpec = RevoltTweenInt,
|
||||||
|
initialOffsetY = { it }
|
||||||
|
) + fadeIn(animationSpec = RevoltTweenFloat),
|
||||||
|
exit = slideOutVertically(
|
||||||
|
animationSpec = RevoltTweenInt,
|
||||||
|
targetOffsetY = { it }
|
||||||
|
) + fadeOut(animationSpec = RevoltTweenFloat)) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Send,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
contentDescription = stringResource(id = R.string.unknown),
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.size(32.dp)
|
||||||
|
.clickable { onSendMessage() }
|
||||||
|
.padding(4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,8 +137,6 @@ fun MessageField(
|
||||||
@Composable
|
@Composable
|
||||||
fun MessageFieldPreview() {
|
fun MessageFieldPreview() {
|
||||||
MessageField(
|
MessageField(
|
||||||
showButtons = true,
|
|
||||||
onToggleButtons = {},
|
|
||||||
messageContent = "Hello world!",
|
messageContent = "Hello world!",
|
||||||
onMessageContentChange = {},
|
onMessageContentChange = {},
|
||||||
onSendMessage = {},
|
onSendMessage = {},
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,17 @@
|
||||||
package chat.revolt.components.screens.chat
|
package chat.revolt.components.screens.chat
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.revolt.R
|
import chat.revolt.R
|
||||||
|
|
@ -30,24 +25,30 @@ fun AttachmentManager(
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
|
||||||
.horizontalScroll(rememberScrollState())
|
.horizontalScroll(rememberScrollState())
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
) {
|
) {
|
||||||
AnimatedVisibility(uploading) {
|
AnimatedVisibility(uploading) {
|
||||||
CircularProgressIndicator()
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.padding(4.dp),
|
||||||
|
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
attachments.forEach { attachment ->
|
attachments.forEach { attachment ->
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(4.dp)
|
.padding(4.dp)
|
||||||
.border(
|
.clip(MaterialTheme.shapes.small)
|
||||||
1.dp,
|
|
||||||
MaterialTheme.colorScheme.onBackground.copy(alpha = 0.3f),
|
|
||||||
MaterialTheme.shapes.small
|
|
||||||
)
|
|
||||||
.clickable {
|
.clickable {
|
||||||
onRemove(attachment)
|
onRemove(attachment)
|
||||||
}
|
}
|
||||||
|
.background(
|
||||||
|
color = MaterialTheme.colorScheme.background,
|
||||||
|
shape = MaterialTheme.shapes.small
|
||||||
|
)
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
) {
|
) {
|
||||||
Text(attachment.filename, maxLines = 1)
|
Text(attachment.filename, maxLines = 1)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
|
@ -26,7 +27,10 @@ fun DrawerChannel(
|
||||||
.padding(vertical = 4.dp, horizontal = 8.dp)
|
.padding(vertical = 4.dp, horizontal = 8.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clip(MaterialTheme.shapes.medium)
|
.clip(MaterialTheme.shapes.medium)
|
||||||
.background(if (selected) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.surfaceVariant)
|
.background(
|
||||||
|
if (selected) MaterialTheme.colorScheme.surface
|
||||||
|
else MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)
|
||||||
|
)
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
.padding(vertical = 8.dp, horizontal = 16.dp)
|
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.size
|
||||||
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.material3.Text
|
||||||
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
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
|
||||||
|
|
@ -41,7 +42,7 @@ fun DrawerServer(
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
.size(48.dp)
|
.size(48.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp))
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ fun DrawerServerlikeIcon(
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
.size(48.dp)
|
.size(48.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
.background(MaterialTheme.colorScheme.surface)
|
||||||
) {
|
) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package chat.revolt.components.screens.chat
|
||||||
|
|
||||||
|
import androidx.compose.animation.*
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import chat.revolt.R
|
||||||
|
import chat.revolt.RevoltTweenFloat
|
||||||
|
import chat.revolt.RevoltTweenInt
|
||||||
|
import chat.revolt.api.RevoltAPI
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TypingIndicator(
|
||||||
|
users: List<String>,
|
||||||
|
) {
|
||||||
|
fun typingMessageResource(): Int {
|
||||||
|
return when (users.size) {
|
||||||
|
0 -> R.string.typing_blank
|
||||||
|
1 -> R.string.typing_one
|
||||||
|
in 2..4 -> R.string.typing_many
|
||||||
|
else -> R.string.typing_several
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = users.isNotEmpty(),
|
||||||
|
enter = slideInVertically(
|
||||||
|
animationSpec = RevoltTweenInt,
|
||||||
|
initialOffsetY = { it }
|
||||||
|
) + fadeIn(animationSpec = RevoltTweenFloat),
|
||||||
|
exit = slideOutVertically(
|
||||||
|
animationSpec = RevoltTweenInt,
|
||||||
|
targetOffsetY = { it }
|
||||||
|
) + fadeOut(animationSpec = RevoltTweenFloat)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.background(MaterialTheme.colorScheme.surface)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(all = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(
|
||||||
|
id = typingMessageResource(),
|
||||||
|
users.joinToString {
|
||||||
|
RevoltAPI.userCache[it]?.let { u ->
|
||||||
|
u.username ?: u.id
|
||||||
|
} ?: it
|
||||||
|
}
|
||||||
|
),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -85,14 +85,16 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = vie
|
||||||
DismissibleNavigationDrawer(
|
DismissibleNavigationDrawer(
|
||||||
drawerState = channelDrawerState,
|
drawerState = channelDrawerState,
|
||||||
drawerContent = {
|
drawerContent = {
|
||||||
ModalDrawerSheet(drawerContainerColor = MaterialTheme.colorScheme.surfaceVariant) {
|
ModalDrawerSheet(
|
||||||
|
drawerContainerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)
|
||||||
|
) {
|
||||||
Column(Modifier.fillMaxWidth()) {
|
Column(Modifier.fillMaxWidth()) {
|
||||||
Row {
|
Row {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.background(MaterialTheme.colorScheme.surface)
|
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp))
|
||||||
) {
|
) {
|
||||||
DrawerServerlikeIcon(
|
DrawerServerlikeIcon(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import android.widget.Toast
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.animation.*
|
import androidx.compose.animation.*
|
||||||
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
|
@ -24,7 +25,6 @@ import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
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.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
|
@ -34,6 +34,7 @@ import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import chat.revolt.R
|
import chat.revolt.R
|
||||||
|
import chat.revolt.RevoltTweenDp
|
||||||
import chat.revolt.RevoltTweenFloat
|
import chat.revolt.RevoltTweenFloat
|
||||||
import chat.revolt.RevoltTweenInt
|
import chat.revolt.RevoltTweenInt
|
||||||
import chat.revolt.api.RevoltAPI
|
import chat.revolt.api.RevoltAPI
|
||||||
|
|
@ -55,6 +56,7 @@ import chat.revolt.components.generic.CollapsibleCard
|
||||||
import chat.revolt.components.generic.PageHeader
|
import chat.revolt.components.generic.PageHeader
|
||||||
import chat.revolt.components.screens.chat.AttachmentManager
|
import chat.revolt.components.screens.chat.AttachmentManager
|
||||||
import chat.revolt.components.screens.chat.ChannelIcon
|
import chat.revolt.components.screens.chat.ChannelIcon
|
||||||
|
import chat.revolt.components.screens.chat.TypingIndicator
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.datetime.Instant
|
import kotlinx.datetime.Instant
|
||||||
|
|
@ -89,20 +91,6 @@ class ChannelScreenViewModel : ViewModel() {
|
||||||
|
|
||||||
fun setMessageContent(content: String) {
|
fun setMessageContent(content: String) {
|
||||||
_messageContent = content
|
_messageContent = content
|
||||||
|
|
||||||
if (content.isEmpty()) {
|
|
||||||
_showButtons = true
|
|
||||||
} else if (showButtons) {
|
|
||||||
_showButtons = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var _showButtons by mutableStateOf(true)
|
|
||||||
val showButtons: Boolean
|
|
||||||
get() = _showButtons
|
|
||||||
|
|
||||||
fun setShowButtons(show: Boolean) {
|
|
||||||
_showButtons = show
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var _attachments = mutableStateListOf<FileArgs>()
|
private var _attachments = mutableStateListOf<FileArgs>()
|
||||||
|
|
@ -262,23 +250,6 @@ class ChannelScreenViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun typingMessageResource(): Int {
|
|
||||||
return when (typingUsers.size) {
|
|
||||||
0 -> R.string.typing_blank
|
|
||||||
1 -> R.string.typing_one
|
|
||||||
in 2..4 -> R.string.typing_many
|
|
||||||
else -> R.string.typing_several
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getTypingUsernames(): String {
|
|
||||||
return typingUsers.joinToString {
|
|
||||||
RevoltAPI.userCache[it]?.let { u ->
|
|
||||||
u.username ?: u.id
|
|
||||||
} ?: it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun regroupMessages(newMessages: List<MessageSchema> = renderableMessages) {
|
private fun regroupMessages(newMessages: List<MessageSchema> = renderableMessages) {
|
||||||
val groupedMessages = arrayListOf<MessageSchema>()
|
val groupedMessages = arrayListOf<MessageSchema>()
|
||||||
|
|
||||||
|
|
@ -430,6 +401,11 @@ fun ChannelScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val scrollDownFABPadding by animateDpAsState(
|
||||||
|
if (viewModel.typingUsers.isNotEmpty()) 32.dp else 0.dp,
|
||||||
|
animationSpec = RevoltTweenDp
|
||||||
|
)
|
||||||
|
|
||||||
LaunchedEffect(channelId) {
|
LaunchedEffect(channelId) {
|
||||||
viewModel.fetchChannel(channelId)
|
viewModel.fetchChannel(channelId)
|
||||||
}
|
}
|
||||||
|
|
@ -488,7 +464,7 @@ fun ChannelScreen(
|
||||||
|
|
||||||
val isScrolledToBottom = remember(lazyListState) {
|
val isScrolledToBottom = remember(lazyListState) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
lazyListState.firstVisibleItemIndex <= 5
|
lazyListState.firstVisibleItemIndex <= 6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -505,6 +481,10 @@ fun ChannelScreen(
|
||||||
contentAlignment = Alignment.BottomEnd
|
contentAlignment = Alignment.BottomEnd
|
||||||
) {
|
) {
|
||||||
LazyColumn(state = lazyListState, reverseLayout = true) {
|
LazyColumn(state = lazyListState, reverseLayout = true) {
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
}
|
||||||
|
|
||||||
items(viewModel.renderableMessages) { message ->
|
items(viewModel.renderableMessages) { message ->
|
||||||
Message(message)
|
Message(message)
|
||||||
}
|
}
|
||||||
|
|
@ -540,6 +520,7 @@ fun ChannelScreen(
|
||||||
) {
|
) {
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.padding(bottom = scrollDownFABPadding)
|
||||||
.align(Alignment.BottomEnd)
|
.align(Alignment.BottomEnd)
|
||||||
.padding(16.dp),
|
.padding(16.dp),
|
||||||
text = {
|
text = {
|
||||||
|
|
@ -560,34 +541,10 @@ fun ChannelScreen(
|
||||||
containerColor = MaterialTheme.colorScheme.primary
|
containerColor = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
AnimatedVisibility(
|
TypingIndicator(
|
||||||
visible = viewModel.typingUsers.isNotEmpty(),
|
users = viewModel.typingUsers
|
||||||
enter = slideInVertically(
|
)
|
||||||
animationSpec = RevoltTweenInt,
|
|
||||||
initialOffsetY = { it }
|
|
||||||
) + fadeIn(animationSpec = RevoltTweenFloat),
|
|
||||||
exit = slideOutVertically(
|
|
||||||
animationSpec = RevoltTweenInt,
|
|
||||||
targetOffsetY = { it }
|
|
||||||
) + fadeOut(animationSpec = RevoltTweenFloat)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
Modifier
|
|
||||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(all = 4.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(
|
|
||||||
id = viewModel.typingMessageResource(),
|
|
||||||
viewModel.getTypingUsernames()
|
|
||||||
),
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatedVisibility(visible = viewModel.attachments.isNotEmpty()) {
|
AnimatedVisibility(visible = viewModel.attachments.isNotEmpty()) {
|
||||||
|
|
@ -599,8 +556,6 @@ fun ChannelScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageField(
|
MessageField(
|
||||||
showButtons = viewModel.showButtons,
|
|
||||||
onToggleButtons = viewModel::setShowButtons,
|
|
||||||
messageContent = viewModel.messageContent,
|
messageContent = viewModel.messageContent,
|
||||||
onMessageContentChange = viewModel::setMessageContent,
|
onMessageContentChange = viewModel::setMessageContent,
|
||||||
onSendMessage = viewModel::sendPendingMessage,
|
onSendMessage = viewModel::sendPendingMessage,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package chat.revolt.screens.settings
|
package chat.revolt.screens.settings
|
||||||
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
|
@ -12,9 +13,11 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.dynamicDarkColorScheme
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
|
@ -75,13 +78,25 @@ fun AppearanceSettingsScreen(
|
||||||
modifier = Modifier.padding(bottom = 10.dp)
|
modifier = Modifier.padding(bottom = 10.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "old revolt blue will come back soon i promise, needs a bit of optimisation first 🐈",
|
||||||
|
style = MaterialTheme.typography.headlineMedium.copy(
|
||||||
|
color = MaterialTheme.colorScheme.background,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = 10.dp)
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
.background(MaterialTheme.colorScheme.primary)
|
||||||
|
.padding(10.dp)
|
||||||
|
)
|
||||||
|
|
||||||
FlowRow(
|
FlowRow(
|
||||||
mainAxisSpacing = 10.dp,
|
mainAxisSpacing = 10.dp,
|
||||||
crossAxisSpacing = 10.dp,
|
crossAxisSpacing = 10.dp,
|
||||||
) {
|
) {
|
||||||
ThemeChip(
|
ThemeChip(
|
||||||
color = Color(0xff172333),
|
color = Color(0xff1e1e1e),
|
||||||
text = stringResource(id = R.string.settings_appearance_theme_revolt),
|
text = stringResource(id = R.string.settings_appearance_theme_revolt),
|
||||||
selected = GlobalState.theme == Theme.Revolt,
|
selected = GlobalState.theme == Theme.Revolt,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
|
|
@ -108,7 +123,7 @@ fun AppearanceSettingsScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
ThemeChip(
|
ThemeChip(
|
||||||
color = if (isSystemInDarkTheme()) Color(0xff172333) else Color(0xfff7f7f7),
|
color = if (isSystemInDarkTheme()) Color(0xff1e1e1e) else Color(0xfff7f7f7),
|
||||||
text = stringResource(id = R.string.settings_appearance_theme_none),
|
text = stringResource(id = R.string.settings_appearance_theme_none),
|
||||||
selected = GlobalState.theme == Theme.None,
|
selected = GlobalState.theme == Theme.None,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
|
|
|
||||||
|
|
@ -14,18 +14,17 @@ import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
|
|
||||||
val RevoltColorScheme = darkColorScheme(
|
val RevoltColorScheme = darkColorScheme(
|
||||||
primary = Color(0xfffe4654),
|
primary = Color(0xffda4e5b),
|
||||||
onPrimary = Color(0xffffffff),
|
onPrimary = Color(0xffffffff),
|
||||||
secondary = Color(0xfffd6671),
|
secondary = Color(0xffe96a7a),
|
||||||
onSecondary = Color(0xffffffff),
|
onSecondary = Color(0xffffffff),
|
||||||
tertiary = Color(0xffff6667),
|
background = Color(0xff121212),
|
||||||
onTertiary = Color(0xffffffff),
|
|
||||||
background = Color(0xff101823),
|
|
||||||
onBackground = Color(0xffffffff),
|
onBackground = Color(0xffffffff),
|
||||||
surfaceVariant = Color(0xff172333),
|
surfaceVariant = Color(0xff1e1e1e),
|
||||||
onSurfaceVariant = Color(0xffffffff),
|
onSurfaceVariant = Color(0xffffffff),
|
||||||
surface = Color(0xff111a26),
|
surface = Color(0xff2b2b2b),
|
||||||
onSurface = Color(0xffffffff),
|
onSurface = Color(0xffffffff),
|
||||||
|
surfaceTint = Color(0xffc0c0c0),
|
||||||
)
|
)
|
||||||
|
|
||||||
val AmoledColorScheme = RevoltColorScheme.copy(
|
val AmoledColorScheme = RevoltColorScheme.copy(
|
||||||
|
|
@ -38,18 +37,17 @@ val AmoledColorScheme = RevoltColorScheme.copy(
|
||||||
)
|
)
|
||||||
|
|
||||||
val LightColorScheme = lightColorScheme(
|
val LightColorScheme = lightColorScheme(
|
||||||
primary = Color(0xfffe4654),
|
primary = Color(0xffda4e5b),
|
||||||
onPrimary = Color(0xffffffff),
|
onPrimary = Color(0xffffffff),
|
||||||
secondary = Color(0xfffd6671),
|
secondary = Color(0xffe96a7a),
|
||||||
onSecondary = Color(0xffffffff),
|
onSecondary = Color(0xffffffff),
|
||||||
tertiary = Color(0xffff6667),
|
|
||||||
onTertiary = Color(0xffffffff),
|
|
||||||
background = Color(0xffffffff),
|
background = Color(0xffffffff),
|
||||||
onBackground = Color(0xff000000),
|
onBackground = Color(0xff000000),
|
||||||
surfaceVariant = Color(0xffe6e6e6),
|
surfaceVariant = Color(0xffe0e0e0),
|
||||||
onSurfaceVariant = Color(0xff000000),
|
onSurfaceVariant = Color(0xff000000),
|
||||||
surface = Color(0xffdddddd),
|
surface = Color(0xfff5f5f5),
|
||||||
onSurface = Color(0xff000000),
|
onSurface = Color(0xff000000),
|
||||||
|
surfaceTint = Color(0xff000000),
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class Theme {
|
enum class Theme {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue