Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions domain/src/main/java/com/moez/QKSMS/util/Preferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,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 dynamicColors = rxPrefs.getBoolean("dynamicColors", false)
val autoColor = rxPrefs.getBoolean("autoColor", true)
val systemFont = rxPrefs.getBoolean("systemFont", false)
val showStt = rxPrefs.getBoolean("showStt", true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,14 @@ abstract class QkThemedActivity : QkActivity() {
super.onCreate(savedInstanceState)

// When certain preferences change, we need to recreate the activity
val triggers = listOf(prefs.nightMode, prefs.night, prefs.black, prefs.textSize, prefs.systemFont)
val triggers = listOf(
prefs.nightMode,
prefs.night,
prefs.black,
prefs.dynamicColors,
prefs.textSize,
prefs.systemFont
)
Observable.merge(triggers.map { it.asObservable().skip(1) })
.debounce(400, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
Expand Down Expand Up @@ -155,6 +162,7 @@ abstract class QkThemedActivity : QkActivity() {
*/
open fun getActivityThemeRes(black: Boolean) = when {
black -> R.style.AppTheme_Black
colors.dynamicColorsSupported && prefs.dynamicColors.get() -> R.style.AppTheme_Dynamic
else -> R.style.AppTheme
}

Expand Down
41 changes: 39 additions & 2 deletions presentation/src/main/java/com/moez/QKSMS/common/util/Colors.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
package dev.octoshrimpy.quik.common.util

import android.content.Context
import android.content.res.Configuration
import android.graphics.Color
import androidx.core.content.res.getColorOrThrow
import dev.octoshrimpy.quik.R
import dev.octoshrimpy.quik.common.util.extensions.getColorCompat
import dev.octoshrimpy.quik.model.Recipient
import dev.octoshrimpy.quik.util.Preferences
import io.reactivex.Observable
import io.reactivex.rxkotlin.Observables
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.absoluteValue
Expand All @@ -37,6 +39,9 @@ class Colors @Inject constructor(
private val prefs: Preferences
) {

val dynamicColorsSupported: Boolean
get() = context.resources.getBoolean(R.bool.dynamic_colors_supported)

Comment on lines +42 to +44

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think supported should matter based on a config, rather it should matter based on android version.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially I had this as an OS version check, but this approach felt more natural to me.

My reasoning is that we need new SDK v31 (Android 12) theme files for Dynamic Color support, and they get automatically preferred based on the OS version. So by having this bool file, the OS level determines which resource files to use and this comes along with it. This boolean will be available everywhere that the values are.

I do see your point about the file just containing a boolean though. Wrapping a version check in a properly named function could probably also be sufficient.

I'm happy to go either way if you have a strong preference.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the version check would be better, as that is the pattern in the rest of the codebase, and is more idiomatic to Android. Nice thought though.

data class Theme(val theme: Int, private val colors: Colors) {
val highlight by lazy { colors.highlightColorForTheme(theme) }
val textPrimary by lazy { colors.textPrimaryOnThemeForColor(theme) }
Expand Down Expand Up @@ -80,7 +85,8 @@ class Colors @Inject constructor(
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()
recipient == null -> dynamicThemeColor() ?: pref.get()
!prefs.autoColor.get() || pref.isSet -> pref.get()
else -> generateColor(recipient)
}
return Theme(color, this)
Expand All @@ -92,10 +98,41 @@ class Colors @Inject constructor(
prefs.autoColor.get() -> prefs.theme(recipient.id, generateColor(recipient))
else -> prefs.theme(recipient.id, prefs.theme().get())
}
return pref.asObservable()
val colors = when {
recipient == null -> Observables.combineLatest(
pref.asObservable(),
prefs.dynamicColors.asObservable()
) { color, _ -> dynamicThemeColor() ?: color }
else -> pref.asObservable()
}
return colors
.map { color -> Theme(color, this) }
}

private fun dynamicThemeColor(): Int? {
val isNight = prefs.night.get() ||
(context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
Configuration.UI_MODE_NIGHT_YES

return dynamicColor(
android.R.color.system_accent1_600,
android.R.color.system_accent1_200,
Comment on lines +118 to +119

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible not to hardcode these?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see if there is a more natural way to handle this when exploring the Material 3 migration.

isNight
)
}

fun dynamicBackgroundColor(isNight: Boolean): Int? = dynamicColor(
android.R.color.system_neutral1_10,
android.R.color.system_neutral1_900,
isNight
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to not hardcode these?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as #864 (comment).


private fun dynamicColor(lightColor: Int, darkColor: Int, isNight: Boolean): Int? {
if (!dynamicColorsSupported || !prefs.dynamicColors.get()) return null

return context.getColor(if (isNight) darkColor else lightColor)
}

fun highlightColorForTheme(theme: Int): Int = FloatArray(3)
.apply { Color.colorToHSV(theme, this) }
.let { hsv -> hsv.apply { set(2, 0.75f) } } // 75% value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package dev.octoshrimpy.quik.feature.qkreply

import android.app.Activity
import android.content.Intent
import android.os.Build

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this import needed?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I missed cleaning that up from the initial use of https://developer.android.com/reference/android/os/Build.VERSION.

Thanks for catching it!

import android.os.Bundle
import android.speech.RecognizerIntent
import android.view.GestureDetector
Expand Down Expand Up @@ -177,6 +178,7 @@ class QkReplyActivity : QkThemedActivity(), QkReplyView {

override fun getActivityThemeRes(black: Boolean) = when {
black -> R.style.AppThemeDialog_Black
colors.dynamicColorsSupported && prefs.dynamicColors.get() -> R.style.AppThemeDialog_Dynamic
else -> R.style.AppThemeDialog
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ class SettingsController : QkController<SettingsControllerBinding, SettingsView,
override fun messageLinkHandlingSelected(): Observable<Int> = messageLinkHandlingDialog.adapter.menuItemClicks

override fun render(state: SettingsState) {
val dynamicColorsEnabled = colors.dynamicColorsSupported && state.dynamicColors
binding.theme.isEnabled = !dynamicColorsEnabled
binding.theme.alpha = if (dynamicColorsEnabled) 0.5f else 1f
binding.theme.findViewById<View>(R.id.themePreview)?.setBackgroundTint(state.theme)
binding.night.summary = state.nightModeSummary
nightModeDialog.adapter.selectedItem = state.nightModeId
Expand All @@ -154,6 +157,9 @@ class SettingsController : QkController<SettingsControllerBinding, SettingsView,
binding.black.setVisible(state.nightModeId != Preferences.NIGHT_MODE_OFF)
binding.black.checkbox?.isChecked = state.black

binding.dynamicColors.setVisible(colors.dynamicColorsSupported)
binding.dynamicColors.checkbox?.isChecked = state.dynamicColors

binding.autoEmoji.checkbox?.isChecked = state.autoEmojiEnabled

binding.delayed.summary = state.sendDelaySummary
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import dev.octoshrimpy.quik.common.util.DateFormatter
import dev.octoshrimpy.quik.common.util.extensions.makeToast
import dev.octoshrimpy.quik.interactor.SyncMessages
import dev.octoshrimpy.quik.manager.BillingManager
import dev.octoshrimpy.quik.manager.WidgetManager
import dev.octoshrimpy.quik.repository.SyncRepository
import dev.octoshrimpy.quik.util.NightModeManager
import dev.octoshrimpy.quik.util.Preferences
Expand All @@ -49,7 +50,8 @@ class SettingsPresenter @Inject constructor(
private val externalNavigator: ExternalNavigator,
private val nightModeManager: NightModeManager,
private val prefs: Preferences,
private val syncMessages: SyncMessages
private val syncMessages: SyncMessages,
private val widgetManager: WidgetManager
) : QkPresenter<SettingsView, SettingsState>(SettingsState(
nightModeId = prefs.nightMode.get()
)) {
Expand Down Expand Up @@ -79,6 +81,9 @@ class SettingsPresenter @Inject constructor(
disposables += prefs.black.asObservable()
.subscribe { black -> newState { copy(black = black) } }

disposables += prefs.dynamicColors.asObservable()
.subscribe { dynamicColors -> newState { copy(dynamicColors = dynamicColors) } }

disposables += prefs.notifications().asObservable()
.subscribe { enabled -> newState { copy(notificationsEnabled = enabled) } }

Expand Down Expand Up @@ -178,6 +183,11 @@ class SettingsPresenter @Inject constructor(

R.id.black -> prefs.black.set(!prefs.black.get())

R.id.dynamicColors -> {
prefs.dynamicColors.set(!prefs.dynamicColors.get())
widgetManager.updateTheme()
}

R.id.autoEmoji -> prefs.autoEmoji.set(!prefs.autoEmoji.get())

R.id.notifications -> externalNavigator.showNotificationSettings()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ data class SettingsState(
val nightStart: String = "",
val nightEnd: String = "",
val black: Boolean = false,
val dynamicColors: Boolean = false,
val autoColor: Boolean = true,
val autoEmojiEnabled: Boolean = true,
val notificationsEnabled: Boolean = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import dev.octoshrimpy.quik.common.util.Colors
import dev.octoshrimpy.quik.manager.BillingManager
import dev.octoshrimpy.quik.manager.WidgetManager
import dev.octoshrimpy.quik.util.Preferences
import io.reactivex.Observable
import io.reactivex.rxkotlin.Observables
import javax.inject.Inject
import javax.inject.Named
Expand All @@ -45,7 +46,12 @@ class ThemePickerPresenter @Inject constructor(
override fun bindIntents(view: ThemePickerView) {
super.bindIntents(view)

theme.asObservable()
val currentTheme: Observable<Int> = when (recipientId) {
0L -> colors.themeObservable().map { it.theme }
else -> theme.asObservable()
}

currentTheme
.autoDisposable(view.scope())
.subscribe { color -> view.setCurrentTheme(color) }

Expand All @@ -68,7 +74,7 @@ class ThemePickerPresenter @Inject constructor(
.subscribe()

// Toggle the visibility of the apply group
Observables.combineLatest(theme.asObservable(), view.hsvThemeSelected()) { old, new -> old != new }
Observables.combineLatest(currentTheme, view.hsvThemeSelected()) { old, new -> old != new }
.autoDisposable(view.scope())
.subscribe { themeChanged -> newState { copy(applyThemeVisible = themeChanged) } }

Expand All @@ -95,7 +101,7 @@ class ThemePickerPresenter @Inject constructor(

// Reset the theme
view.clearHsvThemeClicks()
.withLatestFrom(theme.asObservable()) { _, color -> color }
.withLatestFrom(currentTheme) { _, color -> color }
.autoDisposable(view.scope())
.subscribe { color -> view.setCurrentTheme(color) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,18 +116,23 @@ class WidgetProvider : AppWidgetProvider() {
// Apply colors from theme
val night = prefs.night.get() || isNightMode
val black = prefs.black.get()
val dynamicBackground = colors.dynamicBackgroundColor(night)

remoteViews.setInt(R.id.background, "setColorFilter", context.getColorCompat(when {
night && black -> R.color.black
night && !black -> R.color.backgroundDark
else -> R.color.white
}))
val backgroundColor = when {
night && black -> context.getColorCompat(R.color.black)
dynamicBackground != null -> dynamicBackground
night -> context.getColorCompat(R.color.backgroundDark)
else -> context.getColorCompat(R.color.white)
}
remoteViews.setInt(R.id.background, "setColorFilter", backgroundColor)

remoteViews.setInt(R.id.toolbar, "setColorFilter", context.getColorCompat(when {
night && black -> R.color.black
night && !black -> R.color.backgroundDark
else -> R.color.backgroundLight
}))
val toolbarColor = when {
night && black -> context.getColorCompat(R.color.black)
dynamicBackground != null -> dynamicBackground
night -> context.getColorCompat(R.color.backgroundDark)
else -> context.getColorCompat(R.color.backgroundLight)
}
remoteViews.setInt(R.id.toolbar, "setColorFilter", toolbarColor)

remoteViews.setTextColor(R.id.title, context.getColorCompat(when (night) {
true -> R.color.textPrimaryDark
Expand Down
8 changes: 8 additions & 0 deletions presentation/src/main/res/layout/settings_controller.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@
app:title="@string/settings_black_title"
app:widget="@layout/settings_switch_widget" />

<dev.octoshrimpy.quik.common.widget.PreferenceView
android:id="@+id/dynamicColors"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:title="@string/settings_dynamic_colors_title"
app:widget="@layout/settings_switch_widget" />

<dev.octoshrimpy.quik.common.widget.PreferenceView
android:id="@+id/nightStart"
android:layout_width="match_parent"
Expand Down
30 changes: 30 additions & 0 deletions presentation/src/main/res/values-night-v31/themes.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>

<style name="AppTheme.Dynamic" parent="AppTheme">
<item name="android:colorBackground">@android:color/system_neutral1_900</item>
<item name="android:navigationBarColor">@android:color/system_neutral1_900</item>
<item name="android:statusBarColor">@android:color/system_neutral1_900</item>
<item name="android:windowBackground">@android:color/system_neutral1_900</item>
<item name="android:queryBackground">@android:color/system_neutral2_800</item>
<item name="bubbleColor">@android:color/system_neutral2_800</item>
<item name="colorPrimary">@android:color/system_neutral1_900</item>
<item name="colorPrimaryDark">@android:color/system_neutral1_900</item>
</style>

<style name="AppThemeDialog.Dynamic" parent="AppThemeDialog">
<item name="android:colorBackground">@android:color/system_neutral1_900</item>
<item name="android:windowBackground">@android:color/system_neutral1_900</item>
<item name="actionBarPopupTheme">@style/PopupTheme.Dynamic</item>
<item name="bubbleColor">@android:color/system_neutral2_800</item>
<item name="colorPrimary">@android:color/system_neutral1_900</item>
<item name="colorPrimaryDark">@android:color/system_neutral1_900</item>
</style>

<style name="PopupTheme.Dynamic" parent="PopupTheme">
<item name="android:colorBackground">@android:color/system_neutral1_900</item>
<item name="android:textColor">@color/textPrimaryDark</item>
<item name="bubbleColor">@android:color/system_neutral2_800</item>
</style>

</resources>
4 changes: 4 additions & 0 deletions presentation/src/main/res/values-v31/bools.xml

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the point of this file? If we are going to merge it, we intend to support it. I would prefer you remove this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="dynamic_colors_supported">true</bool>
</resources>
29 changes: 29 additions & 0 deletions presentation/src/main/res/values-v31/themes.xml

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use Material Components attributes here instead? If you are unwilling to switch to Material colors, I can potentially do that. I would just prefer, if we are updating the theming engine, then maybe we should update to Material 3 as well?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll explore a larger migration/refactor soon, as mentioned in #864 (comment).

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>

<style name="AppTheme.Dynamic" parent="AppTheme">
<item name="android:colorBackground">@android:color/system_neutral1_10</item>
<item name="android:navigationBarColor">@android:color/system_neutral1_10</item>
<item name="android:statusBarColor">@android:color/system_neutral1_10</item>
<item name="android:windowBackground">@android:color/system_neutral1_10</item>
<item name="android:queryBackground">@android:color/system_neutral2_100</item>
<item name="bubbleColor">@android:color/system_neutral2_100</item>
<item name="colorPrimary">@android:color/system_neutral1_10</item>
<item name="colorPrimaryDark">@android:color/system_neutral1_10</item>
</style>

<style name="AppThemeDialog.Dynamic" parent="AppThemeDialog">
<item name="android:colorBackground">@android:color/system_neutral1_10</item>
<item name="android:windowBackground">@android:color/system_neutral1_10</item>
<item name="actionBarPopupTheme">@style/PopupTheme.Dynamic</item>
<item name="bubbleColor">@android:color/system_neutral2_100</item>
<item name="colorPrimary">@android:color/system_neutral1_10</item>
<item name="colorPrimaryDark">@android:color/system_neutral1_10</item>
</style>

<style name="PopupTheme.Dynamic" parent="PopupTheme">
<item name="android:colorBackground">@android:color/system_neutral1_10</item>
<item name="bubbleColor">@android:color/system_neutral2_100</item>
</style>

</resources>
4 changes: 4 additions & 0 deletions presentation/src/main/res/values/bools.xml

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, not needed.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="dynamic_colors_supported">false</bool>
</resources>
1 change: 1 addition & 0 deletions presentation/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@
<string name="settings_theme_title">Theme</string>
<string name="settings_night_title">Night mode</string>
<string name="settings_black_title">Pure black night mode</string>
<string name="settings_dynamic_colors_title">Dynamic colors</string>
<string name="settings_night_start_title">Start time</string>
<string name="settings_night_end_title">End time</string>
<string name="settings_automatic_colors_title">Automatic contact colors</string>
Expand Down
4 changes: 4 additions & 0 deletions presentation/src/main/res/values/themes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@
<item name="android:textColor">@color/textPrimary</item>
</style>

<style name="AppTheme.Dynamic" parent="AppTheme" />
<style name="AppThemeDialog.Dynamic" parent="AppThemeDialog" />
<style name="PopupTheme.Dynamic" parent="PopupTheme" />

<style name="AppTheme.Gallery" parent="Theme.AppCompat.NoActionBar">
<item name="android:background">@color/black</item>
</style>
Expand Down
Loading