diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppFragmentHTMLNotification.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppFragmentHTMLNotification.java index 779c1c93c..df67a8248 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppFragmentHTMLNotification.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppFragmentHTMLNotification.java @@ -26,6 +26,7 @@ import android.view.WindowManager; import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import android.widget.FrameLayout; import android.widget.RelativeLayout; import androidx.annotation.NonNull; @@ -58,6 +59,12 @@ public class IterableInAppFragmentHTMLNotification extends DialogFragment implem private boolean callbackOnCancel = false; private String htmlString; private String messageId; + + // Resize debouncing fields + private Handler resizeHandler = new Handler(); + private Runnable pendingResizeRunnable; + private float lastContentHeight = -1; + private static final int RESIZE_DEBOUNCE_DELAY_MS = 200; private double backgroundAlpha; //TODO: remove in a future version private Rect insetPadding; @@ -105,6 +112,35 @@ public IterableInAppFragmentHTMLNotification() { insetPadding = new Rect(); this.setStyle(DialogFragment.STYLE_NO_FRAME, androidx.appcompat.R.style.Theme_AppCompat_NoActionBar); } + + @Override + public void onStart() { + super.onStart(); + + // Set dialog positioning after the dialog is created and shown + Dialog dialog = getDialog(); + if (dialog != null) { + Window window = dialog.getWindow(); + if (window != null) { + WindowManager.LayoutParams windowParams = window.getAttributes(); + int startGravity = getVerticalLocation(insetPadding); + + if (startGravity == Gravity.CENTER_VERTICAL) { + windowParams.gravity = Gravity.CENTER; + IterableLogger.d(TAG, "Set dialog gravity to CENTER in onStart"); + } else if (startGravity == Gravity.TOP) { + windowParams.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; + IterableLogger.d(TAG, "Set dialog gravity to TOP in onStart"); + } else if (startGravity == Gravity.BOTTOM) { + windowParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; + IterableLogger.d(TAG, "Set dialog gravity to BOTTOM in onStart"); + } + + window.setAttributes(windowParams); + IterableLogger.d(TAG, "Applied window gravity in onStart: " + windowParams.gravity); + } + } + } @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -144,6 +180,25 @@ public void onCancel(DialogInterface dialog) { } }); dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + + // Set window gravity for the dialog + Window window = dialog.getWindow(); + WindowManager.LayoutParams windowParams = window.getAttributes(); + int dialogGravity = getVerticalLocation(insetPadding); + + if (dialogGravity == Gravity.CENTER_VERTICAL) { + windowParams.gravity = Gravity.CENTER; + IterableLogger.d(TAG, "Set dialog gravity to CENTER in onCreateDialog"); + } else if (dialogGravity == Gravity.TOP) { + windowParams.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; + IterableLogger.d(TAG, "Set dialog gravity to TOP in onCreateDialog"); + } else if (dialogGravity == Gravity.BOTTOM) { + windowParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; + IterableLogger.d(TAG, "Set dialog gravity to BOTTOM in onCreateDialog"); + } + + window.setAttributes(windowParams); + if (getInAppLayout(insetPadding) == InAppLayout.FULLSCREEN) { dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); } else if (getInAppLayout(insetPadding) != InAppLayout.TOP) { @@ -162,23 +217,57 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c if (getInAppLayout(insetPadding) == InAppLayout.FULLSCREEN) { getDialog().getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); } + + // Set initial window gravity based on inset padding + Window window = getDialog().getWindow(); + WindowManager.LayoutParams windowParams = window.getAttributes(); + int windowGravity = getVerticalLocation(insetPadding); + + if (windowGravity == Gravity.CENTER_VERTICAL) { + windowParams.gravity = Gravity.CENTER; + IterableLogger.d(TAG, "Set initial CENTER window gravity in onCreateView"); + } else if (windowGravity == Gravity.TOP) { + windowParams.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; + IterableLogger.d(TAG, "Set initial TOP window gravity in onCreateView"); + } else if (windowGravity == Gravity.BOTTOM) { + windowParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; + IterableLogger.d(TAG, "Set initial BOTTOM window gravity in onCreateView"); + } + + window.setAttributes(windowParams); webView = new IterableWebView(getContext()); webView.setId(R.id.webView); + + // Debug the HTML content + IterableLogger.d(TAG, "HTML content preview: " + (htmlString.length() > 200 ? htmlString.substring(0, 200) + "..." : htmlString)); + webView.createWithHtml(this, htmlString); if (orientationListener == null) { orientationListener = new OrientationEventListener(getContext(), SensorManager.SENSOR_DELAY_NORMAL) { + private int lastOrientation = -1; + // Resize the webView on device rotation public void onOrientationChanged(int orientation) { - if (loaded) { - final Handler handler = new Handler(); - handler.postDelayed(new Runnable() { - @Override - public void run() { - runResizeScript(); - } - }, 1000); + if (loaded && webView != null) { + // Only trigger on significant orientation changes (90 degree increments) + int currentOrientation = ((orientation + 45) / 90) * 90; + if (currentOrientation != lastOrientation && lastOrientation != -1) { + lastOrientation = currentOrientation; + + // Use longer delay for orientation changes to allow layout to stabilize + final Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + @Override + public void run() { + IterableLogger.d(TAG, "Orientation changed, triggering resize"); + runResizeScript(); + } + }, 1500); // Increased delay for better stability + } else if (lastOrientation == -1) { + lastOrientation = currentOrientation; + } } } }; @@ -186,17 +275,53 @@ public void run() { orientationListener.enable(); - RelativeLayout relativeLayout = new RelativeLayout(this.getContext()); - RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); - relativeLayout.setVerticalGravity(getVerticalLocation(insetPadding)); - relativeLayout.addView(webView, layoutParams); + // Create a FrameLayout as the main container for better positioning control + FrameLayout frameLayout = new FrameLayout(this.getContext()); + + // Create a RelativeLayout as a wrapper for the WebView + RelativeLayout webViewContainer = new RelativeLayout(this.getContext()); + + int gravity = getVerticalLocation(insetPadding); + IterableLogger.d(TAG, "Initial setup - gravity: " + gravity + " for inset padding: " + insetPadding); + + // Set FrameLayout gravity based on positioning + FrameLayout.LayoutParams containerParams = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT + ); + + if (gravity == Gravity.CENTER_VERTICAL) { + containerParams.gravity = Gravity.CENTER; + IterableLogger.d(TAG, "Applied CENTER gravity to container"); + } else if (gravity == Gravity.TOP) { + containerParams.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; + IterableLogger.d(TAG, "Applied TOP gravity to container"); + } else if (gravity == Gravity.BOTTOM) { + containerParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; + IterableLogger.d(TAG, "Applied BOTTOM gravity to container"); + } + + // Add WebView to the RelativeLayout container with WRAP_CONTENT for proper sizing + RelativeLayout.LayoutParams webViewParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ); + webViewParams.addRule(RelativeLayout.CENTER_IN_PARENT); + webViewContainer.addView(webView, webViewParams); + + IterableLogger.d(TAG, "Added WebView with WRAP_CONTENT and CENTER_IN_PARENT rule"); + + // Add the container to the FrameLayout + frameLayout.addView(webViewContainer, containerParams); + + IterableLogger.d(TAG, "Created FrameLayout with positioned RelativeLayout container"); if (savedInstanceState == null || !savedInstanceState.getBoolean(IN_APP_OPEN_TRACKED, false)) { IterableApi.sharedInstance.trackInAppOpen(messageId, location); } prepareToShowWebView(); - return relativeLayout; + return frameLayout; } public void setLoaded(boolean loaded) { @@ -226,6 +351,12 @@ public void onStop() { public void onDestroy() { super.onDestroy(); + // Clean up pending resize operations + if (resizeHandler != null && pendingResizeRunnable != null) { + resizeHandler.removeCallbacks(pendingResizeRunnable); + pendingResizeRunnable = null; + } + if (this.getActivity() != null && this.getActivity().isChangingConfigurations()) { return; } @@ -414,7 +545,50 @@ private void processMessageRemoval() { @Override public void runResizeScript() { - resize(webView.getContentHeight()); + // Cancel any pending resize operation + if (pendingResizeRunnable != null) { + resizeHandler.removeCallbacks(pendingResizeRunnable); + } + + // Schedule a debounced resize operation + pendingResizeRunnable = new Runnable() { + @Override + public void run() { + performResizeWithValidation(); + } + }; + + resizeHandler.postDelayed(pendingResizeRunnable, RESIZE_DEBOUNCE_DELAY_MS); + } + + private void performResizeWithValidation() { + if (webView == null) { + IterableLogger.w(TAG, "WebView is null, skipping resize"); + return; + } + + float currentHeight = webView.getContentHeight(); + + // Validate content height + if (currentHeight <= 0) { + IterableLogger.w(TAG, "Invalid content height: " + currentHeight + "dp, skipping resize"); + return; + } + + // Check if height has stabilized (avoid unnecessary resizes for same height) + if (Math.abs(currentHeight - lastContentHeight) < 1.0f) { + IterableLogger.d(TAG, "Content height unchanged (" + currentHeight + "dp), skipping resize"); + return; + } + + lastContentHeight = currentHeight; + + IterableLogger.d( + TAG, + "💚 Resizing in-app to height: " + currentHeight + "dp" + ); + + resize(currentHeight); } /** @@ -462,9 +636,44 @@ public void run() { window.setLayout(webViewWidth, webViewHeight); getDialog().getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); } else { + // Resize the WebView directly with explicit size float relativeHeight = height * getResources().getDisplayMetrics().density; - RelativeLayout.LayoutParams webViewLayout = new RelativeLayout.LayoutParams(getResources().getDisplayMetrics().widthPixels, (int) relativeHeight); - webView.setLayoutParams(webViewLayout); + int newWebViewWidth = getResources().getDisplayMetrics().widthPixels; + int newWebViewHeight = (int) relativeHeight; + + // Set WebView to explicit size + RelativeLayout.LayoutParams webViewParams = new RelativeLayout.LayoutParams(newWebViewWidth, newWebViewHeight); + + // Apply positioning based on gravity + int resizeGravity = getVerticalLocation(insetPadding); + IterableLogger.d(TAG, "Resizing WebView directly - gravity: " + resizeGravity + " size: " + newWebViewWidth + "x" + newWebViewHeight + "px for inset padding: " + insetPadding); + + if (resizeGravity == Gravity.CENTER_VERTICAL) { + webViewParams.addRule(RelativeLayout.CENTER_IN_PARENT); + IterableLogger.d(TAG, "Applied CENTER_IN_PARENT to WebView"); + } else if (resizeGravity == Gravity.TOP) { + webViewParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + webViewParams.addRule(RelativeLayout.CENTER_HORIZONTAL); + IterableLogger.d(TAG, "Applied TOP alignment to WebView"); + } else if (resizeGravity == Gravity.BOTTOM) { + webViewParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); + webViewParams.addRule(RelativeLayout.CENTER_HORIZONTAL); + IterableLogger.d(TAG, "Applied BOTTOM alignment to WebView"); + } + + // Make dialog full screen to allow proper positioning + window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); + + // Apply the new layout params to WebView + webView.setLayoutParams(webViewParams); + + // Force layout updates + webView.requestLayout(); + if (webView.getParent() instanceof ViewGroup) { + ((ViewGroup) webView.getParent()).requestLayout(); + } + + IterableLogger.d(TAG, "Applied explicit size and positioning to WebView: " + newWebViewWidth + "x" + newWebViewHeight); } } catch (IllegalArgumentException e) { IterableLogger.e(TAG, "Exception while trying to resize an in-app message", e); diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableWebChromeClient.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableWebChromeClient.java index 7631bbae0..8e630ca84 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableWebChromeClient.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableWebChromeClient.java @@ -12,6 +12,9 @@ public class IterableWebChromeClient extends WebChromeClient { @Override public void onProgressChanged(WebView view, int newProgress) { - inAppHTMLNotification.runResizeScript(); + // Only trigger resize when page is fully loaded (100%) to avoid multiple rapid calls + if (newProgress == 100) { + inAppHTMLNotification.runResizeScript(); + } } }