Signal-Android/app/src/main/java/org/thoughtcrime/securesms/conversation/quotes/MessageQuotesBottomSheet.kt

224 wiersze
8.6 KiB
Kotlin

package org.thoughtcrime.securesms.conversation.quotes
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.doOnNextLayout
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialogFragment
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager
import org.thoughtcrime.securesms.conversation.ConversationAdapter
import org.thoughtcrime.securesms.conversation.colors.Colorizer
import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
import org.thoughtcrime.securesms.linkpreview.LinkPreview
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.BottomSheetUtil
import org.thoughtcrime.securesms.util.LifecycleDisposable
import org.thoughtcrime.securesms.util.StickyHeaderDecoration
import org.thoughtcrime.securesms.util.fragments.findListener
import java.util.Locale
class MessageQuotesBottomSheet : FixedRoundedCornerBottomSheetDialogFragment() {
override val peekHeightPercentage: Float = 0.66f
override val themeResId: Int = R.style.Widget_Signal_FixedRoundedCorners_Messages
private lateinit var messageAdapter: ConversationAdapter
private val viewModel: MessageQuotesViewModel by viewModels(
factoryProducer = {
val messageId = MessageId.deserialize(arguments?.getString(KEY_MESSAGE_ID, null) ?: throw IllegalArgumentException())
val conversationRecipientId = RecipientId.from(arguments?.getString(KEY_CONVERSATION_RECIPIENT_ID, null) ?: throw IllegalArgumentException())
MessageQuotesViewModel.Factory(ApplicationDependencies.getApplication(), messageId, conversationRecipientId)
}
)
private val disposables: LifecycleDisposable = LifecycleDisposable()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val view = inflater.inflate(R.layout.message_quotes_bottom_sheet, container, false)
disposables.bindTo(viewLifecycleOwner)
return view
}
@SuppressLint("WrongThread")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val conversationRecipientId = RecipientId.from(arguments?.getString(KEY_CONVERSATION_RECIPIENT_ID, null) ?: throw IllegalArgumentException())
val conversationRecipient = Recipient.resolved(conversationRecipientId)
val colorizer = Colorizer()
messageAdapter = ConversationAdapter(requireContext(), viewLifecycleOwner, GlideApp.with(this), Locale.getDefault(), ConversationAdapterListener(), conversationRecipient, colorizer).apply {
setCondensedMode(true)
}
val list: RecyclerView = view.findViewById<RecyclerView>(R.id.quotes_list).apply {
layoutManager = SmoothScrollingLinearLayoutManager(requireContext(), true)
adapter = messageAdapter
itemAnimator = null
addItemDecoration(MessageQuoteHeaderDecoration(context))
doOnNextLayout {
// Adding this without waiting for a layout pass would result in an indeterminate amount of padding added to the top of the view
addItemDecoration(StickyHeaderDecoration(messageAdapter, false, false, ConversationAdapter.HEADER_TYPE_INLINE_DATE))
}
}
val recyclerViewColorizer = RecyclerViewColorizer(list)
disposables += viewModel.getMessages().subscribe { messages ->
messageAdapter.submitList(messages) {
(list.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(messages.size - 1, 100)
}
recyclerViewColorizer.setChatColors(conversationRecipient.chatColors)
}
disposables += viewModel.getNameColorsMap().subscribe { map ->
colorizer.onNameColorsChanged(map)
messageAdapter.notifyItemRangeChanged(0, messageAdapter.itemCount, ConversationAdapter.PAYLOAD_NAME_COLORS)
}
}
private fun getCallback(): Callback {
return findListener<Callback>() ?: throw IllegalStateException("Parent must implement callback interface!")
}
private fun getAdapterListener(): ConversationAdapter.ItemClickListener {
return getCallback().getConversationAdapterListener()
}
private inner class ConversationAdapterListener : ConversationAdapter.ItemClickListener by getAdapterListener() {
override fun onItemClick(item: MultiselectPart) {
dismiss()
getCallback().jumpToMessage(item.getMessageRecord())
}
override fun onItemLongClick(itemView: View, item: MultiselectPart) {
onItemClick(item)
}
override fun onQuoteClicked(messageRecord: MmsMessageRecord) {
dismiss()
getCallback().jumpToMessage(messageRecord)
}
override fun onLinkPreviewClicked(linkPreview: LinkPreview) {
dismiss()
getAdapterListener().onLinkPreviewClicked(linkPreview)
}
override fun onQuotedIndicatorClicked(messageRecord: MessageRecord) {
dismiss()
getAdapterListener().onQuotedIndicatorClicked(messageRecord)
}
override fun onReactionClicked(multiselectPart: MultiselectPart, messageId: Long, isMms: Boolean) {
dismiss()
getAdapterListener().onReactionClicked(multiselectPart, messageId, isMms)
}
override fun onGroupMemberClicked(recipientId: RecipientId, groupId: GroupId) {
dismiss()
getAdapterListener().onGroupMemberClicked(recipientId, groupId)
}
override fun onMessageWithRecaptchaNeededClicked(messageRecord: MessageRecord) {
dismiss()
getAdapterListener().onMessageWithRecaptchaNeededClicked(messageRecord)
}
override fun onGroupMigrationLearnMoreClicked(membershipChange: GroupMigrationMembershipChange) {
dismiss()
getAdapterListener().onGroupMigrationLearnMoreClicked(membershipChange)
}
override fun onChatSessionRefreshLearnMoreClicked() {
dismiss()
getAdapterListener().onChatSessionRefreshLearnMoreClicked()
}
override fun onBadDecryptLearnMoreClicked(author: RecipientId) {
dismiss()
getAdapterListener().onBadDecryptLearnMoreClicked(author)
}
override fun onSafetyNumberLearnMoreClicked(recipient: Recipient) {
dismiss()
getAdapterListener().onSafetyNumberLearnMoreClicked(recipient)
}
override fun onJoinGroupCallClicked() {
dismiss()
getAdapterListener().onJoinGroupCallClicked()
}
override fun onInviteFriendsToGroupClicked(groupId: GroupId.V2) {
dismiss()
getAdapterListener().onInviteFriendsToGroupClicked(groupId)
}
override fun onEnableCallNotificationsClicked() {
dismiss()
getAdapterListener().onEnableCallNotificationsClicked()
}
override fun onCallToAction(action: String) {
dismiss()
getAdapterListener().onCallToAction(action)
}
override fun onDonateClicked() {
dismiss()
getAdapterListener().onDonateClicked()
}
override fun onRecipientNameClicked(target: RecipientId) {
dismiss()
getAdapterListener().onRecipientNameClicked(target)
}
override fun onViewGiftBadgeClicked(messageRecord: MessageRecord) {
dismiss()
getAdapterListener().onViewGiftBadgeClicked(messageRecord)
}
}
interface Callback {
fun getConversationAdapterListener(): ConversationAdapter.ItemClickListener
fun jumpToMessage(messageRecord: MessageRecord)
}
companion object {
private const val KEY_MESSAGE_ID = "message_id"
private const val KEY_CONVERSATION_RECIPIENT_ID = "conversation_recipient_id"
@JvmStatic
fun show(fragmentManager: FragmentManager, messageId: MessageId, conversationRecipientId: RecipientId) {
val args = Bundle().apply {
putString(KEY_MESSAGE_ID, messageId.serialize())
putString(KEY_CONVERSATION_RECIPIENT_ID, conversationRecipientId.serialize())
}
val fragment = MessageQuotesBottomSheet().apply {
arguments = args
}
fragment.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
}
}
}