From 1d8501f7a09de6535ffdc661f3ec715074881a4c Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Wed, 14 May 2025 17:09:31 +0200 Subject: [PATCH] DROID-3444 Chats | Enhancement | Bookmark flow updates (#2399) --- .../anytype/core_models/LinkPreview.kt | 17 +++++++ .../auth/repo/block/BlockDataRepository.kt | 6 +++ .../data/auth/repo/block/BlockRemote.kt | 5 ++ .../domain/block/repo/BlockRepository.kt | 5 ++ .../anytype/domain/misc/GetLinkPreview.kt | 17 +++++++ .../domain/objects/CreateObjectFromUrl.kt | 27 +++++++++++ .../feature_chats/presentation/ChatView.kt | 5 ++ .../presentation/ChatViewModel.kt | 48 ++++++++++++++++++- .../presentation/ChatViewModelFactory.kt | 10 +++- .../anytype/feature_chats/ui/ChatBox.kt | 22 ++++++++- .../feature_chats/ui/ChatBoxAttachments.kt | 38 +++++++++++++-- .../anytype/feature_chats/ui/ChatPreviews.kt | 3 +- .../anytype/feature_chats/ui/ChatScreen.kt | 10 ++-- .../middleware/block/BlockMiddleware.kt | 9 ++++ .../middleware/interactor/Middleware.kt | 25 ++++++++++ .../anytype/middleware/mappers/Alias.kt | 4 +- .../middleware/mappers/ToCoreModelMappers.kt | 12 ++++- .../middleware/service/MiddlewareService.kt | 6 +++ .../MiddlewareServiceImplementation.kt | 26 ++++++++++ .../bookmark/CreateBookmarkViewModel.kt | 2 +- 20 files changed, 282 insertions(+), 15 deletions(-) create mode 100644 core-models/src/main/java/com/anytypeio/anytype/core_models/LinkPreview.kt create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/misc/GetLinkPreview.kt create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/objects/CreateObjectFromUrl.kt diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/LinkPreview.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/LinkPreview.kt new file mode 100644 index 0000000000..41f43566f4 --- /dev/null +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/LinkPreview.kt @@ -0,0 +1,17 @@ +package com.anytypeio.anytype.core_models + +data class LinkPreview( + val url: String, + val title: String = "", + val description: String = "", + val imageUrl: String = "", + val faviconUrl: String = "", + val type: Type = Type.Unknown +) { + enum class Type { + Unknown, + Page, + Image, + Text; + } +} \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt index b749e85d32..3d09e43e1e 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt @@ -14,6 +14,7 @@ import com.anytypeio.anytype.core_models.DeviceNetworkType import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Key +import com.anytypeio.anytype.core_models.LinkPreview import com.anytypeio.anytype.core_models.ManifestInfo import com.anytypeio.anytype.core_models.NodeUsageInfo import com.anytypeio.anytype.core_models.ObjectType @@ -1122,4 +1123,9 @@ class BlockDataRepository( override suspend fun setDataViewProperties(command: Command.SetDataViewProperties): Payload { return remote.setDataViewProperties(command) } + + override suspend fun getLinkPreview(url: Url): LinkPreview = remote.getLinkPreview(url) + + override suspend fun createObjectFromUrl(space: SpaceId, url: Url): ObjectWrapper.Basic = + remote.createObjectFromUrl(space = space, url = url) } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt index 3cbb631b6f..7d422c4ee2 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt @@ -14,6 +14,7 @@ import com.anytypeio.anytype.core_models.DeviceNetworkType import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Key +import com.anytypeio.anytype.core_models.LinkPreview import com.anytypeio.anytype.core_models.ManifestInfo import com.anytypeio.anytype.core_models.NodeUsageInfo import com.anytypeio.anytype.core_models.ObjectType @@ -476,4 +477,8 @@ interface BlockRemote { suspend fun objectTypeSetRecommendedFields(command: Command.ObjectTypeSetRecommendedFields) suspend fun setDataViewProperties(command: Command.SetDataViewProperties): Payload + + suspend fun getLinkPreview(url: Url): LinkPreview + + suspend fun createObjectFromUrl(space: SpaceId, url: Url): ObjectWrapper.Basic } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt b/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt index fc01a0fe7f..cb76b1a95b 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt @@ -14,6 +14,7 @@ import com.anytypeio.anytype.core_models.DeviceNetworkType import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Key +import com.anytypeio.anytype.core_models.LinkPreview import com.anytypeio.anytype.core_models.ManifestInfo import com.anytypeio.anytype.core_models.NodeUsageInfo import com.anytypeio.anytype.core_models.ObjectType @@ -519,4 +520,8 @@ interface BlockRepository { suspend fun objectTypeSetRecommendedFields(command: Command.ObjectTypeSetRecommendedFields) suspend fun setDataViewProperties(command: Command.SetDataViewProperties): Payload + + suspend fun getLinkPreview(url: Url): LinkPreview + + suspend fun createObjectFromUrl(space: SpaceId, url: Url): ObjectWrapper.Basic } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/misc/GetLinkPreview.kt b/domain/src/main/java/com/anytypeio/anytype/domain/misc/GetLinkPreview.kt new file mode 100644 index 0000000000..42adeb6178 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/misc/GetLinkPreview.kt @@ -0,0 +1,17 @@ +package com.anytypeio.anytype.domain.misc + +import com.anytypeio.anytype.core_models.LinkPreview +import com.anytypeio.anytype.core_models.Url +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.base.ResultInteractor +import com.anytypeio.anytype.domain.block.repo.BlockRepository +import javax.inject.Inject + +class GetLinkPreview @Inject constructor( + private val repo: BlockRepository, + dispatchers: AppCoroutineDispatchers +) : ResultInteractor(dispatchers.io) { + override suspend fun doWork(params: Url): LinkPreview { + return repo.getLinkPreview(url = params) + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/objects/CreateObjectFromUrl.kt b/domain/src/main/java/com/anytypeio/anytype/domain/objects/CreateObjectFromUrl.kt new file mode 100644 index 0000000000..e1e0a0cf3e --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/objects/CreateObjectFromUrl.kt @@ -0,0 +1,27 @@ +package com.anytypeio.anytype.domain.objects + +import com.anytypeio.anytype.core_models.ObjectWrapper +import com.anytypeio.anytype.core_models.Url +import com.anytypeio.anytype.core_models.primitives.SpaceId +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.base.ResultInteractor +import com.anytypeio.anytype.domain.block.repo.BlockRepository +import javax.inject.Inject + +class CreateObjectFromUrl @Inject constructor( + private val repository: BlockRepository, + dispatchers: AppCoroutineDispatchers +) : ResultInteractor(dispatchers.io) { + + override suspend fun doWork(params: Params): ObjectWrapper.Basic { + return repository.createObjectFromUrl( + space = params.space, + url = params.url + ) + } + + data class Params( + val space: SpaceId, + val url: Url + ) +} \ No newline at end of file diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatView.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatView.kt index 22da7b30ed..6df7543de8 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatView.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatView.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.feature_chats.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.LinkPreview import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.Url import com.anytypeio.anytype.domain.chats.ChatContainer @@ -129,6 +130,10 @@ sealed interface ChatView { val wrapper: GlobalSearchItemView ): ChatBoxAttachment() + data class Bookmark( + val preview: LinkPreview + ) : ChatBoxAttachment() + sealed class State { data object Idle : State() data object Uploading : State() diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt index b101abcd83..312b3a1d5b 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt @@ -4,8 +4,10 @@ import androidx.lifecycle.viewModelScope import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.LinkPreview import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.ObjectWrapper +import com.anytypeio.anytype.core_models.Url 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 @@ -23,10 +25,12 @@ import com.anytypeio.anytype.domain.chats.DeleteChatMessage import com.anytypeio.anytype.domain.chats.EditChatMessage import com.anytypeio.anytype.domain.chats.ToggleChatMessageReaction import com.anytypeio.anytype.domain.media.UploadFile +import com.anytypeio.anytype.domain.misc.GetLinkPreview import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.multiplayer.ActiveSpaceMemberSubscriptionContainer import com.anytypeio.anytype.domain.multiplayer.ActiveSpaceMemberSubscriptionContainer.Store import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer +import com.anytypeio.anytype.domain.objects.CreateObjectFromUrl import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.getTypeOfObject import com.anytypeio.anytype.feature_chats.BuildConfig @@ -76,6 +80,8 @@ class ChatViewModel @Inject constructor( private val storeOfObjectTypes: StoreOfObjectTypes, private val copyFileToCacheDirectory: CopyFileToCacheDirectory, private val exitToVaultDelegate: ExitToVaultDelegate, + private val getLinkPreview: GetLinkPreview, + private val createObjectFromUrl: CreateObjectFromUrl ) : BaseViewModel(), ExitToVaultDelegate by exitToVaultDelegate { private val visibleRangeUpdates = MutableSharedFlow>( @@ -489,6 +495,27 @@ class ChatViewModel @Inject constructor( } } } + is ChatView.Message.ChatBoxAttachment.Bookmark -> { + createObjectFromUrl.async( + params = CreateObjectFromUrl.Params( + url = attachment.preview.url, + space = vmParams.space + ) + ).onSuccess { obj -> + if (obj.isValid) { + add( + Chat.Message.Attachment( + target = obj.id, + type = Chat.Message.Attachment.Type.Link + ) + ) + } else { + Timber.w("DROID-2966 Created object from URL is not valid") + } + }.onFailure { + Timber.e(it, "DROID-2966 Error while creating object from url") + } + } is ChatView.Message.ChatBoxAttachment.File -> { val path = withContext(dispatchers.io) { copyFileToCacheDirectory.copy(attachment.uri) @@ -961,6 +988,25 @@ class ChatViewModel @Inject constructor( visibleRangeUpdates.tryEmit(from to to) } + fun onUrlPasted(url: Url) { + viewModelScope.launch { + getLinkPreview.async( + params = url + ).onSuccess { preview -> + chatBoxAttachments.value = buildList { + addAll(chatBoxAttachments.value) + add( + ChatView.Message.ChatBoxAttachment.Bookmark( + preview = preview + ) + ) + } + }.onFailure { + Timber.e(it, "Failed to get link preview") + } + } + } + /** * Used for testing. Will be deleted. */ @@ -1021,7 +1067,7 @@ class ChatViewModel @Inject constructor( ): ChatBoxMode() } - fun ChatBoxMode.updateIsSendingBlocked(isBlocked: Boolean): ChatBoxMode { + private fun ChatBoxMode.updateIsSendingBlocked(isBlocked: Boolean): ChatBoxMode { return when (this) { is ChatBoxMode.Default -> copy(isSendingMessageBlocked = isBlocked) is ChatBoxMode.EditMessage -> copy(isSendingMessageBlocked = isBlocked) diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModelFactory.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModelFactory.kt index b326a08b6f..0735a55078 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModelFactory.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModelFactory.kt @@ -10,11 +10,13 @@ import com.anytypeio.anytype.domain.chats.DeleteChatMessage import com.anytypeio.anytype.domain.chats.EditChatMessage import com.anytypeio.anytype.domain.chats.ToggleChatMessageReaction import com.anytypeio.anytype.domain.media.UploadFile +import com.anytypeio.anytype.domain.misc.GetLinkPreview import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.multiplayer.ActiveSpaceMemberSubscriptionContainer import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer import com.anytypeio.anytype.domain.`object`.OpenObject import com.anytypeio.anytype.domain.`object`.SetObjectDetails +import com.anytypeio.anytype.domain.objects.CreateObjectFromUrl import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory import com.anytypeio.anytype.presentation.vault.ExitToVaultDelegate @@ -35,7 +37,9 @@ class ChatViewModelFactory @Inject constructor( private val uploadFile: UploadFile, private val storeOfObjectTypes: StoreOfObjectTypes, private val copyFileToCacheDirectory: CopyFileToCacheDirectory, - private val exitToVaultDelegate: ExitToVaultDelegate + private val exitToVaultDelegate: ExitToVaultDelegate, + private val getLinkPreview: GetLinkPreview, + private val createObjectFromUrl: CreateObjectFromUrl ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T = ChatViewModel( @@ -53,6 +57,8 @@ class ChatViewModelFactory @Inject constructor( uploadFile = uploadFile, storeOfObjectTypes = storeOfObjectTypes, copyFileToCacheDirectory = copyFileToCacheDirectory, - exitToVaultDelegate = exitToVaultDelegate + exitToVaultDelegate = exitToVaultDelegate, + getLinkPreview = getLinkPreview, + createObjectFromUrl = createObjectFromUrl ) as T } \ No newline at end of file diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBox.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBox.kt index 48b5a19c67..ed2f7b7794 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBox.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBox.kt @@ -1,6 +1,7 @@ package com.anytypeio.anytype.feature_chats.ui import android.net.Uri +import android.util.Patterns import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts @@ -61,6 +62,7 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp +import com.anytypeio.anytype.core_models.Url import com.anytypeio.anytype.core_ui.common.DEFAULT_DISABLED_ALPHA import com.anytypeio.anytype.core_ui.common.DefaultPreviews import com.anytypeio.anytype.core_ui.common.FULL_ALPHA @@ -94,7 +96,8 @@ fun ChatBox( onChatBoxMediaPicked: (List) -> Unit, onChatBoxFilePicked: (List) -> Unit, onExitEditMessageMode: () -> Unit, - onValueChange: (TextFieldValue, List) -> Unit + onValueChange: (TextFieldValue, List) -> Unit, + onUrlInserted: (Url) -> Unit, ) { val length = text.text.length @@ -309,7 +312,8 @@ fun ChatBox( end = 4.dp, top = 16.dp, bottom = 16.dp - ) + ), + onUrlInserted = onUrlInserted ) if (length >= ChatConfig.MAX_MESSAGE_CHARACTER_OFFSET_LIMIT) { Box( @@ -478,15 +482,29 @@ private fun ChatBoxUserInput( text: TextFieldValue, spans: List, onValueChange: (TextFieldValue, List) -> Unit, + onUrlInserted: (Url) -> Unit, onFocusChanged: (Boolean) -> Unit ) { BasicTextField( value = text, onValueChange = { newValue -> + val newText = newValue.text val oldText = text.text // Keep a reference to the current text before updating val textLengthDifference = newText.length - oldText.length + // URL insert detection + if (textLengthDifference > 0) { + val prefixLen = newText.commonPrefixWith(oldText).length + val inserted = newText.substring(prefixLen, prefixLen + textLengthDifference) + val urlMatcher = Patterns.WEB_URL.matcher(inserted) + if (urlMatcher.find()) { + val url = urlMatcher.group() + onUrlInserted(url) + } + } + + // SPANS normalization val updatedSpans = spans.mapNotNull { span -> // Detect the common prefix length val commonPrefixLength = newText.commonPrefixWith(oldText).length diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBoxAttachments.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBoxAttachments.kt index 6a72e05ffc..262d319a99 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBoxAttachments.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBoxAttachments.kt @@ -58,7 +58,7 @@ internal fun ChatBoxAttachments( type = attachment.typeName, icon = attachment.icon, onAttachmentClicked = { - // TODO + // Do nothing } ) Image( @@ -90,7 +90,7 @@ internal fun ChatBoxAttachments( type = attachment.wrapper.type, icon = attachment.wrapper.icon, onAttachmentClicked = { - // TODO + // Do nothing } ) Image( @@ -191,7 +191,7 @@ internal fun ChatBoxAttachments( fileName = null ), onAttachmentClicked = { - // TODO + // Do nothing } ) Image( @@ -210,6 +210,38 @@ internal fun ChatBoxAttachments( } } } + is ChatView.Message.ChatBoxAttachment.Bookmark -> { + item { + Box { + AttachedObject( + modifier = Modifier + .padding( + top = 12.dp, + end = 4.dp + ) + .width(216.dp), + title = attachment.preview.title, + type = stringResource(R.string.bookmark), + icon = ObjectIcon.None, + onAttachmentClicked = { + // Do nothing + } + ) + Image( + painter = painterResource(id = R.drawable.ic_clear_chatbox_attachment), + contentDescription = "Close icon", + modifier = Modifier + .align( + Alignment.TopEnd + ) + .padding(top = 6.dp) + .noRippleClickable { + onClearAttachmentClicked(attachment) + } + ) + } + } + } } } } diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatPreviews.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatPreviews.kt index 96b5c8887b..debf0a22e2 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatPreviews.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatPreviews.kt @@ -200,7 +200,8 @@ fun ChatScreenPreview() { onScrollToReplyClicked = {}, onClearIntent = {}, onScrollToBottomClicked = {}, - onVisibleRangeChanged = { _, _ -> } + onVisibleRangeChanged = { _, _ -> }, + onUrlInserted = {} ) } diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt index 61dc779235..b9e0bef2f4 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt @@ -70,6 +70,7 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.Url import com.anytypeio.anytype.core_ui.foundation.AlertConfig import com.anytypeio.anytype.core_ui.foundation.AlertIcon import com.anytypeio.anytype.core_ui.foundation.Divider @@ -238,7 +239,8 @@ fun ChatScreenWrapper( onScrollToReplyClicked = vm::onChatScrollToReply, onClearIntent = vm::onClearChatViewStateIntent, onScrollToBottomClicked = vm::onScrollToBottomClicked, - onVisibleRangeChanged = vm::onVisibleRangeChanged + onVisibleRangeChanged = vm::onVisibleRangeChanged, + onUrlInserted = vm::onUrlPasted ) LaunchedEffect(Unit) { vm.uXCommands.collect { command -> @@ -352,7 +354,8 @@ fun ChatScreen( onScrollToReplyClicked: (Id) -> Unit, onClearIntent: () -> Unit, onScrollToBottomClicked: (Id?) -> Unit, - onVisibleRangeChanged: (Id, Id) -> Unit + onVisibleRangeChanged: (Id, Id) -> Unit, + onUrlInserted: (Url) -> Unit, ) { Timber.d("DROID-2966 Render called with state, number of messages: ${messages.size}") @@ -688,7 +691,8 @@ fun ChatScreen( onTextChanged(t) }, text = text, - spans = spans + spans = spans, + onUrlInserted = onUrlInserted ) } } diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt index 7c0f9931c3..460ed796fb 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt @@ -15,6 +15,7 @@ import com.anytypeio.anytype.core_models.DeviceNetworkType import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Key +import com.anytypeio.anytype.core_models.LinkPreview import com.anytypeio.anytype.core_models.ManifestInfo import com.anytypeio.anytype.core_models.NodeUsageInfo import com.anytypeio.anytype.core_models.ObjectType @@ -1092,4 +1093,12 @@ class BlockMiddleware( override suspend fun setDataViewProperties(command: Command.SetDataViewProperties): Payload { return middleware.setDataViewProperties(command) } + + override suspend fun getLinkPreview(url: Url): LinkPreview { + return middleware.getLinkPreview(url) + } + + override suspend fun createObjectFromUrl(space: SpaceId, url: Url): ObjectWrapper.Basic { + return middleware.createObjectFromUrl(space = space, url = url) + } } \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt index 59fb6aa7e5..4208f65a6a 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt @@ -22,9 +22,11 @@ import com.anytypeio.anytype.core_models.DeviceNetworkType import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Key +import com.anytypeio.anytype.core_models.LinkPreview import com.anytypeio.anytype.core_models.ManifestInfo import com.anytypeio.anytype.core_models.NodeUsageInfo import com.anytypeio.anytype.core_models.ObjectType +import com.anytypeio.anytype.core_models.ObjectTypeUniqueKeys import com.anytypeio.anytype.core_models.ObjectView import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.Payload @@ -64,6 +66,7 @@ import com.anytypeio.anytype.middleware.mappers.core import com.anytypeio.anytype.middleware.mappers.mw import com.anytypeio.anytype.middleware.mappers.parse import com.anytypeio.anytype.middleware.mappers.toCore +import com.anytypeio.anytype.middleware.mappers.toCoreLinkPreview import com.anytypeio.anytype.middleware.mappers.toCoreModel import com.anytypeio.anytype.middleware.mappers.toCoreModelSearchResults import com.anytypeio.anytype.middleware.mappers.toCoreModels @@ -2990,6 +2993,28 @@ class Middleware @Inject constructor( logResponseIfDebug(response, time) } + @Throws(Exception::class) + fun getLinkPreview(url: Url): LinkPreview { + val request = Rpc.LinkPreview.Request(url = url) + logRequestIfDebug(request) + val (response, time) = measureTimedValue { service.linkPreview(request) } + logResponseIfDebug(response, time) + return response.linkPreview?.toCoreLinkPreview() ?: throw Exception("MW return empty link preview") + } + + @Throws(Exception::class) + fun createObjectFromUrl(space: SpaceId, url: Url) : ObjectWrapper.Basic { + val request = Rpc.Object.CreateFromUrl.Request( + url = url, + spaceId = space.id, + objectTypeUniqueKey = ObjectTypeUniqueKeys.BOOKMARK + ) + logRequestIfDebug(request) + val (response, time) = measureTimedValue { service.objectCreateFromUrl(request) } + logResponseIfDebug(response, time) + return ObjectWrapper.Basic(response.details.orEmpty()) + } + private fun logRequestIfDebug(request: Any) { if (BuildConfig.DEBUG) { logger.logRequest(request).also { diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt index 0c538b91ec..cf55b96ef4 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt @@ -112,4 +112,6 @@ typealias MP2PStatus = anytype.Event.P2PStatus.Status typealias MP2PStatusUpdate = P2PStatus.Update typealias MSyncStatusUpdate = Space.SyncStatus.Update -typealias MDeviceNetworkType = anytype.model.DeviceNetworkType \ No newline at end of file +typealias MDeviceNetworkType = anytype.model.DeviceNetworkType + +typealias MLinkPreview = anytype.model.LinkPreview \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt index 4cb218c14a..211e7c8bcd 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt @@ -3,7 +3,6 @@ package com.anytypeio.anytype.middleware.mappers import anytype.ResponseEvent import anytype.Rpc import anytype.model.Account -import anytype.model.ChatState import anytype.model.NameserviceNameType import anytype.model.ParticipantPermissions import anytype.model.Restrictions @@ -28,6 +27,7 @@ import com.anytypeio.anytype.core_models.DVViewerType import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.ImportErrorCode +import com.anytypeio.anytype.core_models.LinkPreview import com.anytypeio.anytype.core_models.ManifestInfo import com.anytypeio.anytype.core_models.NodeUsage import com.anytypeio.anytype.core_models.NodeUsageInfo @@ -1207,4 +1207,14 @@ fun Rpc.Relation.ListWithValue.Response.ResponseItem.toCoreModel(): RelationList key = RelationKey(key = relationKey), counter = counter ) +} + +fun MLinkPreview.toCoreLinkPreview(): LinkPreview { + return LinkPreview( + url = url, + faviconUrl = faviconUrl, + imageUrl = imageUrl, + description = description, + title = title + ) } \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt index c6d222c477..707799f816 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt @@ -637,4 +637,10 @@ interface MiddlewareService { @Throws(Exception::class) fun pushNotificationRegisterToken(request: Rpc.PushNotification.RegisterToken.Request): Rpc.PushNotification.RegisterToken.Response + + @Throws(Exception::class) + fun linkPreview(request: Rpc.LinkPreview.Request) : Rpc.LinkPreview.Response + + @Throws(Exception::class) + fun objectCreateFromUrl(request: Rpc.Object.CreateFromUrl.Request): Rpc.Object.CreateFromUrl.Response } \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt index 4977638efc..5ea84838e6 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt @@ -2586,4 +2586,30 @@ class MiddlewareServiceImplementation @Inject constructor( return response } } + + override fun linkPreview(request: Rpc.LinkPreview.Request): Rpc.LinkPreview.Response { + val encoded = Service.linkPreview( + Rpc.LinkPreview.Request.ADAPTER.encode(request) + ) + val response = Rpc.LinkPreview.Response.ADAPTER.decode(encoded) + val error = response.error + if (error != null && error.code != Rpc.LinkPreview.Response.Error.Code.NULL) { + throw Exception(error.description) + } else { + return response + } + } + + override fun objectCreateFromUrl(request: Rpc.Object.CreateFromUrl.Request): Rpc.Object.CreateFromUrl.Response { + val encoded = Service.objectCreateFromUrl( + Rpc.Object.CreateFromUrl.Request.ADAPTER.encode(request) + ) + val response = Rpc.Object.CreateFromUrl.Response.ADAPTER.decode(encoded) + val error = response.error + if (error != null && error.code != Rpc.Object.CreateFromUrl.Response.Error.Code.NULL) { + throw Exception(error.description) + } else { + return response + } + } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/bookmark/CreateBookmarkViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/bookmark/CreateBookmarkViewModel.kt index 7d40f563de..c2ea2a4770 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/bookmark/CreateBookmarkViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/bookmark/CreateBookmarkViewModel.kt @@ -16,7 +16,7 @@ class CreateBookmarkViewModel() : ViewStateViewModel() { sealed class ViewState { data class Success(val url: String) : ViewState() data class Error(val message: String) : ViewState() - object Exit : ViewState() + data object Exit : ViewState() } class Factory() : ViewModelProvider.Factory {