diff --git a/data/src/main/java/receiver/MarkReadReceiver.kt b/data/src/main/java/receiver/MarkReadReceiver.kt index 5ee03529..8a177c2f 100644 --- a/data/src/main/java/receiver/MarkReadReceiver.kt +++ b/data/src/main/java/receiver/MarkReadReceiver.kt @@ -33,7 +33,7 @@ class MarkReadReceiver : BroadcastReceiver() { AndroidInjection.inject(this, context) val threadId = intent.getLongExtra("threadId", 0) - markRead.execute(threadId) + markRead.execute(listOf(threadId)) } } \ No newline at end of file diff --git a/data/src/main/java/receiver/RemoteMessagingReceiver.kt b/data/src/main/java/receiver/RemoteMessagingReceiver.kt index fde382ee..a64f1154 100644 --- a/data/src/main/java/receiver/RemoteMessagingReceiver.kt +++ b/data/src/main/java/receiver/RemoteMessagingReceiver.kt @@ -44,7 +44,7 @@ class RemoteMessagingReceiver : BroadcastReceiver() { val threadId = bundle.getLong("threadId") val body = remoteInput.getCharSequence("body").toString() - markRead.execute(threadId) + markRead.execute(listOf(threadId)) val lastMessage = messageRepo.getMessages(threadId).lastOrNull() val subId = subscriptionManager.activeSubscriptionInfoList.firstOrNull { it.subscriptionId == lastMessage?.subId }?.subscriptionId ?: -1 diff --git a/data/src/main/java/repository/MessageRepositoryImpl.kt b/data/src/main/java/repository/MessageRepositoryImpl.kt index 85fb428c..2b9317d2 100644 --- a/data/src/main/java/repository/MessageRepositoryImpl.kt +++ b/data/src/main/java/repository/MessageRepositoryImpl.kt @@ -49,6 +49,7 @@ import model.Conversation import model.Message import model.MmsPart import model.SearchResult +import timber.log.Timber import util.Preferences import util.extensions.anyOf import util.extensions.map @@ -351,10 +352,10 @@ class MessageRepositoryImpl @Inject constructor( realm.close() } - override fun markRead(threadId: Long) { + override fun markRead(vararg threadIds: Long) { Realm.getDefaultInstance()?.use { realm -> val messages = realm.where(Message::class.java) - .equalTo("threadId", threadId) + .anyOf("threadId", threadIds) .beginGroup() .equalTo("read", false) .or() @@ -374,11 +375,26 @@ class MessageRepositoryImpl @Inject constructor( values.put(Telephony.Sms.SEEN, true) values.put(Telephony.Sms.READ, true) - try { - val uri = ContentUris.withAppendedId(Telephony.MmsSms.CONTENT_CONVERSATIONS_URI, threadId) - context.contentResolver.update(uri, values, "${Telephony.Sms.READ} = 0", null) - } catch (exception: Exception) { + threadIds.forEach { threadId -> + try { + val uri = ContentUris.withAppendedId(Telephony.MmsSms.CONTENT_CONVERSATIONS_URI, threadId) + context.contentResolver.update(uri, values, "${Telephony.Sms.READ} = 0", null) + } catch (exception: Exception) { + Timber.w(exception) + } + } + } + override fun markUnread(vararg threadIds: Long) { + Realm.getDefaultInstance()?.use { realm -> + val conversation = realm.where(Conversation::class.java) + .anyOf("id", threadIds) + .equalTo("read", true) + .findAll() + + realm.executeTransaction { + conversation.forEach { it.read = false } + } } } diff --git a/domain/src/main/java/interactor/MarkRead.kt b/domain/src/main/java/interactor/MarkRead.kt index 026afa5e..4d2f10db 100644 --- a/domain/src/main/java/interactor/MarkRead.kt +++ b/domain/src/main/java/interactor/MarkRead.kt @@ -27,13 +27,13 @@ class MarkRead @Inject constructor( private val messageRepo: MessageRepository, private val notificationManager: NotificationManager, private val updateBadge: UpdateBadge -) : Interactor() { +) : Interactor>() { - override fun buildObservable(params: Long): Flowable<*> { - return Flowable.just(Unit) - .doOnNext { messageRepo.markRead(params) } - .doOnNext { messageRepo.updateConversations(params) } // Update the conversation - .doOnNext { notificationManager.update(params) } + override fun buildObservable(params: List): Flowable<*> { + return Flowable.just(params.toLongArray()) + .doOnNext { threadIds -> messageRepo.markRead(*threadIds) } + .doOnNext { threadIds -> messageRepo.updateConversations(*threadIds) } // Update the conversation + .doOnNext { threadIds -> threadIds.forEach(notificationManager::update) } .flatMap { updateBadge.buildObservable(Unit) } // Update the badge } diff --git a/domain/src/main/java/interactor/MarkUnread.kt b/domain/src/main/java/interactor/MarkUnread.kt new file mode 100644 index 00000000..121f2791 --- /dev/null +++ b/domain/src/main/java/interactor/MarkUnread.kt @@ -0,0 +1,36 @@ +/* + * 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 interactor + +import io.reactivex.Flowable +import repository.MessageRepository +import javax.inject.Inject + +class MarkUnread @Inject constructor( + private val messageRepo: MessageRepository, + private val updateBadge: UpdateBadge +) : Interactor>() { + + override fun buildObservable(params: List): Flowable<*> { + return Flowable.just(params.toLongArray()) + .doOnNext { threadId -> messageRepo.markUnread(*threadId) } + .flatMap { updateBadge.buildObservable(Unit) } // Update the badge + } + +} \ No newline at end of file diff --git a/domain/src/main/java/repository/MessageRepository.kt b/domain/src/main/java/repository/MessageRepository.kt index 7ee454e1..ced0f283 100644 --- a/domain/src/main/java/repository/MessageRepository.kt +++ b/domain/src/main/java/repository/MessageRepository.kt @@ -90,7 +90,9 @@ interface MessageRepository { fun markSeen(threadId: Long) - fun markRead(threadId: Long) + fun markRead(vararg threadIds: Long) + + fun markUnread(vararg threadIds: Long) /** * Persists the SMS and attempts to send it diff --git a/presentation/src/main/java/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/feature/compose/ComposeViewModel.kt index eacf3aa0..328912df 100644 --- a/presentation/src/main/java/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/feature/compose/ComposeViewModel.kt @@ -441,7 +441,7 @@ class ComposeViewModel @Inject constructor( .mapNotNull { conversation -> conversation.takeIf { it.isValid }?.id } .debounce(200, TimeUnit.MILLISECONDS) .autoDisposable(view.scope()) - .subscribe { threadId -> markRead.execute(threadId) } + .subscribe { threadId -> markRead.execute(listOf(threadId)) } // Open the attachment options view.attachIntent diff --git a/presentation/src/main/java/feature/main/MainActivity.kt b/presentation/src/main/java/feature/main/MainActivity.kt index c8c4d720..69136420 100644 --- a/presentation/src/main/java/feature/main/MainActivity.kt +++ b/presentation/src/main/java/feature/main/MainActivity.kt @@ -169,6 +169,12 @@ class MainActivity : QkThemedActivity(), MainView { return } + val markRead = when (state.page) { + is Inbox -> state.page.markRead + is Archived -> state.page.markRead + else -> true + } + val selectedConversations = when (state.page) { is Inbox -> state.page.selected is Archived -> state.page.selected @@ -178,6 +184,8 @@ class MainActivity : QkThemedActivity(), MainView { toolbarSearch.setVisible(state.page is Inbox && state.page.selected == 0 || state.page is Searching) toolbarTitle.setVisible(toolbarSearch.visibility != View.VISIBLE) + toolbar.menu.findItem(R.id.read)?.isVisible = markRead && selectedConversations != 0 + toolbar.menu.findItem(R.id.unread)?.isVisible = !markRead && selectedConversations != 0 toolbar.menu.findItem(R.id.archive)?.isVisible = state.page is Inbox && selectedConversations != 0 toolbar.menu.findItem(R.id.unarchive)?.isVisible = state.page is Archived && selectedConversations != 0 toolbar.menu.findItem(R.id.block)?.isVisible = selectedConversations != 0 diff --git a/presentation/src/main/java/feature/main/MainState.kt b/presentation/src/main/java/feature/main/MainState.kt index e621b09b..14d0cbc9 100644 --- a/presentation/src/main/java/feature/main/MainState.kt +++ b/presentation/src/main/java/feature/main/MainState.kt @@ -38,6 +38,7 @@ sealed class MainPage data class Inbox( val showClearButton: Boolean = false, + val markRead: Boolean = false, val data: RealmResults? = null, val selected: Int = 0, val showArchivedSnackbar: Boolean = false) : MainPage() @@ -49,6 +50,7 @@ data class Searching( data class Archived( val showClearButton: Boolean = false, + val markRead: Boolean = false, val data: RealmResults? = null, val selected: Int = 0) : MainPage() diff --git a/presentation/src/main/java/feature/main/MainViewModel.kt b/presentation/src/main/java/feature/main/MainViewModel.kt index 57089903..0917de81 100644 --- a/presentation/src/main/java/feature/main/MainViewModel.kt +++ b/presentation/src/main/java/feature/main/MainViewModel.kt @@ -27,7 +27,9 @@ import interactor.DeleteConversations import interactor.MarkAllSeen import interactor.MarkArchived import interactor.MarkBlocked +import interactor.MarkRead import interactor.MarkUnarchived +import interactor.MarkUnread import interactor.MigratePreferences import interactor.SyncMessages import io.reactivex.Observable @@ -50,6 +52,8 @@ class MainViewModel @Inject constructor( private val messageRepo: MessageRepository, private val markAllSeen: MarkAllSeen, private val deleteConversations: DeleteConversations, + private val markRead: MarkRead, + private val markUnread: MarkUnread, private val markArchived: MarkArchived, private val markUnarchived: MarkUnarchived, private val markBlocked: MarkBlocked, @@ -195,6 +199,16 @@ class MainViewModel @Inject constructor( view.optionsItemIntent .withLatestFrom(view.conversationsSelectedIntent, { itemId, conversations -> when (itemId) { + R.id.read -> { + markRead.execute(conversations) + view.clearSelection() + } + + R.id.unread -> { + markUnread.execute(conversations) + view.clearSelection() + } + R.id.archive -> { markArchived.execute(conversations) view.clearSelection() @@ -228,16 +242,20 @@ class MainViewModel @Inject constructor( .subscribe { ratingManager.dismiss() } view.conversationsSelectedIntent - .map { selection -> selection.size } - .withLatestFrom(state, { selected, state -> + .withLatestFrom(state, { selection, state -> + val read = selection + .mapNotNull(messageRepo::getConversation) + .sumBy { if (it.read) -1 else 1 } >= 0 + val selected = selection.size + when (state.page) { is Inbox -> { - val page = state.page.copy(selected = selected, showClearButton = selected > 0) - newState { copy(page = page) } + val page = state.page.copy(markRead = read, selected = selected, showClearButton = selected > 0) + newState { copy(page = page.copy(markRead = read, selected = selected, showClearButton = selected > 0)) } } is Archived -> { - val page = state.page.copy(selected = selected, showClearButton = selected > 0) + val page = state.page.copy(markRead = read, selected = selected, showClearButton = selected > 0) newState { copy(page = page) } } } diff --git a/presentation/src/main/java/feature/qkreply/QkReplyViewModel.kt b/presentation/src/main/java/feature/qkreply/QkReplyViewModel.kt index 35148b1d..8b7748fb 100644 --- a/presentation/src/main/java/feature/qkreply/QkReplyViewModel.kt +++ b/presentation/src/main/java/feature/qkreply/QkReplyViewModel.kt @@ -113,7 +113,7 @@ class QkReplyViewModel @Inject constructor( .map { conversation -> conversation.id } .autoDisposable(view.scope()) .subscribe { threadId -> - markRead.execute(threadId) { newState { copy(hasError = true) } } + markRead.execute(listOf(threadId)) { newState { copy(hasError = true) } } } // Call @@ -216,7 +216,7 @@ class QkReplyViewModel @Inject constructor( threadId }) .doOnNext { threadId -> - markRead.execute(threadId) { + markRead.execute(listOf(threadId)) { newState { copy(hasError = true) } } } diff --git a/presentation/src/main/res/drawable/ic_drafts_black_24dp.xml b/presentation/src/main/res/drawable/ic_drafts_black_24dp.xml new file mode 100644 index 00000000..7751006e --- /dev/null +++ b/presentation/src/main/res/drawable/ic_drafts_black_24dp.xml @@ -0,0 +1,27 @@ + + + + diff --git a/presentation/src/main/res/drawable/ic_markunread_black_24dp.xml b/presentation/src/main/res/drawable/ic_markunread_black_24dp.xml new file mode 100644 index 00000000..dfccf44a --- /dev/null +++ b/presentation/src/main/res/drawable/ic_markunread_black_24dp.xml @@ -0,0 +1,27 @@ + + + + diff --git a/presentation/src/main/res/menu/main.xml b/presentation/src/main/res/menu/main.xml index 46744f95..e251e409 100644 --- a/presentation/src/main/res/menu/main.xml +++ b/presentation/src/main/res/menu/main.xml @@ -19,6 +19,20 @@ + + + + %d selected Clear + Mark read + Mark unread Archive Unarchive Block