Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Developing in the Dark

Daniele
April 21, 2020

Developing in the Dark

Dark Mode is live! Android 10 brought this option in front of the users and apps are starting to adopt it, but it's not easy. In this talk, Daniele will share the journey of the rider app at Deliveroo to support dark theme: how to migrate to the new material components library, how to tackle the changes in a design system that supports multiple apps, how to design a new day and night theme and how to approach this migration in a team with the constraint of a release every 2 weeks.

Daniele

April 21, 2020
Tweet

Other Decks in Programming

Transcript

  1. Why

  2. Android >= 10 Android < 10 Night qualifier automatic ✅

    ?attr/ ✅ Android >= 6 Options Light, dark, 
 system default Light, dark, 
 by battery saver
  3. How we got there Lint checks Material Components Library Adoption

    Design System
 Night Mode App Night Mode
  4. @Test fun `should report a missing night color`() { val

    colorFile = TestFiles.xml("res/values/colors.xml", """<?xml version="1.0" encoding="utf-8"?> <resources> <color name="color_primary">#00a7f7</color> <color name="color_primary_slightly_dark">#0193e8</color> </resources>""").indented() val colorNightFile = TestFiles.xml("res/values-night/colors.xml", """<?xml version="1.0" encoding="utf-8"?> <resources> <color name="color_primary">#224411</color> </resources>""").indented() TestLintTask.lint() .files(colorFile, colorNightFile) .issues(MISSING_NIGHT_COLOR_ISSUE) .run() .expect(""" |res/values/colors.xml:4: Error: Night color value for this color resource seems to be missing [MissingNightColor] | <color name="color_primary_slightly_dark">#0193e8</color> | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |1 errors, 0 warnings""".trimMargin()) }
  5. private val nightModeColors = mutableListOf<String>() private val regularColors = mutableMapOf<String,

    Location>() override fun visitElement(context: XmlContext, element: Element) { if (context.getFolderConfiguration()?.isDefault()){ regularColors[element.getAttribute("name")] = context.getLocation(element) } if (context.getFolderConfiguration()?.isNight()) nightModeColors.add(element.getAttribute(“name")) } }
  6. private val nightModeColors = mutableListOf<String>() private val regularColors = mutableMapOf<String,

    Location>() override fun visitElement(context: XmlContext, element: Element) { if (context.getFolderConfiguration()?.isDefault()){ regularColors[element.getAttribute("name")] = context.getLocation(element) } if (context.getFolderConfiguration()?.isNight()) nightModeColors.add(element.getAttribute(“name")) } }
  7. private val nightModeColors = mutableListOf<String>() private val regularColors = mutableMapOf<String,

    Location>() override fun visitElement(context: XmlContext, element: Element) { if (context.getFolderConfiguration()?.isDefault()){ regularColors[element.getAttribute("name")] = context.getLocation(element) } if (context.getFolderConfiguration()?.isNight()) nightModeColors.add(element.getAttribute(“name")) } }
  8. private val nightModeColors = mutableListOf<String>() private val regularColors = mutableMapOf<String,

    Location>() override fun visitElement(context: XmlContext, element: Element) { if (context.getFolderConfiguration()?.isDefault()){ regularColors[element.getAttribute("name")] = context.getLocation(element) } if (context.getFolderConfiguration()?.isNight()) nightModeColors.add(element.getAttribute(“name")) } }
  9. override fun getApplicableElements(): Collection<String>? { return listOf("color") } override fun

    afterCheckEachProject(context: Context) { regularColors.forEach { (color, location) -> if (!nightModeColors.contains(color)) context.report( MISSING_NIGHT_COLOR_ISSUE, location, MISSING_NIGHT_COLOR_ISSUE.getExplanation(TextFormat.RAW) ) } }
  10. override fun getApplicableElements(): Collection<String>? { return listOf("color") } override fun

    afterCheckEachProject(context: Context) { regularColors.forEach { (color, location) -> if (!nightModeColors.contains(color)) context.report( MISSING_NIGHT_COLOR_ISSUE, location, MISSING_NIGHT_COLOR_ISSUE.getExplanation(TextFormat.RAW) ) } }
  11. override fun getApplicableElements(): Collection<String>? { return listOf("color") } override fun

    afterCheckEachProject(context: Context) { regularColors.forEach { (color, location) -> if (!nightModeColors.contains(color)) context.report( MISSING_NIGHT_COLOR_ISSUE, location, MISSING_NIGHT_COLOR_ISSUE.getExplanation(TextFormat.RAW) ) } }
  12. @Test fun `should report a direct color`() { val contentFile

    = """<?xml version="1.0" encoding="utf-8"?> <View xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/toolbar" android:background="#453344" android:foreground="#667788" android:layout_width="match_parent" android:layout_height="wrap_content" />""" TestLintTask.lint() .files(TestFiles.xml("res/layout/toolbar.xml", contentFile).indented()) .issues(DIRECT_COLOR_ISSUE) .run() .expect(""" |res/layout/toolbar.xml:5: Error: Avoid direct use of colors in XML files. [DirectColorUse] | android:background="#453344" | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |res/layout/toolbar.xml:6: Error: Avoid direct use of colors in XML files. [DirectColorUse] | android:foreground="#667788" | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |2 errors, 0 warnings""".trimMargin()) }
  13. override fun getApplicableAttributes(): Collection<String>? { return listOf("background", "foreground", "src", "textColor",

    "tint", "color") } override fun visitAttribute(context: XmlContext, attribute: Attr) { if (attribute.value.startsWith("#")) { context.report( DIRECT_COLOR_ISSUE, context.getLocation(attribute), DIRECT_COLOR_ISSUE.getExplanation(TextFormat.RAW)) } }
  14. override fun getApplicableAttributes(): Collection<String>? { return listOf("background", "foreground", "src", “textColor",

    "tint", "color") } override fun visitAttribute(context: XmlContext, attribute: Attr) { if (attribute.value.startsWith("#")) { context.report( DIRECT_COLOR_ISSUE, context.getLocation(attribute), DIRECT_COLOR_ISSUE.getExplanation(TextFormat.RAW)) } }
  15. override fun getApplicableAttributes(): Collection<String>? { return listOf("background", "foreground", "src", “textColor",

    "tint", "color") } override fun visitAttribute(context: XmlContext, attribute: Attr) { if (attribute.value.startsWith("#")) { context.report( DIRECT_COLOR_ISSUE, context.getLocation(attribute), DIRECT_COLOR_ISSUE.getExplanation(TextFormat.RAW)) } }
  16. @Test fun `should report a non semantic color usage`() {

    val contentFile = """<?xml version="1.0" encoding="utf-8"?> <View android:id="@+id/toolbar" android:background="@color/white" android:foreground="@color/red" android:layout_width="match_parent" android:layout_height="wrap_content" />""" TestLintTask.lint() .files(TestFiles.xml("res/layout/toolbar.xml", contentFile).indented()) .issues(NON_SEMANTIC_COLOR_ISSUE) .run() .expect(""" |res/layout/toolbar.xml:5: Error: Avoid non semantic use of colors in XML files. [NonSemanticColorUse] | android:background="@color/white" | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |res/layout/toolbar.xml:6: Error: Avoid non semantic use of colors in XML files. [NonSemanticColorUse] | android:foreground="@color/red" | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |2 errors, 0 warnings""".trimMargin()) }
  17. override fun getApplicableAttributes() = listOf("background", "foreground", "src", "textColor", "tint", "color")

    override fun visitAttribute(context: XmlContext, attribute: Attr) { val found = listOf( "anchovy", "aubergine", "black", "burger", "teal", “white", "orange", "red", "chiliJam").any { attribute.value.contains(it) } if (found) { context.report( NON_SEMANTIC_COLOR_ISSUE, context.getLocation(attribute), NON_SEMANTIC_COLOR_ISSUE.getExplanation(TextFormat.RAW)) } }
  18. override fun getApplicableAttributes() = listOf("background", "foreground", "src", "textColor", "tint", "color")

    override fun visitAttribute(context: XmlContext, attribute: Attr) { val found = listOf( "anchovy", "aubergine", "black", "burger", "teal", “white", "orange", "red", "chiliJam").any { attribute.value.contains(it) } if (found) { context.report( NON_SEMANTIC_COLOR_ISSUE, context.getLocation(attribute), NON_SEMANTIC_COLOR_ISSUE.getExplanation(TextFormat.RAW)) } }
  19. override fun getApplicableAttributes() = listOf("background", "foreground", "src", "textColor", "tint", "color")

    override fun visitAttribute(context: XmlContext, attribute: Attr) { val found = listOf( "anchovy", "aubergine", "black", "burger", "teal", “white", "orange", "red", "chiliJam").any { attribute.value.contains(it) } if (found) { context.report( NON_SEMANTIC_COLOR_ISSUE, context.getLocation(attribute), NON_SEMANTIC_COLOR_ISSUE.getExplanation(TextFormat.RAW)) } }
  20. • Theme.MaterialComponents.DayNight • Theme.MaterialComponents.DayNight.NoActionBar • Theme.MaterialComponents.DayNight.DarkActionBar • Theme.MaterialComponents.DayNight • Theme.MaterialComponents.DayNight.NoActionBar

    • Theme.MaterialComponents.DayNight.DarkActionBar https://github.com/material-components/material-components-android/
 blob/master/docs/theming/Dark.md 1.1.0 from 03/02/2020
  21. • Theme.MaterialComponents.DayNight • Theme.MaterialComponents.DayNight.NoActionBar • Theme.MaterialComponents.DayNight.DarkActionBar <item name="colorPrimary">@color/my_app_primary_color</item> <item name="colorSecondary">@color/my_app_secondary_color</item>

    <item name="android:colorBackground">@color/my_app_background_color</item> <item name="colorError">@color/my_app_error_color</item> <!-- New MaterialComponents attributes. --> <item name="colorPrimaryVariant">@color/my_app_primary_variant_color</item> <item name="colorSecondaryVariant">@color/my_app_secondary_variant_color</item> <item name="colorSurface">@color/my_app_surface_color</item> <item name="colorOnPrimary">@color/my_app_color_on_primary</item> <item name="colorOnSecondary">@color/my_app_color_on_secondary</item> <item name="colorOnBackground">@color/my_app_color_on_background</item> <item name="colorOnError">@color/my_app_color_on_error</item> <item name="colorOnSurface">@color/my_app_color_on_surface</item> <item name="scrimBackground">@color/mtrl_scrim_color</item>
  22. <declare-styleable name="UiKitTheme"> <attr name="backgroundMainColor" format="reference" /> <attr name="backgroundSurfaceColor" format="reference" />

    <attr name="backgroundBrandColor" format="reference" /> <attr name="backgroundSuccessColor" format="reference" /> <attr name="backgroundAttentionColor" format="reference" /> <attr name="backgroundErrorColor" format="reference" /> <attr name="textColorPrimary" format="reference" /> <attr name="textColorSuccess" format="reference" /> <attr name="textColorError" format="reference" /> <attr name="textColorAttention" format="reference" /> <attr name="iconColorPrimary" format="reference" /> <attr name="iconColorSuccess" format="reference" /> <attr name="iconColorAttention" format="reference" /> <attr name="iconColorError" format="reference" /> </declare-styleable> src/main/res/values/attrs.xml
  23. <style name="UiKitTheme" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="backgroundMainColor">@color/anchovy_5</item> <item name="backgroundSurfaceColor">@color/white</item> <item name="textColorPrimary">@color/black_100</item> ...

    </style> <style name="UiKitTheme.DayNight" parent="Theme.MaterialComponents.DayNight.NoActionBar"> <item name="backgroundMainColor">@color/main</item> <item name="backgroundSurfaceColor">@color/surfaceColor</item> <item name="textColorPrimary">@color/textColorPrimary</item> ... </style>
  24. <style name="UiKitTheme" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="backgroundMainColor">@color/anchovy_5</item> <item name="backgroundSurfaceColor">@color/white</item> <item name="textColorPrimary">@color/black_100</item> ...

    </style> <style name="UiKitTheme.DayNight" parent="Theme.MaterialComponents.DayNight.NoActionBar"> <item name="backgroundMainColor">@color/main</item> <item name="backgroundSurfaceColor">@color/surfaceColor</item> <item name="textColorPrimary">@color/textColorPrimary</item> ... </style> Direct color
  25. <style name="UiKitTheme" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="backgroundMainColor">@color/anchovy_5</item> <item name="backgroundSurfaceColor">@color/white</item> <item name="textColorPrimary">@color/black_100</item> ...

    </style> <style name="UiKitTheme.DayNight" parent="Theme.MaterialComponents.DayNight.NoActionBar"> <item name="backgroundMainColor">@color/main</item> <item name="backgroundSurfaceColor">@color/surfaceColor</item> <item name="textColorPrimary">@color/textColorPrimary</item> ... </style> Day and night colors
  26. <style name=“UiKitTheme"> <item name="textColorPrimary">@color/black_100</item> ... </style> <style name="UiKitTheme.DayNight"> <item name=“textColorPrimary">@color/textColorPrimary</item>

    ... </style> src/main/res/values/colors.xml <color name=“textColorPrimary">@color/black_100</color> src/main/res/values-night/colors.xml <color name="textColorPrimary">@color/white_alpha_87</color>
  27. Day Night UiKitTheme @color/black_100 @color/black_100 UiKitTheme
 .DayNight @color/black_100 @color/white_alpha_87 <style

    name="UIKit.TextAppearance" parent="TextAppearance.AppCompat"> <item name="android:textColor">?attr/textColorPrimary</item> </style>
  28. <style name="UiKitTheme" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="backgroundMainColor">@color/anchovy_5</item> <item name="backgroundSurfaceColor">@color/white</item> </style> <style name="UiKitTheme.DayNight"

    parent="Theme.MaterialComponents.DayNight.NoActionBar"> <item name="backgroundMainColor">@color/main</item> <item name="backgroundSurfaceColor">@color/surfaceColor</item> </style> <color name="main">@color/anchovy_5</color> <color name="surfaceColor">@color/white</color> <color name="main">#121212</color> <color name="surfaceColor">#1E1E1E</color>
  29. <style name="UiKitTheme" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="backgroundMainColor">@color/anchovy_5</item> <item name="backgroundSurfaceColor">@color/white</item> </style> <style name="UiKitTheme.DayNight"

    parent="Theme.MaterialComponents.DayNight.NoActionBar"> <item name="backgroundMainColor">@color/main</item> <item name="backgroundSurfaceColor">@color/surfaceColor</item> </style> <color name="main">@color/anchovy_5</color> <color name="surfaceColor">@color/white</color> <color name="main">#121212</color> <color name="surfaceColor">#1E1E1E</color> values-night/ values/
  30. enum class Type { PRIMARY, SECONDARY, TERTIARY, PRIMARY_ALT, SECONDARY_ALT, TERTIARY_ALT,

    PRIMARY_PLUS, SECONDARY_PLUS, TERTIARY_PLUS } private fun getBackgroundDrawable(): Drawable? { return when (type) { Type.PRIMARY -> getDrawable(R.drawable.button_background_primary) Type.SECONDARY -> getDrawable(R.drawable.button_background_secondary) Type.PRIMARY_ALT -> getDrawable(R.drawable.button_background_primary_alt) Type.PRIMARY_PLUS -> getDrawable(R.drawable.button_background_primary_plus) Type.SECONDARY_ALT -> getDrawable(R.drawable.button_background_secondary_alt) Type.SECONDARY_PLUS -> getDrawable(R.drawable.button_background_secondary_alt) Type.TERTIARY, Type.TERTIARY_ALT, Type.TERTIARY_PLUS -> null } }
  31. enum class Type { PRIMARY, SECONDARY, TERTIARY, PRIMARY_ALT, SECONDARY_ALT, TERTIARY_ALT,

    PRIMARY_PLUS, SECONDARY_PLUS, TERTIARY_PLUS } private fun getTextColor(): ColorStateList? { val colorResId = when (type) { Type.PRIMARY -> R.color.button_text_primary Type.SECONDARY -> R.color.button_text_secondary Type.TERTIARY -> R.color.button_text_tertiary Type.PRIMARY_ALT, Type.PRIMARY_PLUS -> R.color.button_text_primary_alt Type.SECONDARY_ALT -> R.color.button_text_secondary_alt Type.TERTIARY_ALT -> R.color.button_text_tertiary_alt Type.SECONDARY_PLUS, Type.TERTIARY_PLUS -> R.color.button_text_secondary_plus } return context.colorStateList(colorResId) }
  32. <attr name="backgroundButtonPrimary" format="reference"/> <attr name="backgroundButtonPrimaryDestructive" format="reference"/> <attr name="backgroundButtonPrimaryDisabled" format="reference"/> <attr

    name="backgroundButtonSecondary" format="reference"/> <attr name="backgroundButtonSecondaryDestructive" format="reference"/> <attr name="backgroundButtonSecondaryDisabled" format="reference"/> <attr name="buttonOutlineColor" format="reference"/> <attr name="buttonOutlineColorDestructive" format="reference"/> <attr name="buttonOutlineColorDisabled" format="reference"/> <attr name="textButtonPrimary" format="reference"/> <attr name="textButtonPrimaryDestructive" format="reference"/> <attr name="textButtonPrimaryDisabled" format="reference"/> <attr name="textButtonSecondary" format="reference"/> <attr name="textButtonSecondaryDestructive" format="reference"/> <attr name="textButtonSecondaryDisabled" format="reference"/> <attr name="textButtonTertiary" format="reference"/> <attr name="textButtonTertiaryDestructive" format="reference"/> <attr name="textButtonTertiaryDisabled" format="reference"/>
  33. <attr name="backgroundButtonPrimary" format="reference"/> <attr name="backgroundButtonPrimaryDestructive" format="reference"/> <attr name="backgroundButtonPrimaryDisabled" format="reference"/> <attr

    name="backgroundButtonSecondary" format="reference"/> <attr name="backgroundButtonSecondaryDestructive" format="reference"/> <attr name="backgroundButtonSecondaryDisabled" format="reference"/> <attr name="buttonOutlineColor" format="reference"/> <attr name="buttonOutlineColorDestructive" format="reference"/> <attr name="buttonOutlineColorDisabled" format="reference"/> <attr name="textButtonPrimary" format="reference"/> <attr name="textButtonPrimaryDestructive" format="reference"/> <attr name="textButtonPrimaryDisabled" format="reference"/> <attr name="textButtonSecondary" format="reference"/> <attr name="textButtonSecondaryDestructive" format="reference"/> <attr name="textButtonSecondaryDisabled" format="reference"/> <attr name="textButtonTertiary" format="reference"/> <attr name="textButtonTertiaryDestructive" format="reference"/> <attr name="textButtonTertiaryDisabled" format="reference"/>
  34. <attr name="backgroundButtonPrimary" format="reference"/> <attr name="backgroundButtonPrimaryDestructive" format="reference"/> <attr name="backgroundButtonPrimaryDisabled" format="reference"/> <attr

    name="backgroundButtonSecondary" format="reference"/> <attr name="backgroundButtonSecondaryDestructive" format="reference"/> <attr name="backgroundButtonSecondaryDisabled" format="reference"/> <attr name="buttonOutlineColor" format="reference"/> <attr name="buttonOutlineColorDestructive" format="reference"/> <attr name="buttonOutlineColorDisabled" format="reference"/> <attr name="textButtonPrimary" format="reference"/> <attr name="textButtonPrimaryDestructive" format="reference"/> <attr name="textButtonPrimaryDisabled" format="reference"/> <attr name="textButtonSecondary" format="reference"/> <attr name="textButtonSecondaryDestructive" format="reference"/> <attr name="textButtonSecondaryDisabled" format="reference"/> <attr name="textButtonTertiary" format="reference"/> <attr name="textButtonTertiaryDestructive" format="reference"/> <attr name="textButtonTertiaryDisabled" format="reference"/>
  35. <attr name="backgroundButtonPrimary" format="reference"/> <attr name="backgroundButtonPrimaryDestructive" format="reference"/> <attr name="backgroundButtonPrimaryDisabled" format="reference"/> <attr

    name="backgroundButtonSecondary" format="reference"/> <attr name="backgroundButtonSecondaryDestructive" format="reference"/> <attr name="backgroundButtonSecondaryDisabled" format="reference"/> <attr name="buttonOutlineColor" format="reference"/> <attr name="buttonOutlineColorDestructive" format="reference"/> <attr name="buttonOutlineColorDisabled" format="reference"/> <attr name="textButtonPrimary" format="reference"/> <attr name="textButtonPrimaryDestructive" format="reference"/> <attr name="textButtonPrimaryDisabled" format="reference"/> <attr name="textButtonSecondary" format="reference"/> <attr name="textButtonSecondaryDestructive" format="reference"/> <attr name="textButtonSecondaryDisabled" format="reference"/> <attr name="textButtonTertiary" format="reference"/> <attr name="textButtonTertiaryDestructive" format="reference"/> <attr name="textButtonTertiaryDisabled" format="reference"/>
  36. <style name="UiKitTheme" parent="Theme.MaterialComponents.Light.NoActionBar"> <color name="backgroundButtonPrimary">@color/teal_100</color> <color name="backgroundButtonPrimaryDestructive">@color/red_100</color> <color name="backgroundButtonPrimaryDisabled">@color/anchovy_20</color> <color

    name="backgroundButtonSecondary">@color/white</color> <style name="UiKitTheme.DayNight" parent="Theme.MaterialComponents.DayNight.NoActionBar"> <item name="backgroundButtonPrimary">@color/backgroundButtonPrimary</item> <item name=“backgroundButtonPrimaryDestructive"> @color/backgroundButtonPrimaryDestructive</item> <item name=“backgroundButtonPrimaryDisabled"> @color/backgroundButtonPrimaryDisabled</item> <item name="backgroundButtonSecondary">@color/backgroundButtonSecondary</item>
  37. <style name="UiKitTheme" parent="Theme.MaterialComponents.Light.NoActionBar"> <color name="backgroundButtonPrimary">@color/teal_100</color> <color name="backgroundButtonPrimaryDestructive">@color/red_100</color> <color name="backgroundButtonPrimaryDisabled">@color/anchovy_20</color> <color

    name="backgroundButtonSecondary">@color/white</color> <style name="UiKitTheme.DayNight" parent="Theme.MaterialComponents.DayNight.NoActionBar"> <item name="backgroundButtonPrimary">@color/backgroundButtonPrimary</item> <item name=“backgroundButtonPrimaryDestructive"> @color/backgroundButtonPrimaryDestructive</item> <item name=“backgroundButtonPrimaryDisabled"> @color/backgroundButtonPrimaryDisabled</item> <item name="backgroundButtonSecondary">@color/backgroundButtonSecondary</item>
  38. <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_enabled="false"> <shape> <solid android:color="?attr/backgroundButtonPrimaryDisabled"

    /> <corners android:radius="@dimen/border_radius_medium" /> </shape> </item> <item> <shape> <solid android:color="?attr/backgroundButtonPrimary" /> <corners android:radius="@dimen/border_radius_medium" /> </shape> </item> </selector> R.drawable.button_background_primary
  39. <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_enabled="false"> <shape> <solid android:color="?attr/backgroundButtonPrimaryDisabled"

    /> <corners android:radius="@dimen/border_radius_medium" /> </shape> </item> <item> <shape> <solid android:color="?attr/backgroundButtonPrimary" /> <corners android:radius="@dimen/border_radius_medium" /> </shape> </item> </selector> R.drawable.button_background_primary API Level 19
  40. <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:ignore="NonSemanticColorUse"> <item android:state_enabled="false"> <shape>

    <solid android:color="@color/anchovy_20" /> <corners android:radius="@dimen/border_radius_medium" /> </shape> </item> <item> <shape> <solid android:color="@color/teal_100" /> <corners android:radius="@dimen/border_radius_medium" /> </shape> </item> </selector> R.drawable.button_background_primary
  41. <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:ignore="NonSemanticColorUse"> <item android:state_enabled="false"> <shape>

    <solid android:color="@color/anchovy_20" /> <corners android:radius="@dimen/border_radius_medium" /> </shape> </item> <item> <shape> <solid android:color="@color/teal_100" /> <corners android:radius="@dimen/border_radius_medium" /> </shape> </item> </selector> R.drawable.button_background_primary API Level 19 ✅
  42. <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_enabled="false"> <shape> <solid android:color="?attr/backgroundButtonPrimaryDisabled"

    /> <corners android:radius="@dimen/border_radius_medium" /> </shape> </item> <item> <shape> <solid android:color="?attr/backgroundButtonPrimary" /> <corners android:radius="@dimen/border_radius_medium" /> </shape> </item> </selector> values-v21/R.drawable.button_background_primary
  43. DrawableCompat.setTint(drawable, context.color(R.color.white)) fun Context.color(@ColorRes colorRes: Int) = ContextCompat.getColor(this, colorRes) DrawableCompat.setTint(drawable,

    context.themeColor(R.attr.iconColorPrimary)) @ColorInt fun Context.themeColor(@AttrRes attribute: Int) = TypedValue().let { theme.resolveAttribute(attribute, it, true); it.data }
  44. DrawableCompat.setTint(drawable, context.color(R.color.white)) @Deprecated fun Context.color(@ColorRes colorRes: Int) = ContextCompat.getColor(this, colorRes)

    DrawableCompat.setTint(drawable, context.themeColor(R.attr.iconColorPrimary)) @ColorInt fun Context.themeColor(@AttrRes attribute: Int) = TypedValue().let { theme.resolveAttribute(attribute, it, true); it.data }
  45. fun Context.alert(init: AlertBuilder<DialogInterface>.() -> Unit): AlertBuilder<DialogInterface> = MaterialDialogBuilder(this).apply { init()

    } interface AlertBuilder<out D : DialogInterface> { val ctx: Context var title: CharSequence var titleResource: Int var message: CharSequence var messageResource: Int var isCancelable: Boolean fun onCancelled(handler: (dialog: DialogInterface) -> Unit) fun positiveButton(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit) fun positiveButton(@StringRes buttonTextResource: Int, onClicked: (dialog: DialogInterface) -> Unit) fun negativeButton(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit) fun negativeButton(@StringRes buttonTextResource: Int, onClicked: (dialog: DialogInterface) -> Unit) fun show(): D }
  46. fun setNightMode(mode: NightMode) { val flag = when (mode) {

    NightMode.ON -> AppCompatDelegate.MODE_NIGHT_YES NightMode.OFF -> AppCompatDelegate.MODE_NIGHT_NO NightMode.FOLLOW_SYSTEM -> { if (CompatUtil.minSdk29) AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM else AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY } } AppCompatDelegate.setDefaultNightMode(flag) }