import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import '../services/app_state.dart'; import 'faq_screen.dart'; /// The very first thing shown on a fresh install — before the onboarding /// tour, before anything else — gating [MainShell] entirely until the user /// taps "I Agree" (see [AppRoot] in main.dart and /// [AppState.hasAcceptedUserAgreement]/[AppState.acceptUserAgreement]). /// Its whole purpose is the liability section below: this app stores data /// on the user's own device (and, optionally, their own cloud storage /// account) with no server or backend of ours involved, so we have no /// ability to recover anything for them if it's lost — plus the privacy /// section, disclosing that we don't track/log anything ourselves, and /// that AdMob (only active for users on the free, ad-supported tier) is /// the sole source of any data collection, governed by Google's own /// practices rather than anything this app adds. class UserAgreementScreen extends StatelessWidget { /// True when reached from Settings (see [SettingsScreen]) to re-read the /// agreement/privacy policy at any time — required so this content stays /// reachable in the app beyond the one-time first-launch gate, not just /// something a user saw once and can never get back to. Hides the /// Decline/I Agree actions (already answered, and "Decline" quitting the /// app would be a bizarre side effect of just re-reading this) and shows /// a normal back button instead of gating navigation away entirely. final bool isReview; const UserAgreementScreen({super.key, this.isReview = false}); @override Widget build(BuildContext context) { final textTheme = Theme.of(context).textTheme; return Scaffold( appBar: AppBar( title: const Text('User Agreement'), automaticallyImplyLeading: isReview, ), body: SafeArea( child: Column( children: [ Expanded( child: ListView( padding: const EdgeInsets.all(20), children: [ Text( isReview ? 'This is the agreement you accepted when you first opened Show Me ' 'The Fuel Refund.' : 'Please read and accept the following before using Show Me The Fuel ' 'Refund.', style: textTheme.bodyLarge, ), const SizedBox(height: 20), _Section( title: 'Your Data Lives on Your Device', body: 'Every vehicle, fuel receipt, and photo you log is stored locally on ' '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.\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', body: "We don't track or log your data in any way — there's no analytics, no " "crash reporting, and no server of ours receiving anything you enter. If " 'you haven\'t purchased "Remove Ads for a Year" and are using the free, ' 'ad-supported version, the only data collected is whatever Google AdMob ' 'itself requires to serve ads (e.g. a device advertising identifier) — ' "that collection is Google's, governed by its own privacy practices, not " 'something this app adds on top of.', ), _Section( title: 'We Are Not Responsible for Your Data or Any Data Loss', body: 'The developer is not responsible for your data, or for any loss, ' 'corruption, or unavailability of it, for any reason — including but ' 'not limited to device loss, theft, or damage; an uninstall or app/OS ' 'update; a bug in this app; or an outage or account issue with a cloud ' 'storage provider you connect. Backing up your data (by connecting a ' 'cloud storage account, and by keeping it connected and in good ' 'standing) is entirely your responsibility. If you never connect a ' 'backup, the only copy of your data is the one on this device.', ), _Section( title: 'Provided As-Is', body: 'This app is provided "as is," without warranty of any kind, express or ' 'implied. Fuel and gallon values are read automatically from your ' 'receipt photos and may be misread — always review them before relying ' 'on them, including for a refund claim.', ), _Section( title: 'Not Tax or Legal Advice', body: 'This app is purely informational. Missouri actually offers two ' 'separate fuel tax refunds — up to 12.5¢ per gallon for highway use, ' "and up to 29.5¢ per gallon for non-highway use — and it's worth " "evaluating both to see how you could benefit. This app helps you " "organize the receipts you'd need for either one.\n\n" "Nothing here is tax or legal advice, and using this app doesn't " 'guarantee your eligibility for either refund. For guidance on your ' 'specific situation, consult a tax professional. Both refunds are set ' 'by Missouri law, which can change at any time — always confirm ' 'current eligibility and rates with the Missouri Department of ' 'Revenue before filing.', ), Align( alignment: Alignment.centerLeft, child: TextButton.icon( onPressed: () => Navigator.of(context).push( MaterialPageRoute(builder: (_) => const FaqScreen()), ), icon: const Icon(Icons.help_outline), label: const Text('See the FAQ for more'), ), ), const SizedBox(height: 8), ], ), ), if (!isReview) ...[ const Divider(height: 1), Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Expanded( child: OutlinedButton( onPressed: () => SystemNavigator.pop(), child: const Text('Decline'), ), ), const SizedBox(width: 12), Expanded( child: FilledButton( onPressed: () => context.read().acceptUserAgreement(), child: const Text('I Agree'), ), ), ], ), ), ], ], ), ), ); } } class _Section extends StatelessWidget { final String title; final String body; const _Section({required this.title, required this.body}); @override Widget build(BuildContext context) { final textTheme = Theme.of(context).textTheme; return Padding( padding: const EdgeInsets.only(bottom: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700)), const SizedBox(height: 6), Text(body, style: textTheme.bodyMedium), ], ), ); } }