Skip to content

Commit

Permalink
fix: Fix in-app messages overlay background color being ignored from …
Browse files Browse the repository at this point in the history
…message payload (#485)
  • Loading branch information
mahmoud-elmorabea authored Jan 8, 2025
1 parent 37d3dde commit dba96e0
Show file tree
Hide file tree
Showing 10 changed files with 235 additions and 124 deletions.
8 changes: 5 additions & 3 deletions messaginginapp/api/messaginginapp.api
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,19 @@ public final class io/customer/messaginginapp/gist/data/listeners/Queue : io/cus
}

public final class io/customer/messaginginapp/gist/data/model/GistProperties {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/customer/messaginginapp/gist/data/model/MessagePosition;Z)V
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/customer/messaginginapp/gist/data/model/MessagePosition;ZLjava/lang/String;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/lang/String;
public final fun component4 ()Lio/customer/messaginginapp/gist/data/model/MessagePosition;
public final fun component5 ()Z
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/customer/messaginginapp/gist/data/model/MessagePosition;Z)Lio/customer/messaginginapp/gist/data/model/GistProperties;
public static synthetic fun copy$default (Lio/customer/messaginginapp/gist/data/model/GistProperties;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/customer/messaginginapp/gist/data/model/MessagePosition;ZILjava/lang/Object;)Lio/customer/messaginginapp/gist/data/model/GistProperties;
public final fun component6 ()Ljava/lang/String;
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/customer/messaginginapp/gist/data/model/MessagePosition;ZLjava/lang/String;)Lio/customer/messaginginapp/gist/data/model/GistProperties;
public static synthetic fun copy$default (Lio/customer/messaginginapp/gist/data/model/GistProperties;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/customer/messaginginapp/gist/data/model/MessagePosition;ZLjava/lang/String;ILjava/lang/Object;)Lio/customer/messaginginapp/gist/data/model/GistProperties;
public fun equals (Ljava/lang/Object;)Z
public final fun getCampaignId ()Ljava/lang/String;
public final fun getElementId ()Ljava/lang/String;
public final fun getOverlayColor ()Ljava/lang/String;
public final fun getPersistent ()Z
public final fun getPosition ()Lio/customer/messaginginapp/gist/data/model/MessagePosition;
public final fun getRouteRule ()Ljava/lang/String;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ data class GistProperties(
val elementId: String?,
val campaignId: String?,
val position: MessagePosition,
val persistent: Boolean
val persistent: Boolean,
// This color is formated as #RRGGBBAA
val overlayColor: String?
)

data class Message(
Expand All @@ -32,6 +34,7 @@ data class Message(
var campaignId: String? = null
var position: MessagePosition = MessagePosition.CENTER
var persistent = false
var overlayColor: String? = null

(properties?.get("gist") as? Map<String, Any?>)?.let { gistProperties ->

Check warning on line 39 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/model/Message.kt

View workflow job for this annotation

GitHub Actions / Deploy git tag

Unchecked cast: Any? to Map<String, Any?>

Check warning on line 39 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/model/Message.kt

View workflow job for this annotation

GitHub Actions / API check

Unchecked cast: Any? to Map<String, Any?>

Check warning on line 39 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/model/Message.kt

View workflow job for this annotation

GitHub Actions / Android Lint (messaginginapp)

Unchecked cast: Any? to Map<String, Any?>

Check warning on line 39 in messaginginapp/src/main/java/io/customer/messaginginapp/gist/data/model/Message.kt

View workflow job for this annotation

GitHub Actions / Deploy SDK to Maven Central

Unchecked cast: Any? to Map<String, Any?>
gistProperties["routeRuleAndroid"]?.let { rule ->
Expand Down Expand Up @@ -59,8 +62,20 @@ data class Message(
persistent = persistentValue
}
}
gistProperties["overlayColor"]?.let { id ->
(id as? String)?.let { color ->
overlayColor = color
}
}
}
return GistProperties(routeRule = routeRule, elementId = elementId, campaignId = campaignId, position = position, persistent = persistent)
return GistProperties(
routeRule = routeRule,
elementId = elementId,
campaignId = campaignId,
position = position,
persistent = persistent,
overlayColor = overlayColor
)
}

override fun toString(): String {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.customer.messaginginapp.gist.presentation

import android.animation.AnimatorInflater
import android.content.Context
import android.content.Intent
import android.os.Bundle
Expand All @@ -11,12 +10,13 @@ import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.animation.doOnEnd
import com.google.gson.Gson
import io.customer.messaginginapp.R
import io.customer.messaginginapp.databinding.ActivityGistBinding
import io.customer.messaginginapp.di.inAppMessagingManager
import io.customer.messaginginapp.gist.data.model.Message
import io.customer.messaginginapp.gist.data.model.MessagePosition
import io.customer.messaginginapp.gist.utilities.ElapsedTimer
import io.customer.messaginginapp.gist.utilities.MessageOverlayColorParser
import io.customer.messaginginapp.gist.utilities.ModalAnimationUtil
import io.customer.messaginginapp.state.InAppMessagingAction
import io.customer.messaginginapp.state.InAppMessagingState
import io.customer.messaginginapp.state.MessageState
Expand Down Expand Up @@ -128,14 +128,13 @@ class GistModalActivity : AppCompatActivity(), GistViewListener, TrackableScreen
override fun finish() {
logger.debug("GistModelActivity finish")
runOnUiThread {
val animation = if (messagePosition == MessagePosition.TOP) {
AnimatorInflater.loadAnimator(this, R.animator.animate_out_to_top)
val animationSet = if (messagePosition == MessagePosition.TOP) {
ModalAnimationUtil.createAnimationSetOutToTop(binding.modalGistViewLayout)
} else {
AnimatorInflater.loadAnimator(this, R.animator.animate_out_to_bottom)
ModalAnimationUtil.createAnimationSetOutToBottom(binding.modalGistViewLayout)
}
animation.setTarget(binding.modalGistViewLayout)
animation.start()
animation.doOnEnd {
animationSet.start()
animationSet.doOnEnd {
logger.debug("GistModelActivity finish animation completed")
super.finish()
}
Expand Down Expand Up @@ -169,14 +168,16 @@ class GistModalActivity : AppCompatActivity(), GistViewListener, TrackableScreen
runOnUiThread {
window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
binding.modalGistViewLayout.visibility = View.VISIBLE
val animation = if (messagePosition == MessagePosition.TOP) {
AnimatorInflater.loadAnimator(this, R.animator.animate_in_from_top)

val overlayColor = MessageOverlayColorParser.parseColor(message.gistProperties.overlayColor)
?: ModalAnimationUtil.FALLBACK_COLOR_STRING
val animatorSet = if (messagePosition == MessagePosition.TOP) {
ModalAnimationUtil.createAnimationSetInFromTop(binding.modalGistViewLayout, overlayColor)
} else {
AnimatorInflater.loadAnimator(this, R.animator.animate_in_from_bottom)
ModalAnimationUtil.createAnimationSetInFromBottom(binding.modalGistViewLayout, overlayColor)
}
animation.setTarget(binding.modalGistViewLayout)
animation.start()
animation.doOnEnd {
animatorSet.start()
animatorSet.doOnEnd {
logger.debug("GistModelActivity Message Animation Completed: $message")
elapsedTimer.end()
}
Expand All @@ -189,7 +190,7 @@ class GistModalActivity : AppCompatActivity(), GistViewListener, TrackableScreen
binding.gistView.stopLoading()
}
// and finish the activity without performing any further actions
super.finish()
finish()
}

override fun onGistViewSizeChanged(width: Int, height: Int) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.customer.messaginginapp.gist.utilities

internal object MessageOverlayColorParser {

/**
* The expected color is formatted as #RRGGBBAA with alpha channel at the end, we need
* to reformat it to be #AARRGGBB to be usable on Android
*/
fun parseColor(color: String?): String? {
if (color == null) {
return null
}

val cleanColor = color.removePrefix("#")

if (doesNotHaveExpectedColorCharCount(cleanColor)) {
return null
}

val red = cleanColor.substring(0, 2)
val green = cleanColor.substring(2, 4)
val blue = cleanColor.substring(4, 6)
val alpha = if (cleanColor.length == 8) cleanColor.substring(6, 8) else ""

return "#$alpha$red$green$blue"
}

private fun doesNotHaveExpectedColorCharCount(color: String): Boolean {
return color.length != 6 && color.length != 8
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package io.customer.messaginginapp.gist.utilities

import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.view.View
import androidx.annotation.ColorInt
import io.customer.sdk.core.di.SDKComponent

internal object ModalAnimationUtil {

const val FALLBACK_COLOR_STRING = "#33000000"

private const val TRANSLATION_ANIMATION_DURATION = 150L
private const val ALPHA_ANIMATION_DURATION = 150L
private const val COLOR_ANIMATION_DURATION = 100L

private val logger = SDKComponent.logger

fun createAnimationSetInFromTop(target: View, overlayEndColor: String): AnimatorSet {
return createEnterAnimation(target, overlayEndColor, -100f)
}

fun createAnimationSetInFromBottom(target: View, overlayEndColor: String): AnimatorSet {
return createEnterAnimation(target, overlayEndColor, 100f)
}

fun createAnimationSetOutToTop(target: View): AnimatorSet {
return createExitAnimation(target, -100f)
}

fun createAnimationSetOutToBottom(target: View): AnimatorSet {
return createExitAnimation(target, 100f)
}

private fun createEnterAnimation(
target: View,
overlayEndColor: String,
translationYStart: Float
): AnimatorSet {
val translationYAnimator = ObjectAnimator.ofFloat(target, View.TRANSLATION_Y, translationYStart, 0f).apply {
duration = TRANSLATION_ANIMATION_DURATION
}
val alphaAnimator = ObjectAnimator.ofFloat(target, View.ALPHA, 0f, 1f).apply {
duration = ALPHA_ANIMATION_DURATION
}
val translationAndAlphaSet = AnimatorSet().apply {
playTogether(translationYAnimator, alphaAnimator)
}
target.alpha = 0f

val backgroundColorAnimator = ObjectAnimator.ofArgb(
target,
"backgroundColor",
Color.TRANSPARENT,
parseColorSafely(overlayEndColor)
).apply {
duration = COLOR_ANIMATION_DURATION
startDelay = 0
}
val colorSet = AnimatorSet().apply {
play(backgroundColorAnimator)
}

return AnimatorSet().apply {
playSequentially(translationAndAlphaSet, colorSet)
}
}

private fun createExitAnimation(target: View, translationYEnd: Float): AnimatorSet {
val backgroundColor = extractBackgroundColor(target)
val backgroundColorAnimator = ObjectAnimator.ofArgb(
target,
"backgroundColor",
parseColorSafely(backgroundColor),
Color.TRANSPARENT
).apply {
duration = COLOR_ANIMATION_DURATION
startDelay = 0
}
val colorSet = AnimatorSet().apply {
play(backgroundColorAnimator)
}

val translationYAnimator = ObjectAnimator.ofFloat(target, View.TRANSLATION_Y, 0f, translationYEnd).apply {
duration = TRANSLATION_ANIMATION_DURATION
}
val alphaAnimator = ObjectAnimator.ofFloat(target, View.ALPHA, 1f, 0f).apply {
duration = ALPHA_ANIMATION_DURATION
}
val translationAndAlphaSet = AnimatorSet().apply {
playTogether(translationYAnimator, alphaAnimator)
}

return AnimatorSet().apply {
playSequentially(colorSet, translationAndAlphaSet)
}
}

@ColorInt
private fun parseColorSafely(color: String): Int {
return try {
Color.parseColor(color)
} catch (ignored: IllegalArgumentException) {
logger.error(ignored.message ?: "Error parsing in-app overlay color")
Color.parseColor(FALLBACK_COLOR_STRING)
}
}

private fun extractBackgroundColor(target: View): String {
val backgroundDrawable = target.background
if (backgroundDrawable is ColorDrawable) {
return String.format("#%08X", backgroundDrawable.color)
}

return FALLBACK_COLOR_STRING
}
}
26 changes: 0 additions & 26 deletions messaginginapp/src/main/res/animator/animate_in_from_bottom.xml

This file was deleted.

26 changes: 0 additions & 26 deletions messaginginapp/src/main/res/animator/animate_in_from_top.xml

This file was deleted.

26 changes: 0 additions & 26 deletions messaginginapp/src/main/res/animator/animate_out_to_bottom.xml

This file was deleted.

Loading

0 comments on commit dba96e0

Please sign in to comment.