From 66f165cccf83e0576eccd5a7a01dc20984b66c63 Mon Sep 17 00:00:00 2001 From: peanut250 <209579534+peanut250@users.noreply.github.com> Date: Mon, 7 Jul 2025 15:30:12 -0500 Subject: [PATCH] message content filter feature initial implementation --- .../moez/QKSMS/migration/QkRealmMigration.kt | 13 +- .../QKSMS/repository/ContactRepositoryImpl.kt | 10 ++ .../MessageContentFilterRepositoryImpl.kt | 86 +++++++++++++ .../com/moez/QKSMS/interactor/ReceiveMms.kt | 12 +- .../com/moez/QKSMS/interactor/ReceiveSms.kt | 12 +- .../moez/QKSMS/model/MessageContentFilter.kt | 20 +++ .../QKSMS/repository/ContactRepository.kt | 2 + .../MessageContentFilterRepository.kt | 37 ++++++ .../feature/blocking/BlockingController.kt | 8 ++ .../feature/blocking/BlockingPresenter.kt | 4 + .../QKSMS/feature/blocking/BlockingView.kt | 2 + .../filters/MessageContentFiltersAdapter.kt | 55 +++++++++ .../MessageContentFiltersController.kt | 115 ++++++++++++++++++ .../filters/MessageContentFiltersPresenter.kt | 55 +++++++++ .../filters/MessageContentFiltersState.kt | 26 ++++ .../filters/MessageContentFiltersView.kt | 33 +++++ .../com/moez/QKSMS/injection/AppComponent.kt | 2 + .../com/moez/QKSMS/injection/AppModule.kt | 5 + .../main/res/layout/blocking_controller.xml | 6 + .../message_content_filter_list_item.xml | 72 +++++++++++ .../message_content_filters_add_dialog.xml | 74 +++++++++++ .../message_content_filters_controller.xml | 59 +++++++++ presentation/src/main/res/values/strings.xml | 10 ++ 23 files changed, 715 insertions(+), 3 deletions(-) create mode 100644 data/src/main/java/com/moez/QKSMS/repository/MessageContentFilterRepositoryImpl.kt create mode 100644 domain/src/main/java/com/moez/QKSMS/model/MessageContentFilter.kt create mode 100644 domain/src/main/java/com/moez/QKSMS/repository/MessageContentFilterRepository.kt create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersAdapter.kt create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersController.kt create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersPresenter.kt create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersState.kt create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersView.kt create mode 100644 presentation/src/main/res/layout/message_content_filter_list_item.xml create mode 100644 presentation/src/main/res/layout/message_content_filters_add_dialog.xml create mode 100644 presentation/src/main/res/layout/message_content_filters_controller.xml 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 92e14040..7165b59c 100644 --- a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt +++ b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt @@ -36,7 +36,7 @@ class QkRealmMigration @Inject constructor( ) : RealmMigration { companion object { - const val SchemaVersion: Long = 11 + const val SchemaVersion: Long = 12 } @SuppressLint("ApplySharedPref") @@ -234,6 +234,17 @@ class QkRealmMigration @Inject constructor( version++ } + if (version == 11L) { + realm.schema.create("MessageContentFilter") + .addField("id", Long::class.java, FieldAttribute.PRIMARY_KEY, FieldAttribute.REQUIRED) + .addField("value", String::class.java, FieldAttribute.REQUIRED) + .addField("caseSensitive", Boolean::class.java, FieldAttribute.REQUIRED) + .addField("isRegex", Boolean::class.java, FieldAttribute.REQUIRED) + .addField("includeContacts", Boolean::class.java, FieldAttribute.REQUIRED) + + version++ + } + check(version >= newVersion) { "Migration missing from v$oldVersion to v$newVersion" } } diff --git a/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt index 2d48fbb0..ed70ecd1 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/ContactRepositoryImpl.kt @@ -171,4 +171,14 @@ class ContactRepositoryImpl @Inject constructor( } } + override fun isContact(address: String): Boolean { + val uri : Uri + if (address.contains('@')) { + uri = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, Uri.encode(address)) + } else { + uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(address)) + } + return context.contentResolver.query(uri, arrayOf(BaseColumns._ID), null, null, null)?.count!! > 0 + } + } diff --git a/data/src/main/java/com/moez/QKSMS/repository/MessageContentFilterRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/MessageContentFilterRepositoryImpl.kt new file mode 100644 index 00000000..869c5506 --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/repository/MessageContentFilterRepositoryImpl.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package dev.octoshrimpy.quik.repository + +import dev.octoshrimpy.quik.model.MessageContentFilter +import dev.octoshrimpy.quik.model.MessageContentFilterData +import io.realm.Realm +import io.realm.RealmResults +import javax.inject.Inject + +class MessageContentFilterRepositoryImpl @Inject constructor() : MessageContentFilterRepository { + override fun createFilter(data: MessageContentFilterData) { + Realm.getDefaultInstance().use { realm -> + realm.refresh() + val maxId = realm.where(MessageContentFilter::class.java) + .max("id")?.toLong() ?: -1 + + realm.executeTransaction { + realm.insert(MessageContentFilter(maxId + 1, data.value, data.caseSensitive, data.isRegex, data.includeContacts)) + } + } + } + + override fun getMessageContentFilters(): RealmResults { + return Realm.getDefaultInstance() + .where(MessageContentFilter::class.java) + .findAllAsync() + } + + override fun getMessageContentFilter(id: Long): MessageContentFilter? { + return Realm.getDefaultInstance() + .where(MessageContentFilter::class.java) + .equalTo("id", id) + .findFirst() + } + + override fun isBlocked(messageBody: String, address: String, contactsRepo: ContactRepository): Boolean { + val isContact = contactsRepo.isContact(address) + + return Realm.getDefaultInstance().use { realm -> + realm.where(MessageContentFilter::class.java) + .findAll() + .any { filter -> + if (isContact && !filter.includeContacts) { + false + } else if (filter.isRegex) { + Regex(filter.value).matches(messageBody) + } else if (filter.caseSensitive) { + val regexp = ".*\\b" + Regex.escape(filter.value) + "\\b.*" + Regex(regexp).matches(messageBody) + } else { + val regexp = ".*\\b" + Regex.escape(filter.value.lowercase()) + "\\b.*" + Regex(regexp).matches(messageBody.lowercase()) + } + } + } + } + + override fun removeFilter(id: Long) { + Realm.getDefaultInstance().use { realm -> + realm.executeTransaction { + realm.where(MessageContentFilter::class.java) + .equalTo("id", id) + .findAll() + .deleteAllFromRealm() + } + } + } + +} 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 10a533f4..b3c01680 100644 --- a/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveMms.kt +++ b/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveMms.kt @@ -23,7 +23,9 @@ import dev.octoshrimpy.quik.blocking.BlockingClient import dev.octoshrimpy.quik.extensions.mapNotNull import dev.octoshrimpy.quik.manager.ActiveConversationManager import dev.octoshrimpy.quik.manager.NotificationManager +import dev.octoshrimpy.quik.repository.ContactRepository import dev.octoshrimpy.quik.repository.ConversationRepository +import dev.octoshrimpy.quik.repository.MessageContentFilterRepository import dev.octoshrimpy.quik.repository.MessageRepository import dev.octoshrimpy.quik.repository.SyncRepository import dev.octoshrimpy.quik.util.Preferences @@ -39,7 +41,9 @@ class ReceiveMms @Inject constructor( private val syncManager: SyncRepository, private val messageRepo: MessageRepository, private val notificationManager: NotificationManager, - private val updateBadge: UpdateBadge + private val updateBadge: UpdateBadge, + private val filterRepo: MessageContentFilterRepository, + private val contactsRepo: ContactRepository ) : Interactor() { override fun buildObservable(params: Uri): Flowable<*> { @@ -75,6 +79,12 @@ class ReceiveMms @Inject constructor( else -> Unit } + if (filterRepo.isBlocked(message.getText(), message.address, contactsRepo)) { + Timber.v("message dropped based on content filters") + messageRepo.deleteMessages(listOf(message.id)) + return@mapNotNull null + } + message } .doOnNext { message -> 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 667dc268..d6837f9c 100644 --- a/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveSms.kt +++ b/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveSms.kt @@ -22,7 +22,9 @@ import dev.octoshrimpy.quik.blocking.BlockingClient import dev.octoshrimpy.quik.extensions.mapNotNull import dev.octoshrimpy.quik.manager.NotificationManager import dev.octoshrimpy.quik.manager.ShortcutManager +import dev.octoshrimpy.quik.repository.ContactRepository import dev.octoshrimpy.quik.repository.ConversationRepository +import dev.octoshrimpy.quik.repository.MessageContentFilterRepository import dev.octoshrimpy.quik.repository.MessageRepository import dev.octoshrimpy.quik.util.Preferences import io.reactivex.Flowable @@ -36,7 +38,9 @@ class ReceiveSms @Inject constructor( private val messageRepo: MessageRepository, private val notificationManager: NotificationManager, private val updateBadge: UpdateBadge, - private val shortcutManager: ShortcutManager + private val shortcutManager: ShortcutManager, + private val filterRepo: MessageContentFilterRepository, + private val contactsRepo: ContactRepository ) : Interactor() { override fun buildObservable(params: Long): Flowable<*> { @@ -69,6 +73,12 @@ class ReceiveSms @Inject constructor( } } + if (filterRepo.isBlocked(it.getText(), it.address, contactsRepo)) { + Timber.v("message dropped based on content filters") + messageRepo.deleteMessages(listOf(it.id)) + return@mapNotNull null + } + // update and fetch conversation conversationRepo.updateConversations(it.threadId) conversationRepo.getOrCreateConversation(it.threadId) diff --git a/domain/src/main/java/com/moez/QKSMS/model/MessageContentFilter.kt b/domain/src/main/java/com/moez/QKSMS/model/MessageContentFilter.kt new file mode 100644 index 00000000..c0560617 --- /dev/null +++ b/domain/src/main/java/com/moez/QKSMS/model/MessageContentFilter.kt @@ -0,0 +1,20 @@ +package dev.octoshrimpy.quik.model + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey + +open class MessageContentFilter( + @PrimaryKey var id: Long = 0, + + var value: String = "", + var caseSensitive: Boolean = false, + var isRegex: Boolean = false, + var includeContacts: Boolean = false +) : RealmObject() + +data class MessageContentFilterData( + var value: String = "", + var caseSensitive: Boolean = false, + var isRegex: Boolean = false, + var includeContacts: Boolean = false +) diff --git a/domain/src/main/java/com/moez/QKSMS/repository/ContactRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/ContactRepository.kt index 88341465..446dbebb 100644 --- a/domain/src/main/java/com/moez/QKSMS/repository/ContactRepository.kt +++ b/domain/src/main/java/com/moez/QKSMS/repository/ContactRepository.kt @@ -41,4 +41,6 @@ interface ContactRepository { fun setDefaultPhoneNumber(lookupKey: String, phoneNumberId: Long) + fun isContact(address: String): Boolean + } diff --git a/domain/src/main/java/com/moez/QKSMS/repository/MessageContentFilterRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/MessageContentFilterRepository.kt new file mode 100644 index 00000000..031e89d9 --- /dev/null +++ b/domain/src/main/java/com/moez/QKSMS/repository/MessageContentFilterRepository.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package dev.octoshrimpy.quik.repository + +import dev.octoshrimpy.quik.model.MessageContentFilter +import dev.octoshrimpy.quik.model.MessageContentFilterData +import io.realm.RealmResults + +interface MessageContentFilterRepository { + + fun createFilter(data: MessageContentFilterData) + + fun getMessageContentFilters(): RealmResults + + fun getMessageContentFilter(id: Long): MessageContentFilter? + + fun isBlocked(messageBody: String, address: String, contactsRepo: ContactRepository): Boolean + + fun removeFilter(id: Long) + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt index e29f62ed..40bf0207 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt @@ -29,6 +29,7 @@ import dev.octoshrimpy.quik.common.util.extensions.animateLayoutChanges import dev.octoshrimpy.quik.feature.blocking.manager.BlockingManagerController import dev.octoshrimpy.quik.feature.blocking.messages.BlockedMessagesController import dev.octoshrimpy.quik.feature.blocking.numbers.BlockedNumbersController +import dev.octoshrimpy.quik.feature.blocking.filters.MessageContentFiltersController import dev.octoshrimpy.quik.injection.appComponent import kotlinx.android.synthetic.main.blocking_controller.* import kotlinx.android.synthetic.main.settings_switch_widget.view.* @@ -38,6 +39,7 @@ class BlockingController : QkController { val blockingManagerIntent: Observable<*> val blockedNumbersIntent: Observable<*> + val messageContentFiltersIntent: Observable<*> val blockedMessagesIntent: Observable<*> val dropClickedIntent: Observable<*> fun openBlockingManager() fun openBlockedNumbers() + fun openMessageContentFilters() fun openBlockedMessages() } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersAdapter.kt new file mode 100644 index 00000000..f6b6818d --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersAdapter.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package dev.octoshrimpy.quik.feature.blocking.filters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import dev.octoshrimpy.quik.R +import dev.octoshrimpy.quik.common.base.QkRealmAdapter +import dev.octoshrimpy.quik.common.base.QkViewHolder +import dev.octoshrimpy.quik.model.MessageContentFilter +import io.reactivex.subjects.PublishSubject +import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.message_content_filter_list_item.* +import kotlinx.android.synthetic.main.message_content_filter_list_item.view.* + +class MessageContentFiltersAdapter : QkRealmAdapter() { + + val removeMessageContentFilter: Subject = PublishSubject.create() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.message_content_filter_list_item, parent, false) + return QkViewHolder(view).apply { + containerView.removeFilter.setOnClickListener { + val filter = getItem(adapterPosition) ?: return@setOnClickListener + removeMessageContentFilter.onNext(filter.id) + } + } + } + + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + val item = getItem(position)!! + holder.caseIcon.visibility = if (item.caseSensitive) View.VISIBLE else View.GONE + holder.regexIcon.visibility = if (item.isRegex) View.VISIBLE else View.GONE + holder.contactsIcon.visibility = if (item.includeContacts) View.VISIBLE else View.GONE + holder.filter.text = item.value + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersController.kt new file mode 100644 index 00000000..61eb2079 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersController.kt @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package dev.octoshrimpy.quik.feature.blocking.filters + +import android.view.LayoutInflater +import android.view.View +import androidx.appcompat.app.AlertDialog +import com.jakewharton.rxbinding2.view.clicks +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable +import dev.octoshrimpy.quik.R +import dev.octoshrimpy.quik.common.base.QkController +import dev.octoshrimpy.quik.common.util.Colors +import dev.octoshrimpy.quik.common.util.extensions.setBackgroundTint +import dev.octoshrimpy.quik.common.util.extensions.setTint +import dev.octoshrimpy.quik.common.widget.PreferenceView +import dev.octoshrimpy.quik.injection.appComponent +import dev.octoshrimpy.quik.model.MessageContentFilterData +import io.reactivex.Observable +import io.reactivex.subjects.PublishSubject +import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.message_content_filters_add_dialog.view.* +import kotlinx.android.synthetic.main.message_content_filters_controller.* +import kotlinx.android.synthetic.main.settings_switch_widget.view.* +import javax.inject.Inject + +class MessageContentFiltersController : QkController(), MessageContentFiltersView { + + @Inject override lateinit var presenter: MessageContentFiltersPresenter + @Inject lateinit var colors: Colors + + private val adapter = MessageContentFiltersAdapter() + private val saveFilterSubject: Subject = PublishSubject.create() + + init { + appComponent.inject(this) + retainViewMode = RetainViewMode.RETAIN_DETACH + layoutRes = R.layout.message_content_filters_controller + } + + override fun onAttach(view: View) { + super.onAttach(view) + presenter.bindIntents(this) + setTitle(R.string.message_content_filters_title) + showBackButton(true) + } + + override fun onViewCreated() { + super.onViewCreated() + add.setBackgroundTint(colors.theme().theme) + add.setTint(colors.theme().textPrimary) + adapter.emptyView = empty + filters.adapter = adapter + } + + override fun render(state: MessageContentFiltersState) { + adapter.updateData(state.filters) + } + + override fun removeFilter(): Observable = adapter.removeMessageContentFilter + override fun addFilter(): Observable<*> = add.clicks() + override fun saveFilter(): Observable = saveFilterSubject + + override fun showAddDialog() { + val layout = LayoutInflater.from(activity).inflate(R.layout.message_content_filters_add_dialog, null) + + (0 until layout.add_dialog.childCount) + .map { index -> layout.add_dialog.getChildAt(index) } + .mapNotNull { view -> view as? PreferenceView } + .map { preference -> preference.clicks().map { preference } } + .let { Observable.merge(it) } + .autoDisposable(scope()) + .subscribe { + it.checkbox.isChecked = !it.checkbox.isChecked + layout.caseSensitivity.isEnabled = !layout.regexp.checkbox.isChecked + } + + val dialog = AlertDialog.Builder(activity!!) + .setView(layout) + .setPositiveButton(R.string.message_content_filters_dialog_create) { _, _ -> + var text = layout.input.text.toString(); + if (!text.isBlank()) { + if (!layout.regexp.checkbox.isChecked) text = text.trim() + saveFilterSubject.onNext( + MessageContentFilterData( + text, + layout.caseSensitivity.checkbox.isChecked && !layout.regexp.checkbox.isChecked, + layout.regexp.checkbox.isChecked, + layout.contacts.checkbox.isChecked + ) + ) + } + } + .setNegativeButton(R.string.button_cancel) { _, _ -> } + dialog.show() + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersPresenter.kt new file mode 100644 index 00000000..a4860f4a --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersPresenter.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package dev.octoshrimpy.quik.feature.blocking.filters + +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable +import dev.octoshrimpy.quik.common.base.QkPresenter +import dev.octoshrimpy.quik.repository.MessageContentFilterRepository +import io.reactivex.schedulers.Schedulers +import javax.inject.Inject + +class MessageContentFiltersPresenter @Inject constructor( + private val filterRepo: MessageContentFilterRepository, +) : QkPresenter( + MessageContentFiltersState(filters = filterRepo.getMessageContentFilters()) +) { + + override fun bindIntents(view: MessageContentFiltersView) { + super.bindIntents(view) + + view.removeFilter() + .observeOn(Schedulers.io()) + .doOnNext(filterRepo::removeFilter) + .subscribeOn(Schedulers.io()) + .autoDisposable(view.scope()) + .subscribe() + + view.addFilter() + .autoDisposable(view.scope()) + .subscribe { view.showAddDialog() } + + view.saveFilter() + .observeOn(Schedulers.io()) + .subscribeOn(Schedulers.io()) + .autoDisposable(view.scope()) + .subscribe { filterData -> filterRepo.createFilter(filterData) } + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersState.kt new file mode 100644 index 00000000..a3c44663 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersState.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package dev.octoshrimpy.quik.feature.blocking.filters + +import dev.octoshrimpy.quik.model.MessageContentFilter +import io.realm.RealmResults + +data class MessageContentFiltersState( + val filters: RealmResults? = null +) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersView.kt new file mode 100644 index 00000000..e3dda14b --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/filters/MessageContentFiltersView.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package dev.octoshrimpy.quik.feature.blocking.filters + +import dev.octoshrimpy.quik.common.base.QkViewContract +import dev.octoshrimpy.quik.model.MessageContentFilterData +import io.reactivex.Observable + +interface MessageContentFiltersView : QkViewContract { + + fun removeFilter(): Observable + fun addFilter(): Observable<*> + fun saveFilter(): Observable + + fun showAddDialog() + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/AppComponent.kt b/presentation/src/main/java/com/moez/QKSMS/injection/AppComponent.kt index 2fe2a806..6c96873b 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/AppComponent.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/AppComponent.kt @@ -32,6 +32,7 @@ import dev.octoshrimpy.quik.common.widget.QkTextView import dev.octoshrimpy.quik.common.widget.RadioPreferenceView import dev.octoshrimpy.quik.feature.backup.BackupController import dev.octoshrimpy.quik.feature.blocking.BlockingController +import dev.octoshrimpy.quik.feature.blocking.filters.MessageContentFiltersController import dev.octoshrimpy.quik.feature.blocking.manager.BlockingManagerController import dev.octoshrimpy.quik.feature.blocking.messages.BlockedMessagesController import dev.octoshrimpy.quik.feature.blocking.numbers.BlockedNumbersController @@ -65,6 +66,7 @@ interface AppComponent { fun inject(controller: BackupController) fun inject(controller: BlockedMessagesController) fun inject(controller: BlockedNumbersController) + fun inject(controller: MessageContentFiltersController) fun inject(controller: BlockingController) fun inject(controller: BlockingManagerController) fun inject(controller: SettingsController) diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt index 84cc8b71..8a1d35b2 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt @@ -81,6 +81,8 @@ import dev.octoshrimpy.quik.repository.ContactRepository import dev.octoshrimpy.quik.repository.ContactRepositoryImpl import dev.octoshrimpy.quik.repository.ConversationRepository import dev.octoshrimpy.quik.repository.ConversationRepositoryImpl +import dev.octoshrimpy.quik.repository.MessageContentFilterRepository +import dev.octoshrimpy.quik.repository.MessageContentFilterRepositoryImpl import dev.octoshrimpy.quik.repository.MessageRepository import dev.octoshrimpy.quik.repository.MessageRepositoryImpl import dev.octoshrimpy.quik.repository.ScheduledMessageRepository @@ -199,6 +201,9 @@ class AppModule(private var application: Application) { @Provides fun provideBlockingRepository(repository: BlockingRepositoryImpl): BlockingRepository = repository + @Provides + fun provideMessageContentFilterRepository(repository: MessageContentFilterRepositoryImpl): MessageContentFilterRepository = repository + @Provides fun provideContactRepository(repository: ContactRepositoryImpl): ContactRepository = repository diff --git a/presentation/src/main/res/layout/blocking_controller.xml b/presentation/src/main/res/layout/blocking_controller.xml index 24812b76..ef796e76 100644 --- a/presentation/src/main/res/layout/blocking_controller.xml +++ b/presentation/src/main/res/layout/blocking_controller.xml @@ -46,6 +46,12 @@ android:layout_height="wrap_content" app:title="@string/blocked_numbers_title" /> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/message_content_filters_add_dialog.xml b/presentation/src/main/res/layout/message_content_filters_add_dialog.xml new file mode 100644 index 00000000..90394f2e --- /dev/null +++ b/presentation/src/main/res/layout/message_content_filters_add_dialog.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + diff --git a/presentation/src/main/res/layout/message_content_filters_controller.xml b/presentation/src/main/res/layout/message_content_filters_controller.xml new file mode 100644 index 00000000..e294eb09 --- /dev/null +++ b/presentation/src/main/res/layout/message_content_filters_controller.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index fcf36f16..ea0d5397 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -360,6 +360,16 @@ Phone number Block + Message content filters + Your message content filters will appear here + Create a new filter + Drop messages matching + Word, phrase or regular expression + Case sensitive + Regular expression + Include contacts + Create + Blocked messages Your blocked messages will appear here