diff options
author | Tim Krüger <t@timkrueger.me> | 2022-10-06 14:19:14 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-06 14:19:14 +0300 |
commit | 009cd5a42525b0383cf19fcc35c2221ba3f9dcf3 (patch) | |
tree | 7951dcf65a9dd89666f93d307d4ac539d7446c9d /app | |
parent | b4cf0b722a4ab88164b0286ef6d4b9150e4fd98d (diff) | |
parent | 66eea709b48b65358b28e7b68d0a2f257a56b786 (diff) |
Merge pull request #2460 from nextcloud/feature/2419/linkPreviews
add openGraph link previews
Diffstat (limited to 'app')
30 files changed, 1190 insertions, 59 deletions
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/ReactionsInterface.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/CommonMessageInterface.kt index 589c6e363..a40509b30 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/ReactionsInterface.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/CommonMessageInterface.kt @@ -2,7 +2,7 @@ package com.nextcloud.talk.adapters.messages import com.nextcloud.talk.models.json.chat.ChatMessage -interface ReactionsInterface { +interface CommonMessageInterface { fun onClickReactions(chatMessage: ChatMessage) - fun onLongClickReactions(chatMessage: ChatMessage) + fun onOpenMessageActionsDialog(chatMessage: ChatMessage) } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt new file mode 100644 index 000000000..14f96025c --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt @@ -0,0 +1,211 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * @author Marcel Hibbe + * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de> + * Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.nextcloud.talk.adapters.messages + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable +import android.os.Build +import android.text.TextUtils +import android.view.View +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import autodagger.AutoInjector +import coil.load +import com.amulyakhare.textdrawable.TextDrawable +import com.nextcloud.talk.R +import com.nextcloud.talk.api.NcApi +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.databinding.ItemCustomIncomingLinkPreviewMessageBinding +import com.nextcloud.talk.models.json.chat.ChatMessage +import com.nextcloud.talk.ui.theme.ViewThemeUtils +import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.DisplayUtils +import com.nextcloud.talk.utils.preferences.AppPreferences +import com.stfalcon.chatkit.messages.MessageHolders +import javax.inject.Inject + +@AutoInjector(NextcloudTalkApplication::class) +class IncomingLinkPreviewMessageViewHolder(incomingView: View, payload: Any) : MessageHolders +.IncomingTextMessageViewHolder<ChatMessage>(incomingView, payload) { + + private val binding: ItemCustomIncomingLinkPreviewMessageBinding = + ItemCustomIncomingLinkPreviewMessageBinding.bind(itemView) + + @Inject + lateinit var context: Context + + @Inject + lateinit var appPreferences: AppPreferences + + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + + @Inject + lateinit var ncApi: NcApi + + lateinit var message: ChatMessage + + lateinit var commonMessageInterface: CommonMessageInterface + + @SuppressLint("SetTextI18n") + override fun onBind(message: ChatMessage) { + super.onBind(message) + this.message = message + sharedApplication!!.componentApplication.inject(this) + + setAvatarAndAuthorOnMessageItem(message) + + colorizeMessageBubble(message) + + itemView.isSelected = false + + // parent message handling + setParentMessageDataOnMessageItem(message) + + LinkPreview().showLink( + message, + ncApi, + binding.referenceInclude, + context + ) + binding.referenceInclude.referenceWrapper.setOnLongClickListener { l: View? -> + commonMessageInterface.onOpenMessageActionsDialog(message) + true + } + + Reaction().showReactions( + message, + binding.reactions, + binding.messageTime.context, + false, + viewThemeUtils + ) + binding.reactions.reactionsEmojiWrapper.setOnClickListener { + commonMessageInterface.onClickReactions(message) + } + binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? -> + commonMessageInterface.onOpenMessageActionsDialog(message) + true + } + } + + private fun setAvatarAndAuthorOnMessageItem(message: ChatMessage) { + val author: String = message.actorDisplayName!! + if (!TextUtils.isEmpty(author)) { + binding.messageAuthor.text = author + binding.messageUserAvatar.setOnClickListener { + (payload as? MessagePayload)?.profileBottomSheet?.showFor(message.actorId!!, itemView.context) + } + } else { + binding.messageAuthor.setText(R.string.nc_nick_guest) + } + + if (!message.isGrouped && !message.isOneToOneConversation) { + setAvatarOnMessage(message) + } else { + if (message.isOneToOneConversation) { + binding.messageUserAvatar.visibility = View.GONE + } else { + binding.messageUserAvatar.visibility = View.INVISIBLE + } + binding.messageAuthor.visibility = View.GONE + } + } + + private fun setAvatarOnMessage(message: ChatMessage) { + binding.messageUserAvatar.visibility = View.VISIBLE + if (message.actorType == "guests") { + // do nothing, avatar is set + } else if (message.actorType == "bots" && message.actorId == "changelog") { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val layers = arrayOfNulls<Drawable>(2) + layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background) + layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground) + val layerDrawable = LayerDrawable(layers) + binding.messageUserAvatar.setImageDrawable(DisplayUtils.getRoundedDrawable(layerDrawable)) + } else { + binding.messageUserAvatar.setImageResource(R.mipmap.ic_launcher) + } + } else if (message.actorType == "bots") { + val drawable = TextDrawable.builder() + .beginConfig() + .bold() + .endConfig() + .buildRound( + ">", + ResourcesCompat.getColor(context.resources, R.color.black, null) + ) + binding.messageUserAvatar.visibility = View.VISIBLE + binding.messageUserAvatar.setImageDrawable(drawable) + } + } + + private fun colorizeMessageBubble(message: ChatMessage) { + viewThemeUtils.talk.themeIncomingMessageBubble(bubble, message.isGrouped, message.isDeleted) + } + + private fun setParentMessageDataOnMessageItem(message: ChatMessage) { + if (!message.isDeleted && message.parentMessage != null) { + val parentChatMessage = message.parentMessage + parentChatMessage!!.activeUser = message.activeUser + parentChatMessage.imageUrl?.let { + binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE + binding.messageQuote.quotedMessageImage.load(it) { + addHeader( + "Authorization", + ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token) + ) + } + } ?: run { + binding.messageQuote.quotedMessageImage.visibility = View.GONE + } + binding.messageQuote.quotedMessageAuthor.text = parentChatMessage.actorDisplayName + ?: context.getText(R.string.nc_nick_guest) + binding.messageQuote.quotedMessage.text = parentChatMessage.text + + binding.messageQuote.quotedMessageAuthor + .setTextColor(ContextCompat.getColor(context, R.color.textColorMaxContrast)) + + if (parentChatMessage.actorId?.equals(message.activeUser!!.userId) == true) { + viewThemeUtils.platform.colorPrimaryView(binding.messageQuote.quoteColoredView) + } else { + binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.textColorMaxContrast) + } + + binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE + } else { + binding.messageQuote.quotedChatMessageView.visibility = View.GONE + } + } + + fun assignCommonMessageInterface(commonMessageInterface: CommonMessageInterface) { + this.commonMessageInterface = commonMessageInterface + } + + companion object { + private val TAG = IncomingLinkPreviewMessageViewHolder::class.java.simpleName + } +} diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt index d579503c7..64b489216 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt @@ -78,7 +78,7 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : Mess @Inject lateinit var viewThemeUtils: ViewThemeUtils - lateinit var reactionsInterface: ReactionsInterface + lateinit var commonMessageInterface: CommonMessageInterface @SuppressLint("SetTextI18n") override fun onBind(message: ChatMessage) { @@ -109,10 +109,10 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : Mess viewThemeUtils ) binding.reactions.reactionsEmojiWrapper.setOnClickListener { - reactionsInterface.onClickReactions(message) + commonMessageInterface.onClickReactions(message) } binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? -> - reactionsInterface.onLongClickReactions(message) + commonMessageInterface.onOpenMessageActionsDialog(message) true } } @@ -267,8 +267,8 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : Mess return locationGeoLink.replace("geo:", "geo:0,0?q=") } - fun assignReactionInterface(reactionsInterface: ReactionsInterface) { - this.reactionsInterface = reactionsInterface + fun assignCommonMessageInterface(commonMessageInterface: CommonMessageInterface) { + this.commonMessageInterface = commonMessageInterface } companion object { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt index 937c95ed5..d3e7dcd78 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt @@ -68,7 +68,7 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) : MessageH lateinit var message: ChatMessage - lateinit var reactionsInterface: ReactionsInterface + lateinit var commonMessageInterface: CommonMessageInterface @SuppressLint("SetTextI18n") override fun onBind(message: ChatMessage) { @@ -95,10 +95,10 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) : MessageH viewThemeUtils ) binding.reactions.reactionsEmojiWrapper.setOnClickListener { - reactionsInterface.onClickReactions(message) + commonMessageInterface.onClickReactions(message) } binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? -> - reactionsInterface.onLongClickReactions(message) + commonMessageInterface.onOpenMessageActionsDialog(message) true } } @@ -228,8 +228,8 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) : MessageH } } - fun assignReactionInterface(reactionsInterface: ReactionsInterface) { - this.reactionsInterface = reactionsInterface + fun assignCommonMessageInterface(commonMessageInterface: CommonMessageInterface) { + this.commonMessageInterface = commonMessageInterface } companion object { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt index a11e737a4..10f6284a9 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt @@ -76,7 +76,7 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : Message lateinit var message: ChatMessage lateinit var voiceMessageInterface: VoiceMessageInterface - lateinit var reactionsInterface: ReactionsInterface + lateinit var commonMessageInterface: CommonMessageInterface @SuppressLint("SetTextI18n") override fun onBind(message: ChatMessage) { @@ -153,10 +153,10 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : Message viewThemeUtils ) binding.reactions.reactionsEmojiWrapper.setOnClickListener { - reactionsInterface.onClickReactions(message) + commonMessageInterface.onClickReactions(message) } binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? -> - reactionsInterface.onLongClickReactions(message) + commonMessageInterface.onOpenMessageActionsDialog(message) true } } @@ -307,8 +307,8 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : Message this.voiceMessageInterface = voiceMessageInterface } - fun assignReactionInterface(reactionsInterface: ReactionsInterface) { - this.reactionsInterface = reactionsInterface + fun assignCommonMessageInterface(commonMessageInterface: CommonMessageInterface) { + this.commonMessageInterface = commonMessageInterface } companion object { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt new file mode 100644 index 000000000..fd78f6200 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt @@ -0,0 +1,122 @@ +/* + * Nextcloud Talk application + * + * @author Marcel Hibbe + * Copyright (C) 2022 Marcel Hibbe (dev@mhibbe.de) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Parts related to account import were either copied from or inspired by the great work done by David Luhmer at: + * https://github.com/nextcloud/ownCloud-Account-Importer + */ + +package com.nextcloud.talk.adapters.messages + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.util.Log +import android.view.View +import com.facebook.drawee.backends.pipeline.Fresco +import com.facebook.drawee.interfaces.DraweeController +import com.nextcloud.talk.api.NcApi +import com.nextcloud.talk.databinding.ReferenceInsideMessageBinding +import com.nextcloud.talk.models.json.chat.ChatMessage +import com.nextcloud.talk.models.json.opengraph.OpenGraphOverall +import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.DisplayUtils +import io.reactivex.Observer +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers + +class LinkPreview { + + fun showLink( + message: ChatMessage, + ncApi: NcApi, + binding: ReferenceInsideMessageBinding, + context: Context + ) { + if (!message.extractedUrlToPreview.isNullOrEmpty()) { + val credentials: String = ApiUtils.getCredentials(message.activeUser?.username, message.activeUser?.token) + val openGraphLink = ApiUtils.getUrlForOpenGraph(message.activeUser?.baseUrl) + ncApi.getOpenGraph( + credentials, + openGraphLink, + message.extractedUrlToPreview + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : Observer<OpenGraphOverall> { + override fun onSubscribe(d: Disposable) { + // unused atm + } + + override fun onNext(openGraphOverall: OpenGraphOverall) { + val reference = openGraphOverall.ocs?.data?.references?.entries?.iterator()?.next()?.value + + if (reference != null) { + val referenceName = reference.openGraphObject?.name + if (!referenceName.isNullOrEmpty()) { + binding.referenceName.visibility = View.VISIBLE + binding.referenceName.text = referenceName + } else { + binding.referenceName.visibility = View.GONE + } + + val referenceLink = reference.openGraphObject?.link + if (!referenceLink.isNullOrEmpty()) { + binding.referenceLink.visibility = View.VISIBLE + binding.referenceLink.text = referenceLink + } else { + binding.referenceLink.visibility = View.GONE + } + + val referenceThumbUrl = reference.openGraphObject?.thumb + if (!referenceThumbUrl.isNullOrEmpty()) { + binding.referenceThumbImage.visibility = View.VISIBLE + val draweeController: DraweeController = Fresco.newDraweeControllerBuilder() + .setAutoPlayAnimations(true) + .setImageRequest(DisplayUtils.getImageRequestForUrl(referenceThumbUrl)) + .build() + binding.referenceThumbImage.controller = + draweeController + } else { + binding.referenceThumbImage.visibility = View.GONE + } + + binding.referenceWrapper.setOnClickListener { + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(referenceLink)) + browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(browserIntent) + } + } + } + + override fun onError(e: Throwable) { + Log.e(TAG, "failed to get openGraph data", e) + } + + override fun onComplete() { + // unused atm + } + }) + } + } + + companion object { + private val TAG = LinkPreview::class.java.simpleName + } +} diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.kt index db93d9e90..c8f2a7a2e 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.kt @@ -69,7 +69,7 @@ class MagicIncomingTextMessageViewHolder(itemView: View, payload: Any) : Message @Inject lateinit var appPreferences: AppPreferences - lateinit var reactionsInterface: ReactionsInterface + lateinit var commonMessageInterface: CommonMessageInterface override fun onBind(message: ChatMessage) { super.onBind(message) @@ -125,10 +125,10 @@ class MagicIncomingTextMessageViewHolder(itemView: View, payload: Any) : Message viewThemeUtils ) binding.reactions.reactionsEmojiWrapper.setOnClickListener { - reactionsInterface.onClickReactions(message) + commonMessageInterface.onClickReactions(message) } binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? -> - reactionsInterface.onLongClickReactions(message) + commonMessageInterface.onOpenMessageActionsDialog(message) true } } @@ -249,8 +249,8 @@ class MagicIncomingTextMessageViewHolder(itemView: View, payload: Any) : Message return messageStringInternal } - fun assignReactionInterface(reactionsInterface: ReactionsInterface) { - this.reactionsInterface = reactionsInterface + fun assignCommonMessageInterface(commonMessageInterface: CommonMessageInterface) { + this.commonMessageInterface = commonMessageInterface } companion object { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.kt index 4ae870b0f..66a0d4f3f 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.kt @@ -60,7 +60,7 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage @Inject lateinit var viewThemeUtils: ViewThemeUtils - lateinit var reactionsInterface: ReactionsInterface + lateinit var commonMessageInterface: CommonMessageInterface override fun onBind(message: ChatMessage) { super.onBind(message) @@ -123,10 +123,10 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage Reaction().showReactions(message, binding.reactions, context, true, viewThemeUtils) binding.reactions.reactionsEmojiWrapper.setOnClickListener { - reactionsInterface.onClickReactions(message) + commonMessageInterface.onClickReactions(message) } binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? -> - reactionsInterface.onLongClickReactions(message) + commonMessageInterface.onOpenMessageActionsDialog(message) true } } @@ -193,8 +193,8 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage return messageString1 } - fun assignReactionInterface(reactionsInterface: ReactionsInterface) { - this.reactionsInterface = reactionsInterface + fun assignCommonMessageInterface(commonMessageInterface: CommonMessageInterface) { + this.commonMessageInterface = commonMessageInterface } companion object { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.java index d61e5df5f..ca7394341 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.java @@ -108,7 +108,7 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom View clickView; - ReactionsInterface reactionsInterface; + CommonMessageInterface commonMessageInterface; PreviewMessageInterface previewMessageInterface; public MagicPreviewMessageViewHolder(View itemView, Object payload) { @@ -251,10 +251,10 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom true, viewThemeUtils); reactionsBinding.reactionsEmojiWrapper.setOnClickListener(l -> { - reactionsInterface.onClickReactions(message); + commonMessageInterface.onClickReactions(message); }); reactionsBinding.reactionsEmojiWrapper.setOnLongClickListener(l -> { - reactionsInterface.onLongClickReactions(message); + commonMessageInterface.onOpenMessageActionsDialog(message); return true; }); } @@ -347,8 +347,8 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom }); } - public void assignReactionInterface(ReactionsInterface reactionsInterface) { - this.reactionsInterface = reactionsInterface; + public void assignCommonMessageInterface(CommonMessageInterface commonMessageInterface) { + this.commonMessageInterface = commonMessageInterface; } public void assignPreviewMessageInterface(PreviewMessageInterface previewMessageInterface) { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt new file mode 100644 index 000000000..7d501e646 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt @@ -0,0 +1,172 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * @author Marcel Hibbe + * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de> + * Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.nextcloud.talk.adapters.messages + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.PorterDuff +import android.view.View +import androidx.appcompat.content.res.AppCompatResources +import autodagger.AutoInjector +import coil.load +import com.nextcloud.talk.R +import com.nextcloud.talk.api.NcApi +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.databinding.ItemCustomOutcomingLinkPreviewMessageBinding +import com.nextcloud.talk.models.json.chat.ChatMessage +import com.nextcloud.talk.models.json.chat.ReadStatus +import com.nextcloud.talk.ui.theme.ViewThemeUtils +import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.preferences.AppPreferences +import com.stfalcon.chatkit.messages.MessageHolders +import javax.inject.Inject + +@AutoInjector(NextcloudTalkApplication::class) +class OutcomingLinkPreviewMessageViewHolder(outcomingView: View, payload: Any) : MessageHolders +.OutcomingTextMessageViewHolder<ChatMessage>(outcomingView, payload) { + + private val binding: ItemCustomOutcomingLinkPreviewMessageBinding = + ItemCustomOutcomingLinkPreviewMessageBinding.bind(itemView) + + @Inject + lateinit var context: Context + + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + + @Inject + lateinit var appPreferences: AppPreferences + + @Inject + lateinit var ncApi: NcApi + + lateinit var message: ChatMessage + + lateinit var commonMessageInterface: CommonMessageInterface + + @SuppressLint("SetTextI18n") + override fun onBind(message: ChatMessage) { + super.onBind(message) + this.message = message + sharedApplication!!.componentApplication.inject(this) + val textColor = viewThemeUtils.getScheme(binding.messageTime.context).onSurfaceVariant + binding.messageTime.setTextColor(textColor) + + colorizeMessageBubble(message) + + itemView.isSelected = false + + // parent message handling + setParentMessageDataOnMessageItem(message) + + val readStatusDrawableInt = when (message.readStatus) { + ReadStatus.READ -> R.drawable.ic_check_all + ReadStatus.SENT -> R.drawable.ic_check + else -> null + } + + val readStatusContentDescriptionString = when (message.readStatus) { + ReadStatus.READ -> context?.resources?.getString(R.string.nc_message_read) + ReadStatus.SENT -> context?.resources?.getString(R.string.nc_message_sent) + else -> null + } + + readStatusDrawableInt?.let { drawableInt -> + AppCompatResources.getDrawable(context, drawableInt)?.let { + binding.checkMark.setImageDrawable(it) + binding.checkMark.setColorFilter( + viewThemeUtils.getScheme(binding.checkMark.context).onSurfaceVariant, PorterDuff.Mode.SRC_ATOP + ) + } + } + + binding.checkMark.contentDescription = readStatusContentDescriptionString + + LinkPreview().showLink( + message, + ncApi, + binding.referenceInclude, + context + ) + binding.referenceInclude.referenceWrapper.setOnLongClickListener { l: View? -> + commonMessageInterface.onOpenMessageActionsDialog(message) + true + } + + Reaction().showReactions( + message, + binding.reactions, + binding.messageTime.context, + true, + viewThemeUtils + ) + binding.reactions.reactionsEmojiWrapper.setOnClickListener { + commonMessageInterface.onClickReactions(message) + } + binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? -> + commonMessageInterface.onOpenMessageActionsDialog(message) + true + } + } + + private fun setParentMessageDataOnMessageItem(message: ChatMessage) { + if (!message.isDeleted && message.parentMessage != null) { + val parentChatMessage = message.parentMessage + parentChatMessage!!.activeUser = message.activeUser + parentChatMessage.imageUrl?.let { + binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE + binding.messageQuote.quotedMessageImage.load(it) { + addHeader( + "Authorization", + ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token) + ) + } + } ?: run { + binding.messageQuote.quotedMessageImage.visibility = View.GONE + } + binding.messageQuote.quotedMessageAuthor.text = parentChatMessage.actorDisplayName + ?: context.getText(R.string.nc_nick_guest) + binding.messageQuote.quotedMessage.text = parentChatMessage.text + viewThemeUtils.talk.colorOutgoingQuoteText(binding.messageQuote.quotedMessage) + viewThemeUtils.talk.colorOutgoingQuoteAuthorText(binding.messageQuote.quotedMessageAuthor) + viewThemeUtils.talk.colorOutgoingQuoteBackground(binding.messageQuote.quoteColoredView) + + binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE + } else { + binding.messageQuote.quotedChatMessageView.visibility = View.GONE + } + } + + private fun colorizeMessageBubble(message: ChatMessage) { + viewThemeUtils.talk.themeOutgoingMessageBubble(bubble, message.isGrouped, message.isDeleted) + } + + fun assignCommonMessageInterface(commonMessageInterface: CommonMessageInterface) { + this.commonMessageInterface = commonMessageInterface + } + + companion object { + private val TAG = OutcomingLinkPreviewMessageViewHolder::class.java.simpleName + } +} diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt index 209b24496..148c45a4d 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt @@ -70,7 +70,7 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders @Inject lateinit var viewThemeUtils: ViewThemeUtils - lateinit var reactionsInterface: ReactionsInterface + lateinit var commonMessageInterface: CommonMessageInterface @SuppressLint("SetTextI18n") override fun onBind(message: ChatMessage) { @@ -128,10 +128,10 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders viewThemeUtils ) binding.reactions.reactionsEmojiWrapper.setOnClickListener { - reactionsInterface.onClickReactions(message) + commonMessageInterface.onClickReactions(message) } binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? -> - reactionsInterface.onLongClickReactions(message) + commonMessageInterface.onOpenMessageActionsDialog(message) true } } @@ -242,8 +242,8 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders return locationGeoLink.replace("geo:", "geo:0,0?q=") } - fun assignReactionInterface(reactionsInterface: ReactionsInterface) { - this.reactionsInterface = reactionsInterface + fun assignCommonMessageInterface(commonMessageInterface: CommonMessageInterface) { + this.commonMessageInterface = commonMessageInterface } companion object { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt index 47de01e13..0fb552edd 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt @@ -64,7 +64,7 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) : Messag lateinit var message: ChatMessage - lateinit var reactionsInterface: ReactionsInterface + lateinit var commonMessageInterface: CommonMessageInterface @SuppressLint("SetTextI18n") override fun onBind(message: ChatMessage) { @@ -114,10 +114,10 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) : Messag viewThemeUtils ) binding.reactions.reactionsEmojiWrapper.setOnClickListener { - reactionsInterface.onClickReactions(message) + commonMessageInterface.onClickReactions(message) } binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? -> - reactionsInterface.onLongClickReactions(message) + commonMessageInterface.onOpenMessageActionsDialog(message) true } } @@ -190,8 +190,8 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) : Messag viewThemeUtils.talk.themeOutgoingMessageBubble(bubble, message.isGrouped, message.isDeleted) } - fun assignReactionInterface(reactionsInterface: ReactionsInterface) { - this.reactionsInterface = reactionsInterface + fun assignCommonMessageInterface(commonMessageInterface: CommonMessageInterface) { + this.commonMessageInterface = commonMessageInterface } companion object { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingVoiceMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingVoiceMessageViewHolder.kt index 96bed4b6c..845aa20c5 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingVoiceMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingVoiceMessageViewHolder.kt @@ -71,7 +71,7 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders lateinit var handler: Handler lateinit var voiceMessageInterface: VoiceMessageInterface - lateinit var reactionsInterface: ReactionsInterface + lateinit var commonMessageInterface: CommonMessageInterface @SuppressLint("SetTextI18n") override fun onBind(message: ChatMessage) { @@ -146,10 +146,10 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders viewThemeUtils ) binding.reactions.reactionsEmojiWrapper.setOnClickListener { - reactionsInterface.onClickReactions(message) + commonMessageInterface.onClickReactions(message) } binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? -> - reactionsInterface.onLongClickReactions(message) + commonMessageInterface.onOpenMessageActionsDialog(message) true } } @@ -281,8 +281,8 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders this.voiceMessageInterface = voiceMessageInterface } - fun assignReactionInterface(reactionsInterface: ReactionsInterface) { - this.reactionsInterface = reactionsInterface + fun assignCommonMessageInterface(commonMessageInterface: CommonMessageInterface) { + this.commonMessageInterface = commonMessageInterface } companion object { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java index 7f5e19f09..2947a2faa 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java @@ -50,25 +50,30 @@ public class TalkMessagesListAdapter<M extends IMessage> extends MessagesListAda super.onBindViewHolder(holder, position); if (holder instanceof MagicIncomingTextMessageViewHolder) { - ((MagicIncomingTextMessageViewHolder) holder).assignReactionInterface(chatController); + ((MagicIncomingTextMessageViewHolder) holder).assignCommonMessageInterface(chatController); } else if (holder instanceof MagicOutcomingTextMessageViewHolder) { - ((MagicOutcomingTextMessageViewHolder) holder).assignReactionInterface(chatController); + ((MagicOutcomingTextMessageViewHolder) holder).assignCommonMessageInterface(chatController); } else if (holder instanceof IncomingLocationMessageViewHolder) { - ((IncomingLocationMessageViewHolder) holder).assignReactionInterface(chatController); + ((IncomingLocationMessageViewHolder) holder).assignCommonMessageInterface(chatController); } else if (holder instanceof OutcomingLocationMessageViewHolder) { - ((OutcomingLocationMessageViewHolder) holder).assignReactionInterface(chatController); + ((OutcomingLocationMessageViewHolder) holder).assignCommonMessageInterface(chatController); + + } else if (holder instanceof IncomingLinkPreviewMessageViewHolder) { + ((IncomingLinkPreviewMessageViewHolder) holder).assignCommonMessageInterface(chatController); + } else if (holder instanceof OutcomingLinkPreviewMessageViewHolder) { + ((OutcomingLinkPreviewMessageViewHolder) holder).assignCommonMessageInterface(chatController); } else if (holder instanceof IncomingVoiceMessageViewHolder) { ((IncomingVoiceMessageViewHolder) holder).assignVoiceMessageInterface(chatController); - ((IncomingVoiceMessageViewHolder) holder).assignReactionInterface(chatController); + ((IncomingVoiceMessageViewHolder) holder).assignCommonMessageInterface(chatController); } else if (holder instanceof OutcomingVoiceMessageViewHolder) { ((OutcomingVoiceMessageViewHolder) holder).assignVoiceMessageInterface(chatController); - ((OutcomingVoiceMessageViewHolder) holder).assignReactionInterface(chatController); + ((OutcomingVoiceMessageViewHolder) holder).assignCommonMessageInterface(chatController); } else if (holder instanceof MagicPreviewMessageViewHolder) { ((MagicPreviewMessageViewHolder) holder).assignPreviewMessageInterface(chatController); - ((MagicPreviewMessageViewHolder) holder).assignReactionInterface(chatController); + ((MagicPreviewMessageViewHolder) holder).assignCommonMessageInterface(chatController); } } } diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index f1c25b638..5b75bff8b 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -35,6 +35,7 @@ import com.nextcloud.talk.models.json.generic.Status; import com.nextcloud.talk.models.json.hovercard.HoverCardOverall; import com.nextcloud.talk.models.json.mention.MentionOverall; import com.nextcloud.talk.models.json.notifications.NotificationOverall; +import com.nextcloud.talk.models.json.opengraph.OpenGraphOverall; import com.nextcloud.talk.models.json.participants.AddParticipantOverall; import com.nextcloud.talk.models.json.participants.ParticipantsOverall; import com.nextcloud.talk.models.json.push.PushRegistrationOverall; @@ -570,4 +571,9 @@ public interface NcApi { Observable<GenericOverall> setMessageExpiration(@Header("Authorization") String authorization, @Url String url, @Field("seconds") Integer seconds); + + @GET + Observable<OpenGraphOverall> getOpenGraph(@Header("Authorization") String authorization, + @Url String url, + @Query("reference") String urlToFindPreviewFor); } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt index f498a52ba..0a227e5f7 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt @@ -106,6 +106,8 @@ import com.nextcloud.talk.R import com.nextcloud.talk.activities.CallActivity import com.nextcloud.talk.activities.MainActivity import com.nextcloud.talk.activities.TakePhotoActivity +import com.nextcloud.talk.adapters.messages.CommonMessageInterface +import com.nextcloud.talk.adapters.messages.IncomingLinkPreviewMessageViewHolder import com.nextcloud.talk.adapters.messages.IncomingLocationMessageViewHolder import com.nextcloud.talk.adapters.messages.IncomingPollMessageViewHolder import com.nextcloud.talk.adapters.messages.IncomingPreviewMessageViewHolder @@ -115,12 +117,12 @@ import com.nextcloud.talk.adapters.messages.MagicOutcomingTextMessageViewHolder import com.nextcloud.talk.adapters.messages.MagicSystemMessageViewHolder import com.nextcloud.talk.adapters.messages.MagicUnreadNoticeMessageViewHolder import com.nextcloud.talk.adapters.messages.MessagePayload +import com.nextcloud.talk.adapters.messages.OutcomingLinkPreviewMessageViewHolder import com.nextcloud.talk.adapters.messages.OutcomingLocationMessageViewHolder import com.nextcloud.talk.adapters.messages.OutcomingPollMessageViewHolder import com.nextcloud.talk.adapters.messages.OutcomingPreviewMessageViewHolder import com.nextcloud.talk.adapters.messages.OutcomingVoiceMessageViewHolder import com.nextcloud.talk.adapters.messages.PreviewMessageInterface -import com.nextcloud.talk.adapters.messages.ReactionsInterface import com.nextcloud.talk.adapters.messages.TalkMessagesListAdapter import com.nextcloud.talk.adapters.messages.VoiceMessageInterface import com.nextcloud.talk.api.NcApi @@ -222,7 +224,7 @@ class ChatController(args: Bundle) : MessagesListAdapter.OnMessageViewLongClickListener<IMessage>, ContentChecker<ChatMessage>, VoiceMessageInterface, - ReactionsInterface, + CommonMessageInterface, PreviewMessageInterface { private val binding: ControllerChatBinding by viewBinding(ControllerChatBinding::bind) @@ -587,6 +589,17 @@ class ChatController(args: Bundle) : this ) + messageHolders.registerContentType( + CONTENT_TYPE_LINK_PREVIEW, + IncomingLinkPreviewMessageViewHolder::class.java, + payload, + R.layout.item_custom_incoming_link_preview_message, + OutcomingLinkPreviewMessageViewHolder::class.java, + payload, + R.layout.item_custom_outcoming_link_preview_message, + this + ) + val senderId = if (!conversationUser.userId.equals("?")) { "users/" + conversationUser.userId } else { @@ -2788,7 +2801,7 @@ class ChatController(args: Bundle) : } } - override fun onLongClickReactions(chatMessage: ChatMessage) { + override fun onOpenMessageActionsDialog(chatMessage: ChatMessage) { openMessageActionsDialog(chatMessage) } @@ -2797,7 +2810,7 @@ class ChatController(args: Bundle) : } override fun onPreviewMessageLongClick(chatMessage: ChatMessage) { - openMessageActionsDialog(chatMessage) + onOpenMessageActionsDialog(chatMessage) } private fun openMessageActionsDialog(iMessage: IMessage?) { @@ -3162,6 +3175,7 @@ class ChatController(args: Bundle) : CONTENT_TYPE_LOCATION -> message.hasGeoLocation() CONTENT_TYPE_VOICE_MESSAGE -> message.isVoiceMessage CONTENT_TYPE_POLL -> message.isPoll() + CONTENT_TYPE_LINK_PREVIEW -> message.isLinkPreview() CONTENT_TYPE_SYSTEM_MESSAGE -> !TextUtils.isEmpty(message.systemMessage) CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> message.id == "-1" else -> false @@ -3322,6 +3336,7 @@ class ChatController(args: Bundle) : private const val CONTENT_TYPE_LOCATION: Byte = 3 private const val CONTENT_TYPE_VOICE_MESSAGE: Byte = 4 private const val CONTENT_TYPE_POLL: Byte = 5 + private const val CONTENT_TYPE_LINK_PREVIEW: Byte = 6 private const val NEW_MESSAGES_POPUP_BUBBLE_DELAY: Long = 200 private const val POP_CURRENT_CONTROLLER_DELAY: Long = 100 private const val LOBBY_TIMER_DELAY: Long = 5000 diff --git a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/Capabilities.kt b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/Capabilities.kt index 6682469b5..bc94d731a 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/Capabilities.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/Capabilities.kt @@ -29,6 +29,8 @@ import kotlinx.android.parcel.Parcelize @Parcelize @JsonObject data class Capabilities( + @JsonField(name = ["core"]) + var coreCapability: CoreCapability?, @JsonField(name = ["spreed"]) var spreedCapability: SpreedCapability?, @JsonField(name = ["notifications"]) @@ -43,5 +45,5 @@ data class Capabilities( var userStatusCapability: UserStatusCapability? ) : Parcelable { // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' - constructor() : this(null, null, null, null, null, null) + constructor() : this(null, null, null, null, null, null, null) } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/capabilities/CoreCapability.kt b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/CoreCapability.kt new file mode 100644 index 000000000..d5bf24ff9 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/capabilities/CoreCapability.kt @@ -0,0 +1,43 @@ +/* + * Nextcloud Talk application + * + * @author Marcel Hibbe + * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package com.nextcloud.talk.models.json.capabilities + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.android.parcel.Parcelize +import kotlinx.serialization.Serializable + +@Parcelize +@JsonObject +@Serializable +data class CoreCapability( + @JsonField(name = ["pollinterval"]) + var pollInterval: Int?, + @JsonField(name = ["webdav-root"]) + var webdavRoot: String?, + @JsonField(name = ["reference-api"]) + var referenceApi: String?, + @JsonField(name = ["reference-regex"]) + var referenceRegex: String? +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null, null, null, null) +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt index 173c32c14..98937fddf 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt @@ -37,6 +37,7 @@ import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.models.json.chat.ChatUtils.Companion.getParsedMessage import com.nextcloud.talk.models.json.converters.EnumSystemMessageTypeConverter import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew import com.stfalcon.chatkit.commons.models.IUser import com.stfalcon.chatkit.commons.models.MessageContentType import kotlinx.android.parcel.Parcelize @@ -126,8 +127,11 @@ data class ChatMessage( var voiceMessagePlayedSeconds: Int = 0, var voiceMessageDownloadProgress: Int = 0, + ) : Parcelable, MessageContentType, MessageContentType.Image { + var extractedUrlToPreview: String? = null + // messageTypesToIgnore is weird. must be deleted by refactoring!!! @JsonIgnore var messageTypesToIgnore = Arrays.asList( @@ -174,6 +178,33 @@ data class ChatMessage( return false } + @Suppress("ReturnCount") + fun isLinkPreview(): Boolean { + if (CapabilitiesUtilNew.isLinkPreviewAvailable(activeUser!!)) { + val regexStringFromServer = activeUser?.capabilities?.coreCapability?.referenceRegex + + val regexFromServer = regexStringFromServer?.toRegex(setOf(RegexOption.MULTILINE, RegexOption.IGNORE_CASE)) + val regexDefault = REGEX_STRING_DEFAULT.toRegex(setOf(RegexOption.MULTILINE, RegexOption.IGNORE_CASE)) + + val messageCharSequence: CharSequence = StringBuffer(message!!) + + if (regexFromServer != null) { + val foundLinkInServerRegex = regexFromServer.containsMatchIn(messageCharSequence) + if (foundLinkInServerRegex) { + extractedUrlToPreview = regexFromServer.find(messageCharSequence)?.groups?.get(0)?.value?.trim() + return true + } + } + + val foundLinkInDefaultRegex = regexDefault.containsMatchIn(messageCharSequence) + if (foundLinkInDefaultRegex) { + extractedUrlToPreview = regexDefault.find(messageCharSequence)?.groups?.get(0)?.value?.trim() + return true + } + } + return false + } + @Suppress("Detekt.NestedBlockDepth") override fun getImageUrl(): String? { if (messageParameters != null && messageParameters!!.size > 0) { @@ -492,5 +523,8 @@ data class ChatMessage( companion object { private const val TAG = "ChatMessage" private const val MILLIES: Long = 1000L + + private const val REGEX_STRING_DEFAULT = + """(\s|\n|^)(https?:\/\/)((?:[-A-Z0-9+_]+\.)+[-A-Z]+(?:\/[-A-Z0-9+&@#%?=~_|!:,.;()]*)*)(\s|\n|$)""" } } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/opengraph/OpenGraphOCS.kt b/app/src/main/java/com/nextcloud/talk/models/json/opengraph/OpenGraphOCS.kt new file mode 100644 index 000000000..eb834c5f8 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/opengraph/OpenGraphOCS.kt @@ -0,0 +1,38 @@ +/* + * Nextcloud Talk application + * + * @author Marcel Hibbe + * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package com.nextcloud.talk.models.json.opengraph + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import com.nextcloud.talk.models.json.generic.GenericMeta +import kotlinx.android.parcel.Parcelize + +@Parcelize +@JsonObject +data class OpenGraphOCS( + @JsonField(name = ["meta"]) + var meta: GenericMeta?, + @JsonField(name = ["data"]) + var data: OpenGraphResponse? +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null, null) +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/opengraph/OpenGraphObject.kt b/app/src/main/java/com/nextcloud/talk/models/json/opengraph/OpenGraphObject.kt new file mode 100644 index 000000000..d92d80ffa --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/opengraph/OpenGraphObject.kt @@ -0,0 +1,44 @@ +/* + * Nextcloud Talk application + * + * @author Marcel Hibbe + * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package com.nextcloud.talk.models.json.opengraph + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.android.parcel.Parcelize + +@Parcelize +@JsonObject +data class OpenGraphObject( + @JsonField(name = ["id"]) + var id: String, + @JsonField(name = ["name"]) + var name: String, + @JsonField(name = ["description"]) + var description: String? = null, + @JsonField(name = ["thumb"]) + var thumb: String? = null, + @JsonField(name = ["link"]) + var link: String? = null, + +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this("", "", null, null) +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/opengraph/OpenGraphOverall.kt b/app/src/main/java/com/nextcloud/talk/models/json/opengraph/OpenGraphOverall.kt new file mode 100644 index 000000000..8cedd0d0a --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/opengraph/OpenGraphOverall.kt @@ -0,0 +1,35 @@ +/* + * Nextcloud Talk application + * + * @author Marcel Hibbe + * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package com.nextcloud.talk.models.json.opengraph + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.android.parcel.Parcelize + +@Parcelize +@JsonObject +data class OpenGraphOverall( + @JsonField(name = ["ocs"]) + var ocs: OpenGraphOCS? = null +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null) +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/opengraph/OpenGraphResponse.kt b/app/src/main/java/com/nextcloud/talk/models/json/opengraph/OpenGraphResponse.kt new file mode 100644 index 000000000..f4ac43008 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/opengraph/OpenGraphResponse.kt @@ -0,0 +1,35 @@ +/* + * Nextcloud Talk application + * + * @author Marcel Hibbe + * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package com.nextcloud.talk.models.json.opengraph + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.android.parcel.Parcelize + +@Parcelize +@JsonObject +data class OpenGraphResponse( + @JsonField(name = ["references"]) + var references: HashMap<String, Reference>? +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null) +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/opengraph/Reference.kt b/app/src/main/java/com/nextcloud/talk/models/json/opengraph/Reference.kt new file mode 100644 index 000000000..581c20dd8 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/opengraph/Reference.kt @@ -0,0 +1,42 @@ +/* + * Nextcloud Talk application + * + * @author Marcel Hibbe + * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package com.nextcloud.talk.models.json.opengraph + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.android.parcel.Parcelize + +@Parcelize +@JsonObject +data class Reference( + @JsonField(name = ["richObjectType"]) + var richObjectType: String? = null, + @JsonField(name = ["richObject"]) + var richObject: RichObject? = null, + @JsonField(name = ["openGraphObject"]) + var openGraphObject: OpenGraphObject? = null, + @JsonField(name = ["accessible"]) + var accessible: Boolean, + +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null, null, null, false) +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/opengraph/RichObject.kt b/app/src/main/java/com/nextcloud/talk/models/json/opengraph/RichObject.kt new file mode 100644 index 000000000..129d1d4fb --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/opengraph/RichObject.kt @@ -0,0 +1,44 @@ +/* + * Nextcloud Talk application + * + * @author Marcel Hibbe + * Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package com.nextcloud.talk.models.json.opengraph + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.android.parcel.Parcelize + +@Parcelize +@JsonObject +data class RichObject( + @JsonField(name = ["id"]) + var id: String, + @JsonField(name = ["name"]) + var name: String, + @JsonField(name = ["description"]) + var description: String? = null, + @JsonField(name = ["thumb"]) + var thumb: String? = null, + @JsonField(name = ["link"]) + var link: String? = null, + +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this("", "", null, null) +} diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java index 368b4ea4c..cac2501cd 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java @@ -491,4 +491,7 @@ public class ApiUtils { return getUrlForRoom(version, baseUrl, token) + "/message-expiration"; } + public static String getUrlForOpenGraph(String baseUrl) { + return baseUrl + ocsApiVersion + "/references/resolve"; + } } diff --git a/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt b/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt index f6bcda59d..7f86a9dcf 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt @@ -154,5 +154,15 @@ object CapabilitiesUtilNew { return hasSpreedFeatureCapability(user, "unified-search") } + @JvmStatic + fun isLinkPreviewAvailable(user: User): Boolean { + if (user.capabilities?.coreCapability?.referenceApi != null && + user.capabilities?.coreCapability?.referenceApi == "true" + ) { + return true + } + return false + } + const val DEFAULT_CHAT_SIZE = 1000 } diff --git a/app/src/main/res/layout/item_custom_incoming_link_preview_message.xml b/app/src/main/res/layout/item_custom_incoming_link_preview_message.xml new file mode 100644 index 000000000..b94e1cb2c --- /dev/null +++ b/app/src/main/res/layout/item_custom_incoming_link_preview_message.xml @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Nextcloud Talk application + ~ + ~ @author Mario Danic + ~ @author Andy Scherzinger + ~ @author Marcel Hibbe + ~ Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de> + ~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> + ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com> + ~ + ~ This program is free software: you can redistribute it and/or modify + ~ it under the terms of the GNU General Public License as published by + ~ the Free Software Foundation, either version 3 of the License, or + ~ at your option) any later version. + ~ + ~ This program is distributed in the hope that it will be useful, + ~ but WITHOUT ANY WARRANTY; without even the implied warranty of + ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ~ GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License + ~ along with this program. If not, see <http://www.gnu.org/licenses/>. + --> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="16dp" + android:layout_marginTop="2dp" + android:layout_marginRight="16dp" + android:layout_marginBottom="2dp"> + + <com.facebook.drawee.view.SimpleDraweeView + android:id="@id/messageUserAvatar" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_alignParentTop="true" + android:layout_marginEnd="8dp" + app:roundAsCircle="true" /> + + <com.google.android.flexbox.FlexboxLayout + android:id="@id/bubble" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/message_incoming_bubble_margin_right" + android:layout_toEndOf="@id/messageUserAvatar" + android:orientation="vertical" + app:alignContent="stretch" + app:alignItems="stretch" + app:flexWrap="wrap"> + + <include + android:id="@+id/message_quote" + layout="@layout/item_message_quote" + android:visibility="gone" /> + + <androidx.emoji.widget.EmojiTextView + android:id="@+id/messageAuthor" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="4dp" + android:alpha="0.6" + android:textAlignment="viewStart" + android:textColor="@color/no_emphasis_text" + android:textIsSelectable="false" + android:textSize="12sp" + tools:text="Jane Doe" /> + + <androidx.emoji.widget.EmojiTextView + android:id="@id/messageText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:lineSpacingMultiplier="1.2" + android:textAlignment="viewStart" + android:textIsSelectable="false" + app:layout_alignSelf="flex_start" + app:layout_flexGrow="1" + app:layout_wrapBefore="true" + tools:text="Talk to you later!" /> + + <include + android:id="@+id/referenceInclude" + layout="@layout/reference_inside_message" /> + + <TextView + android:id="@id/messageTime" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/messageText" + android:layout_marginStart="8dp" + android:alpha="0.6" + android:gravity="end" + android:textColor="@color/no_emphasis_text" + android:textIsSelectable="false" + app:layout_alignSelf="center" + app:layout_flexGrow="1" + app:layout_wrapBefore="false" + tools:text="12:38" /> + + <include + android:id="@+id/reactions" + layout="@layout/reactions_inside_message" /> + + </com.google.android.flexbox.FlexboxLayout> +</RelativeLayout> diff --git a/app/src/main/res/layout/item_custom_outcoming_link_preview_message.xml b/app/src/main/res/layout/item_custom_outcoming_link_preview_message.xml new file mode 100644 index 000000000..924ad8553 --- /dev/null +++ b/app/src/main/res/layout/item_custom_outcoming_link_preview_message.xml @@ -0,0 +1,96 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Nextcloud Talk application + ~ + ~ @author Mario Danic + ~ @author Andy Scherzinger + ~ @author Marcel Hibbe + ~ Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de> + ~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> + ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com> + ~ + ~ This program is free software: you can redistribute it and/or modify + ~ it under the terms of the GNU General Public License as published by + ~ the Free Software Foundation, either version 3 of the License, or + ~ at your option) any later version. + ~ + ~ This program is distributed in the hope that it will be useful, + ~ but WITHOUT ANY WARRANTY; without even the implied warranty of + ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ~ GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License + ~ along with this program. If not, see <http://www.gnu.org/licenses/>. + --> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="16dp" + android:layout_marginTop="2dp" + android:layout_marginRight="16dp" + android:layout_marginBottom="2dp"> + + <com.google.android.flexbox.FlexboxLayout + android:id="@id/bubble" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_marginStart="@dimen/message_outcoming_bubble_margin_left" + app:alignContent="stretch" + app:alignItems="stretch" + app:flexWrap="wrap" + app:justifyContent="flex_end"> + + <include + android:id="@+id/message_quote" + layout="@layout/item_message_quote" + android:visibility="gone" /> + + <androidx.emoji.widget.EmojiTextView + android:id="@id/messageText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignWithParentIfMissing="true" + android:lineSpacingMultiplier="1.2" + android:textAlignment="viewStart" + android:textColorHighlight="@color/nc_grey" + android:textIsSelectable="false" + tools:text="Talk to you later!" /> + + <include + android:id="@+id/referenceInclude" + layout="@layout/reference_inside_message" /> + + <TextView + android:id="@id/messageTime" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/messageText" + android:layout_marginStart="8dp" + android:alpha="0.6" + android:gravity="end" + android:textColor="@color/no_emphasis_text" + android:textIsSelectable="false" + app:layout_alignSelf="center" + app:layout_flexGrow="1" + app:layout_wrapBefore="false" + tools:text="10:35" /> + + <ImageView + android:id="@+id/checkMark" + android:layout_width="wrap_content" + android:layout_height="@dimen/message_bubble_checkmark_height" + android:layout_below="@id/messageTime" + android:layout_marginStart="8dp" + android:contentDescription="@null" + app:layout_alignSelf="center" + app:tint="@color/high_emphasis_text" /> + + <include + android:id="@+id/reactions" + layout="@layout/reactions_inside_message" /> + + </com.google.android.flexbox.FlexboxLayout> +</RelativeLayout> diff --git a/app/src/main/res/layout/reference_inside_message.xml b/app/src/main/res/layout/reference_inside_message.xml new file mode 100644 index 000000000..4e87579a1 --- /dev/null +++ b/app/src/main/res/layout/reference_inside_message.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + + Nextcloud Talk application + + Copyright (C) 2022 Marcel Hibbe + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/referenceWrapper" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="5dp"> + + <View + android:id="@+id/referenceColoredView" + android:layout_width="2dp" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:background="@color/high_emphasis_text" + tools:layout_height="100dp"/> + + <androidx.emoji.widget.EmojiTextView + android:id="@+id/referenceName" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:lineSpacingMultiplier="1.2" + android:textAlignment="viewStart" + android:textIsSelectable="false" + android:layout_marginStart="10dp" + tools:text="Name of Website" /> + + <androidx.emoji.widget.EmojiTextView + android:id="@+id/referenceLink" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/referenceName" + android:lineSpacingMultiplier="1.2" + android:textAlignment="viewStart" + android:textIsSelectable="false" + android:layout_marginStart="10dp" + tools:text="http://nextcloud.com" /> + + <com.facebook.drawee.view.SimpleDraweeView + android:id="@+id/referenceThumbImage" + android:layout_width="match_parent" + android:layout_height="120dp" + android:scaleType="fitEnd" + android:layout_below="@id/referenceLink" + android:layout_marginTop="5dp" + android:layout_marginStart="10dp" + app:roundedCornerRadius="6dp" /> +</RelativeLayout>
\ No newline at end of file |