Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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