Updates to add more ads and fix ad types.

This commit is contained in:
Courtney Arnold 2026-08-31 20:17:22 -05:00
parent ad850d2be6
commit ad2d932842
17 changed files with 552 additions and 59 deletions

View file

@ -54,6 +54,8 @@ class _ConfirmFuelEntryScreenState extends State<ConfirmFuelEntryScreen> {
_totalController = TextEditingController(
text: widget.parsed.totalCost?.toStringAsFixed(2) ?? '',
);
// So an ad is ready by the time _save() finishes without delaying it.
context.read<AppState>().preloadFuelSaveAd();
}
@override

View file

@ -92,6 +92,25 @@ class _DataSettingsScreenState extends State<DataSettingsScreen> {
}
}
/// The resting (non-syncing) Sync Now icon — badged with the same
/// play-triangle [CloudBackupActionButton] uses, for anyone who isn't on
/// an active ad-free purchase, since tapping this can trigger the
/// rewarded ad gate (see [AppState.syncNow]/`AdService.showGateAd`). Not
/// a live prediction of whether *this* tap specifically will show one —
/// same general "this leads to an ad-supported feature" disclosure the
/// icon-badge uses elsewhere, not an attempt to account for the open
/// gate window or the free-first-sync case.
Widget _syncNowIcon(BuildContext context, AppState appState) {
const icon = Icon(Icons.sync);
if (appState.adsCurrentlyDisabled) return icon;
final colors = Theme.of(context).colorScheme;
return Badge(
backgroundColor: colors.tertiary,
label: Icon(Icons.play_arrow, size: 8, color: colors.onTertiary),
child: icon,
);
}
/// Shared warning-dialog shell for both purge actions — [message] is the
/// caller's job to make specific and unambiguous, since this is the only
/// thing standing between the user and an unrecoverable delete.
@ -283,7 +302,7 @@ class _DataSettingsScreenState extends State<DataSettingsScreen> {
height: 16,
width: 16,
child: CircularProgressIndicator(strokeWidth: 2))
: const Icon(Icons.sync),
: _syncNowIcon(context, appState),
label: const Text('Sync Now'),
),
TextButton.icon(

View file

@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
import '../models/vehicle.dart';
import '../services/app_state.dart';
import '../services/onboarding_keys.dart';
import '../widgets/cloud_backup_action_button.dart';
import 'add_edit_vehicle_screen.dart';
import 'faq_screen.dart';
import 'vehicle_detail_screen.dart';
@ -31,6 +32,7 @@ class HomeScreen extends StatelessWidget {
MaterialPageRoute(builder: (_) => const AddEditVehicleScreen()),
),
),
const CloudBackupActionButton(),
const FaqActionButton(),
],
),

View file

@ -8,6 +8,7 @@ import '../services/app_state.dart';
import '../services/estimated_refund.dart';
import '../services/onboarding_keys.dart';
import '../theme/app_theme.dart';
import '../widgets/cloud_backup_action_button.dart';
import '../widgets/hero_banner.dart';
import '../widgets/receipt_capture.dart';
import 'add_edit_vehicle_screen.dart';
@ -144,6 +145,7 @@ class _ReceiptsScreenState extends State<ReceiptsScreen> with AutomaticKeepAlive
tooltip: 'Log Fuel Receipt',
onPressed: () => _addReceipt(context),
),
const CloudBackupActionButton(),
const FaqActionButton(),
],
),

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart';
@ -12,6 +13,7 @@ import '../services/fuel_report.dart';
import '../services/fuel_report_images.dart';
import '../services/fuel_report_pdf.dart';
import '../services/onboarding_keys.dart';
import '../widgets/cloud_backup_action_button.dart';
import 'faq_screen.dart';
/// Missouri's Motor Fuel Refund Claim form isn't something this app can
@ -59,6 +61,7 @@ class _ReportScreenState extends State<ReportScreen> with AutomaticKeepAliveClie
final (start, end) = defaultReportDateRange(DateTime.now());
_startDate = start;
_endDate = end;
context.read<AppState>().preloadReportAd();
}
Future<void> _pickDate({required bool isStart}) async {
@ -118,6 +121,7 @@ class _ReportScreenState extends State<ReportScreen> with AutomaticKeepAliveClie
try {
final bytes = await _buildPdfBytes(report);
await Printing.sharePdf(bytes: bytes, filename: fuelReportFileName(report));
if (mounted) unawaited(context.read<AppState>().maybeShowReportAd());
} finally {
if (mounted) setState(() => _busy = false);
}
@ -128,6 +132,7 @@ class _ReportScreenState extends State<ReportScreen> with AutomaticKeepAliveClie
try {
final bytes = await _buildPdfBytes(report);
await Printing.layoutPdf(onLayout: (_) async => bytes, name: fuelReportFileName(report));
if (mounted) unawaited(context.read<AppState>().maybeShowReportAd());
} finally {
if (mounted) setState(() => _busy = false);
}
@ -194,7 +199,7 @@ class _ReportScreenState extends State<ReportScreen> with AutomaticKeepAliveClie
return Scaffold(
appBar: AppBar(
title: const Text('Fuel Report'),
actions: const [FaqActionButton()],
actions: const [CloudBackupActionButton(), FaqActionButton()],
),
body: ListView(
padding: const EdgeInsets.all(16),

View file

@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
import '../services/app_state.dart';
import '../services/onboarding_keys.dart';
import '../widgets/cloud_backup_action_button.dart';
import 'data_settings_screen.dart';
import 'faq_screen.dart';
import 'ui_settings_screen.dart';
@ -22,7 +23,7 @@ class SettingsScreen extends StatelessWidget {
return Scaffold(
appBar: AppBar(
title: const Text('Settings'),
actions: const [FaqActionButton()],
actions: const [CloudBackupActionButton(), FaqActionButton()],
),
body: ListView(
padding: const EdgeInsets.all(16),

View file

@ -61,7 +61,12 @@ class UserAgreementScreen extends StatelessWidget {
'this device. If you connect a cloud storage account (Google Drive, '
'Dropbox, OneDrive, or your own WebDAV server) in Settings, a copy is '
'also kept there — in storage you control, not on any server we run. '
"We don't operate a backend, and we don't have a copy of your data.",
"We don't operate a backend, and we don't have a copy of your data.\n\n"
"This is also how sharing works: if the folder you connect to is one "
"you share with someone else, anyone else who connects the app to "
"that same folder sees and can edit the same vehicles, fuel entries, "
"and receipt photos you do. Data in a shared folder isn't private to "
"just you.",
),
_Section(
title: 'Privacy',