import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/responsive/viewport_size.dart'; import '../../core/theme/theme_extensions.dart'; import '../../services/billing/billing_models.dart'; import '../../services/billing/billing_providers.dart'; import '../../services/billing/product_catalog.dart'; import '../../services/entitlements/entitlement_providers.dart'; import '../../services/entitlements/entitlement_state.dart'; import 'widgets/pro_lock_badge.dart'; enum ProPurchaseStep { idle, purchasing, verifying, restoring } class ProScreen extends ConsumerStatefulWidget { const ProScreen({super.key}); @override ConsumerState createState() => _ProScreenState(); } class _ProScreenState extends ConsumerState { ProPurchaseStep _currentStep = ProPurchaseStep.idle; String? _statusBanner; bool _isBannerError = false; List _products = []; bool _isLoadingProducts = true; bool _storeAvailable = true; @override void initState() { super.initState(); _loadStoreProducts(); } Future _loadStoreProducts() async { setState(() => _isLoadingProducts = true); final billing = ref.read(billingServiceProvider); final isAvail = await billing.isAvailable(); if (!isAvail) { if (mounted) { setState(() { _storeAvailable = false; _isLoadingProducts = false; }); } return; } final prods = await billing.queryProducts(); if (mounted) { setState(() { _storeAvailable = true; _products = prods; _isLoadingProducts = false; }); } } Future _handleBuy(String productId) async { final billing = ref.read(billingServiceProvider); setState(() { _currentStep = ProPurchaseStep.purchasing; _statusBanner = null; }); final launched = await billing.launchPurchaseFlow(productId); if (!launched) { if (mounted) { setState(() { _currentStep = ProPurchaseStep.idle; _statusBanner = 'Unable to launch Google Play purchase. Please try again.'; _isBannerError = true; }); } } else { if (mounted) { setState(() { _currentStep = ProPurchaseStep.idle; }); } } } Future _handleRestore() async { setState(() { _currentStep = ProPurchaseStep.restoring; _statusBanner = null; }); try { final billing = ref.read(billingServiceProvider); await billing.restorePurchases(); await Future.delayed(const Duration(seconds: 1)); if (mounted) { final entitlement = ref.read(entitlementProvider); setState(() { _currentStep = ProPurchaseStep.idle; if (entitlement.isPro) { _statusBanner = 'MotorSignal Pro restored successfully.'; _isBannerError = false; } else if (entitlement.status == MotorSignalEntitlementStatus.syncPending) { _statusBanner = 'Purchase found. Verifying entitlement...'; _isBannerError = false; } else { _statusBanner = 'No active purchases found for this Google Play account.'; _isBannerError = false; } }); } } catch (e) { if (mounted) { setState(() { _currentStep = ProPurchaseStep.idle; _statusBanner = 'Restore request failed. Please check your network connection.'; _isBannerError = true; }); } } } @override Widget build(BuildContext context) { final entitlement = ref.watch(entitlementProvider); final colors = context.motorColors; final snapshot = context.responsive; final isWide = snapshot.isMedium || snapshot.isExpanded; final monthlyProd = _products .where((p) => p.id == ProductCatalog.proMonthly) .firstOrNull; final lifetimeProd = _products .where((p) => p.id == ProductCatalog.proLifetime) .firstOrNull; final isDark = Theme.of(context).brightness == Brightness.dark; return Scaffold( backgroundColor: isDark ? const Color(0xFF0B0D0F) : Theme.of(context).colorScheme.surface, appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, title: const FittedBox( fit: BoxFit.scaleDown, child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( 'MOTORSIGNAL', style: TextStyle( fontFamily: 'Sora', fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 1.5, ), ), SizedBox(width: 6), ProBadge(), ], ), ), actions: [ Padding( padding: const EdgeInsets.only(right: 8.0), child: TextButton( style: TextButton.styleFrom( minimumSize: const Size(48, 48), padding: const EdgeInsets.symmetric(horizontal: 12), ), onPressed: _currentStep != ProPurchaseStep.idle ? null : _handleRestore, child: Text( 'RESTORE', style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: colors.cyberCyan, ), ), ), ), ], ), body: SafeArea( child: SingleChildScrollView( padding: EdgeInsets.symmetric( horizontal: isWide ? 24.0 : 20.0, vertical: 12.0, ), child: Center( child: ConstrainedBox( constraints: BoxConstraints( maxWidth: isWide ? 1100 : 640, ), child: isWide ? _buildWideLayout(entitlement, monthlyProd, lifetimeProd) : _buildCompactLayout(entitlement, monthlyProd, lifetimeProd), ), ), ), ), ); } Widget _buildWideLayout( EntitlementState entitlement, StoreProduct? monthlyProd, StoreProduct? lifetimeProd, ) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 5, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (_statusBanner != null) ...[ _buildStatusBanner(), const SizedBox(height: 16), ], if (entitlement.isPro) ...[ _buildActiveProBanner(entitlement), const SizedBox(height: 20), ], _buildPitchHeader(), const SizedBox(height: 24), _buildPlanSection(entitlement, monthlyProd, lifetimeProd), ], ), ), const SizedBox(width: 32), Expanded( flex: 6, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildBenefitsSection(), const SizedBox(height: 20), _buildDisclaimer(), ], ), ), ], ); } Widget _buildCompactLayout( EntitlementState entitlement, StoreProduct? monthlyProd, StoreProduct? lifetimeProd, ) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (_statusBanner != null) ...[ _buildStatusBanner(), const SizedBox(height: 16), ], if (entitlement.isPro) ...[ _buildActiveProBanner(entitlement), const SizedBox(height: 20), ], _buildPitchHeader(), const SizedBox(height: 24), _buildPlanSection(entitlement, monthlyProd, lifetimeProd), const SizedBox(height: 28), const Divider(color: Colors.white12), const SizedBox(height: 16), _buildBenefitsSection(), const SizedBox(height: 24), const Divider(color: Colors.white12), const SizedBox(height: 12), _buildDisclaimer(), const SizedBox(height: 24), ], ); } Widget _buildStatusBanner() { final colors = context.motorColors; return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: _isBannerError ? colors.raceRed.withValues(alpha: 0.15) : colors.launchGreen.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(8), border: Border.all( color: _isBannerError ? colors.raceRed : colors.launchGreen, width: 0.8, ), ), child: Row( children: [ Icon( _isBannerError ? Icons.error_outline : Icons.check_circle_outline, color: _isBannerError ? colors.raceRed : colors.launchGreen, size: 20, ), const SizedBox(width: 10), Expanded( child: Text( _statusBanner!, style: TextStyle( fontSize: 12, color: _isBannerError ? colors.raceRed : colors.launchGreen, fontWeight: FontWeight.w600, ), ), ), ], ), ); } Widget _buildActiveProBanner(EntitlementState entitlement) { final colors = context.motorColors; return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: colors.cyberCyan.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), border: Border.all(color: colors.cyberCyan), ), child: Row( children: [ Icon( Icons.verified, color: colors.cyberCyan, size: 28, ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( entitlement.plan == MotorSignalPlan.proLifetime ? 'MOTORSIGNAL PRO LIFETIME ACTIVE' : 'MOTORSIGNAL PRO ACTIVE', style: TextStyle( fontFamily: 'Sora', fontSize: 13, fontWeight: FontWeight.w900, color: colors.cyberCyan, letterSpacing: 0.5, ), ), const SizedBox(height: 2), Text( entitlement.plan == MotorSignalPlan.proLifetime ? 'Permanent access enabled. Thank you for supporting MotorSignal.' : (entitlement.expiresAt != null ? 'Subscription active. Renews ${entitlement.expiresAt!.toLocal().toString().split(' ')[0]}.' : 'Subscription active.'), style: TextStyle( fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ], ), ), ], ), ); } Widget _buildPitchHeader() { final theme = Theme.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'PRECISION TELEMETRY.\nUNLIMITED RIDE DATA.', style: TextStyle( fontFamily: 'Sora', fontSize: 22, fontWeight: FontWeight.w900, color: theme.colorScheme.onSurface, letterSpacing: 0.2, height: 1.2, ), ), const SizedBox(height: 8), Text( 'Unlock 20Hz deep telemetry analysis, multiple motorcycle profiles, unlimited cloud sync, and forensic data exports.', style: TextStyle( fontSize: 13, color: theme.colorScheme.onSurfaceVariant, height: 1.4, ), ), ], ); } Widget _buildPlanSection( EntitlementState entitlement, StoreProduct? monthlyProd, StoreProduct? lifetimeProd, ) { if (_isLoadingProducts) { return Center( child: Padding( padding: const EdgeInsets.all(32.0), child: CircularProgressIndicator(color: context.motorColors.cyberCyan), ), ); } if (!_storeAvailable) { return _buildStoreUnavailableCard(); } return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Lifetime Plan Card _buildPlanCard( title: 'PRO LIFETIME', subtitle: 'One-time purchase • Permanent access', priceDisplay: lifetimeProd?.price ?? 'Unavailable', badgeText: lifetimeProd != null ? 'BEST VALUE' : null, isHighlighted: true, isLoading: _currentStep == ProPurchaseStep.purchasing, isCurrentPlan: entitlement.plan == MotorSignalPlan.proLifetime && entitlement.isPro, onTap: lifetimeProd != null ? () => _handleBuy(ProductCatalog.proLifetime) : null, ), const SizedBox(height: 14), // Monthly Plan Card _buildPlanCard( title: 'PRO MONTHLY', subtitle: 'Recurring subscription • Cancel anytime', priceDisplay: monthlyProd != null ? '${monthlyProd.price}/month' : 'Unavailable', badgeText: null, isHighlighted: false, isLoading: _currentStep == ProPurchaseStep.purchasing, isCurrentPlan: entitlement.plan == MotorSignalPlan.proMonthly && entitlement.isPro, onTap: monthlyProd != null ? () => _handleBuy(ProductCatalog.proMonthly) : null, ), ], ); } Widget _buildBenefitsSection() { final colors = context.motorColors; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'WHAT IS INCLUDED IN PRO', style: TextStyle( fontFamily: 'Sora', fontSize: 12, fontWeight: FontWeight.w800, color: colors.cyberCyan, letterSpacing: 1.0, ), ), const SizedBox(height: 16), _buildBenefitRow( icon: Icons.speed, title: 'Advanced Acceleration & Braking', desc: 'G-force timeline, sprint intervals, roll-on speed, and deceleration thresholds.', ), _buildBenefitRow( icon: Icons.screen_rotation, title: 'Advanced Lean Angle Analysis', desc: 'Left and right apex lean distributions, transition dynamics, and turn stability.', ), _buildBenefitRow( icon: Icons.show_chart, title: '20Hz Sub-Second Telemetry', desc: 'High-frequency forensic sensor fusion playback with interactive scrub control.', ), _buildBenefitRow( icon: Icons.history, title: 'Unlimited Ride History', desc: 'Complete persistent ledger of every ride without duration or count limitations.', ), _buildBenefitRow( icon: Icons.table_chart_outlined, title: 'Forensic CSV & PDF Export', desc: 'Export structured engineering data and printable performance run sheets.', ), _buildBenefitRow( icon: Icons.two_wheeler, title: 'Multiple Motorcycle Garage', desc: 'Log and compare telemetry across your entire personal motorcycle fleet.', ), _buildBenefitRow( icon: Icons.cloud_done_outlined, title: 'Unlimited MotorCloud Sync', desc: 'Automatic encrypted cloud backup with multi-device restore capability.', ), _buildBenefitRow( icon: Icons.alt_route, title: 'Route Planning & Group Rides', desc: 'Multi-waypoint scenic routing and group pack coordination features.', ), ], ); } Widget _buildDisclaimer() { return Text( 'Basic ride recording, cockpit instruments, offline history, local SQLite storage, and crash detection remain 100% free forever. MotorSignal Pro subscriptions can be managed or canceled anytime in Google Play Store settings.', textAlign: TextAlign.center, style: TextStyle( fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.6), height: 1.4, ), ); } Widget _buildStoreUnavailableCard() { final colors = context.motorColors; return Container( padding: const EdgeInsets.all(18), decoration: BoxDecoration( color: colors.surfaceContainer, borderRadius: BorderRadius.circular(12), border: Border.all(color: colors.outlineVariant), ), child: Column( children: [ Icon(Icons.cloud_off, color: colors.warningAmber, size: 32), const SizedBox(height: 12), const Text( 'Google Play Billing Unavailable', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14), ), const SizedBox(height: 6), Text( 'Cannot connect to Google Play. Please check your internet connection or Google Play Store service status.', textAlign: TextAlign.center, style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant), ), const SizedBox(height: 14), OutlinedButton( style: OutlinedButton.styleFrom(minimumSize: const Size(120, 48)), onPressed: _loadStoreProducts, child: const Text('RETRY CONNECTION'), ), ], ), ); } Widget _buildPlanCard({ required String title, required String subtitle, required String priceDisplay, required String? badgeText, required bool isHighlighted, required bool isLoading, required bool isCurrentPlan, required VoidCallback? onTap, }) { final colors = context.motorColors; return Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(14), onTap: isCurrentPlan ? null : onTap, child: Container( constraints: const BoxConstraints(minHeight: 64), decoration: BoxDecoration( color: isHighlighted ? colors.cyberCyan.withValues(alpha: 0.08) : colors.surfaceContainer, borderRadius: BorderRadius.circular(14), border: Border.all( color: isCurrentPlan ? colors.launchGreen : (isHighlighted ? colors.cyberCyan : colors.outlineVariant), width: isHighlighted || isCurrentPlan ? 1.6 : 1.0, ), ), padding: const EdgeInsets.all(16.0), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Wrap( crossAxisAlignment: WrapCrossAlignment.center, spacing: 8, runSpacing: 4, children: [ Text( title, style: TextStyle( fontFamily: 'Sora', fontSize: 15, fontWeight: FontWeight.w900, color: Theme.of(context).colorScheme.onSurface, letterSpacing: 0.5, ), ), if (badgeText != null) Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2, ), decoration: BoxDecoration( color: colors.cyberCyan, borderRadius: BorderRadius.circular(4), ), child: const Text( 'BEST VALUE', style: TextStyle( fontSize: 9, fontWeight: FontWeight.w900, color: Colors.black, letterSpacing: 0.5, ), ), ), if (isCurrentPlan) Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2, ), decoration: BoxDecoration( color: colors.launchGreen.withValues( alpha: 0.2, ), borderRadius: BorderRadius.circular(4), border: Border.all(color: colors.launchGreen), ), child: Text( 'CURRENT PLAN', style: TextStyle( fontSize: 9, fontWeight: FontWeight.w900, color: colors.launchGreen, letterSpacing: 0.5, ), ), ), ], ), const SizedBox(height: 4), Text( subtitle, style: TextStyle( fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ], ), ), const SizedBox(width: 12), ConstrainedBox( constraints: const BoxConstraints(maxWidth: 130), child: Column( crossAxisAlignment: CrossAxisAlignment.end, mainAxisSize: MainAxisSize.min, children: [ FittedBox( fit: BoxFit.scaleDown, child: Text( priceDisplay, style: TextStyle( fontFamily: 'JetBrains Mono', fontSize: 17, fontWeight: FontWeight.w900, color: isHighlighted ? colors.cyberCyan : Theme.of(context).colorScheme.onSurface, ), ), ), const SizedBox(height: 2), Text( isCurrentPlan ? 'Active' : (onTap == null ? 'Unavailable' : 'Select'), style: TextStyle( fontSize: 11, color: isHighlighted ? colors.cyberCyan : (onTap == null ? Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.5) : Theme.of(context).colorScheme.onSurfaceVariant), fontWeight: FontWeight.bold, ), ), ], ), ), ], ), ), ), ); } Widget _buildBenefitRow({ required IconData icon, required String title, required String desc, }) { final colors = context.motorColors; return Padding( padding: const EdgeInsets.only(bottom: 16.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: colors.cyberCyan.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(icon, color: colors.cyberCyan, size: 20), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurface, ), ), const SizedBox(height: 2), Text( desc, style: TextStyle( fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant, height: 1.3, ), ), ], ), ), ], ), ); } }