Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f5352e0
Support for in app in full compose apps
franco-zalamena-iterable Jan 15, 2026
ffb653c
InAppServices for removing duplicated code
franco-zalamena-iterable Jan 16, 2026
2a0eacd
Fixing imports and styling
franco-zalamena-iterable Feb 13, 2026
cae2eaf
Add activity recreation state handling for Dialog in-app
franco-zalamena-iterable Apr 6, 2026
b859d71
Add unit tests for IterableInAppDialogNotification lifecycle
franco-zalamena-iterable Apr 6, 2026
35156fb
Add volatile to static singleton fields for thread safety
franco-zalamena-iterable Apr 6, 2026
86bd80f
Fix Java 16 instanceof and lazy-init InAppServices tracking
franco-zalamena-iterable Apr 10, 2026
f75a3ca
Revert HTMLNotificationCallbacks to package-private visibility
franco-zalamena-iterable Apr 10, 2026
6bd035d
Merge branch 'master' into SDK-100-compose-support
franco-zalamena-iterable Apr 10, 2026
c072da5
Merge branch 'master' into SDK-100-compose-support
joaodordio Apr 13, 2026
601c592
Fixes for dialog in compose
franco-zalamena-iterable Apr 14, 2026
ca8ae30
Merge branch 'master' into SDK-100-compose-support
joaodordio Apr 16, 2026
5b4e294
Changelog
franco-zalamena-iterable Apr 16, 2026
cd30b21
Merge branch 'master' into SDK-100-compose-support
franco-zalamena-iterable Apr 21, 2026
e98af86
Revert volatile on IterableInAppFragmentHTMLNotification statics
franco-zalamena-iterable Apr 21, 2026
f47cba0
Remove silent exception swallowing in InAppTrackingService.removeMessage
franco-zalamena-iterable Apr 21, 2026
b7594a1
Drop ineffective onSaveInstanceState dance in Dialog notification
franco-zalamena-iterable Apr 21, 2026
e90f917
Tighten IterableInAppDialogNotification.createInstance guard
franco-zalamena-iterable Apr 21, 2026
f5ea996
Simplify Dialog back-press handling to a single setOnKeyListener path
franco-zalamena-iterable Apr 21, 2026
158b0e0
Port layout-aware enter/exit animations to Dialog for Fragment parity
franco-zalamena-iterable Apr 21, 2026
a3f44ce
Fixing deprecation warnings
franco-zalamena-iterable Apr 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.iterable.iterableapi

import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.TransitionDrawable
import android.view.View
import android.view.Window
import androidx.core.graphics.ColorUtils

internal class InAppAnimationService {

fun createInAppBackgroundDrawable(hexColor: String?, alpha: Double): ColorDrawable? {
val backgroundColor = try {
if (!hexColor.isNullOrEmpty()) {
Color.parseColor(hexColor)
} else {
Color.BLACK
}
} catch (e: IllegalArgumentException) {
IterableLogger.w(TAG, "Invalid background color: $hexColor. Using BLACK.", e)
Color.BLACK
}

val backgroundWithAlpha = ColorUtils.setAlphaComponent(
backgroundColor,
(alpha * 255).toInt()
)

return ColorDrawable(backgroundWithAlpha)
}

fun animateWindowBackground(window: Window, from: Drawable, to: Drawable, shouldAnimate: Boolean) {
if (shouldAnimate) {
val layers = arrayOf(from, to)
val transition = TransitionDrawable(layers)
window.setBackgroundDrawable(transition)
transition.startTransition(ANIMATION_DURATION_MS)
} else {
window.setBackgroundDrawable(to)
}
}

fun showInAppBackground(window: Window, hexColor: String?, alpha: Double, shouldAnimate: Boolean) {
val backgroundDrawable = createInAppBackgroundDrawable(hexColor, alpha)

if (backgroundDrawable == null) {
IterableLogger.w(TAG, "Failed to create background drawable")
return
}

if (shouldAnimate) {
val transparentDrawable = ColorDrawable(Color.TRANSPARENT)
animateWindowBackground(window, transparentDrawable, backgroundDrawable, true)
} else {
window.setBackgroundDrawable(backgroundDrawable)
}
}

fun showAndAnimateWebView(webView: View, shouldAnimate: Boolean, context: Context?) {
if (shouldAnimate && context != null) {
webView.alpha = 0f
webView.visibility = View.VISIBLE
webView.animate()
.alpha(1.0f)
.setDuration(ANIMATION_DURATION_MS.toLong())
.start()
} else {
webView.alpha = 1.0f
webView.visibility = View.VISIBLE
}
}

fun hideInAppBackground(window: Window, hexColor: String?, alpha: Double, shouldAnimate: Boolean) {
if (shouldAnimate) {
val backgroundDrawable = createInAppBackgroundDrawable(hexColor, alpha)
val transparentDrawable = ColorDrawable(Color.TRANSPARENT)

if (backgroundDrawable != null) {
animateWindowBackground(window, backgroundDrawable, transparentDrawable, true)
}
} else {
window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
}
}

fun prepareViewForDisplay(view: View) {
view.alpha = 0f
view.visibility = View.INVISIBLE
}

companion object {
private const val ANIMATION_DURATION_MS = 300
private const val TAG = "InAppAnimService"
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.iterable.iterableapi

import android.graphics.Rect
import android.view.Gravity
import android.view.Window
import android.view.WindowManager

internal class InAppLayoutService {
internal enum class InAppLayout {
TOP,
BOTTOM,
CENTER,
FULLSCREEN
}

fun getInAppLayout(padding: Rect): InAppLayout {
return getInAppLayout(InAppPadding.fromRect(padding))
}

fun getInAppLayout(padding: InAppPadding): InAppLayout {
if (padding.top == 0 && padding.bottom == 0) {
return InAppLayout.FULLSCREEN
} else if (padding.top > 0 && padding.bottom <= 0) {
return InAppLayout.TOP
} else if (padding.top <= 0 && padding.bottom > 0) {
return InAppLayout.BOTTOM
} else {
return InAppLayout.CENTER
}
}

fun getVerticalLocation(padding: Rect): Int {
return getVerticalLocation(InAppPadding.fromRect(padding))
}

fun getVerticalLocation(padding: InAppPadding): Int {
val layout = getInAppLayout(padding)

when (layout) {
InAppLayout.TOP -> return Gravity.TOP
InAppLayout.BOTTOM -> return Gravity.BOTTOM
InAppLayout.CENTER -> return Gravity.CENTER_VERTICAL
InAppLayout.FULLSCREEN -> return Gravity.CENTER_VERTICAL
}
}

fun configureWindowFlags(window: Window?, layout: InAppLayout) {
if (window == null) {
return
}

if (layout == InAppLayout.FULLSCREEN) {
window.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
} else if (layout != InAppLayout.TOP) {
window.setFlags(
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
)
}
}

fun setWindowToFullScreen(window: Window?) {
window?.setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT
)
}

fun applyWindowGravity(window: Window?, padding: Rect, source: String?) {
if (window == null) {
return
}

val verticalGravity = getVerticalLocation(padding)
val params = window.attributes

when (verticalGravity) {
Gravity.CENTER_VERTICAL -> params.gravity = Gravity.CENTER
Gravity.TOP -> params.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
Gravity.BOTTOM -> params.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
else -> params.gravity = Gravity.CENTER
}

window.attributes = params

if (source != null) {
IterableLogger.d(
"InAppLayoutService",
"Applied window gravity from " + source + ": " + params.gravity
)
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.iterable.iterableapi

import android.content.Context
import android.hardware.SensorManager
import android.os.Handler
import android.os.Looper
import android.view.OrientationEventListener

internal class InAppOrientationService {

fun interface OrientationChangeCallback {
fun onOrientationChanged()
}

fun createOrientationListener(
context: Context,
callback: OrientationChangeCallback
): OrientationEventListener {
return object : OrientationEventListener(context, SensorManager.SENSOR_DELAY_NORMAL) {
private var lastOrientation = -1

override fun onOrientationChanged(orientation: Int) {
val currentOrientation = roundToNearest90Degrees(orientation)

if (currentOrientation != lastOrientation && lastOrientation != -1) {
lastOrientation = currentOrientation

Handler(Looper.getMainLooper()).postDelayed({
IterableLogger.d(TAG, "Orientation changed, triggering callback")
callback.onOrientationChanged()
}, ORIENTATION_CHANGE_DELAY_MS)
} else if (lastOrientation == -1) {
lastOrientation = currentOrientation
}
}
}
}

fun roundToNearest90Degrees(orientation: Int): Int {
return ((orientation + 45) / 90 * 90) % 360
}

fun enableListener(listener: OrientationEventListener?) {
if (listener != null && listener.canDetectOrientation()) {
listener.enable()
IterableLogger.d(TAG, "Orientation listener enabled")
} else {
IterableLogger.w(TAG, "Cannot enable orientation listener")
}
}

fun disableListener(listener: OrientationEventListener?) {
listener?.disable()
IterableLogger.d(TAG, "Orientation listener disabled")
}

companion object {
private const val TAG = "InAppOrientService"
private const val ORIENTATION_CHANGE_DELAY_MS = 1500L
}
}

27 changes: 27 additions & 0 deletions iterableapi/src/main/java/com/iterable/iterableapi/InAppPadding.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.iterable.iterableapi

import android.graphics.Rect

internal data class InAppPadding(
val left: Int = 0,
val top: Int = 0,
val right: Int = 0,
val bottom: Int = 0
) {
companion object {
@JvmStatic
fun fromRect(rect: Rect): InAppPadding {
return InAppPadding(
left = rect.left,
top = rect.top,
right = rect.right,
bottom = rect.bottom
)
}
}

fun toRect(): Rect {
return Rect(left, top, right, bottom)
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.iterable.iterableapi

internal object InAppServices {
val layout: InAppLayoutService = InAppLayoutService()
val animation: InAppAnimationService = InAppAnimationService()
val tracking: InAppTrackingService = InAppTrackingService(IterableApi.sharedInstance)
val webView: InAppWebViewService = InAppWebViewService()
val orientation: InAppOrientationService = InAppOrientationService()
}

Loading
Loading