diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 00000000..4bec4ea8
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 00000000..a55e7a17
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/src/main/java/com/nextcloud/android/common/ui/color/ColorUtil.kt b/ui/src/main/java/com/nextcloud/android/common/ui/color/ColorUtil.kt
index 0c6060b1..2aba757b 100644
--- a/ui/src/main/java/com/nextcloud/android/common/ui/color/ColorUtil.kt
+++ b/ui/src/main/java/com/nextcloud/android/common/ui/color/ColorUtil.kt
@@ -51,16 +51,20 @@ class ColorUtil @Inject constructor(private val context: Context) {
@ColorInt
fun getForegroundColorForBackgroundColor(@ColorInt color: Int): Int {
- val hsl = FloatArray(HSL_SIZE)
- ColorUtils.RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hsl)
-
- return if (hsl[INDEX_LIGHTNESS] < LIGHTNESS_DARK_THRESHOLD) {
+ return if (isDarkBackground(color)) {
Color.WHITE
} else {
ContextCompat.getColor(context, R.color.grey_900)
}
}
+ fun isDarkBackground(@ColorInt color: Int): Boolean {
+ val hsl = FloatArray(HSL_SIZE)
+ ColorUtils.RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hsl)
+
+ return hsl[INDEX_LIGHTNESS] < LIGHTNESS_DARK_THRESHOLD
+ }
+
fun setLightness(@ColorInt color: Int, lightness: Float): Int {
require(lightness in 0.0..1.0) { "Lightness must be between 0 and 1" }
val hsl = FloatArray(HSL_SIZE)
@@ -71,9 +75,17 @@ class ColorUtil @Inject constructor(private val context: Context) {
return ColorUtils.HSLToColor(hsl)
}
+ fun colorToHexString(@ColorInt color: Int): String {
+ return String.format(null, "#%06X", HEX_WHITE and color)
+ }
+
@ColorInt
private fun String?.parseColorOrFallback(fallback: () -> Int): Int {
- return this?.let { Color.parseColor(this) } ?: fallback()
+ return if (this?.isNotBlank() == true) {
+ Color.parseColor(this)
+ } else {
+ fallback()
+ }
}
@ColorInt
@@ -90,5 +102,6 @@ class ColorUtil @Inject constructor(private val context: Context) {
private const val HSL_SIZE: Int = 3
private const val INDEX_LIGHTNESS: Int = 2
private const val LIGHTNESS_DARK_THRESHOLD: Float = 0.6f
+ private const val HEX_WHITE = 0xFFFFFF
}
}
diff --git a/ui/src/main/java/com/nextcloud/android/common/ui/theme/MaterialSchemes.kt b/ui/src/main/java/com/nextcloud/android/common/ui/theme/MaterialSchemes.kt
index 9ee03e4e..7f4233f9 100644
--- a/ui/src/main/java/com/nextcloud/android/common/ui/theme/MaterialSchemes.kt
+++ b/ui/src/main/java/com/nextcloud/android/common/ui/theme/MaterialSchemes.kt
@@ -21,6 +21,7 @@
package com.nextcloud.android.common.ui.theme
+import androidx.annotation.ColorInt
import scheme.Scheme
interface MaterialSchemes {
@@ -36,5 +37,6 @@ interface MaterialSchemes {
companion object {
fun fromServerTheme(theme: ServerTheme): MaterialSchemes = MaterialSchemesImpl(theme)
+ fun fromColor(@ColorInt color: Int): MaterialSchemes = MaterialSchemesImpl(color)
}
}
diff --git a/ui/src/main/java/com/nextcloud/android/common/ui/theme/MaterialSchemesImpl.kt b/ui/src/main/java/com/nextcloud/android/common/ui/theme/MaterialSchemesImpl.kt
index 85eb3280..d76ba327 100644
--- a/ui/src/main/java/com/nextcloud/android/common/ui/theme/MaterialSchemesImpl.kt
+++ b/ui/src/main/java/com/nextcloud/android/common/ui/theme/MaterialSchemesImpl.kt
@@ -21,15 +21,18 @@
package com.nextcloud.android.common.ui.theme
+import androidx.annotation.ColorInt
import scheme.Scheme
-internal class MaterialSchemesImpl(serverTheme: ServerTheme) :
+internal class MaterialSchemesImpl :
MaterialSchemes {
override val lightScheme: Scheme
override val darkScheme: Scheme
- init {
- lightScheme = Scheme.light(serverTheme.primaryColor)
- darkScheme = Scheme.dark(serverTheme.primaryColor)
+ constructor(@ColorInt primaryColor: Int) {
+ lightScheme = Scheme.light(primaryColor)
+ darkScheme = Scheme.dark(primaryColor)
}
+
+ constructor(serverTheme: ServerTheme) : this(serverTheme.primaryColor)
}
diff --git a/ui/src/main/java/com/nextcloud/android/common/ui/theme/ViewThemeUtilsBase.kt b/ui/src/main/java/com/nextcloud/android/common/ui/theme/ViewThemeUtilsBase.kt
index e620bb28..46a49b74 100644
--- a/ui/src/main/java/com/nextcloud/android/common/ui/theme/ViewThemeUtilsBase.kt
+++ b/ui/src/main/java/com/nextcloud/android/common/ui/theme/ViewThemeUtilsBase.kt
@@ -28,7 +28,7 @@ import android.view.View
import com.nextcloud.android.common.ui.util.PlatformThemeUtil
import scheme.Scheme
-open class ViewThemeUtilsBase(val schemes: MaterialSchemes) {
+open class ViewThemeUtilsBase(private val schemes: MaterialSchemes) {
/**
* Scheme for painting elements
*/
@@ -40,20 +40,15 @@ open class ViewThemeUtilsBase(val schemes: MaterialSchemes) {
fun getScheme(context: Context): Scheme = getSchemeInternal(context)
@Suppress("MemberVisibilityCanBePrivate")
+ // TODO cache by context hashcode
protected fun getSchemeInternal(context: Context): Scheme = when {
PlatformThemeUtil.isDarkMode(context) -> schemes.darkScheme
else -> schemes.lightScheme
}
- protected fun withScheme(view: View, block: (Scheme) -> Unit) {
- block(getSchemeInternal(view.context))
- }
+ protected fun withScheme(view: View, block: (Scheme) -> R): R = block(getSchemeInternal(view.context))
- protected fun withScheme(context: Context, block: (Scheme) -> Unit) {
- block(getSchemeInternal(context))
- }
+ protected fun withScheme(context: Context, block: (Scheme) -> R): R = block(getSchemeInternal(context))
- protected fun withSchemeDark(block: (Scheme) -> Unit) {
- block(schemes.darkScheme)
- }
+ protected fun withSchemeDark(block: (Scheme) -> R): R = block(schemes.darkScheme)
}
diff --git a/ui/src/main/java/com/nextcloud/android/common/ui/theme/utils/AndroidViewThemeUtils.kt b/ui/src/main/java/com/nextcloud/android/common/ui/theme/utils/AndroidViewThemeUtils.kt
index 28484745..aee8690e 100644
--- a/ui/src/main/java/com/nextcloud/android/common/ui/theme/utils/AndroidViewThemeUtils.kt
+++ b/ui/src/main/java/com/nextcloud/android/common/ui/theme/utils/AndroidViewThemeUtils.kt
@@ -23,34 +23,47 @@
package com.nextcloud.android.common.ui.theme.utils
+import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.Typeface
-import android.os.Build
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
import android.text.Spannable
+import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import android.view.MenuItem
import android.view.View
import android.widget.Button
import android.widget.CheckBox
+import android.widget.CheckedTextView
import android.widget.EditText
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.RadioButton
import android.widget.SeekBar
+import android.widget.Switch
import android.widget.TextView
import androidx.annotation.ColorInt
+import androidx.annotation.DrawableRes
+import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.core.content.res.ResourcesCompat
+import androidx.core.graphics.BlendModeColorFilterCompat
+import androidx.core.graphics.BlendModeCompat
+import androidx.core.graphics.drawable.DrawableCompat
+import androidx.core.view.WindowInsetsControllerCompat
+import com.google.android.material.navigation.NavigationView
import com.nextcloud.android.common.ui.R
import com.nextcloud.android.common.ui.color.ColorUtil
import com.nextcloud.android.common.ui.theme.MaterialSchemes
import com.nextcloud.android.common.ui.theme.ViewThemeUtilsBase
-import com.nextcloud.android.common.ui.util.PlatformThemeUtil
+import com.nextcloud.android.common.ui.util.buildColorStateList
+import scheme.Scheme
import javax.inject.Inject
/**
@@ -60,48 +73,171 @@ import javax.inject.Inject
class AndroidViewThemeUtils @Inject constructor(schemes: MaterialSchemes, private val colorUtil: ColorUtil) :
ViewThemeUtilsBase(schemes) {
- fun colorViewBackground(view: View) {
+ @JvmOverloads
+ fun colorViewBackground(view: View, colorRole: ColorRole = ColorRole.SURFACE) {
withScheme(view) { scheme ->
- view.setBackgroundColor(scheme.surface)
+ view.setBackgroundColor(colorRole.select(scheme))
+ }
+ }
+
+ fun colorNavigationView(navigationView: NavigationView) {
+ withScheme(navigationView) { scheme ->
+ if (navigationView.itemBackground != null) {
+ navigationView.itemBackground!!.setTintList(
+ buildColorStateList(
+ android.R.attr.state_checked to scheme.secondaryContainer,
+ -android.R.attr.state_checked to Color.TRANSPARENT
+ )
+ )
+ }
+ navigationView.setBackgroundColor(scheme.surface)
+
+ val colorStateList = buildColorStateList(
+ android.R.attr.state_checked to scheme.onSecondaryContainer,
+ -android.R.attr.state_checked to scheme.onSurfaceVariant
+ )
+
+ navigationView.itemTextColor = colorStateList
+ navigationView.itemIconTintList = colorStateList
+ }
+ }
+
+ fun getPrimaryColorDrawable(context: Context): Drawable {
+ return withScheme(context) { scheme ->
+ ColorDrawable(scheme.primary)
}
}
fun colorToolbarMenuIcon(context: Context, item: MenuItem) {
withScheme(context) { scheme ->
- item.icon.setColorFilter(scheme.onSurface, PorterDuff.Mode.SRC_ATOP)
+ colorMenuItemIcon(scheme.onSurface, item)
+ }
+ }
+
+ fun colorMenuItemText(context: Context, item: MenuItem) {
+ withScheme(context) { scheme: Scheme ->
+ colorMenuItemText(scheme.onSurface, item)
+ }
+ }
+
+ private fun colorMenuItemIcon(@ColorInt color: Int, item: MenuItem) {
+ val normalDrawable = item.icon
+ val wrapDrawable = DrawableCompat.wrap(normalDrawable)
+ DrawableCompat.setTint(wrapDrawable, color)
+ item.icon = wrapDrawable
+ }
+
+ private fun colorMenuItemText(@ColorInt color: Int, item: MenuItem) {
+ val newItemTitle = SpannableString(item.title)
+ newItemTitle.setSpan(
+ ForegroundColorSpan(color),
+ 0,
+ newItemTitle.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ item.title = newItemTitle
+ }
+
+ @JvmOverloads
+ fun tintDrawable(context: Context, @DrawableRes id: Int, colorRole: ColorRole = ColorRole.PRIMARY): Drawable? {
+ val drawable = ResourcesCompat.getDrawable(context.resources, id, null)
+ return drawable?.let {
+ tintDrawable(context, it, colorRole)
+ }
+ }
+
+ @Deprecated(
+ replaceWith = ReplaceWith(
+ "tintDrawable(context, id, ColorRole.PRIMARY)",
+ imports = ["com.nextcloud.android.common.ui.theme.utils.ColorRole"]
+ ),
+ message = "Use tintDrawable(context, id, ColorRole.PRIMARY) instead"
+ )
+ fun tintPrimaryDrawable(context: Context, @DrawableRes id: Int): Drawable? {
+ return tintDrawable(context, id, ColorRole.PRIMARY)
+ }
+
+ @JvmOverloads
+ fun tintDrawable(context: Context, drawable: Drawable, colorRole: ColorRole = ColorRole.PRIMARY): Drawable {
+ return withScheme(context) { scheme: Scheme ->
+ colorDrawable(drawable, colorRole.select(scheme))
}
}
+ @Deprecated(
+ replaceWith = ReplaceWith(
+ "tintDrawable(context, drawable, ColorRole.PRIMARY)",
+ imports = ["com.nextcloud.android.common.ui.theme.utils.ColorRole"]
+ ),
+ message = "Use tintDrawable(context, drawable, ColorRole.PRIMARY) instead"
+ )
+ fun tintPrimaryDrawable(context: Context, drawable: Drawable?): Drawable? {
+ return drawable?.let { tintDrawable(context, it, ColorRole.PRIMARY) }
+ }
+
+ @Deprecated(
+ replaceWith = ReplaceWith(
+ "tintDrawable(context, drawable, ColorRole.ON_SURFACE)",
+ imports = ["com.nextcloud.android.common.ui.theme.utils.ColorRole"]
+ ),
+ message = "Use tintDrawable(context, drawable, ColorRole.ON_SURFACE) instead"
+ )
+ fun tintTextDrawable(context: Context, drawable: Drawable?): Drawable? {
+ return drawable?.let { tintDrawable(context, it, ColorRole.ON_SURFACE) }
+ }
+
+ /**
+ * Public for edge cases. For most cases use [tintDrawable] instead
+ */
+ fun colorDrawable(drawable: Drawable, @ColorInt color: Int): Drawable {
+ val wrap = DrawableCompat.wrap(drawable)
+ wrap.colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
+ color,
+ BlendModeCompat.SRC_ATOP
+ )
+ return wrap
+ }
+
+ fun tintToolbarArrowDrawable(
+ context: Context,
+ drawerToggle: ActionBarDrawerToggle,
+ drawable: Drawable
+ ) {
+ withScheme(context) { scheme: Scheme ->
+ val wrap = DrawableCompat.wrap(drawable)
+ wrap.setColorFilter(scheme.onSurface, PorterDuff.Mode.SRC_ATOP)
+ drawerToggle.setHomeAsUpIndicator(wrap)
+ drawerToggle.drawerArrowDrawable.color = scheme.onSurface
+ }
+ }
+
+ @Deprecated(message = "Use themeStatusBar(activity) instead", replaceWith = ReplaceWith("themeStatusBar(activity)"))
+ @Suppress("Detekt.UnusedPrivateMember") // deprecated, to be removed
fun themeStatusBar(activity: Activity, view: View) {
- withScheme(view) { scheme ->
- applyColorToStatusBar(activity, scheme.surface)
+ themeStatusBar(activity)
+ }
+
+ fun themeStatusBar(activity: Activity) {
+ withScheme(activity) { scheme ->
+ colorStatusBar(activity, scheme.surface)
}
}
- private fun applyColorToStatusBar(activity: Activity, @ColorInt color: Int) {
+ /**
+ * Public for special cases, e.g. action mode. You probably want [themeStatusBar] for most cases instead.
+ */
+ fun colorStatusBar(activity: Activity, @ColorInt color: Int) {
val window = activity.window ?: return
- val isLightTheme = !PlatformThemeUtil.isDarkMode(activity)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- val decor = window.decorView
- if (isLightTheme) {
- val systemUiFlagLightStatusBar = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or
- View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
- } else {
- View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
- }
- decor.systemUiVisibility = systemUiFlagLightStatusBar
- } else {
- decor.systemUiVisibility = 0
- }
- window.statusBarColor = color
- } else if (isLightTheme) {
- window.statusBarColor = Color.BLACK
- }
+ val isLightBackground = !colorUtil.isDarkBackground(color)
+ val decor = window.decorView
+ window.statusBarColor = color
+ window.navigationBarColor = color
+ WindowInsetsControllerCompat(window, decor).isAppearanceLightStatusBars = isLightBackground
+ WindowInsetsControllerCompat(window, decor).isAppearanceLightNavigationBars = isLightBackground
}
fun resetStatusBar(activity: Activity) {
- applyColorToStatusBar(
+ colorStatusBar(
activity,
ResourcesCompat.getColor(
activity.resources,
@@ -136,25 +272,53 @@ class AndroidViewThemeUtils @Inject constructor(schemes: MaterialSchemes, privat
}
}
- fun themeHorizontalProgressBar(progressBar: ProgressBar?, @ColorInt color: Int) {
- if (progressBar != null) {
- progressBar.indeterminateDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN)
- progressBar.progressDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN)
+ fun themeHorizontalProgressBar(progressBar: ProgressBar) {
+ withScheme(progressBar) { scheme ->
+ themeHorizontalProgressBar(progressBar, scheme.primary)
}
}
- fun colorPrimaryTextViewElement(textView: TextView) {
+ fun themeHorizontalProgressBar(progressBar: ProgressBar?, @ColorInt color: Int) {
+ progressBar?.indeterminateDrawable?.setColorFilter(color, PorterDuff.Mode.SRC_IN)
+ progressBar?.progressDrawable?.setColorFilter(color, PorterDuff.Mode.SRC_IN)
+ }
+
+ @JvmOverloads
+ fun colorTextView(textView: TextView, colorRole: ColorRole = ColorRole.PRIMARY) {
withScheme(textView) { scheme ->
- textView.setTextColor(scheme.primary)
+ textView.setTextColor(colorRole.select(scheme))
}
}
+ @Deprecated(
+ replaceWith = ReplaceWith("colorTextView(textView)"),
+ message = "Use colorTextView(textView) instead"
+ )
+ fun colorPrimaryTextViewElement(textView: TextView) {
+ colorTextView(textView, ColorRole.PRIMARY)
+ }
+
+ @Deprecated(
+ replaceWith = ReplaceWith(
+ "colorTextView(textView, ColorRole.ON_SECONDARY_CONTAINER)",
+ imports = ["com.nextcloud.android.common.ui.theme.utils.ColorRole"]
+ ),
+ message = "Use colorTextView(textView, ColorRole.ON_SECONDARY_CONTAINER) instead"
+ )
+ fun colorOnSecondaryContainerTextViewElement(textView: TextView) {
+ colorTextView(textView, ColorRole.ON_SECONDARY_CONTAINER)
+ }
+
fun colorPrimaryTextViewElementDarkMode(textView: TextView) {
withSchemeDark { scheme ->
textView.setTextColor(scheme.primary)
}
}
+ @Deprecated(
+ replaceWith = ReplaceWith("colorViewBackground(view)"),
+ message = "Use colorViewBackground(view) instead"
+ )
fun colorPrimaryView(view: View) {
withScheme(view) { scheme ->
view.setBackgroundColor(scheme.primary)
@@ -163,8 +327,20 @@ class AndroidViewThemeUtils @Inject constructor(schemes: MaterialSchemes, privat
/**
* Colors the background as element color and the foreground as text color.
+ *
*/
+ @Deprecated(
+ replaceWith = ReplaceWith("colorImageViewBackgroundAndIcon"),
+ message = "Use colorImageViewBackgroundAndIcon, which has a better name, instead"
+ )
fun colorImageViewButton(imageView: ImageView) {
+ colorImageViewBackgroundAndIcon(imageView)
+ }
+
+ /**
+ * Colors the background as element color and the foreground as text color.
+ */
+ fun colorImageViewBackgroundAndIcon(imageView: ImageView) {
withScheme(imageView) { scheme ->
imageView.imageTintList = ColorStateList.valueOf(scheme.onPrimaryContainer)
imageView.backgroundTintList = ColorStateList.valueOf(scheme.primaryContainer)
@@ -173,23 +349,25 @@ class AndroidViewThemeUtils @Inject constructor(schemes: MaterialSchemes, privat
fun themeImageButton(imageButton: ImageButton) {
withScheme(imageButton) { scheme ->
- imageButton.imageTintList = ColorStateList(
- arrayOf(
- intArrayOf(android.R.attr.state_selected),
- intArrayOf(-android.R.attr.state_selected),
- intArrayOf(android.R.attr.state_enabled),
- intArrayOf(-android.R.attr.state_enabled)
- ),
- intArrayOf(
- scheme.primary,
- scheme.onSurfaceVariant,
- scheme.onSurfaceVariant,
- colorUtil.adjustOpacity(scheme.onSurface, ON_SURFACE_OPACITY_BUTTON_DISABLED)
+ imageButton.imageTintList = buildColorStateList(
+ android.R.attr.state_selected to scheme.primary,
+ -android.R.attr.state_selected to scheme.onSurfaceVariant,
+ android.R.attr.state_enabled to scheme.onSurfaceVariant,
+ -android.R.attr.state_enabled to colorUtil.adjustOpacity(
+ scheme.onSurface,
+ ON_SURFACE_OPACITY_BUTTON_DISABLED
)
)
}
}
+ /**
+ * In most cases you'll want to use [themeImageButton] instead.
+ */
+ fun colorImageButton(imageButton: ImageButton?, @ColorInt color: Int) {
+ imageButton?.setColorFilter(color, PorterDuff.Mode.SRC_ATOP)
+ }
+
/**
* Tints the image with element color
*/
@@ -200,17 +378,23 @@ class AndroidViewThemeUtils @Inject constructor(schemes: MaterialSchemes, privat
}
fun colorTextButtons(vararg buttons: Button) {
+ withScheme(buttons[0]) { scheme ->
+ colorTextButtons(scheme.primary, *buttons)
+ }
+ }
+
+ /**
+ * In most cases you'll want to use [colorTextButtons] instead.
+ */
+ fun colorTextButtons(@ColorInt color: Int, vararg buttons: Button) {
withScheme(buttons[0]) { scheme ->
for (button in buttons) {
button.setTextColor(
- ColorStateList(
- arrayOf(
- intArrayOf(android.R.attr.state_enabled),
- intArrayOf(-android.R.attr.state_enabled)
- ),
- intArrayOf(
- scheme.primary,
- colorUtil.adjustOpacity(scheme.onSurface, ON_SURFACE_OPACITY_BUTTON_DISABLED)
+ buildColorStateList(
+ android.R.attr.state_enabled to color,
+ -android.R.attr.state_enabled to colorUtil.adjustOpacity(
+ scheme.onSurface,
+ ON_SURFACE_OPACITY_BUTTON_DISABLED
)
)
)
@@ -236,26 +420,38 @@ class AndroidViewThemeUtils @Inject constructor(schemes: MaterialSchemes, privat
}
}
- fun themeCheckbox(checkbox: CheckBox) {
- withScheme(checkbox) { scheme ->
- checkbox.buttonTintList = ColorStateList(
- arrayOf(
- intArrayOf(-android.R.attr.state_checked),
- intArrayOf(android.R.attr.state_checked)
- ),
- intArrayOf(Color.GRAY, scheme.primary)
+ fun themeCheckedTextView(vararg checkedTextViews: CheckedTextView) {
+ withScheme(checkedTextViews[0]) { scheme ->
+ val colorStateList = buildColorStateList(
+ -android.R.attr.state_checked to Color.GRAY,
+ -android.R.attr.state_enabled to Color.GRAY,
+ android.R.attr.state_checked to scheme.primary
+ )
+
+ checkedTextViews.forEach {
+ it.checkMarkTintList = colorStateList
+ }
+ }
+ }
+
+ fun themeCheckbox(vararg checkboxes: CheckBox) {
+ withScheme(checkboxes[0]) { scheme ->
+ val colorStateList = buildColorStateList(
+ -android.R.attr.state_checked to Color.GRAY,
+ -android.R.attr.state_enabled to Color.GRAY,
+ android.R.attr.state_checked to scheme.primary
)
+ checkboxes.forEach {
+ it.buttonTintList = colorStateList
+ }
}
}
fun themeRadioButton(radioButton: RadioButton) {
withScheme(radioButton) { scheme ->
- radioButton.buttonTintList = ColorStateList(
- arrayOf(
- intArrayOf(-android.R.attr.state_checked),
- intArrayOf(android.R.attr.state_checked)
- ),
- intArrayOf(Color.GRAY, scheme.primary)
+ radioButton.buttonTintList = buildColorStateList(
+ -android.R.attr.state_checked to Color.GRAY,
+ android.R.attr.state_checked to scheme.primary
)
}
}
@@ -264,16 +460,11 @@ class AndroidViewThemeUtils @Inject constructor(schemes: MaterialSchemes, privat
withScheme(editText) { scheme ->
// TODO check API-level compatibility
// editText.background.setColorFilter(color, PorterDuff.Mode.SRC_ATOP)
- editText.backgroundTintList = ColorStateList(
- arrayOf(
- intArrayOf(-android.R.attr.state_focused),
- intArrayOf(android.R.attr.state_focused)
- ),
- intArrayOf(
- scheme.outline,
- scheme.primary
- )
+ editText.backgroundTintList = buildColorStateList(
+ -android.R.attr.state_focused to scheme.outline,
+ android.R.attr.state_focused to scheme.primary
)
+
editText.setHintTextColor(scheme.onSurfaceVariant)
editText.setTextColor(scheme.onSurface)
}
@@ -319,6 +510,23 @@ class AndroidViewThemeUtils @Inject constructor(schemes: MaterialSchemes, privat
} while (index != -1)
}
+ // here for backwards compatibility
+ @SuppressLint("UseSwitchCompatOrMaterialCode")
+ fun colorSwitch(switch: Switch) {
+ withScheme(switch) { scheme ->
+ val colors = SwitchColorUtils.calculateSwitchColors(switch.context, scheme)
+ DrawableCompat.setTintList(switch.thumbDrawable, colors.thumbColor)
+ DrawableCompat.setTintList(switch.trackDrawable, colors.trackColor)
+ }
+ }
+
+ @Deprecated("Don't do this, implement custom viewThemeUtils instead")
+ fun primaryColor(activity: Activity): Int {
+ return withScheme(activity) { scheme ->
+ scheme.primary
+ }
+ }
+
companion object {
private const val ON_SURFACE_OPACITY_BUTTON_DISABLED: Float = 0.38f
}
diff --git a/ui/src/main/java/com/nextcloud/android/common/ui/theme/utils/AndroidXViewThemeUtils.kt b/ui/src/main/java/com/nextcloud/android/common/ui/theme/utils/AndroidXViewThemeUtils.kt
index c38c6e84..5ac2de01 100644
--- a/ui/src/main/java/com/nextcloud/android/common/ui/theme/utils/AndroidXViewThemeUtils.kt
+++ b/ui/src/main/java/com/nextcloud/android/common/ui/theme/utils/AndroidXViewThemeUtils.kt
@@ -23,10 +23,21 @@
package com.nextcloud.android.common.ui.theme.utils
+import android.content.Context
import android.content.res.ColorStateList
-import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
+import android.text.Spannable
+import android.text.SpannableString
+import android.text.style.ForegroundColorSpan
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.appcompat.app.ActionBar
+import androidx.appcompat.widget.AppCompatTextView
+import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.SwitchCompat
-import androidx.core.content.res.ResourcesCompat
+import androidx.core.app.NotificationCompat
+import androidx.core.widget.TextViewCompat
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.nextcloud.android.common.ui.R
import com.nextcloud.android.common.ui.theme.MaterialSchemes
@@ -36,52 +47,88 @@ import javax.inject.Inject
/**
* View theme utils for Android extension views (androidx.*)
*/
-class AndroidXViewThemeUtils @Inject constructor(schemes: MaterialSchemes) :
+class AndroidXViewThemeUtils @Inject constructor(
+ schemes: MaterialSchemes,
+ private val androidViewThemeUtils: AndroidViewThemeUtils
+) :
ViewThemeUtilsBase(schemes) {
fun colorSwitchCompat(switchCompat: SwitchCompat) {
withScheme(switchCompat) { scheme ->
+ val colors = SwitchColorUtils.calculateSwitchColors(switchCompat.context, scheme)
+ switchCompat.thumbTintList = colors.thumbColor
+ switchCompat.trackTintList = colors.trackColor
+ }
+ }
- val context = switchCompat.context
+ fun themeSwipeRefreshLayout(swipeRefreshLayout: SwipeRefreshLayout) {
+ withScheme(swipeRefreshLayout) { scheme ->
+ swipeRefreshLayout.setColorSchemeColors(scheme.primary)
+ swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.refresh_spinner_background)
+ }
+ }
- val thumbUncheckedColor = ResourcesCompat.getColor(
- context.resources,
- R.color.switch_thumb_color_unchecked,
- context.theme
- )
- val trackUncheckedColor = ResourcesCompat.getColor(
- context.resources,
- R.color.switch_track_color_unchecked,
- context.theme
- )
+ fun colorPrimaryTextViewElement(textView: AppCompatTextView) {
+ withScheme(textView) { scheme ->
+ textView.setTextColor(scheme.primary)
+ TextViewCompat.setCompoundDrawableTintList(textView, ColorStateList.valueOf(scheme.primary))
+ }
+ }
- val trackColor = Color.argb(
- SWITCH_COMPAT_TRACK_ALPHA,
- Color.red(scheme.primary),
- Color.green(scheme.primary),
- Color.blue(scheme.primary)
- )
+ // TODO host the back arrow in this lib instead of passing it everywhere
+ fun themeActionBar(context: Context, actionBar: ActionBar, title: String, backArrow: Drawable) {
+ withScheme(context) { scheme ->
+ val text: Spannable = getColoredSpan(title, scheme.onSurface)
+ actionBar.title = text
+ themeActionBar(context, actionBar, backArrow)
+ }
+ }
- switchCompat.thumbTintList = ColorStateList(
- arrayOf(intArrayOf(android.R.attr.state_checked), intArrayOf()),
- intArrayOf(scheme.primary, thumbUncheckedColor)
- )
+ fun themeActionBar(context: Context, actionBar: ActionBar, backArrow: Drawable) {
+ withScheme(context) { scheme ->
+ actionBar.setBackgroundDrawable(ColorDrawable(scheme.surface))
+ val indicator = androidViewThemeUtils.colorDrawable(backArrow, scheme.onSurface)
+ actionBar.setHomeAsUpIndicator(indicator)
+ }
+ }
- switchCompat.trackTintList = ColorStateList(
- arrayOf(intArrayOf(android.R.attr.state_checked), intArrayOf()),
- intArrayOf(trackColor, trackUncheckedColor)
- )
+ fun themeActionBarSubtitle(context: Context, actionBar: ActionBar) {
+ withScheme(context) { scheme ->
+ actionBar.subtitle = getColoredSpan(actionBar.subtitle.toString(), scheme.onSurfaceVariant)
}
}
- fun themeSwipeRefreshLayout(swipeRefreshLayout: SwipeRefreshLayout) {
- withScheme(swipeRefreshLayout) { scheme ->
- swipeRefreshLayout.setColorSchemeColors(scheme.primary)
- swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.refresh_spinner_background)
+ fun themeToolbarSearchView(searchView: SearchView) {
+ withScheme(searchView) { scheme ->
+ // hacky as no default way is provided
+ val editText =
+ searchView.findViewById(androidx.appcompat.R.id.search_src_text)
+ val searchPlate = searchView.findViewById(androidx.appcompat.R.id.search_plate)
+ val closeButton = searchView.findViewById(androidx.appcompat.R.id.search_close_btn)
+ val searchButton = searchView.findViewById(androidx.appcompat.R.id.search_button)
+ editText.setHintTextColor(scheme.onSurfaceVariant)
+ editText.highlightColor = scheme.inverseOnSurface
+ editText.setTextColor(scheme.onSurface)
+ closeButton.setColorFilter(scheme.onSurface)
+ searchButton.setColorFilter(scheme.onSurface)
+ searchPlate.setBackgroundColor(scheme.surface)
+ }
+ }
+
+ fun themeNotificationCompatBuilder(context: Context, builder: NotificationCompat.Builder) {
+ withScheme(context) { scheme ->
+ builder.setColor(scheme.primary)
}
}
- companion object {
- private const val SWITCH_COMPAT_TRACK_ALPHA: Int = 77
+ private fun getColoredSpan(title: String, color: Int): Spannable {
+ val text: Spannable = SpannableString(title)
+ text.setSpan(
+ ForegroundColorSpan(color),
+ 0,
+ text.length,
+ Spannable.SPAN_INCLUSIVE_INCLUSIVE
+ )
+ return text
}
}
diff --git a/ui/src/main/java/com/nextcloud/android/common/ui/theme/utils/ColorRole.kt b/ui/src/main/java/com/nextcloud/android/common/ui/theme/utils/ColorRole.kt
new file mode 100644
index 00000000..454aba86
--- /dev/null
+++ b/ui/src/main/java/com/nextcloud/android/common/ui/theme/utils/ColorRole.kt
@@ -0,0 +1,36 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Álvaro Brey
+ * Copyright (C) 2022 Álvaro Brey
+ * Copyright (C) 2022 Nextcloud GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This program 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see .
+ *
+ */
+
+package com.nextcloud.android.common.ui.theme.utils
+
+import scheme.Scheme
+
+/**
+ * Parameter for library methods so that clients can choose color roles without accessing the Scheme directly
+ */
+enum class ColorRole(internal val select: (Scheme) -> Int) {
+ ON_PRIMARY({ it.onPrimary }),
+ ON_SECONDARY_CONTAINER({ it.onSecondaryContainer }),
+ ON_SURFACE({ it.onSurface }),
+ PRIMARY({ it.primary }),
+ SURFACE({ it.surface })
+}
diff --git a/ui/src/main/java/com/nextcloud/android/common/ui/theme/utils/MaterialViewThemeUtils.kt b/ui/src/main/java/com/nextcloud/android/common/ui/theme/utils/MaterialViewThemeUtils.kt
index 258328f1..882e1224 100644
--- a/ui/src/main/java/com/nextcloud/android/common/ui/theme/utils/MaterialViewThemeUtils.kt
+++ b/ui/src/main/java/com/nextcloud/android/common/ui/theme/utils/MaterialViewThemeUtils.kt
@@ -27,6 +27,7 @@ import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.PorterDuff
+import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.button.MaterialButton
@@ -35,6 +36,7 @@ import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipDrawable
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.progressindicator.LinearProgressIndicator
+import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout
import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.textview.MaterialTextView
@@ -42,6 +44,7 @@ import com.nextcloud.android.common.ui.R
import com.nextcloud.android.common.ui.color.ColorUtil
import com.nextcloud.android.common.ui.theme.MaterialSchemes
import com.nextcloud.android.common.ui.theme.ViewThemeUtilsBase
+import com.nextcloud.android.common.ui.util.buildColorStateList
import scheme.Scheme
import javax.inject.Inject
@@ -65,39 +68,42 @@ class MaterialViewThemeUtils @Inject constructor(schemes: MaterialSchemes, priva
fun themeFAB(fab: FloatingActionButton) {
withScheme(fab) { scheme ->
- fab.backgroundTintList = ColorStateList.valueOf(scheme.primaryContainer)
- fab.imageTintList = ColorStateList.valueOf(scheme.onPrimaryContainer)
+ fab.backgroundTintList = buildColorStateList(
+ android.R.attr.state_enabled to scheme.primaryContainer,
+ -android.R.attr.state_enabled to Color.GRAY
+ )
+
+ fab.imageTintList = buildColorStateList(
+ android.R.attr.state_enabled to scheme.onPrimaryContainer,
+ -android.R.attr.state_enabled to Color.WHITE
+ )
}
}
fun themeCardView(cardView: MaterialCardView) {
withScheme(cardView) { scheme ->
cardView.backgroundTintList = ColorStateList.valueOf(scheme.surface)
+ cardView.setStrokeColor(
+ buildColorStateList(
+ android.R.attr.state_checked to scheme.primary,
+ -android.R.attr.state_checked to scheme.outline
+ )
+ )
}
}
fun colorMaterialTextButton(button: MaterialButton) {
withScheme(button) { scheme ->
- button.rippleColor = ColorStateList(
- arrayOf(
- intArrayOf(android.R.attr.state_pressed)
- ),
- intArrayOf(
- colorUtil.adjustOpacity(scheme.primary, SURFACE_OPACITY_BUTTON_DISABLED)
- )
- )
+ button.rippleColor = rippleColor(scheme)
}
}
fun colorMaterialButtonText(button: MaterialButton) {
withScheme(button) { scheme ->
val disabledColor = ContextCompat.getColor(button.context, R.color.disabled_text)
- val colorStateList = ColorStateList(
- arrayOf(
- intArrayOf(android.R.attr.state_enabled),
- intArrayOf(-android.R.attr.state_enabled)
- ),
- intArrayOf(scheme.primary, disabledColor)
+ val colorStateList = buildColorStateList(
+ android.R.attr.state_enabled to scheme.primary,
+ -android.R.attr.state_enabled to disabledColor
)
button.setTextColor(colorStateList)
button.iconTint = colorStateList
@@ -106,96 +112,151 @@ class MaterialViewThemeUtils @Inject constructor(schemes: MaterialSchemes, priva
fun colorMaterialButtonPrimaryFilled(button: MaterialButton) {
withScheme(button) { scheme ->
- button.backgroundTintList =
- ColorStateList(
- arrayOf(
- intArrayOf(android.R.attr.state_enabled),
- intArrayOf(-android.R.attr.state_enabled)
- ),
- intArrayOf(
- scheme.primary,
- colorUtil.adjustOpacity(scheme.onSurface, SURFACE_OPACITY_BUTTON_DISABLED)
- )
+ button.backgroundTintList = buildColorStateList(
+ android.R.attr.state_enabled to scheme.primary,
+ -android.R.attr.state_enabled to colorUtil.adjustOpacity(
+ scheme.onSurface,
+ SURFACE_OPACITY_BUTTON_DISABLED
)
+ )
- button.setTextColor(
- ColorStateList(
- arrayOf(
- intArrayOf(android.R.attr.state_enabled),
- intArrayOf(-android.R.attr.state_enabled)
- ),
- intArrayOf(
- scheme.onPrimary,
- colorUtil.adjustOpacity(scheme.onSurface, ON_SURFACE_OPACITY_BUTTON_DISABLED)
- )
+ val contentColorList = buildColorStateList(
+ android.R.attr.state_enabled to scheme.onPrimary,
+ -android.R.attr.state_enabled to colorUtil.adjustOpacity(
+ scheme.onSurface,
+ SURFACE_OPACITY_BUTTON_DISABLED
)
)
- button.iconTint = ColorStateList(
- arrayOf(
- intArrayOf(android.R.attr.state_enabled),
- intArrayOf(-android.R.attr.state_enabled)
+ button.setTextColor(contentColorList)
+ button.iconTint = contentColorList
+ }
+ }
+
+ fun colorMaterialButtonPrimaryTonal(button: MaterialButton) {
+ withScheme(button) { scheme ->
+ button.backgroundTintList = buildColorStateList(
+ android.R.attr.state_enabled to scheme.secondaryContainer,
+ -android.R.attr.state_enabled to colorUtil.adjustOpacity(
+ scheme.onSurface,
+ SURFACE_OPACITY_BUTTON_DISABLED
),
- intArrayOf(
- scheme.onPrimary,
- colorUtil.adjustOpacity(scheme.onSurface, ON_SURFACE_OPACITY_BUTTON_DISABLED)
- )
+ -android.R.attr.state_hovered to scheme.onSecondaryContainer,
+ -android.R.attr.state_focused to scheme.onSecondaryContainer,
+ -android.R.attr.state_pressed to scheme.onSecondaryContainer
)
+
+ val contentColorList = buildColorStateList(
+ android.R.attr.state_enabled to scheme.onSecondaryContainer,
+ -android.R.attr.state_enabled to colorUtil.adjustOpacity(
+ scheme.onSurface,
+ ON_SURFACE_OPACITY_BUTTON_DISABLED
+ ),
+ -android.R.attr.state_hovered to scheme.onSecondaryContainer,
+ -android.R.attr.state_focused to scheme.onSecondaryContainer,
+ -android.R.attr.state_pressed to scheme.onSecondaryContainer
+ )
+ button.setTextColor(contentColorList)
+ button.iconTint = contentColorList
}
}
fun colorMaterialButtonPrimaryOutlined(button: MaterialButton) {
withScheme(button) { scheme ->
- button.strokeColor = ColorStateList.valueOf(scheme.outline)
- button.setTextColor(
- ColorStateList(
- arrayOf(
- intArrayOf(android.R.attr.state_enabled),
- intArrayOf(-android.R.attr.state_enabled)
- ),
- intArrayOf(
- scheme.primary,
- colorUtil.adjustOpacity(scheme.onSurface, ON_SURFACE_OPACITY_BUTTON_DISABLED)
- )
+ val contentColorList = buildColorStateList(
+ android.R.attr.state_enabled to scheme.primary,
+ -android.R.attr.state_enabled to colorUtil.adjustOpacity(
+ scheme.onSurface,
+ ON_SURFACE_OPACITY_BUTTON_DISABLED
)
)
- button.iconTint = ColorStateList(
- arrayOf(
- intArrayOf(android.R.attr.state_enabled),
- intArrayOf(-android.R.attr.state_enabled)
+
+ button.setTextColor(contentColorList)
+ button.iconTint = contentColorList
+ button.strokeColor = buildColorStateList(
+ android.R.attr.state_enabled to scheme.outline,
+ -android.R.attr.state_enabled to colorUtil.adjustOpacity(
+ scheme.onSurface,
+ ON_SURFACE_OPACITY_BUTTON_OUTLINE_DISABLED
),
- intArrayOf(
- scheme.primary,
- colorUtil.adjustOpacity(scheme.onSurface, ON_SURFACE_OPACITY_BUTTON_DISABLED)
- )
+ -android.R.attr.state_hovered to scheme.outline,
+ -android.R.attr.state_focused to scheme.primary,
+ -android.R.attr.state_pressed to scheme.outline
)
+
+ button.strokeWidth =
+ button.resources.getDimension(R.dimen.outlinedButtonStrokeWidth).toInt()
+ button.rippleColor = rippleColor(scheme)
}
}
fun colorMaterialButtonPrimaryBorderless(button: MaterialButton) {
withScheme(button) { scheme ->
- button.setTextColor(
- ColorStateList(
- arrayOf(
- intArrayOf(android.R.attr.state_enabled),
- intArrayOf(-android.R.attr.state_enabled)
- ),
- intArrayOf(
- scheme.primary,
- colorUtil.adjustOpacity(scheme.onSurface, ON_SURFACE_OPACITY_BUTTON_DISABLED)
- )
+ val contentColorList = buildColorStateList(
+ android.R.attr.state_enabled to scheme.primary,
+ -android.R.attr.state_enabled to colorUtil.adjustOpacity(
+ scheme.onSurface,
+ ON_SURFACE_OPACITY_BUTTON_DISABLED
)
)
- button.iconTint = ColorStateList(
- arrayOf(
- intArrayOf(android.R.attr.state_enabled),
- intArrayOf(-android.R.attr.state_enabled)
+
+ button.setTextColor(contentColorList)
+ button.iconTint = contentColorList
+ }
+ }
+
+ /**
+ * text is primary, background is on_primary
+ */
+ fun colorMaterialButtonFilledOnPrimary(button: MaterialButton) {
+ withScheme(button) { scheme ->
+ button.backgroundTintList = buildColorStateList(
+ android.R.attr.state_enabled to scheme.onPrimary,
+ -android.R.attr.state_enabled to colorUtil.adjustOpacity(
+ scheme.surface,
+ SURFACE_OPACITY_BUTTON_DISABLED
),
- intArrayOf(
+ -android.R.attr.state_hovered to scheme.onPrimary,
+ -android.R.attr.state_focused to scheme.onPrimary,
+ -android.R.attr.state_pressed to scheme.onPrimary
+ )
+
+ val contentColorList = buildColorStateList(
+ android.R.attr.state_enabled to scheme.primary,
+ -android.R.attr.state_enabled to colorUtil.adjustOpacity(
scheme.primary,
- colorUtil.adjustOpacity(scheme.onSurface, ON_SURFACE_OPACITY_BUTTON_DISABLED)
+ ON_SURFACE_OPACITY_BUTTON_DISABLED
+ ),
+ -android.R.attr.state_hovered to scheme.primary,
+ -android.R.attr.state_focused to scheme.primary,
+ -android.R.attr.state_pressed to scheme.primary
+ )
+
+ button.setTextColor(
+ contentColorList
+ )
+
+ button.iconTint = contentColorList
+ }
+ }
+
+ fun colorMaterialButtonOutlinedOnPrimary(button: MaterialButton) {
+ withScheme(button) { scheme ->
+ button.backgroundTintList = ColorStateList.valueOf(Color.TRANSPARENT)
+ val contentColorList = buildColorStateList(
+ android.R.attr.state_enabled to scheme.onPrimary,
+ -android.R.attr.state_enabled to colorUtil.adjustOpacity(
+ scheme.onPrimary,
+ ON_SURFACE_OPACITY_BUTTON_DISABLED
)
)
+
+ button.setTextColor(contentColorList)
+ button.iconTint = contentColorList
+ button.strokeColor = contentColorList
+ button.strokeWidth =
+ button.resources.getDimension(R.dimen.outlinedButtonStrokeWidth).toInt()
+ button.rippleColor = rippleColor(scheme)
}
}
@@ -207,41 +268,34 @@ class MaterialViewThemeUtils @Inject constructor(schemes: MaterialSchemes, priva
}
}
- fun colorCardViewBackground(card: MaterialCardView) {
- withScheme(card) { scheme ->
- card.setCardBackgroundColor(scheme.surfaceVariant)
- }
- }
+ @Deprecated(
+ "Duplicated, use themeCardView instead",
+ replaceWith = ReplaceWith("themeCardView(card)")
+ )
+ fun colorCardViewBackground(card: MaterialCardView) = themeCardView(card)
fun colorProgressBar(progressIndicator: LinearProgressIndicator) {
withScheme(progressIndicator) { scheme ->
- progressIndicator.setIndicatorColor(scheme.primary)
+ colorProgressBar(progressIndicator, scheme.primary)
}
}
+ fun colorProgressBar(progressIndicator: LinearProgressIndicator, @ColorInt color: Int) {
+ progressIndicator.setIndicatorColor(color)
+ }
+
fun colorTextInputLayout(textInputLayout: TextInputLayout) {
withScheme(textInputLayout) { scheme ->
val errorColor = scheme.onSurfaceVariant
- val errorColorStateList = ColorStateList(
- arrayOf(
- intArrayOf(-android.R.attr.state_focused),
- intArrayOf(android.R.attr.state_focused)
- ),
- intArrayOf(
- errorColor,
- errorColor
- )
+ val errorColorStateList = buildColorStateList(
+ -android.R.attr.state_focused to errorColor,
+ android.R.attr.state_focused to errorColor
)
- val coloredColorStateList = ColorStateList(
- arrayOf(
- intArrayOf(-android.R.attr.state_focused),
- intArrayOf(android.R.attr.state_focused)
- ),
- intArrayOf(
- scheme.outline,
- scheme.primary
- )
+
+ val coloredColorStateList = buildColorStateList(
+ -android.R.attr.state_focused to scheme.outline,
+ android.R.attr.state_focused to scheme.primary
)
textInputLayout.setBoxStrokeColorStateList(coloredColorStateList)
@@ -249,6 +303,14 @@ class MaterialViewThemeUtils @Inject constructor(schemes: MaterialSchemes, priva
textInputLayout.setErrorTextColor(errorColorStateList)
textInputLayout.boxStrokeErrorColor = errorColorStateList
textInputLayout.defaultHintTextColor = coloredColorStateList
+
+ textInputLayout.editText?.highlightColor = scheme.primary
+ }
+ }
+
+ fun themeTabLayout(tabLayout: TabLayout) {
+ withScheme(tabLayout) { scheme ->
+ colorTabLayout(tabLayout, scheme)
}
}
@@ -261,24 +323,17 @@ class MaterialViewThemeUtils @Inject constructor(schemes: MaterialSchemes, priva
fun colorTabLayout(tabLayout: TabLayout, scheme: Scheme) {
tabLayout.setSelectedTabIndicatorColor(scheme.primary)
- tabLayout.tabTextColors = ColorStateList(
- arrayOf(
- intArrayOf(android.R.attr.state_selected),
- intArrayOf(-android.R.attr.state_selected)
- ),
- intArrayOf(
- scheme.primary,
- ContextCompat.getColor(tabLayout.context, R.color.high_emphasis_text)
- )
- )
- tabLayout.tabRippleColor = ColorStateList(
- arrayOf(
- intArrayOf(android.R.attr.state_pressed)
- ),
- intArrayOf(
- colorUtil.adjustOpacity(scheme.primary, SURFACE_OPACITY_BUTTON_DISABLED)
+ val tabContentColors = buildColorStateList(
+ android.R.attr.state_selected to scheme.primary,
+ -android.R.attr.state_selected to ContextCompat.getColor(
+ tabLayout.context,
+ R.color.high_emphasis_text
)
)
+
+ tabLayout.tabTextColors = tabContentColors
+ tabLayout.tabIconTint = tabContentColors
+ tabLayout.tabRippleColor = rippleColor(scheme)
}
fun colorChipBackground(chip: Chip) {
@@ -304,8 +359,24 @@ class MaterialViewThemeUtils @Inject constructor(schemes: MaterialSchemes, priva
}
}
+ fun themeSnackbar(snackbar: Snackbar) {
+ withScheme(snackbar.context) { scheme ->
+ snackbar.setBackgroundTint(scheme.inverseSurface)
+ snackbar.setActionTextColor(scheme.inversePrimary)
+ snackbar.setTextColor(scheme.inverseOnSurface)
+ }
+ }
+
+ private fun rippleColor(scheme: Scheme) = buildColorStateList(
+ android.R.attr.state_pressed to colorUtil.adjustOpacity(
+ scheme.primary,
+ SURFACE_OPACITY_BUTTON_DISABLED
+ )
+ )
+
companion object {
private const val SURFACE_OPACITY_BUTTON_DISABLED: Float = 0.12f
+ private const val ON_SURFACE_OPACITY_BUTTON_OUTLINE_DISABLED: Float = 0.12f
private const val ON_SURFACE_OPACITY_BUTTON_DISABLED: Float = 0.38f
}
}
diff --git a/ui/src/main/java/com/nextcloud/android/common/ui/theme/utils/SwitchColorUtils.kt b/ui/src/main/java/com/nextcloud/android/common/ui/theme/utils/SwitchColorUtils.kt
new file mode 100644
index 00000000..8de89a5c
--- /dev/null
+++ b/ui/src/main/java/com/nextcloud/android/common/ui/theme/utils/SwitchColorUtils.kt
@@ -0,0 +1,74 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Álvaro Brey
+ * Copyright (C) 2022 Álvaro Brey
+ * Copyright (C) 2022 Nextcloud GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This program 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see .
+ *
+ */
+
+package com.nextcloud.android.common.ui.theme.utils
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Color
+import androidx.core.content.res.ResourcesCompat
+import com.nextcloud.android.common.ui.R
+import scheme.Scheme
+
+/**
+ * To be used to calculate color lists for both Switch and SwitchCompat
+ */
+internal object SwitchColorUtils {
+
+ private const val SWITCH_COMPAT_TRACK_ALPHA: Int = 77
+
+ data class SwitchColors(
+ val thumbColor: ColorStateList,
+ val trackColor: ColorStateList
+ )
+
+ fun calculateSwitchColors(context: Context, scheme: Scheme): SwitchColors {
+ val thumbUncheckedColor = ResourcesCompat.getColor(
+ context.resources,
+ R.color.switch_thumb_color_unchecked,
+ context.theme
+ )
+ val trackUncheckedColor = ResourcesCompat.getColor(
+ context.resources,
+ R.color.switch_track_color_unchecked,
+ context.theme
+ )
+
+ val trackColor = Color.argb(
+ SWITCH_COMPAT_TRACK_ALPHA,
+ Color.red(scheme.primary),
+ Color.green(scheme.primary),
+ Color.blue(scheme.primary)
+ )
+
+ return SwitchColors(
+ thumbColor = ColorStateList(
+ arrayOf(intArrayOf(android.R.attr.state_checked), intArrayOf()),
+ intArrayOf(scheme.primary, thumbUncheckedColor)
+ ),
+ trackColor = ColorStateList(
+ arrayOf(intArrayOf(android.R.attr.state_checked), intArrayOf()),
+ intArrayOf(trackColor, trackUncheckedColor)
+ )
+ )
+ }
+}
diff --git a/ui/src/main/java/com/nextcloud/android/common/ui/util/ColorStateListUtils.kt b/ui/src/main/java/com/nextcloud/android/common/ui/util/ColorStateListUtils.kt
new file mode 100644
index 00000000..05ea3ae8
--- /dev/null
+++ b/ui/src/main/java/com/nextcloud/android/common/ui/util/ColorStateListUtils.kt
@@ -0,0 +1,31 @@
+/*
+ * Nextcloud Android Common Library
+ *
+ * Copyright (C) 2022 Nextcloud GmbH
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see .
+ */
+
+package com.nextcloud.android.common.ui.util
+
+import android.content.res.ColorStateList
+
+/**
+ * First element of each pair is @AttRes (can be negated) and second one is @ColorInt
+ */
+fun buildColorStateList(vararg pairs: Pair): ColorStateList {
+ val stateArray = pairs.map { intArrayOf(it.first) }.toTypedArray()
+ val colorArray = pairs.map { it.second }.toIntArray()
+ return ColorStateList(stateArray, colorArray)
+}
diff --git a/ui/src/main/java/com/nextcloud/android/common/ui/util/PlatformThemeUtil.kt b/ui/src/main/java/com/nextcloud/android/common/ui/util/PlatformThemeUtil.kt
index 973eea93..04603205 100644
--- a/ui/src/main/java/com/nextcloud/android/common/ui/util/PlatformThemeUtil.kt
+++ b/ui/src/main/java/com/nextcloud/android/common/ui/util/PlatformThemeUtil.kt
@@ -25,6 +25,7 @@ import android.content.Context
import android.content.res.Configuration
object PlatformThemeUtil {
+ @JvmStatic
fun isDarkMode(context: Context): Boolean =
when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_YES -> true
diff --git a/ui/src/main/res/values/dimens.xml b/ui/src/main/res/values/dimens.xml
index 354fce31..12e08de0 100644
--- a/ui/src/main/res/values/dimens.xml
+++ b/ui/src/main/res/values/dimens.xml
@@ -19,4 +19,5 @@
28dp
+ 1dp