diff --git a/clevertap-pushtemplates/build.gradle b/clevertap-pushtemplates/build.gradle index aed042ea2..5adbac293 100644 --- a/clevertap-pushtemplates/build.gradle +++ b/clevertap-pushtemplates/build.gradle @@ -32,6 +32,9 @@ dependencies { implementation (libs.androidx.appcompat) testImplementation (libs.test.junit) + testImplementation project(':clevertap-core') + testImplementation(libs.test.mockk) + testImplementation(libs.test.robolectric) androidTestImplementation (libs.test.ext.junit) androidTestImplementation (libs.test.espresso.core) diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/ActionButton.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/ActionButton.kt new file mode 100644 index 000000000..b18fd3f24 --- /dev/null +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/ActionButton.kt @@ -0,0 +1,9 @@ +package com.clevertap.android.pushtemplates + +// Data class for action button information +data class ActionButton( + val id: String, + val label: String, + val icon: Int +) + diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/PTConstants.java b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/PTConstants.java index 81bb7cf5e..87809f205 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/PTConstants.java +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/PTConstants.java @@ -1,5 +1,7 @@ package com.clevertap.android.pushtemplates; +import java.util.Set; + @SuppressWarnings("WeakerAccess") //Using common keys from core-sdk constants public class PTConstants { @@ -56,6 +58,8 @@ public class PTConstants { public static final String PT_TITLE_COLOR = "pt_title_clr"; + public static final String PT_DARK_MODE_SUFFIX = "_dark"; + public static final String PT_MSG_COLOR = "pt_msg_clr"; public static final String PT_BG = "pt_bg"; @@ -136,6 +140,8 @@ public class PTConstants { public static final String PT_BUY_NOW = "buynow"; + public static final String PT_SCALE_TYPE = "pt_scale_type"; + public static final String TEXT_ONLY = "text_only"; public static final String PT_SUBTITLE = "pt_subtitle"; @@ -152,21 +158,24 @@ public class PTConstants { public static final String PT_DOT_SEP = "pt_dot_sep"; - public static final String PT_COLOUR_WHITE = "#FFFFFF"; - public static final String PT_COLOUR_GREY = "#A6A6A6"; - public static final String PT_COLOUR_BLACK = "#000000"; - public static final String PT_META_CLR_DEFAULTS = PT_COLOUR_GREY; - public static final String PT_PRODUCT_DISPLAY_ACTION_CLR_DEFAULTS = "#FFBB33"; - - public static final String PT_PRODUCT_DISPLAY_ACTION_TEXT_CLR_DEFAULT = PT_COLOUR_WHITE; - public static final int PT_FLIP_INTERVAL_TIME = 4 * ONE_SECOND; public static final String KEY_CLICKED_STAR = "clickedStar"; public static final String KEY_REQUEST_CODES = "requestCodes"; + + public static final Set COLOR_KEYS = Set.of( + PT_TITLE_COLOR, + PT_MSG_COLOR, + PT_BG, + PT_META_CLR, + PT_SMALL_ICON_COLOUR, + PT_CHRONO_TITLE_COLOUR, + PT_PRODUCT_DISPLAY_ACTION_COLOUR, + PT_PRODUCT_DISPLAY_ACTION_TEXT_COLOUR); + } diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/PTScaleType.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/PTScaleType.kt new file mode 100644 index 000000000..df1f4950d --- /dev/null +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/PTScaleType.kt @@ -0,0 +1,14 @@ +package com.clevertap.android.pushtemplates + +enum class PTScaleType { + FIT_CENTER, + CENTER_CROP; + + companion object { + fun fromString(value: String?): PTScaleType { + return values().firstOrNull { + it.name.equals(value, ignoreCase = true) + } ?: CENTER_CROP + } + } +} \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/PTXtensions.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/PTXtensions.kt new file mode 100644 index 000000000..444a5da28 --- /dev/null +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/PTXtensions.kt @@ -0,0 +1,10 @@ +package com.clevertap.android.pushtemplates + +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +@OptIn(ExperimentalContracts::class) +fun String?.isNotNullAndEmpty() : Boolean { + contract { returns(true) implies (this@isNotNullAndEmpty != null) } + return isNullOrEmpty().not() +} \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/PushTemplateReceiver.java b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/PushTemplateReceiver.java index 61754aba1..896002f36 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/PushTemplateReceiver.java +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/PushTemplateReceiver.java @@ -805,9 +805,6 @@ private void setKeysFromDashboard(Bundle extras) { if (pt_subtitle == null || pt_subtitle.isEmpty()) { pt_subtitle = extras.getString(Constants.WZRK_SUBTITLE); } - if (pt_small_icon_clr == null || pt_small_icon_clr.isEmpty()) { - pt_small_icon_clr = extras.getString(Constants.WZRK_COLOR); - } } private void setToast(Context context, String message, diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/TemplateRenderer.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/TemplateRenderer.kt index 0b91139e8..9e1cffa2e 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/TemplateRenderer.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/TemplateRenderer.kt @@ -5,6 +5,7 @@ import android.app.PendingIntent import android.content.ContentResolver import android.content.Context import android.content.Intent +import android.content.res.Configuration import android.graphics.Bitmap import android.media.RingtoneManager import android.net.Uri @@ -12,7 +13,6 @@ import android.os.* import android.os.Build.VERSION import android.os.Build.VERSION_CODES import androidx.annotation.RequiresApi -import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.Builder import com.clevertap.android.pushtemplates.PTConstants.* import com.clevertap.android.pushtemplates.content.FiveIconBigContentView @@ -77,11 +77,14 @@ class TemplateRenderer : INotificationRenderer, AudibleNotification { private var pt_cancel_notif_id: String? = null private var pt_cancel_notif_ids: ArrayList? = null var actions: JSONArray? = null + var actionButtons = emptyList() + var actionButtonPendingIntents = mutableMapOf() internal var pt_subtitle: String? = null private var pID: String? = null internal var pt_flip_interval = 0 private var pt_collapse_key: Any? = null internal var pt_manual_carousel_type: String? = null + internal var pt_scale_type: PTScaleType = PTScaleType.CENTER_CROP internal var config: CleverTapInstanceConfig? = null internal var notificationId: Int = -1//Creates a instance field for access in ContentViews->PendingIntentFactory @@ -110,15 +113,16 @@ class TemplateRenderer : INotificationRenderer, AudibleNotification { } override fun renderNotification( - extras: Bundle, context: Context, nb: NotificationCompat.Builder, + extras: Bundle, context: Context, nb: Builder, config: CleverTapInstanceConfig, notificationId: Int - ): NotificationCompat.Builder? { + ): Builder? { if (pt_id == null) { PTLog.verbose("Template ID not provided. Cannot create the notification") return null } this.notificationId = notificationId + this.actionButtons = getActionButtons(context, extras, notificationId, actions) when (templateType) { TemplateType.BASIC -> if (ValidatorFactory.getValidator(TemplateType.BASIC, this)?.validate() == true) @@ -166,7 +170,7 @@ class TemplateRenderer : INotificationRenderer, AudibleNotification { if (ValidatorFactory.getValidator(TemplateType.ZERO_BEZEL, this)?.validate() == true) return ZeroBezelStyle(this).builderFromStyle(context, extras, notificationId, nb) - TemplateType.TIMER -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + TemplateType.TIMER -> if (VERSION.SDK_INT >= VERSION_CODES.O) { ValidatorFactory.getValidator(TemplateType.TIMER, this) ?.takeIf { it.validate() } ?.let { @@ -309,7 +313,7 @@ class TemplateRenderer : INotificationRenderer, AudibleNotification { override fun setSmallIcon(smallIcon: Int, context: Context) { this.smallIcon = smallIcon try { - pt_small_icon = Utils.setBitMapColour(context, smallIcon, pt_small_icon_clr) + pt_small_icon = Utils.setBitMapColour(context, smallIcon, pt_small_icon_clr, PT_META_CLR_DEFAULTS) } catch (e: NullPointerException) { PTLog.debug("NPE while setting small icon color") } @@ -336,7 +340,7 @@ class TemplateRenderer : INotificationRenderer, AudibleNotification { var s = o if (s == "true") { soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) - } else if (!s.isEmpty()) { + } else if (s.isNotEmpty()) { if (s.contains(".mp3") || s.contains(".ogg") || s.contains(".wav")) { s = s.substring(0, s.length - 4) } @@ -358,13 +362,16 @@ class TemplateRenderer : INotificationRenderer, AudibleNotification { } private fun setUp(context: Context, extras: Bundle, config: CleverTapInstanceConfig?) { + val isDarkMode = (context.resources.configuration.uiMode and + Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES + pt_id = extras.getString(PT_ID) val pt_json = extras.getString(PT_JSON) if (pt_id != null) { templateType = TemplateType.fromString(pt_id) var newExtras: Bundle? = null try { - if (pt_json != null && pt_json.isNotEmpty()) { + if (pt_json.isNotNullAndEmpty()) { newExtras = Utils.fromJson(JSONObject(pt_json)) } } catch (e: JSONException) { @@ -372,13 +379,15 @@ class TemplateRenderer : INotificationRenderer, AudibleNotification { } if (newExtras != null) extras.putAll(newExtras) } + + val darkModeAdaptiveColors = Utils.createColorMap(extras, isDarkMode) pt_msg = extras.getString(PT_MSG) pt_msg_summary = extras.getString(PT_MSG_SUMMARY) - pt_msg_clr = extras.getString(PT_MSG_COLOR) + pt_msg_clr = darkModeAdaptiveColors[PT_MSG_COLOR] pt_title = extras.getString(PT_TITLE) - pt_title_clr = extras.getString(PT_TITLE_COLOR) - pt_meta_clr = extras.getString(PT_META_CLR) - pt_bg = extras.getString(PT_BG) + pt_title_clr = darkModeAdaptiveColors[PT_TITLE_COLOR] + pt_meta_clr = darkModeAdaptiveColors[PT_META_CLR] + pt_bg = darkModeAdaptiveColors[PT_BG] pt_big_img = extras.getString(PT_BIG_IMG) pt_large_icon = extras.getString(PT_NOTIF_ICON) pt_small_view = extras.getString(PT_SMALL_VIEW) @@ -396,23 +405,23 @@ class TemplateRenderer : INotificationRenderer, AudibleNotification { pt_input_feedback = extras.getString(PT_INPUT_FEEDBACK) pt_input_auto_open = extras.getString(PT_INPUT_AUTO_OPEN) pt_dismiss_on_click = extras.getString(PT_DISMISS_ON_CLICK) - pt_chrono_title_clr = extras.getString(PT_CHRONO_TITLE_COLOUR) + pt_chrono_title_clr = darkModeAdaptiveColors[PT_CHRONO_TITLE_COLOUR] pt_product_display_action = extras.getString(PT_PRODUCT_DISPLAY_ACTION) - pt_product_display_action_clr = extras.getString(PT_PRODUCT_DISPLAY_ACTION_COLOUR) + pt_product_display_action_clr = darkModeAdaptiveColors[PT_PRODUCT_DISPLAY_ACTION_COLOUR] pt_timer_end = Utils.getTimerEnd(extras) pt_big_img_alt = extras.getString(PT_BIG_IMG_ALT) pt_msg_alt = extras.getString(PT_MSG_ALT) pt_title_alt = extras.getString(PT_TITLE_ALT) pt_product_display_linear = extras.getString(PT_PRODUCT_DISPLAY_LINEAR) - pt_product_display_action_text_clr = - extras.getString(PT_PRODUCT_DISPLAY_ACTION_TEXT_COLOUR) - pt_small_icon_clr = extras.getString(PT_SMALL_ICON_COLOUR) + pt_product_display_action_text_clr = darkModeAdaptiveColors[PT_PRODUCT_DISPLAY_ACTION_TEXT_COLOUR] + pt_small_icon_clr = darkModeAdaptiveColors[PT_SMALL_ICON_COLOUR] pt_cancel_notif_id = extras.getString(PT_CANCEL_NOTIF_ID) pt_cancel_notif_ids = Utils.getNotificationIds(context) actions = Utils.getActionKeys(extras) pt_subtitle = extras.getString(PT_SUBTITLE) pt_collapse_key = extras[PT_COLLAPSE_KEY] pt_flip_interval = Utils.getFlipInterval(extras) + pt_scale_type = PTScaleType.fromString(extras.getString(PT_SCALE_TYPE)) pID = extras.getString(Constants.WZRK_PUSH_ID) pt_manual_carousel_type = extras.getString(PT_MANUAL_CAROUSEL_TYPE) if (config != null) { @@ -446,141 +455,191 @@ class TemplateRenderer : INotificationRenderer, AudibleNotification { if (pt_subtitle == null || pt_subtitle!!.isEmpty()) { pt_subtitle = extras.getString(Constants.WZRK_SUBTITLE) } - if (pt_small_icon_clr == null || pt_small_icon_clr!!.isEmpty()) { - pt_small_icon_clr = extras.getString(Constants.WZRK_COLOR) - } if (pt_collapse_key == null) { pt_collapse_key = extras[Constants.WZRK_COLLAPSE] } } override fun setActionButtons( - context: Context, - extras: Bundle, + context: Context?, + extras: Bundle?, notificationId: Int, - nb: Builder, actions: JSONArray? + nb: Builder, + actions: JSONArray? ): Builder { - val intentServiceName = ManifestInfo.getInstance(context).intentServiceName - var clazz: Class<*>? = null - if (intentServiceName != null) { - try { - clazz = Class.forName(intentServiceName) - } catch (e: ClassNotFoundException) { - try { - clazz = Class.forName("com.clevertap.android.sdk.pushnotification.CTNotificationIntentService") - } catch (ex: ClassNotFoundException) { - Logger.d("No Intent Service found") - } - } - } else { - try { - clazz = Class.forName("com.clevertap.android.sdk.pushnotification.CTNotificationIntentService") - } catch (ex: ClassNotFoundException) { - Logger.d("No Intent Service found") + actionButtons.forEach { button -> + val pendingIntent = actionButtonPendingIntents[button.id] + if (pendingIntent != null) { + nb.addAction(button.icon, button.label, pendingIntent) } } - val isCTIntentServiceAvailable = com.clevertap.android.sdk.Utils.isServiceAvailable(context, clazz) + return nb + } + + internal fun getActionButtons( + context: Context, + extras: Bundle, + notificationId: Int, + actions: JSONArray? + ): List { + val actionButtons = mutableListOf() if (actions != null && actions.length() > 0) { for (i in 0 until actions.length()) { try { val action = actions.getJSONObject(i) val label = action.optString("l") - val dl = action.optString("dl") val ico = action.optString(actionButtonIconKey) val id = action.optString("id") - val autoCancel = action.optBoolean("ac", true) + if (label.isEmpty() || id.isEmpty()) { Logger.d("not adding push notification action: action label or id missing") continue } var icon = 0 - if (!ico.isEmpty()) { + if (ico.isNotEmpty()) { try { icon = context.resources.getIdentifier(ico, "drawable", context.packageName) } catch (t: Throwable) { Logger.d("unable to add notification action icon: " + t.localizedMessage) } } - var sendToCTIntentService = (VERSION.SDK_INT < VERSION_CODES.S && autoCancel - && isCTIntentServiceAvailable) - val dismissOnClick = extras.getString("pt_dismiss_on_click") - /** - * Send to CTIntentService in case (OS >= S) and notif is for Push templates with remind action - */ - if (!sendToCTIntentService && PushNotificationHandler.isForPushTemplates(extras) - && id.contains("remind") && dismissOnClick != null && - dismissOnClick.equals("true", ignoreCase = true) && autoCancel && - isCTIntentServiceAvailable - ) { - sendToCTIntentService = true - } - /** - * Send to CTIntentService in case (OS >= S) and notif is for Push templates with pt_dismiss_on_click - * true - */ - if (!sendToCTIntentService && PushNotificationHandler.isForPushTemplates(extras) - && dismissOnClick != null && dismissOnClick.equals("true", ignoreCase = true) - && autoCancel && isCTIntentServiceAvailable - ) { - sendToCTIntentService = true - } - var actionLaunchIntent: Intent? - if (sendToCTIntentService) { - actionLaunchIntent = Intent(CTNotificationIntentService.MAIN_ACTION) - actionLaunchIntent.setPackage(context.packageName) - actionLaunchIntent.putExtra( - Constants.KEY_CT_TYPE, - CTNotificationIntentService.TYPE_BUTTON_CLICK - ) - if (dl.isNotEmpty()) { - actionLaunchIntent.putExtra("dl", dl) - } - } else { - if (dl.isNotEmpty()) { - actionLaunchIntent = Intent(Intent.ACTION_VIEW, Uri.parse(dl)) - Utils.setPackageNameFromResolveInfoList( - context, - actionLaunchIntent - ) - } else { - actionLaunchIntent = context.packageManager - .getLaunchIntentForPackage(context.packageName) - } - } - if (actionLaunchIntent != null) { - actionLaunchIntent.putExtras(extras) - actionLaunchIntent.removeExtra(Constants.WZRK_ACTIONS) - actionLaunchIntent.putExtra("actionId", id) - actionLaunchIntent.putExtra("autoCancel", autoCancel) - actionLaunchIntent.putExtra("wzrk_c2a", id) - actionLaunchIntent.putExtra("notificationId", notificationId) - actionLaunchIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP + + // Create the button object + val button = ActionButton(id, label, icon) + actionButtons.add(button) + + // Create and store the pendingIntent + val pendingIntent = createActionButtonPendingIntent(context, action, extras, notificationId) + if (pendingIntent != null) { + actionButtonPendingIntents[id] = pendingIntent } - var actionIntent: PendingIntent? - val requestCode = Random().nextInt() - var flagsActionLaunchPendingIntent = PendingIntent.FLAG_UPDATE_CURRENT - if (VERSION.SDK_INT >= VERSION_CODES.M) { - flagsActionLaunchPendingIntent = - flagsActionLaunchPendingIntent or PendingIntent.FLAG_IMMUTABLE - } - actionIntent = if (sendToCTIntentService) { - PendingIntent.getService( - context, requestCode, - actionLaunchIntent!!, flagsActionLaunchPendingIntent - ) - } else { - PendingIntent.getActivity( - context, requestCode, - actionLaunchIntent!!, flagsActionLaunchPendingIntent, null - ) - } - nb.addAction(icon, label, actionIntent) } catch (t: Throwable) { Logger.d("error adding notification action : " + t.localizedMessage) } } - } // Uncommon - END - return nb + } + return actionButtons + } + + private fun createActionButtonPendingIntent( + context: Context, + action: JSONObject, + extras: Bundle, + notificationId: Int + ): PendingIntent? { + try { + // Extract necessary properties from the action + val dl = action.optString("dl") + val id = action.optString("id") + val autoCancel = action.optBoolean("ac", true) + + // Determine if the intent should be sent to CTIntentService + val intentServiceName = ManifestInfo.getInstance(context).intentServiceName + var clazz: Class<*>? = null + if (intentServiceName != null) { + try { + clazz = Class.forName(intentServiceName) + } catch (e: ClassNotFoundException) { + try { + clazz = Class.forName("com.clevertap.android.sdk.pushnotification.CTNotificationIntentService") + } catch (ex: ClassNotFoundException) { + Logger.d("No Intent Service found") + } + } + } else { + try { + clazz = Class.forName("com.clevertap.android.sdk.pushnotification.CTNotificationIntentService") + } catch (ex: ClassNotFoundException) { + Logger.d("No Intent Service found") + } + } + val isCTIntentServiceAvailable = com.clevertap.android.sdk.Utils.isServiceAvailable(context, clazz) + + var sendToCTIntentService = (VERSION.SDK_INT < VERSION_CODES.S && autoCancel + && isCTIntentServiceAvailable) + val dismissOnClick = extras.getString("pt_dismiss_on_click") + + /** + * Send to CTIntentService in case (OS >= S) and notif is for Push templates with remind action + */ + if (!sendToCTIntentService && PushNotificationHandler.isForPushTemplates(extras) + && id.contains("remind") && dismissOnClick != null && + dismissOnClick.equals("true", ignoreCase = true) && autoCancel && + isCTIntentServiceAvailable + ) { + sendToCTIntentService = true + } + + /** + * Send to CTIntentService in case (OS >= S) and notif is for Push templates with pt_dismiss_on_click + * true + */ + if (!sendToCTIntentService && PushNotificationHandler.isForPushTemplates(extras) + && dismissOnClick != null && dismissOnClick.equals("true", ignoreCase = true) + && autoCancel && isCTIntentServiceAvailable + ) { + sendToCTIntentService = true + } + + // Create the appropriate intent + var actionLaunchIntent: Intent? = null + + if (sendToCTIntentService) { + actionLaunchIntent = Intent(CTNotificationIntentService.MAIN_ACTION) + actionLaunchIntent.setPackage(context.packageName) + actionLaunchIntent.putExtra( + Constants.KEY_CT_TYPE, + CTNotificationIntentService.TYPE_BUTTON_CLICK + ) + if (dl.isNotEmpty()) { + actionLaunchIntent.putExtra("dl", dl) + } + } else { + if (dl.isNotEmpty()) { + actionLaunchIntent = Intent(Intent.ACTION_VIEW, Uri.parse(dl)) + Utils.setPackageNameFromResolveInfoList(context, actionLaunchIntent) + } else { + actionLaunchIntent = context.packageManager + .getLaunchIntentForPackage(context.packageName) + } + } + + // Configure intent extras + if (actionLaunchIntent != null) { + actionLaunchIntent.putExtras(extras) + actionLaunchIntent.removeExtra(Constants.WZRK_ACTIONS) + actionLaunchIntent.putExtra("actionId", id) + actionLaunchIntent.putExtra("autoCancel", autoCancel) + actionLaunchIntent.putExtra("wzrk_c2a", id) + actionLaunchIntent.putExtra("notificationId", notificationId) + actionLaunchIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP + } else { + return null + } + + // Create and return the PendingIntent + val requestCode = Random().nextInt() + var flagsActionLaunchPendingIntent = PendingIntent.FLAG_UPDATE_CURRENT + if (VERSION.SDK_INT >= VERSION_CODES.M) { + flagsActionLaunchPendingIntent = + flagsActionLaunchPendingIntent or PendingIntent.FLAG_IMMUTABLE + } + + return if (sendToCTIntentService) { + PendingIntent.getService( + context, requestCode, + actionLaunchIntent, flagsActionLaunchPendingIntent + ) + } else { + PendingIntent.getActivity( + context, requestCode, + actionLaunchIntent, flagsActionLaunchPendingIntent, null + ) + } + } catch (t: Throwable) { + Logger.d("error creating pending intent for action button: " + t.localizedMessage) + return null + } } companion object { diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/Utils.java b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/Utils.java index 6bfaae337..aeb74b8b7 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/Utils.java +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/Utils.java @@ -2,6 +2,9 @@ import static android.content.Context.NOTIFICATION_SERVICE; +import static com.clevertap.android.pushtemplates.PTConstants.COLOR_KEYS; +import static com.clevertap.android.pushtemplates.PTConstants.PT_DARK_MODE_SUFFIX; + import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -49,7 +52,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Objects; +import java.util.Map; import java.util.concurrent.Callable; @SuppressWarnings("WeakerAccess") @@ -196,6 +199,44 @@ static ArrayList getPriceFromExtras(Bundle extras) { return stList; } + /** + * Creates a map of colors for the specified display mode (dark/light) + * @param extras The original extras bundle containing all color values + * @param isDarkMode Whether to use dark mode colors + * @return A map containing appropriate colors for the specified mode + */ + public static Map createColorMap(Bundle extras, boolean isDarkMode) { + Map colorMap = new HashMap<>(); + + // Process each color key + for (String key : COLOR_KEYS) { + String color = getDarkModeAdaptiveColor(extras, isDarkMode, key); + colorMap.put(key, color); + } + + return colorMap; + } + + /** + * Gets color value based on dark mode preference + * @param extras The extras bundle containing color values + * @param isDarkMode Whether to use dark mode colors + * @param key The color key to retrieve + * @return The appropriate color for the specified mode + */ + static String getDarkModeAdaptiveColor(Bundle extras, boolean isDarkMode, String key) { + String colorDark = extras.getString(key + PT_DARK_MODE_SUFFIX); + String color = extras.getString(key); + + if (isDarkMode && colorDark != null) { + return colorDark; + } else { + return color; + } + } + + + public static void loadImageBitmapIntoRemoteView(int imageViewID, Bitmap image, RemoteViews remoteViews) { remoteViews.setImageViewBitmap(imageViewID, image); @@ -592,16 +633,21 @@ static boolean isNotificationChannelEnabled(NotificationChannel channel) { return false; } - public static Bitmap setBitMapColour(Context context, int resourceID, String clr) - throws NullPointerException { - if (clr != null && !clr.isEmpty()) { - int color = getColour(clr, PTConstants.PT_COLOUR_GREY); + public static Bitmap setBitMapColour(Context context, int resourceID, String clr, String defaultClr) { + int color = getColour(clr, defaultClr); + + try { + Drawable mDrawable = ContextCompat.getDrawable(context, resourceID); + if (mDrawable == null) { + return null; + } - Drawable mDrawable = Objects.requireNonNull(ContextCompat.getDrawable(context, resourceID)).mutate(); + mDrawable = mDrawable.mutate(); mDrawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)); - return Utils.drawableToBitmap(mDrawable); + return drawableToBitmap(mDrawable); + } catch (Exception e) { + return null; } - return null; } public static int getColour(String clr, String default_clr) { @@ -613,6 +659,25 @@ public static int getColour(String clr, String default_clr) { } } + /** + * Safely parses a color string (e.g., "#RRGGBB" or "#AARRGGBB") into an integer color value. + *

+ * If the input is null, empty, or an invalid color format, this method returns {@code null} instead of throwing an exception. + *

+ * + * @param clr the color string to parse (e.g., "#FF0000" for red) + * @return the parsed color as an {@link Integer}, or {@code null} if parsing fails + */ + @Nullable + public static Integer getColourOrNull(String clr) { + try { + return Color.parseColor(clr); + } catch (Exception e) { + PTLog.debug("Can not parse colour value: " + clr); + return null; + } + } + static void setFallback(Boolean val) { PTConstants.PT_FALLBACK = val; } diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ActionButtonsContentView.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ActionButtonsContentView.kt new file mode 100644 index 000000000..8a3041c48 --- /dev/null +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ActionButtonsContentView.kt @@ -0,0 +1,47 @@ +package com.clevertap.android.pushtemplates.content +import android.app.PendingIntent +import android.content.Context +import android.os.Build.VERSION +import android.os.Build.VERSION_CODES +import android.view.View +import com.clevertap.android.pushtemplates.ActionButton +import com.clevertap.android.pushtemplates.R +import com.clevertap.android.pushtemplates.TemplateRenderer +import com.clevertap.android.sdk.Logger + +internal open class ActionButtonsContentView(context: Context, layoutId: Int, renderer: TemplateRenderer) : + ContentView(context, layoutId, renderer) { + init { + setActionButtons(renderer.actionButtons, renderer.actionButtonPendingIntents) + } + + private fun setActionButtons(actionButtons: List, pendingIntentsMap: Map) { + if (VERSION.SDK_INT >= VERSION_CODES.S) { + // Action Buttons for API 31 and above are set using the OS API and not remote views + return + } + + var visibleButtonCount = 0 + actionButtons.take(2).forEach { button -> + val buttonId = if (visibleButtonCount == 0) { + R.id.action0 + } else { + R.id.action1 + } + if (button.label.isEmpty()) { + Logger.d("not adding push notification action: action label or id missing") + return@forEach + } + remoteView.setTextViewText(buttonId, button.label) + remoteView.setViewVisibility(buttonId, View.VISIBLE) + + // Set up the pending intent for this button + val pendingIntent = pendingIntentsMap[button.id] + if (pendingIntent != null) { + remoteView.setOnClickPendingIntent(buttonId, pendingIntent) + } + + visibleButtonCount++ + } + } +} \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/AutoCarouselContentView.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/AutoCarouselContentView.kt index cda6b14ad..8a8d7ed5f 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/AutoCarouselContentView.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/AutoCarouselContentView.kt @@ -3,19 +3,21 @@ package com.clevertap.android.pushtemplates.content import android.content.Context import android.os.Build import android.text.Html +import android.view.View import android.widget.RemoteViews import com.clevertap.android.pushtemplates.PTLog +import com.clevertap.android.pushtemplates.PTScaleType import com.clevertap.android.pushtemplates.R import com.clevertap.android.pushtemplates.TemplateRenderer import com.clevertap.android.pushtemplates.Utils -class AutoCarouselContentView(context: Context, renderer: TemplateRenderer) : +internal class AutoCarouselContentView(context: Context, renderer: TemplateRenderer) : BigImageContentView(context, renderer, R.layout.auto_carousel) { init { setCustomContentViewMessageSummary(renderer.pt_msg_summary) setCustomContentViewViewFlipperInterval(renderer.pt_flip_interval) - setViewFlipper() + setViewFlipper(renderer.pt_scale_type) } private fun setCustomContentViewMessageSummary(pt_msg_summary: String?) { @@ -35,14 +37,25 @@ class AutoCarouselContentView(context: Context, renderer: TemplateRenderer) : remoteView.setInt(R.id.view_flipper, "setFlipInterval", interval) } - private fun setViewFlipper() { - var imageCounter = 0 - for (index in renderer.imageList!!.indices) { - val tempRemoteView = RemoteViews(context.packageName, R.layout.image_view) - Utils.loadImageURLIntoRemoteView(R.id.fimg, renderer.imageList!![index], tempRemoteView,context) + private fun setViewFlipper(scaleType: PTScaleType) { + val imageViewId = when (scaleType) { + PTScaleType.FIT_CENTER -> R.id.big_image_fitCenter + PTScaleType.CENTER_CROP -> R.id.big_image + } + + renderer.imageList?.forEach { imageUrl -> + val tempRemoteView = RemoteViews(context.packageName, R.layout.image_view_flipper_dynamic) + + Utils.loadImageURLIntoRemoteView( + imageViewId, + imageUrl, + tempRemoteView, + context + ) + if (!Utils.getFallback()) { + tempRemoteView.setViewVisibility(imageViewId, View.VISIBLE) remoteView.addView(R.id.view_flipper, tempRemoteView) - imageCounter++ } else { PTLog.debug("Skipping Image in Auto Carousel.") } diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/BigImageContentView.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/BigImageContentView.kt index 9ab226d2a..f27a19970 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/BigImageContentView.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/BigImageContentView.kt @@ -1,54 +1,39 @@ package com.clevertap.android.pushtemplates.content import android.content.Context -import android.os.Build +import android.os.Build.VERSION +import android.os.Build.VERSION_CODES import android.text.Html -import android.view.View import com.clevertap.android.pushtemplates.R import com.clevertap.android.pushtemplates.TemplateRenderer -import com.clevertap.android.pushtemplates.Utils +import com.clevertap.android.pushtemplates.isNotNullAndEmpty -open class BigImageContentView( - context: Context, - renderer: TemplateRenderer, - layoutId: Int = R.layout.image_only_big -) : - ContentView(context, layoutId, renderer) { +internal open class BigImageContentView( + context: Context, renderer: TemplateRenderer, layoutId: Int = R.layout.image_only_big +) : ActionButtonsContentView(context, layoutId, renderer) { init { setCustomContentViewBasicKeys() setCustomContentViewTitle(renderer.pt_title) setCustomContentViewMessage(renderer.pt_msg) - setCustomContentViewExpandedBackgroundColour(renderer.pt_bg) - setCustomContentViewTitleColour(renderer.pt_title_clr) - setCustomContentViewMessageColour(renderer.pt_msg_clr) + setCustomBackgroundColour(renderer.pt_bg, R.id.content_view_big) + setCustomTextColour(renderer.pt_title_clr, R.id.title) + setCustomTextColour(renderer.pt_msg_clr, R.id.msg) setCustomContentViewMessageSummary(renderer.pt_msg_summary) setCustomContentViewSmallIcon() - setCustomContentViewBigImage(renderer.pt_big_img) + setCustomContentViewBigImage(renderer.pt_big_img, renderer.pt_scale_type) setCustomContentViewLargeIcon(renderer.pt_large_icon) } private fun setCustomContentViewMessageSummary(pt_msg_summary: String?) { - if (pt_msg_summary != null && pt_msg_summary.isNotEmpty()) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (pt_msg_summary.isNotNullAndEmpty()) { + if (VERSION.SDK_INT >= VERSION_CODES.N) { remoteView.setTextViewText( - R.id.msg, - Html.fromHtml(pt_msg_summary, Html.FROM_HTML_MODE_LEGACY) + R.id.msg, Html.fromHtml(pt_msg_summary, Html.FROM_HTML_MODE_LEGACY) ) } else { remoteView.setTextViewText(R.id.msg, Html.fromHtml(pt_msg_summary)) } } } - - private fun setCustomContentViewBigImage(pt_big_img: String?) { - if (pt_big_img != null && pt_big_img.isNotEmpty()) { - Utils.loadImageURLIntoRemoteView(R.id.big_image, pt_big_img, remoteView,context) - if (Utils.getFallback()) { - remoteView.setViewVisibility(R.id.big_image, View.GONE) - } - } else { - remoteView.setViewVisibility(R.id.big_image, View.GONE) - } - } } \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ContentView.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ContentView.kt index 797c935b3..e3335208f 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ContentView.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ContentView.kt @@ -7,12 +7,15 @@ import android.view.View import android.widget.RemoteViews import com.clevertap.android.pushtemplates.PTConstants import com.clevertap.android.pushtemplates.PTLog +import com.clevertap.android.pushtemplates.PTScaleType import com.clevertap.android.pushtemplates.R import com.clevertap.android.pushtemplates.TemplateRenderer import com.clevertap.android.pushtemplates.Utils +import com.clevertap.android.pushtemplates.isNotNullAndEmpty -open class ContentView( - internal var context: Context, layoutId: Int, +internal open class ContentView( + internal var context: Context, + layoutId: Int, internal var renderer: TemplateRenderer ) { @@ -34,21 +37,12 @@ open class ContentView( remoteView.setViewVisibility(R.id.subtitle, View.GONE) remoteView.setViewVisibility(R.id.sep_subtitle, View.GONE) } - if (renderer.pt_meta_clr != null && renderer.pt_meta_clr!!.isNotEmpty()) { - remoteView.setTextColor( - R.id.app_name, - Utils.getColour(renderer.pt_meta_clr, PTConstants.PT_META_CLR_DEFAULTS) - ) - remoteView.setTextColor( - R.id.timestamp, - Utils.getColour(renderer.pt_meta_clr, PTConstants.PT_META_CLR_DEFAULTS) - ) - remoteView.setTextColor( - R.id.subtitle, - Utils.getColour(renderer.pt_meta_clr, PTConstants.PT_META_CLR_DEFAULTS) - ) - setDotSep() + + listOf(R.id.app_name, R.id.timestamp, R.id.subtitle).forEach { resId -> + setCustomTextColour(renderer.pt_meta_clr, resId) } + + setDotSep() } private fun setDotSep() { @@ -58,14 +52,14 @@ open class ContentView( "drawable", context.packageName ) - renderer.pt_dot_sep = Utils.setBitMapColour(context, renderer.pt_dot, renderer.pt_meta_clr) + renderer.pt_dot_sep = Utils.setBitMapColour(context, renderer.pt_dot, renderer.pt_meta_clr, PTConstants.PT_META_CLR_DEFAULTS) } catch (e: NullPointerException) { PTLog.debug("NPE while setting dot sep color") } } fun setCustomContentViewTitle(pt_title: String?) { - if (pt_title != null && pt_title.isNotEmpty()) { + if (pt_title.isNotNullAndEmpty()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { remoteView.setTextViewText( R.id.title, @@ -78,7 +72,7 @@ open class ContentView( } fun setCustomContentViewMessage(pt_msg: String?) { - if (pt_msg != null && pt_msg.isNotEmpty()) { + if (pt_msg.isNotNullAndEmpty()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { remoteView.setTextViewText( R.id.msg, @@ -90,34 +84,6 @@ open class ContentView( } } - fun setCustomContentViewCollapsedBackgroundColour(pt_bg: String?) { - if (pt_bg != null && pt_bg.isNotEmpty()) { - remoteView.setInt( - R.id.content_view_small, - "setBackgroundColor", - Utils.getColour(pt_bg, PTConstants.PT_COLOUR_WHITE) - ) - } - } - - fun setCustomContentViewTitleColour(pt_title_clr: String?) { - if (pt_title_clr != null && pt_title_clr.isNotEmpty()) { - remoteView.setTextColor( - R.id.title, - Utils.getColour(pt_title_clr, PTConstants.PT_COLOUR_BLACK) - ) - } - } - - fun setCustomContentViewMessageColour(pt_msg_clr: String?) { - if (pt_msg_clr != null && pt_msg_clr.isNotEmpty()) { - remoteView.setTextColor( - R.id.msg, - Utils.getColour(pt_msg_clr, PTConstants.PT_COLOUR_BLACK) - ) - } - } - fun setCustomContentViewSmallIcon() { if (renderer.pt_small_icon != null) { Utils.loadImageBitmapIntoRemoteView(R.id.small_icon, renderer.pt_small_icon, remoteView) @@ -127,20 +93,46 @@ open class ContentView( } fun setCustomContentViewLargeIcon(pt_large_icon: String?) { - if (pt_large_icon != null && pt_large_icon.isNotEmpty()) { + if (pt_large_icon.isNotNullAndEmpty()) { Utils.loadImageURLIntoRemoteView(R.id.large_icon, pt_large_icon, remoteView,context) } else { remoteView.setViewVisibility(R.id.large_icon, View.GONE) } } - fun setCustomContentViewExpandedBackgroundColour(pt_bg: String?) { - if (pt_bg != null && pt_bg.isNotEmpty()) { - remoteView.setInt( - R.id.content_view_big, - "setBackgroundColor", - Utils.getColour(pt_bg, PTConstants.PT_COLOUR_WHITE) - ) + fun setCustomBackgroundColour(pt_bg: String?, resId: Int) { + pt_bg?.takeIf { it.isNotEmpty() }?.let { + Utils.getColourOrNull(it)?.let { color -> + remoteView.setInt( + resId, + "setBackgroundColor", + color + ) + } + } + } + + fun setCustomTextColour(pt_text_clr: String?, resId: Int) { + pt_text_clr?.takeIf { it.isNotEmpty() }?.let { + Utils.getColourOrNull(it)?.let { color -> + remoteView.setTextColor( + resId, + color + ) + } + } + } + + fun setCustomContentViewBigImage(pt_big_img: String?, scaleType: PTScaleType) { + if (pt_big_img.isNotNullAndEmpty()) { + val imageViewId = when (scaleType) { + PTScaleType.FIT_CENTER -> R.id.big_image_fitCenter + PTScaleType.CENTER_CROP -> R.id.big_image + } + Utils.loadImageURLIntoRemoteView(imageViewId, pt_big_img, remoteView, context) + if (!Utils.getFallback()) { + remoteView.setViewVisibility(imageViewId, View.VISIBLE) + } } } } \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/FiveIconBigContentView.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/FiveIconBigContentView.kt index 91c317154..fd3a397ec 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/FiveIconBigContentView.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/FiveIconBigContentView.kt @@ -11,7 +11,7 @@ import com.clevertap.android.pushtemplates.Utils import com.clevertap.android.sdk.Constants import com.clevertap.android.sdk.pushnotification.LaunchPendingIntentFactory -class FiveIconBigContentView constructor( +internal class FiveIconBigContentView constructor( context: Context, renderer: TemplateRenderer, extras: Bundle @@ -23,7 +23,7 @@ class FiveIconBigContentView constructor( if (renderer.pt_title == null || renderer.pt_title!!.isEmpty()) { renderer.pt_title = Utils.getApplicationName(context) } - setCustomContentViewExpandedBackgroundColour(renderer.pt_bg) + setCustomBackgroundColour(renderer.pt_bg, R.id.content_view_big) for (imageKey in renderer.imageList!!.indices) { if (imageKey == 0) { remoteView.setViewVisibility(R.id.cta1, View.VISIBLE) diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/FiveIconSmallContentView.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/FiveIconSmallContentView.kt index f43c63b9b..eec8c7187 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/FiveIconSmallContentView.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/FiveIconSmallContentView.kt @@ -11,7 +11,7 @@ import com.clevertap.android.pushtemplates.Utils import com.clevertap.android.sdk.Constants import com.clevertap.android.sdk.pushnotification.LaunchPendingIntentFactory -class FiveIconSmallContentView constructor( +internal class FiveIconSmallContentView constructor( context: Context, renderer: TemplateRenderer, extras: Bundle @@ -23,7 +23,7 @@ class FiveIconSmallContentView constructor( if (renderer.pt_title == null || renderer.pt_title!!.isEmpty()) { renderer.pt_title = Utils.getApplicationName(context) } - setCustomContentViewExpandedBackgroundColour(renderer.pt_bg) + setCustomBackgroundColour(renderer.pt_bg, R.id.content_view_big) for (imageKey in renderer.imageList!!.indices) { if (imageKey == 0) { remoteView.setViewVisibility(R.id.cta1, View.VISIBLE) diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ManualCarouselContentView.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ManualCarouselContentView.kt index d9716049e..c4b55cca0 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ManualCarouselContentView.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ManualCarouselContentView.kt @@ -8,6 +8,7 @@ import android.view.View import android.widget.RemoteViews import com.clevertap.android.pushtemplates.PTConstants import com.clevertap.android.pushtemplates.PTLog +import com.clevertap.android.pushtemplates.PTScaleType import com.clevertap.android.pushtemplates.R import com.clevertap.android.pushtemplates.R.id import com.clevertap.android.pushtemplates.TemplateRenderer @@ -16,44 +17,57 @@ import com.clevertap.android.pushtemplates.content.PendingIntentFactory.getPendi import com.clevertap.android.sdk.Constants import java.util.ArrayList -class ManualCarouselContentView(context: Context, renderer: TemplateRenderer, extras: Bundle) : +internal class ManualCarouselContentView(context: Context, renderer: TemplateRenderer, extras: Bundle) : BigImageContentView(context, renderer, R.layout.manual_carousel) { init { setCustomContentViewMessageSummary(renderer.pt_msg_summary) + val scaleType = renderer.pt_scale_type + remoteView.setViewVisibility(R.id.leftArrowPos0, View.VISIBLE) remoteView.setViewVisibility(R.id.rightArrowPos0, View.VISIBLE) var imageCounter = 0 var isFirstImageOk = false - val dl = renderer.deepLinkList!![0] var currentPosition = 0 val tempImageList = ArrayList() - for (index in renderer.imageList!!.indices) { - val tempRemoteView = RemoteViews(context.packageName, R.layout.image_view_rounded) + val imageViewId = when (scaleType) { + PTScaleType.FIT_CENTER -> R.id.big_image_fitCenter + PTScaleType.CENTER_CROP -> R.id.big_image + } + renderer.imageList?.forEachIndexed { index, imageUrl -> + val tempRemoteView = RemoteViews(context.packageName, R.layout.image_view_flipper_dynamic) + Utils.loadImageURLIntoRemoteView( - R.id.flipper_img, - renderer.imageList!![index], + imageViewId, + imageUrl, tempRemoteView, context ) + if (!Utils.getFallback()) { if (!isFirstImageOk) { currentPosition = index isFirstImageOk = true } + + tempRemoteView.setViewVisibility(imageViewId, View.VISIBLE) remoteView.addView(R.id.carousel_image, tempRemoteView) remoteView.addView(R.id.carousel_image_right, tempRemoteView) remoteView.addView(R.id.carousel_image_left, tempRemoteView) + imageCounter++ - tempImageList.add(renderer.imageList!![index]) + tempImageList.add(imageUrl) } else { - if (renderer.deepLinkList != null && renderer.deepLinkList!!.size == renderer.imageList!!.size) { - renderer.deepLinkList!!.removeAt(index) + val deepLinkList = renderer.deepLinkList + val imageListSize = renderer.imageList?.size ?: 0 + if (deepLinkList != null && deepLinkList.size == imageListSize) { + deepLinkList.removeAt(index) } PTLog.debug("Skipping Image in Manual Carousel.") } } + if (renderer.pt_manual_carousel_type == null || !renderer.pt_manual_carousel_type.equals( PTConstants.PT_MANUAL_CAROUSEL_FILMSTRIP, ignoreCase = true diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ProductDisplayLinearBigContentView.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ProductDisplayLinearBigContentView.kt index 89aecaed0..9e5142e1f 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ProductDisplayLinearBigContentView.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ProductDisplayLinearBigContentView.kt @@ -8,13 +8,14 @@ import android.view.View import android.widget.RemoteViews import com.clevertap.android.pushtemplates.PTConstants import com.clevertap.android.pushtemplates.PTLog +import com.clevertap.android.pushtemplates.PTScaleType import com.clevertap.android.pushtemplates.R import com.clevertap.android.pushtemplates.TemplateRenderer import com.clevertap.android.pushtemplates.Utils import com.clevertap.android.sdk.Constants import java.util.ArrayList -open class ProductDisplayLinearBigContentView( +internal open class ProductDisplayLinearBigContentView( context: Context, renderer: TemplateRenderer, extras: Bundle, layoutId: Int = R.layout.product_display_linear_expanded ) : @@ -38,11 +39,14 @@ open class ProductDisplayLinearBigContentView( setCustomContentViewBasicKeys() if (renderer.bigTextList!!.isNotEmpty()) setCustomContentViewText(R.id.product_name, productName) if (renderer.priceList!!.isNotEmpty()) setCustomContentViewText(R.id.product_price, productPrice) - setCustomContentViewExpandedBackgroundColour(renderer.pt_bg) + setCustomContentViewButtonLabel(R.id.product_action, renderer.pt_product_display_action) - setCustomContentViewButtonColour(R.id.product_action, renderer.pt_product_display_action_clr) - setCustomContentViewButtonText(R.id.product_action, renderer.pt_product_display_action_text_clr) - setImageList(extras) + + setCustomBackgroundColour(renderer.pt_bg, R.id.content_view_big) + setCustomBackgroundColour(renderer.pt_product_display_action_clr, R.id.product_action) + setCustomTextColour(renderer.pt_product_display_action_text_clr, R.id.product_action) + + setImageList(extras, renderer.pt_scale_type) remoteView.setDisplayedChild(R.id.carousel_image, currentPosition) setCustomContentViewSmallIcon() @@ -86,7 +90,7 @@ open class ProductDisplayLinearBigContentView( ) } - internal fun setImageList(extras: Bundle) { + internal fun setImageList(extras: Bundle, scaleType: PTScaleType) { var imageCounter = 0 var isFirstImageOk = false val smallImageLayoutIds = ArrayList() @@ -94,32 +98,38 @@ open class ProductDisplayLinearBigContentView( smallImageLayoutIds.add(R.id.small_image2) smallImageLayoutIds.add(R.id.small_image3) val tempImageList = ArrayList() - for (index in renderer.imageList!!.indices) { + val imageViewId = when (scaleType) { + PTScaleType.FIT_CENTER -> R.id.big_image_fitCenter + PTScaleType.CENTER_CROP -> R.id.big_image + } + + renderer.imageList?.forEachIndexed { index, imageUrl -> Utils.loadImageURLIntoRemoteView( - smallImageLayoutIds[imageCounter], renderer.imageList!![index], remoteView,context + smallImageLayoutIds[imageCounter], imageUrl, remoteView, context ) - val tempRemoteView = RemoteViews(context.packageName, R.layout.image_view) - Utils.loadImageURLIntoRemoteView(R.id.fimg, renderer.imageList!![index], tempRemoteView,context) + + val tempRemoteView = RemoteViews(context.packageName, R.layout.image_view_flipper_dynamic) + Utils.loadImageURLIntoRemoteView(imageViewId, imageUrl, tempRemoteView, context) + if (!Utils.getFallback()) { if (!isFirstImageOk) { isFirstImageOk = true } - remoteView.setViewVisibility( - smallImageLayoutIds[imageCounter], - View.VISIBLE - ) + tempRemoteView.setViewVisibility(imageViewId, View.VISIBLE) + remoteView.setViewVisibility(smallImageLayoutIds[imageCounter], View.VISIBLE) remoteView.addView(R.id.carousel_image, tempRemoteView) imageCounter++ - tempImageList.add(renderer.imageList!![index]) + tempImageList.add(imageUrl) } else { - renderer.deepLinkList!!.removeAt(index) - renderer.bigTextList!!.removeAt(index) - renderer.smallTextList!!.removeAt(index) - renderer.priceList!!.removeAt(index) + renderer.deepLinkList?.removeAt(index) + renderer.bigTextList?.removeAt(index) + renderer.smallTextList?.removeAt(index) + renderer.priceList?.removeAt(index) } } + extras.putStringArrayList(PTConstants.PT_IMAGE_LIST, tempImageList) extras.putStringArrayList(PTConstants.PT_DEEPLINK_LIST, renderer.deepLinkList) extras.putStringArrayList(PTConstants.PT_BIGTEXT_LIST, renderer.bigTextList) @@ -156,29 +166,4 @@ open class ProductDisplayLinearBigContentView( } } } - - private fun setCustomContentViewButtonColour(resourceID: Int, pt_product_display_action_clr: String?) { - if (pt_product_display_action_clr != null && pt_product_display_action_clr.isNotEmpty()) { - remoteView.setInt( - resourceID, - "setBackgroundColor", - Utils.getColour( - pt_product_display_action_clr, - PTConstants.PT_PRODUCT_DISPLAY_ACTION_CLR_DEFAULTS - ) - ) - } - } - - internal fun setCustomContentViewButtonText(resourceID: Int, pt_product_display_action_text_clr: String?) { - if (pt_product_display_action_text_clr != null && pt_product_display_action_text_clr.isNotEmpty()) { - remoteView.setTextColor( - resourceID, - Utils.getColour( - pt_product_display_action_text_clr, - PTConstants.PT_PRODUCT_DISPLAY_ACTION_TEXT_CLR_DEFAULT - ) - ) - } - } } \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ProductDisplayNonLinearBigContentView.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ProductDisplayNonLinearBigContentView.kt index ca26324bc..f0f7511e8 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ProductDisplayNonLinearBigContentView.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ProductDisplayNonLinearBigContentView.kt @@ -2,24 +2,16 @@ package com.clevertap.android.pushtemplates.content import android.content.Context import android.os.Bundle -import com.clevertap.android.pushtemplates.PTConstants import com.clevertap.android.pushtemplates.R import com.clevertap.android.pushtemplates.TemplateRenderer -import com.clevertap.android.pushtemplates.Utils -class ProductDisplayNonLinearBigContentView(context: Context, renderer: TemplateRenderer, extras: Bundle) : +internal class ProductDisplayNonLinearBigContentView(context: Context, renderer: TemplateRenderer, extras: Bundle) : ProductDisplayLinearBigContentView(context, renderer, extras, R.layout.product_display_template) { init { setCustomContentViewTitle(productName) setCustomContentViewMessage(productMessage) - setCustomContentViewElementColour(R.id.msg, renderer.pt_msg_clr) - setCustomContentViewElementColour(R.id.title, renderer.pt_title_clr) - } - - private fun setCustomContentViewElementColour(rId: Int, colour: String?) { - if (colour != null && colour.isNotEmpty()) { - remoteView.setTextColor(rId, Utils.getColour(colour, PTConstants.PT_COLOUR_BLACK)) - } + setCustomTextColour(renderer.pt_msg_clr, R.id.msg) + setCustomTextColour(renderer.pt_title_clr, R.id.title) } } \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ProductDisplayNonLinearSmallContentView.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ProductDisplayNonLinearSmallContentView.kt index f3674c7b2..365903af7 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ProductDisplayNonLinearSmallContentView.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ProductDisplayNonLinearSmallContentView.kt @@ -4,7 +4,7 @@ import android.content.Context import com.clevertap.android.pushtemplates.R import com.clevertap.android.pushtemplates.TemplateRenderer -class ProductDisplayNonLinearSmallContentView(context: Context, renderer: TemplateRenderer) : +internal class ProductDisplayNonLinearSmallContentView(context: Context, renderer: TemplateRenderer) : SmallContentView(context, renderer, R.layout.content_view_small_single_line_msg) { } \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/RatingContentView.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/RatingContentView.kt index 583b33780..00009e678 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/RatingContentView.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/RatingContentView.kt @@ -14,7 +14,7 @@ import com.clevertap.android.sdk.Constants import com.clevertap.android.sdk.pushnotification.LaunchPendingIntentFactory import java.util.* -class RatingContentView(context: Context, renderer: TemplateRenderer, extras: Bundle) : +internal class RatingContentView(context: Context, renderer: TemplateRenderer, extras: Bundle) : BigImageContentView(context, renderer, R.layout.rating) { init { diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/SmallContentView.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/SmallContentView.kt index 86e3787cb..60abc0987 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/SmallContentView.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/SmallContentView.kt @@ -4,7 +4,7 @@ import android.content.Context import com.clevertap.android.pushtemplates.R import com.clevertap.android.pushtemplates.TemplateRenderer -open class SmallContentView( +internal open class SmallContentView( context: Context, renderer: TemplateRenderer, layoutId: Int = R.layout.content_view_small_single_line_msg ) : ContentView(context, layoutId, renderer) { @@ -13,9 +13,9 @@ open class SmallContentView( setCustomContentViewBasicKeys() setCustomContentViewTitle(renderer.pt_title) setCustomContentViewMessage(renderer.pt_msg) - setCustomContentViewCollapsedBackgroundColour(renderer.pt_bg) - setCustomContentViewTitleColour(renderer.pt_title_clr) - setCustomContentViewMessageColour(renderer.pt_msg_clr) + setCustomBackgroundColour(renderer.pt_bg, R.id.content_view_small) + setCustomTextColour(renderer.pt_title_clr, R.id.title) + setCustomTextColour(renderer.pt_msg_clr, R.id.msg) setCustomContentViewSmallIcon() setCustomContentViewLargeIcon(renderer.pt_large_icon) } diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/TimerBigContentView.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/TimerBigContentView.kt index 1ea78deda..c5e59c1c6 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/TimerBigContentView.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/TimerBigContentView.kt @@ -3,18 +3,16 @@ package com.clevertap.android.pushtemplates.content import android.content.Context import android.os.Build import android.text.Html -import android.view.View import com.clevertap.android.pushtemplates.R import com.clevertap.android.pushtemplates.TemplateRenderer -import com.clevertap.android.pushtemplates.Utils -class TimerBigContentView(context: Context, timer_end: Int?, renderer: TemplateRenderer) : +internal class TimerBigContentView(context: Context, timer_end: Int?, renderer: TemplateRenderer) : TimerSmallContentView(context, timer_end, renderer, R.layout.timer) { init { - setCustomContentViewExpandedBackgroundColour(renderer.pt_bg) + setCustomBackgroundColour(renderer.pt_bg, R.id.content_view_big) setCustomContentViewMessageSummary(renderer.pt_msg_summary) - setCustomContentViewBigImage(renderer.pt_big_img) + setCustomContentViewBigImage(renderer.pt_big_img, renderer.pt_scale_type) } private fun setCustomContentViewMessageSummary(pt_msg_summary: String?) { @@ -29,15 +27,4 @@ class TimerBigContentView(context: Context, timer_end: Int?, renderer: TemplateR } } } - - private fun setCustomContentViewBigImage(pt_big_img: String?) { - if (pt_big_img != null && pt_big_img.isNotEmpty()) { - Utils.loadImageURLIntoRemoteView(R.id.big_image, pt_big_img, remoteView,context) - if (Utils.getFallback()) { - remoteView.setViewVisibility(R.id.big_image, View.GONE) - } - } else { - remoteView.setViewVisibility(R.id.big_image, View.GONE) - } - } } \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/TimerSmallContentView.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/TimerSmallContentView.kt index 3e5de94d3..750cc4a13 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/TimerSmallContentView.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/TimerSmallContentView.kt @@ -3,31 +3,30 @@ package com.clevertap.android.pushtemplates.content import android.content.Context import android.os.Build import android.os.SystemClock -import com.clevertap.android.pushtemplates.PTConstants import com.clevertap.android.pushtemplates.R import com.clevertap.android.pushtemplates.TemplateRenderer -import com.clevertap.android.pushtemplates.Utils +import com.clevertap.android.pushtemplates.isNotNullAndEmpty -open class TimerSmallContentView( +internal open class TimerSmallContentView( context: Context, timer_end: Int?, renderer: TemplateRenderer, layoutId: Int = R.layout.timer_collapsed ) : - ContentView(context, layoutId, renderer) { + ActionButtonsContentView(context, layoutId, renderer) { init { setCustomContentViewBasicKeys() setCustomContentViewTitle(renderer.pt_title) setCustomContentViewMessage(renderer.pt_msg) - setCustomContentViewCollapsedBackgroundColour(renderer.pt_bg) - setCustomContentViewChronometerBackgroundColour(renderer.pt_bg) - setCustomContentViewTitleColour(renderer.pt_title_clr) + setCustomBackgroundColour(renderer.pt_bg, R.id.content_view_small) + setCustomBackgroundColour(renderer.pt_bg, R.id.chronometer) + setCustomTextColour(renderer.pt_title_clr, R.id.title) setCustomContentViewChronometerTitleColour( renderer.pt_chrono_title_clr, renderer.pt_title_clr ) - setCustomContentViewMessageColour(renderer.pt_msg_clr) + setCustomTextColour(renderer.pt_msg_clr, R.id.msg) remoteView.setChronometer( R.id.chronometer, SystemClock.elapsedRealtime() + timer_end!!, @@ -40,32 +39,14 @@ open class TimerSmallContentView( setCustomContentViewSmallIcon() } - internal fun setCustomContentViewChronometerBackgroundColour(pt_bg: String?) { - if (pt_bg != null && pt_bg.isNotEmpty()) { - remoteView.setInt( - R.id.chronometer, - "setBackgroundColor", - Utils.getColour(pt_bg, PTConstants.PT_COLOUR_WHITE) - ) - } - } - - internal fun setCustomContentViewChronometerTitleColour( + private fun setCustomContentViewChronometerTitleColour( pt_chrono_title_clr: String?, pt_title_clr: String? ) { - if (pt_chrono_title_clr != null && pt_chrono_title_clr.isNotEmpty()) { - remoteView.setTextColor( - R.id.chronometer, - Utils.getColour(pt_chrono_title_clr, PTConstants.PT_COLOUR_BLACK) - ) - } else { - if (pt_title_clr != null && pt_title_clr.isNotEmpty()) { - remoteView.setTextColor( - R.id.chronometer, - Utils.getColour(pt_title_clr, PTConstants.PT_COLOUR_BLACK) - ) - } + if (pt_chrono_title_clr.isNotNullAndEmpty()) { + setCustomTextColour(pt_chrono_title_clr, R.id.chronometer) + } else if (pt_title_clr.isNotNullAndEmpty()) { + setCustomTextColour(pt_title_clr, R.id.chronometer) } } } \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ZeroBezelBigContentView.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ZeroBezelBigContentView.kt index e8242ca44..0b608af29 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ZeroBezelBigContentView.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ZeroBezelBigContentView.kt @@ -3,28 +3,27 @@ package com.clevertap.android.pushtemplates.content import android.content.Context import android.os.Build import android.text.Html -import android.view.View import com.clevertap.android.pushtemplates.R import com.clevertap.android.pushtemplates.TemplateRenderer -import com.clevertap.android.pushtemplates.Utils +import com.clevertap.android.pushtemplates.isNotNullAndEmpty -class ZeroBezelBigContentView(context: Context, renderer: TemplateRenderer) : - ContentView(context, R.layout.zero_bezel, renderer) { +internal class ZeroBezelBigContentView(context: Context, renderer: TemplateRenderer) : + ActionButtonsContentView(context, R.layout.zero_bezel, renderer) { init { setCustomContentViewBasicKeys() setCustomContentViewTitle(renderer.pt_title) setCustomContentViewMessage(renderer.pt_msg) setCustomContentViewMessageSummary(renderer.pt_msg_summary) - setCustomContentViewTitleColour(renderer.pt_title_clr) - setCustomContentViewExpandedBackgroundColour(renderer.pt_bg) - setCustomContentViewMessageColour(renderer.pt_msg_clr) - setCustomContentViewBigImage(renderer.pt_big_img) + setCustomTextColour(renderer.pt_title_clr, R.id.title) + setCustomBackgroundColour(renderer.pt_bg, R.id.content_view_big) + setCustomTextColour(renderer.pt_msg_clr, R.id.msg) + setCustomContentViewBigImage(renderer.pt_big_img, renderer.pt_scale_type) setCustomContentViewSmallIcon() } private fun setCustomContentViewMessageSummary(pt_msg_summary: String?) { - if (pt_msg_summary != null && pt_msg_summary.isNotEmpty()) { + if (pt_msg_summary.isNotNullAndEmpty()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { remoteView.setTextViewText( R.id.msg, @@ -35,15 +34,4 @@ class ZeroBezelBigContentView(context: Context, renderer: TemplateRenderer) : } } } - - private fun setCustomContentViewBigImage(pt_big_img: String?) { - if (pt_big_img != null && pt_big_img.isNotEmpty()) { - Utils.loadImageURLIntoRemoteView(R.id.big_image, pt_big_img, remoteView,context) - if (Utils.getFallback()) { - remoteView.setViewVisibility(R.id.big_image, View.GONE) - } - } else { - remoteView.setViewVisibility(R.id.big_image, View.GONE) - } - } } \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ZeroBezelMixedSmallContentView.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ZeroBezelMixedSmallContentView.kt index 8c7f8e8c8..acd4b2e75 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ZeroBezelMixedSmallContentView.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ZeroBezelMixedSmallContentView.kt @@ -1,27 +1,14 @@ package com.clevertap.android.pushtemplates.content import android.content.Context -import android.view.View import com.clevertap.android.pushtemplates.R import com.clevertap.android.pushtemplates.TemplateRenderer -import com.clevertap.android.pushtemplates.Utils -class ZeroBezelMixedSmallContentView(context: Context, renderer: TemplateRenderer) : +internal class ZeroBezelMixedSmallContentView(context: Context, renderer: TemplateRenderer) : ZeroBezelSmallContentView(context, R.layout.cv_small_zero_bezel, renderer) { init { setCustomContentViewMessage(renderer.pt_msg) - setCustomContentViewBigImage(renderer.pt_big_img) - } - - private fun setCustomContentViewBigImage(pt_big_img: String?) { - if (pt_big_img != null && pt_big_img.isNotEmpty()) { - Utils.loadImageURLIntoRemoteView(R.id.big_image, pt_big_img, remoteView,context) - if (Utils.getFallback()) { - remoteView.setViewVisibility(R.id.big_image, View.GONE) - } - } else { - remoteView.setViewVisibility(R.id.big_image, View.GONE) - } + setCustomContentViewBigImage(renderer.pt_big_img, renderer.pt_scale_type) } } \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ZeroBezelSmallContentView.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ZeroBezelSmallContentView.kt index dc4669960..bbb66590e 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ZeroBezelSmallContentView.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ZeroBezelSmallContentView.kt @@ -1,17 +1,18 @@ package com.clevertap.android.pushtemplates.content import android.content.Context +import com.clevertap.android.pushtemplates.R import com.clevertap.android.pushtemplates.TemplateRenderer -open class ZeroBezelSmallContentView(context: Context, layoutId: Int, renderer: TemplateRenderer) : +internal open class ZeroBezelSmallContentView(context: Context, layoutId: Int, renderer: TemplateRenderer) : ContentView(context, layoutId, renderer) { init { setCustomContentViewBasicKeys() setCustomContentViewTitle(renderer.pt_title) - setCustomContentViewTitleColour(renderer.pt_title_clr) - setCustomContentViewCollapsedBackgroundColour(renderer.pt_bg) - setCustomContentViewMessageColour(renderer.pt_msg_clr) + setCustomTextColour(renderer.pt_title_clr, R.id.title) + setCustomBackgroundColour(renderer.pt_bg, R.id.content_view_small) + setCustomTextColour(renderer.pt_msg_clr, R.id.msg) setCustomContentViewSmallIcon() } } \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ZeroBezelTextOnlySmallContentView.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ZeroBezelTextOnlySmallContentView.kt index 92272e8fd..e0865ccae 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ZeroBezelTextOnlySmallContentView.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/content/ZeroBezelTextOnlySmallContentView.kt @@ -5,7 +5,7 @@ import android.view.View import com.clevertap.android.pushtemplates.R import com.clevertap.android.pushtemplates.TemplateRenderer -class ZeroBezelTextOnlySmallContentView(context: Context, renderer: TemplateRenderer) : +internal class ZeroBezelTextOnlySmallContentView(context: Context, renderer: TemplateRenderer) : ZeroBezelSmallContentView(context, R.layout.cv_small_text_only, renderer) { init { diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/ActionButtonsHandler.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/ActionButtonsHandler.kt new file mode 100644 index 000000000..470260e7d --- /dev/null +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/ActionButtonsHandler.kt @@ -0,0 +1,40 @@ +package com.clevertap.android.pushtemplates.styles + +import android.content.Context +import android.os.Build +import android.os.Bundle +import androidx.core.app.NotificationCompat +import com.clevertap.android.pushtemplates.TemplateRenderer + +/** + * Handles action buttons for notification styles. + * This class implements the functionality that was previously in StyleWithActionButtons, + * but using composition instead of inheritance. + */ +internal class ActionButtonsHandler(private val renderer: TemplateRenderer) { + + /** + * Adds action buttons to the notification builder + * + * @param context The context + * @param extras The notification extras + * @param notificationId The notification ID + * @param builder The notification builder + * @param isInputBoxStyle Whether this is an InputBoxStyle (needs special handling) + * @return The updated notification builder + */ + fun addActionButtons( + context: Context, + extras: Bundle, + notificationId: Int, + builder: NotificationCompat.Builder, + isInputBoxStyle: Boolean = false + ): NotificationCompat.Builder { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S || isInputBoxStyle) { + // Make sure the notification in collapsed state doesn't take up action buttons + // InputBox Template use Android CTAs for all API levels + renderer.setActionButtons(context, extras, notificationId, builder, renderer.actions) + } + return builder + } +} diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/AutoCarouselStyle.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/AutoCarouselStyle.kt index a28661acc..a173692f9 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/AutoCarouselStyle.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/AutoCarouselStyle.kt @@ -4,13 +4,16 @@ import android.app.PendingIntent import android.content.Context import android.os.Bundle import android.widget.RemoteViews +import androidx.core.app.NotificationCompat import com.clevertap.android.pushtemplates.TemplateRenderer import com.clevertap.android.pushtemplates.content.AUTO_CAROUSEL_CONTENT_PENDING_INTENT import com.clevertap.android.pushtemplates.content.AutoCarouselContentView import com.clevertap.android.pushtemplates.content.PendingIntentFactory import com.clevertap.android.pushtemplates.content.SmallContentView -class AutoCarouselStyle(private var renderer: TemplateRenderer) : Style(renderer) { +internal class AutoCarouselStyle(private var renderer: TemplateRenderer) : Style(renderer) { + + private val actionButtonsHandler = ActionButtonsHandler(renderer) override fun makeSmallContentRemoteView(context: Context, renderer: TemplateRenderer): RemoteViews { return SmallContentView(context, renderer).remoteView @@ -38,4 +41,14 @@ class AutoCarouselStyle(private var renderer: TemplateRenderer) : Style(renderer ): PendingIntent? { return null } + + override fun builderFromStyle( + context: Context, + extras: Bundle, + notificationId: Int, + nb: NotificationCompat.Builder + ): NotificationCompat.Builder { + val builder = super.builderFromStyle(context, extras, notificationId, nb) + return actionButtonsHandler.addActionButtons(context, extras, notificationId, builder) + } } \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/BasicStyle.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/BasicStyle.kt index 1f167c388..b42f19894 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/BasicStyle.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/BasicStyle.kt @@ -4,13 +4,16 @@ import android.app.PendingIntent import android.content.Context import android.os.Bundle import android.widget.RemoteViews +import androidx.core.app.NotificationCompat import com.clevertap.android.pushtemplates.TemplateRenderer import com.clevertap.android.pushtemplates.content.BASIC_CONTENT_PENDING_INTENT import com.clevertap.android.pushtemplates.content.BigImageContentView import com.clevertap.android.pushtemplates.content.PendingIntentFactory import com.clevertap.android.pushtemplates.content.SmallContentView -class BasicStyle(private var renderer: TemplateRenderer) : Style(renderer) { +internal class BasicStyle(private var renderer: TemplateRenderer) : Style(renderer) { + + private val actionButtonsHandler = ActionButtonsHandler(renderer) override fun makeSmallContentRemoteView(context: Context, renderer: TemplateRenderer): RemoteViews { return SmallContentView(context, renderer).remoteView @@ -30,4 +33,14 @@ class BasicStyle(private var renderer: TemplateRenderer) : Style(renderer) { override fun makeDismissIntent(context: Context, extras: Bundle, notificationId: Int): PendingIntent? { return null } + + override fun builderFromStyle( + context: Context, + extras: Bundle, + notificationId: Int, + nb: NotificationCompat.Builder + ): NotificationCompat.Builder { + val builder = super.builderFromStyle(context, extras, notificationId, nb) + return actionButtonsHandler.addActionButtons(context, extras, notificationId, builder) + } } \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/FiveIconStyle.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/FiveIconStyle.kt index 070fb83cb..b67273bf2 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/FiveIconStyle.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/FiveIconStyle.kt @@ -8,7 +8,7 @@ import com.clevertap.android.pushtemplates.TemplateRenderer import com.clevertap.android.pushtemplates.content.* import com.clevertap.android.pushtemplates.content.PendingIntentFactory -class FiveIconStyle(private var renderer: TemplateRenderer, private var extras: Bundle) : Style(renderer) { +internal class FiveIconStyle(private var renderer: TemplateRenderer, private var extras: Bundle) : Style(renderer) { lateinit var fiveIconSmallContentView: ContentView lateinit var fiveIconBigContentView: ContentView diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/InputBoxStyle.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/InputBoxStyle.kt index c37354a57..89184f535 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/InputBoxStyle.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/InputBoxStyle.kt @@ -14,7 +14,9 @@ import com.clevertap.android.pushtemplates.content.INPUT_BOX_CONTENT_PENDING_INT import com.clevertap.android.pushtemplates.content.INPUT_BOX_REPLY_PENDING_INTENT import com.clevertap.android.pushtemplates.content.PendingIntentFactory -class InputBoxStyle(private var renderer: TemplateRenderer) : Style(renderer) { +internal class InputBoxStyle(private var renderer: TemplateRenderer) : Style(renderer) { + + private val actionButtonsHandler = ActionButtonsHandler(renderer) override fun setNotificationBuilderBasics( notificationBuilder: NotificationCompat.Builder, @@ -45,6 +47,12 @@ class InputBoxStyle(private var renderer: TemplateRenderer) : Style(renderer) { nb: NotificationCompat.Builder ): NotificationCompat.Builder { var inputBoxNotificationBuilder = super.builderFromStyle(context, extras, notificationId, nb) + + // Apply action buttons with special flag for InputBoxStyle + inputBoxNotificationBuilder = actionButtonsHandler.addActionButtons( + context, extras, notificationId, inputBoxNotificationBuilder, true + ) + inputBoxNotificationBuilder = setStandardViewBigImageStyle( renderer.pt_big_img, extras, context, inputBoxNotificationBuilder @@ -74,7 +82,6 @@ class InputBoxStyle(private var renderer: TemplateRenderer) : Style(renderer) { if (renderer.pt_dismiss_on_click != null && renderer.pt_dismiss_on_click!!.isNotEmpty()) { extras.putString(PTConstants.PT_DISMISS_ON_CLICK, renderer.pt_dismiss_on_click) } - renderer.setActionButtons(context, extras, notificationId, inputBoxNotificationBuilder, renderer.actions) return inputBoxNotificationBuilder } diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/ManualCarouselStyle.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/ManualCarouselStyle.kt index 2ddba0c94..6f78a0d33 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/ManualCarouselStyle.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/ManualCarouselStyle.kt @@ -4,6 +4,7 @@ import android.app.PendingIntent import android.content.Context import android.os.Bundle import android.widget.RemoteViews +import androidx.core.app.NotificationCompat import com.clevertap.android.pushtemplates.TemplateRenderer import com.clevertap.android.pushtemplates.content.MANUAL_CAROUSEL_CONTENT_PENDING_INTENT import com.clevertap.android.pushtemplates.content.MANUAL_CAROUSEL_DISMISS_PENDING_INTENT @@ -12,7 +13,9 @@ import com.clevertap.android.pushtemplates.content.PendingIntentFactory import com.clevertap.android.pushtemplates.content.SmallContentView import com.clevertap.android.sdk.Constants -class ManualCarouselStyle(private var renderer: TemplateRenderer, private var extras: Bundle) : Style(renderer) { +internal class ManualCarouselStyle(private var renderer: TemplateRenderer, private var extras: Bundle) : Style(renderer) { + + private val actionButtonsHandler = ActionButtonsHandler(renderer) override fun makeSmallContentRemoteView(context: Context, renderer: TemplateRenderer): RemoteViews { return SmallContentView(context, renderer).remoteView @@ -51,4 +54,14 @@ class ManualCarouselStyle(private var renderer: TemplateRenderer, private var ex MANUAL_CAROUSEL_DISMISS_PENDING_INTENT, renderer ) } + + override fun builderFromStyle( + context: Context, + extras: Bundle, + notificationId: Int, + nb: NotificationCompat.Builder + ): NotificationCompat.Builder { + val builder = super.builderFromStyle(context, extras, notificationId, nb) + return actionButtonsHandler.addActionButtons(context, extras, notificationId, builder) + } } \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/ProductDisplayStyle.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/ProductDisplayStyle.kt index 601e3c4e9..f18d8fbce 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/ProductDisplayStyle.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/ProductDisplayStyle.kt @@ -12,7 +12,7 @@ import com.clevertap.android.pushtemplates.content.ProductDisplayLinearBigConten import com.clevertap.android.pushtemplates.content.ProductDisplayNonLinearBigContentView import com.clevertap.android.pushtemplates.content.ProductDisplayNonLinearSmallContentView -class ProductDisplayStyle(private var renderer: TemplateRenderer, private var extras: Bundle) : Style(renderer) { +internal class ProductDisplayStyle(private var renderer: TemplateRenderer, private var extras: Bundle) : Style(renderer) { override fun makeSmallContentRemoteView(context: Context, renderer: TemplateRenderer): RemoteViews { return ProductDisplayNonLinearSmallContentView(context, renderer).remoteView diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/RatingStyle.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/RatingStyle.kt index 744c03287..2e05f8c3d 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/RatingStyle.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/RatingStyle.kt @@ -10,7 +10,7 @@ import com.clevertap.android.pushtemplates.content.RATING_CONTENT_PENDING_INTENT import com.clevertap.android.pushtemplates.content.RatingContentView import com.clevertap.android.pushtemplates.content.SmallContentView -class RatingStyle(private var renderer: TemplateRenderer, private var extras: Bundle) : Style(renderer) { +internal class RatingStyle(private var renderer: TemplateRenderer, private var extras: Bundle) : Style(renderer) { override fun makeSmallContentRemoteView(context: Context, renderer: TemplateRenderer): RemoteViews { return SmallContentView(context, renderer).remoteView diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/Style.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/Style.kt index 5e2225dd4..ea7aacc9b 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/Style.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/Style.kt @@ -10,7 +10,7 @@ import android.widget.RemoteViews import androidx.core.app.NotificationCompat import com.clevertap.android.pushtemplates.TemplateRenderer -abstract class Style(private var renderer: TemplateRenderer) { +internal abstract class Style(private var renderer: TemplateRenderer) { protected open fun setNotificationBuilderBasics( notificationBuilder: NotificationCompat.Builder, @@ -41,7 +41,13 @@ abstract class Style(private var renderer: TemplateRenderer) { .setWhen(System.currentTimeMillis()) .setColor(Color.parseColor(renderer.pt_small_icon_clr ?: "#FFFFFF")) .setAutoCancel(true) - .setOnlyAlertOnce(true) + .setStyle( + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + NotificationCompat.DecoratedCustomViewStyle() + } else { + null + } + ) } protected abstract fun makeSmallContentRemoteView(context: Context, renderer: TemplateRenderer): RemoteViews? diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/TimerStyle.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/TimerStyle.kt index 10f77f4f5..96c0f9546 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/TimerStyle.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/TimerStyle.kt @@ -4,6 +4,7 @@ import android.app.PendingIntent import android.content.Context import android.os.Bundle import android.widget.RemoteViews +import androidx.core.app.NotificationCompat import com.clevertap.android.pushtemplates.PTConstants import com.clevertap.android.pushtemplates.PTLog import com.clevertap.android.pushtemplates.TemplateRenderer @@ -12,7 +13,9 @@ import com.clevertap.android.pushtemplates.content.TIMER_CONTENT_PENDING_INTENT import com.clevertap.android.pushtemplates.content.TimerBigContentView import com.clevertap.android.pushtemplates.content.TimerSmallContentView -class TimerStyle(private var renderer: TemplateRenderer, private var extras: Bundle) : Style(renderer) { +internal class TimerStyle(private var renderer: TemplateRenderer, private var extras: Bundle) : Style(renderer) { + + private val actionButtonsHandler = ActionButtonsHandler(renderer) override fun makeSmallContentRemoteView(context: Context, renderer: TemplateRenderer): RemoteViews? { return if (getTimerEnd() == null) @@ -48,6 +51,16 @@ class TimerStyle(private var renderer: TemplateRenderer, private var extras: Bun ): PendingIntent? { return null } + + override fun builderFromStyle( + context: Context, + extras: Bundle, + notificationId: Int, + nb: NotificationCompat.Builder + ): NotificationCompat.Builder { + val builder = super.builderFromStyle(context, extras, notificationId, nb) + return actionButtonsHandler.addActionButtons(context, extras, notificationId, builder) + } @Suppress("LocalVariableName") private fun getTimerEnd(): Int? { diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/ZeroBezelStyle.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/ZeroBezelStyle.kt index 34e153bca..9550c0e07 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/ZeroBezelStyle.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/styles/ZeroBezelStyle.kt @@ -4,6 +4,7 @@ import android.app.PendingIntent import android.content.Context import android.os.Bundle import android.widget.RemoteViews +import androidx.core.app.NotificationCompat import com.clevertap.android.pushtemplates.PTConstants import com.clevertap.android.pushtemplates.TemplateRenderer import com.clevertap.android.pushtemplates.content.PendingIntentFactory @@ -12,9 +13,14 @@ import com.clevertap.android.pushtemplates.content.ZeroBezelBigContentView import com.clevertap.android.pushtemplates.content.ZeroBezelMixedSmallContentView import com.clevertap.android.pushtemplates.content.ZeroBezelTextOnlySmallContentView -class ZeroBezelStyle(private var renderer: TemplateRenderer) : Style(renderer) { +internal class ZeroBezelStyle(private var renderer: TemplateRenderer) : Style(renderer) { - override fun makeSmallContentRemoteView(context: Context, renderer: TemplateRenderer): RemoteViews { + private val actionButtonsHandler = ActionButtonsHandler(renderer) + + override fun makeSmallContentRemoteView( + context: Context, + renderer: TemplateRenderer + ): RemoteViews { val textOnlySmallView = renderer.pt_small_view != null && renderer.pt_small_view == PTConstants.TEXT_ONLY return if (textOnlySmallView) { @@ -24,7 +30,10 @@ class ZeroBezelStyle(private var renderer: TemplateRenderer) : Style(renderer) { } } - override fun makeBigContentRemoteView(context: Context, renderer: TemplateRenderer): RemoteViews { + override fun makeBigContentRemoteView( + context: Context, + renderer: TemplateRenderer + ): RemoteViews { return ZeroBezelBigContentView(context, renderer).remoteView } @@ -46,4 +55,14 @@ class ZeroBezelStyle(private var renderer: TemplateRenderer) : Style(renderer) { ): PendingIntent? { return null } + + override fun builderFromStyle( + context: Context, + extras: Bundle, + notificationId: Int, + nb: NotificationCompat.Builder + ): NotificationCompat.Builder { + val builder = super.builderFromStyle(context, extras, notificationId, nb) + return actionButtonsHandler.addActionButtons(context, extras, notificationId, builder) + } } \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/validators/BackgroundValidator.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/validators/BackgroundValidator.kt deleted file mode 100644 index 4f375425d..000000000 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/validators/BackgroundValidator.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.clevertap.android.pushtemplates.validators - -import com.clevertap.android.pushtemplates.checkers.Checker - -class BackgroundValidator(keys: Map>) : Validator(keys) { - - override fun loadKeys(): List> { - return listOf(keys[PT_BG]!!) - } -} \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/validators/BasicTemplateValidator.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/validators/BasicTemplateValidator.kt deleted file mode 100644 index b735d9953..000000000 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/validators/BasicTemplateValidator.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.clevertap.android.pushtemplates.validators - -import com.clevertap.android.pushtemplates.checkers.Checker - -class BasicTemplateValidator(private var validator: Validator) : TemplateValidator( - validator.keys -) { - - override fun validate(): Boolean { - return validator.validate() and super.validateKeys()// All check must be true - } - - override fun loadKeys(): List> { - return listOf(keys[PT_BG]!!) - } -} \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/validators/FiveIconsTemplateValidator.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/validators/FiveIconsTemplateValidator.kt index 2ae600e55..2354081fc 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/validators/FiveIconsTemplateValidator.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/validators/FiveIconsTemplateValidator.kt @@ -2,11 +2,7 @@ package com.clevertap.android.pushtemplates.validators import com.clevertap.android.pushtemplates.checkers.Checker -class FiveIconsTemplateValidator(private var validator: Validator) : TemplateValidator(validator.keys) { - - override fun validate(): Boolean { - return validator.validate() && super.validateKeys()// All check must be true - } +class FiveIconsTemplateValidator(keys: Map>) : Validator(keys) { override fun loadKeys(): List> { return listOf(keys[PT_FIVE_DEEPLINK_LIST]!!, keys[PT_FIVE_IMAGE_LIST]!!) diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/validators/ProductDisplayTemplateValidator.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/validators/ProductDisplayTemplateValidator.kt index 8fbe4184b..1d0cbebaa 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/validators/ProductDisplayTemplateValidator.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/validators/ProductDisplayTemplateValidator.kt @@ -13,7 +13,7 @@ class ProductDisplayTemplateValidator(private var validator: Validator) : return listOf( keys[PT_THREE_DEEPLINK_LIST]!!, keys[PT_BIG_TEXT_LIST]!!, keys[PT_SMALL_TEXT_LIST]!!, keys[PT_PRODUCT_DISPLAY_ACTION]!!, - keys[PT_PRODUCT_DISPLAY_ACTION_CLR]!!, keys[PT_PRODUCT_THREE_IMAGE_LIST]!! + keys[PT_PRODUCT_THREE_IMAGE_LIST]!! ) } } \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/validators/ValidatorFactory.kt b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/validators/ValidatorFactory.kt index 344c2ce03..4d7932c9c 100644 --- a/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/validators/ValidatorFactory.kt +++ b/clevertap-pushtemplates/src/main/java/com/clevertap/android/pushtemplates/validators/ValidatorFactory.kt @@ -7,7 +7,6 @@ import com.clevertap.android.pushtemplates.checkers.* const val PT_TITLE = "PT_TITLE" const val PT_MSG = "PT_MSG" -const val PT_BG = "PT_BG" const val PT_DEEPLINK_LIST = "PT_DEEPLINK_LIST" const val PT_THREE_IMAGE_LIST = "PT_IMAGE_LIST" const val PT_PRODUCT_THREE_IMAGE_LIST = "PT_PRODUCT_THREE_IMAGE_LIST" @@ -56,23 +55,19 @@ internal class ValidatorFactory { keys = createKeysMap(templateRenderer) return when (templateType) { - BASIC -> BasicTemplateValidator(ContentValidator(keys)) + BASIC -> ContentValidator(keys) AUTO_CAROUSEL, MANUAL_CAROUSEL -> CarouselTemplateValidator( - BasicTemplateValidator( - ContentValidator( - keys - ) + ContentValidator( + keys ) ) - RATING -> RatingTemplateValidator(BasicTemplateValidator(ContentValidator(keys))) - FIVE_ICONS -> FiveIconsTemplateValidator(BackgroundValidator(keys)) + RATING -> RatingTemplateValidator(ContentValidator(keys)) + FIVE_ICONS -> FiveIconsTemplateValidator(keys) PRODUCT_DISPLAY -> ProductDisplayTemplateValidator( - BasicTemplateValidator( - ContentValidator(keys) - ) + ContentValidator(keys) ) ZERO_BEZEL -> ZeroBezelTemplateValidator(ContentValidator(keys)) - TIMER -> TimerTemplateValidator(BasicTemplateValidator(ContentValidator(keys))) + TIMER -> TimerTemplateValidator(ContentValidator(keys)) INPUT_BOX -> InputBoxTemplateValidator(ContentValidator(keys)) else -> null } @@ -85,11 +80,7 @@ internal class ValidatorFactory { StringSizeChecker(templateRenderer.pt_title, 0, "Title is missing or empty") hashMap[PT_MSG] = StringSizeChecker(templateRenderer.pt_msg, 0, "Message is missing or empty") - hashMap[PT_BG] = StringSizeChecker( - templateRenderer.pt_bg, - 0, - "Background colour is missing or empty" - ) + //----------CAROUSEL------------- hashMap[PT_DEEPLINK_LIST] = ListSizeChecker(templateRenderer.deepLinkList, 1, "Deeplink is missing or empty") diff --git a/clevertap-pushtemplates/src/main/res/drawable/pt_arrow_back.png b/clevertap-pushtemplates/src/main/res/drawable/pt_arrow_back.png index ea2265330..ef5fcd8a6 100644 Binary files a/clevertap-pushtemplates/src/main/res/drawable/pt_arrow_back.png and b/clevertap-pushtemplates/src/main/res/drawable/pt_arrow_back.png differ diff --git a/clevertap-pushtemplates/src/main/res/drawable/pt_arrow_forward.png b/clevertap-pushtemplates/src/main/res/drawable/pt_arrow_forward.png index eee582b92..9c7c0a24f 100644 Binary files a/clevertap-pushtemplates/src/main/res/drawable/pt_arrow_forward.png and b/clevertap-pushtemplates/src/main/res/drawable/pt_arrow_forward.png differ diff --git a/clevertap-pushtemplates/src/main/res/drawable/pt_button_background.xml b/clevertap-pushtemplates/src/main/res/drawable/pt_button_background.xml new file mode 100644 index 000000000..76254abb0 --- /dev/null +++ b/clevertap-pushtemplates/src/main/res/drawable/pt_button_background.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/res/drawable/pt_rounded_shape.xml b/clevertap-pushtemplates/src/main/res/drawable/pt_rounded_shape.xml index 0a959edad..2e8c56546 100644 --- a/clevertap-pushtemplates/src/main/res/drawable/pt_rounded_shape.xml +++ b/clevertap-pushtemplates/src/main/res/drawable/pt_rounded_shape.xml @@ -4,13 +4,6 @@ - - - @@ -15,7 +16,6 @@ + android:layout_height="wrap_content" + android:background="@android:color/transparent"> - + + android:layout_alignBottom="@+id/big_image_configurable" + android:background="@drawable/pt_scrim" + android:contentDescription="@string/notification_scrim" /> - + android:textAppearance="@style/PushTitle" /> + android:textAppearance="@style/PushMessage" /> \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/res/layout-v31/product_display_linear_expanded.xml b/clevertap-pushtemplates/src/main/res/layout-v31/product_display_linear_expanded.xml index c92287fbe..62b592b44 100644 --- a/clevertap-pushtemplates/src/main/res/layout-v31/product_display_linear_expanded.xml +++ b/clevertap-pushtemplates/src/main/res/layout-v31/product_display_linear_expanded.xml @@ -3,7 +3,7 @@ android:id="@+id/content_view_big" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@android:color/white" + android:background="@android:color/transparent" android:orientation="vertical" android:clipToPadding="false"> @@ -40,9 +40,9 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" - android:background="@android:color/holo_red_dark" - android:textAppearance="@style/PushMessage" - android:textColor="@android:color/white" /> + android:padding="@dimen/custom_btn_padding" + android:background="@color/customButtonBackground" + android:textAppearance="@style/PushMessage" /> diff --git a/clevertap-pushtemplates/src/main/res/layout-v31/timer.xml b/clevertap-pushtemplates/src/main/res/layout-v31/timer.xml index af32dc0ce..01f9a3049 100644 --- a/clevertap-pushtemplates/src/main/res/layout-v31/timer.xml +++ b/clevertap-pushtemplates/src/main/res/layout-v31/timer.xml @@ -1,10 +1,10 @@ - - + - \ No newline at end of file + \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/res/layout-v31/timer_collapsed.xml b/clevertap-pushtemplates/src/main/res/layout-v31/timer_collapsed.xml index 2e14be434..8a3e312c4 100644 --- a/clevertap-pushtemplates/src/main/res/layout-v31/timer_collapsed.xml +++ b/clevertap-pushtemplates/src/main/res/layout-v31/timer_collapsed.xml @@ -3,7 +3,7 @@ android:id="@+id/content_view_small" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@android:color/white" + android:background="@android:color/transparent" android:orientation="vertical"> - - - + + android:textAppearance="@style/PushTitle" /> + android:textAppearance="@style/PushMessageMultiLine"/> diff --git a/clevertap-pushtemplates/src/main/res/layout/action_buttons.xml b/clevertap-pushtemplates/src/main/res/layout/action_buttons.xml new file mode 100644 index 000000000..fe9fa7403 --- /dev/null +++ b/clevertap-pushtemplates/src/main/res/layout/action_buttons.xml @@ -0,0 +1,45 @@ + + + + + + + + \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/res/layout/auto_carousel.xml b/clevertap-pushtemplates/src/main/res/layout/auto_carousel.xml index 3d2a26fda..f2e7c6e5b 100644 --- a/clevertap-pushtemplates/src/main/res/layout/auto_carousel.xml +++ b/clevertap-pushtemplates/src/main/res/layout/auto_carousel.xml @@ -1,8 +1,9 @@ - - \ No newline at end of file + + + \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/res/layout/content_view_small_multi_line_msg.xml b/clevertap-pushtemplates/src/main/res/layout/content_view_small_multi_line_msg.xml index 2285f517d..00f07f93c 100644 --- a/clevertap-pushtemplates/src/main/res/layout/content_view_small_multi_line_msg.xml +++ b/clevertap-pushtemplates/src/main/res/layout/content_view_small_multi_line_msg.xml @@ -2,6 +2,7 @@ + android:layout_height="wrap_content" + android:background="@android:color/transparent"> - + + android:layout_alignBottom="@+id/big_image_configurable" + android:background="@drawable/pt_scrim" + android:contentDescription="@string/notification_scrim"/> + android:textAppearance="@style/PushTitle"/> + android:textAppearance="@style/PushMessage"/> \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/res/layout/five_cta_collapsed.xml b/clevertap-pushtemplates/src/main/res/layout/five_cta_collapsed.xml index ac0178024..bff65e741 100644 --- a/clevertap-pushtemplates/src/main/res/layout/five_cta_collapsed.xml +++ b/clevertap-pushtemplates/src/main/res/layout/five_cta_collapsed.xml @@ -4,6 +4,7 @@ android:layout_width="match_parent" android:layout_height="64dp" android:padding="@dimen/five_cta_padding" + android:background="@android:color/transparent" android:orientation="horizontal"> diff --git a/clevertap-pushtemplates/src/main/res/layout/image_only_big.xml b/clevertap-pushtemplates/src/main/res/layout/image_only_big.xml index f7cf69ab1..039497ed6 100644 --- a/clevertap-pushtemplates/src/main/res/layout/image_only_big.xml +++ b/clevertap-pushtemplates/src/main/res/layout/image_only_big.xml @@ -3,15 +3,18 @@ android:id="@+id/content_view_big" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="@android:color/transparent" android:orientation="vertical"> - + + + + \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/res/layout/image_view.xml b/clevertap-pushtemplates/src/main/res/layout/image_view.xml deleted file mode 100644 index a0241d7d8..000000000 --- a/clevertap-pushtemplates/src/main/res/layout/image_view.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/res/layout/image_view_dynamic.xml b/clevertap-pushtemplates/src/main/res/layout/image_view_dynamic.xml new file mode 100644 index 000000000..af5481177 --- /dev/null +++ b/clevertap-pushtemplates/src/main/res/layout/image_view_dynamic.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/res/layout/image_view_flipper_dynamic.xml b/clevertap-pushtemplates/src/main/res/layout/image_view_flipper_dynamic.xml new file mode 100644 index 000000000..fd7d2a122 --- /dev/null +++ b/clevertap-pushtemplates/src/main/res/layout/image_view_flipper_dynamic.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/res/layout/input_box_collapsed.xml b/clevertap-pushtemplates/src/main/res/layout/input_box_collapsed.xml deleted file mode 100644 index 499d9b04b..000000000 --- a/clevertap-pushtemplates/src/main/res/layout/input_box_collapsed.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/res/layout/input_box_expanded.xml b/clevertap-pushtemplates/src/main/res/layout/input_box_expanded.xml deleted file mode 100644 index 401665e20..000000000 --- a/clevertap-pushtemplates/src/main/res/layout/input_box_expanded.xml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/res/layout/manual_carousel.xml b/clevertap-pushtemplates/src/main/res/layout/manual_carousel.xml index 3cf3ebcf0..d5424c4c1 100644 --- a/clevertap-pushtemplates/src/main/res/layout/manual_carousel.xml +++ b/clevertap-pushtemplates/src/main/res/layout/manual_carousel.xml @@ -1,17 +1,23 @@ - + android:layout_height="wrap_content" + android:background="@android:color/transparent" + android:orientation="vertical"> + layout="@layout/content_view_small_multi_line_msg" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + android:layout_height="0dp" + android:layout_weight="1" + android:minHeight="196dp"> @@ -34,8 +40,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="3" - android:layout_marginLeft="-4dp" - android:layout_marginRight="-4dp" android:inAnimation="@anim/pt_fade_in" android:outAnimation="@anim/pt_fade_out" /> @@ -45,7 +49,7 @@ android:layout_height="match_parent" android:layout_marginTop="10dp" android:layout_marginBottom="10dp" - android:layout_marginStart="3dp" + android:layout_marginStart="8dp" android:layout_weight="6" android:inAnimation="@anim/pt_fade_in" android:outAnimation="@anim/pt_fade_out" /> @@ -62,5 +66,10 @@ android:background="@drawable/pt_btn_ripple_background" /> + - \ No newline at end of file + \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/res/layout/product_display_linear_collapsed.xml b/clevertap-pushtemplates/src/main/res/layout/product_display_linear_collapsed.xml index 1ef6cdbe0..332de7e1d 100644 --- a/clevertap-pushtemplates/src/main/res/layout/product_display_linear_collapsed.xml +++ b/clevertap-pushtemplates/src/main/res/layout/product_display_linear_collapsed.xml @@ -4,6 +4,7 @@ android:id="@+id/content_view_small" android:layout_width="match_parent" android:layout_height="100dp" + android:background="@android:color/transparent" android:padding="8dp"> diff --git a/clevertap-pushtemplates/src/main/res/layout/product_display_linear_expanded.xml b/clevertap-pushtemplates/src/main/res/layout/product_display_linear_expanded.xml index e92de5596..5a17a0d93 100644 --- a/clevertap-pushtemplates/src/main/res/layout/product_display_linear_expanded.xml +++ b/clevertap-pushtemplates/src/main/res/layout/product_display_linear_expanded.xml @@ -3,7 +3,7 @@ android:id="@+id/content_view_big" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@android:color/white" + android:background="@android:color/transparent" android:orientation="vertical" android:clipToPadding="false" android:padding="8dp"> @@ -48,9 +48,9 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" - android:background="@android:color/holo_red_dark" - android:textAppearance="@style/PushMessage" - android:textColor="@android:color/white" /> + android:padding="@dimen/custom_btn_padding" + android:background="@color/customButtonBackground" + android:textAppearance="@style/PushMessage" /> diff --git a/clevertap-pushtemplates/src/main/res/layout/product_display_template.xml b/clevertap-pushtemplates/src/main/res/layout/product_display_template.xml index 8df545217..9f5386cc6 100644 --- a/clevertap-pushtemplates/src/main/res/layout/product_display_template.xml +++ b/clevertap-pushtemplates/src/main/res/layout/product_display_template.xml @@ -52,6 +52,8 @@ android:layout_marginTop="8dp" android:layout_below="@id/product_price" android:gravity="center" + android:padding="@dimen/custom_btn_padding" + android:background="@color/customButtonBackground" android:layout_centerHorizontal="true" android:textAppearance="@style/PushMessage"/> diff --git a/clevertap-pushtemplates/src/main/res/layout/rating.xml b/clevertap-pushtemplates/src/main/res/layout/rating.xml index 464062407..7420f48c8 100644 --- a/clevertap-pushtemplates/src/main/res/layout/rating.xml +++ b/clevertap-pushtemplates/src/main/res/layout/rating.xml @@ -1,7 +1,7 @@ @@ -15,19 +15,15 @@ android:orientation="vertical" android:layout_below="@id/rel_lyt"> - + + android:src="@drawable/pt_star_outline" + android:contentDescription="@string/rating_star_2" /> + android:src="@drawable/pt_star_outline" + android:contentDescription="@string/rating_star_3" /> + android:src="@drawable/pt_star_outline" + android:contentDescription="@string/rating_star_4" /> + android:src="@drawable/pt_star_outline" + android:contentDescription="@string/rating_star_5" /> diff --git a/clevertap-pushtemplates/src/main/res/layout/timer.xml b/clevertap-pushtemplates/src/main/res/layout/timer.xml index 1cd1171d0..2ff0fee0f 100644 --- a/clevertap-pushtemplates/src/main/res/layout/timer.xml +++ b/clevertap-pushtemplates/src/main/res/layout/timer.xml @@ -1,10 +1,10 @@ - - + + + - \ No newline at end of file + \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/res/layout/timer_collapsed.xml b/clevertap-pushtemplates/src/main/res/layout/timer_collapsed.xml index 9ce21259d..79a3a190b 100644 --- a/clevertap-pushtemplates/src/main/res/layout/timer_collapsed.xml +++ b/clevertap-pushtemplates/src/main/res/layout/timer_collapsed.xml @@ -3,13 +3,15 @@ android:id="@+id/content_view_small" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@android:color/white" + android:background="@android:color/transparent" android:orientation="vertical"> - - + android:layout_height="0dp" + android:layout_weight="1" + android:minHeight="196dp" + android:orientation="vertical"> - + - - - - - - - + + + android:layout_alignBottom="@+id/big_image_configurable" + android:paddingStart="@dimen/padding_horizontal" + android:paddingLeft="@dimen/padding_horizontal" + android:paddingTop="@dimen/padding_vertical" + android:paddingEnd="@dimen/padding_horizontal" + android:paddingRight="@dimen/padding_horizontal" + android:paddingBottom="@dimen/padding_vertical"> - + + + - \ No newline at end of file + + + + + + \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/res/values-night/colors.xml b/clevertap-pushtemplates/src/main/res/values-night/colors.xml new file mode 100644 index 000000000..76dae4f96 --- /dev/null +++ b/clevertap-pushtemplates/src/main/res/values-night/colors.xml @@ -0,0 +1,9 @@ + + + #C9C9C9 + #C9C9C9 + #A0A0A0 + #000000 + #121314 + #404040 + \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/res/values/colors.xml b/clevertap-pushtemplates/src/main/res/values/colors.xml index 369c7e6c9..f45f17308 100644 --- a/clevertap-pushtemplates/src/main/res/values/colors.xml +++ b/clevertap-pushtemplates/src/main/res/values/colors.xml @@ -3,7 +3,13 @@ #808080 #212121 #FFFFFF + #FFFFFF + #E0E0E0 #008577 #00574B #D81B60 + #212121 + #191919 + #212121 + #E0E0E0 \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/res/values/dimens.xml b/clevertap-pushtemplates/src/main/res/values/dimens.xml index 2a68e7673..5e287ab2a 100644 --- a/clevertap-pushtemplates/src/main/res/values/dimens.xml +++ b/clevertap-pushtemplates/src/main/res/values/dimens.xml @@ -42,6 +42,7 @@ 24dp 8dp 96dp + 4dp \ No newline at end of file diff --git a/clevertap-pushtemplates/src/main/res/values/strings.xml b/clevertap-pushtemplates/src/main/res/values/strings.xml index 44e2bb1e5..40511bfe6 100644 --- a/clevertap-pushtemplates/src/main/res/values/strings.xml +++ b/clevertap-pushtemplates/src/main/res/values/strings.xml @@ -1,3 +1,12 @@ PushTemplates + Confirm + Notification Image + Notification Scrim + Rating One Star + Rating Two Star + Rating Three Star + Rating Four Star + Rating Five Star + diff --git a/clevertap-pushtemplates/src/main/res/values/styles.xml b/clevertap-pushtemplates/src/main/res/values/styles.xml index b935f6f96..7a54e8445 100644 --- a/clevertap-pushtemplates/src/main/res/values/styles.xml +++ b/clevertap-pushtemplates/src/main/res/values/styles.xml @@ -1,25 +1,25 @@ diff --git a/clevertap-pushtemplates/src/test/java/com/clevertap/android/pushtemplates/TemplateRendererTest.kt b/clevertap-pushtemplates/src/test/java/com/clevertap/android/pushtemplates/TemplateRendererTest.kt new file mode 100644 index 000000000..07533aff2 --- /dev/null +++ b/clevertap-pushtemplates/src/test/java/com/clevertap/android/pushtemplates/TemplateRendererTest.kt @@ -0,0 +1,834 @@ +package com.clevertap.android.pushtemplates + +import android.app.NotificationManager +import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.ServiceInfo +import android.graphics.Bitmap +import android.net.Uri +import android.os.Build +import android.os.Bundle +import androidx.core.app.NotificationCompat +import com.clevertap.android.pushtemplates.content.FiveIconBigContentView +import com.clevertap.android.pushtemplates.content.FiveIconSmallContentView +import com.clevertap.android.pushtemplates.styles.* +import com.clevertap.android.pushtemplates.validators.BasicTemplateValidator +import com.clevertap.android.pushtemplates.validators.ValidatorFactory +import com.clevertap.android.sdk.CleverTapInstanceConfig +import com.clevertap.android.sdk.ManifestInfo +import io.mockk.* +import io.mockk.impl.annotations.MockK +import org.json.JSONArray +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.Shadows.shadowOf +import org.robolectric.annotation.Config +import org.robolectric.shadows.ShadowPackageManager +import java.lang.reflect.Field + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [Build.VERSION_CODES.P]) +class TemplateRendererTest { + + @MockK + private lateinit var mockNotificationBuilder: NotificationCompat.Builder + + @MockK + private lateinit var mockConfig: CleverTapInstanceConfig + + @MockK + private lateinit var mockBasicTemplateValidator: BasicTemplateValidator + + @MockK + private lateinit var mockNotificationManager: NotificationManager + + @MockK + private lateinit var mockBitmap: Bitmap + + @MockK + private lateinit var mockManifestInfo: ManifestInfo + + private lateinit var testBundle: Bundle + private lateinit var templateRenderer: TemplateRenderer + private lateinit var context: Context + private lateinit var shadowPackageManager: ShadowPackageManager + + @Before + fun setup() { + MockKAnnotations.init(this) + testBundle = Bundle() + + // Use Robolectric's application context for ShadowPackageManager + context = RuntimeEnvironment.getApplication() + shadowPackageManager = shadowOf(context.packageManager) + + val service = ServiceInfo().also { + it.name = "com.clevertap.android.sdk.pushnotification.CTNotificationIntentService" + it.packageName = "com.clevertap.android.pushtemplates.test" + } + val packageInfo = PackageInfo().also { + it.services = arrayOf(service) + it.packageName = "com.clevertap.android.pushtemplates.test" + } + + shadowPackageManager.installPackage(packageInfo) + + // Mock static methods + mockkObject(ValidatorFactory.Companion) + mockkStatic(Utils::class) + mockkStatic(ManifestInfo::class) + + // Default bundle setup + testBundle.putString(PTConstants.PT_ID, "pt_carousel") + testBundle.putString(PTConstants.PT_TITLE, "Test Title") + testBundle.putString(PTConstants.PT_MSG, "Test Message") + + // Setup common mocks + every { ManifestInfo.getInstance(any()) } returns mockManifestInfo + every { mockManifestInfo.intentServiceName } returns "com.clevertap.android.sdk.pushnotification.CTNotificationIntentService" + + // We use a mock for NotificationManager as this is simpler to verify + mockNotificationManager = mockk(relaxed = true) + + // Create TemplateRenderer instance with the mock context + templateRenderer = TemplateRenderer(context, testBundle) + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun test_getTitle_returnsCorrectTitle() { + // Act + val title = templateRenderer.getTitle(testBundle, context) + + // Assert + assertEquals("Test Title", title) + } + + @Test + fun test_getMessage_returnsCorrectMessage() { + // Act + val message = templateRenderer.getMessage(testBundle) + + // Assert + assertEquals("Test Message", message) + } + + @Test + fun test_getCollapseKey_returnsCorrectValue() { + // Arrange + val expectedCollapseKey = "test_collapse_key" + testBundle.putString(PTConstants.PT_COLLAPSE_KEY, expectedCollapseKey) + + // Use reflection to set the private field + val field: Field = TemplateRenderer::class.java.getDeclaredField("pt_collapse_key") + field.isAccessible = true + field.set(templateRenderer, expectedCollapseKey) + + // Act + val collapseKey = templateRenderer.getCollapseKey(testBundle) + + // Assert + assertEquals(expectedCollapseKey, collapseKey) + } + + @Test + fun test_setSmallIcon_setsIconCorrectly() { + // Arrange + val testIcon = 123 + every { Utils.setBitMapColour(any(), testIcon, any()) } returns mockBitmap + + // Act + templateRenderer.setSmallIcon(testIcon, context) + + // Assert + verify { Utils.setBitMapColour(any(), testIcon, any()) } + assertEquals(testIcon, templateRenderer.smallIcon) + } + + @Test + fun test_renderNotification_basic_template_valid() { + val basicBundle = Bundle(testBundle) + basicBundle.putString(PTConstants.PT_ID, "pt_basic") + + val templateRendererLocal = TemplateRenderer(context, basicBundle) + + // Arrange + every { + ValidatorFactory.getValidator( + TemplateType.BASIC, + any() + ) + } returns mockBasicTemplateValidator + every { mockBasicTemplateValidator.validate() } returns true + + mockkConstructor(BasicStyle::class) + every { + anyConstructed().builderFromStyle( + any(), + any(), + any(), + any() + ) + } returns mockNotificationBuilder + + // Act + val result = templateRendererLocal.renderNotification( + basicBundle, + context, + mockNotificationBuilder, + mockConfig, + 123 + ) + + // Assert + verify { + anyConstructed().builderFromStyle( + any(), + basicBundle, + 123, + mockNotificationBuilder + ) + } + assertEquals(mockNotificationBuilder, result) + } + + @Test + fun test_renderNotification_basic_template_invalid() { + // Arrange + every { + ValidatorFactory.getValidator( + TemplateType.BASIC, + any() + ) + } returns mockBasicTemplateValidator + every { mockBasicTemplateValidator.validate() } returns false + + // Act + val result = templateRenderer.renderNotification( + testBundle, + context, + mockNotificationBuilder, + mockConfig, + 123 + ) + + // Assert + assertNull(result) + } + + @Test + fun test_renderNotification_auto_carousel_template_valid() { + // Arrange + val carouselBundle = Bundle(testBundle) + carouselBundle.putString(PTConstants.PT_ID, "pt_carousel") + + val templateRendererLocal = TemplateRenderer(context, testBundle) + + every { + ValidatorFactory.getValidator( + TemplateType.AUTO_CAROUSEL, + any() + ) + } returns mockBasicTemplateValidator + every { mockBasicTemplateValidator.validate() } returns true + + mockkConstructor(AutoCarouselStyle::class) + every { + anyConstructed().builderFromStyle( + any(), + any(), + any(), + any() + ) + } returns mockNotificationBuilder + + // Act + val result = templateRendererLocal.renderNotification( + testBundle, + context, + mockNotificationBuilder, + mockConfig, + 123 + ) + + // Assert + verify { + anyConstructed().builderFromStyle( + any(), + testBundle, + 123, + mockNotificationBuilder + ) + } + assertEquals(mockNotificationBuilder, result) + } + + @Test + fun test_renderNotification_rating_template_valid() { + // Arrange + val ratingBundle = Bundle(testBundle) + ratingBundle.putString(PTConstants.PT_ID, "pt_rating") + + val templateRendererLocal = TemplateRenderer(context, ratingBundle) + + every { + ValidatorFactory.getValidator( + TemplateType.RATING, + any() + ) + } returns mockBasicTemplateValidator + every { mockBasicTemplateValidator.validate() } returns true + + mockkConstructor(RatingStyle::class) + every { + anyConstructed().builderFromStyle( + any(), + any(), + any(), + any() + ) + } returns mockNotificationBuilder + + // Act + val result = templateRendererLocal.renderNotification( + ratingBundle, + context, + mockNotificationBuilder, + mockConfig, + 123 + ) + + // Assert + verify { + anyConstructed().builderFromStyle( + any(), + ratingBundle, + 123, + mockNotificationBuilder + ) + } + assertEquals(mockNotificationBuilder, result) + } + + + @Test + fun test_renderNotification_manual_carousel_template_valid() { + // Arrange + val carouselBundle = Bundle(testBundle) + carouselBundle.putString(PTConstants.PT_ID, "pt_manual_carousel") + + val templateRendererLocal = TemplateRenderer(context, carouselBundle) + + every { + ValidatorFactory.getValidator( + TemplateType.MANUAL_CAROUSEL, + any() + ) + } returns mockBasicTemplateValidator + every { mockBasicTemplateValidator.validate() } returns true + + mockkConstructor(ManualCarouselStyle::class) + every { + anyConstructed().builderFromStyle( + any(), + any(), + any(), + any() + ) + } returns mockNotificationBuilder + + // Act + val result = templateRendererLocal.renderNotification( + carouselBundle, + context, + mockNotificationBuilder, + mockConfig, + 123 + ) + + // Assert + verify { + anyConstructed().builderFromStyle( + any(), + carouselBundle, + 123, + mockNotificationBuilder + ) + } + assertEquals(mockNotificationBuilder, result) + } + + + @Test + fun test_renderNotification_five_icons_template_valid() { + // Arrange + val fiveIconsBundle = Bundle(testBundle) + fiveIconsBundle.putString(PTConstants.PT_ID, "pt_five_icons") + + val templateRendererLocal = TemplateRenderer(context, fiveIconsBundle) + + every { + ValidatorFactory.getValidator( + TemplateType.FIVE_ICONS, + any() + ) + } returns mockBasicTemplateValidator + every { mockBasicTemplateValidator.validate() } returns true + + val mockSmallContentView = mockk() + val mockBigContentView = mockk() + every { mockSmallContentView.getUnloadedFiveIconsCount() } returns 1 + every { mockBigContentView.getUnloadedFiveIconsCount() } returns 1 + + mockkConstructor(FiveIconStyle::class) + every { + anyConstructed().builderFromStyle( + any(), + any(), + any(), + any() + ) + } returns mockNotificationBuilder + every { anyConstructed().fiveIconSmallContentView } returns mockSmallContentView + every { anyConstructed().fiveIconBigContentView } returns mockBigContentView + + // Act + val result = templateRendererLocal.renderNotification( + fiveIconsBundle, + context, + mockNotificationBuilder, + mockConfig, + 123 + ) + + // Assert + verify { + anyConstructed().builderFromStyle( + any(), + fiveIconsBundle, + 123, + mockNotificationBuilder + ) + } + assertEquals(mockNotificationBuilder, result) + } + + @Test + fun test_renderNotification_five_icons_template_invalid_unloaded_icons() { + // Arrange + val fiveIconsBundle = Bundle(testBundle) + fiveIconsBundle.putString(PTConstants.PT_ID, "pt_five_icons") + + val templateRendererLocal = TemplateRenderer(context, fiveIconsBundle) + + every { + ValidatorFactory.getValidator( + TemplateType.FIVE_ICONS, + any() + ) + } returns mockBasicTemplateValidator + every { mockBasicTemplateValidator.validate() } returns true + + val mockSmallContentView = mockk() + val mockBigContentView = mockk() + every { mockSmallContentView.getUnloadedFiveIconsCount() } returns 3 // More than 2 + every { mockBigContentView.getUnloadedFiveIconsCount() } returns 1 + + mockkConstructor(FiveIconStyle::class) + every { + anyConstructed().builderFromStyle( + any(), + any(), + any(), + any() + ) + } returns mockNotificationBuilder + every { anyConstructed().fiveIconSmallContentView } returns mockSmallContentView + every { anyConstructed().fiveIconBigContentView } returns mockBigContentView + + // Act + val result = templateRendererLocal.renderNotification( + fiveIconsBundle, + context, + mockNotificationBuilder, + mockConfig, + 123 + ) + + // Assert + verify { + anyConstructed().builderFromStyle( + any(), + fiveIconsBundle, + 123, + mockNotificationBuilder + ) + } + assertNull(result) // Should return null when too many unloaded icons + } + + @Test + fun test_renderNotification_product_display_template_valid() { + // Arrange + val productBundle = Bundle(testBundle) + productBundle.putString(PTConstants.PT_ID, "pt_product_display") + + val templateRendererLocal = TemplateRenderer(context, productBundle) + + every { + ValidatorFactory.getValidator( + TemplateType.PRODUCT_DISPLAY, + any() + ) + } returns mockBasicTemplateValidator + every { mockBasicTemplateValidator.validate() } returns true + + mockkConstructor(ProductDisplayStyle::class) + every { + anyConstructed().builderFromStyle( + any(), + any(), + any(), + any() + ) + } returns mockNotificationBuilder + + // Act + val result = templateRendererLocal.renderNotification( + productBundle, + context, + mockNotificationBuilder, + mockConfig, + 123 + ) + + // Assert + verify { + anyConstructed().builderFromStyle( + any(), + productBundle, + 123, + mockNotificationBuilder + ) + } + assertEquals(mockNotificationBuilder, result) + } + + @Test + fun test_renderNotification_zero_bezel_template_valid() { + // Arrange + val zeroBezelBundle = Bundle(testBundle) + zeroBezelBundle.putString(PTConstants.PT_ID, "pt_zero_bezel") + + val templateRendererLocal = TemplateRenderer(context, zeroBezelBundle) + + every { + ValidatorFactory.getValidator( + TemplateType.ZERO_BEZEL, + any() + ) + } returns mockBasicTemplateValidator + every { mockBasicTemplateValidator.validate() } returns true + + mockkConstructor(ZeroBezelStyle::class) + every { + anyConstructed().builderFromStyle( + any(), + any(), + any(), + any() + ) + } returns mockNotificationBuilder + + // Act + val result = templateRendererLocal.renderNotification( + zeroBezelBundle, + context, + mockNotificationBuilder, + mockConfig, + 123 + ) + + // Assert + verify { + anyConstructed().builderFromStyle( + any(), + zeroBezelBundle, + 123, + mockNotificationBuilder + ) + } + assertEquals(mockNotificationBuilder, result) + } + + @Test + @Config(sdk = [Build.VERSION_CODES.TIRAMISU]) + fun test_renderNotification_timer_template_valid() { + // Arrange + val timerBundle = Bundle(testBundle) + timerBundle.putString(PTConstants.PT_ID, "pt_timer") + timerBundle.putString(PTConstants.PT_TIMER_END, "10") + + every { Utils.getTimerEnd(timerBundle) } returns 10 + + val templateRendererLocal = TemplateRenderer(context, timerBundle) + + every { + ValidatorFactory.getValidator( + TemplateType.TIMER, + any() + ) + } returns mockBasicTemplateValidator + every { mockBasicTemplateValidator.validate() } returns true + + + mockkConstructor(TimerStyle::class) + every { + anyConstructed().builderFromStyle( + any(), + any(), + any(), + any() + ) + } returns mockNotificationBuilder + every { mockNotificationBuilder.setTimeoutAfter(any()) } returns mockNotificationBuilder + + // Act + val result = templateRendererLocal.renderNotification( + timerBundle, + context, + mockNotificationBuilder, + mockConfig, + 123 + ) + + // Assert + verify { + anyConstructed().builderFromStyle( + any(), + timerBundle, + 123, + mockNotificationBuilder + ) + } + verify { mockNotificationBuilder.setTimeoutAfter(11000) } + assertEquals(mockNotificationBuilder, result) + } + + @Test + @Config(sdk = [Build.VERSION_CODES.M]) // Below Nougat + fun test_renderNotification_timer_template_below_nougat() { + // Arrange + val timerBundle = Bundle(testBundle) + timerBundle.putString(PTConstants.PT_ID, "pt_timer") + + val templateRendererLocal = TemplateRenderer(context, timerBundle) + + every { + ValidatorFactory.getValidator( + TemplateType.TIMER, + any() + ) + } returns mockBasicTemplateValidator + every { + ValidatorFactory.getValidator( + TemplateType.BASIC, + any() + ) + } returns mockBasicTemplateValidator + every { mockBasicTemplateValidator.validate() } returns true + + mockkConstructor(BasicStyle::class) + every { + anyConstructed().builderFromStyle( + any(), + any(), + any(), + any() + ) + } returns mockNotificationBuilder + + // Act + val result = templateRendererLocal.renderNotification( + timerBundle, + context, + mockNotificationBuilder, + mockConfig, + 123 + ) + + // Assert + verify { + anyConstructed().builderFromStyle( + any(), + timerBundle, + 123, + mockNotificationBuilder + ) + } + assertEquals(mockNotificationBuilder, result) + } + + @Test + fun test_renderNotification_input_box_template_valid() { + // Arrange + val inputBoxBundle = Bundle(testBundle) + inputBoxBundle.putString(PTConstants.PT_ID, "pt_input") + + val templateRendererLocal = TemplateRenderer(context, inputBoxBundle) + + every { + ValidatorFactory.getValidator( + TemplateType.INPUT_BOX, + any() + ) + } returns mockBasicTemplateValidator + every { mockBasicTemplateValidator.validate() } returns true + + mockkConstructor(InputBoxStyle::class) + every { + anyConstructed().builderFromStyle( + any(), + any(), + any(), + any() + ) + } returns mockNotificationBuilder + + // Act + val result = templateRendererLocal.renderNotification( + inputBoxBundle, + context, + mockNotificationBuilder, + mockConfig, + 123 + ) + + // Assert + verify { + anyConstructed().builderFromStyle( + any(), + inputBoxBundle, + 123, + mockNotificationBuilder + ) + } + assertEquals(mockNotificationBuilder, result) + } + + + @Test + fun test_renderNotification_unknown_template_type() { + // Arrange + val unknownBundle = Bundle(testBundle) + unknownBundle.putString(PTConstants.PT_ID, "pt_unknown") + + val templateRendererLocal = TemplateRenderer(context, unknownBundle) + + // Act + val result = templateRendererLocal.renderNotification( + unknownBundle, + context, + mockNotificationBuilder, + mockConfig, + 123 + ) + + // Assert + // Should log "operation not defined!" and return null + assertNull(result) + } + @Test + fun test_renderNotification_with_null_template_id() { + // Arrange + val nullIdBundle = Bundle() + nullIdBundle.putString(PTConstants.PT_TITLE, "Test Title") + nullIdBundle.putString(PTConstants.PT_MSG, "Test Message") + // Note: We're not adding PT_ID on purpose + + // Act + val result = templateRenderer.renderNotification( + nullIdBundle, + context, + mockNotificationBuilder, + mockConfig, + 123 + ) + + // Assert + assertNull(result) + } + + @Test + fun test_getActionButtons_with_valid_actions() { + // Arrange + val actionsJson = JSONArray( + """ + [ + { + "l": "Action 1", + "id": "action1", + "dl": "https://example.com", + "ac": true + }, + { + "l": "Action 2", + "id": "action2", + "dl": "https://example2.com", + "ac": false + } + ] + """ + ) + + // Mock the necessary methods with relaxed settings + mockkStatic(Uri::class) + every { Uri.parse(any()) } returns mockk(relaxed = true) + + every { Utils.setPackageNameFromResolveInfoList(any(), any()) } just Runs + + // Act + val result = templateRenderer.getActionButtons(context, testBundle, 123, actionsJson) + + // Assert + assertEquals(2, result.size) + assertEquals("Action 1", result[0].label) + assertEquals("Action 2", result[1].label) + } + + @Test + fun test_getActionButtons_with_missing_required_fields() { + // Arrange + val actionsJson = JSONArray( + """ + [ + { + "l": "", + "id": "action1" + }, + { + "l": "Action 2", + "id": "" + } + ] + """ + ) + + // Act + val result = templateRenderer.getActionButtons(context, testBundle, 123, actionsJson) + + // Assert + assertTrue(result.isEmpty()) + } +} + +