import 'dart:async'; import 'package:flutter/foundation.dart' show debugPrint; import 'package:google_mobile_ads/google_mobile_ads.dart'; /// GDPR/UMP consent I/O abstraction (FIX 4, code review): every other file /// depends on this port, never on the `google_mobile_ads` UMP classes /// (`ConsentInformation`, `ConsentForm`) directly — matches /// `PuertoCompras`'s injection shape, and keeps this testable with zero /// AdMob/UMP platform channels in unit tests. abstract class PuertoConsentimiento { /// Requests consent info, loads-and-shows the consent form if required, /// and resolves whether ads may be requested afterwards /// (`ConsentInformation.canRequestAds()`). Implementations must NEVER /// throw — any underlying failure degrades to `false` (no ads served), /// never crashes or blocks the caller. Future resolver(); } /// The SOLE UMP call site (FIX 4) — every other file depends on /// [PuertoConsentimiento] instead. class ServicioConsentimientoUmp implements PuertoConsentimiento { ServicioConsentimientoUmp({ ConsentRequestParameters? parametros, Duration? timeoutActualizacion, }) : _parametros = parametros ?? ConsentRequestParameters(), _timeoutActualizacion = timeoutActualizacion ?? const Duration(seconds: 10); final ConsentRequestParameters _parametros; final Duration _timeoutActualizacion; @override Future resolver() async { try { // 1. Request an up-to-date consent status. FIX 2's lesson applies // here too: bound the callback-based wait so a callback that never // fires cannot hang startup. final actualizacionCompleter = Completer(); ConsentInformation.instance.requestConsentInfoUpdate( _parametros, () { if (!actualizacionCompleter.isCompleted) { actualizacionCompleter.complete(); } }, (error) { debugPrint( '[PluriWave][consentimiento] requestConsentInfoUpdate ERROR ' '${error.message}', ); if (!actualizacionCompleter.isCompleted) { actualizacionCompleter.complete(); } }, ); await actualizacionCompleter.future.timeout( _timeoutActualizacion, onTimeout: () {}, ); // 2. Load-and-show the consent form ONLY IF the UMP SDK itself // determines it is required (EEA/UK traffic, no prior valid // consent) — this single call is a no-op everywhere else. await ConsentForm.loadAndShowConsentFormIfRequired((formError) { if (formError != null) { debugPrint( '[PluriWave][consentimiento] ' 'loadAndShowConsentFormIfRequired ERROR ${formError.message}', ); } }); // 3. The only gate that matters for the caller: may ads be // requested at all right now? return await ConsentInformation.instance.canRequestAds(); } catch (e) { debugPrint('[PluriWave][consentimiento] ERROR $e'); return false; } } } /// Orchestrates the whole gate (FIX 4): premium users NEVER see a consent /// form at all — they get zero ads regardless of consent — so /// [PuertoConsentimiento] is never even touched for them. Free-tier users /// get the real flow, with any failure degrading silently to "ads not /// allowed" rather than crashing or blocking `main()`. Future resolverConsentimientoAnuncios({ required bool esPremium, required PuertoConsentimiento consentimiento, }) async { if (esPremium) return false; try { return await consentimiento.resolver(); } catch (e) { // Defense in depth: [PuertoConsentimiento.resolver] is documented to // never throw, but a caller-provided implementation (fake or future // adapter) failing to honor that contract still may not crash or block // `main()`. debugPrint( '[PluriWave][consentimiento] resolverConsentimientoAnuncios ERROR $e', ); return false; } }