Implement archive functionality using system API

This commit is contained in:
Ensar Sarajčić 2023-07-17 16:43:31 +02:00
parent 4d13fa1079
commit 857a4f0b93
17 changed files with 127 additions and 218 deletions

View file

@ -64,7 +64,6 @@ class MainActivity : SimpleActivity() {
updateMaterialActivityViews(main_coordinator, conversations_list, useTransparentNavigation = true, useTopSearchMenu = true)
if (savedInstanceState == null) {
checkAndDeleteOldArchivedConversations()
handleAppPasswordProtection {
wasProtectionHandled = it
if (it) {

View file

@ -11,15 +11,12 @@ import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.extensions.conversationsDB
import com.simplemobiletools.smsmessenger.extensions.removeAllArchivedConversations
import com.simplemobiletools.smsmessenger.helpers.*
import kotlinx.android.synthetic.main.activity_settings.*
import java.util.*
class SettingsActivity : SimpleActivity() {
private var blockedNumbersAtPause = -1
private var archiveConversations = 0
override fun onCreate(savedInstanceState: Bundle?) {
isMaterialActivity = true
@ -50,8 +47,6 @@ class SettingsActivity : SimpleActivity() {
setupGroupMessageAsMMS()
setupLockScreenVisibility()
setupMMSFileSizeLimit()
setupUseRecycleBin()
setupEmptyRecycleBin()
setupAppPasswordProtection()
updateTextColors(settings_nested_scrollview)
@ -64,7 +59,6 @@ class SettingsActivity : SimpleActivity() {
settings_general_settings_label,
settings_outgoing_messages_label,
settings_notifications_label,
settings_archive_label,
settings_security_label
).forEach {
it.setTextColor(getProperPrimaryColor())
@ -250,43 +244,6 @@ class SettingsActivity : SimpleActivity() {
}
}
private fun setupUseRecycleBin() {
updateRecycleBinButtons()
settings_use_archive.isChecked = config.useArchive
settings_use_archive_holder.setOnClickListener {
settings_use_archive.toggle()
config.useArchive = settings_use_archive.isChecked
updateRecycleBinButtons()
}
}
private fun updateRecycleBinButtons() {
settings_empty_archive_holder.beVisibleIf(config.useArchive)
}
private fun setupEmptyRecycleBin() {
ensureBackgroundThread {
archiveConversations = conversationsDB.getArchivedCount()
runOnUiThread {
settings_empty_archive_size.text =
resources.getQuantityString(R.plurals.delete_conversations, archiveConversations, archiveConversations)
}
}
settings_empty_archive_holder.setOnClickListener {
if (archiveConversations == 0) {
toast(R.string.archive_is_empty)
} else {
ConfirmationDialog(this, "", R.string.empty_archive_confirmation, R.string.yes, R.string.no) {
removeAllArchivedConversations()
archiveConversations = 0
settings_empty_archive_size.text =
resources.getQuantityString(R.plurals.delete_conversations, archiveConversations, archiveConversations)
}
}
}
}
private fun setupAppPasswordProtection() {
settings_app_password_protection.isChecked = config.isAppPasswordProtectionOn
settings_app_password_protection_holder.setOnClickListener {

View file

@ -55,7 +55,6 @@ import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.adapters.AttachmentsAdapter
import com.simplemobiletools.smsmessenger.adapters.AutoCompleteTextViewAdapter
import com.simplemobiletools.smsmessenger.adapters.ThreadAdapter
import com.simplemobiletools.smsmessenger.dialogs.DeleteConfirmationDialog
import com.simplemobiletools.smsmessenger.dialogs.InvalidNumberDialog
import com.simplemobiletools.smsmessenger.dialogs.RenameConversationDialog
import com.simplemobiletools.smsmessenger.dialogs.ScheduleMessageDialog
@ -245,6 +244,8 @@ 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.block_number).title = addLockedLabelIfNeeded(R.string.block_number)
@ -269,6 +270,8 @@ class ThreadActivity : SimpleActivity() {
when (menuItem.itemId) {
R.id.block_number -> tryBlocking()
R.id.delete -> askConfirmDelete()
R.id.archive -> archiveConversation()
R.id.unarchive -> unarchiveConversation()
R.id.rename_conversation -> renameConversation()
R.id.conversation_details -> showConversationDetails()
R.id.add_number_to_contact -> addNumberToContact()
@ -889,18 +892,10 @@ class ThreadActivity : SimpleActivity() {
}
private fun askConfirmDelete() {
val confirmationMessage = if (config.useArchive) {
R.string.archive_whole_conversation_confirmation
} else {
R.string.delete_whole_conversation_confirmation
}
DeleteConfirmationDialog(this, getString(confirmationMessage), config.useArchive) { skipRecycleBin ->
val confirmationMessage = R.string.delete_whole_conversation_confirmation
ConfirmationDialog(this, getString(confirmationMessage)) {
ensureBackgroundThread {
if (skipRecycleBin || config.useArchive.not()) {
deleteConversation(threadId)
} else {
moveConversationToRecycleBin(threadId)
}
deleteConversation(threadId)
runOnUiThread {
refreshMessages()
finish()
@ -909,6 +904,26 @@ class ThreadActivity : SimpleActivity() {
}
}
private fun archiveConversation() {
ensureBackgroundThread {
updateConversationArchivedStatus(threadId, true)
runOnUiThread {
refreshMessages()
finish()
}
}
}
private fun unarchiveConversation() {
ensureBackgroundThread {
updateConversationArchivedStatus(threadId, false)
runOnUiThread {
refreshMessages()
finish()
}
}
}
private fun dialNumber() {
val phoneNumber = participants.first().phoneNumbers.first().normalizedNumber
dialNumber(phoneNumber)
@ -1337,7 +1352,7 @@ class ThreadActivity : SimpleActivity() {
}
}
messagesDB.insertOrUpdate(message)
conversationsDB.deleteThreadFromArchivedConversations(message.threadId)
updateConversationArchivedStatus(message.threadId, false)
}
// show selected contacts, properly split to new lines when appropriate

View file

@ -1,14 +1,14 @@
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.dialogs.DeleteConfirmationDialog
import com.simplemobiletools.smsmessenger.extensions.conversationsDB
import com.simplemobiletools.smsmessenger.extensions.deleteConversation
import com.simplemobiletools.smsmessenger.extensions.updateConversationArchivedStatus
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
import com.simplemobiletools.smsmessenger.models.Conversation
@ -38,7 +38,7 @@ class ArchivedConversationsAdapter(
val baseString = R.string.deletion_confirmation
val question = String.format(resources.getString(baseString), items)
DeleteConfirmationDialog(activity, question, showSkipArchiveOption = false) { _ ->
ConfirmationDialog(activity, question) {
ensureBackgroundThread {
deleteConversations()
}
@ -66,7 +66,7 @@ class ArchivedConversationsAdapter(
val conversationsToUnarchive = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
conversationsToUnarchive.forEach {
activity.conversationsDB.deleteThreadFromArchivedConversations(it.threadId)
activity.updateConversationArchivedStatus(it.threadId, false)
}
removeConversationsFromList(conversationsToUnarchive)

View file

@ -12,7 +12,6 @@ import com.simplemobiletools.commons.helpers.isNougatPlus
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.dialogs.DeleteConfirmationDialog
import com.simplemobiletools.smsmessenger.dialogs.RenameConversationDialog
import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
@ -54,6 +53,7 @@ class ConversationsAdapter(
R.id.cab_dial_number -> dialNumber()
R.id.cab_copy_number -> copyNumberToClipboard()
R.id.cab_delete -> askConfirmDelete()
R.id.cab_archive -> ensureBackgroundThread { archiveConversations() }
R.id.cab_rename_conversation -> renameConversation(getSelectedItems().first())
R.id.cab_mark_as_read -> markAsRead()
R.id.cab_mark_as_unread -> markAsUnread()
@ -118,32 +118,54 @@ class ConversationsAdapter(
val itemsCnt = selectedKeys.size
val items = resources.getQuantityString(R.plurals.delete_conversations, itemsCnt, itemsCnt)
val baseString = if (activity.config.useArchive) {
R.string.archive_confirmation
} else {
R.string.deletion_confirmation
}
val baseString = R.string.deletion_confirmation
val question = String.format(resources.getString(baseString), items)
DeleteConfirmationDialog(activity, question, activity.config.useArchive) { skipRecycleBin ->
ConfirmationDialog(activity, question) {
ensureBackgroundThread {
deleteConversations(skipRecycleBin)
deleteConversations()
}
}
}
private fun deleteConversations(skipRecycleBin: Boolean) {
private fun archiveConversations() {
if (selectedKeys.isEmpty()) {
return
}
val conversationsToRemove = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
conversationsToRemove.forEach {
if (skipRecycleBin || activity.config.useArchive.not()) {
activity.deleteConversation(it.threadId)
activity.updateConversationArchivedStatus(it.threadId, true)
activity.notificationManager.cancel(it.threadId.hashCode())
}
val newList = try {
currentList.toMutableList().apply { removeAll(conversationsToRemove) }
} catch (ignored: Exception) {
currentList.toMutableList()
}
activity.runOnUiThread {
if (newList.none { selectedKeys.contains(it.hashCode()) }) {
refreshMessages()
finishActMode()
} else {
activity.moveConversationToRecycleBin(it.threadId)
submitList(newList)
if (newList.isEmpty()) {
refreshMessages()
}
}
}
}
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())
}

View file

@ -14,7 +14,7 @@ import com.simplemobiletools.smsmessenger.interfaces.MessageAttachmentsDao
import com.simplemobiletools.smsmessenger.interfaces.MessagesDao
import com.simplemobiletools.smsmessenger.models.*
@Database(entities = [Conversation::class, ArchivedConversation::class, Attachment::class, MessageAttachment::class, Message::class], version = 8)
@Database(entities = [Conversation::class, Attachment::class, MessageAttachment::class, Message::class], version = 8)
@TypeConverters(Converters::class)
abstract class MessagesDatabase : RoomDatabase() {
@ -117,8 +117,7 @@ abstract class MessagesDatabase : RoomDatabase() {
private val MIGRATION_7_8 = object : Migration(7, 8) {
override fun migrate(database: SupportSQLiteDatabase) {
database.apply {
execSQL("CREATE TABLE IF NOT EXISTS archived_conversations (`thread_id` INTEGER NOT NULL PRIMARY KEY, `deleted_ts` INTEGER NOT NULL)")
execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_archived_conversations_thread_id` ON `archived_conversations` (`thread_id`)")
execSQL("ALTER TABLE conversations ADD COLUMN archived INTEGER NOT NULL DEFAULT 0")
}
}
}

View file

@ -1,39 +0,0 @@
package com.simplemobiletools.smsmessenger.dialogs
import android.app.Activity
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.extensions.beGoneIf
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.smsmessenger.R
import kotlinx.android.synthetic.main.dialog_delete_confirmation.view.delete_remember_title
import kotlinx.android.synthetic.main.dialog_delete_confirmation.view.skip_the_archive_checkbox
class DeleteConfirmationDialog(
private val activity: Activity,
private val message: String,
private val showSkipArchiveOption: Boolean,
private val callback: (skipRecycleBin: Boolean) -> Unit
) {
private var dialog: AlertDialog? = null
val view = activity.layoutInflater.inflate(R.layout.dialog_delete_confirmation, null)!!
init {
view.delete_remember_title.text = message
view.skip_the_archive_checkbox.beGoneIf(!showSkipArchiveOption)
activity.getAlertDialogBuilder()
.setPositiveButton(R.string.yes) { _, _ -> dialogConfirmed() }
.setNegativeButton(R.string.no, null)
.apply {
activity.setupDialogStuff(view, this) { alertDialog ->
dialog = alertDialog
}
}
}
private fun dialogConfirmed() {
dialog?.dismiss()
callback(view.skip_the_archive_checkbox.isChecked)
}
}

View file

@ -238,7 +238,8 @@ fun Context.getConversations(threadId: Long? = null, privateContacts: ArrayList<
Threads.SNIPPET,
Threads.DATE,
Threads.READ,
Threads.RECIPIENT_IDS
Threads.RECIPIENT_IDS,
Threads.ARCHIVED
)
var selection = "${Threads.MESSAGE_COUNT} > ?"
@ -277,7 +278,8 @@ fun Context.getConversations(threadId: Long? = null, privateContacts: ArrayList<
val photoUri = if (phoneNumbers.size == 1) simpleContactHelper.getPhotoUriFromPhoneNumber(phoneNumbers.first()) else ""
val isGroupConversation = phoneNumbers.size > 1
val read = cursor.getIntValue(Threads.READ) == 1
val conversation = Conversation(id, snippet, date.toInt(), read, title, photoUri, isGroupConversation, phoneNumbers.first())
val archived = cursor.getIntValue(Threads.ARCHIVED) == 1
val conversation = Conversation(id, snippet, date.toInt(), read, title, photoUri, isGroupConversation, phoneNumbers.first(), isArchived = archived)
conversations.add(conversation)
}
@ -581,21 +583,6 @@ fun Context.insertNewSMS(address: String, subject: String, body: String, date: L
}
}
fun Context.checkAndDeleteOldArchivedConversations(callback: (() -> Unit)? = null) {
if (config.useArchive && config.lastArchiveCheck < System.currentTimeMillis() - DAY_SECONDS * 1000) {
config.lastArchiveCheck = System.currentTimeMillis()
ensureBackgroundThread {
try {
for (conversation in conversationsDB.getOldArchived(System.currentTimeMillis() - MONTH_SECONDS * 1000L)) {
deleteConversation(conversation.threadId)
}
callback?.invoke()
} catch (e: Exception) {
}
}
}
}
fun Context.removeAllArchivedConversations(callback: (() -> Unit)? = null) {
ensureBackgroundThread {
try {
@ -631,13 +618,19 @@ fun Context.deleteConversation(threadId: Long) {
messagesDB.deleteThreadMessages(threadId)
}
fun Context.moveConversationToRecycleBin(threadId: Long) {
conversationsDB.archiveConversation(
ArchivedConversation(
threadId = threadId,
deletedTs = System.currentTimeMillis()
)
)
fun Context.updateConversationArchivedStatus(threadId: Long, archived: Boolean) {
val uri = Threads.CONTENT_URI
val values = ContentValues().apply {
put(Threads.ARCHIVED, archived)
}
val selection = "${Threads._ID} = ?"
val selectionArgs = arrayOf(threadId.toString())
contentResolver.update(uri, values, selection, selectionArgs)
if (archived) {
conversationsDB.moveToArchive(threadId)
} else {
conversationsDB.unarchive(threadId)
}
}
fun Context.deleteMessage(id: Long, isMMS: Boolean) {
@ -968,7 +961,8 @@ fun Context.createTemporaryThread(message: Message, threadId: Long = generateRan
isGroupConversation = addresses.size > 1,
phoneNumber = addresses.first(),
isScheduled = true,
usesCustomTitle = cachedConv?.usesCustomTitle == true
usesCustomTitle = cachedConv?.usesCustomTitle == true,
isArchived = false
)
try {
conversationsDB.insertOrUpdate(conversation)

View file

@ -1,7 +1,6 @@
package com.simplemobiletools.smsmessenger.interfaces
import androidx.room.*
import com.simplemobiletools.smsmessenger.models.ArchivedConversation
import com.simplemobiletools.smsmessenger.models.Conversation
@Dao
@ -9,21 +8,12 @@ interface ConversationsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrUpdate(conversation: Conversation): Long
@Query("SELECT conversations.* FROM conversations LEFT OUTER JOIN archived_conversations ON conversations.thread_id = archived_conversations.thread_id WHERE archived_conversations.deleted_ts is NULL")
@Query("SELECT * FROM conversations WHERE archived = 0")
fun getNonArchived(): List<Conversation>
@Query("SELECT conversations.* FROM archived_conversations INNER JOIN conversations ON conversations.thread_id = archived_conversations.thread_id")
@Query("SELECT * FROM conversations WHERE archived = 1")
fun getAllArchived(): List<Conversation>
@Query("SELECT COUNT(*) FROM archived_conversations")
fun getArchivedCount(): Int
@Query("SELECT * FROM archived_conversations WHERE deleted_ts < :timestamp")
fun getOldArchived(timestamp: Long): List<ArchivedConversation>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun archiveConversation(archivedConversation: ArchivedConversation)
@Query("SELECT * FROM conversations WHERE thread_id = :threadId")
fun getConversationWithThreadId(threadId: Long): Conversation?
@ -39,15 +29,12 @@ interface ConversationsDao {
@Query("UPDATE conversations SET read = 0 WHERE thread_id = :threadId")
fun markUnread(threadId: Long)
@Query("UPDATE conversations SET archived = 1 WHERE thread_id = :threadId")
fun moveToArchive(threadId: Long)
@Query("UPDATE conversations SET archived = 0 WHERE thread_id = :threadId")
fun unarchive(threadId: Long)
@Query("DELETE FROM conversations WHERE thread_id = :threadId")
fun deleteThreadFromConversations(threadId: Long)
@Query("DELETE FROM archived_conversations WHERE thread_id = :threadId")
fun deleteThreadFromArchivedConversations(threadId: Long)
@Transaction
fun deleteThreadId(threadId: Long) {
deleteThreadFromConversations(threadId)
deleteThreadFromArchivedConversations(threadId)
}
fun deleteThreadId(threadId: Long)
}

View file

@ -16,7 +16,8 @@ data class Conversation(
@ColumnInfo(name = "is_group_conversation") var isGroupConversation: Boolean,
@ColumnInfo(name = "phone_number") var phoneNumber: String,
@ColumnInfo(name = "is_scheduled") var isScheduled: Boolean = false,
@ColumnInfo(name = "uses_custom_title") var usesCustomTitle: Boolean = false
@ColumnInfo(name = "uses_custom_title") var usesCustomTitle: Boolean = false,
@ColumnInfo(name = "archived") var isArchived: Boolean = false
) {
companion object {

View file

@ -100,7 +100,7 @@ class SmsReceiver : BroadcastReceiver() {
subscriptionId
)
context.messagesDB.insertOrUpdate(message)
context.conversationsDB.deleteThreadFromArchivedConversations(threadId)
context.updateConversationArchivedStatus(threadId, false)
refreshMessages()
context.showReceivedMessageNotification(newMessageId, address, body, threadId, bitmap)
}