Skip to content

Commit

Permalink
Add disabled color state for chat input
Browse files Browse the repository at this point in the history
- Added inputDisabled Json to UI remote theme configurations
- Switched simple color/drawables to ColorStateList/DrawableStateList

MOB-3855
  • Loading branch information
gugalo committed Dec 12, 2024
1 parent 93ed0a8 commit c10901b
Show file tree
Hide file tree
Showing 89 changed files with 365 additions and 190 deletions.
37 changes: 26 additions & 11 deletions widgetssdk/src/main/java/com/glia/widgets/chat/ChatView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,12 @@ import com.glia.widgets.view.dialog.base.DialogDelegateImpl
import com.glia.widgets.view.head.ChatHeadContract
import com.glia.widgets.view.unifiedui.applyButtonTheme
import com.glia.widgets.view.unifiedui.applyColorTheme
import com.glia.widgets.view.unifiedui.applyHintColor
import com.glia.widgets.view.unifiedui.applyHintTheme
import com.glia.widgets.view.unifiedui.applyImageColor
import com.glia.widgets.view.unifiedui.applyImageColorTheme
import com.glia.widgets.view.unifiedui.applyLayerTheme
import com.glia.widgets.view.unifiedui.applyTextColor
import com.glia.widgets.view.unifiedui.applyTextTheme
import com.glia.widgets.view.unifiedui.theme.UnifiedTheme
import com.glia.widgets.view.unifiedui.theme.base.HeaderTheme
Expand Down Expand Up @@ -509,6 +513,12 @@ internal class ChatView(context: Context, attrs: AttributeSet?, defStyleAttr: In
else -> binding.chatEditText.setLocaleHint(R.string.chat_input_placeholder)
}
binding.chatEditText.isEnabled = chatState.chatInputMode.isEnabled
Dependencies.gliaThemeManager.theme?.chatTheme?.let {
binding.chatEditText.apply{
val inputTheme = if (isEnabled) it.input else it.inputDisabled
applyTextTheme(inputTheme?.text, withAlignment = false)
}
}
}

private fun updateAppBar(chatState: ChatState) {
Expand Down Expand Up @@ -643,14 +653,17 @@ internal class ChatView(context: Context, attrs: AttributeSet?, defStyleAttr: In
?.also(binding.chatDivider::setBackgroundColor)
?.also(binding.scBottomBannerDivider::setBackgroundColor)

theme.baseDarkColor?.let(::getColorCompat)?.also(binding.chatEditText::setTextColor)
theme.baseDarkColor?.let(::getColorCompat)
?.also{ binding.chatEditText.applyTextColor(it, theme.baseShadeColor) }
theme.baseNormalColor?.let(::getColorCompat)
?.also(binding.chatEditText::setHintTextColor)
?.also{ binding.chatEditText.applyHintColor(it, theme.baseShadeColor) }
?.also{ binding.addAttachmentButton.applyImageColor(it, theme.baseShadeColor) }
?.also(binding.scBottomBannerLabel::setTextColor)
theme.systemAgentBubbleColor?.let(::getColorCompat)?.also(binding.scBottomBannerLabel::setBackgroundColor)

// other
theme.sendMessageButtonTintColor?.let(::getColorStateListCompat)?.also(binding.sendButton::setImageTintList)
theme.sendMessageButtonTintColor?.let(::getColorCompat)
?.also { binding.sendButton.applyImageColor(it, theme.baseShadeColor) }
theme.gliaChatBackgroundColor?.let(::getColorCompat)?.also(::setBackgroundColor)
binding.gvaQuickRepliesLayout.updateTheme(theme)

Expand Down Expand Up @@ -828,7 +841,7 @@ internal class ChatView(context: Context, attrs: AttributeSet?, defStyleAttr: In

chatTheme.header?.also(::applyHeaderTheme)

chatTheme.input?.also(::applyInputTheme)
chatTheme.input?.also{ applyInputTheme(it, chatTheme.inputDisabled) }

chatTheme.typingIndicator?.primaryColor?.also(binding.operatorTypingAnimationView::addColorFilter)

Expand All @@ -841,13 +854,15 @@ internal class ChatView(context: Context, attrs: AttributeSet?, defStyleAttr: In
binding.appBarView.applyHeaderTheme(headerTheme)
}

private fun applyInputTheme(inputTheme: InputTheme) {
binding.sendButton.applyButtonTheme(inputTheme.sendButton)
binding.addAttachmentButton.applyButtonTheme(inputTheme.mediaButton)
binding.chatDivider.applyColorTheme(inputTheme.divider)
binding.messageInputBackground.applyLayerTheme(inputTheme.background)
binding.chatEditText.applyTextTheme(textTheme = inputTheme.text, withAlignment = false)
inputTheme.placeholder?.textColor?.primaryColor?.also(binding.chatEditText::setHintTextColor)
private fun applyInputTheme(defaultTheme: InputTheme, disabledTheme: InputTheme?) {
binding.sendButton.applyButtonTheme(defaultTheme.sendButton, disabledTheme?.sendButton)
binding.addAttachmentButton.applyButtonTheme(defaultTheme.mediaButton, disabledTheme?.mediaButton)
binding.chatDivider.applyColorTheme(defaultTheme.divider, disabledTheme?.divider)
binding.messageInputBackground.applyLayerTheme(defaultTheme.background, disabledTheme?.background)
// It is impossible to support all possible change to text view related to disabled state
// So it is achived manually in [updateChatEditText] method
binding.chatEditText.applyTextTheme(textTheme = defaultTheme.text, withAlignment = false)
binding.chatEditText.applyHintTheme(defaultTheme.placeholder, disabledTheme?.placeholder)
}

private fun applyUnreadMessagesTheme(unreadIndicatorTheme: UnreadIndicatorTheme) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package com.glia.widgets.view.unifiedui

import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.LinearGradient
import android.graphics.Shader
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.StateListDrawable
import android.os.Build
import android.util.StateSet
import android.util.TypedValue
import android.view.View
import android.widget.ImageButton
Expand All @@ -17,6 +20,7 @@ import android.widget.TextView
import com.glia.widgets.R
import com.glia.widgets.helper.applyShadow
import com.glia.widgets.helper.colorForState
import com.glia.widgets.helper.setCompoundDrawableTintListCompat
import com.glia.widgets.view.button.GliaSurveyOptionButton
import com.glia.widgets.view.unifiedui.theme.base.ButtonTheme
import com.glia.widgets.view.unifiedui.theme.base.ColorTheme
Expand All @@ -32,9 +36,11 @@ import com.google.android.material.imageview.ShapeableImageView
import com.google.android.material.progressindicator.CircularProgressIndicator
import com.google.android.material.shape.CornerFamily

internal fun View.applyColorTheme(color: ColorTheme?) {
background = createBackgroundFromTheme(color ?: return)
backgroundTintList = null
internal fun View.applyColorTheme(defaultColor: ColorTheme?, disabledColor: ColorTheme? = null) {
createDrawableStateList(defaultColor, disabledColor)?.let {
background = it
backgroundTintList = null
}
}

internal fun createBackgroundFromTheme(color: ColorTheme): Drawable = color.run {
Expand All @@ -49,34 +55,76 @@ internal fun createBackgroundFromTheme(color: ColorTheme): Drawable = color.run
* will update whole background without keeping an old background or stroke
* in case when the old background differs from [GradientDrawable]
*/
internal fun View.applyLayerTheme(layer: LayerTheme?) {
if (layer?.fill == null && layer?.stroke == null) return

val drawable = (background as? GradientDrawable) ?: GradientDrawable()
internal fun View.applyLayerTheme(defTheme: LayerTheme?, disabledTheme: LayerTheme? = null) {
if (defTheme?.fill == null && defTheme?.stroke == null) return

var drawable: GradientDrawable? = null
// The part below is needed to preserve GradientDrawable/background properties that are `null`.
// For example without this implementation if `cornerRadius = null` but `stroke = Color.RED` then
// whatever corner radios is in the default UI implementation it would automatically become 0 instead
if (background is StateListDrawable) {
drawable = background.current as? GradientDrawable
} else if (background is GradientDrawable) {
drawable = background as GradientDrawable
}
if (drawable == null) {
drawable = GradientDrawable()
}

if (backgroundTintList != null) {
drawable.color = backgroundTintList
backgroundTintList = null
}

layer.fill?.also {
applyLayerTheme(drawable, defTheme, disabledTheme)
}

private fun View.applyLayerTheme(originalDrawable: GradientDrawable?, defTheme: LayerTheme?, disabledTheme: LayerTheme?) {
if (originalDrawable == null || defTheme == null) return
val newBackground = StateListDrawable()
newBackground.addState(
StateSet.WILD_CARD,
originalDrawable.mutateWithSettings(defTheme, context)
)
if (disabledTheme != null) {
newBackground.addState(
intArrayOf(-android.R.attr.state_enabled),
originalDrawable.mutateWithSettings(disabledTheme, context)
)
}

background = newBackground
}


private fun GradientDrawable.mutateWithSettings(theme: LayerTheme?, context: Context): GradientDrawable {
return mutateWithSettings(theme, context.resources.getDimensionPixelSize(R.dimen.glia_px))
}

private fun GradientDrawable.mutateWithSettings(theme: LayerTheme?, fallbackStrokeWidth: Int): GradientDrawable {
val clone = (mutate() as? GradientDrawable) ?: GradientDrawable()

if (theme == null) return clone
theme.fill?.let {
if (it.isGradient) {
drawable.colors = it.valuesArray
clone.colors = it.valuesArray
} else {
drawable.setColor(it.primaryColor)
clone.setColor(it.primaryColor)
}
}

layer.stroke?.also {
drawable.setStroke(
layer.borderWidthInt ?: context.resources.getDimensionPixelSize(R.dimen.glia_px),
theme.stroke?.let {
clone.setStroke(
theme.borderWidthInt ?: fallbackStrokeWidth,
it
)
}

layer.cornerRadius?.also { drawable.cornerRadius = it }
theme.cornerRadius?.let {
clone.cornerRadius = it
}

background = drawable
return clone
}

internal fun ShapeableImageView.applyLayerTheme(layer: LayerTheme?) {
Expand Down Expand Up @@ -135,6 +183,21 @@ internal fun TextView.applyTextColorTheme(color: ColorTheme?) {
color.primaryColorStateList.let(::setCompoundDrawableTintList)
}

internal fun TextView.applyTextColor(defaultColor: Int, disabledColor: Int? = null) {
setTextColor(createColorStateList(defaultColor, disabledColor))
setCompoundDrawableTintListCompat(createColorStateList(defaultColor, disabledColor))
}

internal fun TextView.applyHintColor(defaultColor: Int, disabledColor: Int? = null) {
setHintTextColor(createColorStateList(defaultColor, disabledColor))
}

internal fun TextView.applyHintTheme(defaultTheme: TextTheme?, disabledTheme: TextTheme? = null) {
defaultTheme?.textColor?.primaryColor?.let {
applyHintColor(it, disabledTheme?.textColor?.primaryColor)
}
}

internal fun TextView.applyTextTheme(
textTheme: TextTheme?,
withBackground: Boolean = false,
Expand Down Expand Up @@ -181,8 +244,8 @@ internal fun MaterialButton.applyButtonTheme(buttonTheme: ButtonTheme?) {
}
}

internal fun ImageView.applyButtonTheme(buttonTheme: ButtonTheme?) {
applyImageColorTheme(buttonTheme?.iconColor)
internal fun ImageView.applyButtonTheme(buttonMainTheme: ButtonTheme?, buttonDisabledTheme: ButtonTheme? = null) {
applyImageColorTheme(buttonMainTheme?.iconColor, buttonDisabledTheme?.iconColor)
}

internal fun CircularProgressIndicator.applyIndicatorColorTheme(colorTheme: ColorTheme?) {
Expand All @@ -193,8 +256,40 @@ internal fun ProgressBar.applyProgressColorTheme(colorTheme: ColorTheme?) {
colorTheme?.primaryColorStateList?.also(::setIndeterminateTintList)
}

internal fun ImageView.applyImageColorTheme(colorTheme: ColorTheme?) {
colorTheme?.primaryColorStateList?.also(::setImageTintList)
internal fun ImageView.applyImageColor(defaultColor: Int, disabledColor: Int? = null) {
imageTintList = createColorStateList(defaultColor, disabledColor)
}

internal fun ImageView.applyImageColorTheme(defaultTheme: ColorTheme?, disabledTheme: ColorTheme? = null) {
defaultTheme?.primaryColor?.let { applyImageColor(it, disabledTheme?.primaryColor) }
}

private fun createColorStateList(defaultColor: Int?, disabledColor: Int?): ColorStateList? {
if (defaultColor == null) return null
if (disabledColor == null) return ColorStateList.valueOf(defaultColor)

// For future -> default state list can be found here:
// https://developer.android.com/guide/topics/resources/color-list-resource
return ColorStateList(
arrayOf( // Reminder: order maters
intArrayOf(-android.R.attr.state_enabled),
intArrayOf(), // empty array -> default fallback
),
intArrayOf(
disabledColor,
defaultColor
)
)
}

private fun createDrawableStateList(defColor: ColorTheme?, disabledColor: ColorTheme? = null): StateListDrawable? {
if (defColor == null) return null
val stateList = StateListDrawable()
stateList.addState(StateSet.WILD_CARD, createBackgroundFromTheme(defColor))

if (disabledColor == null) return stateList
stateList.addState(intArrayOf(-android.R.attr.state_enabled), createBackgroundFromTheme(disabledColor))
return stateList
}

internal fun FloatingActionButton.applyBarButtonStatesTheme(barButtonStatesTheme: BarButtonStatesTheme?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ internal data class ChatRemoteConfig(
@SerializedName("input")
val inputRemoteConfig: InputRemoteConfig?,

@SerializedName("inputDisabled")
val inputDisabledRemoteConfig: InputRemoteConfig?,

@SerializedName("responseCard")
val responseCardRemoteConfig: ResponseCardRemoteConfig?,

Expand Down Expand Up @@ -69,6 +72,7 @@ internal data class ChatRemoteConfig(
visitorMessage = visitorMessage?.toMessageBalloonTheme(),
connect = connect?.toEngagementStatesTheme(),
input = inputRemoteConfig?.toInputTheme(),
inputDisabled = inputDisabledRemoteConfig?.toInputTheme(),
responseCard = responseCardRemoteConfig?.toResponseCardTheme(),
audioUpgrade = audioUpgradeRemoteConfig?.toUpgradeTheme(),
videoUpgrade = videoUpgradeRemoteConfig?.toUpgradeTheme(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ internal data class ChatTheme(
val visitorMessage: MessageBalloonTheme? = null,
val connect: EngagementStatesTheme? = null,
val input: InputTheme? = null,
val inputDisabled: InputTheme? = null,
val responseCard: ResponseCardTheme? = null,
val audioUpgrade: MediaUpgradeTheme? = null,
val videoUpgrade: MediaUpgradeTheme? = null,
Expand All @@ -36,6 +37,7 @@ internal data class ChatTheme(
visitorMessage = visitorMessage merge other.visitorMessage,
connect = connect merge other.connect,
input = input merge other.input,
inputDisabled = inputDisabled merge other.inputDisabled,
responseCard = responseCard merge other.responseCard,
audioUpgrade = audioUpgrade merge other.audioUpgrade,
videoUpgrade = videoUpgrade merge other.videoUpgrade,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ internal fun EndScreenSharingTheme(pallet: ColorPallet): EndScreenSharingTheme =
internal fun VisitorCodeTheme(pallet: ColorPallet): VisitorCodeTheme =
VisitorCodeTheme(
numberSlotText = BaseDarkColorTextTheme(pallet),
numberSlotBackground = LayerTheme(fill = pallet.lightColorTheme),
numberSlotBackground = LayerTheme(fill = pallet.lightColorTheme, stroke = pallet.shadeColorTheme?.primaryColor),
closeButtonColor = pallet.normalColorTheme,
refreshButton = PositiveDefaultButtonTheme(pallet),
background = LayerTheme(fill = pallet.lightColorTheme),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ internal fun ChatTheme(pallet: ColorPallet): ChatTheme = ChatTheme(
visitorMessage = ChatVisitorMessageTheme(pallet),
connect = ChatEngagementStatesTheme(pallet),
input = ChatInputTheme(pallet),
inputDisabled = ChatInputDisabledTheme(pallet),
responseCard = ChatResponseCardTheme(pallet),
audioUpgrade = MediaUpgradeTheme(pallet),
videoUpgrade = MediaUpgradeTheme(pallet),
Expand Down Expand Up @@ -145,6 +146,41 @@ private fun ChatInputTheme(pallet: ColorPallet): InputTheme? = pallet.run {
}
}

/**
* Default theme for Disabled Input
*/
private fun ChatInputDisabledTheme(pallet: ColorPallet): InputTheme? = pallet.run {
composeIfAtLeastOneNotNull(
darkColorTheme,
normalColorTheme,
primaryColorTheme,
shadeColorTheme,
negativeColorTheme
) {
InputTheme(
text = BaseShaderColorTextTheme(this),
placeholder = BaseShaderColorTextTheme(this),
divider = shadeColorTheme,
sendButton = ButtonTheme(iconColor = shadeColorTheme),
mediaButton = ButtonTheme(iconColor = shadeColorTheme),
fileUploadBar = FileUploadBarTheme(
filePreview = FilePreviewTheme(
text = BaseLightColorTextTheme(this),
errorIcon = negativeColorTheme,
background = LayerTheme(neutralColorTheme),
errorBackground = LayerTheme(negativeColorTheme)
),
uploading = DefaultUploadFileTheme(this),
uploaded = DefaultUploadFileTheme(this),
error = UploadFileTheme(text = BaseNegativeColorTextTheme(this), info = BaseDarkColorTextTheme(this)),
progress = primaryColorTheme,
errorProgress = negativeColorTheme,
removeButton = normalColorTheme
)
)
}
}

/**
* Default theme for Unread messages indicator
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ internal fun BaseLightColorTextTheme(pallet: ColorPallet): TextTheme? =
internal fun BaseNormalColorTextTheme(pallet: ColorPallet): TextTheme? =
BaseText(pallet.normalColorTheme)

/**
* Default theme for `shadeColor` text
*/
internal fun BaseShaderColorTextTheme(pallet: ColorPallet): TextTheme? =
BaseText(pallet.shadeColorTheme)

/**
* Default theme for `basePrimaryColor` text
*/
Expand Down
5 changes: 5 additions & 0 deletions widgetssdk/src/main/res/color/input_dark_color_state_list.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/gliaBaseShadeColor" android:state_enabled="false" />
<item android:color="?attr/gliaBaseDarkColor" /> <!-- default state color -->
</selector>
Loading

0 comments on commit c10901b

Please sign in to comment.