package org.thoughtcrime.securesms.home import android.content.Context import android.content.res.Resources import android.graphics.Typeface import android.graphics.drawable.ColorDrawable import android.util.AttributeSet import android.util.TypedValue import android.view.View import android.widget.LinearLayout import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import network.loki.messenger.databinding.ViewConversationBinding import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.highlightMentions import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_ALL import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_NONE import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.getAccentColor import org.thoughtcrime.securesms.util.getConversationUnread import java.util.Locale import javax.inject.Inject @AndroidEntryPoint class ConversationView : LinearLayout { @Inject lateinit var configFactory: ConfigFactory private val binding: ViewConversationBinding by lazy { ViewConversationBinding.bind(this) } private val screenWidth = Resources.getSystem().displayMetrics.widthPixels var thread: ThreadRecord? = null // region Lifecycle constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet) : super(context, attrs) constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) override fun onFinishInflate() { super.onFinishInflate() layoutParams = RecyclerView.LayoutParams(screenWidth, RecyclerView.LayoutParams.WRAP_CONTENT) } // endregion // region Updating fun bind(thread: ThreadRecord, isTyping: Boolean, glide: GlideRequests) { this.thread = thread if (thread.isPinned) { binding.conversationViewDisplayNameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds( 0, 0, R.drawable.ic_pin, 0 ) } else { binding.conversationViewDisplayNameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0) } binding.root.background = if (thread.unreadCount > 0) { ContextCompat.getDrawable(context, R.drawable.conversation_unread_background) } else { ContextCompat.getDrawable(context, R.drawable.conversation_view_background) } binding.profilePictureView.root.glide = glide val unreadCount = thread.unreadCount if (thread.recipient.isBlocked) { binding.accentView.setBackgroundResource(R.color.destructive) binding.accentView.visibility = View.VISIBLE } else { val accentColor = context.getAccentColor() val background = ColorDrawable(accentColor) binding.accentView.background = background // Using thread.isRead we can determine if the last message was our own, and display it as 'read' even though previous messages may not be // This would also not trigger the disappearing message timer which may or may not be desirable binding.accentView.visibility = if (unreadCount > 0 && !thread.isRead) View.VISIBLE else View.INVISIBLE } val formattedUnreadCount = if (unreadCount == 0) { null } else { if (unreadCount < 10000) unreadCount.toString() else "9999+" } binding.unreadCountTextView.text = formattedUnreadCount val textSize = if (unreadCount < 1000) 12.0f else 10.0f binding.unreadCountTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize) binding.unreadCountIndicator.isVisible = (unreadCount != 0 && !thread.isRead) || (configFactory.convoVolatile?.getConversationUnread(thread) == true) binding.unreadMentionTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize) binding.unreadMentionIndicator.isVisible = (thread.unreadMentionCount != 0 && thread.recipient.address.isGroup) val senderDisplayName = getUserDisplayName(thread.recipient) ?: thread.recipient.address.toString() binding.conversationViewDisplayNameTextView.text = senderDisplayName binding.timestampTextView.text = DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), thread.date) val recipient = thread.recipient binding.muteIndicatorImageView.isVisible = recipient.isMuted || recipient.notifyType != NOTIFY_TYPE_ALL val drawableRes = if (recipient.isMuted || recipient.notifyType == NOTIFY_TYPE_NONE) { R.drawable.ic_outline_notifications_off_24 } else { R.drawable.ic_notifications_mentions } binding.muteIndicatorImageView.setImageResource(drawableRes) val rawSnippet = thread.getDisplayBody(context) val snippet = highlightMentions(rawSnippet, thread.threadId, context) binding.snippetTextView.text = snippet binding.snippetTextView.typeface = if (unreadCount > 0 && !thread.isRead) Typeface.DEFAULT_BOLD else Typeface.DEFAULT binding.snippetTextView.visibility = if (isTyping) View.GONE else View.VISIBLE if (isTyping) { binding.typingIndicatorView.root.startAnimation() } else { binding.typingIndicatorView.root.stopAnimation() } binding.typingIndicatorView.root.visibility = if (isTyping) View.VISIBLE else View.GONE binding.statusIndicatorImageView.visibility = View.VISIBLE when { !thread.isOutgoing -> binding.statusIndicatorImageView.visibility = View.GONE thread.isFailed -> { val drawable = ContextCompat.getDrawable(context, R.drawable.ic_error)?.mutate() drawable?.setTint(ContextCompat.getColor(context, R.color.destructive)) binding.statusIndicatorImageView.setImageDrawable(drawable) } thread.isPending -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_circle_dot_dot_dot) thread.isRead -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_filled_circle_check) else -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_circle_check) } binding.profilePictureView.root.update(thread.recipient) } fun recycle() { binding.profilePictureView.root.recycle() } private fun getUserDisplayName(recipient: Recipient): String? { return if (recipient.isLocalNumber) { context.getString(R.string.note_to_self) } else { recipient.toShortString() // Internally uses the Contact API } } // endregion }