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

DROID-3444 Chats | Enhancement | Bookmark flow updates (#2399)

This commit is contained in:
Evgenii Kozlov 2025-05-14 17:09:31 +02:00 committed by GitHub
parent 191b675fc9
commit 1d8501f7a0
Signed by: github
GPG key ID: B5690EEEBB952194
20 changed files with 282 additions and 15 deletions

View file

@ -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;
}
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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<Url, LinkPreview>(dispatchers.io) {
override suspend fun doWork(params: Url): LinkPreview {
return repo.getLinkPreview(url = params)
}
}

View file

@ -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<CreateObjectFromUrl.Params, ObjectWrapper.Basic>(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
)
}

View file

@ -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()

View file

@ -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<Pair<Id, Id>>(
@ -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)

View file

@ -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 <T : ViewModel> create(modelClass: Class<T>): 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
}

View file

@ -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<Uri>) -> Unit,
onChatBoxFilePicked: (List<Uri>) -> Unit,
onExitEditMessageMode: () -> Unit,
onValueChange: (TextFieldValue, List<ChatBoxSpan>) -> Unit
onValueChange: (TextFieldValue, List<ChatBoxSpan>) -> 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<ChatBoxSpan>,
onValueChange: (TextFieldValue, List<ChatBoxSpan>) -> 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

View file

@ -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)
}
)
}
}
}
}
}
}

View file

@ -200,7 +200,8 @@ fun ChatScreenPreview() {
onScrollToReplyClicked = {},
onClearIntent = {},
onScrollToBottomClicked = {},
onVisibleRangeChanged = { _, _ -> }
onVisibleRangeChanged = { _, _ -> },
onUrlInserted = {}
)
}

View file

@ -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
)
}
}

View file

@ -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)
}
}

View file

@ -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 {

View file

@ -112,4 +112,6 @@ typealias MP2PStatus = anytype.Event.P2PStatus.Status
typealias MP2PStatusUpdate = P2PStatus.Update
typealias MSyncStatusUpdate = Space.SyncStatus.Update
typealias MDeviceNetworkType = anytype.model.DeviceNetworkType
typealias MDeviceNetworkType = anytype.model.DeviceNetworkType
typealias MLinkPreview = anytype.model.LinkPreview

View file

@ -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
)
}

View file

@ -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
}

View file

@ -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
}
}
}

View file

@ -16,7 +16,7 @@ class CreateBookmarkViewModel() : ViewStateViewModel<ViewState>() {
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 {