forked from upstream-mirrors/quik
Merge pull request #213 from gavine99/unread_at_top
make unread conversations float at the top of conversation lists
This commit is contained in:
@@ -23,17 +23,45 @@ import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.klinker.android.send_message.BroadcastUtils
|
||||
import dev.octoshrimpy.quik.util.Preferences
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import javax.inject.Inject
|
||||
|
||||
class WidgetManagerImpl @Inject constructor(private val context: Context) : WidgetManager {
|
||||
class WidgetManagerImpl @Inject constructor(
|
||||
private val context: Context,
|
||||
private val prefs: Preferences
|
||||
) : WidgetManager {
|
||||
|
||||
override fun updateUnreadCount() {
|
||||
BroadcastUtils.sendExplicitBroadcast(context, Intent(), WidgetManager.ACTION_NOTIFY_DATASET_CHANGED)
|
||||
companion object {
|
||||
private var staticUnreadAtTopPrefsDisposable = AtomicReference<Disposable>(null)
|
||||
|
||||
fun sendDatasetChanged(context: Context) {
|
||||
BroadcastUtils.sendExplicitBroadcast(context, Intent(), WidgetManager.ACTION_NOTIFY_DATASET_CHANGED)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
// this class is always instantiated by it's factory with the application context so we can capture that context value below
|
||||
// when unreadAtTop preference changes, send broadcast to widgets to update themselves
|
||||
if (staticUnreadAtTopPrefsDisposable.get() === null)
|
||||
staticUnreadAtTopPrefsDisposable.set(prefs.unreadAtTop.asObservable()
|
||||
.skip(1)
|
||||
.debounce(400, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { sendDatasetChanged(context) }
|
||||
.subscribe())
|
||||
}
|
||||
|
||||
override fun sendDatasetChanged() {
|
||||
sendDatasetChanged(context)
|
||||
}
|
||||
|
||||
override fun updateTheme() {
|
||||
val ids = AppWidgetManager.getInstance(context)
|
||||
.getAppWidgetIds(ComponentName("dev.octoshrimpy.quik", "dev.octoshrimpy.quik.feature.widget.WidgetProvider"))
|
||||
.getAppWidgetIds(ComponentName(context.packageName, "dev.octoshrimpy.quik.feature.widget.WidgetProvider"))
|
||||
|
||||
val intent = Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
|
||||
|
||||
|
||||
@@ -56,7 +56,15 @@ class ConversationRepositoryImpl @Inject constructor(
|
||||
private val phoneNumberUtils: PhoneNumberUtils
|
||||
) : ConversationRepository {
|
||||
|
||||
override fun getConversations(archived: Boolean): RealmResults<Conversation> {
|
||||
override fun getConversations(unreadAtTop: Boolean, archived: Boolean): RealmResults<Conversation> {
|
||||
val sortOrder: MutableList<String> = arrayListOf("pinned", "draft", "lastMessage.date")
|
||||
val sortDirections: MutableList<Sort> = arrayListOf(Sort.DESCENDING, Sort.DESCENDING, Sort.DESCENDING)
|
||||
|
||||
if (unreadAtTop) {
|
||||
sortOrder.add(0, "lastMessage.read")
|
||||
sortDirections.add(0, Sort.ASCENDING)
|
||||
}
|
||||
|
||||
return Realm.getDefaultInstance()
|
||||
.where(Conversation::class.java)
|
||||
.notEqualTo("id", 0L)
|
||||
@@ -69,13 +77,21 @@ class ConversationRepositoryImpl @Inject constructor(
|
||||
.isNotEmpty("draft")
|
||||
.endGroup()
|
||||
.sort(
|
||||
arrayOf("pinned", "draft", "lastMessage.date"),
|
||||
arrayOf(Sort.DESCENDING, Sort.DESCENDING, Sort.DESCENDING)
|
||||
sortOrder.toTypedArray(),
|
||||
sortDirections.toTypedArray()
|
||||
)
|
||||
.findAllAsync()
|
||||
}
|
||||
|
||||
override fun getConversationsSnapshot(): List<Conversation> {
|
||||
override fun getConversationsSnapshot(unreadAtTop: Boolean): List<Conversation> {
|
||||
val sortOrder: MutableList<String> = arrayListOf("pinned", "draft", "lastMessage.date")
|
||||
val sortDirections: MutableList<Sort> = arrayListOf(Sort.DESCENDING, Sort.DESCENDING, Sort.DESCENDING)
|
||||
|
||||
if (unreadAtTop) {
|
||||
sortOrder.add(0, "lastMessage.read")
|
||||
sortDirections.add(0, Sort.ASCENDING)
|
||||
}
|
||||
|
||||
return Realm.getDefaultInstance().use { realm ->
|
||||
realm.refresh()
|
||||
realm.copyFromRealm(realm.where(Conversation::class.java)
|
||||
@@ -89,8 +105,8 @@ class ConversationRepositoryImpl @Inject constructor(
|
||||
.isNotEmpty("draft")
|
||||
.endGroup()
|
||||
.sort(
|
||||
arrayOf("pinned", "draft", "lastMessage.date"),
|
||||
arrayOf(Sort.DESCENDING, Sort.DESCENDING, Sort.DESCENDING)
|
||||
sortOrder.toTypedArray(),
|
||||
sortDirections.toTypedArray()
|
||||
)
|
||||
.findAll())
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class UpdateBadge @Inject constructor(
|
||||
override fun buildObservable(params: Unit): Flowable<*> {
|
||||
return Flowable.just(params)
|
||||
.doOnNext { shortcutManager.updateBadge() }
|
||||
.doOnNext { widgetManager.updateUnreadCount() }
|
||||
.doOnNext { widgetManager.sendDatasetChanged() }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,7 +24,7 @@ interface WidgetManager {
|
||||
const val ACTION_NOTIFY_DATASET_CHANGED = "dev.octoshrimpy.quik.intent.action.ACTION_NOTIFY_DATASET_CHANGED"
|
||||
}
|
||||
|
||||
fun updateUnreadCount()
|
||||
fun sendDatasetChanged()
|
||||
|
||||
fun updateTheme()
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ import io.realm.RealmResults
|
||||
|
||||
interface ConversationRepository {
|
||||
|
||||
fun getConversations(archived: Boolean = false): RealmResults<Conversation>
|
||||
fun getConversations(unreadAtTop: Boolean, archived: Boolean = false): RealmResults<Conversation>
|
||||
|
||||
fun getConversationsSnapshot(): List<Conversation>
|
||||
fun getConversationsSnapshot(unreadAtTop: Boolean): List<Conversation>
|
||||
|
||||
/**
|
||||
* Returns the top conversations that were active in the last week
|
||||
|
||||
@@ -123,6 +123,7 @@ class Preferences @Inject constructor(
|
||||
val longAsMms = rxPrefs.getBoolean("longAsMms", false)
|
||||
val mmsSize = rxPrefs.getInteger("mmsSize", 300)
|
||||
val logging = rxPrefs.getBoolean("logging", false)
|
||||
val unreadAtTop = rxPrefs.getBoolean("unreadAtTop", false)
|
||||
|
||||
init {
|
||||
// Migrate from old night mode preference to new one, now that we support android Q night mode
|
||||
|
||||
@@ -136,7 +136,7 @@ class ComposeViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
// Otherwise, we'll monitor the conversations until our expected conversation is created
|
||||
conversationRepo.getConversations().asObservable()
|
||||
conversationRepo.getConversations(prefs.unreadAtTop.get()).asObservable()
|
||||
.filter { it.isLoaded }
|
||||
.observeOn(Schedulers.io())
|
||||
.map { conversationRepo.getOrCreateConversation(addresses)?.id ?: 0 }
|
||||
|
||||
@@ -83,7 +83,7 @@ class MainViewModel @Inject constructor(
|
||||
private val ratingManager: RatingManager,
|
||||
private val syncContacts: SyncContacts,
|
||||
private val syncMessages: SyncMessages
|
||||
) : QkViewModel<MainView, MainState>(MainState(page = Inbox(data = conversationRepo.getConversations()))) {
|
||||
) : QkViewModel<MainView, MainState>(MainState(page = Inbox(data = conversationRepo.getConversations(prefs.unreadAtTop.get())))) {
|
||||
|
||||
init {
|
||||
disposables += deleteConversations
|
||||
@@ -140,6 +140,25 @@ class MainViewModel @Inject constructor(
|
||||
!permissionManager.hasReadSms() || !permissionManager.hasContacts() -> view.requestPermissions()
|
||||
}
|
||||
|
||||
|
||||
// when unreadAtTop preference changes, reload the model view data to refresh view
|
||||
prefs.unreadAtTop.asObservable()
|
||||
.skip(1)
|
||||
.debounce(400, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.withLatestFrom(state) { _, state ->
|
||||
if (state.page is Inbox)
|
||||
newState {
|
||||
copy(page = Inbox(data = conversationRepo.getConversations(prefs.unreadAtTop.get())))
|
||||
}
|
||||
else if (state.page is Archived)
|
||||
newState {
|
||||
copy(page = Inbox(data = conversationRepo.getConversations(prefs.unreadAtTop.get(), true)))
|
||||
}
|
||||
}
|
||||
.autoDisposable(view.scope())
|
||||
.subscribe()
|
||||
|
||||
// If the default SMS state changes, reflect it in the State
|
||||
view.activityResumedIntent
|
||||
.filter { resumed -> resumed }
|
||||
@@ -226,7 +245,7 @@ class MainViewModel @Inject constructor(
|
||||
.map { query -> query.trim() }
|
||||
.withLatestFrom(state) { query, state ->
|
||||
if (query.isEmpty() && state.page is Searching) {
|
||||
newState { copy(page = Inbox(data = conversationRepo.getConversations())) }
|
||||
newState { copy(page = Inbox(data = conversationRepo.getConversations(prefs.unreadAtTop.get()))) }
|
||||
}
|
||||
query
|
||||
}
|
||||
@@ -288,7 +307,7 @@ class MainViewModel @Inject constructor(
|
||||
state.page is Inbox && state.page.selected > 0 -> view.clearSelection()
|
||||
state.page is Archived && state.page.selected > 0 -> view.clearSelection()
|
||||
state.page !is Inbox -> {
|
||||
newState { copy(page = Inbox(data = conversationRepo.getConversations())) }
|
||||
newState { copy(page = Inbox(data = conversationRepo.getConversations(prefs.unreadAtTop.get()))) }
|
||||
}
|
||||
else -> newState { copy(hasError = true) }
|
||||
}
|
||||
@@ -306,8 +325,8 @@ class MainViewModel @Inject constructor(
|
||||
.distinctUntilChanged()
|
||||
.doOnNext { drawerItem ->
|
||||
when (drawerItem) {
|
||||
NavItem.INBOX -> newState { copy(page = Inbox(data = conversationRepo.getConversations())) }
|
||||
NavItem.ARCHIVED -> newState { copy(page = Archived(data = conversationRepo.getConversations(true))) }
|
||||
NavItem.INBOX -> newState { copy(page = Inbox(data = conversationRepo.getConversations(prefs.unreadAtTop.get()))) }
|
||||
NavItem.ARCHIVED -> newState { copy(page = Archived(data = conversationRepo.getConversations(prefs.unreadAtTop.get(), true))) }
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +169,8 @@ class SettingsController : QkController<SettingsView, SettingsState, SettingsPre
|
||||
|
||||
delivery.checkbox.isChecked = state.deliveryEnabled
|
||||
|
||||
unreadAtTop.checkbox.isChecked = state.unreadAtTopEnabled
|
||||
|
||||
signature.summary = state.signature.takeIf { it.isNotBlank() }
|
||||
?: context.getString(R.string.settings_signature_summary)
|
||||
|
||||
|
||||
@@ -98,7 +98,10 @@ class SettingsPresenter @Inject constructor(
|
||||
.subscribe { id -> newState { copy(sendDelaySummary = delayedSendingLabels[id], sendDelayId = id) } }
|
||||
|
||||
disposables += prefs.delivery.asObservable()
|
||||
.subscribe { enabled -> newState { copy(deliveryEnabled = enabled) } }
|
||||
.subscribe { enabled -> newState { copy(deliveryEnabled = enabled) } }
|
||||
|
||||
disposables += prefs.unreadAtTop.asObservable()
|
||||
.subscribe { enabled -> newState { copy(unreadAtTopEnabled = enabled) } }
|
||||
|
||||
disposables += prefs.signature.asObservable()
|
||||
.subscribe { signature -> newState { copy(signature = signature) } }
|
||||
@@ -178,6 +181,8 @@ class SettingsPresenter @Inject constructor(
|
||||
|
||||
R.id.delivery -> prefs.delivery.set(!prefs.delivery.get())
|
||||
|
||||
R.id.unreadAtTop -> prefs.unreadAtTop.set(!prefs.unreadAtTop.get())
|
||||
|
||||
R.id.signature -> view.showSignatureDialog(prefs.signature.get())
|
||||
|
||||
R.id.textSize -> view.showTextSizePicker()
|
||||
|
||||
@@ -34,6 +34,7 @@ data class SettingsState(
|
||||
val sendDelaySummary: String = "",
|
||||
val sendDelayId: Int = 0,
|
||||
val deliveryEnabled: Boolean = false,
|
||||
val unreadAtTopEnabled: Boolean = false,
|
||||
val signature: String = "",
|
||||
val textSizeSummary: String = "",
|
||||
val textSizeId: Int = Preferences.TEXT_SIZE_NORMAL,
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
package dev.octoshrimpy.quik.feature.widget
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.text.SpannableStringBuilder
|
||||
@@ -34,8 +33,6 @@ import dev.octoshrimpy.quik.common.util.Colors
|
||||
import dev.octoshrimpy.quik.common.util.DateFormatter
|
||||
import dev.octoshrimpy.quik.common.util.extensions.dpToPx
|
||||
import dev.octoshrimpy.quik.common.util.extensions.getColorCompat
|
||||
import dev.octoshrimpy.quik.feature.compose.ComposeActivity
|
||||
import dev.octoshrimpy.quik.feature.main.MainActivity
|
||||
import dev.octoshrimpy.quik.injection.appComponent
|
||||
import dev.octoshrimpy.quik.model.Contact
|
||||
import dev.octoshrimpy.quik.model.Conversation
|
||||
@@ -85,7 +82,7 @@ class WidgetAdapter(intent: Intent) : RemoteViewsService.RemoteViewsFactory {
|
||||
}
|
||||
|
||||
override fun onDataSetChanged() {
|
||||
conversations = conversationRepo.getConversationsSnapshot()
|
||||
conversations = conversationRepo.getConversationsSnapshot(prefs.unreadAtTop.get())
|
||||
|
||||
val remoteViews = RemoteViews(context.packageName, R.layout.widget)
|
||||
appWidgetManager.partiallyUpdateAppWidget(appWidgetId, remoteViews)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<!--
|
||||
~ Copyright (C) 2019 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="M5,8h14v-3L5,5v2z,M5,13h14v-3L5,10v2z,M5,16h14v-1L5,15v2z,M5,19h14v-1L5,18v2z" />
|
||||
</vector>
|
||||
@@ -152,6 +152,15 @@
|
||||
app:title="@string/settings_delivery_title"
|
||||
app:widget="@layout/settings_switch_widget" />
|
||||
|
||||
<dev.octoshrimpy.quik.common.widget.PreferenceView
|
||||
android:id="@+id/unreadAtTop"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/ic_unreadattop_all_black_24dp"
|
||||
app:summary="@string/settings_unread_at_top_summary"
|
||||
app:title="@string/settings_unread_at_top_title"
|
||||
app:widget="@layout/settings_switch_widget" />
|
||||
|
||||
<dev.octoshrimpy.quik.common.widget.PreferenceView
|
||||
android:id="@+id/signature"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -259,6 +259,8 @@
|
||||
</string-array>
|
||||
<string name="settings_delivery_title">Delivery confirmations</string>
|
||||
<string name="settings_delivery_summary">Confirm that messages were sent successfully</string>
|
||||
<string name="settings_unread_at_top_title">Unread at the top</string>
|
||||
<string name="settings_unread_at_top_summary">Show unread conversations at the top</string>
|
||||
<string name="settings_signature_title">Signature</string>
|
||||
<string name="settings_signature_summary">Add a signature to the end of your messages</string>
|
||||
<string name="settings_unicode_title">Strip accents</string>
|
||||
|
||||
Reference in New Issue
Block a user