Merge pull request #4327 from nextcloud/deck_card_is_shown_as_object

show deck card in messages
This commit is contained in:
Marcel Hibbe 2024-10-23 12:57:59 +02:00 committed by GitHub
commit 5bee52d182
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 820 additions and 4 deletions

View File

@ -410,6 +410,15 @@ class ConversationItem(
chatMessage?.getNullsafeActorDisplayName()
)
}
} else if (MessageType.DECK_CARD == chatMessage?.getCalculateMessageType()) {
return if (chatMessage?.actorId == chatMessage?.activeUser!!.userId) {
sharedApplication!!.getString(R.string.nc_sent_deck_card_you)
} else {
String.format(
sharedApplication!!.resources.getString(R.string.nc_sent_deck_card),
chatMessage?.getNullsafeActorDisplayName()
)
}
}
}
return ""

View File

@ -0,0 +1,268 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kch@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.adapters.messages
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.text.TextUtils
import android.util.Log
import android.view.View
import android.widget.ImageView
import androidx.core.content.ContextCompat
import autodagger.AutoInjector
import coil.load
import com.nextcloud.android.common.ui.theme.utils.ColorRole
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.chat.ChatActivity
import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.databinding.ItemCustomIncomingDeckCardMessageBinding
import com.nextcloud.talk.extensions.loadBotsAvatar
import com.nextcloud.talk.extensions.loadChangelogBotAvatar
import com.nextcloud.talk.extensions.loadFederatedUserAvatar
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.message.MessageUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class IncomingDeckCardViewHolder(incomingView: View, payload: Any) : MessageHolders
.IncomingTextMessageViewHolder<ChatMessage>(incomingView, payload) {
private val binding: ItemCustomIncomingDeckCardMessageBinding =
ItemCustomIncomingDeckCardMessageBinding.bind(itemView)
@Inject
lateinit var context: Context
@Inject
lateinit var appPreferences: AppPreferences
@Inject
lateinit var viewThemeUtils: ViewThemeUtils
@Inject
lateinit var messageUtils: MessageUtils
@Inject
lateinit var dateUtils: DateUtils
@Inject
lateinit var ncApi: NcApi
lateinit var message: ChatMessage
lateinit var commonMessageInterface: CommonMessageInterface
var stackName: String? = null
var cardName: String? = null
var boardName: String? = null
var cardLink: String? = null
@SuppressLint("SetTextI18n")
override fun onBind(message: ChatMessage) {
super.onBind(message)
this.message = message
sharedApplication!!.componentApplication.inject(this)
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
setAvatarAndAuthorOnMessageItem(message)
showDeckCard(message)
colorizeMessageBubble(message)
binding.cardView.findViewById<ImageView>(R.id.deckCardImage)?.let {
viewThemeUtils.platform.colorImageView(it, ColorRole.SECONDARY)
}
itemView.isSelected = false
// parent message handling
setParentMessageDataOnMessageItem(message)
binding.cardView.setOnLongClickListener { l: View? ->
commonMessageInterface.onOpenMessageActionsDialog(message)
true
}
binding.cardView.setOnClickListener {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(cardLink))
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(browserIntent)
}
itemView.setTag(R.string.replyable_message_view_tag, message.replyable)
Reaction().showReactions(
message,
::clickOnReaction,
::longClickOnReaction,
binding.reactions,
binding.messageTime.context,
false,
viewThemeUtils
)
}
private fun showDeckCard(message: ChatMessage) {
if (message.messageParameters != null && message.messageParameters!!.size > 0) {
for (key in message.messageParameters!!.keys) {
val individualHashMap: Map<String?, String?> = message.messageParameters!![key]!!
if (individualHashMap["type"] == "deck-card") {
cardName = individualHashMap["name"]
stackName = individualHashMap["stackname"]
boardName = individualHashMap["boardname"]
cardLink = individualHashMap["link"]
}
}
}
if (cardName?.isNotEmpty() == true) {
val cardDescription = String.format(
context.resources.getString(R.string.deck_card_description),
stackName,
boardName
)
binding.cardName.visibility = View.VISIBLE
binding.cardDescription.visibility = View.VISIBLE
binding.cardName.text = cardName
binding.cardDescription.text = cardDescription
}
}
private fun longClickOnReaction(chatMessage: ChatMessage) {
commonMessageInterface.onLongClickReactions(chatMessage)
}
private fun clickOnReaction(chatMessage: ChatMessage, emoji: String) {
commonMessageInterface.onClickReaction(chatMessage, emoji)
}
private fun setAvatarAndAuthorOnMessageItem(message: ChatMessage) {
val author: String = message.actorDisplayName!!
if (!TextUtils.isEmpty(author)) {
binding.messageAuthor.visibility = View.VISIBLE
binding.messageAuthor.text = author
binding.messageUserAvatar.setOnClickListener {
(payload as? MessagePayload)?.profileBottomSheet?.showFor(message, itemView.context)
}
} else {
binding.messageAuthor.setText(R.string.nc_nick_guest)
}
if (!message.isGrouped && !message.isOneToOneConversation && !message.isFormerOneToOneConversation) {
setAvatarOnMessage(message)
} else {
if (message.isOneToOneConversation || message.isFormerOneToOneConversation) {
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") {
binding.messageUserAvatar.loadChangelogBotAvatar()
} else if (message.actorType == "bots") {
binding.messageUserAvatar.loadBotsAvatar()
} else if (message.actorType == "federated_users") {
binding.messageUserAvatar.loadFederatedUserAvatar(message)
}
}
private fun colorizeMessageBubble(message: ChatMessage) {
viewThemeUtils.talk.themeIncomingMessageBubble(bubble, message.isGrouped, message.isDeleted)
}
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
if (message.parentMessageId != null && !message.isDeleted) {
CoroutineScope(Dispatchers.Main).launch {
try {
val chatActivity = commonMessageInterface as ChatActivity
val urlForChatting = ApiUtils.getUrlForChat(
chatActivity.chatApiVersion,
chatActivity.conversationUser?.baseUrl,
chatActivity.roomToken
)
val parentChatMessage = withContext(Dispatchers.IO) {
chatActivity.chatViewModel.getMessageById(
urlForChatting,
chatActivity.currentConversation!!,
message.parentMessageId!!
).first()
}
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 = messageUtils
.enrichChatReplyMessageText(
binding.messageQuote.quotedMessage.context,
parentChatMessage,
true,
viewThemeUtils
)
binding.messageQuote.quotedMessageAuthor
.setTextColor(ContextCompat.getColor(context, R.color.textColorMaxContrast))
if (parentChatMessage.actorId?.equals(message.activeUser!!.userId) == true) {
viewThemeUtils.platform.colorViewBackground(
binding.messageQuote.quoteColoredView,
ColorRole.PRIMARY
)
} else {
binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.textColorMaxContrast)
}
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
} catch (e: Exception) {
Log.d(TAG, "Error when processing parent message in view holder", e)
}
}
} else {
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
}
}
fun assignCommonMessageInterface(commonMessageInterface: CommonMessageInterface) {
this.commonMessageInterface = commonMessageInterface
}
companion object {
private val TAG = IncomingDeckCardViewHolder::class.java.simpleName
}
}

View File

@ -0,0 +1,245 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Sowjanya Kota<sowjanya.kch@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.adapters.messages
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
import android.view.View
import android.widget.ImageView
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import autodagger.AutoInjector
import coil.load
import com.nextcloud.android.common.ui.theme.utils.ColorRole
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.chat.ChatActivity
import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.databinding.ItemCustomOutcomingDeckCardMessageBinding
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.DateUtils
import com.nextcloud.talk.utils.message.MessageUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class OutcomingDeckCardViewHolder(
outcomingView: View
) : MessageHolders.OutcomingTextMessageViewHolder<ChatMessage>(outcomingView) {
private val binding: ItemCustomOutcomingDeckCardMessageBinding = ItemCustomOutcomingDeckCardMessageBinding.bind(
itemView
)
@Inject
lateinit var context: Context
@Inject
lateinit var viewThemeUtils: ViewThemeUtils
@Inject
lateinit var messageUtils: MessageUtils
@Inject
lateinit var appPreferences: AppPreferences
@Inject
lateinit var ncApi: NcApi
lateinit var message: ChatMessage
@Inject
lateinit var dateUtils: DateUtils
lateinit var commonMessageInterface: CommonMessageInterface
var stackName: String? = null
var cardName: String? = null
var boardName: String? = null
var cardLink: String? = null
@SuppressLint("SetTextI18n")
override fun onBind(message: ChatMessage) {
super.onBind(message)
this.message = message
sharedApplication!!.componentApplication.inject(this)
viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT)
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
colorizeMessageBubble(message)
binding.cardView.findViewById<ImageView>(R.id.deckCardImage)?.let {
viewThemeUtils.platform.colorImageView(it, ColorRole.SECONDARY)
}
itemView.isSelected = false
showDeckCard(message)
// 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)
viewThemeUtils.talk.themeMessageCheckMark(binding.checkMark)
}
}
binding.checkMark.contentDescription = readStatusContentDescriptionString
binding.cardView.setOnClickListener {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(cardLink))
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(browserIntent)
}
itemView.setTag(R.string.replyable_message_view_tag, message.replyable)
Reaction().showReactions(
message,
::clickOnReaction,
::longClickOnReaction,
binding.reactions,
binding.messageTime.context,
true,
viewThemeUtils
)
}
private fun showDeckCard(message: ChatMessage) {
if (message.messageParameters != null && message.messageParameters!!.size > 0) {
for (key in message.messageParameters!!.keys) {
val individualHashMap: Map<String?, String?> = message.messageParameters!![key]!!
if (individualHashMap["type"] == "deck-card") {
cardName = individualHashMap["name"]
stackName = individualHashMap["stackname"]
boardName = individualHashMap["boardname"]
cardLink = individualHashMap["link"]
}
}
}
val cardDescription = String.format(
context.resources.getString(R.string.deck_card_description),
stackName,
boardName
)
binding.cardName.visibility = View.VISIBLE
binding.cardDescription.visibility = View.VISIBLE
binding.cardName.text = cardName
binding.cardDescription.text = cardDescription
}
private fun longClickOnReaction(chatMessage: ChatMessage) {
commonMessageInterface.onLongClickReactions(chatMessage)
}
private fun clickOnReaction(chatMessage: ChatMessage, emoji: String) {
commonMessageInterface.onClickReaction(chatMessage, emoji)
}
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
if (message.parentMessageId != null && !message.isDeleted) {
CoroutineScope(Dispatchers.Main).launch {
try {
val chatActivity = commonMessageInterface as ChatActivity
val urlForChatting = ApiUtils.getUrlForChat(
chatActivity.chatApiVersion,
chatActivity.conversationUser?.baseUrl,
chatActivity.roomToken
)
val parentChatMessage = withContext(Dispatchers.IO) {
chatActivity.chatViewModel.getMessageById(
urlForChatting,
chatActivity.currentConversation!!,
message.parentMessageId!!
).first()
}
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 = messageUtils
.enrichChatReplyMessageText(
binding.messageQuote.quotedMessage.context,
parentChatMessage,
true,
viewThemeUtils
)
binding.messageQuote.quotedMessageAuthor
.setTextColor(ContextCompat.getColor(context, R.color.textColorMaxContrast))
if (parentChatMessage.actorId?.equals(message.activeUser!!.userId) == true) {
viewThemeUtils.platform.colorViewBackground(
binding.messageQuote.quoteColoredView,
ColorRole.PRIMARY
)
} else {
binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.textColorMaxContrast)
}
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
} catch (e: Exception) {
Log.d(TAG, "Error when processing parent message in view holder", e)
}
}
} 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 = OutcomingDeckCardViewHolder::class.java.simpleName
}
}

View File

@ -67,6 +67,10 @@ public class TalkMessagesListAdapter<M extends IMessage> extends MessagesListAda
((CallStartedViewHolder) holder).assignCallStartedMessageInterface(chatActivity);
} else if (holder instanceof TemporaryMessageViewHolder) {
((TemporaryMessageViewHolder) holder).assignTemporaryMessageInterface(chatActivity);
}else if (holder instanceof IncomingDeckCardViewHolder){
((IncomingDeckCardViewHolder) holder).assignCommonMessageInterface(chatActivity);
} else if(holder instanceof OutcomingDeckCardViewHolder){
((OutcomingDeckCardViewHolder) holder).assignCommonMessageInterface(chatActivity);
}
super.onBindViewHolder(holder, position);

View File

@ -83,6 +83,7 @@ import com.nextcloud.talk.activities.TakePhotoActivity
import com.nextcloud.talk.adapters.messages.CallStartedMessageInterface
import com.nextcloud.talk.adapters.messages.CallStartedViewHolder
import com.nextcloud.talk.adapters.messages.CommonMessageInterface
import com.nextcloud.talk.adapters.messages.IncomingDeckCardViewHolder
import com.nextcloud.talk.adapters.messages.IncomingLinkPreviewMessageViewHolder
import com.nextcloud.talk.adapters.messages.IncomingLocationMessageViewHolder
import com.nextcloud.talk.adapters.messages.IncomingPollMessageViewHolder
@ -96,6 +97,7 @@ import com.nextcloud.talk.adapters.messages.OutcomingPollMessageViewHolder
import com.nextcloud.talk.adapters.messages.OutcomingPreviewMessageViewHolder
import com.nextcloud.talk.adapters.messages.OutcomingTextMessageViewHolder
import com.nextcloud.talk.adapters.messages.OutcomingVoiceMessageViewHolder
import com.nextcloud.talk.adapters.messages.OutcomingDeckCardViewHolder
import com.nextcloud.talk.adapters.messages.PreviewMessageInterface
import com.nextcloud.talk.adapters.messages.PreviewMessageViewHolder
import com.nextcloud.talk.adapters.messages.SystemMessageInterface
@ -1291,6 +1293,18 @@ class ChatActivity :
R.layout.item_custom_outcoming_link_preview_message,
this
)
messageHolders.registerContentType(
CONTENT_TYPE_DECK_CARD,
IncomingDeckCardViewHolder::class.java,
payload,
R.layout.item_custom_incoming_deck_card_message,
OutcomingDeckCardViewHolder::class.java,
payload,
R.layout.item_custom_outcoming_deck_card_message,
this
)
return messageHolders
}
@ -3517,6 +3531,7 @@ class ChatActivity :
CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> message.id == "-1"
CONTENT_TYPE_CALL_STARTED -> message.id == "-2"
CONTENT_TYPE_TEMP -> message.id == "-3"
CONTENT_TYPE_DECK_CARD -> message.isDeckCard()
else -> false
}
@ -3721,7 +3736,8 @@ class ChatActivity :
private const val CONTENT_TYPE_VOICE_MESSAGE: Byte = 5
private const val CONTENT_TYPE_POLL: Byte = 6
private const val CONTENT_TYPE_LINK_PREVIEW: Byte = 7
private const val CONTENT_TYPE_TEMP: Byte = 8
private const val CONTENT_TYPE_DECK_CARD: Byte = 8
private const val CONTENT_TYPE_TEMP: Byte = 9
private const val NEW_MESSAGES_POPUP_BUBBLE_DELAY: Long = 200
private const val GET_ROOM_INFO_DELAY_NORMAL: Long = 30000
private const val GET_ROOM_INFO_DELAY_LOBBY: Long = 5000

View File

@ -131,9 +131,21 @@ data class ChatMessage(
MessageType.SINGLE_LINK_MESSAGE,
MessageType.SINGLE_NC_GEOLOCATION_MESSAGE,
MessageType.VOICE_MESSAGE,
MessageType.POLL_MESSAGE
MessageType.POLL_MESSAGE,
MessageType.DECK_CARD
)
fun isDeckCard(): Boolean {
if (messageParameters != null && messageParameters!!.size > 0) {
for ((_, individualHashMap) in messageParameters!!) {
if (isHashMapEntryEqualTo(individualHashMap, "type", "deck-card")) {
return true
}
}
}
return false
}
fun hasFileAttachment(): Boolean {
if (messageParameters != null && messageParameters!!.size > 0) {
for ((_, individualHashMap) in messageParameters!!) {
@ -237,6 +249,8 @@ data class ChatMessage(
MessageType.SINGLE_NC_GEOLOCATION_MESSAGE
} else if (isPoll()) {
MessageType.POLL_MESSAGE
} else if (isDeckCard()) {
MessageType.DECK_CARD
} else {
MessageType.REGULAR_TEXT_MESSAGE
}
@ -345,7 +359,8 @@ data class ChatMessage(
SINGLE_NC_ATTACHMENT_MESSAGE,
SINGLE_NC_GEOLOCATION_MESSAGE,
POLL_MESSAGE,
VOICE_MESSAGE
VOICE_MESSAGE,
DECK_CARD
}
/**

View File

@ -0,0 +1,25 @@
<!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kch@gmail.com>
~ SPDX-License-Identifier: GPL-3.0-or-later
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M2,7L14,7A1,1 0,0 1,15 8L15,14A1,1 0,0 1,14 15L2,15A1,1 0,0 1,1 14L1,8A1,1 0,0 1,2 7z"
android:fillColor="#fff"/>
<path
android:pathData="M2.5,5L13.5,5A0.5,0.5 0,0 1,14 5.5L14,5.5A0.5,0.5 0,0 1,13.5 6L2.5,6A0.5,0.5 0,0 1,2 5.5L2,5.5A0.5,0.5 0,0 1,2.5 5z"
android:fillColor="#fff"/>
<path
android:pathData="M3.5,3L12.5,3A0.5,0.5 0,0 1,13 3.5L13,3.5A0.5,0.5 0,0 1,12.5 4L3.5,4A0.5,0.5 0,0 1,3 3.5L3,3.5A0.5,0.5 0,0 1,3.5 3z"
android:fillColor="#fff"/>
<path
android:pathData="M4.5,1L11.5,1A0.5,0.5 0,0 1,12 1.5L12,1.5A0.5,0.5 0,0 1,11.5 2L4.5,2A0.5,0.5 0,0 1,4 1.5L4,1.5A0.5,0.5 0,0 1,4.5 1z"
android:fillColor="#fff"/>
</vector>

View File

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kch@gmail.com>
~ SPDX-License-Identifier: GPL-3.0-or-later
-->
<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/cardView"
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">
<ImageView
android:id="@id/messageUserAvatar"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentTop="true"
android:layout_marginTop ="2dp"
android:layout_marginEnd="8dp"
android:contentDescription="@string/avatar" />
<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.emoji2.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" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:id="@+id/deckCardImage"
android:layout_width="16dp"
android:layout_height="16dp"
android:contentDescription="@null"
android:src="@drawable/deck" />
<androidx.emoji2.widget.EmojiTextView
android:id="@+id/cardName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
android:textStyle="bold"
android:visibility="gone"
tools:text="Card Name"
tools:visibility="visible" />
</LinearLayout>
<androidx.emoji2.widget.EmojiTextView
android:id="@+id/cardDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:textAlignment="viewStart"
android:maxLines="1"
android:textIsSelectable="false"
android:visibility="gone"
android:layout_marginBottom="4dp"
tools:text="Card Description"
tools:visibility="visible" />
<TextView
android:id="@id/messageTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.6"
android:layout_gravity="end"
android:textColor="@color/no_emphasis_text"
android:textIsSelectable="false"
tools:text="12:38" />
</LinearLayout>
<include
android:id="@+id/reactions"
layout="@layout/reactions_inside_message" />
</com.google.android.flexbox.FlexboxLayout>
</RelativeLayout>

View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kch@gmail.com>
~ SPDX-License-Identifier: GPL-3.0-or-later
-->
<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/cardView"
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" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/deckCardImage"
android:layout_width="16dp"
android:layout_height="16dp"
android:contentDescription="@null"
android:src="@drawable/deck" />
<androidx.emoji2.widget.EmojiTextView
android:id="@+id/cardName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:ellipsize="end"
android:maxLines="1"
android:textIsSelectable="false"
android:textStyle="bold"
android:visibility="gone"
tools:text="Card Name"
tools:visibility="visible" />
</LinearLayout>
<androidx.emoji2.widget.EmojiTextView
android:id="@+id/cardDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textAlignment="viewStart"
android:textIsSelectable="false"
android:visibility="gone"
tools:text="Card Description"
tools:visibility="visible" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="4dp"
android:layout_gravity="end"
tools:ignore="UseCompoundDrawables">
<TextView
android:id="@id/messageTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.6"
android:textColor="@color/no_emphasis_text"
android:textIsSelectable="false"
tools:text="12:38" />
<ImageView
android:id="@+id/checkMark"
android:layout_width="wrap_content"
android:layout_height="@dimen/message_bubble_checkmark_height"
android:layout_marginStart="4dp"
android:contentDescription="@null"
app:tint="@color/high_emphasis_text" />
</LinearLayout>
</LinearLayout>
<include
android:id="@+id/reactions"
layout="@layout/reactions_inside_message" />
</com.google.android.flexbox.FlexboxLayout>
</RelativeLayout>

View File

@ -407,6 +407,7 @@ How to translate with transifex:
<string name="nc_sent_poll" formatted="true">%1$s sent a poll.</string>
<string name="nc_sent_location" formatted="true">%1$s sent a location.</string>
<string name="nc_sent_voice" formatted="true">%1$s sent a voice message.</string>
<string name="nc_sent_deck_card" formatted="true">%1$s sent a deck card</string>
<string name="nc_sent_a_gif_you">You sent a GIF.</string>
<string name="nc_sent_an_attachment_you">You sent an attachment.</string>
<string name="nc_sent_an_audio_you">You sent an audio.</string>
@ -415,6 +416,7 @@ How to translate with transifex:
<string name="nc_sent_poll_you">You sent a poll.</string>
<string name="nc_sent_location_you">You sent a location.</string>
<string name="nc_sent_voice_you">You sent a voice message.</string>
<string name="nc_sent_deck_card_you">You sent a deck card</string>
<string name="nc_formatted_message" translatable="false">%1$s: %2$s</string>
<string name="nc_message_quote_cancel_reply">Cancel reply</string>
<!-- When translating to German, please use non-formal variant -->
@ -515,7 +517,7 @@ How to translate with transifex:
<string name="typing_are_typing">are typing …</string>
<string name="typing_1_other">and 1 other is typing …</string>
<string name="typing_x_others">and %1$s others are typing …</string>
<string name="deck_card_description">%1$s in %2$s</string>
<!-- Upload -->
<string name="nc_add_file">Add to conversation</string>
<string name="nc_upload_picture_from_cam">Take photo</string>