From 9bdc8b6f7e09b1ce5ed3ff760d77f6e882c50620 Mon Sep 17 00:00:00 2001 From: Naveen Date: Sun, 13 Nov 2022 00:38:11 +0530 Subject: [PATCH 01/16] Use DiffUtil at conversations screen --- .../smsmessenger/activities/MainActivity.kt | 16 ++-- .../adapters/ConversationsAdapter.kt | 86 ++++++++++++------- .../smsmessenger/models/Conversation.kt | 4 + 3 files changed, 68 insertions(+), 38 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt index a736c0fe..36e1772c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt @@ -299,7 +299,7 @@ class MainActivity : SimpleActivity() { val currAdapter = conversations_list.adapter if (currAdapter == null) { hideKeyboard() - ConversationsAdapter(this, sortedConversations, conversations_list) { + ConversationsAdapter(this, conversations_list) { Intent(this, ThreadActivity::class.java).apply { val conversation = it as Conversation putExtra(THREAD_ID, conversation.threadId) @@ -308,6 +308,7 @@ class MainActivity : SimpleActivity() { } }.apply { conversations_list.adapter = this + updateConversations(sortedConversations) } if (areSystemAnimationsEnabled) { @@ -315,12 +316,13 @@ class MainActivity : SimpleActivity() { } } else { try { - (currAdapter as ConversationsAdapter).updateConversations(sortedConversations) - if (currAdapter.conversations.isEmpty()) { - conversations_fastscroller.beGone() - no_conversations_placeholder.text = getString(R.string.no_conversations_found) - no_conversations_placeholder.beVisible() - no_conversations_placeholder_2.beVisible() + (currAdapter as ConversationsAdapter).updateConversations(sortedConversations) { + if (currAdapter.currentList.isEmpty()) { + conversations_fastscroller.beGone() + no_conversations_placeholder.text = getString(R.string.no_conversations_found) + no_conversations_placeholder.beVisible() + no_conversations_placeholder_2.beVisible() + } } } catch (ignored: Exception) { } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt index 0dbd8eb0..aa0106ea 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt @@ -2,15 +2,18 @@ package com.simplemobiletools.smsmessenger.adapters import android.content.Intent import android.graphics.Typeface +import android.os.Parcelable import android.text.TextUtils import android.util.TypedValue import android.view.Menu import android.view.View import android.view.ViewGroup import android.widget.TextView +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller -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.KEY_PHONE @@ -26,14 +29,23 @@ import com.simplemobiletools.smsmessenger.models.Conversation import kotlinx.android.synthetic.main.item_conversation.view.* class ConversationsAdapter( - activity: SimpleActivity, var conversations: ArrayList, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit -) : MyRecyclerViewAdapter(activity, recyclerView, itemClick), RecyclerViewFastScroller.OnPopupTextUpdate { + activity: SimpleActivity, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit +) : MyRecyclerViewListAdapter(activity, recyclerView, ConversationDiffCallback(), itemClick), RecyclerViewFastScroller.OnPopupTextUpdate { private var fontSize = activity.getTextSize() private var drafts = HashMap() + private var recyclerViewState: Parcelable? = null + init { setupDragListener(true) fetchDrafts(drafts) + setHasStableIds(true) + + registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + override fun onChanged() = restoreRecyclerViewState() + override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) = restoreRecyclerViewState() + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) = restoreRecyclerViewState() + }) } override fun getActionMenuId() = R.menu.cab_conversations @@ -71,13 +83,13 @@ class ConversationsAdapter( } } - override fun getSelectableItemCount() = conversations.size + override fun getSelectableItemCount() = itemCount override fun getIsItemSelectable(position: Int) = true - override fun getItemSelectionKey(position: Int) = conversations.getOrNull(position)?.hashCode() + override fun getItemSelectionKey(position: Int) = currentList.getOrNull(position)?.hashCode() - override fun getItemKeyPosition(key: Int) = conversations.indexOfFirst { it.hashCode() == key } + override fun getItemKeyPosition(key: Int) = currentList.indexOfFirst { it.hashCode() == key } override fun onActionModeCreated() {} @@ -86,14 +98,14 @@ class ConversationsAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_conversation, parent) override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val conversation = conversations[position] - holder.bindView(conversation, true, true) { itemView, layoutPosition -> + val conversation = getItem(position) + holder.bindView(conversation, allowSingleClick = true, allowLongClick = true) { itemView, _ -> setupView(itemView, conversation) } bindViewHolder(holder) } - override fun getItemCount() = conversations.size + override fun getItemId(position: Int) = getItem(position).threadId override fun onViewRecycled(holder: ViewHolder) { super.onViewRecycled(holder) @@ -118,8 +130,7 @@ class ConversationsAdapter( } val numbersToBlock = getSelectedItems() - val positions = getSelectedItemPositions() - conversations.removeAll(numbersToBlock) + val newList = currentList.toMutableList().apply { removeAll(numbersToBlock) } ensureBackgroundThread { numbersToBlock.map { it.phoneNumber }.forEach { number -> @@ -127,7 +138,7 @@ class ConversationsAdapter( } activity.runOnUiThread { - removeSelectedItems(positions) + submitList(newList) finishActMode() } } @@ -165,25 +176,25 @@ class ConversationsAdapter( return } - val conversationsToRemove = conversations.filter { selectedKeys.contains(it.hashCode()) } as ArrayList - val positions = getSelectedItemPositions() + val conversationsToRemove = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList conversationsToRemove.forEach { activity.deleteConversation(it.threadId) activity.notificationManager.cancel(it.hashCode()) } - try { - conversations.removeAll(conversationsToRemove.toSet()) + val newList = try { + currentList.toMutableList().apply { removeAll(conversationsToRemove) } } catch (ignored: Exception) { + currentList.toMutableList() } activity.runOnUiThread { - if (conversationsToRemove.isEmpty()) { + if (newList.none { selectedKeys.contains(it.hashCode()) }) { refreshMessages() finishActMode() } else { - removeSelectedItems(positions) - if (conversations.isEmpty()) { + submitList(newList) + if (newList.isEmpty()) { refreshMessages() } } @@ -195,7 +206,7 @@ class ConversationsAdapter( return } - val conversationsMarkedAsRead = conversations.filter { selectedKeys.contains(it.hashCode()) } as ArrayList + val conversationsMarkedAsRead = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList ensureBackgroundThread { conversationsMarkedAsRead.filter { conversation -> !conversation.read }.forEach { activity.markThreadMessagesRead(it.threadId) @@ -213,7 +224,7 @@ class ConversationsAdapter( return } - val conversationsMarkedAsUnread = conversations.filter { selectedKeys.contains(it.hashCode()) } as ArrayList + val conversationsMarkedAsUnread = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList ensureBackgroundThread { conversationsMarkedAsUnread.filter { conversation -> conversation.read }.forEach { activity.markThreadMessagesUnread(it.threadId) @@ -236,7 +247,7 @@ class ConversationsAdapter( } } - private fun getSelectedItems() = conversations.filter { selectedKeys.contains(it.hashCode()) } as ArrayList + private fun getSelectedItems() = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList private fun pinConversation(pin: Boolean) { val conversations = getSelectedItems() @@ -275,14 +286,9 @@ class ConversationsAdapter( notifyDataSetChanged() } - fun updateConversations(newConversations: ArrayList) { - val latestConversations = newConversations.clone() as ArrayList - val oldHashCode = conversations.hashCode() - val newHashCode = latestConversations.hashCode() - if (newHashCode != oldHashCode) { - conversations = latestConversations - notifyDataSetChanged() - } + fun updateConversations(newConversations: ArrayList, commitCallback: (() -> Unit)? = null) { + saveRecyclerViewState() + submitList(newConversations.toList(), commitCallback) } fun updateDrafts() { @@ -346,5 +352,23 @@ class ConversationsAdapter( } } - override fun onChange(position: Int) = conversations.getOrNull(position)?.title ?: "" + override fun onChange(position: Int) = currentList.getOrNull(position)?.title ?: "" + + private fun saveRecyclerViewState() { + recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState() + } + + private fun restoreRecyclerViewState() { + recyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState) + } + + private class ConversationDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Conversation, newItem: Conversation): Boolean { + return oldItem.areItemsTheSame(newItem) + } + + override fun areContentsTheSame(oldItem: Conversation, newItem: Conversation): Boolean { + return oldItem.areContentsTheSame(newItem) + } + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt index 86d09777..b2182968 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt @@ -18,6 +18,10 @@ data class Conversation( @ColumnInfo(name = "is_scheduled") var isScheduled: Boolean = false ) { + fun areItemsTheSame(other: Conversation): Boolean { + return threadId == other.threadId + } + fun areContentsTheSame(other: Conversation): Boolean { return snippet == other.snippet && date == other.date && From c5034a64d53a25e2788d6517fb7fd5bd0ab37e93 Mon Sep 17 00:00:00 2001 From: Naveen Date: Wed, 16 Nov 2022 15:54:25 +0530 Subject: [PATCH 02/16] Don't fetch file size in thread list --- .../smsmessenger/helpers/AttachmentPreviews.kt | 14 +++++++++----- .../main/res/layout/item_attachment_document.xml | 2 ++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/AttachmentPreviews.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/AttachmentPreviews.kt index 82d7eeb8..5b8a5e9d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/AttachmentPreviews.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/AttachmentPreviews.kt @@ -25,11 +25,15 @@ fun View.setupDocumentPreview( filename.text = title } - try { - val size = context.getFileSizeFromUri(uri) - file_size.beVisible() - file_size.text = size.formatSize() - } catch (e: Exception) { + if (attachment) { + try { + val size = context.getFileSizeFromUri(uri) + file_size.beVisible() + file_size.text = size.formatSize() + } catch (e: Exception) { + file_size.beGone() + } + } else { file_size.beGone() } diff --git a/app/src/main/res/layout/item_attachment_document.xml b/app/src/main/res/layout/item_attachment_document.xml index 603454b4..ba70eef4 100644 --- a/app/src/main/res/layout/item_attachment_document.xml +++ b/app/src/main/res/layout/item_attachment_document.xml @@ -29,6 +29,7 @@ @@ -48,6 +49,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="@dimen/normal_text_size" + android:visibility="gone" tools:text="2.18 KB" /> From d8dadd1f55608b11c65a8379914abf13183b9765 Mon Sep 17 00:00:00 2001 From: Naveen Date: Thu, 17 Nov 2022 01:47:39 +0530 Subject: [PATCH 03/16] 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 @@ + + + + + From e4a6e4062148628f2798263b9094a1bcee717fb6 Mon Sep 17 00:00:00 2001 From: Naveen Date: Thu, 17 Nov 2022 15:18:46 +0530 Subject: [PATCH 04/16] Increase progress indicator vertical margin --- app/src/main/res/layout/item_thread_loading.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/layout/item_thread_loading.xml b/app/src/main/res/layout/item_thread_loading.xml index c907adc3..244cea63 100644 --- a/app/src/main/res/layout/item_thread_loading.xml +++ b/app/src/main/res/layout/item_thread_loading.xml @@ -4,7 +4,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" - android:orientation="vertical"> + android:orientation="vertical" + android:paddingVertical="@dimen/normal_margin"> Date: Fri, 18 Nov 2022 01:00:33 +0530 Subject: [PATCH 05/16] Disable `setHasStableIds` because we really don't have stable ids (yet) --- .../smsmessenger/adapters/ThreadAdapter.kt | 11 +---------- .../simplemobiletools/smsmessenger/models/Message.kt | 1 - 2 files changed, 1 insertion(+), 11 deletions(-) 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 706b8e5d..5273fdc4 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt @@ -67,7 +67,6 @@ class ThreadAdapter( init { setupDragListener(true) - setHasStableIds(true) } override fun getActionMenuId() = R.menu.cab_thread @@ -143,13 +142,6 @@ class ThreadAdapter( bindViewHolder(holder) } - 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 { return when (val item = getItem(position)) { is ThreadLoading -> THREAD_LOADING @@ -491,11 +483,10 @@ private class ThreadItemDiffCallback : DiffUtil.ItemCallback() { override fun areContentsTheSame(oldItem: ThreadItem, newItem: ThreadItem): Boolean { if (oldItem::class.java != newItem::class.java) return false return when (oldItem) { - is ThreadLoading -> false + is ThreadLoading, is ThreadSending -> true 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/models/Message.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt index c852e00c..92320779 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt @@ -35,7 +35,6 @@ data class Message( 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 && From ca1ad0043b32e776a493bbe4e858e8e383f77a27 Mon Sep 17 00:00:00 2001 From: Naveen Date: Fri, 18 Nov 2022 01:01:10 +0530 Subject: [PATCH 06/16] Minor messaging code improvement --- .../smsmessenger/helpers/Messaging.kt | 12 ++++-------- .../receivers/ScheduledMessageReceiver.kt | 6 +++++- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt index e4e2ce63..a29b01ae 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Messaging.kt @@ -4,8 +4,6 @@ import android.app.AlarmManager import android.app.PendingIntent import android.content.Context import android.content.Intent -import android.os.Handler -import android.os.Looper import androidx.core.app.AlarmManagerCompat import com.klinker.android.send_message.Settings import com.klinker.android.send_message.Transaction @@ -71,12 +69,10 @@ fun Context.sendMessage(text: String, addresses: List, subscriptionId: I transaction.setExplicitBroadcastForSentSms(smsSentIntent) transaction.setExplicitBroadcastForDeliveredSms(deliveredIntent) - Handler(Looper.getMainLooper()).post { - try { - transaction.sendNewMessage(message) - } catch (e: Exception) { - showErrorToast(e) - } + try { + transaction.sendNewMessage(message) + } catch (e: Exception) { + showErrorToast(e) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt index 53c9e020..1b32ca2c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/receivers/ScheduledMessageReceiver.kt @@ -3,6 +3,8 @@ package com.simplemobiletools.smsmessenger.receivers import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.os.Handler +import android.os.Looper import android.os.PowerManager import com.simplemobiletools.commons.extensions.showErrorToast import com.simplemobiletools.commons.helpers.ensureBackgroundThread @@ -43,7 +45,9 @@ class ScheduledMessageReceiver : BroadcastReceiver() { val attachments = message.attachment?.attachments ?: emptyList() try { - context.sendMessage(message.body, addresses, message.subscriptionId, attachments) + Handler(Looper.getMainLooper()).post { + context.sendMessage(message.body, addresses, message.subscriptionId, attachments) + } // delete temporary conversation and message as it's already persisted to the telephony db now context.deleteScheduledMessage(messageId) From da6354c9a16c1ce4ed581be0a328e5fa29106da1 Mon Sep 17 00:00:00 2001 From: Naveen Date: Fri, 18 Nov 2022 01:05:19 +0530 Subject: [PATCH 07/16] Update the Ui ASAP when sending a message --- .../smsmessenger/activities/ThreadActivity.kt | 66 ++++++++++++------- .../smsmessenger/extensions/Context.kt | 10 ++- 2 files changed, 52 insertions(+), 24 deletions(-) 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 1674c2b6..9e4262b7 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -1075,22 +1075,18 @@ class ThreadActivity : SimpleActivity() { threadId = message.threadId createTemporaryThread(message, message.threadId) } - messagesDB.insertOrUpdate(message) val conversation = conversationsDB.getConversationWithThreadId(threadId) if (conversation != null) { val nowSeconds = (System.currentTimeMillis() / 1000).toInt() conversationsDB.insertOrUpdate(conversation.copy(date = nowSeconds)) } scheduleMessage(message) + insertOrUpdateMessage(message) runOnUiThread { clearCurrentMessage() hideScheduleSendUi() scheduledMessage = null - - if (!refreshedSinceSent) { - refreshMessages() - } } } } catch (e: Exception) { @@ -1105,11 +1101,16 @@ class ThreadActivity : SimpleActivity() { try { refreshedSinceSent = false sendMessage(text, addresses, subscriptionId, attachments) + ensureBackgroundThread { + val messageIds = messages.map { it.id } + val message = getMessages(threadId, getImageResolutions = true, limit = 1).firstOrNull { it.id !in messageIds } + if (message != null) { + maybeUpdateMessageSubId(message) + insertOrUpdateMessage(message) + } + } clearCurrentMessage() - if (!refreshedSinceSent) { - refreshMessages() - } } catch (e: Exception) { showErrorToast(e) } catch (e: Error) { @@ -1123,6 +1124,24 @@ class ThreadActivity : SimpleActivity() { checkSendMessageAvailability() } + private fun insertOrUpdateMessage(message: Message) { + if (messages.map { it.id }.contains(message.id)) { + val messageToReplace = messages.find { it.id == message.id } + messages[messages.indexOf(messageToReplace)] = message + } else { + messages.add(message) + } + + val newItems = getThreadItems() + runOnUiThread { + getOrCreateThreadAdapter().updateMessages(newItems) + if (!refreshedSinceSent) { + refreshMessages() + } + } + messagesDB.insertOrUpdate(message) + } + // show selected contacts, properly split to new lines when appropriate // based on https://stackoverflow.com/a/13505029/1967672 private fun showSelectedContact(views: ArrayList) { @@ -1249,7 +1268,6 @@ class ThreadActivity : SimpleActivity() { } } - @SuppressLint("MissingPermission") @Subscribe(threadMode = ThreadMode.ASYNC) fun refreshMessages(event: Events.RefreshMessages) { refreshedSinceSent = true @@ -1260,6 +1278,7 @@ class ThreadActivity : SimpleActivity() { notificationManager.cancel(threadId.hashCode()) } + val lastMaxId = messages.filterNot { it.isScheduled }.maxByOrNull { it.id }?.id ?: 0L val newThreadId = getThreadId(participants.getAddresses().toSet()) val newMessages = getMessages(newThreadId, false) messages = if (messages.all { it.isScheduled } && newMessages.isNotEmpty()) { @@ -1271,18 +1290,8 @@ class ThreadActivity : SimpleActivity() { getMessages(threadId, true) } - val lastMaxId = messages.filterNot { it.isScheduled }.maxByOrNull { it.id }?.id ?: 0L - messages.filter { !it.isReceivedMessage() && it.id > lastMaxId }.forEach { latestMessage -> - // subscriptionIds seem to be not filled out at sending with multiple SIM cards, so fill it manually - if ((subscriptionManagerCompat().activeSubscriptionInfoList?.size ?: 0) > 1) { - val subscriptionId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId - if (subscriptionId != null) { - updateMessageSubscriptionId(latestMessage.id, subscriptionId) - latestMessage.subscriptionId = subscriptionId - } - } - + maybeUpdateMessageSubId(latestMessage) messagesDB.insertOrIgnore(latestMessage) } @@ -1292,6 +1301,18 @@ class ThreadActivity : SimpleActivity() { } } + @SuppressLint("MissingPermission") + private fun maybeUpdateMessageSubId(message: Message) { + // subscriptionIds seem to be not filled out at sending with multiple SIM cards, so fill it manually + if ((subscriptionManagerCompat().activeSubscriptionInfoList?.size ?: 0) > 1) { + val subscriptionId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId + if (subscriptionId != null) { + updateMessageSubscriptionId(message.id, subscriptionId) + message.subscriptionId = subscriptionId + } + } + } + private fun isMmsMessage(text: String): Boolean { val isGroupMms = participants.size > 1 && config.sendGroupMessageMMS val isLongMmsMessage = isLongMmsMessage(text) && config.sendLongMessageMMS @@ -1314,11 +1335,12 @@ class ThreadActivity : SimpleActivity() { RadioItem(TYPE_SEND, getString(R.string.send_now)), RadioItem(TYPE_DELETE, getString(R.string.delete)) ) - RadioGroupDialog(activity = this, items = items, titleId = R.string.scheduled_message) { - when (it as Int) { + RadioGroupDialog(activity = this, items = items, titleId = R.string.scheduled_message) { any -> + when (any as Int) { TYPE_DELETE -> cancelScheduledMessageAndRefresh(message.id) TYPE_EDIT -> editScheduledMessage(message) TYPE_SEND -> { + messages.removeAll { message.id == it.id } extractAttachments(message) sendNormalMessage(message.body, message.subscriptionId) cancelScheduledMessageAndRefresh(message.id) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt index 6b97672f..28190c1f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt @@ -58,7 +58,13 @@ val Context.messageAttachmentsDB: MessageAttachmentsDao get() = getMessagesDB(). val Context.messagesDB: MessagesDao get() = getMessagesDB().MessagesDao() -fun Context.getMessages(threadId: Long, getImageResolutions: Boolean, dateFrom: Int = -1, includeScheduledMessages: Boolean = true): ArrayList { +fun Context.getMessages( + threadId: Long, + getImageResolutions: Boolean, + dateFrom: Int = -1, + includeScheduledMessages: Boolean = true, + limit: Int = MESSAGES_LIMIT +): ArrayList { val uri = Sms.CONTENT_URI val projection = arrayOf( Sms._ID, @@ -75,7 +81,7 @@ fun Context.getMessages(threadId: Long, getImageResolutions: Boolean, dateFrom: val rangeQuery = if (dateFrom == -1) "" else "AND ${Sms.DATE} < ${dateFrom.toLong() * 1000}" val selection = "${Sms.THREAD_ID} = ? $rangeQuery" val selectionArgs = arrayOf(threadId.toString()) - val sortOrder = "${Sms.DATE} DESC LIMIT $MESSAGES_LIMIT" + val sortOrder = "${Sms.DATE} DESC LIMIT $limit" val blockStatus = HashMap() val blockedNumbers = getBlockedNumbers() From 417a37bbb4edb514fe9112d7fea7977d7d13b40c Mon Sep 17 00:00:00 2001 From: Naveen Date: Fri, 18 Nov 2022 01:15:46 +0530 Subject: [PATCH 08/16] Move blocking stuff to background thread --- .../smsmessenger/helpers/AttachmentPreviews.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/AttachmentPreviews.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/AttachmentPreviews.kt index 5b8a5e9d..58aea416 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/AttachmentPreviews.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/AttachmentPreviews.kt @@ -5,6 +5,7 @@ import android.net.Uri import android.view.View import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.SimpleContactsHelper +import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.extensions.* import kotlinx.android.synthetic.main.item_attachment_document.view.* @@ -25,16 +26,18 @@ fun View.setupDocumentPreview( filename.text = title } - if (attachment) { + ensureBackgroundThread { try { val size = context.getFileSizeFromUri(uri) - file_size.beVisible() - file_size.text = size.formatSize() + post { + file_size.beVisible() + file_size.text = size.formatSize() + } } catch (e: Exception) { - file_size.beGone() + post { + file_size.beGone() + } } - } else { - file_size.beGone() } val textColor = context.getProperTextColor() From 9fbf6fa581db8e2dc96748515ec439a76767bc55 Mon Sep 17 00:00:00 2001 From: Naveen Date: Fri, 18 Nov 2022 01:29:16 +0530 Subject: [PATCH 09/16] Increase rounded corner radius for messages --- app/src/main/res/drawable/item_received_background.xml | 7 ++++--- app/src/main/res/drawable/item_sent_background.xml | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/drawable/item_received_background.xml b/app/src/main/res/drawable/item_received_background.xml index 3930c18e..8b0f65f8 100644 --- a/app/src/main/res/drawable/item_received_background.xml +++ b/app/src/main/res/drawable/item_received_background.xml @@ -3,9 +3,10 @@ android:shape="rectangle"> + android:bottomLeftRadius="@dimen/small_margin" + android:bottomRightRadius="@dimen/big_margin" + android:topLeftRadius="@dimen/big_margin" + android:topRightRadius="@dimen/big_margin" /> diff --git a/app/src/main/res/drawable/item_sent_background.xml b/app/src/main/res/drawable/item_sent_background.xml index d75f6f79..e4f45e2c 100644 --- a/app/src/main/res/drawable/item_sent_background.xml +++ b/app/src/main/res/drawable/item_sent_background.xml @@ -3,9 +3,10 @@ android:shape="rectangle"> + android:bottomLeftRadius="@dimen/big_margin" + android:bottomRightRadius="@dimen/small_margin" + android:topLeftRadius="@dimen/big_margin" + android:topRightRadius="@dimen/big_margin" /> From 89f378b973b1c6922ca3dcd00a15af435412a5ec Mon Sep 17 00:00:00 2001 From: Naveen Date: Fri, 18 Nov 2022 01:45:17 +0530 Subject: [PATCH 10/16] Minor code consistency change --- .../smsmessenger/activities/MainActivity.kt | 2 +- .../adapters/ConversationsAdapter.kt | 4 ++-- .../smsmessenger/models/Conversation.kt | 24 ++++++++++--------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt index 36e1772c..c4ab1ac6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt @@ -257,7 +257,7 @@ class MainActivity : SimpleActivity() { } cachedConversations.forEach { cachedConv -> - val conv = conversations.find { it.threadId == cachedConv.threadId && !cachedConv.areContentsTheSame(it) } + val conv = conversations.find { it.threadId == cachedConv.threadId && !Conversation.areContentsTheSame(cachedConv, it) } if (conv != null) { val conversation = conv.copy(date = maxOf(cachedConv.date, conv.date)) conversationsDB.insertOrUpdate(conversation) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt index 77100cb4..880d96dc 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ConversationsAdapter.kt @@ -374,11 +374,11 @@ class ConversationsAdapter( private class ConversationDiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Conversation, newItem: Conversation): Boolean { - return oldItem.areItemsTheSame(newItem) + return Conversation.areItemsTheSame(oldItem, newItem) } override fun areContentsTheSame(oldItem: Conversation, newItem: Conversation): Boolean { - return oldItem.areContentsTheSame(newItem) + return Conversation.areContentsTheSame(oldItem, newItem) } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt index b2182968..5da76a0f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Conversation.kt @@ -18,17 +18,19 @@ data class Conversation( @ColumnInfo(name = "is_scheduled") var isScheduled: Boolean = false ) { - fun areItemsTheSame(other: Conversation): Boolean { - return threadId == other.threadId - } + companion object { + fun areItemsTheSame(old: Conversation, new: Conversation): Boolean { + return old.threadId == new.threadId + } - fun areContentsTheSame(other: Conversation): Boolean { - return snippet == other.snippet && - date == other.date && - read == other.read && - title == other.title && - photoUri == other.photoUri && - isGroupConversation == other.isGroupConversation && - phoneNumber == other.phoneNumber + fun areContentsTheSame(old: Conversation, new: Conversation): Boolean { + return old.snippet == new.snippet && + old.date == new.date && + old.read == new.read && + old.title == new.title && + old.photoUri == new.photoUri && + old.isGroupConversation == new.isGroupConversation && + old.phoneNumber == new.phoneNumber + } } } From 4295e5cf5538a895ba923eb9cd88f172e71aea8e Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 19 Nov 2022 05:30:11 +0530 Subject: [PATCH 11/16] Fix scheduled message glitch and clarify logic --- .../smsmessenger/activities/ThreadActivity.kt | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) 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 9e4262b7..1e9b9c0a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -433,13 +433,13 @@ class ThreadActivity : SimpleActivity() { } updateLastConversationMessage(threadId) + // move all scheduled messages to a temporary thread when there are no real messages left 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 + val scheduledMessage = messages.last() + val fakeThreadId = generateRandomId() + createTemporaryThread(scheduledMessage, fakeThreadId) + updateScheduledMessagesThreadId(messages, fakeThreadId) + threadId = fakeThreadId } refreshMessages() } @@ -1078,7 +1078,7 @@ class ThreadActivity : SimpleActivity() { val conversation = conversationsDB.getConversationWithThreadId(threadId) if (conversation != null) { val nowSeconds = (System.currentTimeMillis() / 1000).toInt() - conversationsDB.insertOrUpdate(conversation.copy(date = nowSeconds)) + conversationsDB.insertOrUpdate(conversation.copy(date = nowSeconds, snippet = message.body)) } scheduleMessage(message) insertOrUpdateMessage(message) @@ -1280,17 +1280,21 @@ class ThreadActivity : SimpleActivity() { val lastMaxId = messages.filterNot { it.isScheduled }.maxByOrNull { it.id }?.id ?: 0L val newThreadId = getThreadId(participants.getAddresses().toSet()) - val newMessages = getMessages(newThreadId, false) - messages = if (messages.all { it.isScheduled } && newMessages.isNotEmpty()) { - threadId = newThreadId + val newMessages = getMessages(newThreadId, getImageResolutions = true, includeScheduledMessages = false) + + if (messages.isNotEmpty() && messages.all { it.isScheduled } && newMessages.isNotEmpty()) { // update scheduled messages with real thread id - updateScheduledMessagesThreadId(messages, newThreadId) - getMessages(newThreadId, true) - } else { - getMessages(threadId, true) + threadId = newThreadId + updateScheduledMessagesThreadId(messages = messages.filter { it.threadId != threadId }, threadId) } - messages.filter { !it.isReceivedMessage() && it.id > lastMaxId }.forEach { latestMessage -> + messages = newMessages.apply { + val scheduledMessages = messagesDB.getScheduledThreadMessages(threadId) + .filterNot { it.isScheduled && it.millis() < System.currentTimeMillis() } + addAll(scheduledMessages) + } + + messages.filter { !it.isScheduled && !it.isReceivedMessage() && it.id > lastMaxId }.forEach { latestMessage -> maybeUpdateMessageSubId(latestMessage) messagesDB.insertOrIgnore(latestMessage) } From 6cc827769476bf0ddd3d51d9a1f4fba43d8a5254 Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 19 Nov 2022 06:08:01 +0530 Subject: [PATCH 12/16] Enable stable ids again now we have stable ids, sorta --- .../smsmessenger/adapters/ThreadAdapter.kt | 8 ++++++++ .../smsmessenger/models/Message.kt | 15 +++++++++++++++ 2 files changed, 23 insertions(+) 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 5273fdc4..12dd23d5 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt @@ -67,6 +67,7 @@ class ThreadAdapter( init { setupDragListener(true) + setHasStableIds(true) } override fun getActionMenuId() = R.menu.cab_thread @@ -142,6 +143,13 @@ class ThreadAdapter( bindViewHolder(holder) } + override fun getItemId(position: Int): Long { + return when (val item = getItem(position)) { + is Message -> Message.getStableId(item) + else -> item.hashCode().toLong() + } + } + override fun getItemViewType(position: Int): Int { return when (val item = getItem(position)) { is ThreadLoading -> THREAD_LOADING 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 92320779..36123978 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt @@ -29,6 +29,20 @@ data class Message( fun millis() = date * 1000L companion object { + + fun getStableId(message: Message): Long { + var result = message.id.hashCode() + result = 31 * result + message.body.hashCode() + result = 32 * result + message.date.hashCode() + result = 31 * result + message.threadId.hashCode() + result = 31 * result + message.isMMS.hashCode() + result = 31 * result + (message.attachment?.hashCode() ?: 0) + result = 31 * result + message.senderName.hashCode() + result = 31 * result + message.senderPhotoUri.hashCode() + result = 31 * result + message.isScheduled.hashCode() + return result.toLong() + } + fun areItemsTheSame(old: Message, new: Message): Boolean { return old.id == new.id } @@ -36,6 +50,7 @@ data class Message( fun areContentsTheSame(old: Message, new: Message): Boolean { return old.body == new.body && old.threadId == new.threadId && + old.date == new.date && old.isMMS == new.isMMS && old.attachment == new.attachment && old.senderName == new.senderName && From 9ca01ba73e39cbe8600fc02fd7b0f94cd40bf696 Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 19 Nov 2022 07:01:41 +0530 Subject: [PATCH 13/16] Update commons --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 3d91c9c5..6f38ab32 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -63,7 +63,7 @@ android { } dependencies { - implementation 'com.github.SimpleMobileTools:Simple-Commons:af11ea2e30' + implementation 'com.github.SimpleMobileTools:Simple-Commons:8a01bdee8c' implementation 'org.greenrobot:eventbus:3.3.1' implementation 'com.github.tibbi:IndicatorFastScroll:4524cd0b61' implementation 'com.github.tibbi:android-smsmms:3581774c39' From c6d453f5ab04210a86f2cd1cbe200836a3dded01 Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 19 Nov 2022 22:04:57 +0530 Subject: [PATCH 14/16] Fix typo --- .../kotlin/com/simplemobiletools/smsmessenger/models/Message.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 36123978..8816d2d8 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/Message.kt @@ -33,7 +33,7 @@ data class Message( fun getStableId(message: Message): Long { var result = message.id.hashCode() result = 31 * result + message.body.hashCode() - result = 32 * result + message.date.hashCode() + result = 31 * result + message.date.hashCode() result = 31 * result + message.threadId.hashCode() result = 31 * result + message.isMMS.hashCode() result = 31 * result + (message.attachment?.hashCode() ?: 0) From 398c2237bcf374fbbad7b46b58b4c432acbe266a Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 19 Nov 2022 22:46:18 +0530 Subject: [PATCH 15/16] Retain scroll position when deleting messages Removed the call to `refreshMessages()` as it's not really needed here and also because it'll reset the scroll position in some cases --- .../smsmessenger/activities/ThreadActivity.kt | 4 ++-- .../simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) 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 1e9b9c0a..628e516d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -408,6 +408,7 @@ class ThreadActivity : SimpleActivity() { } private fun deleteMessages(messagesToRemove: List) { + val deletePosition = threadItems.indexOf(messagesToRemove.first()) messages.removeAll(messagesToRemove.toSet()) threadItems = getThreadItems() @@ -416,7 +417,7 @@ class ThreadActivity : SimpleActivity() { finish() } else { getOrCreateThreadAdapter().apply { - updateMessages(threadItems) + updateMessages(threadItems, scrollPosition = deletePosition) finishActMode() } } @@ -441,7 +442,6 @@ class ThreadActivity : SimpleActivity() { updateScheduledMessagesThreadId(messages, fakeThreadId) threadId = fakeThreadId } - refreshMessages() } private fun fetchNextMessages() { 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 12dd23d5..0fa9e199 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/adapters/ThreadAdapter.kt @@ -230,7 +230,9 @@ class ThreadAdapter( fun updateMessages(newMessages: ArrayList, scrollPosition: Int = newMessages.lastIndex) { val latestMessages = newMessages.toMutableList() submitList(latestMessages) { - recyclerView.scrollToPosition(scrollPosition) + if (scrollPosition != -1) { + recyclerView.scrollToPosition(scrollPosition) + } } } From a54a65c081cae6cb4d634aaf7dbf411cb03d5a71 Mon Sep 17 00:00:00 2001 From: Naveen Date: Sat, 19 Nov 2022 22:59:19 +0530 Subject: [PATCH 16/16] Minor code formatting --- .../smsmessenger/activities/ThreadActivity.kt | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) 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 628e516d..f1a9d0d2 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/ThreadActivity.kt @@ -397,7 +397,9 @@ class ThreadActivity : SimpleActivity() { private fun scrollToBottom() { val position = getOrCreateThreadAdapter().currentList.lastIndex - if (position >= 0) thread_messages_list.smoothScrollToPosition(position) + if (position >= 0) { + thread_messages_list.smoothScrollToPosition(position) + } } private fun handleItemClick(any: Any) { @@ -448,7 +450,9 @@ class ThreadActivity : SimpleActivity() { if (messages.isEmpty() || allMessagesFetched || loadingOlderMessages) { if (allMessagesFetched) { getOrCreateThreadAdapter().apply { - val newList = currentList.toMutableList().apply { removeAll { it is ThreadLoading } } + val newList = currentList.toMutableList().apply { + removeAll { it is ThreadLoading } + } updateMessages(newMessages = newList as ArrayList, scrollPosition = 0) } } @@ -486,7 +490,9 @@ class ThreadActivity : SimpleActivity() { val textColor = getProperTextColor() thread_send_message.apply { setTextColor(textColor) - compoundDrawables.forEach { it?.applyColorFilter(textColor) } + compoundDrawables.forEach { + it?.applyColorFilter(textColor) + } } confirm_manage_contacts.applyColorFilter(textColor) @@ -513,7 +519,11 @@ class ThreadActivity : SimpleActivity() { thread_send_message.isClickable = false thread_type_message.onTextChangeListener { checkSendMessageAvailability() - val messageString = if (config.useSimpleCharacters) it.normalizeString() else it + val messageString = if (config.useSimpleCharacters) { + it.normalizeString() + } else { + it + } val messageLength = SmsMessage.calculateLength(messageString, false) thread_character_counter.text = "${messageLength[2]}/${messageLength[0]}" } @@ -873,7 +883,8 @@ class ThreadActivity : SimpleActivity() { } if (!allMessagesFetched && messages.size >= MESSAGES_LIMIT) { - items.add(0, ThreadLoading(generateRandomId())) + val threadLoading = ThreadLoading(generateRandomId()) + items.add(0, threadLoading) } return items