From d8dadd1f55608b11c65a8379914abf13183b9765 Mon Sep 17 00:00:00 2001 From: Naveen Date: Thu, 17 Nov 2022 01:47:39 +0530 Subject: [PATCH] Use DiffUtil with thread recyclerview Also added a progress indicator at the top while fetching older messages --- .../smsmessenger/activities/ThreadActivity.kt | 116 +++++++++++---- .../smsmessenger/adapters/ThreadAdapter.kt | 137 ++++++++++-------- .../smsmessenger/helpers/Constants.kt | 1 + .../smsmessenger/models/Message.kt | 17 +++ .../smsmessenger/models/ThreadDateTime.kt | 3 - .../smsmessenger/models/ThreadError.kt | 3 - .../smsmessenger/models/ThreadItem.kt | 3 - .../smsmessenger/models/ThreadItems.kt | 12 ++ .../smsmessenger/models/ThreadSending.kt | 3 - .../smsmessenger/models/ThreadSent.kt | 5 - .../main/res/layout/item_thread_loading.xml | 17 +++ 11 files changed, 207 insertions(+), 110 deletions(-) delete mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadDateTime.kt delete mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadError.kt delete mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadItem.kt create mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadItems.kt delete mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadSending.kt delete mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadSent.kt create mode 100644 app/src/main/res/layout/item_thread_loading.xml diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt index 3de1380e..1674c2b6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -55,6 +55,7 @@ import com.simplemobiletools.smsmessenger.dialogs.ScheduleMessageDialog import com.simplemobiletools.smsmessenger.extensions.* import com.simplemobiletools.smsmessenger.helpers.* import com.simplemobiletools.smsmessenger.models.* +import com.simplemobiletools.smsmessenger.models.ThreadItem.* import kotlinx.android.synthetic.main.activity_thread.* import kotlinx.android.synthetic.main.item_selected_contact.view.* import kotlinx.android.synthetic.main.layout_attachment_picker.* @@ -116,8 +117,8 @@ class ThreadActivity : SimpleActivity() { bus = EventBus.getDefault() bus!!.register(this) - handlePermission(PERMISSION_READ_PHONE_STATE) { - if (it) { + handlePermission(PERMISSION_READ_PHONE_STATE) { granted -> + if (granted) { setupButtons() setupCachedMessages { val searchedMessageId = intent.getLongExtra(SEARCHED_MESSAGE_ID, -1L) @@ -263,7 +264,7 @@ class ThreadActivity : SimpleActivity() { } private fun setupThread() { - val privateCursor = getMyContactsCursor(false, true) + val privateCursor = getMyContactsCursor(favoritesOnly = false, withPhoneNumbersOnly = true) ensureBackgroundThread { privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor) @@ -329,33 +330,40 @@ class ThreadActivity : SimpleActivity() { } } + private fun getOrCreateThreadAdapter(): ThreadAdapter { + var currAdapter = thread_messages_list.adapter + if (currAdapter == null) { + currAdapter = ThreadAdapter( + activity = this, + recyclerView = thread_messages_list, + itemClick = { handleItemClick(it) }, + deleteMessages = { deleteMessages(it) } + ) + + thread_messages_list.adapter = currAdapter + thread_messages_list.endlessScrollListener = object : MyRecyclerView.EndlessScrollListener { + override fun updateBottom() {} + + override fun updateTop() { + fetchNextMessages() + } + } + } + return currAdapter as ThreadAdapter + } + private fun setupAdapter() { threadItems = getThreadItems() runOnUiThread { refreshMenuItems() - - val currAdapter = thread_messages_list.adapter - if (currAdapter == null) { - ThreadAdapter( - activity = this, - messages = threadItems, - recyclerView = thread_messages_list, - itemClick = { handleItemClick(it) }, - onThreadIdUpdate = { threadId = it } - ).apply { - thread_messages_list.adapter = this + getOrCreateThreadAdapter().apply { + val scrollPosition = if (currentList.lastOrNull() != threadItems.lastOrNull()) { + threadItems.lastIndex + } else { + -1 } - - thread_messages_list.endlessScrollListener = object : MyRecyclerView.EndlessScrollListener { - override fun updateBottom() {} - - override fun updateTop() { - fetchNextMessages() - } - } - } else { - (currAdapter as ThreadAdapter).updateMessages(threadItems) + updateMessages(threadItems, scrollPosition) } } @@ -387,6 +395,11 @@ class ThreadActivity : SimpleActivity() { } } + private fun scrollToBottom() { + val position = getOrCreateThreadAdapter().currentList.lastIndex + if (position >= 0) thread_messages_list.smoothScrollToPosition(position) + } + private fun handleItemClick(any: Any) { when { any is Message && any.isScheduled -> showScheduledMessageInfo(any) @@ -394,8 +407,51 @@ class ThreadActivity : SimpleActivity() { } } + private fun deleteMessages(messagesToRemove: List) { + messages.removeAll(messagesToRemove.toSet()) + threadItems = getThreadItems() + + runOnUiThread { + if (messages.isEmpty()) { + finish() + } else { + getOrCreateThreadAdapter().apply { + updateMessages(threadItems) + finishActMode() + } + } + } + + messagesToRemove.forEach { message -> + val messageId = message.id + if (message.isScheduled) { + deleteScheduledMessage(messageId) + cancelScheduleSendPendingIntent(messageId) + } else { + deleteMessage(messageId, message.isMMS) + } + } + updateLastConversationMessage(threadId) + + if (messages.isNotEmpty() && messages.all { it.isScheduled }) { + // move all scheduled messages to a temporary thread as there are no real messages left + val message = messagesToRemove.last() + val newThreadId = generateRandomId() + createTemporaryThread(message, newThreadId) + updateScheduledMessagesThreadId(messagesToRemove, newThreadId) + threadId = newThreadId + } + refreshMessages() + } + private fun fetchNextMessages() { if (messages.isEmpty() || allMessagesFetched || loadingOlderMessages) { + if (allMessagesFetched) { + getOrCreateThreadAdapter().apply { + val newList = currentList.toMutableList().apply { removeAll { it is ThreadLoading } } + updateMessages(newMessages = newList as ArrayList, scrollPosition = 0) + } + } return } @@ -414,16 +470,13 @@ class ThreadActivity : SimpleActivity() { .filter { message -> !messages.contains(message) } messages.addAll(0, olderMessages) - threadItems = getThreadItems() - allMessagesFetched = olderMessages.size < MESSAGES_LIMIT || olderMessages.isEmpty() + threadItems = getThreadItems() runOnUiThread { loadingOlderMessages = false val itemAtRefreshIndex = threadItems.indexOfFirst { it == firstItem } - (thread_messages_list.adapter as ThreadAdapter).apply { - updateMessages(threadItems, itemAtRefreshIndex) - } + getOrCreateThreadAdapter().updateMessages(threadItems, itemAtRefreshIndex) } } } @@ -819,6 +872,10 @@ class ThreadActivity : SimpleActivity() { bus?.post(Events.RefreshMessages()) } + if (!allMessagesFetched && messages.size >= MESSAGES_LIMIT) { + items.add(0, ThreadLoading(generateRandomId())) + } + return items } @@ -988,6 +1045,7 @@ class ThreadActivity : SimpleActivity() { showErrorToast(getString(R.string.unknown_error_occurred)) return } + scrollToBottom() text = removeDiacriticsIfNeeded(text) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt index 63a484d3..706b8e5d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt @@ -11,6 +11,7 @@ import android.util.TypedValue import android.view.Menu import android.view.View import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil import com.bumptech.glide.Glide import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.DiskCacheStrategy @@ -21,7 +22,7 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.target.Target -import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter +import com.simplemobiletools.commons.adapters.MyRecyclerViewListAdapter import com.simplemobiletools.commons.dialogs.ConfirmationDialog import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.SimpleContactsHelper @@ -35,7 +36,10 @@ import com.simplemobiletools.smsmessenger.activities.VCardViewerActivity import com.simplemobiletools.smsmessenger.dialogs.SelectTextDialog import com.simplemobiletools.smsmessenger.extensions.* import com.simplemobiletools.smsmessenger.helpers.* -import com.simplemobiletools.smsmessenger.models.* +import com.simplemobiletools.smsmessenger.models.Attachment +import com.simplemobiletools.smsmessenger.models.Message +import com.simplemobiletools.smsmessenger.models.ThreadItem +import com.simplemobiletools.smsmessenger.models.ThreadItem.* import kotlinx.android.synthetic.main.item_attachment_image.view.* import kotlinx.android.synthetic.main.item_received_message.view.* import kotlinx.android.synthetic.main.item_received_message.view.thread_mesage_attachments_holder @@ -45,12 +49,16 @@ import kotlinx.android.synthetic.main.item_received_message.view.thread_message_ import kotlinx.android.synthetic.main.item_sent_message.view.* import kotlinx.android.synthetic.main.item_thread_date_time.view.* import kotlinx.android.synthetic.main.item_thread_error.view.* +import kotlinx.android.synthetic.main.item_thread_loading.view.* import kotlinx.android.synthetic.main.item_thread_sending.view.* import kotlinx.android.synthetic.main.item_thread_success.view.* class ThreadAdapter( - activity: SimpleActivity, var messages: ArrayList, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit, val onThreadIdUpdate: (Long) -> Unit -) : MyRecyclerViewAdapter(activity, recyclerView, itemClick) { + activity: SimpleActivity, + recyclerView: MyRecyclerView, + itemClick: (Any) -> Unit, + val deleteMessages: (messages: List) -> Unit +) : MyRecyclerViewListAdapter(activity, recyclerView, ThreadItemDiffCallback(), itemClick) { private var fontSize = activity.getTextSize() @SuppressLint("MissingPermission") @@ -59,6 +67,7 @@ class ThreadAdapter( init { setupDragListener(true) + setHasStableIds(true) } override fun getActionMenuId() = R.menu.cab_thread @@ -92,13 +101,13 @@ class ThreadAdapter( } } - override fun getSelectableItemCount() = messages.filter { it is Message }.size + override fun getSelectableItemCount() = currentList.filterIsInstance().size override fun getIsItemSelectable(position: Int) = !isThreadDateTime(position) - override fun getItemSelectionKey(position: Int) = (messages.getOrNull(position) as? Message)?.hashCode() + override fun getItemSelectionKey(position: Int) = (currentList.getOrNull(position) as? Message)?.hashCode() - override fun getItemKeyPosition(key: Int) = messages.indexOfFirst { (it as? Message)?.hashCode() == key } + override fun getItemKeyPosition(key: Int) = currentList.indexOfFirst { (it as? Message)?.hashCode() == key } override fun onActionModeCreated() {} @@ -106,6 +115,7 @@ class ThreadAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val layout = when (viewType) { + THREAD_LOADING -> R.layout.item_thread_loading THREAD_DATE_TIME -> R.layout.item_thread_date_time THREAD_RECEIVED_MESSAGE -> R.layout.item_received_message THREAD_SENT_MESSAGE_ERROR -> R.layout.item_thread_error @@ -117,32 +127,37 @@ class ThreadAdapter( } override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val item = messages[position] + val item = getItem(position) val isClickable = item is ThreadError || item is Message val isLongClickable = item is Message - holder.bindView(item, isClickable, isLongClickable) { itemView, layoutPosition -> + holder.bindView(item, isClickable, isLongClickable) { itemView, _ -> when (item) { + is ThreadLoading -> setupThreadLoading(itemView) is ThreadDateTime -> setupDateTime(itemView, item) - is ThreadSent -> setupThreadSuccess(itemView, item.delivered) is ThreadError -> setupThreadError(itemView) + is ThreadSent -> setupThreadSuccess(itemView, item.delivered) is ThreadSending -> setupThreadSending(itemView) - else -> setupView(holder, itemView, item as Message) + is Message -> setupView(holder, itemView, item) } } bindViewHolder(holder) } - override fun getItemCount() = messages.size + override fun getItemId(position: Int): Long { + val item = getItem(position) + // hashcode is probably okay as the chances of collision here are nearly zero considering the max list size of 50 items + // future: generate a unique id for each item instead of relying on the telephony id for uniqueness + return item.hashCode().toLong() + } override fun getItemViewType(position: Int): Int { - val item = messages[position] - return when { - item is ThreadDateTime -> THREAD_DATE_TIME - (messages[position] as? Message)?.isReceivedMessage() == true -> THREAD_RECEIVED_MESSAGE - item is ThreadError -> THREAD_SENT_MESSAGE_ERROR - item is ThreadSent -> THREAD_SENT_MESSAGE_SENT - item is ThreadSending -> THREAD_SENT_MESSAGE_SENDING - else -> THREAD_SENT_MESSAGE + return when (val item = getItem(position)) { + is ThreadLoading -> THREAD_LOADING + is ThreadDateTime -> THREAD_DATE_TIME + is ThreadError -> THREAD_SENT_MESSAGE_ERROR + is ThreadSent -> THREAD_SENT_MESSAGE_SENT + is ThreadSending -> THREAD_SENT_MESSAGE_SENDING + is Message -> if (item.isReceivedMessage()) THREAD_RECEIVED_MESSAGE else THREAD_SENT_MESSAGE } } @@ -185,45 +200,14 @@ class ThreadAdapter( ConfirmationDialog(activity, question) { ensureBackgroundThread { - deleteMessages() + val messagesToRemove = getSelectedItems() + if (messagesToRemove.isNotEmpty()) { + deleteMessages(messagesToRemove.filterIsInstance()) + } } } } - private fun deleteMessages() { - val messagesToRemove = getSelectedItems() - if (messagesToRemove.isEmpty()) { - return - } - - val positions = getSelectedItemPositions() - val threadId = (messagesToRemove.firstOrNull() as? Message)?.threadId ?: return - messagesToRemove.forEach { - activity.deleteMessage((it as Message).id, it.isMMS) - } - messages.removeAll(messagesToRemove.toSet()) - activity.updateLastConversationMessage(threadId) - - val messages = messages.filterIsInstance() - if (messages.isNotEmpty() && messages.all { it.isScheduled }) { - // move all scheduled messages to a temporary thread as there are no real messages left - val message = messages.last() - val newThreadId = generateRandomId() - activity.createTemporaryThread(message, newThreadId) - activity.updateScheduledMessagesThreadId(messages, newThreadId) - onThreadIdUpdate(newThreadId) - } - - activity.runOnUiThread { - if (messages.isEmpty()) { - activity.finish() - } else { - removeSelectedItems(positions) - } - refreshMessages() - } - } - private fun forwardMessage() { val message = getSelectedItems().firstOrNull() as? Message ?: return val attachment = message.attachment?.attachments?.firstOrNull() @@ -239,17 +223,13 @@ class ThreadAdapter( } } - private fun getSelectedItems() = messages.filter { selectedKeys.contains((it as? Message)?.hashCode() ?: 0) } as ArrayList + private fun getSelectedItems() = currentList.filter { selectedKeys.contains((it as? Message)?.hashCode() ?: 0) } as ArrayList - private fun isThreadDateTime(position: Int) = messages.getOrNull(position) is ThreadDateTime + private fun isThreadDateTime(position: Int) = currentList.getOrNull(position) is ThreadDateTime - fun updateMessages(newMessages: ArrayList, scrollPosition: Int = newMessages.size - 1) { - val latestMessages = newMessages.clone() as ArrayList - val oldHashCode = messages.hashCode() - val newHashCode = latestMessages.hashCode() - if (newHashCode != oldHashCode) { - messages = latestMessages - notifyDataSetChanged() + fun updateMessages(newMessages: ArrayList, scrollPosition: Int = newMessages.lastIndex) { + val latestMessages = newMessages.toMutableList() + submitList(latestMessages) { recyclerView.scrollToPosition(scrollPosition) } } @@ -484,6 +464,8 @@ class ThreadAdapter( } } + private fun setupThreadLoading(view: View) = view.thread_loading.setIndicatorColor(properPrimaryColor) + override fun onViewRecycled(holder: ViewHolder) { super.onViewRecycled(holder) if (!activity.isDestroyed && !activity.isFinishing && holder.itemView.thread_message_sender_photo != null) { @@ -491,3 +473,30 @@ class ThreadAdapter( } } } + +private class ThreadItemDiffCallback : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: ThreadItem, newItem: ThreadItem): Boolean { + if (oldItem::class.java != newItem::class.java) return false + return when (oldItem) { + is ThreadLoading -> oldItem.id == (newItem as ThreadLoading).id + is ThreadDateTime -> oldItem.date == (newItem as ThreadDateTime).date + is ThreadError -> oldItem.messageId == (newItem as ThreadError).messageId + is ThreadSent -> oldItem.messageId == (newItem as ThreadSent).messageId + is ThreadSending -> oldItem.messageId == (newItem as ThreadSending).messageId + is Message -> Message.areItemsTheSame(oldItem, newItem as Message) + } + } + + override fun areContentsTheSame(oldItem: ThreadItem, newItem: ThreadItem): Boolean { + if (oldItem::class.java != newItem::class.java) return false + return when (oldItem) { + is ThreadLoading -> false + is ThreadDateTime -> oldItem.simID == (newItem as ThreadDateTime).simID + is ThreadError -> oldItem.messageText == (newItem as ThreadError).messageText + is ThreadSent -> oldItem.delivered == (newItem as ThreadSent).delivered + is ThreadSending -> true + is Message -> Message.areContentsTheSame(oldItem, newItem as Message) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt index 45c41f48..74b72a02 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt @@ -46,6 +46,7 @@ const val THREAD_SENT_MESSAGE = 3 const val THREAD_SENT_MESSAGE_ERROR = 4 const val THREAD_SENT_MESSAGE_SENT = 5 const val THREAD_SENT_MESSAGE_SENDING = 6 +const val THREAD_LOADING = 7 // view types for attachment list const val ATTACHMENT_DOCUMENT = 7 diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt index 48a25679..c852e00c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt @@ -27,4 +27,21 @@ data class Message( fun isReceivedMessage() = type == Telephony.Sms.MESSAGE_TYPE_INBOX fun millis() = date * 1000L + + companion object { + fun areItemsTheSame(old: Message, new: Message): Boolean { + return old.id == new.id + } + + fun areContentsTheSame(old: Message, new: Message): Boolean { + return old.body == new.body && + old.type == new.type && + old.threadId == new.threadId && + old.isMMS == new.isMMS && + old.attachment == new.attachment && + old.senderName == new.senderName && + old.senderPhotoUri == new.senderPhotoUri && + old.isScheduled == new.isScheduled + } + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadDateTime.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadDateTime.kt deleted file mode 100644 index aae15f0f..00000000 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadDateTime.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.simplemobiletools.smsmessenger.models - -data class ThreadDateTime(val date: Int, val simID: String) : ThreadItem() diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadError.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadError.kt deleted file mode 100644 index 9e32530d..00000000 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadError.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.simplemobiletools.smsmessenger.models - -data class ThreadError(val messageId: Long, val messageText: String) : ThreadItem() diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadItem.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadItem.kt deleted file mode 100644 index 534397fa..00000000 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadItem.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.simplemobiletools.smsmessenger.models - -open class ThreadItem diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadItems.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadItems.kt new file mode 100644 index 00000000..a26c0bf9 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadItems.kt @@ -0,0 +1,12 @@ +package com.simplemobiletools.smsmessenger.models + +/** + * Thread item representations for the main thread recyclerview. [Message] is also a [ThreadItem] + */ +sealed class ThreadItem { + data class ThreadLoading(val id: Long) : ThreadItem() + data class ThreadDateTime(val date: Int, val simID: String) : ThreadItem() + data class ThreadError(val messageId: Long, val messageText: String) : ThreadItem() + data class ThreadSent(val messageId: Long, val delivered: Boolean) : ThreadItem() + data class ThreadSending(val messageId: Long) : ThreadItem() +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadSending.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadSending.kt deleted file mode 100644 index 131f7386..00000000 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadSending.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.simplemobiletools.smsmessenger.models - -data class ThreadSending(val messageId: Long) : ThreadItem() diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadSent.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadSent.kt deleted file mode 100644 index 36faf7ab..00000000 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ThreadSent.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.simplemobiletools.smsmessenger.models - -// show a check after the latest message, if it is a sent one and succeeded, -// show a double check if it is delivered -data class ThreadSent(val messageID: Long, val delivered: Boolean) : ThreadItem() diff --git a/app/src/main/res/layout/item_thread_loading.xml b/app/src/main/res/layout/item_thread_loading.xml new file mode 100644 index 00000000..c907adc3 --- /dev/null +++ b/app/src/main/res/layout/item_thread_loading.xml @@ -0,0 +1,17 @@ + + + + +