Twidere-App-Android-Twitter.../twidere/src/main/kotlin/org/mariotaku/twidere/adapter/MessagesConversationAdapter.kt

242 lines
10 KiB
Kotlin

/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.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 org.mariotaku.twidere.adapter
import android.content.Context
import android.content.res.ColorStateList
import androidx.core.graphics.ColorUtils
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.bumptech.glide.RequestManager
import org.mariotaku.chameleon.Chameleon
import org.mariotaku.chameleon.ChameleonUtils
import org.mariotaku.kpreferences.get
import org.mariotaku.library.objectcursor.ObjectCursor
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.iface.IItemCountsAdapter
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.annotation.PreviewStyle
import org.mariotaku.twidere.constant.linkHighlightOptionKey
import org.mariotaku.twidere.constant.mediaPreviewStyleKey
import org.mariotaku.twidere.constant.nameFirstKey
import org.mariotaku.twidere.exception.UnsupportedCountIndexException
import org.mariotaku.twidere.extension.isSameDay
import org.mariotaku.twidere.extension.model.timestamp
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.ParcelableMessage.MessageType
import org.mariotaku.twidere.provider.TwidereDataStore.Messages
import org.mariotaku.twidere.util.DirectMessageOnLinkClickHandler
import org.mariotaku.twidere.util.ThemeUtils
import org.mariotaku.twidere.util.TwidereLinkify
import org.mariotaku.twidere.view.CardMediaContainer.OnMediaClickListener
import org.mariotaku.twidere.view.holder.LoadIndicatorViewHolder
import org.mariotaku.twidere.view.holder.message.AbsMessageViewHolder
import org.mariotaku.twidere.view.holder.message.MessageViewHolder
import org.mariotaku.twidere.view.holder.message.NoticeSummaryEventViewHolder
import org.mariotaku.twidere.view.holder.message.StickerMessageViewHolder
import java.util.*
class MessagesConversationAdapter(
context: Context,
requestManager: RequestManager
) : LoadMoreSupportAdapter<RecyclerView.ViewHolder>(context, requestManager),
IItemCountsAdapter {
override val itemCounts: ItemCounts = ItemCounts(2)
@PreviewStyle
val mediaPreviewStyle: Int = preferences[mediaPreviewStyleKey]
val linkHighlightingStyle: Int = preferences[linkHighlightOptionKey]
val nameFirst: Boolean = preferences[nameFirstKey]
val linkify: TwidereLinkify = TwidereLinkify(DirectMessageOnLinkClickHandler(context, null, preferences))
val mediaClickListener: OnMediaClickListener = object : OnMediaClickListener {
override fun onMediaClick(view: View, current: ParcelableMedia, accountKey: UserKey?, id: Long) {
listener?.onMediaClick(id.toInt(), current, accountKey)
}
}
val messageRange: IntRange
get() {
return itemCounts.getItemStartPosition(ITEM_START_MESSAGE) until itemCounts[ITEM_START_MESSAGE]
}
var messages: List<ParcelableMessage>? = null
private set
var conversation: ParcelableMessageConversation? = null
private set
var listener: Listener? = null
var displaySenderProfile: Boolean = false
val bubbleColorOutgoing: ColorStateList? = ThemeUtils.getColorStateListFromAttribute(context, R.attr.messageBubbleColor)
val bubbleColorIncoming: ColorStateList? = context.getIncomingMessageColor()
override var loadMoreIndicatorPosition: Long
get() = super.loadMoreIndicatorPosition
set(value) {
super.loadMoreIndicatorPosition = value
updateItemCounts()
}
private val calendars = Pair(Calendar.getInstance(), Calendar.getInstance())
private val reuseMessage = ParcelableMessage()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
when (viewType) {
ITEM_TYPE_TEXT_MESSAGE -> {
val view = inflater.inflate(MessageViewHolder.layoutResource, parent, false)
val holder = MessageViewHolder(view, this)
holder.setup()
return holder
}
ITEM_TYPE_STICKER_MESSAGE -> {
val view = inflater.inflate(StickerMessageViewHolder.layoutResource, parent, false)
val holder = StickerMessageViewHolder(view, this)
holder.setup()
return holder
}
ITEM_TYPE_NOTICE_MESSAGE -> {
val view = inflater.inflate(NoticeSummaryEventViewHolder.layoutResource, parent, false)
val holder = NoticeSummaryEventViewHolder(view, this)
holder.setup()
return holder
}
ITEM_LOAD_OLDER_INDICATOR -> {
val view = inflater.inflate(LoadIndicatorViewHolder.layoutResource, parent, false)
return LoadIndicatorViewHolder(view)
}
}
throw UnsupportedOperationException()
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder.itemViewType) {
ITEM_TYPE_TEXT_MESSAGE, ITEM_TYPE_STICKER_MESSAGE, ITEM_TYPE_NOTICE_MESSAGE -> {
val message = getMessage(position, true)
// Display date for oldest item
var showDate = true
// ... or if current message is > 1 day newer than previous one
if (position < itemCounts.getItemStartPosition(ITEM_START_MESSAGE)
+ itemCounts[ITEM_START_MESSAGE] - 1) {
calendars.first.timeInMillis = getMessageTimestamp(position + 1)
calendars.second.timeInMillis = message.timestamp
showDate = !calendars.first.isSameDay(calendars.second)
}
(holder as AbsMessageViewHolder).display(message, showDate)
}
}
}
override fun getItemCount(): Int {
return itemCounts.itemCount
}
override fun getItemViewType(position: Int): Int {
return when (val countIndex = itemCounts.getItemCountIndex(position)) {
ITEM_START_MESSAGE -> when (getMessage(position, reuse = true).message_type) {
MessageType.STICKER -> {
ITEM_TYPE_STICKER_MESSAGE
}
MessageType.CONVERSATION_CREATE, MessageType.JOIN_CONVERSATION,
MessageType.PARTICIPANTS_LEAVE, MessageType.PARTICIPANTS_JOIN,
MessageType.CONVERSATION_NAME_UPDATE, MessageType.CONVERSATION_AVATAR_UPDATE -> {
ITEM_TYPE_NOTICE_MESSAGE
}
else -> ITEM_TYPE_TEXT_MESSAGE
}
ITEM_START_LOAD_OLDER -> ITEM_LOAD_OLDER_INDICATOR
else -> throw UnsupportedCountIndexException(countIndex, position)
}
}
fun getMessage(position: Int, reuse: Boolean = false): ParcelableMessage {
val messages = this.messages!!
val dataPosition = position - itemCounts.getItemStartPosition(ITEM_START_MESSAGE)
if (reuse && messages is ObjectCursor) {
return messages.setInto(dataPosition, reuseMessage)
}
return messages[dataPosition]
}
private fun getMessageTimestamp(position: Int): Long {
val messages = this.messages!!
if (messages is ObjectCursor) {
val cursor = messages.cursor
cursor.moveToPosition(position)
val indices = messages.indices
val timestamp = cursor.getLong(indices[Messages.MESSAGE_TIMESTAMP])
if (timestamp > 0) return timestamp
return cursor.getLong(indices[Messages.LOCAL_TIMESTAMP])
}
return getMessage(position, false).timestamp
}
fun findUser(key: UserKey): ParcelableUser? {
return conversation?.participants?.firstOrNull { it.key == key }
}
fun setData(conversation: ParcelableMessageConversation?, messages: List<ParcelableMessage>?) {
this.conversation = conversation
this.messages = messages
updateItemCounts()
notifyDataSetChanged()
}
private fun updateItemCounts() {
itemCounts[ITEM_START_MESSAGE] = messages?.size ?: 0
itemCounts[ITEM_START_LOAD_OLDER] = if (loadMoreIndicatorPosition and ILoadMoreSupportAdapter.START != 0L) 1 else 0
}
interface Listener {
fun onMediaClick(position: Int, media: ParcelableMedia, accountKey: UserKey?)
fun onMessageLongClick(position: Int, holder: RecyclerView.ViewHolder): Boolean
}
companion object {
private const val ITEM_START_MESSAGE = 0
private const val ITEM_START_LOAD_OLDER = 1
private const val ITEM_TYPE_TEXT_MESSAGE = 1
private const val ITEM_TYPE_STICKER_MESSAGE = 2
private const val ITEM_TYPE_NOTICE_MESSAGE = 3
private const val ITEM_LOAD_OLDER_INDICATOR = 4
private fun Context.getIncomingMessageColor(): ColorStateList {
val defaultBubbleColor = ThemeUtils.getColorFromAttribute(this, R.attr.messageBubbleColor)
val themeColor = Chameleon.getOverrideTheme(this, ChameleonUtils.getActivity(this)).colorAccent
val normalColor = ColorUtils.compositeColors(ColorUtils.setAlphaComponent(themeColor, 0x33), defaultBubbleColor)
val pressedColor = if (ColorUtils.calculateLuminance(normalColor) < 0.1) {
ColorUtils.compositeColors(0x20FFFFFF, normalColor)
} else {
ColorUtils.compositeColors(0x20000000, normalColor)
}
return ColorStateList(arrayOf(intArrayOf(android.R.attr.state_pressed), intArrayOf(0)),
intArrayOf(pressedColor, normalColor))
}
}
}