Merge pull request #2460 from nextcloud/feature/2419/linkPreviews

add openGraph link previews
This commit is contained in:
Tim Krüger 2022-10-06 13:19:14 +02:00 committed by GitHub
commit 009cd5a425
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1190 additions and 59 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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