@@ -213,27 +213,11 @@ class _MainScreenState extends ConsumerState<MainScreen> {
213213 onSecretTap: _handleSecretTap,
214214 onPingRefresh: _logic.refreshPing,
215215 ),
216- SizedBox (
217- height: connectionState.status ==
218- ConnectionStatus .connected
219- ? 40. h
220- : 80. h,
221- ),
222- SizedBox (
223- height: connectionState.status ==
224- ConnectionStatus .connected
225- ? 0.24 .sh
226- : 0.28 .sh,
227- ),
216+ SizedBox (height: 50. h), // Reduced to raise ads higher
217+ SizedBox (height: 0.16 .sh), // Reduced to raise ads higher
228218 _buildContentSection (
229219 connectionState.status, adsState),
230- SizedBox (
231- height: connectionState.status ==
232- ConnectionStatus .connected &&
233- adsState.showCountdown
234- ? 140. h
235- : 0.15 .sh,
236- ),
220+ SizedBox (height: 0.15 .sh), // Consistent bottom spacing
237221 ],
238222 ),
239223 ],
@@ -275,58 +259,89 @@ class _MainScreenState extends ConsumerState<MainScreen> {
275259 }
276260
277261 Widget _buildContentSection (ConnectionStatus status, dynamic adsState) {
262+ debugPrint ('🎨 _buildContentSection called:' );
263+ debugPrint (' Status: ${status .name }' );
264+ debugPrint (' nativeAdIsLoaded: ${adsState .nativeAdIsLoaded }' );
265+ debugPrint (' showCountdown: ${adsState .showCountdown }' );
266+ debugPrint (' adLoadFailed: ${adsState .adLoadFailed }' );
267+
268+ // Determine if we should show ads based on state
269+ bool shouldShowAd = false ;
270+ Widget ? mainContent;
271+
278272 switch (status) {
279273 case ConnectionStatus .noInternet:
280274 _dinoGame ?? = DinoGame ();
281- return SizedBox (
275+ mainContent = SizedBox (
282276 height: 200. h,
283277 child: ClipRRect (
284278 borderRadius: BorderRadius .circular (16. r),
285279 child: GameWidget (game: _dinoGame! ),
286280 ),
287281 );
282+ break ;
288283
289284 case ConnectionStatus .disconnected:
290- return TipsSliderSection (
291- status: status,
292- );
285+ // DISCONNECTED: Show AdMob ad (after VPN use) if available, else show tips
286+ final shouldShowAdMobAd = adsState.showCountdown &&
287+ adsState.nativeAdIsLoaded;
288+
289+ debugPrint (' 🎯 DISCONNECTED: shouldShowAdMobAd=$shouldShowAdMobAd ' );
290+
291+ if (shouldShowAdMobAd) {
292+ debugPrint (' ✅ Rendering AdMob ad' );
293+ shouldShowAd = true ;
294+ } else {
295+ debugPrint (' ℹ️ Rendering tips slider' );
296+ mainContent = TipsSliderSection (status: status);
297+ }
298+ break ;
293299
294300 default :
295- // MainScreen controls ad visibility (Strategy Pattern approach)
296- // Works for both GoogleAdStrategy and InternalAdStrategy
297- // - nativeAdIsLoaded: true when ad is ready (Google or Internal)
298- // - showCountdown: true during 60-second display window
299- // - connected: only show when VPN is active
300- final shouldShowAd = status == ConnectionStatus .connected &&
301- adsState.showCountdown &&
302- adsState.nativeAdIsLoaded;
303-
304- return SizedBox (
305- height: 280. h,
306- width: 336. w,
307- child: AnimatedSlide (
308- offset: Offset (
309- 0 ,
310- shouldShowAd ? 0.0 : 1.0 ,
311- ),
312- duration: _animationService
313- .adjustDuration (const Duration (milliseconds: 800 )),
314- curve: Curves .easeOut,
315- child: AnimatedOpacity (
316- opacity: shouldShowAd ? 1.0 : 0.0 ,
317- duration: _animationService
318- .adjustDuration (const Duration (milliseconds: 500 )),
319- curve: Curves .easeInOut,
320- child: DecoratedBox (
321- decoration: BoxDecoration (
322- color: const Color (0xFF19312F ),
323- borderRadius: BorderRadius .circular (10. r),
301+ // CONNECTED/CONNECTING: Show internal ad (during VPN use) if available
302+ final shouldShowInternalAd = adsState.showCountdown &&
303+ (adsState.customImageUrl? .isNotEmpty ?? false );
304+
305+ debugPrint (' 🎯 CONNECTED/OTHER: shouldShowInternalAd=$shouldShowInternalAd (hasCustomImage=${adsState .customImageUrl != null })' );
306+
307+ if (shouldShowInternalAd) {
308+ debugPrint (' ✅ Rendering internal ad' );
309+ shouldShowAd = true ;
310+ } else {
311+ debugPrint (' ⚪ Rendering empty (no ad)' );
312+ mainContent = const SizedBox .shrink ();
313+ }
314+ }
315+
316+ // CRITICAL: Keep AdsWidget in ONE position in the tree to prevent dispose/recreate cycles
317+ // Control visibility with Opacity instead of moving widget around
318+ return Stack (
319+ alignment: Alignment .topCenter, // Align to top-center for consistent positioning
320+ children: [
321+ // Always in tree at same position - never recreated
322+ Opacity (
323+ opacity: shouldShowAd ? 1.0 : 0.0 ,
324+ child: IgnorePointer (
325+ ignoring: ! shouldShowAd, // Prevent touch events when hidden
326+ child: Padding (
327+ padding: EdgeInsets .only (top: 50. h), // Match the spacing above ads
328+ child: SizedBox (
329+ height: 280. h,
330+ width: 336. w,
331+ child: DecoratedBox (
332+ decoration: BoxDecoration (
333+ color: const Color (0xFF19312F ),
334+ borderRadius: BorderRadius .circular (10. r),
335+ ),
336+ child: _adsWidget, // Always same widget instance, same position
324337 ),
325- child: _adsWidget,
326338 ),
327339 ),
328340 ),
329- );
330- }
341+ ),
342+ // Show main content when not showing ad
343+ if (! shouldShowAd && mainContent != null ) mainContent,
344+ ],
345+ );
331346 }
332347}
0 commit comments