Skip to content

Commit 10fd74d

Browse files
authored
Merge pull request #698 from Iterable/embedded-clean-merge
[MOB-5473] Embedded GA Release
2 parents 3dd3939 + 006c993 commit 10fd74d

File tree

62 files changed

+3510
-28
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+3510
-28
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,6 @@ pom.xml.tag
6565
pom.xml.releaseBackup
6666
pom.xml.versionsBackup
6767
pom.xml.next
68-
release.properties
68+
release.properties
69+
70+
jacoco.exec

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

55
## [Unreleased]
6+
67
#### Added
78
- nothing yet
89

@@ -12,6 +13,17 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1213
#### Changed
1314
- nothing yet
1415

16+
## [3.5.0]
17+
#### Added
18+
- introduces support for embedded messaging: an eligibility–based, personalized messages sent from Iterable to your mobile and web apps, which can display them inline, using native interface components.
19+
- To display embedded messages, you can use customizable, out-of-the-box components provided by the SDK (cards, notifications, banners), or you can build fully custom components of your own design.
20+
- To learn more, read [Embedded Messages with Iterable's iOS SDK](https://support.iterable.com/hc/articles/23061840746900).
21+
22+
#### Changed
23+
- `IterableConfig` is updated with an `enableEmbeddedMessaging` flag that needs to be set to true to allow use of embedded messaging functionality
24+
25+
## [3.5.0-beta1](https://github.com/Iterable/iterable-android-sdk/releases/tag/3.5.0-beta1)
26+
1527
## [3.4.17](https://github.com/Iterable/iterable-android-sdk/releases/tag/3.4.17)
1628
#### Added
1729
- when JWT is invalid, `IterableAuthManager` is updated to fetch and store a new JWT token locally

gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#Tue Apr 28 13:23:09 PDT 2020
1+
#Tue Oct 10 10:01:47 MDT 2023
22
distributionBase=GRADLE_USER_HOME
33
distributionPath=wrapper/dists
44
zipStoreBase=GRADLE_USER_HOME

iterableapi-ui/build.gradle

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ android {
77
namespace 'com.iterable.iterableapi.ui'
88

99
defaultConfig {
10-
minSdkVersion 15
10+
minSdkVersion 16
1111
targetSdkVersion 28
1212
vectorDrawables.useSupportLibrary = true
1313
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -37,17 +37,21 @@ dependencies {
3737
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
3838
implementation 'androidx.appcompat:appcompat:1.0.0'
3939
implementation 'androidx.recyclerview:recyclerview:1.0.0'
40+
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
41+
implementation 'com.google.android.flexbox:flexbox:3.0.0'
42+
implementation 'androidx.cardview:cardview:1.0.0'
43+
implementation "com.github.bumptech.glide:glide:4.8.0"
44+
implementation 'com.google.android.material:material:1.2.0'
4045

4146
testImplementation 'junit:junit:4.13.2'
4247
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
4348
androidTestImplementation 'androidx.test:runner:1.2.0'
4449
androidTestImplementation 'androidx.test:rules:1.2.0'
45-
4650
}
4751

4852
ext {
4953
libraryName = 'iterableapi-ui'
50-
libraryVersion = '3.4.17'
54+
libraryVersion = '3.5.0-beta1'
5155
}
5256

5357
if (hasProperty("mavenPublishEnabled")) {
@@ -57,6 +61,8 @@ if (hasProperty("mavenPublishEnabled")) {
5761
task javadoc(type: Javadoc) {
5862
source = android.sourceSets.main.java.srcDirs
5963
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
64+
65+
exclude '**/*.kt'
6066
}
6167

6268
// A hack to import the classpath and BuildConfig into the javadoc task
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package com.iterable.iterableapi.ui.embedded
2+
3+
import android.graphics.drawable.GradientDrawable
4+
import android.os.Bundle
5+
import android.view.LayoutInflater
6+
import android.view.View
7+
import android.view.ViewGroup
8+
import android.widget.Button
9+
import android.widget.ImageView
10+
import android.widget.LinearLayout
11+
import android.widget.TextView
12+
import androidx.core.content.ContextCompat
13+
import androidx.fragment.app.Fragment
14+
import com.bumptech.glide.Glide
15+
import com.google.android.flexbox.FlexboxLayout
16+
import com.iterable.iterableapi.EmbeddedMessageElementsButton
17+
import com.iterable.iterableapi.IterableApi
18+
import com.iterable.iterableapi.IterableEmbeddedMessage
19+
import com.iterable.iterableapi.ui.R
20+
21+
class IterableEmbeddedView(
22+
private var viewType: IterableEmbeddedViewType,
23+
private var message: IterableEmbeddedMessage,
24+
private var config: IterableEmbeddedViewConfig?
25+
): Fragment() {
26+
27+
private val defaultBackgroundColor : Int by lazy { getDefaultColor(viewType, R.color.notification_background_color, R.color.banner_background_color, R.color.banner_background_color) }
28+
private val defaultBorderColor : Int by lazy { getDefaultColor(viewType, R.color.notification_border_color, R.color.banner_border_color, R.color.banner_border_color) }
29+
private val defaultPrimaryBtnBackgroundColor: Int by lazy { getDefaultColor(viewType, R.color.white, R.color.white, R.color.banner_button_color) }
30+
private val defaultPrimaryBtnTextColor: Int by lazy { getDefaultColor(viewType, R.color.notification_text_color, R.color.banner_button_color, R.color.white) }
31+
private val defaultSecondaryBtnBackgroundColor: Int by lazy { getDefaultColor(viewType, R.color.notification_background_color, R.color.white, R.color.white) }
32+
private val defaultSecondaryBtnTextColor: Int by lazy { getDefaultColor(viewType, R.color.notification_text_color, R.color.banner_button_color, R.color.banner_button_color) }
33+
private val defaultTitleTextColor: Int by lazy { getDefaultColor(viewType, R.color.notification_text_color, R.color.title_text_color, R.color.title_text_color) }
34+
private val defaultBodyTextColor: Int by lazy { getDefaultColor(viewType, R.color.notification_text_color, R.color.body_text_color, R.color.body_text_color) }
35+
private val defaultBorderWidth = 1
36+
private val defaultBorderCornerRadius = 8f
37+
38+
override fun onCreateView(
39+
inflater: LayoutInflater,
40+
container: ViewGroup?,
41+
savedInstanceState: Bundle?
42+
): View? {
43+
44+
val view = when (viewType) {
45+
IterableEmbeddedViewType.BANNER -> {
46+
val bannerView = inflater.inflate(R.layout.banner_view, container, false)
47+
bind(viewType, bannerView, message)
48+
bannerView
49+
}
50+
IterableEmbeddedViewType.CARD -> {
51+
val cardView = inflater.inflate(R.layout.card_view, container, false)
52+
bind(viewType, cardView, message)
53+
cardView
54+
}
55+
IterableEmbeddedViewType.NOTIFICATION -> {
56+
val notificationView = inflater.inflate(R.layout.notification_view, container, false)
57+
bind(viewType, notificationView, message)
58+
notificationView
59+
}
60+
}
61+
62+
setDefaultAction(view, message)
63+
configure(view, viewType, config)
64+
65+
return view
66+
}
67+
68+
private fun configure(view: View, viewType: IterableEmbeddedViewType, config: IterableEmbeddedViewConfig?) {
69+
70+
val backgroundColor = config?.backgroundColor.takeIf { it != null } ?: defaultBackgroundColor
71+
val borderColor = config?.borderColor.takeIf { it != null } ?: defaultBorderColor
72+
val borderWidth = config?.borderWidth.takeIf { it != null } ?: defaultBorderWidth
73+
val borderCornerRadius = config?.borderCornerRadius.takeIf { it != null } ?: defaultBorderCornerRadius
74+
75+
val primaryBtnBackgroundColor = config?.primaryBtnBackgroundColor.takeIf { it != null } ?: defaultPrimaryBtnBackgroundColor
76+
val primaryBtnTextColor = config?.primaryBtnTextColor.takeIf { it != null } ?: defaultPrimaryBtnTextColor
77+
78+
val secondaryBtnBackgroundColor = config?.secondaryBtnBackgroundColor.takeIf { it != null } ?: defaultSecondaryBtnBackgroundColor
79+
val secondaryBtnTextColor = config?.secondaryBtnTextColor.takeIf { it != null } ?: defaultSecondaryBtnTextColor
80+
81+
val titleTextColor = config?.titleTextColor.takeIf { it != null } ?: defaultTitleTextColor
82+
val bodyTextColor = config?.bodyTextColor.takeIf { it != null } ?: defaultBodyTextColor
83+
84+
val gradientDrawable = GradientDrawable()
85+
86+
gradientDrawable.setColor(backgroundColor)
87+
gradientDrawable.setStroke(borderWidth, borderColor)
88+
gradientDrawable.cornerRadius = borderCornerRadius
89+
view.setBackgroundDrawable(gradientDrawable)
90+
91+
val firstButton = view.findViewById<Button>(R.id.embedded_message_first_button)
92+
val secondButton = view.findViewById<Button>(R.id.embedded_message_second_button)
93+
94+
val titleText = view.findViewById<TextView>(R.id.embedded_message_title)
95+
val bodyText = view.findViewById<TextView>(R.id.embedded_message_body)
96+
97+
if(config?.primaryBtnBackgroundColor != null) {
98+
val primaryBtnBackgroundDrawable = if(viewType == IterableEmbeddedViewType.NOTIFICATION)
99+
ContextCompat.getDrawable(requireContext(), R.drawable.primary_notification_button_background) as? GradientDrawable
100+
else ContextCompat.getDrawable(requireContext(), R.drawable.primary_banner_button_background) as? GradientDrawable
101+
primaryBtnBackgroundDrawable?.setColor(primaryBtnBackgroundColor)
102+
103+
firstButton.setBackgroundDrawable(primaryBtnBackgroundDrawable)
104+
}
105+
106+
if(config?.secondaryBtnBackgroundColor != null) {
107+
val secondaryBtnBackgroundDrawable = if(viewType == IterableEmbeddedViewType.NOTIFICATION)
108+
ContextCompat.getDrawable(requireContext(), R.drawable.secondary_notification_button_background) as? GradientDrawable
109+
else ContextCompat.getDrawable(requireContext(), R.drawable.secondary_banner_button_background) as? GradientDrawable
110+
secondaryBtnBackgroundDrawable?.setColor(secondaryBtnBackgroundColor)
111+
112+
secondButton.setBackgroundDrawable(secondaryBtnBackgroundDrawable)
113+
}
114+
115+
firstButton.setTextColor(primaryBtnTextColor)
116+
secondButton.setTextColor(secondaryBtnTextColor)
117+
118+
titleText.setTextColor(titleTextColor)
119+
bodyText.setTextColor(bodyTextColor)
120+
}
121+
122+
private fun bind(viewType: IterableEmbeddedViewType, view: View, message: IterableEmbeddedMessage): View {
123+
val embeddedMessageViewTitle: TextView = view.findViewById(R.id.embedded_message_title)
124+
val embeddedMessageViewBody: TextView = view.findViewById(R.id.embedded_message_body)
125+
val embeddedMessageViewButton: Button = view.findViewById(R.id.embedded_message_first_button)
126+
val embeddedMessageViewButton2: Button = view.findViewById(R.id.embedded_message_second_button)
127+
128+
if(viewType != IterableEmbeddedViewType.NOTIFICATION) {
129+
val embeddedMessageImageView: ImageView = view.findViewById(R.id.embedded_message_image)
130+
131+
if(message.elements?.mediaURL?.isEmpty() == true) {
132+
embeddedMessageImageView.visibility = View.GONE
133+
} else {
134+
Glide.with(view.context).load(message.elements?.mediaURL).into(embeddedMessageImageView)
135+
embeddedMessageImageView.contentDescription = message.elements?.mediaUrlCaption
136+
}
137+
}
138+
139+
embeddedMessageViewTitle.text = message.elements?.title
140+
embeddedMessageViewBody.text = message.elements?.body
141+
142+
val buttons = message.elements?.buttons
143+
144+
if (buttons != null) {
145+
setButton(embeddedMessageViewButton, buttons.getOrNull(0), message)
146+
147+
if (buttons.size > 1) {
148+
setButton(embeddedMessageViewButton2, buttons.getOrNull(1), message)
149+
} else {
150+
embeddedMessageViewButton2.visibility = View.GONE
151+
}
152+
153+
} else {
154+
embeddedMessageViewButton.visibility = View.GONE
155+
embeddedMessageViewButton2.visibility = View.GONE
156+
}
157+
158+
return view
159+
}
160+
161+
private fun setDefaultAction(view: View, message: IterableEmbeddedMessage) {
162+
if(message.elements?.defaultAction != null) {
163+
val clickedUrl = message.elements?.defaultAction?.data.takeIf { it?.isNotEmpty() == true } ?: message.elements?.defaultAction?.type
164+
165+
view.setOnClickListener {
166+
IterableApi.getInstance().embeddedManager.handleEmbeddedClick(message, null, clickedUrl)
167+
IterableApi.getInstance().trackEmbeddedClick(message, null, clickedUrl)
168+
}
169+
}
170+
}
171+
172+
private fun setButton(buttonView: Button, button: EmbeddedMessageElementsButton?, message: IterableEmbeddedMessage) {
173+
buttonView.visibility = if (button?.title == null) View.GONE else View.VISIBLE
174+
buttonView.text = button?.title.orEmpty()
175+
176+
val clickedUrl = if (button?.action?.data?.isNotEmpty() == true) button.action?.data else button?.action?.type
177+
178+
buttonView.setOnClickListener {
179+
IterableApi.getInstance().embeddedManager.handleEmbeddedClick(message, button?.id, clickedUrl)
180+
IterableApi.getInstance().trackEmbeddedClick(message, button?.id, clickedUrl)
181+
}
182+
}
183+
184+
private fun getDefaultColor(viewType: IterableEmbeddedViewType, notificationColor: Int, cardColor: Int, bannerColor: Int): Int {
185+
return when (viewType) {
186+
IterableEmbeddedViewType.NOTIFICATION -> ContextCompat.getColor(requireContext(), notificationColor)
187+
IterableEmbeddedViewType.CARD -> ContextCompat.getColor(requireContext(), cardColor)
188+
else -> ContextCompat.getColor(requireContext(), bannerColor)
189+
}
190+
}
191+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.iterable.iterableapi.ui.embedded
2+
3+
import android.graphics.Color
4+
5+
data class IterableEmbeddedViewConfig(
6+
val backgroundColor: Int?,
7+
val borderColor: Int?,
8+
val borderWidth: Int?,
9+
val borderCornerRadius: Float?,
10+
val primaryBtnBackgroundColor: Int?,
11+
val primaryBtnTextColor: Int?,
12+
val secondaryBtnBackgroundColor: Int?,
13+
val secondaryBtnTextColor: Int?,
14+
val titleTextColor: Int?,
15+
val bodyTextColor: Int?
16+
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.iterable.iterableapi.ui.embedded
2+
3+
public enum class IterableEmbeddedViewType {
4+
BANNER,
5+
CARD,
6+
NOTIFICATION
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:shape="rectangle">
4+
5+
<stroke
6+
android:width="1dp"
7+
android:color="@color/banner_border_color" />
8+
9+
<solid android:color="@color/banner_background_color" />
10+
11+
<corners
12+
android:radius="8dp" />
13+
</shape>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shapeable
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
android:shape="rectangle">
5+
<solid android:color="#FFFFFF" />
6+
<corners
7+
android:topLeftRadius="16dp"
8+
android:topRightRadius="16dp"
9+
android:bottomLeftRadius="0dp"
10+
android:bottomRightRadius="0dp"
11+
/>
12+
</shapeable>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:shape="rectangle">
4+
5+
<stroke
6+
android:width="1dp"
7+
android:color="@color/notification_background_color" />
8+
9+
<solid android:color="@color/notification_border_color" />
10+
11+
<corners
12+
android:radius="8dp" />
13+
</shape>

0 commit comments

Comments
 (0)