Temporary setup of alternative to advertisements

This commit is contained in:
Courtney Arnold 2026-09-19 20:45:59 -05:00
parent ad2d932842
commit 57a9793a40
14 changed files with 653 additions and 56 deletions

View file

@ -3,6 +3,9 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import '../widgets/ad_placeholder_screen.dart';
import 'ad_config.dart';
/// This app has three ad placements:
///
/// 1. A rewarded ad gating the *upload* phase of a cloud sync (see
@ -42,6 +45,13 @@ import 'package:google_mobile_ads/google_mobile_ads.dart';
/// post-task transitions, which is exactly what plain interstitials are
/// for. Neither ever blocks or gates anything, unlike placement 1.
///
/// All three are gated behind [adsEnabled] (lib/services/ad_config.dart).
/// While that's false, every method here short-circuits to
/// [showAdPlaceholder] and the Mobile Ads SDK is never touched — no
/// requests, no failures — while the *decisions* about when each placement
/// fires stay exactly as they'd be with real ads, including the sync gate
/// still withholding uploads until the user acts.
///
/// This class only owns ad-format *mechanics* — load, preload, show — for
/// all three. Deciding *when* each is actually due (grace periods for a
/// new user, earned watch-credits, the ad-free purchase) is [AppState]'s
@ -115,6 +125,7 @@ class AdService {
/// than unconditionally at app startup, so a user who never connects
/// cloud sync never triggers any ad-related network activity at all.
Future<void> initialize() async {
if (!adsEnabled) return;
final alreadyInitialized = _sdkInitialized;
await _ensureSdkInitialized();
if (!alreadyInitialized) unawaited(_preload());
@ -161,6 +172,16 @@ class AdService {
Future<AdGateResult> showGateAd() async {
if (_gateOpen) return AdGateResult.alreadyOpen;
// Stand-in for the rewarded ad: the upload still waits on the user
// actively dismissing something, and still opens the same
// [gateValidity] window afterward, so the gate's behavior is unchanged
// apart from what's on screen. See lib/services/ad_config.dart.
if (!adsEnabled) {
await showAdPlaceholder(mustConfirm: true);
_lastShownAt = DateTime.now();
return AdGateResult.justShown;
}
if (_preloadedAd == null) {
await _loadCompleter?.future.timeout(const Duration(seconds: 4), onTimeout: () {});
}
@ -205,6 +226,7 @@ class AdService {
/// ad is ready by the time the user shares/prints without delaying the
/// export itself.
Future<void> preloadReportAd() async {
if (!adsEnabled) return;
await _ensureSdkInitialized();
unawaited(_preloadPlainInterstitial(
adUnitId: _reportInterstitialAdUnitId,
@ -224,6 +246,11 @@ class AdService {
/// at all). Returns whether an ad actually showed, so the caller knows
/// whether to treat its credit as earned.
Future<bool> maybeShowReportAd() async {
if (!adsEnabled) {
await showAdPlaceholder(mustConfirm: false);
return true;
}
final ad = _preloadedReportAd;
_preloadedReportAd = null;
final shown = await _showPlainInterstitial(ad, logLabel: 'Report interstitial');
@ -238,6 +265,7 @@ class AdService {
/// Same shape as [preloadReportAd], for the fuel-save interstitial —
/// [AppState] calls this once the confirm-entry screen is first built.
Future<void> preloadFuelSaveAd() async {
if (!adsEnabled) return;
await _ensureSdkInitialized();
unawaited(_preloadPlainInterstitial(
adUnitId: _fuelSaveInterstitialAdUnitId,
@ -249,6 +277,11 @@ class AdService {
/// Same shape and caveats as [maybeShowReportAd], for the fuel-save
/// interstitial.
Future<bool> maybeShowFuelSaveAd() async {
if (!adsEnabled) {
await showAdPlaceholder(mustConfirm: false);
return true;
}
final ad = _preloadedFuelSaveAd;
_preloadedFuelSaveAd = null;
final shown = await _showPlainInterstitial(ad, logLabel: 'Fuel-save interstitial');