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

Commit 68423742 authored by Moez Bhatti's avatar Moez Bhatti
Browse files

Automatic contact colours

Closes #133
parent 76e0f775
Loading
Loading
Loading
Loading
+7 −4
Original line number Diff line number Diff line
@@ -19,9 +19,9 @@
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 com.moez.QKSMS.util.Preferences
import io.realm.DynamicRealm
import io.realm.DynamicRealmObject
import io.realm.FieldAttribute
@@ -32,7 +32,7 @@ import javax.inject.Inject

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

    companion object {
@@ -176,7 +176,7 @@ class QkRealmMigration @Inject constructor(
            // 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")}")
                val pref = prefs.theme(conversation.getLong("id"))
                if (pref.isSet) {
                    conversation.getList("Recipient").forEach { recipient ->
                        recipients[recipient.getLong("id")] = pref.get()
@@ -187,9 +187,12 @@ class QkRealmMigration @Inject constructor(
            }

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

            // This is enabled for new users, but the behaviour shouldn't change automatically for old users
            prefs.autoColor.set(false)

            version++
        }

+7 −5
Original line number Diff line number Diff line
@@ -90,6 +90,7 @@ class Preferences @Inject constructor(
    val nightStart = rxPrefs.getString("nightStart", "18:00")
    val nightEnd = rxPrefs.getString("nightEnd", "6:00")
    val black = rxPrefs.getBoolean("black", false)
    val autoColor = rxPrefs.getBoolean("autoColor", true)
    val systemFont = rxPrefs.getBoolean("systemFont", false)
    val textSize = rxPrefs.getInteger("textSize", TEXT_SIZE_NORMAL)
    val blockingManager = rxPrefs.getInteger("blockingManager", BLOCKING_MANAGER_QKSMS)
@@ -140,12 +141,13 @@ class Preferences @Inject constructor(
        sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
    }.share()

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

    fun theme(
        recipientId: Long = 0,
        default: Int = rxPrefs.getInteger("theme", 0xFF0097A7.toInt()).get()
    ): Preference<Int> {
        return when (recipientId) {
            0L -> default
            else -> rxPrefs.getInteger("theme_$recipientId", default.get())
            0L -> rxPrefs.getInteger("theme", 0xFF0097A7.toInt())
            else -> rxPrefs.getInteger("theme_$recipientId", default)
        }
    }

+6 −13
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ 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.Optional
import com.moez.QKSMS.extensions.asObservable
import com.moez.QKSMS.extensions.mapNotNull
import com.moez.QKSMS.repository.ConversationRepository
@@ -76,9 +77,9 @@ abstract class QkThemedActivity : QkActivity() {
            .switchMap { threadId ->
                val conversation = conversationRepo.getConversation(threadId)
                when {
                    conversation == null -> Observable.just(0L)
                    conversation == null -> Observable.just(Optional(null))

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

                    else -> messageRepo.getLastIncomingMessage(conversation.id)
                            .asObservable()
@@ -89,20 +90,12 @@ abstract class QkThemedActivity : QkActivity() {
                                    phoneNumberUtils.compare(recipient.address, message.address)
                                }
                            }
                            .map { recipient -> recipient.id }
                            .startWith(conversation.recipients.firstOrNull()?.id ?: 0)
                            .map { recipient -> Optional(recipient) }
                            .startWith(Optional(conversation.recipients.firstOrNull()))
                            .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") }
    }
            .switchMap { colors.themeObservable(it.value) }

    @SuppressLint("InlinedApi")
    override fun onCreate(savedInstanceState: Bundle?) {
+57 −26
Original line number Diff line number Diff line
@@ -20,15 +20,23 @@ package com.moez.QKSMS.common.util

import android.content.Context
import android.graphics.Color
import androidx.core.content.res.getColorOrThrow
import com.moez.QKSMS.R
import com.moez.QKSMS.common.util.extensions.getColorCompat
import com.moez.QKSMS.model.Recipient
import com.moez.QKSMS.util.PhoneNumberUtils
import com.moez.QKSMS.util.Preferences
import io.reactivex.Observable
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.absoluteValue

@Singleton
class Colors @Inject constructor(private val context: Context, private val prefs: Preferences) {
class Colors @Inject constructor(
    private val context: Context,
    private val phoneNumberUtils: PhoneNumberUtils,
    private val prefs: Preferences
) {

    data class Theme(val theme: Int, private val colors: Colors) {
        val highlight by lazy { colors.highlightColorForTheme(theme) }
@@ -37,27 +45,31 @@ class Colors @Inject constructor(private val context: Context, private val prefs
        val textTertiary by lazy { colors.textTertiaryOnThemeForColor(theme) }
    }

    val materialColors = listOf(
            listOf(0xffffebee, 0xffffcdd2, 0xffef9a9a, 0xffe57373, 0xffef5350, 0xfff44336, 0xffe53935, 0xffd32f2f, 0xffc62828, 0xffb71c1c),
            listOf(0xffFCE4EC, 0xffF8BBD0, 0xffF48FB1, 0xffF06292, 0xffEC407A, 0xffE91E63, 0xffD81B60, 0xffC2185B, 0xffAD1457, 0xff880E4F),
            listOf(0xffF3E5F5, 0xffE1BEE7, 0xffCE93D8, 0xffBA68C8, 0xffAB47BC, 0xff9C27B0, 0xff8E24AA, 0xff7B1FA2, 0xff6A1B9A, 0xff4A148C),
            listOf(0xffEDE7F6, 0xffD1C4E9, 0xffB39DDB, 0xff9575CD, 0xff7E57C2, 0xff673AB7, 0xff5E35B1, 0xff512DA8, 0xff4527A0, 0xff311B92),
            listOf(0xffE8EAF6, 0xffC5CAE9, 0xff9FA8DA, 0xff7986CB, 0xff5C6BC0, 0xff3F51B5, 0xff3949AB, 0xff303F9F, 0xff283593, 0xff1A237E),
            listOf(0xffE3F2FD, 0xffBBDEFB, 0xff90CAF9, 0xff64B5F6, 0xff42A5F5, 0xff2196F3, 0xff1E88E5, 0xff1976D2, 0xff1565C0, 0xff0D47A1),
            listOf(0xffE1F5FE, 0xffB3E5FC, 0xff81D4FA, 0xff4FC3F7, 0xff29B6F6, 0xff03A9F4, 0xff039BE5, 0xff0288D1, 0xff0277BD, 0xff01579B),
            listOf(0xffE0F7FA, 0xffB2EBF2, 0xff80DEEA, 0xff4DD0E1, 0xff26C6DA, 0xff00BCD4, 0xff00ACC1, 0xff0097A7, 0xff00838F, 0xff006064),
            listOf(0xffE0F2F1, 0xffB2DFDB, 0xff80CBC4, 0xff4DB6AC, 0xff26A69A, 0xff009688, 0xff00897B, 0xff00796B, 0xff00695C, 0xff004D40),
            listOf(0xffE8F5E9, 0xffC8E6C9, 0xffA5D6A7, 0xff81C784, 0xff66BB6A, 0xff4CAF50, 0xff43A047, 0xff388E3C, 0xff2E7D32, 0xff1B5E20),
            listOf(0xffF1F8E9, 0xffDCEDC8, 0xffC5E1A5, 0xffAED581, 0xff9CCC65, 0xff8BC34A, 0xff7CB342, 0xff689F38, 0xff558B2F, 0xff33691E),
            listOf(0xffF9FBE7, 0xffF0F4C3, 0xffE6EE9C, 0xffDCE775, 0xffD4E157, 0xffCDDC39, 0xffC0CA33, 0xffAFB42B, 0xff9E9D24, 0xff827717),
            listOf(0xffFFFDE7, 0xffFFF9C4, 0xffFFF59D, 0xffFFF176, 0xffFFEE58, 0xffFFEB3B, 0xffFDD835, 0xffFBC02D, 0xffF9A825, 0xffF57F17),
            listOf(0xffFFF8E1, 0xffFFECB3, 0xffFFE082, 0xffFFD54F, 0xffFFCA28, 0xffFFC107, 0xffFFB300, 0xffFFA000, 0xffFF8F00, 0xffFF6F00),
            listOf(0xffFFF3E0, 0xffFFE0B2, 0xffFFCC80, 0xffFFB74D, 0xffFFA726, 0xffFF9800, 0xffFB8C00, 0xffF57C00, 0xffEF6C00, 0xffE65100),
            listOf(0xffFBE9E7, 0xffFFCCBC, 0xffFFAB91, 0xffFF8A65, 0xffFF7043, 0xffFF5722, 0xffF4511E, 0xffE64A19, 0xffD84315, 0xffBF360C),
            listOf(0xffEFEBE9, 0xffD7CCC8, 0xffBCAAA4, 0xffA1887F, 0xff8D6E63, 0xff795548, 0xff6D4C41, 0xff5D4037, 0xff4E342E, 0xff3E2723),
            listOf(0xffFAFAFA, 0xffF5F5F5, 0xffEEEEEE, 0xffE0E0E0, 0xffBDBDBD, 0xff9E9E9E, 0xff757575, 0xff616161, 0xff424242, 0xff212121),
            listOf(0xffECEFF1, 0xffCFD8DC, 0xffB0BEC5, 0xff90A4AE, 0xff78909C, 0xff607D8B, 0xff546E7A, 0xff455A64, 0xff37474F, 0xff263238))
            .map { it.map { it.toInt() } }
    val materialColors: List<List<Int>> = listOf(
        R.array.material_red,
        R.array.material_pink,
        R.array.material_purple,
        R.array.material_deep_purple,
        R.array.material_indigo,
        R.array.material_blue,
        R.array.material_light_blue,
        R.array.material_cyan,
        R.array.material_teal,
        R.array.material_green,
        R.array.material_light_green,
        R.array.material_lime,
        R.array.material_yellow,
        R.array.material_amber,
        R.array.material_orange,
        R.array.material_deep_orange,
        R.array.material_brown,
        R.array.material_gray,
        R.array.material_blue_gray)
            .map { res -> context.resources.obtainTypedArray(res) }
            .map { typedArray -> (0 until typedArray.length()).map(typedArray::getColorOrThrow) }

    private val randomColors: List<Int> = context.resources.obtainTypedArray(R.array.random_colors)
            .let { typedArray -> (0 until typedArray.length()).map(typedArray::getColorOrThrow) }

    private val minimumContrastRatio = 2

@@ -66,10 +78,21 @@ class Colors @Inject constructor(private val context: Context, private val prefs
    private val secondaryTextLuminance = measureLuminance(context.getColorCompat(R.color.textSecondaryDark))
    private val tertiaryTextLuminance = measureLuminance(context.getColorCompat(R.color.textTertiaryDark))

    fun theme(recipientId: Long = 0): Theme = Theme(prefs.theme(recipientId).get(), this)
    fun theme(recipient: Recipient? = null): Theme {
        val pref = prefs.theme(recipient?.id ?: 0)
        val color = when {
            recipient == null || !prefs.autoColor.get() || pref.isSet -> pref.get()
            else -> generateColor(recipient)
        }
        return Theme(color, this)
    }

    fun themeObservable(recipientId: Long = 0): Observable<Theme> {
        return prefs.theme(recipientId).asObservable()
    fun themeObservable(recipient: Recipient? = null): Observable<Theme> {
        val pref = when {
            recipient == null || !prefs.autoColor.get() -> prefs.theme()
            else -> prefs.theme(recipient.id, generateColor(recipient))
        }
        return pref.asObservable()
                .map { color -> Theme(color, this) }
    }

@@ -110,4 +133,12 @@ class Colors @Inject constructor(private val context: Context, private val prefs
        return 0.2126 * array[0] + 0.7152 * array[1] + 0.0722 * array[2] + 0.05
    }

    private fun generateColor(recipient: Recipient): Int {
        val first = recipient.contact?.name?.firstOrNull()
                ?: phoneNumberUtils.normalizeNumber(recipient.address).firstOrNull()
                ?: '#'

        val index = first.hashCode().absoluteValue % randomColors.size
        return randomColors[index]
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -139,7 +139,7 @@ class NotificationManagerImpl @Inject constructor(

        val notification = NotificationCompat.Builder(context, getChannelIdForNotification(threadId))
                .setCategory(NotificationCompat.CATEGORY_MESSAGE)
                .setColor(colors.theme(lastRecipient?.id ?: 0).theme)
                .setColor(colors.theme(lastRecipient).theme)
                .setPriority(NotificationCompat.PRIORITY_MAX)
                .setSmallIcon(R.drawable.ic_notification)
                .setNumber(messages.size)
@@ -325,7 +325,7 @@ class NotificationManagerImpl @Inject constructor(
        val notification = NotificationCompat.Builder(context, getChannelIdForNotification(threadId))
                .setContentTitle(context.getString(R.string.notification_message_failed_title))
                .setContentText(context.getString(R.string.notification_message_failed_text, conversation.getTitle()))
                .setColor(colors.theme(lastRecipient?.id ?: 0).theme)
                .setColor(colors.theme(lastRecipient).theme)
                .setPriority(NotificationManagerCompat.IMPORTANCE_MAX)
                .setSmallIcon(R.drawable.ic_notification_failed)
                .setAutoCancel(true)
Loading