Handle conversations with scheduled messages only
This commit is contained in:
parent
f837790948
commit
ee8130c767
|
@ -208,29 +208,51 @@ class MainActivity : SimpleActivity() {
|
|||
setupConversations(conversations)
|
||||
getNewConversations(conversations)
|
||||
}
|
||||
conversations.forEach {
|
||||
clearExpiredScheduledMessages(it.threadId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNewConversations(cachedConversations: ArrayList<Conversation>) {
|
||||
val privateCursor = getMyContactsCursor(false, true)
|
||||
val privateCursor = getMyContactsCursor(favoritesOnly = false, withPhoneNumbersOnly = true)
|
||||
ensureBackgroundThread {
|
||||
val privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor)
|
||||
val conversations = getConversations(privateContacts = privateContacts)
|
||||
|
||||
val scheduledConversations = cachedConversations.filter { it.isScheduled }
|
||||
val allConversations = conversations.toArrayList().apply {
|
||||
addAll(scheduledConversations)
|
||||
}
|
||||
runOnUiThread {
|
||||
setupConversations(conversations)
|
||||
setupConversations(allConversations)
|
||||
}
|
||||
|
||||
conversations.forEach { clonedConversation ->
|
||||
if (!cachedConversations.map { it.threadId }.contains(clonedConversation.threadId)) {
|
||||
val threadIds = cachedConversations.map { it.threadId }
|
||||
if (!threadIds.contains(clonedConversation.threadId)) {
|
||||
conversationsDB.insertOrUpdate(clonedConversation)
|
||||
cachedConversations.add(clonedConversation)
|
||||
}
|
||||
}
|
||||
|
||||
cachedConversations.forEach { cachedConversation ->
|
||||
if (!conversations.map { it.threadId }.contains(cachedConversation.threadId)) {
|
||||
conversationsDB.deleteThreadId(cachedConversation.threadId)
|
||||
val threadId = cachedConversation.threadId
|
||||
|
||||
val isTemporaryThread = cachedConversation.isScheduled
|
||||
val isConversationDeleted = !conversations.map { it.threadId }.contains(threadId)
|
||||
if (isConversationDeleted && !isTemporaryThread) {
|
||||
conversationsDB.deleteThreadId(threadId)
|
||||
}
|
||||
|
||||
val newConversation = conversations.find { it.phoneNumber == cachedConversation.phoneNumber }
|
||||
if (isTemporaryThread && newConversation != null) {
|
||||
// delete the original temporary thread and move any scheduled messages to the new thread
|
||||
conversationsDB.deleteThreadId(threadId)
|
||||
messagesDB.getScheduledThreadMessages(threadId)
|
||||
.forEach { message ->
|
||||
messagesDB.insertOrUpdate(message.copy(threadId = newConversation.threadId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -273,8 +295,9 @@ class MainActivity : SimpleActivity() {
|
|||
hideKeyboard()
|
||||
ConversationsAdapter(this, sortedConversations, conversations_list) {
|
||||
Intent(this, ThreadActivity::class.java).apply {
|
||||
putExtra(THREAD_ID, (it as Conversation).threadId)
|
||||
putExtra(THREAD_TITLE, it.title)
|
||||
val conversation = it as Conversation
|
||||
putExtra(THREAD_ID, conversation.threadId)
|
||||
putExtra(THREAD_TITLE, conversation.title)
|
||||
startActivity(this)
|
||||
}
|
||||
}.apply {
|
||||
|
|
|
@ -232,6 +232,8 @@ class ThreadActivity : SimpleActivity() {
|
|||
} catch (e: Exception) {
|
||||
ArrayList()
|
||||
}
|
||||
clearExpiredScheduledMessages(threadId, messages)
|
||||
messages.removeAll { it.isScheduled && it.millis() < System.currentTimeMillis() }
|
||||
|
||||
messages.sortBy { it.date }
|
||||
if (messages.size > MESSAGES_LIMIT) {
|
||||
|
@ -330,9 +332,13 @@ class ThreadActivity : SimpleActivity() {
|
|||
|
||||
val currAdapter = thread_messages_list.adapter
|
||||
if (currAdapter == null) {
|
||||
ThreadAdapter(this, threadItems, thread_messages_list) { any ->
|
||||
handleItemClick(any)
|
||||
}.apply {
|
||||
ThreadAdapter(
|
||||
activity = this,
|
||||
messages = threadItems,
|
||||
recyclerView = thread_messages_list,
|
||||
itemClick = { handleItemClick(it) },
|
||||
onThreadIdUpdate = { threadId = it }
|
||||
).apply {
|
||||
thread_messages_list.adapter = this
|
||||
}
|
||||
|
||||
|
@ -953,8 +959,13 @@ class ThreadActivity : SimpleActivity() {
|
|||
refreshedSinceSent = false
|
||||
try {
|
||||
ensureBackgroundThread {
|
||||
val messageId = scheduledMessage?.id ?: generateRandomMessageId()
|
||||
val messageId = scheduledMessage?.id ?: generateRandomId()
|
||||
val message = buildScheduledMessage(text, subscriptionId, messageId)
|
||||
if (messages.isEmpty()) {
|
||||
// create a temporary thread until a real message is sent
|
||||
threadId = message.threadId
|
||||
createTemporaryThread(message, message.threadId)
|
||||
}
|
||||
messagesDB.insertOrUpdate(message)
|
||||
scheduleMessage(message)
|
||||
}
|
||||
|
@ -1140,8 +1151,18 @@ class ThreadActivity : SimpleActivity() {
|
|||
notificationManager.cancel(threadId.hashCode())
|
||||
}
|
||||
|
||||
val lastMaxId = messages.maxByOrNull { it.id }?.id ?: 0L
|
||||
messages = getMessages(threadId, true)
|
||||
val newThreadId = getThreadId(participants.getAddresses().toSet())
|
||||
val newMessages = getMessages(newThreadId, false)
|
||||
messages = if (messages.all { it.isScheduled } && newMessages.isNotEmpty()) {
|
||||
threadId = newThreadId
|
||||
// update scheduled messages with real thread id
|
||||
updateScheduledMessagesThreadId(messages, newThreadId)
|
||||
getMessages(newThreadId, true)
|
||||
} else {
|
||||
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
|
||||
|
@ -1233,7 +1254,7 @@ class ThreadActivity : SimpleActivity() {
|
|||
hideScheduleSendUi()
|
||||
if (scheduledMessage != null) {
|
||||
ensureBackgroundThread {
|
||||
messagesDB.delete(scheduledMessage!!.id)
|
||||
deleteScheduledMessage(scheduledMessage!!.id)
|
||||
refreshMessages()
|
||||
}
|
||||
}
|
||||
|
@ -1267,6 +1288,7 @@ class ThreadActivity : SimpleActivity() {
|
|||
}
|
||||
|
||||
private fun buildScheduledMessage(text: String, subscriptionId: Int, messageId: Long): Message {
|
||||
val threadId = if (messages.isEmpty()) messageId else threadId
|
||||
return Message(
|
||||
id = messageId,
|
||||
body = text,
|
||||
|
@ -1287,8 +1309,9 @@ class ThreadActivity : SimpleActivity() {
|
|||
|
||||
private fun buildMessageAttachment(text: String, messageId: Long): MessageAttachment {
|
||||
val attachments = attachmentSelections.values
|
||||
.map { Attachment(null, messageId, it.uri.toString(), "*/*", 0, 0, "") }
|
||||
.map { Attachment(null, messageId, it.uri.toString(), contentResolver.getType(it.uri) ?: "*/*", 0, 0, "") }
|
||||
.toArrayList()
|
||||
|
||||
return MessageAttachment(messageId, text, attachments)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -173,7 +173,7 @@ class ConversationsAdapter(
|
|||
}
|
||||
|
||||
try {
|
||||
conversations.removeAll(conversationsToRemove)
|
||||
conversations.removeAll(conversationsToRemove.toSet())
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
|
||||
|
@ -319,15 +319,16 @@ class ConversationsAdapter(
|
|||
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize * 0.8f)
|
||||
}
|
||||
|
||||
if (conversation.read) {
|
||||
conversation_address.setTypeface(null, Typeface.NORMAL)
|
||||
conversation_body_short.setTypeface(null, Typeface.NORMAL)
|
||||
val style = if (conversation.read) {
|
||||
conversation_body_short.alpha = 0.7f
|
||||
if (conversation.isScheduled) Typeface.ITALIC else Typeface.NORMAL
|
||||
} else {
|
||||
conversation_address.setTypeface(null, Typeface.BOLD)
|
||||
conversation_body_short.setTypeface(null, Typeface.BOLD)
|
||||
conversation_body_short.alpha = 1f
|
||||
if (conversation.isScheduled) Typeface.BOLD_ITALIC else Typeface.BOLD
|
||||
|
||||
}
|
||||
conversation_address.setTypeface(null, style)
|
||||
conversation_body_short.setTypeface(null, style)
|
||||
|
||||
arrayListOf<TextView>(conversation_address, conversation_body_short, conversation_date).forEach {
|
||||
it.setTextColor(textColor)
|
||||
|
|
|
@ -51,9 +51,10 @@ 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_sending.view.*
|
||||
import kotlinx.android.synthetic.main.item_thread_success.view.*
|
||||
import java.util.*
|
||||
|
||||
class ThreadAdapter(
|
||||
activity: SimpleActivity, var messages: ArrayList<ThreadItem>, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit
|
||||
activity: SimpleActivity, var messages: ArrayList<ThreadItem>, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit, val onThreadIdUpdate: (Long) -> Unit
|
||||
) : MyRecyclerViewAdapter(activity, recyclerView, itemClick) {
|
||||
private var fontSize = activity.getTextSize()
|
||||
|
||||
|
@ -204,11 +205,21 @@ class ThreadAdapter(
|
|||
messagesToRemove.forEach {
|
||||
activity.deleteMessage((it as Message).id, it.isMMS)
|
||||
}
|
||||
messages.removeAll(messagesToRemove)
|
||||
messages.removeAll(messagesToRemove.toSet())
|
||||
activity.updateLastConversationMessage(threadId)
|
||||
|
||||
val messages = messages.filterIsInstance<Message>()
|
||||
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.filter { it is Message }.isEmpty()) {
|
||||
if (messages.isEmpty()) {
|
||||
activity.finish()
|
||||
} else {
|
||||
removeSelectedItems(positions)
|
||||
|
@ -333,7 +344,7 @@ class ThreadAdapter(
|
|||
thread_message_scheduled_icon.beGone()
|
||||
|
||||
thread_message_body.setPadding(padding, padding, padding, padding)
|
||||
thread_message_body.typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
|
||||
thread_message_body.typeface = Typeface.DEFAULT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -495,7 +506,7 @@ class ThreadAdapter(
|
|||
private fun launchViewIntent(uri: Uri, mimetype: String, filename: String) {
|
||||
Intent().apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
setDataAndType(uri, mimetype.toLowerCase())
|
||||
setDataAndType(uri, mimetype.lowercase(Locale.getDefault()))
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
|
||||
try {
|
||||
|
|
|
@ -67,8 +67,10 @@ abstract class MessagesDatabase : RoomDatabase() {
|
|||
database.apply {
|
||||
execSQL("CREATE TABLE conversations_new (`thread_id` INTEGER NOT NULL PRIMARY KEY, `snippet` TEXT NOT NULL, `date` INTEGER NOT NULL, `read` INTEGER NOT NULL, `title` TEXT NOT NULL, `photo_uri` TEXT NOT NULL, `is_group_conversation` INTEGER NOT NULL, `phone_number` TEXT NOT NULL)")
|
||||
|
||||
execSQL("INSERT OR IGNORE INTO conversations_new (thread_id, snippet, date, read, title, photo_uri, is_group_conversation, phone_number) " +
|
||||
"SELECT thread_id, snippet, date, read, title, photo_uri, is_group_conversation, phone_number FROM conversations")
|
||||
execSQL(
|
||||
"INSERT OR IGNORE INTO conversations_new (thread_id, snippet, date, read, title, photo_uri, is_group_conversation, phone_number) " +
|
||||
"SELECT thread_id, snippet, date, read, title, photo_uri, is_group_conversation, phone_number FROM conversations"
|
||||
)
|
||||
|
||||
execSQL("DROP TABLE conversations")
|
||||
|
||||
|
@ -91,6 +93,7 @@ abstract class MessagesDatabase : RoomDatabase() {
|
|||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.apply {
|
||||
execSQL("ALTER TABLE messages ADD COLUMN is_scheduled INTEGER NOT NULL DEFAULT 0")
|
||||
execSQL("ALTER TABLE conversations ADD COLUMN is_scheduled INTEGER NOT NULL DEFAULT 0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,8 +118,12 @@ fun Context.getMessages(threadId: Long, getImageResolutions: Boolean, dateFrom:
|
|||
messages.addAll(getMMS(threadId, getImageResolutions, sortOrder))
|
||||
|
||||
if (includeScheduledMessages) {
|
||||
val scheduledMessages = messagesDB.getScheduledThreadMessages(threadId)
|
||||
messages.addAll(scheduledMessages)
|
||||
try {
|
||||
val scheduledMessages = messagesDB.getScheduledThreadMessages(threadId)
|
||||
messages.addAll(scheduledMessages)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
messages = messages
|
||||
|
@ -580,7 +584,11 @@ fun Context.deleteConversation(threadId: Long) {
|
|||
}
|
||||
|
||||
uri = Mms.CONTENT_URI
|
||||
contentResolver.delete(uri, selection, selectionArgs)
|
||||
try {
|
||||
contentResolver.delete(uri, selection, selectionArgs)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
conversationsDB.deleteThreadId(threadId)
|
||||
messagesDB.deleteThreadMessages(threadId)
|
||||
|
@ -598,6 +606,14 @@ fun Context.deleteMessage(id: Long, isMMS: Boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
fun Context.deleteScheduledMessage(messageId: Long) {
|
||||
try {
|
||||
messagesDB.delete(messageId)
|
||||
} catch (e: Exception) {
|
||||
showErrorToast(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.markMessageRead(id: Long, isMMS: Boolean) {
|
||||
val uri = if (isMMS) Mms.CONTENT_URI else Sms.CONTENT_URI
|
||||
val contentValues = ContentValues().apply {
|
||||
|
@ -1001,3 +1017,51 @@ fun Context.subscriptionManagerCompat(): SubscriptionManager {
|
|||
SubscriptionManager.from(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.createTemporaryThread(message: Message, threadId: Long = generateRandomId()) {
|
||||
val simpleContactHelper = SimpleContactsHelper(this)
|
||||
val addresses = message.participants.getAddresses()
|
||||
val photoUri = if (addresses.size == 1) simpleContactHelper.getPhotoUriFromPhoneNumber(addresses.first()) else ""
|
||||
|
||||
val conversation = Conversation(
|
||||
threadId = threadId,
|
||||
snippet = message.body,
|
||||
date = message.date,
|
||||
read = true,
|
||||
title = message.participants.getThreadTitle(),
|
||||
photoUri = photoUri,
|
||||
isGroupConversation = addresses.size > 1,
|
||||
phoneNumber = addresses.first(),
|
||||
isScheduled = true
|
||||
)
|
||||
try {
|
||||
conversationsDB.insertOrUpdate(conversation)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.updateScheduledMessagesThreadId(messages: List<Message>, newThreadId: Long) {
|
||||
val scheduledMessages = messages.map { it.copy(threadId = newThreadId) }.toTypedArray()
|
||||
messagesDB.insertMessages(*scheduledMessages)
|
||||
}
|
||||
|
||||
fun Context.clearExpiredScheduledMessages(threadId: Long, messagesToDelete: List<Message>? = null) {
|
||||
val messages = messagesToDelete ?: messagesDB.getScheduledThreadMessages(threadId)
|
||||
|
||||
try {
|
||||
messages.filter { it.isScheduled && it.millis() < System.currentTimeMillis() }.forEach { msg ->
|
||||
messagesDB.delete(msg.id)
|
||||
}
|
||||
if (messages.filterNot { it.isScheduled && it.millis() < System.currentTimeMillis() }.isEmpty()) {
|
||||
// delete empty temporary thread
|
||||
val conversation = conversationsDB.getConversationWithThreadId(threadId)
|
||||
if (conversation != null && conversation.isScheduled) {
|
||||
conversationsDB.deleteThreadId(threadId)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ fun Context.isLongMmsMessage(text: String): Boolean {
|
|||
}
|
||||
|
||||
/** Not to be used with real messages persisted in the telephony db. This is for internal use only (e.g. scheduled messages). */
|
||||
fun generateRandomMessageId(length: Int = 8): Long {
|
||||
fun generateRandomId(length: Int = 9): Long {
|
||||
val millis = DateTime.now(DateTimeZone.UTC).millis
|
||||
val random = abs(Random(millis).nextLong())
|
||||
return random.toString().takeLast(length).toLong()
|
||||
|
|
|
@ -14,6 +14,9 @@ interface ConversationsDao {
|
|||
@Query("SELECT * FROM conversations")
|
||||
fun getAll(): List<Conversation>
|
||||
|
||||
@Query("SELECT * FROM conversations where thread_id = :threadId")
|
||||
fun getConversationWithThreadId(threadId: Long): Conversation?
|
||||
|
||||
@Query("SELECT * FROM conversations WHERE read = 0")
|
||||
fun getUnreadConversations(): List<Conversation>
|
||||
|
||||
|
|
|
@ -23,10 +23,10 @@ interface MessagesDao {
|
|||
@Query("SELECT * FROM messages WHERE thread_id = :threadId")
|
||||
fun getThreadMessages(threadId: Long): List<Message>
|
||||
|
||||
@Query("SELECT * FROM messages WHERE thread_id = :threadId AND is_scheduled")
|
||||
@Query("SELECT * FROM messages WHERE thread_id = :threadId AND is_scheduled = 1")
|
||||
fun getScheduledThreadMessages(threadId: Long): List<Message>
|
||||
|
||||
@Query("SELECT * FROM messages WHERE thread_id = :threadId AND id = :messageId AND is_scheduled")
|
||||
@Query("SELECT * FROM messages WHERE thread_id = :threadId AND id = :messageId AND is_scheduled = 1")
|
||||
fun getScheduledMessageWithId(threadId: Long, messageId: Long): Message
|
||||
|
||||
@Query("SELECT * FROM messages WHERE body LIKE :text")
|
||||
|
|
|
@ -14,5 +14,6 @@ data class Conversation(
|
|||
@ColumnInfo(name = "title") var title: String,
|
||||
@ColumnInfo(name = "photo_uri") var photoUri: String,
|
||||
@ColumnInfo(name = "is_group_conversation") var isGroupConversation: Boolean,
|
||||
@ColumnInfo(name = "phone_number") var phoneNumber: String
|
||||
@ColumnInfo(name = "phone_number") var phoneNumber: String,
|
||||
@ColumnInfo(name = "is_scheduled") var isScheduled: Boolean = false
|
||||
)
|
||||
|
|
|
@ -7,6 +7,8 @@ import android.os.PowerManager
|
|||
import com.simplemobiletools.commons.extensions.showErrorToast
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import com.simplemobiletools.smsmessenger.R
|
||||
import com.simplemobiletools.smsmessenger.extensions.conversationsDB
|
||||
import com.simplemobiletools.smsmessenger.extensions.deleteScheduledMessage
|
||||
import com.simplemobiletools.smsmessenger.extensions.getAddresses
|
||||
import com.simplemobiletools.smsmessenger.extensions.messagesDB
|
||||
import com.simplemobiletools.smsmessenger.helpers.SCHEDULED_MESSAGE_ID
|
||||
|
@ -33,6 +35,7 @@ class ScheduledMessageReceiver : BroadcastReceiver() {
|
|||
val message = try {
|
||||
context.messagesDB.getScheduledMessageWithId(threadId, messageId)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -41,7 +44,10 @@ class ScheduledMessageReceiver : BroadcastReceiver() {
|
|||
|
||||
try {
|
||||
context.sendMessage(message.body, addresses, message.subscriptionId, attachments)
|
||||
context.messagesDB.delete(messageId)
|
||||
|
||||
// delete temporary conversation and message as it's already persisted to the telephony db now
|
||||
context.deleteScheduledMessage(messageId)
|
||||
context.conversationsDB.deleteThreadId(messageId)
|
||||
refreshMessages()
|
||||
} catch (e: Exception) {
|
||||
context.showErrorToast(e)
|
||||
|
|
Loading…
Reference in New Issue