import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.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 { // Flow handed over to PlayBillingService purchaseStream -> EntitlementCoordinator if (mounted) { setState(() { _currentStep = ProPurchaseStep.idle; }); } } } Future _handleRestore() async { setState(() { _currentStep = ProPurchaseStep.restoring; _statusBanner = null; }); try { final billing = ref.read(billingServiceProvider); await billing.restorePurchases(); // Allow brief moment for store emission 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 monthlyProd = _products .where((p) => p.id == ProductCatalog.proMonthly) .firstOrNull; final lifetimeProd = _products .where((p) => p.id == ProductCatalog.proLifetime) .firstOrNull; return Scaffold( backgroundColor: Colors.black, appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, title: const Row( mainAxisSize: MainAxisSize.min, children: [ Text( 'MOTORSIGNAL', style: TextStyle( fontFamily: 'Sora', fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 1.5, ), ), SizedBox(width: 6), ProBadge(), ], ), actions: [ TextButton( onPressed: _currentStep != ProPurchaseStep.idle ? null : _handleRestore, child: const Text( 'RESTORE', style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: Color(0xFF00E5FF), ), ), ), ], ), body: SafeArea( child: ListView( padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 12.0), children: [ // Status Banner if present if (_statusBanner != null) ...[ Container( padding: const EdgeInsets.all(12), margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: _isBannerError ? Colors.redAccent.withValues(alpha: 0.15) : Colors.greenAccent.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(8), border: Border.all( color: _isBannerError ? Colors.redAccent : Colors.greenAccent, width: 0.8, ), ), child: Row( children: [ Icon( _isBannerError ? Icons.error_outline : Icons.check_circle_outline, color: _isBannerError ? Colors.redAccent : Colors.greenAccent, size: 20, ), const SizedBox(width: 10), Expanded( child: Text( _statusBanner!, style: TextStyle( fontSize: 12, color: _isBannerError ? Colors.redAccent : Colors.greenAccent, fontWeight: FontWeight.w600, ), ), ), ], ), ), ], // Active Pro Banner if user is already verified Pro if (entitlement.isPro) ...[ Container( padding: const EdgeInsets.all(16), margin: const EdgeInsets.only(bottom: 20), decoration: BoxDecoration( color: const Color(0xFF00E5FF).withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFF00E5FF)), ), child: Row( children: [ const Icon( Icons.verified, color: Color(0xFF00E5FF), 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: const TextStyle( fontFamily: 'Sora', fontSize: 13, fontWeight: FontWeight.w900, color: Color(0xFF00E5FF), 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: const TextStyle( fontSize: 11, color: Colors.white70, ), ), ], ), ), ], ), ), ], // Header Pitch const Text( 'PRECISION TELEMETRY.\nUNLIMITED RIDE DATA.', style: TextStyle( fontFamily: 'Sora', fontSize: 22, fontWeight: FontWeight.w900, color: Colors.white, letterSpacing: 0.2, height: 1.2, ), ), const SizedBox(height: 8), const Text( 'Unlock 20Hz deep telemetry analysis, multiple motorcycle profiles, unlimited cloud sync, and forensic data exports.', style: TextStyle( fontSize: 13, color: Colors.white70, height: 1.4, ), ), const SizedBox(height: 24), // Plan Cards if (_isLoadingProducts) ...[ const Center( child: Padding( padding: EdgeInsets.all(32.0), child: CircularProgressIndicator(color: Color(0xFF00E5FF)), ), ), ] else if (!_storeAvailable) ...[ _buildStoreUnavailableCard(), ] else ...[ // Lifetime Plan Card _buildPlanCard( title: 'PRO LIFETIME', subtitle: 'One-time purchase • Permanent access', priceDisplay: lifetimeProd?.price ?? 'Unavailable', badgeText: 'BEST VALUE', 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, ), ], const SizedBox(height: 28), const Divider(color: Colors.white12), const SizedBox(height: 16), // Benefits Checklist const Text( 'WHAT IS INCLUDED IN PRO', style: TextStyle( fontFamily: 'Sora', fontSize: 12, fontWeight: FontWeight.w800, color: Color(0xFF00E5FF), 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.', ), const SizedBox(height: 24), const Divider(color: Colors.white12), const SizedBox(height: 12), // Terms & Clarity Note const 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: Colors.white38, height: 1.4, ), ), const SizedBox(height: 24), ], ), ), ); } Widget _buildStoreUnavailableCard() { return Container( padding: const EdgeInsets.all(18), decoration: BoxDecoration( color: const Color(0xFF141414), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white12), ), child: Column( children: [ const Icon(Icons.cloud_off, color: Colors.amberAccent, size: 32), const SizedBox(height: 12), const Text( 'Google Play Billing Unavailable', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14), ), const SizedBox(height: 6), const 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: Colors.white60), ), const SizedBox(height: 14), OutlinedButton( 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, }) { return Container( decoration: BoxDecoration( color: isHighlighted ? const Color(0xFF001A1E) : const Color(0xFF0F0F0F), borderRadius: BorderRadius.circular(14), border: Border.all( color: isCurrentPlan ? Colors.greenAccent : (isHighlighted ? const Color(0xFF00E5FF) : const Color(0xFF222222)), width: isHighlighted || isCurrentPlan ? 1.6 : 1.0, ), ), child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(14), onTap: isCurrentPlan ? null : onTap, child: Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( title, style: const TextStyle( fontFamily: 'Sora', fontSize: 15, fontWeight: FontWeight.w900, color: Colors.white, letterSpacing: 0.5, ), ), if (badgeText != null) ...[ const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2, ), decoration: BoxDecoration( color: const Color(0xFF00E5FF), borderRadius: BorderRadius.circular(4), ), child: Text( badgeText, style: const TextStyle( fontSize: 9, fontWeight: FontWeight.w900, color: Colors.black, letterSpacing: 0.5, ), ), ), ], if (isCurrentPlan) ...[ const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2, ), decoration: BoxDecoration( color: Colors.greenAccent.withValues( alpha: 0.2, ), borderRadius: BorderRadius.circular(4), border: Border.all(color: Colors.greenAccent), ), child: const Text( 'CURRENT PLAN', style: TextStyle( fontSize: 9, fontWeight: FontWeight.w900, color: Colors.greenAccent, letterSpacing: 0.5, ), ), ), ], ], ), const SizedBox(height: 4), Text( subtitle, style: const TextStyle( fontSize: 11, color: Colors.white60, ), ), ], ), ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( priceDisplay, style: TextStyle( fontFamily: 'JetBrains Mono', fontSize: 17, fontWeight: FontWeight.w900, color: isHighlighted ? const Color(0xFF00E5FF) : Colors.white, ), ), const SizedBox(height: 2), Text( isCurrentPlan ? 'Active' : 'Select', style: TextStyle( fontSize: 11, color: isHighlighted ? const Color(0xFF00E5FF) : Colors.white54, fontWeight: FontWeight.bold, ), ), ], ), ], ), ), ), ), ); } Widget _buildBenefitRow({ required IconData icon, required String title, required String desc, }) { return Padding( padding: const EdgeInsets.only(bottom: 16.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: const Color(0xFF00E5FF).withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(icon, color: const Color(0xFF00E5FF), size: 20), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.bold, color: Colors.white, ), ), const SizedBox(height: 2), Text( desc, style: const TextStyle( fontSize: 11, color: Colors.white60, height: 1.3, ), ), ], ), ), ], ), ); } }