diff --git a/build.gradle b/build.gradle index d53720313..107227b82 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { maven { url 'https://plugins.gradle.org/m2/' } } dependencies { - classpath 'com.android.tools.build:gradle:3.1.3' + classpath 'com.android.tools.build:gradle:3.1.4' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } diff --git a/twidere.component.common/src/main/java/org/mariotaku/microblog/library/model/twitter/dm/DirectMessageEvent.java b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/model/twitter/dm/DirectMessageEvent.java index 3b00c1c2e..384f99019 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/microblog/library/model/twitter/dm/DirectMessageEvent.java +++ b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/model/twitter/dm/DirectMessageEvent.java @@ -29,7 +29,7 @@ public class DirectMessageEvent { String id; @JsonField(name = "created_timestamp") - long created_timestamp; + long createdTimestamp; @JsonField(name = "type") String type; @@ -46,6 +46,10 @@ public class DirectMessageEvent { this.type = type; } + public long getCreatedTimestamp() { + return createdTimestamp; + } + public MessageCreate getMessageCreate() { return messageCreate; } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/databinding/ViewBindingAdapters.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/databinding/ViewBindingAdapters.kt new file mode 100644 index 000000000..e356e34a0 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/databinding/ViewBindingAdapters.kt @@ -0,0 +1,9 @@ +package org.mariotaku.twidere.extension.databinding + +import android.databinding.BindingAdapter +import android.view.View + +@BindingAdapter("activated") +fun View.setActivatedBinding(activated: Boolean) { + isActivated = activated +} \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/ParcelableStatusExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/ParcelableStatusExtensions.kt index 32ca30915..6adae7632 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/ParcelableStatusExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/ParcelableStatusExtensions.kt @@ -1,6 +1,9 @@ +@file:JvmName("ParcelableStatusExtensions") + package org.mariotaku.twidere.extension.model import android.content.Context +import android.support.annotation.DrawableRes import android.text.SpannableStringBuilder import org.mariotaku.ktextension.addAllTo import org.mariotaku.ktextension.appendTo @@ -10,15 +13,24 @@ import org.mariotaku.twidere.R import org.mariotaku.twidere.TwidereConstants.USER_TYPE_FANFOU_COM import org.mariotaku.twidere.model.* import org.mariotaku.twidere.model.attachment.QuotedStatus +import org.mariotaku.twidere.task.CreateFavoriteTask +import org.mariotaku.twidere.task.DestroyFavoriteTask +import org.mariotaku.twidere.task.DestroyStatusTask +import org.mariotaku.twidere.task.RetweetStatusTask import org.mariotaku.twidere.util.HtmlEscapeHelper import org.mariotaku.twidere.util.UriUtils import org.mariotaku.twidere.util.UserColorNameManager +import org.mariotaku.twidere.util.Utils import org.mariotaku.twidere.view.ShortTimeView inline val ParcelableStatus.originalId: String get() = if (is_retweet) (retweet_id ?: id) else id + +val ParcelableStatus.userTypeIcon: Int + @DrawableRes get() = Utils.getUserTypeIconRes(user_is_verified, user_is_protected) + val ParcelableStatus.media_type: Int get() = attachment?.media?.firstOrNull()?.type ?: 0 @@ -261,6 +273,19 @@ fun ParcelableStatus.generateDisplayInfo(context: Context): ParcelableStatus.Dis return info } +fun ParcelableStatus.retweetIconActivated(): Boolean { + return !DestroyStatusTask.isRunning(account_key, my_retweet_id) && + (RetweetStatusTask.isRunning(account_key, id) || + retweeted || account_key == retweeted_by_user_key || + my_retweet_id != null) +} + +fun ParcelableStatus.favoriteIconActivated(): Boolean { + return !DestroyFavoriteTask.isRunning(account_key, id) && + (CreateFavoriteTask.isRunning(account_key, id) || is_favorite) +} + + internal inline val String.plainText: String get() = HtmlEscapeHelper.toPlainText(this) private fun parcelableUserMention(key: UserKey, name: String, screenName: String) = ParcelableUserMention().also { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/model/util/ParcelableMessageUtils.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/model/util/ParcelableMessageUtils.kt index a0ac00158..3335c52bc 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/model/util/ParcelableMessageUtils.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/model/util/ParcelableMessageUtils.kt @@ -6,6 +6,7 @@ import org.mariotaku.microblog.library.model.microblog.DMResponse.Entry.Message import org.mariotaku.microblog.library.model.microblog.DMResponse.Entry.Message.Data import org.mariotaku.microblog.library.model.microblog.DirectMessage import org.mariotaku.microblog.library.model.microblog.User +import org.mariotaku.microblog.library.model.twitter.dm.DirectMessageEvent import org.mariotaku.twidere.extension.model.api.addEntities import org.mariotaku.twidere.extension.model.api.getEntityMedia import org.mariotaku.twidere.extension.model.api.key @@ -17,6 +18,7 @@ import org.mariotaku.twidere.model.message.MessageExtras import org.mariotaku.twidere.model.message.StickerExtras import org.mariotaku.twidere.model.message.UserArrayExtras import org.mariotaku.twidere.util.HtmlBuilder +import java.util.* /** * Created by mariotaku on 2017/2/9. @@ -36,6 +38,20 @@ object ParcelableMessageUtils { return result } + fun fromMessage(accountKey: UserKey, event: DirectMessageEvent, outgoing: Boolean, + @FloatRange(from = 0.0, to = 1.0) sortIdAdj: Double = 0.0): ParcelableMessage { + val result = ParcelableMessage() + val message = event.messageCreate + result.applyMessage(accountKey, event, sortIdAdj) + result.is_outgoing = outgoing + if (outgoing) { + result.conversation_id = outgoingConversationId(message.senderId, message.target.recipientId) + } else { + result.conversation_id = incomingConversationId(message.senderId, message.target.recipientId) + } + return result + } + fun fromEntry(accountKey: UserKey, accountType: String, entry: DMResponse.Entry, users: Map, profileImageSize: ModelCreationConfig = ModelCreationConfig.DEFAULT): ParcelableMessage? { when { @@ -107,6 +123,16 @@ object ParcelableMessageUtils { return builder.buildWithIndices() } + private fun formatDirectMessageText(message: DirectMessageEvent.MessageCreate): Pair> { + var text: String? = message.messageData.text + if (text == null) { + text = "" + } + val builder = HtmlBuilder(text, false, true, false) +// builder.addEntities(message.messageData) + return builder.buildWithIndices() + } + private fun ParcelableMessage.applyConversationCreate(accountKey: UserKey, message: Message) { this.commonEntry(accountKey, message) this.message_type = MessageType.CONVERSATION_CREATE @@ -182,6 +208,32 @@ object ParcelableMessageUtils { this.media = message.getEntityMedia() } + private fun ParcelableMessage.applyMessage( + accountKey: UserKey, + event: DirectMessageEvent, + @FloatRange(from = 0.0, to = 1.0) sortIdAdj: Double = 0.0 + ) { + val message = event.messageCreate + this.account_key = accountKey + this.id = UUID.randomUUID().toString() + this.sender_key = UserKey(message.senderId, account_key.host) + this.recipient_key = UserKey(message.target.recipientId, account_key.host) + this.message_timestamp = event.createdTimestamp + this.local_timestamp = this.message_timestamp + this.sort_id = this.message_timestamp + (499 * sortIdAdj).toLong() + + val (type, extras) = typeAndExtras(message) + val (text, spans) = formatDirectMessageText(message) + val messageMedia = message.messageData.attachment.media + this.message_type = type + this.extras = extras + this.text_unescaped = text + this.spans = spans + if (messageMedia != null) { + this.media = arrayOf(messageMedia.toParcelable()) + } + } + private fun formatDirectMessageText(message: DirectMessage): Pair> { val builder = HtmlBuilder(message.text, false, true, false) builder.addEntities(message) @@ -197,6 +249,9 @@ object ParcelableMessageUtils { } return Pair(MessageType.TEXT, null) } + private fun typeAndExtras(message: DirectMessageEvent.MessageCreate): Pair { + return Pair(MessageType.TEXT, null) + } private fun typeAndExtras(data: Data): Triple?> { val attachment = data.attachment ?: return Triple(MessageType.TEXT, null, null) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/GetMessagesTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/GetMessagesTask.kt index 161b09ef2..821b5bca1 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/GetMessagesTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/GetMessagesTask.kt @@ -58,6 +58,7 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations import org.mariotaku.twidere.singleton.BusSingleton import org.mariotaku.twidere.task.BaseAbstractTask import org.mariotaku.twidere.util.DataStoreUtils +import org.mariotaku.twidere.util.DebugLog import org.mariotaku.twidere.util.UriUtils import org.mariotaku.twidere.util.content.ContentResolverUtils import java.util.* @@ -76,7 +77,8 @@ class GetMessagesTask( val messages = try { if (!details.hasDm) throw APINotSupportedException("DM", details.type) getMessages(details, param, i) - } catch (e: MicroBlogException) { + } catch (e: Exception) { + DebugLog.w(tr = e) return@forEachIndexed } storeMessages(context, messages, details, param.showNotification) @@ -103,10 +105,10 @@ class GetMessagesTask( AccountType.TWITTER -> { val twitter = details.newMicroBlogInstance(context, cls = Twitter::class.java) // Use official DM api -// if (details.isOfficial(context)) { -// return getTwitterOfficialMessages(twitter, details, param, index) -// } - getTwitterMessages(twitter, details, param, index) + if (details.isOfficial(context)) { + return getTwitterOfficialMessages(twitter, details, param, index) + } + return getTwitterMessages(twitter, details, param, index) } } // Use default method @@ -210,7 +212,7 @@ class GetMessagesTask( val users = twitter.lookupUsers(events.flatMap { val mc = it.messageCreate ?: return@flatMap emptyList() return@flatMap listOf(mc.senderId, mc.target.recipientId) - }.distinct().toTypedArray()) + }.distinct().toTypedArray()).map { it.toParcelable(details) } events.forEach { event -> val msg = event.messageCreate @@ -220,8 +222,8 @@ class GetMessagesTask( conversations.addLocalConversations(context, accountKey, conversationIds) events.forEachIndexed { i, dm -> - // addConversationMessage(insertMessages, conversations, details, dm, i, events.size, -// false, profileImageSize, updateLastRead) + addConversationMessage(insertMessages, users, conversations, details, dm, i, events.size, + false, updateLastRead = updateLastRead) } return DatabaseUpdateData(conversations.values, insertMessages) @@ -618,15 +620,15 @@ class GetMessagesTask( internal fun addConversationMessage(messages: MutableCollection, conversations: MutableMap, details: AccountDetails, dm: DirectMessage, index: Int, size: Int, - outgoing: Boolean, profileImageSize: ModelCreationConfig = ModelCreationConfig.DEFAULT, + outgoing: Boolean, creationConfig: ModelCreationConfig = ModelCreationConfig.DEFAULT, updateLastRead: Boolean) { val accountKey = details.key val accountType = details.type val message = ParcelableMessageUtils.fromMessage(accountKey, dm, outgoing, 1.0 - (index.toDouble() / size)) messages.add(message) - val sender = dm.sender.toParcelable(accountKey, accountType, creationConfig = profileImageSize) - val recipient = dm.recipient.toParcelable(accountKey, accountType, creationConfig = profileImageSize) + val sender = dm.sender.toParcelable(details, creationConfig = creationConfig) + val recipient = dm.recipient.toParcelable(details, creationConfig = creationConfig) val conversation = conversations.addConversation(message.conversation_id, details, message, setOf(sender, recipient), updateLastRead = updateLastRead) ?: return conversation.conversation_extras_type = ParcelableMessageConversation.ExtrasType.DEFAULT @@ -636,22 +638,23 @@ class GetMessagesTask( } internal fun addConversationMessage(messages: MutableCollection, - conversations: MutableMap, - details: AccountDetails, dm: DirectMessageEvent.MessageCreate, index: Int, size: Int, - outgoing: Boolean, profileImageSize: String = "normal", updateLastRead: Boolean) { + users: List, conversations: MutableMap, + details: AccountDetails, event: DirectMessageEvent, index: Int, size: Int, + outgoing: Boolean, updateLastRead: Boolean) { val accountKey = details.key - val accountType = details.type -// val message = ParcelableMessageUtils.fromMessage(accountKey, dm, outgoing, -// 1.0 - (index.toDouble() / size)) -// messages.add(message) -// val sender = dm.sender.toParcelable(accountKey, accountType, profileImageSize = profileImageSize) -// val recipient = dm.recipient.toParcelable(accountKey, accountType, profileImageSize = profileImageSize) -// val conversation = conversations.addConversation(message.conversation_id, details, -// message, setOf(sender, recipient), updateLastRead = updateLastRead) ?: return -// conversation.conversation_extras_type = ParcelableMessageConversation.ExtrasType.DEFAULT -// if (conversation.conversation_extras == null) { -// conversation.conversation_extras = DefaultConversationExtras() -// } + val message = ParcelableMessageUtils.fromMessage(accountKey, event, outgoing, + 1.0 - (index.toDouble() / size)) + val dm = event.messageCreate + messages.add(message) + val sender = users.find { it.key.id == dm.senderId } + val recipient = users.find { it.key.id == dm.target.recipientId } + val conversation = conversations.addConversation(message.conversation_id, details, + message, setOf(sender, recipient).filterNotNull(), updateLastRead = updateLastRead) + ?: return + conversation.conversation_extras_type = ParcelableMessageConversation.ExtrasType.DEFAULT + if (conversation.conversation_extras == null) { + conversation.conversation_extras = DefaultConversationExtras() + } } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/LargeMediaStatusViewHolder.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/LargeMediaStatusViewHolder.kt index 638e5b284..91bc9e0d1 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/LargeMediaStatusViewHolder.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/LargeMediaStatusViewHolder.kt @@ -35,9 +35,7 @@ import org.mariotaku.twidere.R import org.mariotaku.twidere.adapter.RecyclerPagerAdapter import org.mariotaku.twidere.adapter.iface.IStatusesAdapter import org.mariotaku.twidere.constant.SharedPreferenceConstants.VALUE_LINK_HIGHLIGHT_OPTION_CODE_NONE -import org.mariotaku.twidere.extension.model.applyTo -import org.mariotaku.twidere.extension.model.aspectRatio -import org.mariotaku.twidere.extension.model.contentDescription +import org.mariotaku.twidere.extension.model.* import org.mariotaku.twidere.graphic.like.LikeAnimationDrawable import org.mariotaku.twidere.model.ParcelableMedia import org.mariotaku.twidere.model.ParcelableStatus @@ -112,8 +110,8 @@ class LargeMediaStatusViewHolder(private val adapter: IStatusesAdapter, itemView } textView.hideIfEmpty() - replyButton.isActivated = StatusViewHolder.isRetweetIconActivated(status) - favoriteButton.isActivated = StatusViewHolder.isFavoriteIconActivated(status) + replyButton.isActivated = status.retweetIconActivated() + favoriteButton.isActivated = status.favoriteIconActivated(status) val aspectRatio = status.attachment?.media?.fold(Double.NaN) { acc, media -> if (acc.isNaN()) return@fold media.aspectRatio diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/StatusViewHolder.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/StatusViewHolder.kt index d3b997e4a..36c2c4f4a 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/StatusViewHolder.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/StatusViewHolder.kt @@ -56,17 +56,12 @@ import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.placeholder.PlaceholderObject import org.mariotaku.twidere.singleton.BidiFormatterSingleton -import org.mariotaku.twidere.task.CreateFavoriteTask -import org.mariotaku.twidere.task.DestroyFavoriteTask -import org.mariotaku.twidere.task.DestroyStatusTask -import org.mariotaku.twidere.task.RetweetStatusTask import org.mariotaku.twidere.text.TwidereClickableSpan import org.mariotaku.twidere.text.style.PlaceholderLineSpan import org.mariotaku.twidere.util.HtmlEscapeHelper.toPlainText import org.mariotaku.twidere.util.HtmlSpanBuilder import org.mariotaku.twidere.util.UriCreator import org.mariotaku.twidere.util.UserColorNameManager -import org.mariotaku.twidere.util.Utils.getUserTypeIconRes import org.mariotaku.twidere.view.ProfileImageView import org.mariotaku.twidere.view.ShortTimeView import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder @@ -241,18 +236,6 @@ class StatusViewHolder(var adapter: IStatusesAdapter, val binding: ItemStatusBin nameView.placeholder = false - if (adapter.profileImageEnabled) { - profileImageView.visibility = View.VISIBLE - - profileTypeView.setImageResource(getUserTypeIconRes(status.user_is_verified, status.user_is_protected)) - profileTypeView.visibility = View.VISIBLE - } else { - profileImageView.visibility = View.GONE - - profileTypeView.setImageDrawable(null) - profileTypeView.visibility = View.GONE - } - if (adapter.showAccountsColor) { itemContent.drawEnd(status.account_color) } else { @@ -264,10 +247,6 @@ class StatusViewHolder(var adapter: IStatusesAdapter, val binding: ItemStatusBin retweetButton.drawable?.mutate() - retweetButton.isActivated = isRetweetIconActivated(status) - - favoriteButton.isActivated = isFavoriteIconActivated(status) - attachmentContainer.setVisible(attachmentHolder != null) attachmentHolder?.display(status) } @@ -504,18 +483,6 @@ class StatusViewHolder(var adapter: IStatusesAdapter, val binding: ItemStatusBin appendCompat(" ", PlaceholderLineSpan(.5f), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } - fun isRetweetIconActivated(status: ParcelableStatus): Boolean { - return !DestroyStatusTask.isRunning(status.account_key, status.my_retweet_id) && - (RetweetStatusTask.isRunning(status.account_key, status.id) || - status.retweeted || status.account_key == status.retweeted_by_user_key || - status.my_retweet_id != null) - } - - fun isFavoriteIconActivated(status: ParcelableStatus): Boolean { - return !DestroyFavoriteTask.isRunning(status.account_key, status.id) && - (CreateFavoriteTask.isRunning(status.account_key, status.id) || status.is_favorite) - } - } } diff --git a/twidere/src/main/res/layout/item_status.xml b/twidere/src/main/res/layout/item_status.xml index 7d942b0db..04a32a913 100644 --- a/twidere/src/main/res/layout/item_status.xml +++ b/twidere/src/main/res/layout/item_status.xml @@ -27,7 +27,7 @@ - +