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..554b2ec13 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt @@ -0,0 +1,207 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * @author Marcel Hibbe + * Copyright (C) 2022 Marcel Hibbe + * Copyright (C) 2017-2019 Mario Danic + * + * 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 . + */ + +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(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 reactionsInterface: ReactionsInterface + + @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 + ) + + Reaction().showReactions( + message, + binding.reactions, + binding.messageTime.context, + false, + viewThemeUtils + ) + binding.reactions.reactionsEmojiWrapper.setOnClickListener { + reactionsInterface.onClickReactions(message) + } + binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? -> + reactionsInterface.onLongClickReactions(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(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 assignReactionInterface(reactionsInterface: ReactionsInterface) { + this.reactionsInterface = reactionsInterface + } + + companion object { + private val TAG = IncomingLinkPreviewMessageViewHolder::class.java.simpleName + } +} 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 . + * + * 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 { + 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/OutcomingLinkPreviewMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt new file mode 100644 index 000000000..4f1f03a4a --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt @@ -0,0 +1,168 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * @author Marcel Hibbe + * Copyright (C) 2022 Marcel Hibbe + * Copyright (C) 2017-2019 Mario Danic + * + * 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 . + */ + +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(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 reactionsInterface: ReactionsInterface + + @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 + ) + + Reaction().showReactions( + message, + binding.reactions, + binding.messageTime.context, + true, + viewThemeUtils + ) + binding.reactions.reactionsEmojiWrapper.setOnClickListener { + reactionsInterface.onClickReactions(message) + } + binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? -> + reactionsInterface.onLongClickReactions(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 assignReactionInterface(reactionsInterface: ReactionsInterface) { + this.reactionsInterface = reactionsInterface + } + + companion object { + private val TAG = OutcomingLinkPreviewMessageViewHolder::class.java.simpleName + } +} 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 setMessageExpiration(@Header("Authorization") String authorization, @Url String url, @Field("seconds") Integer seconds); + + @GET + Observable 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..ad56bae97 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,7 @@ 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.IncomingLinkPreviewMessageViewHolder import com.nextcloud.talk.adapters.messages.IncomingLocationMessageViewHolder import com.nextcloud.talk.adapters.messages.IncomingPollMessageViewHolder import com.nextcloud.talk.adapters.messages.IncomingPreviewMessageViewHolder @@ -115,6 +116,7 @@ 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 @@ -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 { @@ -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 + * + * 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 . + */ +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 + * + * 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 . + */ +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 + * + * 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 . + */ +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 + * + * 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 . + */ +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 + * + * 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 . + */ +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? +) : 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 + * + * 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 . + */ +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 + * + * 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 . + */ +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 @@ + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + \ No newline at end of file