From d46193abbad30490b21094fc0380436286e366e1 Mon Sep 17 00:00:00 2001 From: moezbhatti Date: Thu, 10 Oct 2019 19:15:38 -0400 Subject: [PATCH] Show blocking reason if available --- .../blocking/CallControlBlockingClient.kt | 12 ++-- .../QKSMS/blocking/QksmsBlockingClient.kt | 4 +- .../blocking/ShouldIAnswerBlockingClient.kt | 4 +- .../moez/QKSMS/migration/QkRealmMigration.kt | 12 +++- .../repository/ConversationRepositoryImpl.kt | 17 ++++-- .../QKSMS/repository/SyncRepositoryImpl.kt | 61 ++++++++++--------- .../com/moez/QKSMS/blocking/BlockingClient.kt | 8 +-- .../com/moez/QKSMS/interactor/MarkBlocked.kt | 16 +++-- .../com/moez/QKSMS/interactor/ReceiveMms.kt | 8 +-- .../com/moez/QKSMS/interactor/ReceiveSms.kt | 8 +-- .../java/com/moez/QKSMS/model/Conversation.kt | 3 + .../repository/ConversationRepository.kt | 2 +- .../QKSMS/feature/blocking/BlockingDialog.kt | 8 +-- .../manager/BlockingManagerPresenter.kt | 2 +- .../messages/BlockedMessagesAdapter.kt | 36 ++++++++++- .../src/main/res/layout/blocked_list_item.xml | 61 ++++++++++++++++--- 16 files changed, 182 insertions(+), 80 deletions(-) diff --git a/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt b/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt index 9abd7666..ada74816 100644 --- a/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt +++ b/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt @@ -50,15 +50,15 @@ class CallControlBlockingClient @Inject constructor( override fun getAction(address: String): Single = Single.fromCallable { val uri = Uri.withAppendedPath(CallControl.LOOKUP_TEXT_URI, address) - val blocked = tryOrNull { + val blockReason = tryOrNull { context.contentResolver.query(uri, projection, null, null, null) // Query URI ?.use { cursor -> cursor.map(::LookupResult) } // Map to Result object - ?.any { result -> result.blockReason != null } // Check if any are blocked - } == true // If none are blocked or we errored at some point, return false + ?.find { result -> result.blockReason != null } // Check if any are blocked + }?.blockReason // If none are blocked or we errored at some point, return false - when (blocked) { - true -> BlockingClient.Action.BLOCK - false -> BlockingClient.Action.UNBLOCK + when (blockReason) { + null -> BlockingClient.Action.Unblock + else -> BlockingClient.Action.Block(blockReason) } } diff --git a/data/src/main/java/com/moez/QKSMS/blocking/QksmsBlockingClient.kt b/data/src/main/java/com/moez/QKSMS/blocking/QksmsBlockingClient.kt index b15e0f21..33be2b18 100644 --- a/data/src/main/java/com/moez/QKSMS/blocking/QksmsBlockingClient.kt +++ b/data/src/main/java/com/moez/QKSMS/blocking/QksmsBlockingClient.kt @@ -33,8 +33,8 @@ class QksmsBlockingClient @Inject constructor( override fun getAction(address: String): Single = Single.fromCallable { when (blockingRepo.isBlocked(address)) { - true -> BlockingClient.Action.BLOCK - false -> BlockingClient.Action.UNBLOCK + true -> BlockingClient.Action.Block() + false -> BlockingClient.Action.Unblock } } diff --git a/data/src/main/java/com/moez/QKSMS/blocking/ShouldIAnswerBlockingClient.kt b/data/src/main/java/com/moez/QKSMS/blocking/ShouldIAnswerBlockingClient.kt index 4b1ad8fc..bfbc65a0 100644 --- a/data/src/main/java/com/moez/QKSMS/blocking/ShouldIAnswerBlockingClient.kt +++ b/data/src/main/java/com/moez/QKSMS/blocking/ShouldIAnswerBlockingClient.kt @@ -59,8 +59,8 @@ class ShouldIAnswerBlockingClient @Inject constructor( return Binder(context, address).isBlocked() .map { blocked -> when (blocked) { - true -> BlockingClient.Action.BLOCK - false -> BlockingClient.Action.DO_NOTHING + true -> BlockingClient.Action.Block() + false -> BlockingClient.Action.DoNothing } } } diff --git a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt index 45e66d16..43147fea 100644 --- a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt +++ b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt @@ -25,7 +25,7 @@ import io.realm.RealmMigration class QkRealmMigration : RealmMigration { companion object { - const val SCHEMA_VERSION: Long = 6 + const val SCHEMA_VERSION: Long = 7 } override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { @@ -80,9 +80,17 @@ class QkRealmMigration : RealmMigration { version++ } + if (version == 6L) { + realm.schema.get("Conversation") + ?.addField("blockingClient", Integer::class.java) + ?.addField("blockReason", String::class.java) + + version++ + } + if (version < newVersion) { throw IllegalStateException("Migration missing from v$oldVersion to v$newVersion") } } -} \ No newline at end of file +} diff --git a/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt index 117a44da..b01aca9e 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt @@ -297,14 +297,19 @@ class ConversationRepositoryImpl @Inject constructor( } } - override fun markBlocked(vararg threadIds: Long) { + override fun markBlocked(threadIds: List, blockingClient: Int, blockReason: String?) { Realm.getDefaultInstance().use { realm -> val conversations = realm.where(Conversation::class.java) - .anyOf("id", threadIds) + .anyOf("id", threadIds.toLongArray()) + .equalTo("blocked", false) .findAll() realm.executeTransaction { - conversations.forEach { it.blocked = true } + conversations.forEach { conversation -> + conversation.blocked = true + conversation.blockingClient = blockingClient + conversation.blockReason = blockReason + } } } } @@ -316,7 +321,11 @@ class ConversationRepositoryImpl @Inject constructor( .findAll() realm.executeTransaction { - conversations.forEach { it.blocked = false } + conversations.forEach { conversation -> + conversation.blocked = false + conversation.blockingClient = null + conversation.blockReason = null + } } } } diff --git a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt index ee3be7e7..b9e1787e 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt @@ -81,7 +81,7 @@ class SyncRepositoryImpl @Inject constructor( val realm = Realm.getDefaultInstance() realm.beginTransaction() - var persistedData = realm.where(Conversation::class.java) + val persistedData = realm.copyFromRealm(realm.where(Conversation::class.java) .beginGroup() .equalTo("archived", true) .or() @@ -90,9 +90,14 @@ class SyncRepositoryImpl @Inject constructor( .equalTo("pinned", true) .or() .isNotEmpty("name") + .or() + .isNotNull("blockingClient") + .or() + .isNotEmpty("blockReason") .endGroup() - .findAll() - .map { PersistedData(it.id, it.archived, it.blocked, it.pinned, it.name) } + .findAll()) + .associateBy { conversation -> conversation.id } + .toMutableMap() realm.delete(Contact::class.java) realm.delete(Conversation::class.java) @@ -125,26 +130,26 @@ class SyncRepositoryImpl @Inject constructor( // Migrate blocked conversations from 2.7.3 val oldBlockedSenders = rxPrefs.getStringSet("pref_key_blocked_senders") - persistedData += oldBlockedSenders.get() + oldBlockedSenders.get() .map { threadIdString -> threadIdString.toLong() } - .filter { threadId -> persistedData.none { it.id == threadId } } - .map { threadId -> PersistedData(threadId, false, true, false, "") } + .filter { threadId -> !persistedData.contains(threadId) } + .forEach { threadId -> persistedData[threadId] = Conversation(id = threadId, blocked = true) } // Sync conversations conversationCursor?.use { - val conversations = conversationCursor - .map { cursor -> - progress++ - syncProgress.onNext(SyncRepository.SyncProgress.Running(max, progress, false)) - cursorToConversation.map(cursor) + val conversations = conversationCursor.map { cursor -> + progress++ + syncProgress.onNext(SyncRepository.SyncProgress.Running(max, progress, false)) + cursorToConversation.map(cursor).apply { + persistedData[id]?.let { persistedConversation -> + archived = persistedConversation.archived + blocked = persistedConversation.blocked + pinned = persistedConversation.pinned + name = persistedConversation.name + blockingClient = persistedConversation.blockingClient + blockReason = persistedConversation.blockReason } - - persistedData.forEach { data -> - val conversation = conversations.firstOrNull { conversation -> conversation.id == data.id } - conversation?.archived = data.archived - conversation?.blocked = data.blocked - conversation?.pinned = data.pinned - conversation?.name = data.name + } } realm.where(Message::class.java) @@ -152,8 +157,7 @@ class SyncRepositoryImpl @Inject constructor( .distinct("threadId") .findAll() .forEach { message -> - val conversation = conversations - .firstOrNull { conversation -> conversation.id == message.threadId } + val conversation = conversations.find { conversation -> conversation.id == message.threadId } conversation?.date = message.date conversation?.snippet = message.getSummary() conversation?.me = message.isMe() @@ -165,16 +169,15 @@ class SyncRepositoryImpl @Inject constructor( // Sync recipients recipientCursor?.use { val contacts = realm.copyToRealm(getContacts()) - val recipients = recipientCursor - .map { cursor -> - progress++ - syncProgress.onNext(SyncRepository.SyncProgress.Running(max, progress, false)) - cursorToRecipient.map(cursor).apply { - contact = contacts.firstOrNull { contact -> - contact.numbers.any { phoneNumberUtils.compare(address, it.address) } - } - } + val recipients = recipientCursor.map { cursor -> + progress++ + syncProgress.onNext(SyncRepository.SyncProgress.Running(max, progress, false)) + cursorToRecipient.map(cursor).apply { + contact = contacts.firstOrNull { contact -> + contact.numbers.any { phoneNumberUtils.compare(address, it.address) } } + } + } realm.insertOrUpdate(recipients) } diff --git a/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt b/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt index 42605f4a..97534f8f 100644 --- a/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt +++ b/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt @@ -29,14 +29,14 @@ interface BlockingClient { CANT_BLOCK } - enum class Action { - BLOCK, - UNBLOCK, + sealed class Action { + class Block(val reason: String? = null) : Action() + object Unblock : Action() // We only need these for Should I Answer, because they don't allow us to block numbers in their app directly. // This means there's a good chance that if a number is blocked in QK, it won't be blocked there, so we // shouldn't unblock the conversation in that case - DO_NOTHING + object DoNothing : Action() } /** diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/MarkBlocked.kt b/domain/src/main/java/com/moez/QKSMS/interactor/MarkBlocked.kt index f12ec7be..da6d5e76 100644 --- a/domain/src/main/java/com/moez/QKSMS/interactor/MarkBlocked.kt +++ b/domain/src/main/java/com/moez/QKSMS/interactor/MarkBlocked.kt @@ -25,12 +25,16 @@ import javax.inject.Inject class MarkBlocked @Inject constructor( private val conversationRepo: ConversationRepository, private val markRead: MarkRead -) : Interactor>() { +) : Interactor() { - override fun buildObservable(params: List): Flowable<*> { - return Flowable.just(params.toLongArray()) - .doOnNext { threadIds -> conversationRepo.markBlocked(*threadIds) } - .flatMap { markRead.buildObservable(params) } + data class Params(val threadIds: List, val blockingClient: Int, val blockReason: String?) + + override fun buildObservable(params: Params): Flowable<*> { + return Flowable.just(params) + .doOnNext { (threadIds, blockingClient, blockReason) -> + conversationRepo.markBlocked(threadIds, blockingClient, blockReason) + } + .flatMap { (threadIds) -> markRead.buildObservable(threadIds) } } -} \ No newline at end of file +} diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveMms.kt b/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveMms.kt index 2f53ce5b..0d1ff083 100644 --- a/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveMms.kt +++ b/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveMms.kt @@ -61,17 +61,17 @@ class ReceiveMms @Inject constructor( val shouldDrop = prefs.drop.get() Timber.v("block=$action, drop=$shouldDrop") - if (action == BlockingClient.Action.BLOCK && shouldDrop) { + if (action is BlockingClient.Action.Block && shouldDrop) { messageRepo.deleteMessages(message.id) return@mapNotNull null } when (action) { - BlockingClient.Action.BLOCK -> { + is BlockingClient.Action.Block -> { messageRepo.markRead(message.threadId) - conversationRepo.markBlocked(message.threadId) + conversationRepo.markBlocked(listOf(message.threadId), prefs.blockingManager.get(), action.reason) } - BlockingClient.Action.UNBLOCK -> conversationRepo.markUnblocked(message.threadId) + is BlockingClient.Action.Unblock -> conversationRepo.markUnblocked(message.threadId) else -> Unit } diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveSms.kt b/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveSms.kt index 5ca38e64..6c8254c8 100644 --- a/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveSms.kt +++ b/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveSms.kt @@ -54,7 +54,7 @@ class ReceiveSms @Inject constructor( Timber.v("block=$action, drop=$shouldDrop") // If we should drop the message, don't even save it - if (action == BlockingClient.Action.BLOCK && shouldDrop) { + if (action is BlockingClient.Action.Block && shouldDrop) { return@mapNotNull null } @@ -67,11 +67,11 @@ class ReceiveSms @Inject constructor( val message = messageRepo.insertReceivedSms(it.subId, address, body, time) when (action) { - BlockingClient.Action.BLOCK -> { + is BlockingClient.Action.Block -> { messageRepo.markRead(message.threadId) - conversationRepo.markBlocked(message.threadId) + conversationRepo.markBlocked(listOf(message.threadId), prefs.blockingManager.get(), action.reason) } - BlockingClient.Action.UNBLOCK -> conversationRepo.markUnblocked(message.threadId) + is BlockingClient.Action.Unblock -> conversationRepo.markUnblocked(message.threadId) else -> Unit } diff --git a/domain/src/main/java/com/moez/QKSMS/model/Conversation.kt b/domain/src/main/java/com/moez/QKSMS/model/Conversation.kt index dfb60780..de068b99 100644 --- a/domain/src/main/java/com/moez/QKSMS/model/Conversation.kt +++ b/domain/src/main/java/com/moez/QKSMS/model/Conversation.kt @@ -36,6 +36,9 @@ open class Conversation( var me: Boolean = false, var draft: String = "", + var blockingClient: Int? = null, + var blockReason: String? = null, + var name: String = "" // For group chats, the user is allowed to set a custom title for the conversation ) : RealmObject() { diff --git a/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt index 56785a57..7c517c10 100644 --- a/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt +++ b/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt @@ -78,7 +78,7 @@ interface ConversationRepository { fun markUnpinned(vararg threadIds: Long) - fun markBlocked(vararg threadIds: Long) + fun markBlocked(threadIds: List, blockingClient: Int, blockReason: String?) fun markUnblocked(vararg threadIds: Long) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingDialog.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingDialog.kt index e7d7c3c5..04d6daa5 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingDialog.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingDialog.kt @@ -57,7 +57,7 @@ class BlockingDialog @Inject constructor( if (blockingManager.getClientCapability() == BlockingClient.Capability.BLOCK_WITHOUT_PERMISSION) { // If we can block/unblock in the external manager, then just fire that off and exit if (block) { - markBlocked.execute(conversationIds) + markBlocked.execute(MarkBlocked.Params(conversationIds, prefs.blockingManager.get(), null)) blockingManager.block(addresses).subscribe() } else { markUnblocked.execute(conversationIds) @@ -67,7 +67,7 @@ class BlockingDialog @Inject constructor( // If all of the addresses are already in their correct state in the blocking manager, just marked the // conversations blocked and exit when (block) { - true -> markBlocked.execute(conversationIds) + true -> markBlocked.execute(MarkBlocked.Params(conversationIds, prefs.blockingManager.get(), null)) false -> markUnblocked.execute(conversationIds) } } else { @@ -77,7 +77,7 @@ class BlockingDialog @Inject constructor( } private fun allBlocked(addresses: List): Boolean = addresses.all { address -> - blockingManager.getAction(address).blockingGet() == BlockingClient.Action.BLOCK + blockingManager.getAction(address).blockingGet() is BlockingClient.Action.Block } private suspend fun showDialog( @@ -109,7 +109,7 @@ class BlockingDialog @Inject constructor( .setMessage(message) .setPositiveButton(R.string.button_continue) { _, _ -> if (block) { - markBlocked.execute(conversationIds) + markBlocked.execute(MarkBlocked.Params(conversationIds, prefs.blockingManager.get(), null)) blockingManager.block(addresses).subscribe() } else { markUnblocked.execute(conversationIds) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt index af52e92a..1ef5d91f 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt @@ -117,6 +117,6 @@ class BlockingManagerPresenter @Inject constructor( private fun getAddressesToBlock(client: BlockingClient) = conversationRepo.getBlockedConversations() .fold(listOf(), { numbers, conversation -> numbers + conversation.recipients.map { it.address } }) - .filter { number -> client.getAction(number).blockingGet() != BlockingClient.Action.BLOCK } + .filter { number -> client.getAction(number).blockingGet() !is BlockingClient.Action.Block } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesAdapter.kt index 2972dbd4..4662c9bf 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesAdapter.kt @@ -18,22 +18,38 @@ */ package com.moez.QKSMS.feature.blocking.messages +import android.content.Context +import android.graphics.Typeface import android.view.LayoutInflater import android.view.ViewGroup +import androidx.core.view.isVisible import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkRealmAdapter import com.moez.QKSMS.common.base.QkViewHolder +import com.moez.QKSMS.common.util.DateFormatter +import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.model.Conversation +import com.moez.QKSMS.util.Preferences import io.reactivex.subjects.PublishSubject import kotlinx.android.synthetic.main.blocked_list_item.view.* import javax.inject.Inject -class BlockedMessagesAdapter @Inject constructor() : QkRealmAdapter() { +class BlockedMessagesAdapter @Inject constructor( + private val context: Context, + private val dateFormatter: DateFormatter +) : QkRealmAdapter() { val clicks: PublishSubject = PublishSubject.create() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.blocked_list_item, parent, false) + + if (viewType == 0) { + view.title.setTypeface(view.title.typeface, Typeface.BOLD) + view.date.setTypeface(view.date.typeface, Typeface.BOLD) + view.date.setTextColor(view.context.resolveThemeColor(android.R.attr.textColorPrimary)) + } + return QkViewHolder(view).apply { view.setOnClickListener { val conversation = getItem(adapterPosition) ?: return@setOnClickListener @@ -60,6 +76,22 @@ class BlockedMessagesAdapter @Inject constructor() : QkRealmAdapter 1 view.title.text = conversation.getTitle() + view.date.text = dateFormatter.getConversationTimestamp(conversation.date) + + view.blocker.text = when (conversation.blockingClient) { + Preferences.BLOCKING_MANAGER_CC -> context.getString(R.string.blocking_manager_call_control_title) + Preferences.BLOCKING_MANAGER_SIA -> context.getString(R.string.blocking_manager_sia_title) + else -> null + } + + view.reason.text = conversation.blockReason + view.blocker.isVisible = view.blocker.text.isNotEmpty() + view.reason.isVisible = view.blocker.text.isNotEmpty() } -} \ No newline at end of file + override fun getItemViewType(position: Int): Int { + val conversation = getItem(position) + return if (conversation?.read == true) 1 else 0 + } + +} diff --git a/presentation/src/main/res/layout/blocked_list_item.xml b/presentation/src/main/res/layout/blocked_list_item.xml index 93456ef0..5c41be73 100644 --- a/presentation/src/main/res/layout/blocked_list_item.xml +++ b/presentation/src/main/res/layout/blocked_list_item.xml @@ -23,30 +23,73 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?attr/selectableItemBackground" - android:paddingStart="16dp" android:paddingTop="8dp" - android:paddingEnd="16dp" - android:paddingBottom="8dp"> + android:paddingBottom="8dp" + app:layout_constraintVertical_chainStyle="packed"> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - \ No newline at end of file + + + + + + +