diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/ChatView.kt b/widgetssdk/src/main/java/com/glia/widgets/chat/ChatView.kt index 00743c3b0..10e591fb3 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/chat/ChatView.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/ChatView.kt @@ -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 @@ -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) { @@ -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) @@ -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) @@ -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) { diff --git a/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/UnifiedUiExtensions.kt b/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/UnifiedUiExtensions.kt index 9cf24819a..d077374ed 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/UnifiedUiExtensions.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/UnifiedUiExtensions.kt @@ -1,5 +1,6 @@ package com.glia.widgets.view.unifiedui +import android.content.Context import android.content.res.ColorStateList import android.graphics.Color import android.graphics.LinearGradient @@ -7,7 +8,9 @@ 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 @@ -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 @@ -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 { @@ -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?) { @@ -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, @@ -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?) { @@ -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?) { diff --git a/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/config/chat/ChatRemoteConfig.kt b/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/config/chat/ChatRemoteConfig.kt index bc3d650eb..ebe5d882d 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/config/chat/ChatRemoteConfig.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/config/chat/ChatRemoteConfig.kt @@ -29,6 +29,9 @@ internal data class ChatRemoteConfig( @SerializedName("input") val inputRemoteConfig: InputRemoteConfig?, + @SerializedName("inputDisabled") + val inputDisabledRemoteConfig: InputRemoteConfig?, + @SerializedName("responseCard") val responseCardRemoteConfig: ResponseCardRemoteConfig?, @@ -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(), diff --git a/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/theme/chat/ChatTheme.kt b/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/theme/chat/ChatTheme.kt index 239d2ad69..6ea59aebd 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/theme/chat/ChatTheme.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/theme/chat/ChatTheme.kt @@ -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, @@ -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, diff --git a/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/theme/defaulttheme/CallVisualizer.kt b/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/theme/defaulttheme/CallVisualizer.kt index 39a366c3c..6cc4d7d1a 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/theme/defaulttheme/CallVisualizer.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/theme/defaulttheme/CallVisualizer.kt @@ -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), diff --git a/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/theme/defaulttheme/Chat.kt b/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/theme/defaulttheme/Chat.kt index 4c1a06cf2..da14e2e2b 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/theme/defaulttheme/Chat.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/theme/defaulttheme/Chat.kt @@ -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), @@ -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 */ diff --git a/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/theme/defaulttheme/Text.kt b/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/theme/defaulttheme/Text.kt index aa4302f5c..c2354cdfe 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/theme/defaulttheme/Text.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/view/unifiedui/theme/defaulttheme/Text.kt @@ -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 */ diff --git a/widgetssdk/src/main/res/color/input_dark_color_state_list.xml b/widgetssdk/src/main/res/color/input_dark_color_state_list.xml new file mode 100644 index 000000000..563335a24 --- /dev/null +++ b/widgetssdk/src/main/res/color/input_dark_color_state_list.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/widgetssdk/src/main/res/color/input_normal_color_state_list.xml b/widgetssdk/src/main/res/color/input_normal_color_state_list.xml new file mode 100644 index 000000000..14851ad04 --- /dev/null +++ b/widgetssdk/src/main/res/color/input_normal_color_state_list.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/widgetssdk/src/main/res/color/input_primary_color_state_list.xml b/widgetssdk/src/main/res/color/input_primary_color_state_list.xml new file mode 100644 index 000000000..cb8a3f328 --- /dev/null +++ b/widgetssdk/src/main/res/color/input_primary_color_state_list.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/widgetssdk/src/main/res/layout/chat_view.xml b/widgetssdk/src/main/res/layout/chat_view.xml index 103c112b6..848c1c921 100644 --- a/widgetssdk/src/main/res/layout/chat_view.xml +++ b/widgetssdk/src/main/res/layout/chat_view.xml @@ -237,8 +237,8 @@ android:maxLines="4" android:minHeight="@dimen/glia_chat_edit_text_min_height" android:paddingStart="@dimen/glia_large" - android:textColor="?attr/gliaBaseDarkColor" - android:textColorHint="?attr/gliaBaseNormalColor" + android:textColor="@color/input_dark_color_state_list" + android:textColorHint="@color/input_normal_color_state_list" android:textCursorDrawable="@null" tools:text="@string/chat_input_placeholder"/> @@ -250,6 +250,7 @@ app:layout_constraintBottom_toBottomOf="@+id/message_input_background" app:layout_constraintStart_toEndOf="@+id/chat_edit_text" app:layout_constraintEnd_toStartOf="@+id/send_button" + app:tint="@color/input_normal_color_state_list" android:background="?attr/selectableItemBackground" android:padding="@dimen/glia_large" android:src="@drawable/ic_add_attachment" /> @@ -265,9 +266,8 @@ app:layout_constraintEnd_toEndOf="parent" android:background="?attr/selectableItemBackground" android:src="?attr/gliaIconSendMessage" - app:tint="?attr/gliaBrandPrimaryColor" - tools:src="@drawable/ic_baseline_send" - tools:tint="@color/glia_primary_color" /> + app:tint="@color/input_primary_color_state_list" + tools:src="@drawable/ic_baseline_send" />