1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

DROID-3251 Space-level chat | Design | Chat UI updates 1 (#2125)

This commit is contained in:
Evgenii Kozlov 2025-02-28 12:04:32 +01:00 committed by GitHub
parent c8c0d0c5e2
commit 43cf0dbc34
Signed by: github
GPG key ID: B5690EEEBB952194
5 changed files with 346 additions and 237 deletions

View file

@ -47,7 +47,7 @@ fun BubbleAttachments(
start = 4.dp,
end = 4.dp,
bottom = 4.dp,
top = if (idx == 0) 4.dp else 0.dp
top = 0.dp
)
.size(300.dp)
.background(
@ -83,7 +83,7 @@ fun BubbleAttachments(
start = 4.dp,
end = 4.dp,
bottom = 4.dp,
top = if (idx == 0) 4.dp else 0.dp
top = 0.dp
)
.fillMaxWidth()
,
@ -110,11 +110,11 @@ fun AttachedObject(
Box(
modifier = modifier
.height(72.dp)
.clip(RoundedCornerShape(18.dp))
.clip(RoundedCornerShape(12.dp))
.border(
width = 1.dp,
color = colorResource(id = R.color.shape_transparent_secondary),
shape = RoundedCornerShape(18.dp)
shape = RoundedCornerShape(12.dp)
)
.background(
color = colorResource(id = R.color.background_secondary)

View file

@ -1,6 +1,7 @@
package com.anytypeio.anytype.feature_chats.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@ -14,6 +15,7 @@ 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.layout.wrapContentHeight
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.DropdownMenu
@ -29,7 +31,9 @@ import androidx.compose.runtime.remember
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.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
@ -60,6 +64,7 @@ import com.anytypeio.anytype.core_ui.foundation.GenericAlert
import com.anytypeio.anytype.core_ui.views.BodyRegular
import com.anytypeio.anytype.core_ui.views.Caption1Medium
import com.anytypeio.anytype.core_ui.views.Caption1Regular
import com.anytypeio.anytype.core_ui.views.Caption2Regular
import com.anytypeio.anytype.core_ui.views.PreviewTitle2Medium
import com.anytypeio.anytype.core_ui.views.fontIBM
import com.anytypeio.anytype.core_utils.const.DateConst.TIME_H24
@ -129,176 +134,306 @@ fun Bubble(
Column(
modifier = modifier
.width(IntrinsicSize.Max)
.background(
color = if (isUserAuthor)
colorResource(R.color.background_primary)
else
colorResource(R.color.shape_transparent_secondary),
shape = RoundedCornerShape(20.dp)
)
.clip(RoundedCornerShape(20.dp))
.clickable {
showDropdownMenu = !showDropdownMenu
}
) {
if (reply != null) {
Box(
Text(
text = reply.author,
modifier = Modifier
.padding(4.dp)
.fillMaxWidth()
.height(52.dp)
.background(
color = colorResource(R.color.shape_transparent_secondary),
shape = RoundedCornerShape(16.dp)
.padding(
start = 16.dp,
top = 8.dp,
end = 12.dp
)
.clip(RoundedCornerShape(16.dp))
.alpha(0.5f),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = colorResource(id = R.color.text_primary),
style = Caption1Medium
)
Spacer(modifier = Modifier.height(2.dp))
Row(
modifier = Modifier
.fillMaxWidth()
.height(IntrinsicSize.Min)
.clickable {
onScrollToReplyClicked(reply)
}
.alpha(0.5f)
) {
Box(
modifier = Modifier
.width(4.dp)
.fillMaxHeight()
.width(4.dp)
.background(
color = colorResource(R.color.shape_transparent_primary)
color = colorResource(R.color.shape_transparent_primary),
shape = RoundedCornerShape(4.dp)
)
)
Text(
text = reply.author,
modifier = Modifier.padding(
start = 12.dp,
top = 8.dp,
end = 12.dp
),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = colorResource(id = R.color.text_primary),
style = Caption1Medium
)
Text(
modifier = Modifier.padding(
start = 12.dp,
top = 26.dp,
end = 12.dp
),
text = reply.text,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = colorResource(id = R.color.text_primary),
style = Caption1Regular
)
Spacer(modifier = Modifier.width(4.dp))
Box(
modifier = Modifier
.fillMaxWidth()
.background(
color = colorResource(R.color.shape_transparent_secondary),
shape = RoundedCornerShape(16.dp)
)
.clip(RoundedCornerShape(16.dp))
.clickable {
onScrollToReplyClicked(reply)
}
.alpha(0.5f)
) {
Text(
modifier = Modifier.padding(
horizontal = 12.dp,
vertical = 8.dp
),
text = reply.text,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
color = colorResource(id = R.color.text_primary),
style = Caption1Regular
)
}
}
Spacer(modifier = Modifier.height(4.dp))
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(
start = 12.dp,
end = 12.dp,
top = if (reply == null) 12.dp else 0.dp
),
horizontalArrangement = Arrangement.SpaceBetween
) {
// Username section
if (!isUserAuthor) {
Text(
text = name,
style = PreviewTitle2Medium,
style = Caption1Medium,
color = colorResource(id = R.color.text_primary),
maxLines = 1
)
Spacer(Modifier.width(12.dp))
Text(
modifier = Modifier.padding(top = 1.dp),
text = timestamp.formatTimeInMillis(
TIME_H24
),
style = Caption1Regular,
color = colorResource(id = R.color.text_secondary),
maxLines = 1
maxLines = 1,
modifier = Modifier
.fillMaxWidth()
.padding(
start = 12.dp,
end = 12.dp
)
)
Spacer(modifier = Modifier.height(4.dp))
}
if (content.msg.isNotEmpty()) {
Text(
modifier = Modifier.padding(
top = 0.dp,
start = 12.dp,
end = 12.dp,
bottom = if (reactions.isEmpty() && attachments.isEmpty()) 12.dp else 0.dp
),
text = buildAnnotatedString {
content.parts.forEach { part ->
if (part.link != null && part.link.param != null) {
withLink(
LinkAnnotation.Clickable(
tag = DEFAULT_MENTION_LINK_TAG,
styles = TextLinkStyles(
// Text with attachments
Column(
modifier = Modifier
.fillMaxWidth()
.background(
color = if (isUserAuthor)
colorResource(R.color.background_primary)
else
colorResource(R.color.shape_transparent_secondary),
shape = RoundedCornerShape(16.dp)
)
.clip(RoundedCornerShape(16.dp))
.clickable {
showDropdownMenu = !showDropdownMenu
}
.padding(vertical = 4.dp)
) {
BubbleAttachments(
attachments = attachments,
isUserAuthor = isUserAuthor,
onAttachmentClicked = onAttachmentClicked
)
if (content.msg.isNotEmpty()) {
Box(
modifier = Modifier.padding(
top = 4.dp,
start = 12.dp,
end = 12.dp,
bottom = 4.dp
)
) {
Text(
modifier = Modifier,
text = buildAnnotatedString {
content.parts.forEach { part ->
if (part.link != null && part.link.param != null) {
withLink(
LinkAnnotation.Clickable(
tag = DEFAULT_MENTION_LINK_TAG,
styles = TextLinkStyles(
style = SpanStyle(
fontWeight = if (part.isBold) FontWeight.Bold else null,
fontStyle = if (part.isItalic) FontStyle.Italic else null,
textDecoration = TextDecoration.Underline
)
)
) {
onMarkupLinkClicked(part.link.param.orEmpty())
}
) {
append(part.part)
}
} else if (part.mention != null && part.mention.param != null) {
withLink(
LinkAnnotation.Clickable(
tag = DEFAULT_MENTION_SPAN_TAG,
styles = TextLinkStyles(
style = SpanStyle(
fontWeight = if (part.isBold) FontWeight.Bold else null,
fontStyle = if (part.isItalic) FontStyle.Italic else null,
textDecoration = TextDecoration.Underline
)
)
) {
onMentionClicked(part.mention.param.orEmpty())
}
) {
append(part.part)
}
} else if (part.emoji != null && part.emoji.param != null) {
append(part.emoji.param)
} else {
withStyle(
style = SpanStyle(
fontWeight = if (part.isBold) FontWeight.Bold else null,
fontStyle = if (part.isItalic) FontStyle.Italic else null,
textDecoration = TextDecoration.Underline
textDecoration = if (part.underline)
TextDecoration.Underline
else if (part.isStrike)
TextDecoration.LineThrough
else null,
fontFamily = if (part.isCode) fontIBM else null,
)
)
) {
onMarkupLinkClicked(part.link.param.orEmpty())
) {
append(part.part)
}
}
) {
append(part.part)
}
} else if (part.mention != null && part.mention.param != null) {
withLink(
LinkAnnotation.Clickable(
tag = DEFAULT_MENTION_SPAN_TAG,
styles = TextLinkStyles(
style = SpanStyle(
fontWeight = if (part.isBold) FontWeight.Bold else null,
fontStyle = if (part.isItalic) FontStyle.Italic else null,
textDecoration = TextDecoration.Underline
)
)
if (isEdited) {
withStyle(
style = SpanStyle(color = colorResource(id = R.color.text_tertiary))
) {
onMentionClicked(part.mention.param.orEmpty())
append(
" (${stringResource(R.string.chats_message_edited)})"
)
}
) {
append(part.part)
}
} else if (part.emoji != null && part.emoji.param != null) {
append(part.emoji.param)
} else {
withStyle(
style = SpanStyle(
fontWeight = if (part.isBold) FontWeight.Bold else null,
fontStyle = if (part.isItalic) FontStyle.Italic else null,
textDecoration = if (part.underline)
TextDecoration.Underline
else if (part.isStrike)
TextDecoration.LineThrough
else null,
fontFamily = if (part.isCode) fontIBM else null,
color = Color.Transparent
)
) {
append(part.part)
append(
timestamp.formatTimeInMillis(
TIME_H24
)
)
}
}
},
style = BodyRegular,
color = colorResource(id = R.color.text_primary),
)
Text(
modifier = Modifier
.align(Alignment.BottomEnd),
text = timestamp.formatTimeInMillis(
TIME_H24
),
style = Caption2Regular,
color = colorResource(id = R.color.transparent_active),
maxLines = 1
)
}
}
MaterialTheme(
shapes = MaterialTheme.shapes.copy(
medium = RoundedCornerShape(
16.dp
)
),
colors = MaterialTheme.colors.copy(
surface = colorResource(id = R.color.background_secondary)
)
) {
DropdownMenu(
offset = DpOffset(0.dp, 8.dp),
expanded = showDropdownMenu,
onDismissRequest = {
showDropdownMenu = false
}
if (isEdited) {
withStyle(
style = SpanStyle(color = colorResource(id = R.color.text_tertiary))
) {
append(
" (${stringResource(R.string.chats_message_edited)})"
) {
DropdownMenuItem(
text = {
Text(
text = stringResource(R.string.chats_add_reaction),
color = colorResource(id = R.color.text_primary),
modifier = Modifier.padding(end = 64.dp)
)
},
onClick = {
onAddReactionClicked()
showDropdownMenu = false
}
)
Divider(paddingStart = 0.dp, paddingEnd = 0.dp)
DropdownMenuItem(
text = {
Text(
text = stringResource(R.string.chats_reply),
color = colorResource(id = R.color.text_primary),
modifier = Modifier.padding(end = 64.dp)
)
},
onClick = {
onReply()
showDropdownMenu = false
}
)
if (content.msg.isNotEmpty()) {
Divider(paddingStart = 0.dp, paddingEnd = 0.dp)
DropdownMenuItem(
text = {
Text(
text = stringResource(R.string.copy),
color = colorResource(id = R.color.text_primary),
modifier = Modifier.padding(end = 64.dp)
)
},
onClick = {
onCopyMessage()
showDropdownMenu = false
}
)
}
},
style = BodyRegular,
color = colorResource(id = R.color.text_primary),
)
if (isUserAuthor) {
Divider(paddingStart = 0.dp, paddingEnd = 0.dp)
DropdownMenuItem(
text = {
Text(
text = stringResource(R.string.edit),
color = colorResource(id = R.color.text_primary),
modifier = Modifier.padding(end = 64.dp)
)
},
onClick = {
onEditMessage()
showDropdownMenu = false
}
)
}
if (isUserAuthor) {
Divider(paddingStart = 0.dp, paddingEnd = 0.dp)
DropdownMenuItem(
text = {
Text(
text = stringResource(id = R.string.delete),
color = colorResource(id = R.color.palette_system_red),
modifier = Modifier.padding(end = 64.dp)
)
},
onClick = {
showDeleteMessageWarning = true
showDropdownMenu = false
}
)
}
}
}
}
BubbleAttachments(
attachments = attachments,
isUserAuthor = isUserAuthor,
onAttachmentClicked = onAttachmentClicked
)
if (reactions.isNotEmpty()) {
ReactionList(
reactions = reactions,
@ -308,100 +443,6 @@ fun Bubble(
isMaxReactionCountReached = isMaxReactionCountReached
)
}
MaterialTheme(
shapes = MaterialTheme.shapes.copy(
medium = RoundedCornerShape(
16.dp
)
),
colors = MaterialTheme.colors.copy(
surface = colorResource(id = R.color.background_secondary)
)
) {
DropdownMenu(
offset = DpOffset(0.dp, 8.dp),
expanded = showDropdownMenu,
onDismissRequest = {
showDropdownMenu = false
}
) {
DropdownMenuItem(
text = {
Text(
text = stringResource(R.string.chats_add_reaction),
color = colorResource(id = R.color.text_primary),
modifier = Modifier.padding(end = 64.dp)
)
},
onClick = {
onAddReactionClicked()
showDropdownMenu = false
}
)
Divider(paddingStart = 0.dp, paddingEnd = 0.dp)
DropdownMenuItem(
text = {
Text(
text = stringResource(R.string.chats_reply),
color = colorResource(id = R.color.text_primary),
modifier = Modifier.padding(end = 64.dp)
)
},
onClick = {
onReply()
showDropdownMenu = false
}
)
if (content.msg.isNotEmpty()) {
Divider(paddingStart = 0.dp, paddingEnd = 0.dp)
DropdownMenuItem(
text = {
Text(
text = stringResource(R.string.copy),
color = colorResource(id = R.color.text_primary),
modifier = Modifier.padding(end = 64.dp)
)
},
onClick = {
onCopyMessage()
showDropdownMenu = false
}
)
}
if (isUserAuthor) {
Divider(paddingStart = 0.dp, paddingEnd = 0.dp)
DropdownMenuItem(
text = {
Text(
text = stringResource(R.string.edit),
color = colorResource(id = R.color.text_primary),
modifier = Modifier.padding(end = 64.dp)
)
},
onClick = {
onEditMessage()
showDropdownMenu = false
}
)
}
if (isUserAuthor) {
Divider(paddingStart = 0.dp, paddingEnd = 0.dp)
DropdownMenuItem(
text = {
Text(
text = stringResource(id = R.string.delete),
color = colorResource(id = R.color.palette_system_red),
modifier = Modifier.padding(end = 64.dp)
)
},
onClick = {
showDeleteMessageWarning = true
showDropdownMenu = false
}
)
}
}
}
}
}

View file

@ -30,7 +30,19 @@ fun ChatPreview() {
),
author = "Walter",
timestamp = System.currentTimeMillis(),
creator = ""
creator = "",
reactions = listOf(
ChatView.Message.Reaction(
emoji = "\uD83D\uDE04",
count = 1,
isSelected = true
),
ChatView.Message.Reaction(
emoji = "\uFE0F",
count = 10,
isSelected = false
)
)
),
ChatView.Message(
id = "2",
@ -44,7 +56,8 @@ fun ChatPreview() {
),
author = "Leo",
timestamp = System.currentTimeMillis(),
creator = ""
creator = "",
isUserAuthor = true
),
ChatView.Message(
id = "3",
@ -76,6 +89,55 @@ fun ChatPreview() {
)
}
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Light Mode")
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO, name = "Dark Mode")
@Composable
fun ChatPreview2() {
Messages(
messages = listOf(
ChatView.Message(
id = "1",
content = ChatView.Message.Content(
msg = stringResource(id = R.string.default_text_placeholder),
parts = listOf(
ChatView.Message.Content.Part(
part = stringResource(id = R.string.default_text_placeholder)
)
)
),
author = "Walter",
timestamp = System.currentTimeMillis(),
creator = "",
reactions = listOf(
ChatView.Message.Reaction(
emoji = "\uD83D\uDE04",
count = 1,
isSelected = true
),
ChatView.Message.Reaction(
emoji = "\uFE0F",
count = 10,
isSelected = false
)
),
isUserAuthor = true
)
),
scrollState = LazyListState(),
onReacted = { a, b -> },
onDeleteMessage = {},
onCopyMessage = {},
onAttachmentClicked = {},
onEditMessage = {},
onMarkupLinkClicked = {},
onReplyMessage = {},
onAddReactionClicked = {},
onViewChatReaction = { a, b -> },
onMemberIconClicked = {},
onMentionClicked = {}
)
}
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Light Mode")
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO, name = "Dark Mode")
@Composable

View file

@ -10,10 +10,13 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.layout.wrapContentHeight
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
@ -40,15 +43,15 @@ fun ReactionList(
isMaxReactionCountReached: Boolean = false,
) {
FlowRow(
modifier = Modifier.padding(start = 12.dp, end = 12.dp, bottom = 12.dp, top = 4.dp),
modifier = Modifier
.padding(start = 0.dp, end = 0.dp, bottom = 0.dp, top = 4.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
reactions.forEach { reaction ->
Box(
Row(
modifier = Modifier
.height(28.dp)
.width(46.dp)
.background(
color = if (reaction.isSelected)
colorResource(id = R.color.palette_very_light_orange)
@ -81,18 +84,19 @@ fun ReactionList(
style = BodyCalloutMedium,
modifier = Modifier
.align(
alignment = Alignment.CenterStart
alignment = Alignment.CenterVertically
)
.padding(
start = 8.dp
)
)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = reaction.count.toString(),
style = Caption1Regular,
modifier = Modifier
.align(
alignment = Alignment.CenterEnd
alignment = Alignment.CenterVertically
)
.padding(
end = 8.dp

View file

@ -7,6 +7,8 @@
android:pathData="M0,16C0,7.163 7.163,0 16,0C24.837,0 32,7.163 32,16C32,24.837 24.837,32 16,32C7.163,32 0,24.837 0,16Z"
android:fillColor="@color/glyph_button"/>
<path
android:pathData="M10.259,21.769C9.815,21.34 10.02,20.857 10.359,20.253L12.118,17.088C12.347,16.692 12.524,16.549 12.905,16.54L23.024,16.171C23.143,16.166 23.21,16.093 23.21,16C23.205,15.913 23.143,15.835 23.024,15.83L12.905,15.498C12.509,15.484 12.328,15.328 12.118,14.941L10.325,11.697C10.006,11.122 9.82,10.656 10.259,10.232C10.607,9.896 11.16,9.96 11.713,10.2L23.057,15.088C23.362,15.217 23.605,15.36 23.767,15.517C24.077,15.816 24.077,16.185 23.767,16.484C23.605,16.641 23.362,16.784 23.057,16.913L11.775,21.778C11.141,22.05 10.602,22.101 10.259,21.769Z"
android:fillColor="@color/text_label_inversion"/>
android:pathData="M16,9V23M16,9L10,15M16,9L22,15"
android:strokeWidth="1.5"
android:strokeColor="@color/text_label_inversion"
android:strokeLineCap="round"/>
</vector>