mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-3044 Chats | Enhancement | Naive implementation for fetching and displaying attachments (#1846)
This commit is contained in:
parent
3032c4125a
commit
e0c5137036
7 changed files with 256 additions and 81 deletions
|
@ -17,6 +17,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
@ -58,11 +59,13 @@ import com.anytypeio.anytype.other.DefaultDeepLinkResolver
|
|||
import com.anytypeio.anytype.presentation.home.Command
|
||||
import com.anytypeio.anytype.presentation.home.HomeScreenViewModel
|
||||
import com.anytypeio.anytype.presentation.home.HomeScreenViewModel.Navigation
|
||||
import com.anytypeio.anytype.presentation.home.OpenObjectNavigation
|
||||
import com.anytypeio.anytype.presentation.search.GlobalSearchViewModel
|
||||
import com.anytypeio.anytype.presentation.spaces.SpaceIconView
|
||||
import com.anytypeio.anytype.presentation.widgets.DropDownMenuAction
|
||||
import com.anytypeio.anytype.presentation.widgets.WidgetView
|
||||
import com.anytypeio.anytype.ui.base.navigation
|
||||
import com.anytypeio.anytype.ui.editor.EditorFragment
|
||||
import com.anytypeio.anytype.ui.gallery.GalleryInstallationFragment
|
||||
import com.anytypeio.anytype.ui.multiplayer.RequestJoinSpaceFragment
|
||||
import com.anytypeio.anytype.ui.multiplayer.ShareSpaceFragment
|
||||
|
@ -217,6 +220,27 @@ class HomeScreenFragment : BaseComposeFragment(),
|
|||
)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
// TODO refact navigation here. or reuse nav command from home screen view model
|
||||
spaceLevelChatViewModel.navigation.collect { nav ->
|
||||
when(nav) {
|
||||
is OpenObjectNavigation.OpenEditor -> {
|
||||
runCatching {
|
||||
findNavController().navigate(
|
||||
R.id.objectNavigation,
|
||||
EditorFragment.args(
|
||||
ctx = nav.target,
|
||||
space = nav.space
|
||||
)
|
||||
)
|
||||
}.onFailure {
|
||||
Timber.w("Error while opening editor from chat.")
|
||||
}
|
||||
}
|
||||
else -> toast("TODO")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PageWithWidgets()
|
||||
}
|
||||
|
|
|
@ -1,18 +1,28 @@
|
|||
package com.anytypeio.anytype.domain.chats
|
||||
|
||||
import com.anytypeio.anytype.core_models.Command
|
||||
import com.anytypeio.anytype.core_models.DVFilter
|
||||
import com.anytypeio.anytype.core_models.DVFilterCondition
|
||||
import com.anytypeio.anytype.core_models.Event
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.core_models.chats.Chat
|
||||
import com.anytypeio.anytype.core_models.primitives.Space
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.debugging.Logger
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.isNotEmpty
|
||||
import kotlin.collections.toList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.scan
|
||||
|
||||
class ChatContainer @Inject constructor(
|
||||
|
@ -20,9 +30,55 @@ class ChatContainer @Inject constructor(
|
|||
private val channel: ChatEventChannel,
|
||||
private val logger: Logger
|
||||
) {
|
||||
|
||||
private val payloads = MutableSharedFlow<List<Event.Command.Chats>>()
|
||||
|
||||
private val attachments = MutableSharedFlow<Set<Id>>(replay = 0)
|
||||
|
||||
@Deprecated("Naive implementation. Add caching logic - maybe store for wrappers")
|
||||
fun fetchAttachments(space: Space) : Flow<Map<Id, ObjectWrapper.Basic>> {
|
||||
return attachments
|
||||
.distinctUntilChanged()
|
||||
.map { ids ->
|
||||
if (ids.isNotEmpty()) {
|
||||
repo.searchObjects(
|
||||
sorts = emptyList(),
|
||||
limit = 0,
|
||||
filters = buildList {
|
||||
DVFilter(
|
||||
relation = Relations.ID,
|
||||
value = ids.toList(),
|
||||
condition = DVFilterCondition.IN
|
||||
)
|
||||
},
|
||||
keys = emptyList(),
|
||||
space = space
|
||||
).mapNotNull {
|
||||
val wrapper = ObjectWrapper.Basic(it)
|
||||
if (wrapper.isValid) wrapper else null
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.map { wrappers -> wrappers.associate { it.id to it } }
|
||||
}
|
||||
|
||||
fun watchWhileTrackingAttachments(chat: Id): Flow<List<Chat.Message>> {
|
||||
return watch(chat)
|
||||
.onEach { messages ->
|
||||
val ids = messages
|
||||
.map { msg ->
|
||||
msg.attachments.map {
|
||||
it.target
|
||||
}
|
||||
}
|
||||
.flatten()
|
||||
.toSet()
|
||||
attachments.emit(ids)
|
||||
}
|
||||
}
|
||||
|
||||
fun watch(chat: Id): Flow<List<Chat.Message>> = flow {
|
||||
val initial = repo.subscribeLastChatMessages(
|
||||
command = Command.ChatCommand.SubscribeLastMessages(
|
||||
|
|
|
@ -40,6 +40,8 @@ dependencies {
|
|||
implementation libs.composeMaterial
|
||||
|
||||
implementation libs.coilCompose
|
||||
annotationProcessor libs.glideCompiler
|
||||
implementation libs.glideCompose
|
||||
|
||||
debugImplementation libs.composeTooling
|
||||
|
||||
|
|
|
@ -2,26 +2,28 @@ package com.anytypeio.anytype.feature_discussions.presentation
|
|||
|
||||
import com.anytypeio.anytype.core_models.Block
|
||||
import com.anytypeio.anytype.core_models.Hash
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.chats.Chat
|
||||
|
||||
sealed interface DiscussionView {
|
||||
data class Message(
|
||||
val id: String,
|
||||
val content: List<Content.Part>,
|
||||
val content: Content,
|
||||
val author: String,
|
||||
val timestamp: Long,
|
||||
val attachments: List<Chat.Message.Attachment> = emptyList(),
|
||||
val attachments: List<Attachment> = emptyList(),
|
||||
val reactions: List<Reaction> = emptyList(),
|
||||
val isUserAuthor: Boolean = false,
|
||||
val isEdited: Boolean = false,
|
||||
val avatar: Avatar = Avatar.Initials()
|
||||
) : DiscussionView {
|
||||
|
||||
interface Content {
|
||||
data class Content(val msg: String, val parts: List<Part>) {
|
||||
data class Part(
|
||||
val part: String,
|
||||
val styles: List<Block.Content.Text.Mark> = emptyList()
|
||||
) : Content {
|
||||
) {
|
||||
val isBold: Boolean = styles.any { it.type == Block.Content.Text.Mark.Type.BOLD }
|
||||
val isItalic: Boolean = styles.any { it.type == Block.Content.Text.Mark.Type.ITALIC }
|
||||
val isStrike = styles.any { it.type == Block.Content.Text.Mark.Type.STRIKETHROUGH }
|
||||
|
@ -30,6 +32,18 @@ sealed interface DiscussionView {
|
|||
}
|
||||
}
|
||||
|
||||
sealed class Attachment {
|
||||
data class Image(
|
||||
val target: Id,
|
||||
val url: String
|
||||
): Attachment()
|
||||
|
||||
data class Link(
|
||||
val target: Id,
|
||||
val wrapper: ObjectWrapper.Basic?
|
||||
): Attachment()
|
||||
}
|
||||
|
||||
data class Reaction(
|
||||
val emoji: String,
|
||||
val count: Int,
|
||||
|
|
|
@ -3,11 +3,13 @@ package com.anytypeio.anytype.feature_discussions.presentation
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.core_models.Command
|
||||
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.Relations
|
||||
import com.anytypeio.anytype.core_models.chats.Chat
|
||||
import com.anytypeio.anytype.core_models.primitives.Space
|
||||
import com.anytypeio.anytype.core_ui.text.splitByMarks
|
||||
import com.anytypeio.anytype.core_utils.ext.withLatestFrom
|
||||
import com.anytypeio.anytype.domain.auth.interactor.GetAccount
|
||||
import com.anytypeio.anytype.domain.base.fold
|
||||
import com.anytypeio.anytype.domain.base.onFailure
|
||||
|
@ -25,12 +27,12 @@ import com.anytypeio.anytype.domain.`object`.OpenObject
|
|||
import com.anytypeio.anytype.domain.`object`.SetObjectDetails
|
||||
import com.anytypeio.anytype.presentation.common.BaseViewModel
|
||||
import com.anytypeio.anytype.presentation.home.OpenObjectNavigation
|
||||
import com.anytypeio.anytype.presentation.home.navigation
|
||||
import com.anytypeio.anytype.presentation.search.GlobalSearchItemView
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
|
@ -105,10 +107,9 @@ class DiscussionViewModel @Inject constructor(
|
|||
chat: Id
|
||||
) {
|
||||
chatContainer
|
||||
.watch(chat = chat)
|
||||
.onEach { Timber.d("Got new update: $it") }
|
||||
.collect {
|
||||
messages.value = it.map { msg ->
|
||||
.watchWhileTrackingAttachments(chat = chat)
|
||||
.withLatestFrom(chatContainer.fetchAttachments(vmParams.space)) { result, dependencies ->
|
||||
result.map { msg ->
|
||||
val member = members.get().let { type ->
|
||||
when (type) {
|
||||
is Store.Data -> type.members.find { member ->
|
||||
|
@ -123,15 +124,18 @@ class DiscussionViewModel @Inject constructor(
|
|||
DiscussionView.Message(
|
||||
id = msg.id,
|
||||
timestamp = msg.createdAt * 1000,
|
||||
content = content?.text
|
||||
.orEmpty()
|
||||
.splitByMarks(marks = content?.marks.orEmpty())
|
||||
.map { (part, styles) ->
|
||||
DiscussionView.Message.Content.Part(
|
||||
part = part,
|
||||
styles = styles
|
||||
)
|
||||
},
|
||||
content = DiscussionView.Message.Content(
|
||||
msg = content?.text.orEmpty(),
|
||||
parts = content?.text
|
||||
.orEmpty()
|
||||
.splitByMarks(marks = content?.marks.orEmpty())
|
||||
.map { (part, styles) ->
|
||||
DiscussionView.Message.Content.Part(
|
||||
part = part,
|
||||
styles = styles
|
||||
)
|
||||
}
|
||||
),
|
||||
author = member?.name ?: msg.creator.takeLast(5),
|
||||
isUserAuthor = msg.creator == account,
|
||||
isEdited = msg.modifiedAt > msg.createdAt,
|
||||
|
@ -142,7 +146,32 @@ class DiscussionViewModel @Inject constructor(
|
|||
isSelected = ids.contains(account)
|
||||
)
|
||||
},
|
||||
attachments = msg.attachments,
|
||||
attachments = msg.attachments.map { attachment ->
|
||||
when(attachment.type) {
|
||||
Chat.Message.Attachment.Type.Image -> DiscussionView.Message.Attachment.Image(
|
||||
target = attachment.target,
|
||||
url = urlBuilder.medium(path = attachment.target)
|
||||
)
|
||||
else -> {
|
||||
val wrapper = dependencies[attachment.target]
|
||||
if (wrapper?.layout == ObjectType.Layout.IMAGE) {
|
||||
DiscussionView.Message.Attachment.Image(
|
||||
target = attachment.target,
|
||||
url = urlBuilder.large(path = attachment.target)
|
||||
)
|
||||
} else {
|
||||
DiscussionView.Message.Attachment.Link(
|
||||
target = attachment.target,
|
||||
wrapper = wrapper
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.also {
|
||||
if (it.isNotEmpty()) {
|
||||
Timber.d("Chat attachments: $it")
|
||||
}
|
||||
},
|
||||
avatar = if (member != null && !member.iconImage.isNullOrEmpty()) {
|
||||
DiscussionView.Message.Avatar.Image(
|
||||
urlBuilder.thumbnail(member.iconImage!!)
|
||||
|
@ -153,6 +182,9 @@ class DiscussionViewModel @Inject constructor(
|
|||
)
|
||||
}.reversed()
|
||||
}
|
||||
.collect { result ->
|
||||
messages.value = result
|
||||
}
|
||||
}
|
||||
|
||||
fun onMessageSent(msg: String) {
|
||||
|
@ -274,15 +306,22 @@ class DiscussionViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun onAttachmentClicked(attachment: Chat.Message.Attachment) {
|
||||
fun onAttachmentClicked(attachment: DiscussionView.Message.Attachment) {
|
||||
Timber.d("onAttachmentClicked")
|
||||
viewModelScope.launch {
|
||||
// TODO naive implementation. Currently used for debugging.
|
||||
navigation.emit(
|
||||
OpenObjectNavigation.OpenEditor(
|
||||
target = attachment.target,
|
||||
space = vmParams.space.id
|
||||
)
|
||||
)
|
||||
when(attachment) {
|
||||
is DiscussionView.Message.Attachment.Image -> {
|
||||
// Do nothing.
|
||||
}
|
||||
is DiscussionView.Message.Attachment.Link -> {
|
||||
val wrapper = attachment.wrapper
|
||||
if (wrapper != null) {
|
||||
navigation.emit(wrapper.navigation())
|
||||
} else {
|
||||
Timber.w("Wrapper is not found in attachment")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,9 +19,12 @@ fun DiscussionPreview() {
|
|||
messages = listOf(
|
||||
DiscussionView.Message(
|
||||
id = "1",
|
||||
content = listOf(
|
||||
DiscussionView.Message.Content.Part(
|
||||
part = stringResource(id = R.string.default_text_placeholder)
|
||||
content = DiscussionView.Message.Content(
|
||||
msg = stringResource(id = R.string.default_text_placeholder),
|
||||
parts = listOf(
|
||||
DiscussionView.Message.Content.Part(
|
||||
part = stringResource(id = R.string.default_text_placeholder)
|
||||
)
|
||||
)
|
||||
),
|
||||
author = "Walter",
|
||||
|
@ -29,9 +32,12 @@ fun DiscussionPreview() {
|
|||
),
|
||||
DiscussionView.Message(
|
||||
id = "2",
|
||||
content = listOf(
|
||||
DiscussionView.Message.Content.Part(
|
||||
part = stringResource(id = R.string.default_text_placeholder)
|
||||
content = DiscussionView.Message.Content(
|
||||
msg = stringResource(id = R.string.default_text_placeholder),
|
||||
parts = listOf(
|
||||
DiscussionView.Message.Content.Part(
|
||||
part = stringResource(id = R.string.default_text_placeholder)
|
||||
)
|
||||
)
|
||||
),
|
||||
author = "Leo",
|
||||
|
@ -39,9 +45,12 @@ fun DiscussionPreview() {
|
|||
),
|
||||
DiscussionView.Message(
|
||||
id = "3",
|
||||
content = listOf(
|
||||
DiscussionView.Message.Content.Part(
|
||||
part = stringResource(id = R.string.default_text_placeholder)
|
||||
content = DiscussionView.Message.Content(
|
||||
msg = stringResource(id = R.string.default_text_placeholder),
|
||||
parts = listOf(
|
||||
DiscussionView.Message.Content.Part(
|
||||
part = stringResource(id = R.string.default_text_placeholder)
|
||||
)
|
||||
)
|
||||
),
|
||||
author = "Gilbert",
|
||||
|
@ -74,9 +83,12 @@ fun DiscussionScreenPreview() {
|
|||
add(
|
||||
DiscussionView.Message(
|
||||
id = idx.toString(),
|
||||
content = listOf(
|
||||
DiscussionView.Message.Content.Part(
|
||||
part = stringResource(id = R.string.default_text_placeholder)
|
||||
content = DiscussionView.Message.Content(
|
||||
msg = stringResource(id = R.string.default_text_placeholder),
|
||||
parts = listOf(
|
||||
DiscussionView.Message.Content.Part(
|
||||
part = stringResource(id = R.string.default_text_placeholder)
|
||||
)
|
||||
)
|
||||
),
|
||||
author = "User ${idx.inc()}",
|
||||
|
@ -116,9 +128,12 @@ fun DiscussionScreenPreview() {
|
|||
fun BubblePreview() {
|
||||
Bubble(
|
||||
name = "Leo Marx",
|
||||
msg = listOf(
|
||||
DiscussionView.Message.Content.Part(
|
||||
part = stringResource(id = R.string.default_text_placeholder)
|
||||
content = DiscussionView.Message.Content(
|
||||
msg = stringResource(id = R.string.default_text_placeholder),
|
||||
parts = listOf(
|
||||
DiscussionView.Message.Content.Part(
|
||||
part = stringResource(id = R.string.default_text_placeholder)
|
||||
)
|
||||
)
|
||||
),
|
||||
timestamp = System.currentTimeMillis(),
|
||||
|
@ -137,9 +152,12 @@ fun BubblePreview() {
|
|||
fun BubbleEditedPreview() {
|
||||
Bubble(
|
||||
name = "Leo Marx",
|
||||
msg = listOf(
|
||||
DiscussionView.Message.Content.Part(
|
||||
part = stringResource(id = R.string.default_text_placeholder)
|
||||
content = DiscussionView.Message.Content(
|
||||
msg = stringResource(id = R.string.default_text_placeholder),
|
||||
parts = listOf(
|
||||
DiscussionView.Message.Content.Part(
|
||||
part = stringResource(id = R.string.default_text_placeholder)
|
||||
)
|
||||
)
|
||||
),
|
||||
isEdited = true,
|
||||
|
@ -159,9 +177,12 @@ fun BubbleEditedPreview() {
|
|||
fun BubbleWithAttachmentPreview() {
|
||||
Bubble(
|
||||
name = "Leo Marx",
|
||||
msg = listOf(
|
||||
DiscussionView.Message.Content.Part(
|
||||
part = stringResource(id = R.string.default_text_placeholder)
|
||||
content = DiscussionView.Message.Content(
|
||||
msg = stringResource(id = R.string.default_text_placeholder),
|
||||
parts = listOf(
|
||||
DiscussionView.Message.Content.Part(
|
||||
part = stringResource(id = R.string.default_text_placeholder)
|
||||
)
|
||||
)
|
||||
),
|
||||
timestamp = System.currentTimeMillis(),
|
||||
|
@ -170,9 +191,9 @@ fun BubbleWithAttachmentPreview() {
|
|||
onCopyMessage = {},
|
||||
attachments = buildList {
|
||||
add(
|
||||
Chat.Message.Attachment(
|
||||
target = "Walter Benjamin",
|
||||
type = Chat.Message.Attachment.Type.Image
|
||||
DiscussionView.Message.Attachment.Link(
|
||||
target = "ID",
|
||||
wrapper = null
|
||||
)
|
||||
)
|
||||
},
|
||||
|
|
|
@ -93,6 +93,7 @@ import androidx.navigation.compose.NavHost
|
|||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import coil.compose.AsyncImage
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
import coil.request.ImageRequest
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.chats.Chat
|
||||
|
@ -120,6 +121,8 @@ import com.anytypeio.anytype.feature_discussions.presentation.DiscussionViewMode
|
|||
import com.anytypeio.anytype.feature_discussions.presentation.DiscussionViewModel.UXCommand
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectIcon
|
||||
import com.anytypeio.anytype.presentation.search.GlobalSearchItemView
|
||||
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
|
||||
import com.bumptech.glide.integration.compose.GlideImage
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
|
@ -166,9 +169,7 @@ fun DiscussionScreenWrapper(
|
|||
lazyListState = lazyListState,
|
||||
onReacted = vm::onReacted,
|
||||
onCopyMessage = { msg ->
|
||||
clipboard.setText(
|
||||
AnnotatedString(text = msg.content.joinToString())
|
||||
)
|
||||
clipboard.setText(AnnotatedString(text = msg.content.msg))
|
||||
},
|
||||
onDeleteMessage = vm::onDeleteMessage,
|
||||
onEditMessage = vm::onRequestEditMessageClicked,
|
||||
|
@ -225,7 +226,7 @@ fun DiscussionScreen(
|
|||
onDeleteMessage: (DiscussionView.Message) -> Unit,
|
||||
onCopyMessage: (DiscussionView.Message) -> Unit,
|
||||
onEditMessage: (DiscussionView.Message) -> Unit,
|
||||
onAttachmentClicked: (Chat.Message.Attachment) -> Unit,
|
||||
onAttachmentClicked: (DiscussionView.Message.Attachment) -> Unit,
|
||||
onExitEditMessageMode: () -> Unit,
|
||||
onMarkupLinkClicked: (String) -> Unit,
|
||||
onAttachObjectClicked: () -> Unit,
|
||||
|
@ -277,8 +278,8 @@ fun DiscussionScreen(
|
|||
onEditMessage = { msg ->
|
||||
onEditMessage(msg).also {
|
||||
textState = TextFieldValue(
|
||||
msg.content.joinToString(),
|
||||
selection = TextRange(msg.content.joinToString().length)
|
||||
msg.content.msg,
|
||||
selection = TextRange(msg.content.msg.length)
|
||||
)
|
||||
chatBoxFocusRequester.requestFocus()
|
||||
}
|
||||
|
@ -521,7 +522,7 @@ private fun ChatBox(
|
|||
) {
|
||||
attachments.forEach {
|
||||
Box {
|
||||
Attachment(
|
||||
AttachedObject(
|
||||
modifier = Modifier.padding(
|
||||
top = 12.dp,
|
||||
start = 16.dp,
|
||||
|
@ -826,7 +827,7 @@ fun Messages(
|
|||
onReacted: (Id, String) -> Unit,
|
||||
onDeleteMessage: (DiscussionView.Message) -> Unit,
|
||||
onCopyMessage: (DiscussionView.Message) -> Unit,
|
||||
onAttachmentClicked: (Chat.Message.Attachment) -> Unit,
|
||||
onAttachmentClicked: (DiscussionView.Message.Attachment) -> Unit,
|
||||
onEditMessage: (DiscussionView.Message) -> Unit,
|
||||
onMarkupLinkClicked: (String) -> Unit
|
||||
) {
|
||||
|
@ -859,7 +860,7 @@ fun Messages(
|
|||
Bubble(
|
||||
modifier = Modifier.weight(1.0f),
|
||||
name = msg.author,
|
||||
msg = msg.content,
|
||||
content = msg.content,
|
||||
timestamp = msg.timestamp,
|
||||
attachments = msg.attachments,
|
||||
isUserAuthor = msg.isUserAuthor,
|
||||
|
@ -992,13 +993,14 @@ private fun ChatUserAvatar(
|
|||
val defaultBubbleColor = Color(0x99FFFFFF)
|
||||
val userMessageBubbleColor = Color(0x66000000)
|
||||
|
||||
@OptIn(ExperimentalGlideComposeApi::class)
|
||||
@Composable
|
||||
fun Bubble(
|
||||
modifier: Modifier = Modifier,
|
||||
name: String,
|
||||
msg: List<DiscussionView.Message.Content.Part>,
|
||||
content: DiscussionView.Message.Content,
|
||||
timestamp: Long,
|
||||
attachments: List<Chat.Message.Attachment> = emptyList(),
|
||||
attachments: List<DiscussionView.Message.Attachment> = emptyList(),
|
||||
isUserAuthor: Boolean = false,
|
||||
isEdited: Boolean = false,
|
||||
reactions: List<DiscussionView.Message.Reaction> = emptyList(),
|
||||
|
@ -1006,7 +1008,7 @@ fun Bubble(
|
|||
onDeleteMessage: () -> Unit,
|
||||
onCopyMessage: () -> Unit,
|
||||
onEditMessage: () -> Unit,
|
||||
onAttachmentClicked: (Chat.Message.Attachment) -> Unit,
|
||||
onAttachmentClicked: (DiscussionView.Message.Attachment) -> Unit,
|
||||
onMarkupLinkClicked: (String) -> Unit
|
||||
) {
|
||||
var showDropdownMenu by remember { mutableStateOf(false) }
|
||||
|
@ -1062,7 +1064,7 @@ fun Bubble(
|
|||
bottom = 0.dp
|
||||
),
|
||||
text = buildAnnotatedString {
|
||||
msg.forEach { part ->
|
||||
content.parts.forEach { part ->
|
||||
if (part.link != null && part.link.param != null) {
|
||||
withLink(
|
||||
LinkAnnotation.Clickable(
|
||||
|
@ -1119,19 +1121,33 @@ fun Bubble(
|
|||
colorResource(id = R.color.text_primary),
|
||||
)
|
||||
attachments.forEach { attachment ->
|
||||
Attachment(
|
||||
modifier = Modifier.padding(
|
||||
start = 16.dp,
|
||||
end = 16.dp,
|
||||
top = 8.dp
|
||||
),
|
||||
title = attachment.target,
|
||||
type = attachment.type.toString(),
|
||||
icon = ObjectIcon.None,
|
||||
onAttachmentClicked = {
|
||||
onAttachmentClicked(attachment)
|
||||
when(attachment) {
|
||||
is DiscussionView.Message.Attachment.Image -> {
|
||||
GlideImage(
|
||||
model = attachment.url,
|
||||
contentDescription = "Attachment image",
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.clip(shape = RoundedCornerShape(16.dp))
|
||||
)
|
||||
}
|
||||
)
|
||||
is DiscussionView.Message.Attachment.Link -> {
|
||||
AttachedObject(
|
||||
modifier = Modifier.padding(
|
||||
start = 16.dp,
|
||||
end = 16.dp,
|
||||
top = 8.dp
|
||||
),
|
||||
title = attachment.wrapper?.name.orEmpty(),
|
||||
type = attachment.wrapper?.type?.firstOrNull().orEmpty(),
|
||||
icon = ObjectIcon.None,
|
||||
onAttachmentClicked = {
|
||||
onAttachmentClicked(attachment)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (reactions.isNotEmpty()) {
|
||||
ReactionList(
|
||||
|
@ -1298,7 +1314,7 @@ fun TopDiscussionToolbar(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun Attachment(
|
||||
fun AttachedObject(
|
||||
modifier: Modifier,
|
||||
title: String,
|
||||
type: String,
|
||||
|
@ -1426,7 +1442,7 @@ fun ReactionList(
|
|||
color = if (reaction.isSelected)
|
||||
colorResource(id = R.color.palette_very_light_orange)
|
||||
else
|
||||
colorResource(id = R.color.background_highlighted),
|
||||
colorResource(id = R.color.shape_transparent_primary),
|
||||
shape = RoundedCornerShape(100.dp)
|
||||
)
|
||||
.clip(RoundedCornerShape(100.dp))
|
||||
|
@ -1465,7 +1481,10 @@ fun ReactionList(
|
|||
.padding(
|
||||
end = 12.dp
|
||||
),
|
||||
color = colorResource(id = R.color.text_primary)
|
||||
color = if (reaction.isSelected)
|
||||
colorResource(id = R.color.text_primary)
|
||||
else
|
||||
colorResource(id = R.color.text_white)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1524,7 +1543,7 @@ fun TopDiscussionToolbarPreview() {
|
|||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO, name = "Dark Mode")
|
||||
@Composable
|
||||
fun AttachmentPreview() {
|
||||
Attachment(
|
||||
AttachedObject(
|
||||
modifier = Modifier,
|
||||
icon = ObjectIcon.None,
|
||||
type = "Project",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue