Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 76e0f775 authored by Moez Bhatti's avatar Moez Bhatti
Browse files

Per-contact colours

Closes #1120
parent 5d629125
Loading
Loading
Loading
Loading
+22 −1
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@
 */
package com.moez.QKSMS.migration

import android.annotation.SuppressLint
import com.f2prateek.rx.preferences2.RxSharedPreferences
import com.moez.QKSMS.extensions.map
import com.moez.QKSMS.mapper.CursorToContactImpl
import io.realm.DynamicRealm
@@ -29,13 +31,15 @@ import io.realm.Sort
import javax.inject.Inject

class QkRealmMigration @Inject constructor(
    private val cursorToContact: CursorToContactImpl
    private val cursorToContact: CursorToContactImpl,
    private val prefs: RxSharedPreferences
) : RealmMigration {

    companion object {
        const val SchemaVersion: Long = 9
    }

    @SuppressLint("ApplySharedPref")
    override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
        var version = oldVersion

@@ -169,6 +173,23 @@ class QkRealmMigration @Inject constructor(
                        realmContact.setString("photoUri", photoUri)
                    }

            // Migrate conversation themes
            val recipients = mutableMapOf<Long, Int>() // Map of recipientId:theme
            realm.where("Conversation").findAll().forEach { conversation ->
                val pref = prefs.getInteger("theme_${conversation.getLong("id")}")
                if (pref.isSet) {
                    conversation.getList("Recipient").forEach { recipient ->
                        recipients[recipient.getLong("id")] = pref.get()
                    }

                    pref.delete()
                }
            }

            recipients.forEach { (recipientId, theme) ->
                prefs.getInteger("theme_$recipientId").set(theme)
            }

            version++
        }

+58 −37
Original line number Diff line number Diff line
@@ -28,6 +28,8 @@ import android.media.MediaScannerConnection
import android.os.Build
import android.os.Environment
import android.provider.Telephony
import android.provider.Telephony.Mms
import android.provider.Telephony.Sms
import android.telephony.SmsManager
import android.webkit.MimeTypeMap
import androidx.core.content.contentValuesOf
@@ -110,6 +112,25 @@ class MessageRepositoryImpl @Inject constructor(
                .findFirst()
    }

    override fun getLastIncomingMessage(threadId: Long): RealmResults<Message> {
        return Realm.getDefaultInstance()
                .where(Message::class.java)
                .equalTo("threadId", threadId)
                .beginGroup()
                .beginGroup()
                .equalTo("type", "sms")
                .`in`("boxId", arrayOf(Sms.MESSAGE_TYPE_INBOX, Sms.MESSAGE_TYPE_ALL))
                .endGroup()
                .or()
                .beginGroup()
                .equalTo("type", "mms")
                .`in`("boxId", arrayOf(Mms.MESSAGE_BOX_INBOX, Mms.MESSAGE_BOX_ALL))
                .endGroup()
                .endGroup()
                .sort("date", Sort.DESCENDING)
                .findAll()
    }

    override fun getUnreadCount(): Long {
        return Realm.getDefaultInstance().use { realm ->
            realm.refresh()
@@ -239,13 +260,13 @@ class MessageRepositoryImpl @Inject constructor(
        }

        val values = ContentValues()
        values.put(Telephony.Sms.SEEN, true)
        values.put(Telephony.Sms.READ, true)
        values.put(Sms.SEEN, true)
        values.put(Sms.READ, true)

        threadIds.forEach { threadId ->
            try {
                val uri = ContentUris.withAppendedId(Telephony.MmsSms.CONTENT_CONVERSATIONS_URI, threadId)
                context.contentResolver.update(uri, values, "${Telephony.Sms.READ} = 0", null)
                context.contentResolver.update(uri, values, "${Sms.READ} = 0", null)
            } catch (exception: Exception) {
                Timber.w(exception)
            }
@@ -421,7 +442,7 @@ class MessageRepositoryImpl @Inject constructor(
            this.subId = subId

            id = messageIds.newId()
            boxId = Telephony.Sms.MESSAGE_TYPE_OUTBOX
            boxId = Sms.MESSAGE_TYPE_OUTBOX
            type = "sms"
            read = true
            seen = true
@@ -432,20 +453,20 @@ class MessageRepositoryImpl @Inject constructor(

        // Insert the message to the native content provider
        val values = contentValuesOf(
                Telephony.Sms.ADDRESS to address,
                Telephony.Sms.BODY to body,
                Telephony.Sms.DATE to System.currentTimeMillis(),
                Telephony.Sms.READ to true,
                Telephony.Sms.SEEN to true,
                Telephony.Sms.TYPE to Telephony.Sms.MESSAGE_TYPE_OUTBOX,
                Telephony.Sms.THREAD_ID to threadId
                Sms.ADDRESS to address,
                Sms.BODY to body,
                Sms.DATE to System.currentTimeMillis(),
                Sms.READ to true,
                Sms.SEEN to true,
                Sms.TYPE to Sms.MESSAGE_TYPE_OUTBOX,
                Sms.THREAD_ID to threadId
        )

        if (prefs.canUseSubId.get()) {
            values.put(Telephony.Sms.SUBSCRIPTION_ID, message.subId)
            values.put(Sms.SUBSCRIPTION_ID, message.subId)
        }

        val uri = context.contentResolver.insert(Telephony.Sms.CONTENT_URI, values)
        val uri = context.contentResolver.insert(Sms.CONTENT_URI, values)

        // Update the contentId after the message has been inserted to the content provider
        // The message might have been deleted by now, so only proceed if it's valid
@@ -479,7 +500,7 @@ class MessageRepositoryImpl @Inject constructor(

            id = messageIds.newId()
            threadId = TelephonyCompat.getOrCreateThreadId(context, address)
            boxId = Telephony.Sms.MESSAGE_TYPE_INBOX
            boxId = Sms.MESSAGE_TYPE_INBOX
            type = "sms"
            read = activeConversationManager.getActiveConversation() == threadId
        }
@@ -489,16 +510,16 @@ class MessageRepositoryImpl @Inject constructor(

        // Insert the message to the native content provider
        val values = contentValuesOf(
                Telephony.Sms.ADDRESS to address,
                Telephony.Sms.BODY to body,
                Telephony.Sms.DATE_SENT to sentTime
                Sms.ADDRESS to address,
                Sms.BODY to body,
                Sms.DATE_SENT to sentTime
        )

        if (prefs.canUseSubId.get()) {
            values.put(Telephony.Sms.SUBSCRIPTION_ID, message.subId)
            values.put(Sms.SUBSCRIPTION_ID, message.subId)
        }

        context.contentResolver.insert(Telephony.Sms.Inbox.CONTENT_URI, values)?.lastPathSegment?.toLong()?.let { id ->
        context.contentResolver.insert(Sms.Inbox.CONTENT_URI, values)?.lastPathSegment?.toLong()?.let { id ->
            // Update the contentId after the message has been inserted to the content provider
            realm.executeTransaction { managedMessage?.contentId = id }
        }
@@ -520,15 +541,15 @@ class MessageRepositoryImpl @Inject constructor(
                // Update the message in realm
                realm.executeTransaction {
                    message.boxId = when (message.isSms()) {
                        true -> Telephony.Sms.MESSAGE_TYPE_OUTBOX
                        false -> Telephony.Mms.MESSAGE_BOX_OUTBOX
                        true -> Sms.MESSAGE_TYPE_OUTBOX
                        false -> Mms.MESSAGE_BOX_OUTBOX
                    }
                }

                // Update the message in the native ContentProvider
                val values = when (message.isSms()) {
                    true -> contentValuesOf(Telephony.Sms.TYPE to Telephony.Sms.MESSAGE_TYPE_OUTBOX)
                    false -> contentValuesOf(Telephony.Mms.MESSAGE_BOX to Telephony.Mms.MESSAGE_BOX_OUTBOX)
                    true -> contentValuesOf(Sms.TYPE to Sms.MESSAGE_TYPE_OUTBOX)
                    false -> contentValuesOf(Mms.MESSAGE_BOX to Mms.MESSAGE_BOX_OUTBOX)
                }
                context.contentResolver.update(message.getUri(), values, null, null)
            }
@@ -543,12 +564,12 @@ class MessageRepositoryImpl @Inject constructor(
            message?.let {
                // Update the message in realm
                realm.executeTransaction {
                    message.boxId = Telephony.Sms.MESSAGE_TYPE_SENT
                    message.boxId = Sms.MESSAGE_TYPE_SENT
                }

                // Update the message in the native ContentProvider
                val values = ContentValues()
                values.put(Telephony.Sms.TYPE, Telephony.Sms.MESSAGE_TYPE_SENT)
                values.put(Sms.TYPE, Sms.MESSAGE_TYPE_SENT)
                context.contentResolver.update(message.getUri(), values, null, null)
            }
        }
@@ -562,14 +583,14 @@ class MessageRepositoryImpl @Inject constructor(
            message?.let {
                // Update the message in realm
                realm.executeTransaction {
                    message.boxId = Telephony.Sms.MESSAGE_TYPE_FAILED
                    message.boxId = Sms.MESSAGE_TYPE_FAILED
                    message.errorCode = resultCode
                }

                // Update the message in the native ContentProvider
                val values = ContentValues()
                values.put(Telephony.Sms.TYPE, Telephony.Sms.MESSAGE_TYPE_FAILED)
                values.put(Telephony.Sms.ERROR_CODE, resultCode)
                values.put(Sms.TYPE, Sms.MESSAGE_TYPE_FAILED)
                values.put(Sms.ERROR_CODE, resultCode)
                context.contentResolver.update(message.getUri(), values, null, null)
            }
        }
@@ -583,16 +604,16 @@ class MessageRepositoryImpl @Inject constructor(
            message?.let {
                // Update the message in realm
                realm.executeTransaction {
                    message.deliveryStatus = Telephony.Sms.STATUS_COMPLETE
                    message.deliveryStatus = Sms.STATUS_COMPLETE
                    message.dateSent = System.currentTimeMillis()
                    message.read = true
                }

                // Update the message in the native ContentProvider
                val values = ContentValues()
                values.put(Telephony.Sms.STATUS, Telephony.Sms.STATUS_COMPLETE)
                values.put(Telephony.Sms.DATE_SENT, System.currentTimeMillis())
                values.put(Telephony.Sms.READ, true)
                values.put(Sms.STATUS, Sms.STATUS_COMPLETE)
                values.put(Sms.DATE_SENT, System.currentTimeMillis())
                values.put(Sms.READ, true)
                context.contentResolver.update(message.getUri(), values, null, null)
            }
        }
@@ -606,7 +627,7 @@ class MessageRepositoryImpl @Inject constructor(
            message?.let {
                // Update the message in realm
                realm.executeTransaction {
                    message.deliveryStatus = Telephony.Sms.STATUS_FAILED
                    message.deliveryStatus = Sms.STATUS_FAILED
                    message.dateSent = System.currentTimeMillis()
                    message.read = true
                    message.errorCode = resultCode
@@ -614,10 +635,10 @@ class MessageRepositoryImpl @Inject constructor(

                // Update the message in the native ContentProvider
                val values = ContentValues()
                values.put(Telephony.Sms.STATUS, Telephony.Sms.STATUS_FAILED)
                values.put(Telephony.Sms.DATE_SENT, System.currentTimeMillis())
                values.put(Telephony.Sms.READ, true)
                values.put(Telephony.Sms.ERROR_CODE, resultCode)
                values.put(Sms.STATUS, Sms.STATUS_FAILED)
                values.put(Sms.DATE_SENT, System.currentTimeMillis())
                values.put(Sms.READ, true)
                values.put(Sms.ERROR_CODE, resultCode)
                context.contentResolver.update(message.getUri(), values, null, null)
            }
        }
+2 −0
Original line number Diff line number Diff line
@@ -32,6 +32,8 @@ interface MessageRepository {

    fun getMessageForPart(id: Long): Message?

    fun getLastIncomingMessage(threadId: Long): RealmResults<Message>

    fun getUnreadCount(): Long

    fun getPart(id: Long): MmsPart?
+27 −5
Original line number Diff line number Diff line
@@ -19,16 +19,22 @@
package com.moez.QKSMS.util

import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import android.provider.Settings
import com.f2prateek.rx.preferences2.Preference
import com.f2prateek.rx.preferences2.RxSharedPreferences
import com.moez.QKSMS.common.util.extensions.versionCode
import io.reactivex.Observable
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class Preferences @Inject constructor(context: Context, private val rxPrefs: RxSharedPreferences) {
class Preferences @Inject constructor(
    context: Context,
    private val rxPrefs: RxSharedPreferences,
    private val sharedPrefs: SharedPreferences
) {

    companion object {
        const val NIGHT_MODE_SYSTEM = 0
@@ -118,12 +124,28 @@ class Preferences @Inject constructor(context: Context, private val rxPrefs: RxS
        }
    }

    fun theme(threadId: Long = 0): Preference<Int> {
    /**
     * Returns a stream of preference keys for changing preferences
     */
    val keyChanges: Observable<String> = Observable.create<String> { emitter ->
        // Making this a lambda would cause it to be GCd
        val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
            emitter.onNext(key)
        }

        emitter.setCancellable {
            sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener)
        }

        sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
    }.share()

    fun theme(recipientId: Long = 0): Preference<Int> {
        val default = rxPrefs.getInteger("theme", 0xFF0097A7.toInt())

        return when (threadId) {
        return when (recipientId) {
            0L -> default
            else -> rxPrefs.getInteger("theme_$threadId", default.get())
            else -> rxPrefs.getInteger("theme_$recipientId", default.get())
        }
    }

+40 −2
Original line number Diff line number Diff line
@@ -30,6 +30,11 @@ import com.moez.QKSMS.R
import com.moez.QKSMS.common.util.Colors
import com.moez.QKSMS.common.util.extensions.resolveThemeBoolean
import com.moez.QKSMS.common.util.extensions.resolveThemeColor
import com.moez.QKSMS.extensions.asObservable
import com.moez.QKSMS.extensions.mapNotNull
import com.moez.QKSMS.repository.ConversationRepository
import com.moez.QKSMS.repository.MessageRepository
import com.moez.QKSMS.util.PhoneNumberUtils
import com.moez.QKSMS.util.Preferences
import com.uber.autodispose.android.lifecycle.scope
import com.uber.autodispose.autoDisposable
@@ -51,6 +56,9 @@ import javax.inject.Inject
abstract class QkThemedActivity : QkActivity() {

    @Inject lateinit var colors: Colors
    @Inject lateinit var conversationRepo: ConversationRepository
    @Inject lateinit var messageRepo: MessageRepository
    @Inject lateinit var phoneNumberUtils: PhoneNumberUtils
    @Inject lateinit var prefs: Preferences

    /**
@@ -61,10 +69,40 @@ abstract class QkThemedActivity : QkActivity() {

    /**
     * Switch the theme if the threadId changes
     * Set it based on the latest message in the conversation
     */
    val theme = threadId
    val theme: Observable<Colors.Theme> = threadId
            .distinctUntilChanged()
            .switchMap { threadId -> colors.themeObservable(threadId) }
            .switchMap { threadId ->
                val conversation = conversationRepo.getConversation(threadId)
                when {
                    conversation == null -> Observable.just(0L)

                    conversation.recipients.size == 1 -> Observable.just(conversation.recipients.first()?.id ?: 0L)

                    else -> messageRepo.getLastIncomingMessage(conversation.id)
                            .asObservable()
                            .mapNotNull { messages -> messages.firstOrNull() }
                            .distinctUntilChanged { message -> message.address }
                            .mapNotNull { message ->
                                conversation.recipients.find { recipient ->
                                    phoneNumberUtils.compare(recipient.address, message.address)
                                }
                            }
                            .map { recipient -> recipient.id }
                            .startWith(conversation.recipients.firstOrNull()?.id ?: 0)
                            .distinctUntilChanged()
                }
            }
            .switchMap { colors.themeObservable(it) }

    /**
     * Emits an event whenever any theme changes, whether it be global or for some recipient. This is useful
     * for invalidating recyclerviews
     */
    val allThemes by lazy {
        prefs.keyChanges.filter { key -> key.contains("theme") }
    }

    @SuppressLint("InlinedApi")
    override fun onCreate(savedInstanceState: Bundle?) {
Loading