#1112 - Mark conversations read/unread

This commit is contained in:
Moez Bhatti
2018-06-12 13:42:23 -04:00
parent c41d40cbd6
commit 5ede84acf5
15 changed files with 175 additions and 23 deletions
@@ -33,7 +33,7 @@ class MarkReadReceiver : BroadcastReceiver() {
AndroidInjection.inject(this, context)
val threadId = intent.getLongExtra("threadId", 0)
markRead.execute(threadId)
markRead.execute(listOf(threadId))
}
}
@@ -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
@@ -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 }
}
}
}
+6 -6
View File
@@ -27,13 +27,13 @@ class MarkRead @Inject constructor(
private val messageRepo: MessageRepository,
private val notificationManager: NotificationManager,
private val updateBadge: UpdateBadge
) : Interactor<Long>() {
) : Interactor<List<Long>>() {
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<Long>): 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
}
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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<List<Long>>() {
override fun buildObservable(params: List<Long>): Flowable<*> {
return Flowable.just(params.toLongArray())
.doOnNext { threadId -> messageRepo.markUnread(*threadId) }
.flatMap { updateBadge.buildObservable(Unit) } // Update the badge
}
}
@@ -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
@@ -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
@@ -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
@@ -38,6 +38,7 @@ sealed class MainPage
data class Inbox(
val showClearButton: Boolean = false,
val markRead: Boolean = false,
val data: RealmResults<Conversation>? = 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<Conversation>? = null,
val selected: Int = 0) : MainPage()
@@ -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) }
}
}
@@ -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) }
}
}
@@ -0,0 +1,27 @@
<!--
~ Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com>
~
~ 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 <http://www.gnu.org/licenses/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M21.99,8c0,-0.72 -0.37,-1.35 -0.94,-1.7L12,1 2.95,6.3C2.38,6.65 2,7.28 2,8v10c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2l-0.01,-10zM12,13L3.74,7.84 12,3l8.26,4.84L12,13z"/>
</vector>
@@ -0,0 +1,27 @@
<!--
~ Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com>
~
~ 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 <http://www.gnu.org/licenses/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,8l-8,5 -8,-5L4,6l8,5 8,-5v2z"/>
</vector>
+14
View File
@@ -19,6 +19,20 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/read"
android:icon="@drawable/ic_drafts_black_24dp"
android:title="@string/main_menu_read"
android:visible="false"
app:showAsAction="ifRoom" />
<item
android:id="@+id/unread"
android:icon="@drawable/ic_markunread_black_24dp"
android:title="@string/main_menu_unread"
android:visible="false"
app:showAsAction="ifRoom" />
<item
android:id="@+id/archive"
android:icon="@drawable/ic_archive_black_24dp"
@@ -38,6 +38,8 @@
<string name="main_title_selected">%d selected</string>
<string name="main_menu_clear">Clear</string>
<string name="main_menu_read">Mark read</string>
<string name="main_menu_unread">Mark unread</string>
<string name="main_menu_archive">Archive</string>
<string name="main_menu_unarchive">Unarchive</string>
<string name="main_menu_block">Block</string>