supports participants leave/join event

This commit is contained in:
Mariotaku Lee 2017-02-12 22:26:09 +08:00
parent 59daca9025
commit 6fdd829ce0
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
16 changed files with 414 additions and 115 deletions

View File

@ -122,6 +122,10 @@ public class DMResponse implements Parcelable {
Message joinConversation;
@JsonField(name = "message")
Message message;
@JsonField(name = "participants_leave")
Message participantsLeave;
@JsonField(name = "participants_join")
Message participantsJoin;
public Message getJoinConversation() {
return joinConversation;
@ -135,10 +139,21 @@ public class DMResponse implements Parcelable {
return message;
}
public Message getParticipantsLeave() {
return participantsLeave;
}
public Message getParticipantsJoin() {
return participantsJoin;
}
@Override
public String toString() {
return "Entry{" +
"message=" + message +
"conversationCreate=" + conversationCreate +
", joinConversation=" + joinConversation +
", message=" + message +
", participantsLeave=" + participantsLeave +
'}';
}
@ -161,6 +176,9 @@ public class DMResponse implements Parcelable {
@JsonField(name = "request_id")
String requestId;
@JsonField(name = "sender_id")
String senderId;
@JsonField(name = "message_data")
Data messageData;
@ -187,6 +205,10 @@ public class DMResponse implements Parcelable {
return requestId;
}
public String getSenderId() {
return senderId;
}
public Data getMessageData() {
return messageData;
}
@ -240,9 +262,9 @@ public class DMResponse implements Parcelable {
@JsonField(name = "time")
long time;
@JsonField(name = "sender_id")
long senderId;
String senderId;
@JsonField(name = "recipient_id")
long recipientId;
String recipientId;
@JsonField(name = "text")
String text;
@JsonField(name = "entities")
@ -254,11 +276,11 @@ public class DMResponse implements Parcelable {
return text;
}
public long getRecipientId() {
public String getRecipientId() {
return recipientId;
}
public long getSenderId() {
public String getSenderId() {
return senderId;
}
@ -427,6 +449,26 @@ public class DMResponse implements Parcelable {
@Type
String type;
public String getType() {
return type;
}
public String getStatus() {
return status;
}
public long getSortTimestamp() {
return sortTimestamp;
}
public long getSortEventId() {
return sortEventId;
}
public boolean isReadOnly() {
return readOnly;
}
public Participant[] getParticipants() {
return participants;
}
@ -454,7 +496,7 @@ public class DMResponse implements Parcelable {
@StringDef({Type.ONE_TO_ONE, Type.GROUP_DM})
@Retention(RetentionPolicy.SOURCE)
public @interface Type {
String ONE_TO_ONE = "one_to_one", GROUP_DM = "group_dm";
String ONE_TO_ONE = "ONE_TO_ONE", GROUP_DM = "GROUP_DM";
}
@ParcelablePlease
@ -462,12 +504,18 @@ public class DMResponse implements Parcelable {
public static class Participant implements Parcelable {
@JsonField(name = "user_id")
long userId;
String userId;
@JsonField(name = "join_time")
long joinTime;
public long getUserId() {
public String getUserId() {
return userId;
}
public long getJoinTime() {
return joinTime;
}
@Override
public int describeContents() {
return 0;

View File

@ -152,10 +152,12 @@ public class ParcelableMessage {
@StringDef({MessageType.TEXT, MessageType.STICKER, MessageType.CONVERSATION_CREATE,
MessageType.JOIN_CONVERSATION})
MessageType.JOIN_CONVERSATION, MessageType.PARTICIPANTS_LEAVE})
public @interface MessageType {
String CONVERSATION_CREATE = "conversation_create";
String JOIN_CONVERSATION = "join_conversation";
String PARTICIPANTS_LEAVE = "participants_leave";
String PARTICIPANTS_JOIN = "participants_join";
String TEXT = "text";
String STICKER = "sticker";
}

View File

@ -40,6 +40,10 @@ public abstract class MessageExtras implements Parcelable {
switch (messageType) {
case MessageType.STICKER:
return LoganSquare.parse(json, StickerExtras.class);
case MessageType.JOIN_CONVERSATION:
case MessageType.PARTICIPANTS_LEAVE:
case MessageType.PARTICIPANTS_JOIN:
return LoganSquare.parse(json, UserArrayExtras.class);
}
return null;
}

View File

@ -0,0 +1,72 @@
/*
* Twidere - Twitter client for Android
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.mariotaku.twidere.model.message;
import android.os.Parcel;
import android.os.Parcelable;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease;
import org.mariotaku.twidere.model.ParcelableUser;
/**
* Created by mariotaku on 2017/2/12.
*/
@ParcelablePlease
@JsonObject
public class UserArrayExtras extends MessageExtras implements Parcelable {
@JsonField(name = "users")
ParcelableUser[] users;
public ParcelableUser[] getUsers() {
return users;
}
public void setUsers(final ParcelableUser[] users) {
this.users = users;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
UserArrayExtrasParcelablePlease.writeToParcel(this, dest, flags);
}
public static final Creator<UserArrayExtras> CREATOR = new Creator<UserArrayExtras>() {
public UserArrayExtras createFromParcel(Parcel source) {
UserArrayExtras target = new UserArrayExtras();
UserArrayExtrasParcelablePlease.readFromParcel(target, source);
return target;
}
public UserArrayExtras[] newArray(int size) {
return new UserArrayExtras[size];
}
};
}

View File

@ -29,14 +29,14 @@ import org.mariotaku.twidere.adapter.iface.IItemCountsAdapter
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.extension.model.timestamp
import org.mariotaku.twidere.model.ItemCounts
import org.mariotaku.twidere.model.ParcelableMessage
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.ParcelableMessage.MessageType
import org.mariotaku.twidere.util.TwidereLinkify
import org.mariotaku.twidere.view.holder.message.AbsMessageViewHolder
import org.mariotaku.twidere.view.holder.message.ConversationCreateMessageViewHolder
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.*
@ -48,13 +48,13 @@ class MessagesConversationAdapter(context: Context) : LoadMoreSupportAdapter<Rec
@PreviewStyle
val mediaPreviewStyle: Int = preferences[mediaPreviewStyleKey]
val linkHighlightingStyle: Int = preferences[linkHighlightOptionKey]
val nameFirst: Boolean = preferences[nameFirstKey]
val linkify: TwidereLinkify = TwidereLinkify(null)
var messages: List<ParcelableMessage>? = null
set(value) {
field = value
notifyDataSetChanged()
}
private set
var conversation: ParcelableMessageConversation? = null
private set
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
@ -67,9 +67,10 @@ class MessagesConversationAdapter(context: Context) : LoadMoreSupportAdapter<Rec
val view = inflater.inflate(StickerMessageViewHolder.layoutResource, parent, false)
return StickerMessageViewHolder(view, this)
}
ITEM_TYPE_CONVERSATION_CREATE -> {
val view = inflater.inflate(ConversationCreateMessageViewHolder.layoutResource, parent, false)
return ConversationCreateMessageViewHolder(view, this)
ITEM_TYPE_CONVERSATION_CREATE, ITEM_TYPE_JOIN_CONVERSATION,
ITEM_TYPE_PARTICIPANTS_LEAVE, ITEM_TYPE_PARTICIPANTS_JOIN -> {
val view = inflater.inflate(NoticeSummaryEventViewHolder.layoutResource, parent, false)
return NoticeSummaryEventViewHolder(view, this)
}
}
throw UnsupportedOperationException()
@ -77,7 +78,8 @@ class MessagesConversationAdapter(context: Context) : LoadMoreSupportAdapter<Rec
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder.itemViewType) {
ITEM_TYPE_TEXT_MESSAGE, ITEM_TYPE_STICKER_MESSAGE, ITEM_TYPE_CONVERSATION_CREATE -> {
ITEM_TYPE_TEXT_MESSAGE, ITEM_TYPE_STICKER_MESSAGE, ITEM_TYPE_CONVERSATION_CREATE,
ITEM_TYPE_JOIN_CONVERSATION, ITEM_TYPE_PARTICIPANTS_LEAVE, ITEM_TYPE_PARTICIPANTS_JOIN -> {
val message = getMessage(position)!!
// Display date for oldest item
var showDate = true
@ -99,10 +101,6 @@ class MessagesConversationAdapter(context: Context) : LoadMoreSupportAdapter<Rec
return itemCounts.itemCount
}
fun getMessage(position: Int): ParcelableMessage? {
return messages?.get(position - itemCounts.getItemStartPosition(0))
}
override fun getItemViewType(position: Int): Int {
when (itemCounts.getItemCountIndex(position)) {
ITEM_START_MESSAGE -> {
@ -113,6 +111,15 @@ class MessagesConversationAdapter(context: Context) : LoadMoreSupportAdapter<Rec
MessageType.CONVERSATION_CREATE -> {
return ITEM_TYPE_CONVERSATION_CREATE
}
MessageType.JOIN_CONVERSATION -> {
return ITEM_TYPE_JOIN_CONVERSATION
}
MessageType.PARTICIPANTS_LEAVE -> {
return ITEM_TYPE_PARTICIPANTS_LEAVE
}
MessageType.PARTICIPANTS_JOIN -> {
return ITEM_TYPE_PARTICIPANTS_JOIN
}
else -> return ITEM_TYPE_TEXT_MESSAGE
}
}
@ -120,12 +127,30 @@ class MessagesConversationAdapter(context: Context) : LoadMoreSupportAdapter<Rec
throw UnsupportedOperationException()
}
fun getMessage(position: Int): ParcelableMessage? {
return messages?.get(position - itemCounts.getItemStartPosition(0))
}
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
notifyDataSetChanged()
}
companion object {
private const val ITEM_START_MESSAGE = 0
const val ITEM_TYPE_TEXT_MESSAGE = 1
const val ITEM_TYPE_STICKER_MESSAGE = 2
const val ITEM_TYPE_CONVERSATION_CREATE = 3
const val ITEM_TYPE_JOIN_CONVERSATION = 4
const val ITEM_TYPE_PARTICIPANTS_LEAVE = 5
const val ITEM_TYPE_PARTICIPANTS_JOIN = 6
}
}
}

View File

@ -4,8 +4,10 @@ import android.content.Context
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import org.mariotaku.kpreferences.get
import org.mariotaku.twidere.adapter.iface.IItemCountsAdapter
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.constant.nameFirstKey
import org.mariotaku.twidere.model.ItemCounts
import org.mariotaku.twidere.model.ParcelableMessageConversation
import org.mariotaku.twidere.view.holder.LoadIndicatorViewHolder
@ -31,6 +33,8 @@ class MessagesEntriesAdapter(context: Context) : LoadMoreSupportAdapter<Recycler
notifyDataSetChanged()
}
val nameFirst: Boolean = preferences[nameFirstKey]
var listener: MessageConversationClickListener? = null
override fun getItemCount(): Int {
@ -87,4 +91,5 @@ class MessagesEntriesAdapter(context: Context) : LoadMoreSupportAdapter<Recycler
const val ITEM_VIEW_TYPE_LOAD_INDICATOR = 2
}
}

View File

@ -4,12 +4,12 @@ import android.content.Context
import org.mariotaku.twidere.R
import org.mariotaku.twidere.model.AccountDetails
import org.mariotaku.twidere.model.ParcelableMessage
import org.mariotaku.twidere.model.ParcelableMessage.MessageType
import org.mariotaku.twidere.model.ParcelableMessageConversation
import org.mariotaku.twidere.model.ParcelableMessageConversation.ConversationType
import org.mariotaku.twidere.model.ParcelableUser
import org.mariotaku.twidere.util.UserColorNameManager
fun ParcelableMessageConversation.setFrom(message: ParcelableMessage, details: AccountDetails) {
fun ParcelableMessageConversation.applyFrom(message: ParcelableMessage, details: AccountDetails) {
account_key = details.key
account_color = details.color
message_type = message.message_type
@ -39,13 +39,10 @@ fun ParcelableMessageConversation.getConversationName(context: Context): Pair<St
}, null)
}
fun ParcelableMessageConversation.getSummaryText(context: Context): String {
when (message_type) {
MessageType.STICKER -> {
return context.getString(R.string.message_summary_type_sticker)
}
}
return text_unescaped
fun ParcelableMessageConversation.getSummaryText(context: Context, manager: UserColorNameManager,
nameFirst: Boolean): CharSequence? {
return getSummaryText(context, manager, nameFirst, message_type, extras, sender_key,
text_unescaped, this)
}
val ParcelableMessageConversation.user: ParcelableUser?

View File

@ -1,6 +1,14 @@
package org.mariotaku.twidere.extension.model
import android.content.Context
import org.mariotaku.twidere.R
import org.mariotaku.twidere.model.ParcelableMessage
import org.mariotaku.twidere.model.ParcelableMessage.MessageType
import org.mariotaku.twidere.model.ParcelableMessageConversation
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.message.MessageExtras
import org.mariotaku.twidere.model.message.UserArrayExtras
import org.mariotaku.twidere.util.UserColorNameManager
/**
* Created by mariotaku on 2017/2/9.
@ -8,3 +16,53 @@ import org.mariotaku.twidere.model.ParcelableMessage
val ParcelableMessage.timestamp: Long
get() = if (message_timestamp > 0) message_timestamp else local_timestamp
fun ParcelableMessage.getSummaryText(context: Context, manager: UserColorNameManager,
conversation: ParcelableMessageConversation?, nameFirst: Boolean): CharSequence? {
return getSummaryText(context, manager, nameFirst, message_type, extras, sender_key,
text_unescaped, conversation)
}
internal fun getSummaryText(context: Context, manager: UserColorNameManager, nameFirst: Boolean,
messageType: String?, extras: MessageExtras?, senderKey: UserKey?, text: String?,
conversation: ParcelableMessageConversation?): CharSequence? {
when (messageType) {
MessageType.STICKER -> {
return context.getString(R.string.message_summary_type_sticker)
}
MessageType.JOIN_CONVERSATION -> {
return context.getString(R.string.message_join_conversation)
}
MessageType.CONVERSATION_CREATE -> {
return context.getString(R.string.message_conversation_created)
}
MessageType.PARTICIPANTS_JOIN -> {
val users = (extras as UserArrayExtras).users
val sender = conversation?.participants?.firstOrNull { senderKey == it.key }
val res = context.resources
val joinName = if (users.size == 1) {
manager.getDisplayName(users[0], nameFirst)
} else {
res.getQuantityString(R.plurals.N_users, users.size, users.size)
}
if (sender != null) {
return res.getString(R.string.message_format_participants_join_added,
manager.getDisplayName(sender, nameFirst), joinName)
} else {
return res.getString(R.string.message_format_participants_join, joinName)
}
}
MessageType.PARTICIPANTS_LEAVE -> {
val users = (extras as UserArrayExtras).users
val res = context.resources
if (users.size == 1) {
val displayName = manager.getDisplayName(users[0], nameFirst)
return res.getString(R.string.message_format_participants_leave, displayName)
} else {
val usersName = res.getQuantityString(R.plurals.N_users, users.size, users.size)
return res.getString(R.string.message_format_participants_leave, usersName)
}
}
}
return text
}

View File

@ -1,5 +1,6 @@
package org.mariotaku.twidere.fragment
import android.content.Context
import android.os.Bundle
import android.support.v4.app.LoaderManager
import android.support.v4.content.Loader
@ -17,9 +18,12 @@ import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_KEY
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_CONVERSATION_ID
import org.mariotaku.twidere.loader.ObjectCursorLoader
import org.mariotaku.twidere.model.ParcelableMessage
import org.mariotaku.twidere.model.ParcelableMessageConversation
import org.mariotaku.twidere.model.ParcelableMessageCursorIndices
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.provider.TwidereDataStore.Messages
import org.mariotaku.twidere.util.DataStoreUtils
import java.util.concurrent.atomic.AtomicReference
class MessagesConversationFragment : BaseFragment(), LoaderManager.LoaderCallbacks<List<ParcelableMessage>?> {
private lateinit var adapter: MessagesConversationAdapter
@ -40,22 +44,40 @@ class MessagesConversationFragment : BaseFragment(), LoaderManager.LoaderCallbac
}
override fun onCreateLoader(id: Int, args: Bundle?): Loader<List<ParcelableMessage>?> {
val loader = ObjectCursorLoader(context, ParcelableMessageCursorIndices::class.java)
loader.uri = Messages.CONTENT_URI
loader.projection = Messages.COLUMNS
loader.selection = Expression.and(Expression.equalsArgs(Messages.ACCOUNT_KEY),
Expression.equalsArgs(Messages.CONVERSATION_ID)).sql
loader.selectionArgs = arrayOf(accountKey.toString(), conversationId)
loader.sortOrder = OrderBy(Messages.SORT_ID, false).sql
return loader
return ConversationLoader(context, accountKey, conversationId)
}
override fun onLoaderReset(loader: Loader<List<ParcelableMessage>?>) {
adapter.messages = null
adapter.setData(null, null)
}
override fun onLoadFinished(loader: Loader<List<ParcelableMessage>?>, data: List<ParcelableMessage>?) {
adapter.messages = data
val conversation = (loader as? ConversationLoader)?.conversation
adapter.setData(conversation, data)
}
internal class ConversationLoader(
context: Context,
val accountKey: UserKey,
val conversationId: String
) : ObjectCursorLoader<ParcelableMessage>(context, ParcelableMessageCursorIndices::class.java) {
private val atomicConversation = AtomicReference<ParcelableMessageConversation?>()
val conversation: ParcelableMessageConversation? get() = atomicConversation.get()
init {
uri = Messages.CONTENT_URI
projection = Messages.COLUMNS
selection = Expression.and(Expression.equalsArgs(Messages.ACCOUNT_KEY),
Expression.equalsArgs(Messages.CONVERSATION_ID)).sql
selectionArgs = arrayOf(accountKey.toString(), conversationId)
sortOrder = OrderBy(Messages.SORT_ID, false).sql
}
override fun onLoadInBackground(): MutableList<ParcelableMessage> {
atomicConversation.set(DataStoreUtils.findMessageConversation(context, accountKey, conversationId))
return super.onLoadInBackground()
}
}
}

View File

@ -3,12 +3,14 @@ package org.mariotaku.twidere.model.util
import android.support.annotation.FloatRange
import org.mariotaku.microblog.library.twitter.model.DMResponse
import org.mariotaku.microblog.library.twitter.model.DirectMessage
import org.mariotaku.microblog.library.twitter.model.User
import org.mariotaku.twidere.model.ParcelableMedia
import org.mariotaku.twidere.model.ParcelableMessage
import org.mariotaku.twidere.model.ParcelableMessage.MessageType
import org.mariotaku.twidere.model.UserKey
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.InternalTwitterContentUtils
/**
@ -18,7 +20,8 @@ object ParcelableMessageUtils {
fun fromMessage(accountKey: UserKey, message: DirectMessage, outgoing: Boolean,
@FloatRange(from = 0.0, to = 1.0) sortIdAdj: Double = 0.0): ParcelableMessage {
val result = message(accountKey, message, sortIdAdj)
val result = ParcelableMessage()
result.applyMessage(accountKey, message, sortIdAdj)
result.is_outgoing = outgoing
if (outgoing) {
result.conversation_id = outgoingConversationId(message.senderId, message.recipientId)
@ -28,7 +31,7 @@ object ParcelableMessageUtils {
return result
}
fun fromEntry(accountKey: UserKey, entry: DMResponse.Entry): ParcelableMessage? {
fun fromEntry(accountKey: UserKey, entry: DMResponse.Entry, users: Map<String, User>): ParcelableMessage? {
when {
entry.message != null -> {
return ParcelableMessage().apply { applyMessage(accountKey, entry.message) }
@ -36,6 +39,21 @@ object ParcelableMessageUtils {
entry.conversationCreate != null -> {
return ParcelableMessage().apply { applyConversationCreate(accountKey, entry.conversationCreate) }
}
entry.joinConversation != null -> {
return ParcelableMessage().apply {
applyUsersEvent(accountKey, entry.joinConversation, users, MessageType.JOIN_CONVERSATION)
}
}
entry.participantsLeave != null -> {
return ParcelableMessage().apply {
applyUsersEvent(accountKey, entry.participantsLeave, users, MessageType.PARTICIPANTS_LEAVE)
}
}
entry.participantsJoin != null -> {
return ParcelableMessage().apply {
applyUsersEvent(accountKey, entry.participantsJoin, users, MessageType.PARTICIPANTS_JOIN)
}
}
}
return null
}
@ -52,11 +70,6 @@ object ParcelableMessageUtils {
this.commonEntry(accountKey, message)
val data = message.messageData
this.sender_key = UserKey(data.senderId.toString(), accountKey.host)
this.recipient_key = UserKey(data.recipientId.toString(), accountKey.host)
this.is_outgoing = this.sender_key == accountKey
val (type, extras, media) = typeAndExtras(data)
val (text, spans) = InternalTwitterContentUtils.formatDirectMessageText(data)
this.message_type = type
@ -72,37 +85,59 @@ object ParcelableMessageUtils {
this.is_outgoing = false
}
private fun ParcelableMessage.applyUsersEvent(accountKey: UserKey,
message: DMResponse.Entry.Message, users: Map<String, User>, @MessageType type: String) {
this.commonEntry(accountKey, message)
this.message_type = type
this.extras = UserArrayExtras().apply {
this.users = message.participants.mapNotNull {
val user = users[it.userId] ?: return@mapNotNull null
ParcelableUserUtils.fromUser(user, accountKey)
}.toTypedArray()
}
this.is_outgoing = false
}
private fun ParcelableMessage.commonEntry(accountKey: UserKey, message: DMResponse.Entry.Message) {
val data = message.messageData
this.sender_key = run {
val senderId = data?.senderId ?: message.senderId ?: return@run null
return@run UserKey(senderId, accountKey.host)
}
this.recipient_key = run {
val recipientId = data?.recipientId ?: return@run null
return@run UserKey(recipientId, accountKey.host)
}
this.account_key = accountKey
this.id = message.id.toString()
this.conversation_id = message.conversationId
this.message_timestamp = message.time
this.local_timestamp = this.message_timestamp
this.sort_id = this.message_timestamp
this.is_outgoing = this.sender_key == accountKey
}
private fun message(
private fun ParcelableMessage.applyMessage(
accountKey: UserKey,
message: DirectMessage,
@FloatRange(from = 0.0, to = 1.0) sortIdAdj: Double = 0.0
): ParcelableMessage {
val result = ParcelableMessage()
result.account_key = accountKey
result.id = message.id
result.sender_key = UserKeyUtils.fromUser(message.sender)
result.recipient_key = UserKeyUtils.fromUser(message.recipient)
result.message_timestamp = message.createdAt.time
result.local_timestamp = result.message_timestamp
result.sort_id = result.message_timestamp + (499 * sortIdAdj).toLong()
) {
this.account_key = accountKey
this.id = message.id
this.sender_key = UserKeyUtils.fromUser(message.sender)
this.recipient_key = UserKeyUtils.fromUser(message.recipient)
this.message_timestamp = message.createdAt.time
this.local_timestamp = this.message_timestamp
this.sort_id = this.message_timestamp + (499 * sortIdAdj).toLong()
val (type, extras) = typeAndExtras(message)
val (text, spans) = InternalTwitterContentUtils.formatDirectMessageText(message)
result.message_type = type
result.extras = extras
result.text_unescaped = text
result.spans = spans
result.media = ParcelableMediaUtils.fromEntities(message)
return result
this.message_type = type
this.extras = extras
this.text_unescaped = text
this.spans = spans
this.media = ParcelableMediaUtils.fromEntities(message)
}
private fun typeAndExtras(message: DirectMessage): Pair<String, MessageExtras?> {

View File

@ -7,13 +7,14 @@ import org.mariotaku.ktextension.toInt
import org.mariotaku.ktextension.useCursor
import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.twitter.model.DMResponse
import org.mariotaku.microblog.library.twitter.model.Paging
import org.mariotaku.microblog.library.twitter.model.User
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.extension.model.applyFrom
import org.mariotaku.twidere.extension.model.isOfficial
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
import org.mariotaku.twidere.extension.model.setFrom
import org.mariotaku.twidere.extension.model.timestamp
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.ParcelableMessageConversation.ConversationType
@ -27,6 +28,7 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Messages
import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations
import org.mariotaku.twidere.util.DataStoreUtils
import org.mariotaku.twidere.util.content.ContentResolverUtils
import java.util.*
/**
* Created by mariotaku on 2017/2/8.
@ -86,14 +88,21 @@ class GetMessagesTask(
val conversationIds = inbox.conversations.keys
conversations.addLocalConversations(accountKey, conversationIds)
val messages = inbox.entries.mapNotNull { ParcelableMessageUtils.fromEntry(accountKey, it) }
val messages = inbox.entries.mapNotNull {
ParcelableMessageUtils.fromEntry(accountKey, it, inbox.users)
}
val messagesMap = messages.groupBy(ParcelableMessage::conversation_id)
for ((k, v) in inbox.conversations) {
val message = messagesMap[k]?.maxBy(ParcelableMessage::message_timestamp) ?: continue
val participants = inbox.users.filterKeys { userId ->
v.participants.any { it.userId.toString() == userId }
v.participants.any { it.userId == userId }
}.values
conversations.addConversation(k, details, message, participants)
val conversationType = when (v.type?.toUpperCase(Locale.US)) {
DMResponse.Conversation.Type.ONE_TO_ONE -> ConversationType.ONE_TO_ONE
DMResponse.Conversation.Type.GROUP_DM -> ConversationType.GROUP
else -> ConversationType.ONE_TO_ONE
}
conversations.addConversation(k, details, message, participants, conversationType)
}
return GetMessagesData(conversations.values, messages)
}
@ -256,22 +265,19 @@ class GetMessagesTask(
conversationId: String,
details: AccountDetails,
message: ParcelableMessage,
users: Collection<User>
users: Collection<User>,
conversationType: String = ConversationType.ONE_TO_ONE
): ParcelableMessageConversation {
val conversation = this[conversationId] ?: run {
val obj = ParcelableMessageConversation()
obj.id = conversationId
if (users.size == 2) {
obj.conversation_type = ConversationType.ONE_TO_ONE
} else {
obj.conversation_type = ConversationType.GROUP
}
obj.setFrom(message, details)
obj.conversation_type = conversationType
obj.applyFrom(message, details)
this[conversationId] = obj
return@run obj
}
if (message.timestamp > conversation.timestamp) {
conversation.setFrom(message, details)
conversation.applyFrom(message, details)
}
users.forEach { user ->
conversation.addParticipant(details.key, user)

View File

@ -54,6 +54,7 @@ import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.model.util.ParcelableStatusUtils
import org.mariotaku.twidere.provider.TwidereDataStore
import org.mariotaku.twidere.provider.TwidereDataStore.*
import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations
import org.mariotaku.twidere.util.content.ContentResolverUtils
import java.io.IOException
import java.util.*
@ -65,7 +66,7 @@ object DataStoreUtils {
val STATUSES_URIS = arrayOf(Statuses.CONTENT_URI, CachedStatuses.CONTENT_URI)
val CACHE_URIS = arrayOf(CachedUsers.CONTENT_URI, CachedStatuses.CONTENT_URI, CachedHashtags.CONTENT_URI, CachedTrends.Local.CONTENT_URI)
val MESSAGES_URIS = arrayOf(Messages.CONTENT_URI, Messages.Conversations.CONTENT_URI)
val MESSAGES_URIS = arrayOf(Messages.CONTENT_URI, Conversations.CONTENT_URI)
val ACTIVITIES_URIS = arrayOf(Activities.AboutMe.CONTENT_URI)
private val CONTENT_PROVIDER_URI_MATCHER = UriMatcher(UriMatcher.NO_MATCH)
@ -93,7 +94,7 @@ object DataStoreUtils {
TABLE_ID_FILTERS_SUBSCRIPTIONS)
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Messages.CONTENT_PATH,
TABLE_ID_MESSAGES)
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Messages.Conversations.CONTENT_PATH,
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Conversations.CONTENT_PATH,
TABLE_ID_MESSAGES_CONVERSATIONS)
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedTrends.Local.CONTENT_PATH,
TABLE_ID_TRENDS_LOCAL)
@ -227,7 +228,7 @@ object DataStoreUtils {
}
fun getActivitiesCount(context: Context, uri: Uri,
accountKey: UserKey): Int {
accountKey: UserKey): Int {
val where = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY).sql
return queryCount(context, uri, where, arrayOf(accountKey.toString()))
}
@ -281,11 +282,11 @@ object DataStoreUtils {
}
fun getStatusesCount(context: Context,
preferences: SharedPreferences,
uri: Uri,
extraArgs: Bundle?, compare: Long,
compareColumn: String, greaterThan: Boolean,
accountKeys: Array<UserKey>?): Int {
preferences: SharedPreferences,
uri: Uri,
extraArgs: Bundle?, compare: Long,
compareColumn: String, greaterThan: Boolean,
accountKeys: Array<UserKey>?): Int {
val keys = accountKeys ?: getActivatedAccountKeys(context)
val expressions = ArrayList<Expression>()
@ -317,7 +318,7 @@ object DataStoreUtils {
}
fun getActivitiesCount(context: Context, uri: Uri, compare: Long,
compareColumn: String, greaterThan: Boolean, accountKeys: Array<UserKey>?): Int {
compareColumn: String, greaterThan: Boolean, accountKeys: Array<UserKey>?): Int {
val keys = accountKeys ?: getActivatedAccountKeys(context)
val selection = Expression.and(
Expression.inArgs(Column(Activities.ACCOUNT_KEY), keys.size),
@ -331,9 +332,9 @@ object DataStoreUtils {
}
fun getActivitiesCount(context: Context, uri: Uri,
extraWhere: Expression?, extraWhereArgs: Array<String>?,
since: Long, sinceColumn: String, followingOnly: Boolean,
accountKeys: Array<UserKey>?): Int {
extraWhere: Expression?, extraWhereArgs: Array<String>?,
since: Long, sinceColumn: String, followingOnly: Boolean,
accountKeys: Array<UserKey>?): Int {
val keys = (accountKeys ?: getActivatedAccountKeys(context)).map { it.toString() }.toTypedArray()
val expressions = ArrayList<Expression>()
expressions.add(Expression.inArgs(Column(Activities.ACCOUNT_KEY), keys.size))
@ -403,7 +404,7 @@ object DataStoreUtils {
TABLE_ID_FILTERED_LINKS -> return Filters.Links.TABLE_NAME
TABLE_ID_FILTERS_SUBSCRIPTIONS -> return Filters.Subscriptions.TABLE_NAME
TABLE_ID_MESSAGES -> return Messages.TABLE_NAME
TABLE_ID_MESSAGES_CONVERSATIONS -> return Messages.Conversations.TABLE_NAME
TABLE_ID_MESSAGES_CONVERSATIONS -> return Conversations.TABLE_NAME
TABLE_ID_TRENDS_LOCAL -> return CachedTrends.Local.TABLE_NAME
TABLE_ID_TABS -> return Tabs.TABLE_NAME
TABLE_ID_CACHED_STATUSES -> return CachedStatuses.TABLE_NAME
@ -595,9 +596,9 @@ object DataStoreUtils {
}
private fun getStringFieldArray(context: Context, uri: Uri,
keys: Array<UserKey?>, keyField: String,
valueField: String, sortExpression: OrderBy?,
extraHaving: Expression?, extraHavingArgs: Array<String>?): Array<String?> {
keys: Array<UserKey?>, keyField: String,
valueField: String, sortExpression: OrderBy?,
extraHaving: Expression?, extraHavingArgs: Array<String>?): Array<String?> {
return getFieldArray(context, uri, keys, keyField, valueField, sortExpression, extraHaving,
extraHavingArgs, object : FieldArrayCreator<Array<String?>> {
override fun newArray(size: Int): Array<String?> {
@ -611,9 +612,9 @@ object DataStoreUtils {
}
private fun getLongFieldArray(context: Context, uri: Uri,
keys: Array<UserKey?>, keyField: String,
valueField: String, sortExpression: OrderBy?,
extraHaving: Expression?, extraHavingArgs: Array<String>?): LongArray {
keys: Array<UserKey?>, keyField: String,
valueField: String, sortExpression: OrderBy?,
extraHaving: Expression?, extraHavingArgs: Array<String>?): LongArray {
return getFieldArray(context, uri, keys, keyField, valueField, sortExpression, extraHaving,
extraHavingArgs, object : FieldArrayCreator<LongArray> {
override fun newArray(size: Int): LongArray {
@ -674,7 +675,7 @@ object DataStoreUtils {
}
fun deleteStatus(cr: ContentResolver, accountKey: UserKey,
statusId: String, status: ParcelableStatus?) {
statusId: String, status: ParcelableStatus?) {
val host = accountKey.host
val deleteWhere: String
@ -743,7 +744,7 @@ object DataStoreUtils {
}
fun queryCount(context: Context, uri: Uri,
selection: String?, selectionArgs: Array<String>?): Int {
selection: String?, selectionArgs: Array<String>?): Int {
val resolver = context.contentResolver
val projection = arrayOf(SQLFunctions.COUNT())
val cur = resolver.query(uri, projection, selection, selectionArgs, null) ?: return -1
@ -758,7 +759,7 @@ object DataStoreUtils {
}
fun getInteractionsCount(context: Context, extraArgs: Bundle?,
accountIds: Array<UserKey>, since: Long, sinceColumn: String): Int {
accountIds: Array<UserKey>, since: Long, sinceColumn: String): Int {
var extraWhere: Expression? = null
var extraWhereArgs: Array<String>? = null
var followingOnly = false
@ -840,8 +841,8 @@ object DataStoreUtils {
@WorkerThread
fun findStatusInDatabases(context: Context,
accountKey: UserKey,
statusId: String): ParcelableStatus? {
accountKey: UserKey,
statusId: String): ParcelableStatus? {
val resolver = context.contentResolver
var status: ParcelableStatus? = null
val where = Expression.and(Expression.equalsArgs(Statuses.ACCOUNT_KEY),
@ -853,8 +854,6 @@ object DataStoreUtils {
if (cur.count > 0 && cur.moveToFirst()) {
status = ParcelableStatusCursorIndices.fromCursor(cur)
}
} catch (e: IOException) {
// Ignore
} finally {
cur.close()
}
@ -876,12 +875,25 @@ object DataStoreUtils {
val resolver = context.contentResolver
val status = ParcelableStatusUtils.fromStatus(result, accountKey, false)
resolver.delete(CachedStatuses.CONTENT_URI, where, whereArgs)
try {
resolver.insert(CachedStatuses.CONTENT_URI, ParcelableStatusValuesCreator.create(status))
} catch (e: IOException) {
// Ignore
}
resolver.insert(CachedStatuses.CONTENT_URI, ParcelableStatusValuesCreator.create(status))
return status
}
@WorkerThread
fun findMessageConversation(context: Context, accountKey: UserKey, conversationId: String): ParcelableMessageConversation? {
val resolver = context.contentResolver
val where = Expression.and(Expression.equalsArgs(Conversations.ACCOUNT_KEY),
Expression.equalsArgs(Conversations.CONVERSATION_ID)).sql
val whereArgs = arrayOf(accountKey.toString(), conversationId)
val cur = resolver.query(Conversations.CONTENT_URI, Conversations.COLUMNS, where, whereArgs, null) ?: return null
try {
if (cur.count > 0 && cur.moveToFirst()) {
return ParcelableMessageConversationCursorIndices.fromCursor(cur)
}
} finally {
cur.close()
}
return null
}
}

View File

@ -59,7 +59,8 @@ class MessageEntryViewHolder(itemView: View, val adapter: MessagesEntriesAdapter
this.name.name = name
this.name.screenName = secondaryName
this.name.updateText(adapter.bidiFormatter)
this.text.text = conversation.getSummaryText(itemView.context)
this.text.text = conversation.getSummaryText(itemView.context, adapter.userColorNameManager,
adapter.nameFirst)
if (conversation.is_outgoing) {
stateIndicator.visibility = View.VISIBLE
stateIndicator.setImageResource(R.drawable.ic_activity_action_reply)

View File

@ -23,10 +23,11 @@ import android.view.View
import kotlinx.android.synthetic.main.list_item_message_conversation_notice.view.*
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.MessagesConversationAdapter
import org.mariotaku.twidere.extension.model.getSummaryText
import org.mariotaku.twidere.model.ParcelableMessage
import org.mariotaku.twidere.view.FixedTextView
class ConversationCreateMessageViewHolder(itemView: View, adapter: MessagesConversationAdapter) : AbsMessageViewHolder(itemView, adapter) {
class NoticeSummaryEventViewHolder(itemView: View, adapter: MessagesConversationAdapter) : AbsMessageViewHolder(itemView, adapter) {
override val messageContent: View = itemView
override val date: FixedTextView by lazy { itemView.date }
@ -34,7 +35,8 @@ class ConversationCreateMessageViewHolder(itemView: View, adapter: MessagesConve
override fun display(message: ParcelableMessage, showDate: Boolean) {
super.display(message, showDate)
text.setText(R.string.message_conversation_created)
text.text = message.getSummaryText(adapter.context, adapter.userColorNameManager,
adapter.conversation, adapter.nameFirst)
}
override fun setMessageContentGravity(view: View, outgoing: Boolean) {
@ -44,5 +46,4 @@ class ConversationCreateMessageViewHolder(itemView: View, adapter: MessagesConve
companion object {
const val layoutResource = R.layout.list_item_message_conversation_notice
}
}

View File

@ -40,5 +40,6 @@
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/element_spacing_normal"
tools:text="Notice"/>
</LinearLayout>

View File

@ -669,6 +669,16 @@
<string name="message_direct_message_deleted">Direct message deleted.</string>
<string name="message_direct_message_sent">Direct message sent.</string>
<string name="message_error_invalid_account">Some account data are corrupted, Twidere will remove those accounts to prevent crash.</string>
<string name="message_format_participants_join">
<xliff:g example="User" id="name">%1$s</xliff:g> joined</string>
<string name="message_format_participants_join_added">
<xliff:g example="Sender"
id="sender">%1$s</xliff:g> added <xliff:g
example="Recipient" id="recipient">%2$s</xliff:g>
</string>
<string name="message_format_participants_leave">
<xliff:g example="User" id="name">%1$s</xliff:g> left</string>
<string name="message_join_conversation">Joined conversation.</string>
<string name="message_permission_request_compose_location">Twidere needs location permission for adding location to tweets.</string>
<string name="message_permission_request_save_media">Twidere needs storage permission for saving media.</string>
<string name="message_permission_request_share_media">Twidere needs storage permission for sharing media to some apps.</string>