Add separate screen for recycle bin messages

This commit is contained in:
Ensar Sarajčić 2023-07-20 16:04:51 +02:00
parent 31be5d3d95
commit 3f06b521bf
11 changed files with 239 additions and 27 deletions

View File

@ -191,6 +191,7 @@ class MainActivity : SimpleActivity() {
private fun refreshMenuItems() {
main_menu.getToolbar().menu.apply {
findItem(R.id.more_apps_from_us).isVisible = !resources.getBoolean(R.bool.hide_google_relations)
findItem(R.id.show_recycle_bin).isVisible = config.useRecycleBin
}
}

View File

@ -8,6 +8,7 @@ import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.adapters.ConversationsAdapter
import com.simplemobiletools.smsmessenger.adapters.RecycleBinConversationsAdapter
import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.models.Conversation
@ -97,11 +98,11 @@ class RecycleBinConversationsActivity : SimpleActivity() {
}
}
private fun getOrCreateConversationsAdapter(): ConversationsAdapter {
private fun getOrCreateConversationsAdapter(): RecycleBinConversationsAdapter {
var currAdapter = conversations_list.adapter
if (currAdapter == null) {
hideKeyboard()
currAdapter = ConversationsAdapter(
currAdapter = RecycleBinConversationsAdapter(
activity = this,
recyclerView = conversations_list,
onRefresh = { notifyDatasetChanged() },
@ -113,7 +114,7 @@ class RecycleBinConversationsActivity : SimpleActivity() {
conversations_list.scheduleLayoutAnimation()
}
}
return currAdapter as ConversationsAdapter
return currAdapter as RecycleBinConversationsAdapter
}
private fun setupConversations(conversations: ArrayList<Conversation>) {
@ -150,6 +151,7 @@ class RecycleBinConversationsActivity : SimpleActivity() {
putExtra(THREAD_ID, conversation.threadId)
putExtra(THREAD_TITLE, conversation.title)
putExtra(WAS_PROTECTION_HANDLED, true)
putExtra(IS_RECYCLE_BIN, true)
startActivity(this)
}
}

View File

@ -105,6 +105,7 @@ class ThreadActivity : SimpleActivity() {
private var allMessagesFetched = false
private var oldestMessageDate = -1
private var wasProtectionHandled = false
private var isRecycleBin = false
private var isScheduledMessage: Boolean = false
private var scheduledMessage: Message? = null
@ -140,6 +141,7 @@ class ThreadActivity : SimpleActivity() {
intent.getStringExtra(THREAD_TITLE)?.let {
thread_toolbar.title = it
}
isRecycleBin = intent.getBooleanExtra(IS_RECYCLE_BIN, false)
wasProtectionHandled = intent.getBooleanExtra(WAS_PROTECTION_HANDLED, false)
bus = EventBus.getDefault()
@ -163,6 +165,7 @@ class ThreadActivity : SimpleActivity() {
setupAttachmentPickerView()
setupKeyboardListener()
hideAttachmentPicker()
maybeSetupRecycleBinView()
}
override fun onResume() {
@ -247,20 +250,21 @@ class ThreadActivity : SimpleActivity() {
val firstPhoneNumber = participants.firstOrNull()?.phoneNumbers?.firstOrNull()?.value
thread_toolbar.menu.apply {
findItem(R.id.delete).isVisible = threadItems.isNotEmpty()
findItem(R.id.archive).isVisible = threadItems.isNotEmpty() && conversation?.isArchived == false
findItem(R.id.unarchive).isVisible = threadItems.isNotEmpty() && conversation?.isArchived == true
findItem(R.id.rename_conversation).isVisible = participants.size > 1 && conversation != null
findItem(R.id.conversation_details).isVisible = conversation != null
findItem(R.id.restore).isVisible = threadItems.isNotEmpty() && isRecycleBin
findItem(R.id.archive).isVisible = threadItems.isNotEmpty() && conversation?.isArchived == false && !isRecycleBin
findItem(R.id.unarchive).isVisible = threadItems.isNotEmpty() && conversation?.isArchived == true && !isRecycleBin
findItem(R.id.rename_conversation).isVisible = participants.size > 1 && conversation != null && !isRecycleBin
findItem(R.id.conversation_details).isVisible = conversation != null && !isRecycleBin
findItem(R.id.block_number).title = addLockedLabelIfNeeded(R.string.block_number)
findItem(R.id.block_number).isVisible = isNougatPlus()
findItem(R.id.dial_number).isVisible = participants.size == 1 && !isSpecialNumber()
findItem(R.id.manage_people).isVisible = !isSpecialNumber()
findItem(R.id.mark_as_unread).isVisible = threadItems.isNotEmpty()
findItem(R.id.block_number).isVisible = isNougatPlus() && !isRecycleBin
findItem(R.id.dial_number).isVisible = participants.size == 1 && !isSpecialNumber() && !isRecycleBin
findItem(R.id.manage_people).isVisible = !isSpecialNumber() && !isRecycleBin
findItem(R.id.mark_as_unread).isVisible = threadItems.isNotEmpty() && !isRecycleBin
// allow saving number in cases when we dont have it stored yet and it is a casual readable number
findItem(R.id.add_number_to_contact).isVisible = participants.size == 1 && participants.first().name == firstPhoneNumber && firstPhoneNumber.any {
it.isDigit()
}
} && !isRecycleBin
}
}
@ -273,6 +277,7 @@ class ThreadActivity : SimpleActivity() {
when (menuItem.itemId) {
R.id.block_number -> tryBlocking()
R.id.delete -> askConfirmDelete()
R.id.restore -> askConfirmRestoreAll()
R.id.archive -> archiveConversation()
R.id.unarchive -> unarchiveConversation()
R.id.rename_conversation -> renameConversation()
@ -306,7 +311,11 @@ class ThreadActivity : SimpleActivity() {
private fun setupCachedMessages(callback: () -> Unit) {
ensureBackgroundThread {
messages = try {
messagesDB.getThreadMessages(threadId).toMutableList() as ArrayList<Message>
if (isRecycleBin) {
messagesDB.getThreadMessagesFromRecycleBin(threadId).toMutableList() as ArrayList<Message>
} else {
messagesDB.getThreadMessages(threadId).toMutableList() as ArrayList<Message>
}
} catch (e: Exception) {
ArrayList()
}
@ -341,7 +350,10 @@ class ThreadActivity : SimpleActivity() {
privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor)
val cachedMessagesCode = messages.clone().hashCode()
messages = getMessages(threadId, true)
if (!isRecycleBin) {
val recycledMessages = messagesDB.getThreadMessagesFromRecycleBin(threadId).map { it.id }
messages = getMessages(threadId, true).filter { !recycledMessages.contains(it.id) }.toMutableList() as ArrayList<Message>
}
val hasParticipantWithoutName = participants.any { contact ->
contact.phoneNumbers.map { it.normalizedNumber }.contains(contact.name)
@ -389,8 +401,10 @@ class ThreadActivity : SimpleActivity() {
participants.add(contact)
}
messages.chunked(30).forEach { currentMessages ->
messagesDB.insertMessages(*currentMessages.toTypedArray())
if (!isRecycleBin) {
messages.chunked(30).forEach { currentMessages ->
messagesDB.insertMessages(*currentMessages.toTypedArray())
}
}
setupAttachmentSizes()
@ -409,7 +423,8 @@ class ThreadActivity : SimpleActivity() {
activity = this,
recyclerView = thread_messages_list,
itemClick = { handleItemClick(it) },
deleteMessages = { messages, toRecycleBin -> deleteMessages(messages, toRecycleBin) }
isRecycleBin = isRecycleBin,
deleteMessages = { messages, toRecycleBin, fromRecycleBin -> deleteMessages(messages, toRecycleBin, fromRecycleBin) }
)
thread_messages_list.adapter = currAdapter
@ -496,7 +511,7 @@ class ThreadActivity : SimpleActivity() {
}
}
private fun deleteMessages(messagesToRemove: List<Message>, toRecycleBin: Boolean) {
private fun deleteMessages(messagesToRemove: List<Message>, toRecycleBin: Boolean, fromRecycleBin: Boolean) {
val deletePosition = threadItems.indexOf(messagesToRemove.first())
messages.removeAll(messagesToRemove.toSet())
threadItems = getThreadItems()
@ -515,12 +530,13 @@ class ThreadActivity : SimpleActivity() {
messagesToRemove.forEach { message ->
val messageId = message.id
if (message.isScheduled) {
// TODO: Moving scheduled messages to recycle bin maybe doesn't make sense
deleteScheduledMessage(messageId)
cancelScheduleSendPendingIntent(messageId)
} else {
if (toRecycleBin) {
moveMessageToRecycleBin(messageId)
} else if (fromRecycleBin) {
restoreMessageFromRecycleBin(messageId)
} else {
deleteMessage(messageId, message.isMMS)
}
@ -792,7 +808,7 @@ class ThreadActivity : SimpleActivity() {
}
private fun maybeDisableShortCodeReply() {
if (isSpecialNumber()) {
if (isSpecialNumber() && !isRecycleBin) {
thread_send_message_holder.beGone()
reply_disabled_info_holder.beVisible()
val textColor = getProperTextColor()
@ -922,7 +938,23 @@ class ThreadActivity : SimpleActivity() {
val confirmationMessage = R.string.delete_whole_conversation_confirmation
ConfirmationDialog(this, getString(confirmationMessage)) {
ensureBackgroundThread {
deleteConversation(threadId)
if (isRecycleBin) {
emptyMessagesRecycleBinForConversation(threadId)
} else {
deleteConversation(threadId)
}
runOnUiThread {
refreshMessages()
finish()
}
}
}
}
private fun askConfirmRestoreAll() {
ConfirmationDialog(this, "Restore all messages from this conversation?") {
ensureBackgroundThread {
restoreAllMessagesFromRecycleBinForConversation(threadId)
runOnUiThread {
refreshMessages()
finish()
@ -1485,6 +1517,10 @@ class ThreadActivity : SimpleActivity() {
@Subscribe(threadMode = ThreadMode.ASYNC)
fun refreshMessages(event: Events.RefreshMessages) {
if (isRecycleBin) {
return
}
refreshedSinceSent = true
allMessagesFetched = false
oldestMessageDate = -1
@ -1747,6 +1783,12 @@ class ThreadActivity : SimpleActivity() {
animateAttachmentButton(rotation = -135f)
}
private fun maybeSetupRecycleBinView() {
if (isRecycleBin) {
thread_send_message_holder.beGone()
}
}
private fun hideAttachmentPicker() {
attachment_picker_divider.beGone()
attachment_picker_holder.apply {

View File

@ -0,0 +1,107 @@
package com.simplemobiletools.smsmessenger.adapters
import android.view.Menu
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.extensions.notificationManager
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.extensions.deleteConversation
import com.simplemobiletools.smsmessenger.extensions.restoreAllMessagesFromRecycleBinForConversation
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
import com.simplemobiletools.smsmessenger.models.Conversation
class RecycleBinConversationsAdapter(
activity: SimpleActivity, recyclerView: MyRecyclerView, onRefresh: () -> Unit, itemClick: (Any) -> Unit
) : BaseConversationsAdapter(activity, recyclerView, onRefresh, itemClick) {
override fun getActionMenuId() = R.menu.cab_recycle_bin_conversations
override fun prepareActionMode(menu: Menu) {}
override fun actionItemPressed(id: Int) {
if (selectedKeys.isEmpty()) {
return
}
when (id) {
R.id.cab_delete -> askConfirmDelete()
R.id.cab_restore -> askConfirmRestore()
R.id.cab_select_all -> selectAll()
}
}
private fun askConfirmDelete() {
val itemsCnt = selectedKeys.size
val items = resources.getQuantityString(R.plurals.delete_conversations, itemsCnt, itemsCnt)
val baseString = R.string.deletion_confirmation
val question = String.format(resources.getString(baseString), items)
ConfirmationDialog(activity, question) {
ensureBackgroundThread {
deleteConversations()
}
}
}
private fun deleteConversations() {
if (selectedKeys.isEmpty()) {
return
}
val conversationsToRemove = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
conversationsToRemove.forEach {
activity.deleteConversation(it.threadId)
activity.notificationManager.cancel(it.threadId.hashCode())
}
removeConversationsFromList(conversationsToRemove)
}
private fun askConfirmRestore() {
val itemsCnt = selectedKeys.size
val items = resources.getQuantityString(R.plurals.delete_conversations, itemsCnt, itemsCnt)
val question = String.format("Are you sure you want to restore %s?", items)
ConfirmationDialog(activity, question) {
ensureBackgroundThread {
restoreConversations()
}
}
}
private fun restoreConversations() {
if (selectedKeys.isEmpty()) {
return
}
val conversationsToRemove = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
conversationsToRemove.forEach {
activity.restoreAllMessagesFromRecycleBinForConversation(it.threadId)
}
removeConversationsFromList(conversationsToRemove)
}
private fun removeConversationsFromList(removedConversations: List<Conversation>) {
val newList = try {
currentList.toMutableList().apply { removeAll(removedConversations) }
} catch (ignored: Exception) {
currentList.toMutableList()
}
activity.runOnUiThread {
if (newList.none { selectedKeys.contains(it.hashCode()) }) {
refreshMessages()
finishActMode()
} else {
submitList(newList)
if (newList.isEmpty()) {
refreshMessages()
}
}
}
}
}

View File

@ -23,6 +23,7 @@ import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target
import com.simplemobiletools.commons.adapters.MyRecyclerViewListAdapter
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
@ -58,7 +59,8 @@ class ThreadAdapter(
activity: SimpleActivity,
recyclerView: MyRecyclerView,
itemClick: (Any) -> Unit,
val deleteMessages: (messages: List<Message>, toRecycleBin: Boolean) -> Unit
val isRecycleBin: Boolean,
val deleteMessages: (messages: List<Message>, toRecycleBin: Boolean, fromRecycleBin: Boolean) -> Unit
) : MyRecyclerViewListAdapter<ThreadItem>(activity, recyclerView, ThreadItemDiffCallback(), itemClick) {
private var fontSize = activity.getTextSize()
@ -84,6 +86,7 @@ class ThreadAdapter(
findItem(R.id.cab_forward_message).isVisible = isOneItemSelected
findItem(R.id.cab_select_text).isVisible = isOneItemSelected && hasText
findItem(R.id.cab_properties).isVisible = isOneItemSelected
findItem(R.id.cab_restore).isVisible = isRecycleBin
}
}
@ -99,6 +102,7 @@ class ThreadAdapter(
R.id.cab_forward_message -> forwardMessage()
R.id.cab_select_text -> selectText()
R.id.cab_delete -> askConfirmDelete()
R.id.cab_restore -> askConfirmRestore()
R.id.cab_select_all -> selectAll()
R.id.cab_properties -> showMessageDetails()
}
@ -206,12 +210,36 @@ class ThreadAdapter(
val baseString = R.string.deletion_confirmation
val question = String.format(resources.getString(baseString), items)
DeleteConfirmationDialog(activity, question, activity.config.useRecycleBin) { skipRecycleBin ->
DeleteConfirmationDialog(activity, question, activity.config.useRecycleBin && !isRecycleBin) { skipRecycleBin ->
ensureBackgroundThread {
val messagesToRemove = getSelectedItems()
if (messagesToRemove.isNotEmpty()) {
val toRecycleBin = !skipRecycleBin && activity.config.useRecycleBin
deleteMessages(messagesToRemove.filterIsInstance<Message>(), toRecycleBin)
val toRecycleBin = !skipRecycleBin && activity.config.useRecycleBin && !isRecycleBin
deleteMessages(messagesToRemove.filterIsInstance<Message>(), toRecycleBin, false)
}
}
}
}
private fun askConfirmRestore() {
val itemsCnt = selectedKeys.size
// not sure how we can get UnknownFormatConversionException here, so show the error and hope that someone reports it
val items = try {
resources.getQuantityString(R.plurals.delete_messages, itemsCnt, itemsCnt)
} catch (e: Exception) {
activity.showErrorToast(e)
return
}
val baseString = R.string.deletion_confirmation
val question = String.format("Are you sure you want to restore %s?", items)
ConfirmationDialog(activity, question) {
ensureBackgroundThread {
val messagesToRestore = getSelectedItems()
if (messagesToRestore.isNotEmpty()) {
deleteMessages(messagesToRestore.filterIsInstance<Message>(), false, true)
}
}
}

View File

@ -679,6 +679,17 @@ fun Context.emptyMessagesRecycleBin() {
}
}
fun Context.emptyMessagesRecycleBinForConversation(threadId: Long) {
val messages = messagesDB.getThreadMessagesFromRecycleBin(threadId)
for (message in messages) {
deleteMessage(message.id, message.isMMS)
}
}
fun Context.restoreAllMessagesFromRecycleBinForConversation(threadId: Long) {
messagesDB.deleteThreadMessagesFromRecycleBin(threadId)
}
fun Context.moveMessageToRecycleBin(id: Long) {
try {
messagesDB.insertRecycleBinEntry(RecycleBinMessage(id, System.currentTimeMillis()))
@ -687,6 +698,14 @@ fun Context.moveMessageToRecycleBin(id: Long) {
}
}
fun Context.restoreMessageFromRecycleBin(id: Long) {
try {
messagesDB.deleteFromRecycleBin(id)
} catch (e: Exception) {
showErrorToast(e)
}
}
fun Context.updateConversationArchivedStatus(threadId: Long, archived: Boolean) {
val uri = Threads.CONTENT_URI
val values = ContentValues().apply {

View File

@ -42,7 +42,7 @@ const val IS_MMS = "is_mms"
const val MESSAGE_ID = "message_id"
const val USE_RECYCLE_BIN = "use_recycle_bin"
const val LAST_RECYCLE_BIN_CHECK = "last_recycle_bin_check"
const val USE_ARCHIVE = "use_archive"
const val IS_RECYCLE_BIN = "is_recycle_bin"
private const val PATH = "com.simplemobiletools.smsmessenger.action."
const val MARK_AS_READ = PATH + "mark_as_read"

View File

@ -30,6 +30,9 @@ interface MessagesDao {
@Query("SELECT messages.* FROM messages LEFT OUTER JOIN recycle_bin_messages ON messages.id = recycle_bin_messages.id WHERE recycle_bin_messages.id IS NULL AND thread_id = :threadId")
fun getThreadMessages(threadId: Long): List<Message>
@Query("SELECT messages.* FROM messages LEFT OUTER JOIN recycle_bin_messages ON messages.id = recycle_bin_messages.id WHERE recycle_bin_messages.id IS NOT NULL AND thread_id = :threadId")
fun getThreadMessagesFromRecycleBin(threadId: Long): List<Message>
@Query("SELECT messages.* FROM messages LEFT OUTER JOIN recycle_bin_messages ON messages.id = recycle_bin_messages.id WHERE recycle_bin_messages.id IS NULL AND thread_id = :threadId AND is_scheduled = 1")
fun getScheduledThreadMessages(threadId: Long): List<Message>

View File

@ -12,7 +12,7 @@
<item
android:id="@+id/cab_restore"
android:showAsAction="never"
android:title="Restore this message"
android:title="Restore all messages from this conversation"
app:showAsAction="never" />
<item
android:id="@+id/cab_select_all"

View File

@ -31,6 +31,11 @@
android:icon="@drawable/ic_info_vector"
android:title="@string/properties"
app:showAsAction="ifRoom" />
<item
android:id="@+id/cab_restore"
android:showAsAction="never"
android:title="Restore this message"
app:showAsAction="never" />
<item
android:id="@+id/cab_forward_message"
android:showAsAction="never"

View File

@ -47,6 +47,11 @@
android:showAsAction="never"
android:title="@string/block_number"
app:showAsAction="never" />
<item
android:id="@+id/restore"
android:showAsAction="never"
android:title="Restore all messages from this conversation"
app:showAsAction="never" />
<item
android:id="@+id/mark_as_unread"
android:showAsAction="never"