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 &&