2626import android .view .WindowManager ;
2727import android .view .animation .Animation ;
2828import android .view .animation .AnimationUtils ;
29+ import android .widget .FrameLayout ;
2930import android .widget .RelativeLayout ;
3031
3132import androidx .annotation .NonNull ;
@@ -58,6 +59,12 @@ public class IterableInAppFragmentHTMLNotification extends DialogFragment implem
5859 private boolean callbackOnCancel = false ;
5960 private String htmlString ;
6061 private String messageId ;
62+
63+ // Resize debouncing fields
64+ private Handler resizeHandler = new Handler ();
65+ private Runnable pendingResizeRunnable ;
66+ private float lastContentHeight = -1 ;
67+ private static final int RESIZE_DEBOUNCE_DELAY_MS = 200 ;
6168
6269 private double backgroundAlpha ; //TODO: remove in a future version
6370 private Rect insetPadding ;
@@ -105,6 +112,35 @@ public IterableInAppFragmentHTMLNotification() {
105112 insetPadding = new Rect ();
106113 this .setStyle (DialogFragment .STYLE_NO_FRAME , androidx .appcompat .R .style .Theme_AppCompat_NoActionBar );
107114 }
115+
116+ @ Override
117+ public void onStart () {
118+ super .onStart ();
119+
120+ // Set dialog positioning after the dialog is created and shown
121+ Dialog dialog = getDialog ();
122+ if (dialog != null ) {
123+ Window window = dialog .getWindow ();
124+ if (window != null ) {
125+ WindowManager .LayoutParams windowParams = window .getAttributes ();
126+ int startGravity = getVerticalLocation (insetPadding );
127+
128+ if (startGravity == Gravity .CENTER_VERTICAL ) {
129+ windowParams .gravity = Gravity .CENTER ;
130+ IterableLogger .d (TAG , "Set dialog gravity to CENTER in onStart" );
131+ } else if (startGravity == Gravity .TOP ) {
132+ windowParams .gravity = Gravity .TOP | Gravity .CENTER_HORIZONTAL ;
133+ IterableLogger .d (TAG , "Set dialog gravity to TOP in onStart" );
134+ } else if (startGravity == Gravity .BOTTOM ) {
135+ windowParams .gravity = Gravity .BOTTOM | Gravity .CENTER_HORIZONTAL ;
136+ IterableLogger .d (TAG , "Set dialog gravity to BOTTOM in onStart" );
137+ }
138+
139+ window .setAttributes (windowParams );
140+ IterableLogger .d (TAG , "Applied window gravity in onStart: " + windowParams .gravity );
141+ }
142+ }
143+ }
108144
109145 @ Override
110146 public void onCreate (@ Nullable Bundle savedInstanceState ) {
@@ -144,6 +180,25 @@ public void onCancel(DialogInterface dialog) {
144180 }
145181 });
146182 dialog .requestWindowFeature (Window .FEATURE_NO_TITLE );
183+
184+ // Set window gravity for the dialog
185+ Window window = dialog .getWindow ();
186+ WindowManager .LayoutParams windowParams = window .getAttributes ();
187+ int dialogGravity = getVerticalLocation (insetPadding );
188+
189+ if (dialogGravity == Gravity .CENTER_VERTICAL ) {
190+ windowParams .gravity = Gravity .CENTER ;
191+ IterableLogger .d (TAG , "Set dialog gravity to CENTER in onCreateDialog" );
192+ } else if (dialogGravity == Gravity .TOP ) {
193+ windowParams .gravity = Gravity .TOP | Gravity .CENTER_HORIZONTAL ;
194+ IterableLogger .d (TAG , "Set dialog gravity to TOP in onCreateDialog" );
195+ } else if (dialogGravity == Gravity .BOTTOM ) {
196+ windowParams .gravity = Gravity .BOTTOM | Gravity .CENTER_HORIZONTAL ;
197+ IterableLogger .d (TAG , "Set dialog gravity to BOTTOM in onCreateDialog" );
198+ }
199+
200+ window .setAttributes (windowParams );
201+
147202 if (getInAppLayout (insetPadding ) == InAppLayout .FULLSCREEN ) {
148203 dialog .getWindow ().setFlags (WindowManager .LayoutParams .FLAG_FULLSCREEN , WindowManager .LayoutParams .FLAG_FULLSCREEN );
149204 } else if (getInAppLayout (insetPadding ) != InAppLayout .TOP ) {
@@ -162,41 +217,111 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
162217 if (getInAppLayout (insetPadding ) == InAppLayout .FULLSCREEN ) {
163218 getDialog ().getWindow ().setFlags (WindowManager .LayoutParams .FLAG_FULLSCREEN , WindowManager .LayoutParams .FLAG_FULLSCREEN );
164219 }
220+
221+ // Set initial window gravity based on inset padding
222+ Window window = getDialog ().getWindow ();
223+ WindowManager .LayoutParams windowParams = window .getAttributes ();
224+ int windowGravity = getVerticalLocation (insetPadding );
225+
226+ if (windowGravity == Gravity .CENTER_VERTICAL ) {
227+ windowParams .gravity = Gravity .CENTER ;
228+ IterableLogger .d (TAG , "Set initial CENTER window gravity in onCreateView" );
229+ } else if (windowGravity == Gravity .TOP ) {
230+ windowParams .gravity = Gravity .TOP | Gravity .CENTER_HORIZONTAL ;
231+ IterableLogger .d (TAG , "Set initial TOP window gravity in onCreateView" );
232+ } else if (windowGravity == Gravity .BOTTOM ) {
233+ windowParams .gravity = Gravity .BOTTOM | Gravity .CENTER_HORIZONTAL ;
234+ IterableLogger .d (TAG , "Set initial BOTTOM window gravity in onCreateView" );
235+ }
236+
237+ window .setAttributes (windowParams );
165238
166239 webView = new IterableWebView (getContext ());
167240 webView .setId (R .id .webView );
241+
242+ // Debug the HTML content
243+ IterableLogger .d (TAG , "HTML content preview: " + (htmlString .length () > 200 ? htmlString .substring (0 , 200 ) + "..." : htmlString ));
244+
168245 webView .createWithHtml (this , htmlString );
169246
170247 if (orientationListener == null ) {
171248 orientationListener = new OrientationEventListener (getContext (), SensorManager .SENSOR_DELAY_NORMAL ) {
249+ private int lastOrientation = -1 ;
250+
172251 // Resize the webView on device rotation
173252 public void onOrientationChanged (int orientation ) {
174- if (loaded ) {
175- final Handler handler = new Handler ();
176- handler .postDelayed (new Runnable () {
177- @ Override
178- public void run () {
179- runResizeScript ();
180- }
181- }, 1000 );
253+ if (loaded && webView != null ) {
254+ // Only trigger on significant orientation changes (90 degree increments)
255+ int currentOrientation = ((orientation + 45 ) / 90 ) * 90 ;
256+ if (currentOrientation != lastOrientation && lastOrientation != -1 ) {
257+ lastOrientation = currentOrientation ;
258+
259+ // Use longer delay for orientation changes to allow layout to stabilize
260+ final Handler handler = new Handler ();
261+ handler .postDelayed (new Runnable () {
262+ @ Override
263+ public void run () {
264+ IterableLogger .d (TAG , "Orientation changed, triggering resize" );
265+ runResizeScript ();
266+ }
267+ }, 1500 ); // Increased delay for better stability
268+ } else if (lastOrientation == -1 ) {
269+ lastOrientation = currentOrientation ;
270+ }
182271 }
183272 }
184273 };
185274 }
186275
187276 orientationListener .enable ();
188277
189- RelativeLayout relativeLayout = new RelativeLayout (this .getContext ());
190- RelativeLayout .LayoutParams layoutParams = new RelativeLayout .LayoutParams (RelativeLayout .LayoutParams .MATCH_PARENT , RelativeLayout .LayoutParams .MATCH_PARENT );
191- relativeLayout .setVerticalGravity (getVerticalLocation (insetPadding ));
192- relativeLayout .addView (webView , layoutParams );
278+ // Create a FrameLayout as the main container for better positioning control
279+ FrameLayout frameLayout = new FrameLayout (this .getContext ());
280+
281+ // Create a RelativeLayout as a wrapper for the WebView
282+ RelativeLayout webViewContainer = new RelativeLayout (this .getContext ());
283+
284+ int gravity = getVerticalLocation (insetPadding );
285+ IterableLogger .d (TAG , "Initial setup - gravity: " + gravity + " for inset padding: " + insetPadding );
286+
287+ // Set FrameLayout gravity based on positioning
288+ FrameLayout .LayoutParams containerParams = new FrameLayout .LayoutParams (
289+ FrameLayout .LayoutParams .MATCH_PARENT ,
290+ FrameLayout .LayoutParams .WRAP_CONTENT
291+ );
292+
293+ if (gravity == Gravity .CENTER_VERTICAL ) {
294+ containerParams .gravity = Gravity .CENTER ;
295+ IterableLogger .d (TAG , "Applied CENTER gravity to container" );
296+ } else if (gravity == Gravity .TOP ) {
297+ containerParams .gravity = Gravity .TOP | Gravity .CENTER_HORIZONTAL ;
298+ IterableLogger .d (TAG , "Applied TOP gravity to container" );
299+ } else if (gravity == Gravity .BOTTOM ) {
300+ containerParams .gravity = Gravity .BOTTOM | Gravity .CENTER_HORIZONTAL ;
301+ IterableLogger .d (TAG , "Applied BOTTOM gravity to container" );
302+ }
303+
304+ // Add WebView to the RelativeLayout container with WRAP_CONTENT for proper sizing
305+ RelativeLayout .LayoutParams webViewParams = new RelativeLayout .LayoutParams (
306+ RelativeLayout .LayoutParams .WRAP_CONTENT ,
307+ RelativeLayout .LayoutParams .WRAP_CONTENT
308+ );
309+ webViewParams .addRule (RelativeLayout .CENTER_IN_PARENT );
310+ webViewContainer .addView (webView , webViewParams );
311+
312+ IterableLogger .d (TAG , "Added WebView with WRAP_CONTENT and CENTER_IN_PARENT rule" );
313+
314+ // Add the container to the FrameLayout
315+ frameLayout .addView (webViewContainer , containerParams );
316+
317+ IterableLogger .d (TAG , "Created FrameLayout with positioned RelativeLayout container" );
193318
194319 if (savedInstanceState == null || !savedInstanceState .getBoolean (IN_APP_OPEN_TRACKED , false )) {
195320 IterableApi .sharedInstance .trackInAppOpen (messageId , location );
196321 }
197322
198323 prepareToShowWebView ();
199- return relativeLayout ;
324+ return frameLayout ;
200325 }
201326
202327 public void setLoaded (boolean loaded ) {
@@ -226,6 +351,12 @@ public void onStop() {
226351 public void onDestroy () {
227352 super .onDestroy ();
228353
354+ // Clean up pending resize operations
355+ if (resizeHandler != null && pendingResizeRunnable != null ) {
356+ resizeHandler .removeCallbacks (pendingResizeRunnable );
357+ pendingResizeRunnable = null ;
358+ }
359+
229360 if (this .getActivity () != null && this .getActivity ().isChangingConfigurations ()) {
230361 return ;
231362 }
@@ -414,7 +545,50 @@ private void processMessageRemoval() {
414545
415546 @ Override
416547 public void runResizeScript () {
417- resize (webView .getContentHeight ());
548+ // Cancel any pending resize operation
549+ if (pendingResizeRunnable != null ) {
550+ resizeHandler .removeCallbacks (pendingResizeRunnable );
551+ }
552+
553+ // Schedule a debounced resize operation
554+ pendingResizeRunnable = new Runnable () {
555+ @ Override
556+ public void run () {
557+ performResizeWithValidation ();
558+ }
559+ };
560+
561+ resizeHandler .postDelayed (pendingResizeRunnable , RESIZE_DEBOUNCE_DELAY_MS );
562+ }
563+
564+ private void performResizeWithValidation () {
565+ if (webView == null ) {
566+ IterableLogger .w (TAG , "WebView is null, skipping resize" );
567+ return ;
568+ }
569+
570+ float currentHeight = webView .getContentHeight ();
571+
572+ // Validate content height
573+ if (currentHeight <= 0 ) {
574+ IterableLogger .w (TAG , "Invalid content height: " + currentHeight + "dp, skipping resize" );
575+ return ;
576+ }
577+
578+ // Check if height has stabilized (avoid unnecessary resizes for same height)
579+ if (Math .abs (currentHeight - lastContentHeight ) < 1.0f ) {
580+ IterableLogger .d (TAG , "Content height unchanged (" + currentHeight + "dp), skipping resize" );
581+ return ;
582+ }
583+
584+ lastContentHeight = currentHeight ;
585+
586+ IterableLogger .d (
587+ TAG ,
588+ "💚 Resizing in-app to height: " + currentHeight + "dp"
589+ );
590+
591+ resize (currentHeight );
418592 }
419593
420594 /**
@@ -462,9 +636,44 @@ public void run() {
462636 window .setLayout (webViewWidth , webViewHeight );
463637 getDialog ().getWindow ().setFlags (WindowManager .LayoutParams .FLAG_FULLSCREEN , WindowManager .LayoutParams .FLAG_FULLSCREEN );
464638 } else {
639+ // Resize the WebView directly with explicit size
465640 float relativeHeight = height * getResources ().getDisplayMetrics ().density ;
466- RelativeLayout .LayoutParams webViewLayout = new RelativeLayout .LayoutParams (getResources ().getDisplayMetrics ().widthPixels , (int ) relativeHeight );
467- webView .setLayoutParams (webViewLayout );
641+ int newWebViewWidth = getResources ().getDisplayMetrics ().widthPixels ;
642+ int newWebViewHeight = (int ) relativeHeight ;
643+
644+ // Set WebView to explicit size
645+ RelativeLayout .LayoutParams webViewParams = new RelativeLayout .LayoutParams (newWebViewWidth , newWebViewHeight );
646+
647+ // Apply positioning based on gravity
648+ int resizeGravity = getVerticalLocation (insetPadding );
649+ IterableLogger .d (TAG , "Resizing WebView directly - gravity: " + resizeGravity + " size: " + newWebViewWidth + "x" + newWebViewHeight + "px for inset padding: " + insetPadding );
650+
651+ if (resizeGravity == Gravity .CENTER_VERTICAL ) {
652+ webViewParams .addRule (RelativeLayout .CENTER_IN_PARENT );
653+ IterableLogger .d (TAG , "Applied CENTER_IN_PARENT to WebView" );
654+ } else if (resizeGravity == Gravity .TOP ) {
655+ webViewParams .addRule (RelativeLayout .ALIGN_PARENT_TOP );
656+ webViewParams .addRule (RelativeLayout .CENTER_HORIZONTAL );
657+ IterableLogger .d (TAG , "Applied TOP alignment to WebView" );
658+ } else if (resizeGravity == Gravity .BOTTOM ) {
659+ webViewParams .addRule (RelativeLayout .ALIGN_PARENT_BOTTOM );
660+ webViewParams .addRule (RelativeLayout .CENTER_HORIZONTAL );
661+ IterableLogger .d (TAG , "Applied BOTTOM alignment to WebView" );
662+ }
663+
664+ // Make dialog full screen to allow proper positioning
665+ window .setLayout (WindowManager .LayoutParams .MATCH_PARENT , WindowManager .LayoutParams .MATCH_PARENT );
666+
667+ // Apply the new layout params to WebView
668+ webView .setLayoutParams (webViewParams );
669+
670+ // Force layout updates
671+ webView .requestLayout ();
672+ if (webView .getParent () instanceof ViewGroup ) {
673+ ((ViewGroup ) webView .getParent ()).requestLayout ();
674+ }
675+
676+ IterableLogger .d (TAG , "Applied explicit size and positioning to WebView: " + newWebViewWidth + "x" + newWebViewHeight );
468677 }
469678 } catch (IllegalArgumentException e ) {
470679 IterableLogger .e (TAG , "Exception while trying to resize an in-app message" , e );
0 commit comments