Divide MessageRepository into itself and Conversation Repository

This commit is contained in:
Moez Bhatti
2018-06-15 22:08:05 -04:00
parent 6e14b1ab11
commit 1b022e46e3
28 changed files with 418 additions and 351 deletions
@@ -26,11 +26,13 @@ import compat.SubscriptionManagerCompat
import dagger.android.AndroidInjection
import interactor.MarkRead
import interactor.SendMessage
import repository.ConversationRepository
import repository.MessageRepository
import javax.inject.Inject
class RemoteMessagingReceiver : BroadcastReceiver() {
@Inject lateinit var conversationRepo: ConversationRepository
@Inject lateinit var markRead: MarkRead
@Inject lateinit var messageRepo: MessageRepository
@Inject lateinit var sendMessage: SendMessage
@@ -48,7 +50,7 @@ class RemoteMessagingReceiver : BroadcastReceiver() {
val lastMessage = messageRepo.getMessages(threadId).lastOrNull()
val subId = subscriptionManager.activeSubscriptionInfoList.firstOrNull { it.subscriptionId == lastMessage?.subId }?.subscriptionId ?: -1
val addresses = messageRepo.getConversation(threadId)?.recipients?.map { it.address } ?: return
val addresses = conversationRepo.getConversation(threadId)?.recipients?.map { it.address } ?: return
val pendingRepository = goAsync()
sendMessage.execute(SendMessage.Params(subId, threadId, addresses, body)) { pendingRepository.finish() }
@@ -0,0 +1,275 @@
package repository
import android.content.ContentUris
import android.content.Context
import android.provider.Telephony
import android.telephony.PhoneNumberUtils
import compat.TelephonyCompat
import filter.ConversationFilter
import io.realm.Case
import io.realm.Realm
import io.realm.RealmResults
import io.realm.Sort
import mapper.CursorToConversation
import mapper.CursorToRecipient
import model.Contact
import model.Conversation
import model.Message
import model.SearchResult
import util.extensions.anyOf
import util.extensions.map
import util.tryOrNull
import javax.inject.Inject
class ConversationRepositoryImpl @Inject constructor(
private val context: Context,
private val conversationFilter: ConversationFilter,
private val cursorToConversation: CursorToConversation,
private val cursorToRecipient: CursorToRecipient
) : ConversationRepository {
override fun getConversations(archived: Boolean): RealmResults<Conversation> {
return Realm.getDefaultInstance()
.where(Conversation::class.java)
.notEqualTo("id", 0L)
.greaterThan("count", 0)
.equalTo("archived", archived)
.equalTo("blocked", false)
.isNotEmpty("recipients")
.sort("date", Sort.DESCENDING)
.findAllAsync()
}
override fun getConversationsSnapshot(): List<Conversation> {
val realm = Realm.getDefaultInstance()
return realm.copyFromRealm(realm.where(Conversation::class.java)
.notEqualTo("id", 0L)
.greaterThan("count", 0)
.equalTo("archived", false)
.equalTo("blocked", false)
.isNotEmpty("recipients")
.sort("date", Sort.DESCENDING)
.findAll())
}
override fun setConversationName(id: Long, name: String) {
Realm.getDefaultInstance().use { realm ->
realm.executeTransaction {
realm.where(Conversation::class.java)
.equalTo("id", id)
.findFirst()
?.name = name
}
}
}
override fun searchConversations(query: String): List<SearchResult> {
val conversations = getConversationsSnapshot()
val messagesByConversation = Realm.getDefaultInstance()
.where(Message::class.java)
.contains("body", query, Case.INSENSITIVE)
.findAll()
.groupBy { message -> message.threadId }
.filter { (threadId, _) -> conversations.firstOrNull { it.id == threadId } != null }
.map { (threadId, messages) -> Pair(conversations.first { it.id == threadId }, messages.size) }
.map { (conversation, messages) -> SearchResult(query, conversation, messages) }
.sortedByDescending { result -> result.messages }
return conversations
.filter { conversation -> conversationFilter.filter(conversation, query) }
.map { conversation -> SearchResult(query, conversation, 0) }
.plus(messagesByConversation)
}
override fun getBlockedConversations(): RealmResults<Conversation> {
return Realm.getDefaultInstance()
.where(Conversation::class.java)
.equalTo("blocked", true)
.findAllAsync()
}
override fun getConversationAsync(threadId: Long): Conversation {
return Realm.getDefaultInstance()
.where(Conversation::class.java)
.equalTo("id", threadId)
.findFirstAsync()
}
override fun getConversation(threadId: Long): Conversation? {
return Realm.getDefaultInstance()
.where(Conversation::class.java)
.equalTo("id", threadId)
.findFirst()
}
override fun getOrCreateConversation(threadId: Long): Conversation? {
return getConversation(threadId) ?: getConversationFromCp(threadId)
}
override fun getOrCreateConversation(address: String): Conversation? {
return getOrCreateConversation(listOf(address))
}
override fun getOrCreateConversation(addresses: List<String>): Conversation? {
return tryOrNull { TelephonyCompat.getOrCreateThreadId(context, addresses.toSet()) }
?.takeIf { threadId -> threadId != 0L }
?.let { threadId ->
var conversation = getConversation(threadId)
if (conversation != null) conversation = Realm.getDefaultInstance().copyFromRealm(conversation)
conversation ?: getConversationFromCp(threadId)
}
}
override fun saveDraft(threadId: Long, draft: String) {
Realm.getDefaultInstance().use { realm ->
val conversation = realm.where(Conversation::class.java)
.equalTo("id", threadId)
.findFirst()
conversation?.let {
realm.executeTransaction {
conversation.draft = draft
}
}
}
}
override fun updateConversations(vararg threadIds: Long) {
Realm.getDefaultInstance().use { realm ->
realm.refresh()
threadIds.forEach { threadId ->
val conversation = realm
.where(Conversation::class.java)
.equalTo("id", threadId)
.findFirst() ?: return
val messages = realm
.where(Message::class.java)
.equalTo("threadId", threadId)
.sort("date", Sort.DESCENDING)
.findAll()
val message = messages.firstOrNull()
realm.executeTransaction {
conversation.count = messages.size
conversation.date = message?.date ?: 0
conversation.snippet = message?.getSummary() ?: ""
conversation.read = message?.read ?: true
conversation.me = message?.isMe() ?: false
}
}
}
}
override fun markArchived(vararg threadIds: Long) {
Realm.getDefaultInstance().use { realm ->
val conversations = realm.where(Conversation::class.java)
.anyOf("id", threadIds)
.findAll()
realm.executeTransaction {
conversations.forEach { it.archived = true }
}
}
}
override fun markUnarchived(vararg threadIds: Long) {
Realm.getDefaultInstance().use { realm ->
val conversations = realm.where(Conversation::class.java)
.anyOf("id", threadIds)
.findAll()
realm.executeTransaction {
conversations.forEach { it.archived = false }
}
}
}
override fun markBlocked(vararg threadIds: Long) {
Realm.getDefaultInstance().use { realm ->
val conversations = realm.where(Conversation::class.java)
.anyOf("id", threadIds)
.findAll()
realm.executeTransaction {
conversations.forEach { it.blocked = true }
}
}
}
override fun markUnblocked(vararg threadIds: Long) {
Realm.getDefaultInstance().use { realm ->
val conversations = realm.where(Conversation::class.java)
.anyOf("id", threadIds)
.equalTo("archived", false)
.findAll()
realm.executeTransaction {
conversations.forEach { it.blocked = false }
}
}
}
override fun deleteConversations(vararg threadIds: Long) {
Realm.getDefaultInstance().use { realm ->
val conversation = realm.where(Conversation::class.java).anyOf("id", threadIds).findAll()
val messages = realm.where(Message::class.java).anyOf("threadId", threadIds).findAll()
realm.executeTransaction {
conversation.deleteAllFromRealm()
messages.deleteAllFromRealm()
}
}
threadIds.forEach { threadId ->
val uri = ContentUris.withAppendedId(Telephony.Threads.CONTENT_URI, threadId)
context.contentResolver.delete(uri, null, null)
}
}
/**
* Returns a [Conversation] from the system SMS ContentProvider, based on the [threadId]
*
* It should be noted that even if we have a valid [threadId], that does not guarantee that
* we can return a [Conversation]. On some devices, the ContentProvider won't return the
* conversation unless it contains at least 1 message
*/
private fun getConversationFromCp(threadId: Long): Conversation? {
return cursorToConversation.getConversationCursor(threadId)
?.takeIf { cursor -> cursor.moveToFirst() }
?.use { cursor ->
val conversation = cursorToConversation.map(cursor)
val realm = Realm.getDefaultInstance()
val contacts = realm.copyFromRealm(realm.where(Contact::class.java).findAll())
val recipients = conversation.recipients
.map { recipient -> recipient.id }
.map { id -> cursorToRecipient.getRecipientCursor(id) }
.mapNotNull { recipientCursor ->
// Map the recipient cursor to a list of recipients
recipientCursor?.use { recipientCursor.map { cursorToRecipient.map(recipientCursor) } }
}
.flatten()
.map { recipient ->
recipient.apply {
contact = contacts.firstOrNull {
it.numbers.any { PhoneNumberUtils.compare(recipient.address, it.address) }
}
}
}
conversation.recipients.clear()
conversation.recipients.addAll(recipients)
realm.executeTransaction { it.insertOrUpdate(conversation) }
realm.close()
conversation
}
}
}
@@ -66,120 +66,10 @@ import javax.inject.Singleton
class MessageRepositoryImpl @Inject constructor(
private val context: Context,
private val messageIds: KeyManager,
private val conversationFilter: ConversationFilter,
private val cursorToConversation: CursorToConversation,
private val cursorToRecipient: CursorToRecipient,
private val conversationRepo: ConversationRepository,
private val imageRepository: ImageRepository,
private val prefs: Preferences) : MessageRepository {
override fun getConversations(archived: Boolean): RealmResults<Conversation> {
return Realm.getDefaultInstance()
.where(Conversation::class.java)
.notEqualTo("id", 0L)
.greaterThan("count", 0)
.equalTo("archived", archived)
.equalTo("blocked", false)
.isNotEmpty("recipients")
.sort("date", Sort.DESCENDING)
.findAllAsync()
}
override fun getConversationsSnapshot(): List<Conversation> {
val realm = Realm.getDefaultInstance()
return realm.copyFromRealm(realm.where(Conversation::class.java)
.notEqualTo("id", 0L)
.greaterThan("count", 0)
.equalTo("archived", false)
.equalTo("blocked", false)
.isNotEmpty("recipients")
.sort("date", Sort.DESCENDING)
.findAll())
}
override fun setConversationName(id: Long, name: String) {
Realm.getDefaultInstance().use { realm ->
realm.executeTransaction {
realm.where(Conversation::class.java)
.equalTo("id", id)
.findFirst()
?.name = name
}
}
}
override fun searchConversations(query: String): List<SearchResult> {
val conversations = getConversationsSnapshot()
val messagesByConversation = Realm.getDefaultInstance()
.where(Message::class.java)
.contains("body", query, Case.INSENSITIVE)
.findAll()
.groupBy { message -> message.threadId }
.filter { (threadId, _) -> conversations.firstOrNull { it.id == threadId } != null }
.map { (threadId, messages) -> Pair(conversations.first { it.id == threadId }, messages.size) }
.map { (conversation, messages) -> SearchResult(query, conversation, messages) }
.sortedByDescending { result -> result.messages }
return conversations
.filter { conversation -> conversationFilter.filter(conversation, query) }
.map { conversation -> SearchResult(query, conversation, 0) }
.plus(messagesByConversation)
}
override fun getBlockedConversations(): RealmResults<Conversation> {
return Realm.getDefaultInstance()
.where(Conversation::class.java)
.equalTo("blocked", true)
.findAllAsync()
}
override fun getConversationAsync(threadId: Long): Conversation {
return Realm.getDefaultInstance()
.where(Conversation::class.java)
.equalTo("id", threadId)
.findFirstAsync()
}
override fun getConversation(threadId: Long): Conversation? {
return Realm.getDefaultInstance()
.where(Conversation::class.java)
.equalTo("id", threadId)
.findFirst()
}
override fun getOrCreateConversation(threadId: Long): Conversation? {
return getConversation(threadId) ?: getConversationFromCp(threadId)
}
override fun getOrCreateConversation(address: String): Conversation? {
return getOrCreateConversation(listOf(address))
}
override fun getOrCreateConversation(addresses: List<String>): Conversation? {
return tryOrNull { TelephonyCompat.getOrCreateThreadId(context, addresses.toSet()) }
?.takeIf { threadId -> threadId != 0L }
?.let { threadId ->
var conversation = getConversation(threadId)
if (conversation != null) conversation = Realm.getDefaultInstance().copyFromRealm(conversation)
conversation ?: getConversationFromCp(threadId)
}
}
override fun saveDraft(threadId: Long, draft: String) {
Realm.getDefaultInstance().use { realm ->
val conversation = realm.where(Conversation::class.java)
.equalTo("id", threadId)
.findFirst()
conversation?.let {
realm.executeTransaction {
conversation.draft = draft
}
}
}
}
override fun getMessages(threadId: Long, query: String): RealmResults<Message> {
return Realm.getDefaultInstance()
.where(Message::class.java)
@@ -256,84 +146,6 @@ class MessageRepositoryImpl @Inject constructor(
.findAll()
}
override fun updateConversations(vararg threadIds: Long) {
Realm.getDefaultInstance().use { realm ->
realm.refresh()
threadIds.forEach { threadId ->
val conversation = realm
.where(Conversation::class.java)
.equalTo("id", threadId)
.findFirst() ?: return
val messages = realm
.where(Message::class.java)
.equalTo("threadId", threadId)
.sort("date", Sort.DESCENDING)
.findAll()
val message = messages.firstOrNull()
realm.executeTransaction {
conversation.count = messages.size
conversation.date = message?.date ?: 0
conversation.snippet = message?.getSummary() ?: ""
conversation.read = message?.read ?: true
conversation.me = message?.isMe() ?: false
}
}
}
}
override fun markArchived(vararg threadIds: Long) {
Realm.getDefaultInstance().use { realm ->
val conversations = realm.where(Conversation::class.java)
.anyOf("id", threadIds)
.findAll()
realm.executeTransaction {
conversations.forEach { it.archived = true }
}
}
}
override fun markUnarchived(vararg threadIds: Long) {
Realm.getDefaultInstance().use { realm ->
val conversations = realm.where(Conversation::class.java)
.anyOf("id", threadIds)
.findAll()
realm.executeTransaction {
conversations.forEach { it.archived = false }
}
}
}
override fun markBlocked(vararg threadIds: Long) {
Realm.getDefaultInstance().use { realm ->
val conversations = realm.where(Conversation::class.java)
.anyOf("id", threadIds)
.findAll()
realm.executeTransaction {
conversations.forEach { it.blocked = true }
}
}
}
override fun markUnblocked(vararg threadIds: Long) {
Realm.getDefaultInstance().use { realm ->
val conversations = realm.where(Conversation::class.java)
.anyOf("id", threadIds)
.equalTo("archived", false)
.findAll()
realm.executeTransaction {
conversations.forEach { it.blocked = false }
}
}
}
override fun markAllSeen() {
val realm = Realm.getDefaultInstance()
val messages = realm.where(Message::class.java).equalTo("seen", false).findAll()
@@ -584,7 +396,7 @@ class MessageRepositoryImpl @Inject constructor(
this.subId = subId
id = messageIds.newId()
threadId = getOrCreateConversation(address)?.id ?: 0L
threadId = conversationRepo.getOrCreateConversation(address)?.id ?: 0L
contentId = uri.lastPathSegment.toLong()
boxId = Telephony.Sms.MESSAGE_TYPE_INBOX
type = "sms"
@@ -718,62 +530,4 @@ class MessageRepositoryImpl @Inject constructor(
}
}
override fun deleteConversations(vararg threadIds: Long) {
Realm.getDefaultInstance().use { realm ->
val conversation = realm.where(Conversation::class.java).anyOf("id", threadIds).findAll()
val messages = realm.where(Message::class.java).anyOf("threadId", threadIds).findAll()
realm.executeTransaction {
conversation.deleteAllFromRealm()
messages.deleteAllFromRealm()
}
}
threadIds.forEach { threadId ->
val uri = ContentUris.withAppendedId(Telephony.Threads.CONTENT_URI, threadId)
context.contentResolver.delete(uri, null, null)
}
}
/**
* Returns a [Conversation] from the system SMS ContentProvider, based on the [threadId]
*
* It should be noted that even if we have a valid [threadId], that does not guarantee that
* we can return a [Conversation]. On some devices, the ContentProvider won't return the
* conversation unless it contains at least 1 message
*/
private fun getConversationFromCp(threadId: Long): Conversation? {
return cursorToConversation.getConversationCursor(threadId)
?.takeIf { cursor -> cursor.moveToFirst() }
?.use { cursor ->
val conversation = cursorToConversation.map(cursor)
val realm = Realm.getDefaultInstance()
val contacts = realm.copyFromRealm(realm.where(Contact::class.java).findAll())
val recipients = conversation.recipients
.map { recipient -> recipient.id }
.map { id -> cursorToRecipient.getRecipientCursor(id) }
.mapNotNull { recipientCursor ->
// Map the recipient cursor to a list of recipients
recipientCursor?.use { recipientCursor.map { cursorToRecipient.map(recipientCursor) } }
}
.flatten()
.map { recipient ->
recipient.apply {
contact = contacts.firstOrNull {
it.numbers.any { PhoneNumberUtils.compare(recipient.address, it.address) }
}
}
}
conversation.recipients.clear()
conversation.recipients.addAll(recipients)
realm.executeTransaction { it.insertOrUpdate(conversation) }
realm.close()
conversation
}
}
}
@@ -47,6 +47,7 @@ import javax.inject.Singleton
@Singleton
class SyncRepositoryImpl @Inject constructor(
private val contentResolver: ContentResolver,
private val conversationRepo: ConversationRepository,
private val messageRepo: MessageRepository,
private val cursorToConversation: CursorToConversation,
private val cursorToMessage: CursorToMessage,
@@ -190,7 +191,7 @@ class SyncRepositoryImpl @Inject constructor(
cursorToMessage.map(Pair(cursor, columnsMap)).apply {
existingId?.let { this.id = it }
messageRepo.getOrCreateConversation(threadId)
conversationRepo.getOrCreateConversation(threadId)
insertOrUpdate()
}
}
@@ -24,12 +24,12 @@ import android.net.Uri
import android.telephony.TelephonyManager
import dagger.android.AndroidInjection
import interactor.SendMessage
import repository.MessageRepository
import repository.ConversationRepository
import javax.inject.Inject
class HeadlessSmsSendService : IntentService("HeadlessSmsSendService") {
@Inject lateinit var messageRepo: MessageRepository
@Inject lateinit var conversationRepo: ConversationRepository
@Inject lateinit var sendMessage: SendMessage
override fun onHandleIntent(intent: Intent?) {
@@ -39,7 +39,7 @@ class HeadlessSmsSendService : IntentService("HeadlessSmsSendService") {
intent.extras?.getString(Intent.EXTRA_TEXT)?.takeIf { it.isNotBlank() }?.let { body ->
val intentUri = intent.data
val recipients = getRecipients(intentUri).split(";")
val threadId = messageRepo.getOrCreateConversation(recipients)?.id ?: 0L
val threadId = conversationRepo.getOrCreateConversation(recipients)?.id ?: 0L
sendMessage.execute(SendMessage.Params(-1, threadId, recipients, body))
}
}