mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-3251 Space-level chat | Fix | Attachment gallery + misc. design fixes (#2127)
This commit is contained in:
parent
43cf0dbc34
commit
86ad11c4f5
6 changed files with 266 additions and 106 deletions
|
@ -58,6 +58,27 @@ sealed interface ChatView {
|
|||
)
|
||||
|
||||
sealed class Attachment {
|
||||
|
||||
data class Gallery(val images: List<Image>): Attachment() {
|
||||
|
||||
val rowConfig = getRowConfiguration(images.size)
|
||||
|
||||
private fun getRowConfiguration(imageCount: Int): List<Int> {
|
||||
return when (imageCount) {
|
||||
2 -> listOf(2)
|
||||
3 -> listOf(1, 2)
|
||||
4 -> listOf(2, 2)
|
||||
5 -> listOf(2, 3)
|
||||
6 -> listOf(3, 3)
|
||||
7 -> listOf(2, 2, 3)
|
||||
8 -> listOf(2, 3, 3)
|
||||
9 -> listOf(3, 3, 3)
|
||||
10 -> listOf(2, 2, 3, 3)
|
||||
else -> listOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Image(
|
||||
val target: Id,
|
||||
val url: String,
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.anytypeio.anytype.core_models.Id
|
|||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.chats.Chat
|
||||
import com.anytypeio.anytype.core_models.ext.EMPTY_STRING_VALUE
|
||||
import com.anytypeio.anytype.core_models.primitives.Space
|
||||
import com.anytypeio.anytype.core_models.primitives.SpaceId
|
||||
import com.anytypeio.anytype.core_ui.text.splitByMarks
|
||||
|
@ -249,6 +250,21 @@ class ChatViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
}.let { results ->
|
||||
if (results.size >= 2) {
|
||||
val images = results.filterIsInstance<ChatView.Message.Attachment.Image>()
|
||||
if (images.size == results.size) {
|
||||
listOf(
|
||||
ChatView.Message.Attachment.Gallery(
|
||||
images = images
|
||||
)
|
||||
)
|
||||
} else {
|
||||
results
|
||||
}
|
||||
} else {
|
||||
results
|
||||
}
|
||||
},
|
||||
avatar = if (member != null && !member.iconImage.isNullOrEmpty()) {
|
||||
ChatView.Message.Avatar.Image(
|
||||
|
@ -558,29 +574,43 @@ class ChatViewModel @Inject constructor(
|
|||
fun onRequestEditMessageClicked(msg: ChatView.Message) {
|
||||
Timber.d("onRequestEditMessageClicked")
|
||||
viewModelScope.launch {
|
||||
chatBoxAttachments.value = msg.attachments.mapNotNull { a ->
|
||||
when(a) {
|
||||
is ChatView.Message.Attachment.Image -> {
|
||||
ChatView.Message.ChatBoxAttachment.Existing.Image(
|
||||
target = a.target,
|
||||
url = a.url
|
||||
)
|
||||
}
|
||||
is ChatView.Message.Attachment.Link -> {
|
||||
val wrapper = a.wrapper
|
||||
if (wrapper != null) {
|
||||
val type = wrapper.type.firstOrNull()
|
||||
ChatView.Message.ChatBoxAttachment.Existing.Link(
|
||||
target = wrapper.id,
|
||||
name = wrapper.name.orEmpty(),
|
||||
icon = wrapper.objectIcon(urlBuilder),
|
||||
typeName = if (type != null)
|
||||
storeOfObjectTypes.get(type)?.name.orEmpty()
|
||||
else
|
||||
""
|
||||
chatBoxAttachments.value = buildList {
|
||||
msg.attachments.forEach { a ->
|
||||
when(a) {
|
||||
is ChatView.Message.Attachment.Image -> {
|
||||
add(
|
||||
ChatView.Message.ChatBoxAttachment.Existing.Image(
|
||||
target = a.target,
|
||||
url = a.url
|
||||
)
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
is ChatView.Message.Attachment.Gallery -> {
|
||||
a.images.forEach { image ->
|
||||
add(
|
||||
ChatView.Message.ChatBoxAttachment.Existing.Image(
|
||||
target = image.target,
|
||||
url = image.url
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
is ChatView.Message.Attachment.Link -> {
|
||||
val wrapper = a.wrapper
|
||||
if (wrapper != null) {
|
||||
val type = wrapper.type.firstOrNull()
|
||||
add(
|
||||
ChatView.Message.ChatBoxAttachment.Existing.Link(
|
||||
target = wrapper.id,
|
||||
name = wrapper.name.orEmpty(),
|
||||
icon = wrapper.objectIcon(urlBuilder),
|
||||
typeName = if (type != null)
|
||||
storeOfObjectTypes.get(type)?.name.orEmpty()
|
||||
else
|
||||
""
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -646,6 +676,18 @@ class ChatViewModel @Inject constructor(
|
|||
attachment.name
|
||||
}
|
||||
}
|
||||
is ChatView.Message.Attachment.Gallery -> {
|
||||
val first = attachment.images.firstOrNull()
|
||||
if (first != null) {
|
||||
if (first.ext.isNotEmpty()) {
|
||||
"${first.name}.${first.ext}"
|
||||
} else {
|
||||
first.name
|
||||
}
|
||||
} else {
|
||||
EMPTY_STRING_VALUE
|
||||
}
|
||||
}
|
||||
is ChatView.Message.Attachment.Link -> {
|
||||
attachment.wrapper?.name.orEmpty()
|
||||
}
|
||||
|
@ -685,6 +727,9 @@ class ChatViewModel @Inject constructor(
|
|||
)
|
||||
)
|
||||
}
|
||||
is ChatView.Message.Attachment.Gallery -> {
|
||||
// TODO
|
||||
}
|
||||
is ChatView.Message.Attachment.Link -> {
|
||||
val wrapper = attachment.wrapper
|
||||
if (wrapper != null) {
|
||||
|
|
|
@ -5,6 +5,8 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
@ -33,43 +35,52 @@ import com.bumptech.glide.integration.compose.GlideImage
|
|||
|
||||
@Composable
|
||||
@OptIn(ExperimentalGlideComposeApi::class)
|
||||
fun BubbleAttachments(
|
||||
fun ColumnScope.BubbleAttachments(
|
||||
attachments: List<ChatView.Message.Attachment>,
|
||||
onAttachmentClicked: (ChatView.Message.Attachment) -> Unit,
|
||||
isUserAuthor: Boolean
|
||||
) {
|
||||
attachments.forEachIndexed { idx, attachment ->
|
||||
when (attachment) {
|
||||
is ChatView.Message.Attachment.Gallery -> {
|
||||
val rowConfig = attachment.rowConfig
|
||||
var index = 0
|
||||
rowConfig.forEachIndexed { idx, rowSize ->
|
||||
BubbleGalleryRowLayout(
|
||||
onAttachmentClicked = onAttachmentClicked,
|
||||
images = attachment.images.slice(index until index + rowSize)
|
||||
)
|
||||
if (idx != rowConfig.lastIndex) {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
}
|
||||
index += rowSize
|
||||
}
|
||||
}
|
||||
is ChatView.Message.Attachment.Image -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = 4.dp,
|
||||
end = 4.dp,
|
||||
bottom = 4.dp,
|
||||
top = 0.dp
|
||||
)
|
||||
.size(300.dp)
|
||||
.padding(horizontal = 4.dp)
|
||||
.size(292.dp)
|
||||
.background(
|
||||
color = colorResource(R.color.shape_tertiary),
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.Center)
|
||||
.size(64.dp),
|
||||
.size(48.dp),
|
||||
color = colorResource(R.color.glyph_active),
|
||||
trackColor = colorResource(R.color.glyph_active).copy(alpha = 0.5f),
|
||||
strokeWidth = 8.dp
|
||||
strokeWidth = 4.dp
|
||||
)
|
||||
GlideImage(
|
||||
model = attachment.url,
|
||||
contentDescription = "Attachment image",
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.size(300.dp)
|
||||
.clip(shape = RoundedCornerShape(16.dp))
|
||||
.size(292.dp)
|
||||
.clip(shape = RoundedCornerShape(12.dp))
|
||||
.clickable {
|
||||
onAttachmentClicked(attachment)
|
||||
}
|
||||
|
|
|
@ -1,9 +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
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
|
@ -15,7 +13,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.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.DropdownMenu
|
||||
|
@ -65,7 +63,6 @@ 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
|
||||
import com.anytypeio.anytype.core_utils.ext.formatTimeInMillis
|
||||
|
@ -136,69 +133,12 @@ fun Bubble(
|
|||
.width(IntrinsicSize.Max)
|
||||
) {
|
||||
if (reply != null) {
|
||||
Text(
|
||||
text = reply.author,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
top = 8.dp,
|
||||
end = 12.dp
|
||||
)
|
||||
.alpha(0.5f),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
style = Caption1Medium
|
||||
ChatBubbleReply(
|
||||
reply = reply,
|
||||
onScrollToReplyClicked = onScrollToReplyClicked
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(IntrinsicSize.Min)
|
||||
.clickable {
|
||||
onScrollToReplyClicked(reply)
|
||||
}
|
||||
.alpha(0.5f)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.width(4.dp)
|
||||
.background(
|
||||
color = colorResource(R.color.shape_transparent_primary),
|
||||
shape = RoundedCornerShape(4.dp)
|
||||
)
|
||||
)
|
||||
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))
|
||||
}
|
||||
// Username section
|
||||
// Bubble username section
|
||||
if (!isUserAuthor) {
|
||||
Text(
|
||||
text = name,
|
||||
|
@ -214,12 +154,12 @@ fun Bubble(
|
|||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
}
|
||||
// Text with attachments
|
||||
// Rendering text with attachments
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
color = if (isUserAuthor)
|
||||
color = if (!isUserAuthor)
|
||||
colorResource(R.color.background_primary)
|
||||
else
|
||||
colorResource(R.color.shape_transparent_secondary),
|
||||
|
@ -245,6 +185,7 @@ fun Bubble(
|
|||
bottom = 4.dp
|
||||
)
|
||||
) {
|
||||
// Rendering text body message
|
||||
Text(
|
||||
modifier = Modifier,
|
||||
text = buildAnnotatedString {
|
||||
|
@ -327,6 +268,7 @@ fun Bubble(
|
|||
style = BodyRegular,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
)
|
||||
// Rendering message timestamp
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd),
|
||||
|
@ -446,6 +388,73 @@ fun Bubble(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ChatBubbleReply(
|
||||
reply: ChatView.Message.Reply,
|
||||
onScrollToReplyClicked: (ChatView.Message.Reply) -> Unit
|
||||
) {
|
||||
Text(
|
||||
text = reply.author,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
top = 8.dp,
|
||||
end = 12.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
|
||||
.wrapContentWidth()
|
||||
.height(IntrinsicSize.Min)
|
||||
.clickable {
|
||||
onScrollToReplyClicked(reply)
|
||||
}
|
||||
.alpha(0.5f)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.width(4.dp)
|
||||
.background(
|
||||
color = colorResource(R.color.shape_transparent_primary),
|
||||
shape = RoundedCornerShape(4.dp)
|
||||
)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.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))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChatUserAvatar(
|
||||
msg: ChatView.Message,
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
package com.anytypeio.anytype.feature_chats.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.anytypeio.anytype.feature_chats.R
|
||||
import com.anytypeio.anytype.feature_chats.presentation.ChatView
|
||||
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
|
||||
import com.bumptech.glide.integration.compose.GlideImage
|
||||
|
||||
@OptIn(ExperimentalGlideComposeApi::class)
|
||||
@Composable
|
||||
fun BubbleGalleryRowLayout(
|
||||
images: List<ChatView.Message.Attachment.Image>,
|
||||
onAttachmentClicked: (ChatView.Message.Attachment) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.width(300.dp)
|
||||
.padding(horizontal = 4.dp)
|
||||
,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
images.forEach { image ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.aspectRatio(1f)
|
||||
.background(
|
||||
color = colorResource(R.color.shape_tertiary),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.Center)
|
||||
.size(64.dp),
|
||||
color = colorResource(R.color.glyph_active),
|
||||
trackColor = colorResource(R.color.glyph_active).copy(alpha = 0.5f),
|
||||
strokeWidth = 8.dp
|
||||
)
|
||||
GlideImage(
|
||||
model = image.url,
|
||||
contentDescription = "Attachment image",
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(shape = RoundedCornerShape(12.dp))
|
||||
.clickable {
|
||||
onAttachmentClicked(image)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -44,9 +44,9 @@ fun ReactionList(
|
|||
) {
|
||||
FlowRow(
|
||||
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)
|
||||
.padding(top = 4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
reactions.forEach { reaction ->
|
||||
Row(
|
||||
|
@ -108,9 +108,11 @@ fun ReactionList(
|
|||
if (!isMaxReactionCountReached) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(end = 8.dp)
|
||||
.size(28.dp)
|
||||
.clip(CircleShape)
|
||||
.background(
|
||||
color = colorResource(R.color.shape_transparent_secondary)
|
||||
)
|
||||
.clickable {
|
||||
onAddNewReaction()
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue