import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/responsive/viewport_size.dart'; import '../../core/theme/theme_extensions.dart'; import '../../database/database.dart'; import '../../services/auth/auth_providers.dart'; import '../../services/auth/auth_state.dart'; import '../../services/auth/auth_user.dart'; import '../../services/sync/cloud_restore_controller.dart'; import '../../services/sync/sync_providers.dart'; import '../../services/group_rides/group_ride_providers.dart'; import 'widgets/current_plan_card.dart'; class AccountScreen extends ConsumerStatefulWidget { const AccountScreen({super.key}); @override ConsumerState createState() => _AccountScreenState(); } enum _AuthFormMode { signIn, register, forgotPassword } class _DeleteAccountDialogResult { final String? password; const _DeleteAccountDialogResult({this.password}); } class _AccountScreenState extends ConsumerState { final _formKey = GlobalKey(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); final _displayNameController = TextEditingController(); _AuthFormMode _formMode = _AuthFormMode.signIn; bool _obscurePassword = true; bool _showAnonymousUpgrade = false; String? _statusMessage; @override void dispose() { debugPrint('[DELETE_FLOW] AccountScreen dispose'); _emailController.dispose(); _passwordController.dispose(); _displayNameController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final authState = ref.watch(authNotifierProvider); final snapshot = context.responsive; final isWide = snapshot.isMedium || snapshot.isExpanded; return Scaffold( appBar: AppBar( title: const Text( 'Account & Cloud Identity', style: TextStyle(fontWeight: FontWeight.w800, letterSpacing: 0.5), ), ), body: SafeArea( child: SingleChildScrollView( keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, padding: EdgeInsets.symmetric( horizontal: isWide ? 24.0 : 16.0, vertical: 14.0, ), child: Center( child: ConstrainedBox( constraints: BoxConstraints( maxWidth: isWide ? 1100 : 640, ), child: _buildBodyContent(context, authState, isWide), ), ), ), ), ); } Widget _buildBodyContent(BuildContext context, AuthState authState, bool isWide) { if (authState.isAuthenticated) { return _buildSignedInView(context, authState.user!, isWide); } else if (authState.isAnonymous && !_showAnonymousUpgrade) { return _buildAnonymousView(context, authState.user!, isWide); } else { return _buildSignedOutView(context, authState, isWide); } } Widget _buildSignedOutView( BuildContext context, AuthState authState, bool isWide, ) { final formCard = _buildAuthCard( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( children: [ Icon( Icons.account_circle_outlined, color: context.motorColors.cyberCyan, size: 22, ), const SizedBox(width: 10), Expanded( child: Text( _formMode == _AuthFormMode.signIn ? 'SIGN IN TO MOTORCLOUD' : (_formMode == _AuthFormMode.register ? 'CREATE ACCOUNT' : 'RESET PASSWORD'), style: TextStyle( fontFamily: 'Sora', fontSize: 13, fontWeight: FontWeight.w800, color: context.motorColors.cyberCyan, letterSpacing: 0.8, ), ), ), ], ), const SizedBox(height: 8), Text( 'Cloud sign-in is completely optional. All telemetry, HUD instruments, sensor fusion, and ride history are 100% functional without an account.', style: TextStyle( fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant, height: 1.35, ), ), const SizedBox(height: 16), if (authState.hasError) ...[ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: context.motorColors.raceRed.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(8), border: Border.all( color: context.motorColors.raceRed.withValues(alpha: 0.6), ), ), child: Row( children: [ Icon( Icons.error_outline, color: context.motorColors.raceRed, size: 18, ), const SizedBox(width: 8), Expanded( child: Text( authState.errorMessage!, style: TextStyle( fontSize: 11, color: context.motorColors.raceRed, ), ), ), ], ), ), const SizedBox(height: 16), ], if (_statusMessage != null) ...[ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: context.motorColors.launchGreen.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(8), border: Border.all( color: context.motorColors.launchGreen.withValues(alpha: 0.6), ), ), child: Row( children: [ Icon( Icons.check_circle_outline, color: context.motorColors.launchGreen, size: 18, ), const SizedBox(width: 8), Expanded( child: Text( _statusMessage!, style: TextStyle( fontSize: 11, color: context.motorColors.launchGreen, ), ), ), ], ), ), const SizedBox(height: 16), ], if (_formMode != _AuthFormMode.forgotPassword) ...[ ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: Colors.black87, minimumSize: const Size(double.infinity, 48), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), icon: const Icon( Icons.g_mobiledata, size: 24, color: Colors.redAccent, ), label: const Text( 'Continue with Google', style: TextStyle(fontSize: 13, fontWeight: FontWeight.bold), ), onPressed: authState.isLoading ? null : () => _handleGoogleSignIn(), ), const SizedBox(height: 16), Row( children: [ Expanded(child: Divider(color: context.motorColors.outlineVariant)), Padding( padding: const EdgeInsets.symmetric(horizontal: 10), child: Text( 'OR', style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 10, fontWeight: FontWeight.bold, ), ), ), Expanded(child: Divider(color: context.motorColors.outlineVariant)), ], ), const SizedBox(height: 16), ], Form( key: _formKey, child: Column( children: [ if (_formMode == _AuthFormMode.register) ...[ TextFormField( controller: _displayNameController, decoration: InputDecoration( labelText: 'Display Name (Optional)', prefixIcon: const Icon( Icons.person_outline, size: 18, ), filled: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), isDense: true, ), style: const TextStyle(fontSize: 13), ), const SizedBox(height: 12), ], TextFormField( controller: _emailController, keyboardType: TextInputType.emailAddress, decoration: InputDecoration( labelText: 'Email Address', prefixIcon: const Icon(Icons.email_outlined, size: 18), filled: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), isDense: true, ), style: const TextStyle(fontSize: 13), validator: (value) { if (value == null || value.trim().isEmpty) { return 'Please enter an email address'; } if (!value.contains('@') || !value.contains('.')) { return 'Please enter a valid email address'; } return null; }, ), if (_formMode != _AuthFormMode.forgotPassword) ...[ const SizedBox(height: 12), TextFormField( controller: _passwordController, obscureText: _obscurePassword, decoration: InputDecoration( labelText: 'Password', prefixIcon: const Icon(Icons.lock_outline, size: 18), suffixIcon: IconButton( icon: Icon( _obscurePassword ? Icons.visibility_off : Icons.visibility, size: 18, ), onPressed: () => setState( () => _obscurePassword = !_obscurePassword, ), ), filled: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), isDense: true, ), style: const TextStyle(fontSize: 13), validator: (value) { if (value == null || value.isEmpty) { return 'Please enter your password'; } if (_formMode == _AuthFormMode.register && value.length < 6) { return 'Password must be at least 6 characters'; } return null; }, ), ], ], ), ), const SizedBox(height: 16), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: context.motorColors.cyberCyan, foregroundColor: Colors.black, minimumSize: const Size(double.infinity, 48), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), onPressed: authState.isLoading ? null : () => _submitForm(), child: authState.isLoading ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.black, ), ) : Text( _formMode == _AuthFormMode.signIn ? 'SIGN IN' : (_formMode == _AuthFormMode.register ? 'CREATE ACCOUNT' : 'SEND RESET LINK'), style: const TextStyle( fontWeight: FontWeight.w900, letterSpacing: 0.8, ), ), ), const SizedBox(height: 12), Wrap( alignment: WrapAlignment.spaceBetween, crossAxisAlignment: WrapCrossAlignment.center, children: [ if (_formMode == _AuthFormMode.signIn) ...[ TextButton( onPressed: () => setState(() { _formMode = _AuthFormMode.forgotPassword; _statusMessage = null; }), child: Text( 'Forgot Password?', style: TextStyle( fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ), TextButton( onPressed: () => setState(() { _formMode = _AuthFormMode.register; _statusMessage = null; }), child: Text( 'Create Account', style: TextStyle( fontSize: 11, color: context.motorColors.cyberCyan, fontWeight: FontWeight.bold, ), ), ), ] else if (_formMode == _AuthFormMode.register) ...[ TextButton( onPressed: () => setState(() { _formMode = _AuthFormMode.signIn; _statusMessage = null; }), child: Text( 'Already have an account? Sign In', style: TextStyle( fontSize: 11, color: context.motorColors.cyberCyan, ), ), ), ] else ...[ TextButton( onPressed: () => setState(() { _formMode = _AuthFormMode.signIn; _statusMessage = null; }), child: Text( 'Back to Sign In', style: TextStyle( fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ), ], ], ), const Divider(height: 24), OutlinedButton.icon( style: OutlinedButton.styleFrom( foregroundColor: Theme.of(context).colorScheme.onSurface, side: BorderSide(color: context.motorColors.outlineVariant), minimumSize: const Size(double.infinity, 48), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), icon: const Icon(Icons.person_outline, size: 18), label: const Text( 'Continue as Guest / Anonymous Rider', style: TextStyle(fontSize: 11), ), onPressed: authState.isLoading ? null : () => ref .read(authNotifierProvider.notifier) .signInAnonymously(), ), ], ), ); if (isWide) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: formCard), const SizedBox(width: 24), Expanded( child: Column( children: [ _buildOfflineNoticeCard(authState.isFirebaseConfigured), const SizedBox(height: 16), _buildLocalRidingActiveCard(), ], ), ), ], ); } return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildOfflineNoticeCard(authState.isFirebaseConfigured), const SizedBox(height: 16), formCard, const SizedBox(height: 16), _buildLocalRidingActiveCard(), ], ); } Widget _buildSignedInView( BuildContext context, AuthUser user, bool isWide, ) { final leftColumn = _buildAuthCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ CircleAvatar( radius: 28, backgroundColor: context.motorColors.cyberCyan.withValues(alpha: 0.2), backgroundImage: user.photoUrl != null ? NetworkImage(user.photoUrl!) : null, child: user.photoUrl == null ? Icon( Icons.person, color: context.motorColors.cyberCyan, size: 32, ) : null, ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( user.displayLabel, style: const TextStyle( fontFamily: 'Sora', fontSize: 18, fontWeight: FontWeight.w800, ), ), const SizedBox(height: 2), Text( user.email ?? 'No email associated', style: TextStyle( fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 6), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2, ), decoration: BoxDecoration( color: context.motorColors.cyberCyan.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(4), border: Border.all( color: context.motorColors.cyberCyan, width: 0.8, ), ), child: Text( 'AUTHENTICATED (${user.providerId ?? "cloud"})', style: TextStyle( fontFamily: 'Inter', fontSize: 9, fontWeight: FontWeight.w900, color: context.motorColors.cyberCyan, letterSpacing: 0.5, ), ), ), ], ), ), ], ), const Divider(height: 32), _buildInfoRow('User ID', user.uid, isMonospace: true), if (user.providerId != null) _buildInfoRow('Provider', user.providerId!), if (user.createdAt != null) _buildInfoRow( 'Account Created', user.createdAt!.toLocal().toString().split('.')[0], ), if (user.lastSignInTime != null) _buildInfoRow( 'Last Sign-In', user.lastSignInTime!.toLocal().toString().split('.')[0], ), if (user.providerId == 'password' && !user.emailVerified) ...[ const SizedBox(height: 12), Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: context.motorColors.warningAmber.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(8), border: Border.all( color: context.motorColors.warningAmber.withValues(alpha: 0.5), ), ), child: Row( children: [ Expanded( child: Text( 'Your email is not verified. Verify it to protect account recovery.', style: TextStyle( fontSize: 11, color: Theme.of(context).colorScheme.onSurface, ), ), ), TextButton( onPressed: () => _sendVerification(), child: const Text('VERIFY'), ), IconButton( tooltip: 'Refresh verification status', onPressed: () => _reloadUser(), icon: const Icon(Icons.refresh, size: 18), ), ], ), ), ], const SizedBox(height: 20), Row( children: [ Expanded( child: OutlinedButton.icon( style: OutlinedButton.styleFrom( foregroundColor: context.motorColors.cyberCyan, side: BorderSide(color: context.motorColors.cyberCyan), minimumSize: const Size(0, 48), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), icon: const Icon(Icons.person_outline, size: 18), label: const Text( 'RIDER PROFILE', style: TextStyle( fontWeight: FontWeight.bold, letterSpacing: 0.5, fontSize: 11, ), ), onPressed: () => context.push('/settings/profile'), ), ), const SizedBox(width: 8), Expanded( child: OutlinedButton.icon( style: OutlinedButton.styleFrom( foregroundColor: context.motorColors.cyberCyan, side: BorderSide(color: context.motorColors.cyberCyan), minimumSize: const Size(0, 48), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), icon: const Icon(Icons.people_outline, size: 18), label: const Text( 'FRIENDS', style: TextStyle( fontWeight: FontWeight.bold, letterSpacing: 0.5, fontSize: 11, ), ), onPressed: () => context.push('/settings/friends'), ), ), ], ), const SizedBox(height: 10), SizedBox( width: double.infinity, child: ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: context.motorColors.cyberCyan.withValues(alpha: 0.15), foregroundColor: context.motorColors.cyberCyan, side: BorderSide(color: context.motorColors.cyberCyan), minimumSize: const Size(double.infinity, 48), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), icon: const Icon(Icons.groups_outlined, size: 18), label: const Text( 'GROUP RIDES', style: TextStyle( fontWeight: FontWeight.bold, letterSpacing: 0.5, fontSize: 12, ), ), onPressed: () => context.push('/group-rides'), ), ), const SizedBox(height: 20), SizedBox( width: double.infinity, child: OutlinedButton.icon( style: OutlinedButton.styleFrom( foregroundColor: context.motorColors.raceRed, side: BorderSide(color: context.motorColors.raceRed), minimumSize: const Size(double.infinity, 48), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), icon: const Icon(Icons.logout, size: 18), label: const Text( 'SIGN OUT', style: TextStyle( fontWeight: FontWeight.bold, letterSpacing: 0.8, ), ), onPressed: () => ref.read(authNotifierProvider.notifier).signOut(), ), ), const SizedBox(height: 10), SizedBox( width: double.infinity, child: TextButton.icon( style: TextButton.styleFrom( foregroundColor: context.motorColors.raceRed, minimumSize: const Size(double.infinity, 48), ), icon: const Icon(Icons.delete_forever_outlined, size: 18), label: const Text('DELETE ACCOUNT'), onPressed: () => _handleDeleteAccount(user), ), ), ], ), ); final rightColumn = Column( children: [ const CurrentPlanCard(), const SizedBox(height: 16), _buildCloudSyncSection( context: context, isAuthenticated: true, ), const SizedBox(height: 16), _buildGroupRidePrivacyCard(), const SizedBox(height: 16), _buildLocalRidingActiveCard(), ], ); if (isWide) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: leftColumn), const SizedBox(width: 24), Expanded(child: rightColumn), ], ); } return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ leftColumn, const SizedBox(height: 16), rightColumn, ], ); } Widget _buildGroupRidePrivacyCard() { final colors = context.motorColors; final isAllowed = ref.watch(groupRideLiveSharingAllowedProvider); return _buildAuthCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.share_location, color: colors.cyberCyan, size: 20), const SizedBox(width: 8), Expanded( child: Text( 'GROUP RIDE LIVE LOCATION', style: TextStyle( fontFamily: 'Sora', fontSize: 13, fontWeight: FontWeight.w800, color: Theme.of(context).colorScheme.onSurface, letterSpacing: 0.5, ), ), ), ], ), const SizedBox(height: 8), Text( 'Control whether MotorSignal can broadcast your live location to other accepted riders during an active Group Ride.', style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 12, height: 1.35, ), ), const SizedBox(height: 12), SwitchListTile( contentPadding: EdgeInsets.zero, title: const Text( 'Allow Live Location Sharing', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600), ), subtitle: Text( isAllowed ? 'Permitted when opted in inside individual rides' : 'Master block: disabled for all group rides', style: TextStyle( color: isAllowed ? colors.cyberCyan : colors.outlineVariant, fontSize: 11, ), ), value: isAllowed, activeThumbColor: colors.cyberCyan, onChanged: (val) { ref.read(groupRideLiveSharingAllowedProvider.notifier).state = val; }, ), const SizedBox(height: 6), Text( 'Privacy contract: Live location is ephemeral, shared only with accepted riders while a ride is active, and never saved to historical telemetry or rider profiles.', style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.6), fontSize: 10, height: 1.3, ), ), ], ), ); } Widget _buildAnonymousView( BuildContext context, AuthUser user, bool isWide, ) { final colors = context.motorColors; final leftColumn = _buildAuthCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ CircleAvatar( radius: 28, backgroundColor: colors.warningAmber.withValues(alpha: 0.2), child: Icon( Icons.person_outline, color: colors.warningAmber, size: 32, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Guest / Anonymous Rider', style: TextStyle( fontFamily: 'Sora', fontSize: 17, fontWeight: FontWeight.w800, ), ), const SizedBox(height: 4), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2, ), decoration: BoxDecoration( color: colors.warningAmber.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(4), border: Border.all( color: colors.warningAmber, width: 0.8, ), ), child: Text( 'OFFLINE GUEST SESSION', style: TextStyle( fontFamily: 'Inter', fontSize: 9, fontWeight: FontWeight.w900, color: colors.warningAmber, letterSpacing: 0.5, ), ), ), ], ), ), ], ), const Divider(height: 24), _buildInfoRow('Session ID', user.uid, isMonospace: true), const SizedBox(height: 16), Text( 'You are currently using MotorSignal in local guest mode. All rides are stored locally in your SQLite database. You can link a full cloud account anytime.', style: TextStyle( fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant, height: 1.4, ), ), const SizedBox(height: 20), SizedBox( width: double.infinity, child: ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: colors.cyberCyan, foregroundColor: Colors.black, minimumSize: const Size(double.infinity, 48), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), icon: const Icon(Icons.link, size: 18), label: const Text( 'SIGN IN WITH FULL ACCOUNT', style: TextStyle(fontWeight: FontWeight.bold), ), onPressed: () => setState(() => _showAnonymousUpgrade = true), ), ), ], ), ); final rightColumn = Column( children: [ const CurrentPlanCard(), const SizedBox(height: 16), _buildCloudSyncSection( context: context, isAuthenticated: false, ), const SizedBox(height: 16), _buildLocalRidingActiveCard(), ], ); if (isWide) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: leftColumn), const SizedBox(width: 24), Expanded(child: rightColumn), ], ); } return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ leftColumn, const SizedBox(height: 16), rightColumn, ], ); } Widget _buildCloudSyncSection({ required BuildContext context, required bool isAuthenticated, }) { final colors = context.motorColors; final syncState = ref.watch(syncStateProvider); final syncManager = ref.watch(syncManagerProvider); final restoreState = ref.watch(cloudRestoreControllerProvider); final restoreController = ref.read(cloudRestoreControllerProvider.notifier); return _buildAuthCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Icon( Icons.cloud_sync_outlined, color: colors.cyberCyan, size: 20, ), const SizedBox(width: 8), Text( 'MOTORCLOUD SYNC', style: TextStyle( fontFamily: 'Sora', fontSize: 12, fontWeight: FontWeight.w800, color: colors.cyberCyan, letterSpacing: 0.5, ), ), ], ), if (syncState.isProcessing) Row( children: [ SizedBox( width: 12, height: 12, child: CircularProgressIndicator( strokeWidth: 2, color: colors.cyberCyan, ), ), const SizedBox(width: 6), Text( 'Syncing...', style: TextStyle(fontSize: 10, color: Theme.of(context).colorScheme.onSurfaceVariant), ), ], ), ], ), const SizedBox(height: 12), if (!isAuthenticated) ...[ Text( 'Sign in to MotorCloud to back up eligible rides and restore them on another device. Local rides remain available on this device.', style: TextStyle( fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant, height: 1.4, ), ), ] else ...[ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: colors.surfaceContainerHighest.withValues(alpha: 0.4), borderRadius: BorderRadius.circular(8), border: Border.all(color: colors.outlineVariant), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildSyncStatItem( 'Synced', '${syncState.syncedCount}', colors.launchGreen, ), _buildSyncStatItem( 'Pending', '${syncState.pendingCount}', colors.warningAmber, ), _buildSyncStatItem( 'Failed', '${syncState.failedCount}', colors.raceRed, ), ], ), ), const SizedBox(height: 12), if (syncState.failedCount > 0) ...[ Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 6, ), decoration: BoxDecoration( color: colors.raceRed.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(6), border: Border.all( color: colors.raceRed.withValues(alpha: 0.4), ), ), child: Row( children: [ Icon( Icons.warning_amber_rounded, color: colors.raceRed, size: 16, ), const SizedBox(width: 8), Expanded( child: Text( '${syncState.failedCount} ride(s) need attention. Tap Retry to process.', style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w600, ), ), ), TextButton( style: TextButton.styleFrom( visualDensity: VisualDensity.compact, foregroundColor: colors.raceRed, ), onPressed: () => syncManager.retryAllPending(), child: const Text( 'RETRY', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 11, ), ), ), ], ), ), const SizedBox(height: 12), ], _buildInfoRow('Cloud Provider', 'MotorCloud backup'), if (syncState.lastSuccessfulSync != null) _buildInfoRow( 'Last Successful Sync', syncState.lastSuccessfulSync!.toLocal().toString().split( '.', )[0], ), if (syncState.lastError != null && syncState.failedCount > 0) _buildInfoRow('Last Sync Notice', syncState.lastError!), const SizedBox(height: 14), Row( children: [ Expanded( child: ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: colors.cyberCyan, foregroundColor: Colors.black, minimumSize: const Size(0, 48), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), icon: syncState.isProcessing ? const SizedBox( width: 14, height: 14, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.black, ), ) : const Icon(Icons.sync, size: 16), label: Text( syncState.isProcessing ? 'SYNCING...' : 'SYNC NOW', style: const TextStyle( fontSize: 11, fontWeight: FontWeight.bold, letterSpacing: 0.6, ), ), onPressed: syncState.isProcessing ? null : () => syncManager.processQueue(), ), ), const SizedBox(width: 8), Expanded( child: OutlinedButton.icon( style: OutlinedButton.styleFrom( foregroundColor: Theme.of(context).colorScheme.onSurface, side: BorderSide(color: colors.outlineVariant), minimumSize: const Size(0, 48), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), icon: const Icon(Icons.backup_outlined, size: 16), label: const Text( 'BACK UP ALL', style: TextStyle( fontSize: 11, fontWeight: FontWeight.bold, letterSpacing: 0.6, ), ), onPressed: () => _handleBackupAllHistoricalRides(), ), ), ], ), const SizedBox(height: 12), Text( 'Download your backed-up ride history and telemetry to this device.', style: TextStyle( fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant, height: 1.35, ), ), const SizedBox(height: 10), SizedBox( width: double.infinity, child: OutlinedButton.icon( style: OutlinedButton.styleFrom( foregroundColor: Theme.of(context).colorScheme.onSurface, side: BorderSide(color: colors.outlineVariant), minimumSize: const Size(double.infinity, 48), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), icon: restoreState.isRestoring ? SizedBox( width: 14, height: 14, child: CircularProgressIndicator( strokeWidth: 2, color: Theme.of(context).colorScheme.onSurface, ), ) : const Icon(Icons.cloud_download_outlined, size: 16), label: Text( restoreState.isRestoring ? 'RESTORING...' : 'RESTORE FROM MOTORCLOUD', style: const TextStyle( fontSize: 11, fontWeight: FontWeight.bold, letterSpacing: 0.6, ), ), onPressed: (!isAuthenticated || restoreState.isRestoring) ? null : () => restoreController.restoreFromMotorCloud(), ), ), if (restoreState.message != null) ...[ const SizedBox(height: 10), Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 8, ), decoration: BoxDecoration( color: _restoreStatusColor( restoreState.status, ).withValues(alpha: 0.14), borderRadius: BorderRadius.circular(6), border: Border.all( color: _restoreStatusColor( restoreState.status, ).withValues(alpha: 0.5), ), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon( _restoreStatusIcon(restoreState.status), size: 16, color: _restoreStatusColor(restoreState.status), ), const SizedBox(width: 8), Expanded( child: Text( restoreState.message!, style: const TextStyle( fontSize: 11, height: 1.3, ), ), ), ], ), ), ], ], ], ), ); } Widget _buildSyncStatItem(String label, String value, Color color) { return Column( children: [ Text( value, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w900, color: color, ), ), const SizedBox(height: 2), Text( label.toUpperCase(), style: TextStyle( fontSize: 9, fontWeight: FontWeight.w700, color: Theme.of(context).colorScheme.onSurfaceVariant, letterSpacing: 0.5, ), ), ], ); } Future _sendVerification() async { try { await ref.read(authNotifierProvider.notifier).sendEmailVerification(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Verification email sent.')), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Could not send verification email: $e')), ); } } } Future _reloadUser() async { try { await ref.read(authNotifierProvider.notifier).reloadCurrentUser(); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Could not refresh account status: $e')), ); } } } Future _handleDeleteAccount(AuthUser user) async { debugPrint('[DELETE_FLOW] delete dialog opened'); final controller = TextEditingController(); final formKey = GlobalKey(); final result = await showDialog<_DeleteAccountDialogResult>( context: context, builder: (dialogContext) => AlertDialog( title: const Text('Delete Account'), content: Form( key: formKey, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Deleting your account will remove your MotorCloud backup data and account identity.', ), const SizedBox(height: 12), const Text( 'Rides, telemetry/history, and garage data stored on this device will remain available.', ), const SizedBox(height: 12), const Text( 'Deleting your MotorSignal account does not cancel a Google Play subscription. Subscriptions must be managed in Google Play.', ), if (user.providerId == 'password') ...[ const SizedBox(height: 16), TextFormField( controller: controller, obscureText: true, autofocus: true, decoration: const InputDecoration( labelText: 'Current password', ), validator: (value) => value == null || value.isEmpty ? 'Enter your current password' : null, ), ], ], ), ), ), actions: [ TextButton( onPressed: () { Navigator.of(dialogContext).pop(); }, child: const Text('CANCEL'), ), FilledButton( onPressed: () { if (user.providerId == 'password' && !(formKey.currentState?.validate() ?? false)) { return; } debugPrint('[DELETE_FLOW] delete dialog submitted'); Navigator.of(dialogContext).pop( _DeleteAccountDialogResult( password: user.providerId == 'password' ? controller.text : null, ), ); }, child: const Text('DELETE ACCOUNT'), ), ], ), ); controller.dispose(); if (!mounted || result == null) { if (mounted) debugPrint('[DELETE_FLOW] delete dialog cancelled'); return; } try { await ref .read(accountLifecycleServiceProvider) .deleteAccount(password: result.password); } catch (e) { debugPrint( '[DELETE_FLOW] deletion failure class=${e.runtimeType} mounted=$mounted', ); if (mounted) { ScaffoldMessenger.maybeOf(context)?.showSnackBar( const SnackBar( content: Text( 'Account deletion could not be completed. Your account and local rides are unchanged.', ), ), ); } } } Future _handleBackupAllHistoricalRides() async { final db = ref.read(databaseProvider); final syncManager = ref.read(syncManagerProvider); final user = ref.read(currentUserProvider); if (user == null || user.isAnonymous) return; final rides = await db.select(db.rides).get(); final owned = rides .where((ride) => ride.cloudOwnerUid == user.uid) .toList(); final unclaimed = rides .where((ride) => ride.cloudOwnerUid == null) .toList(); final otherOwned = rides.length - owned.length - unclaimed.length; var claimUnclaimed = false; if (unclaimed.isNotEmpty && mounted) { claimUnclaimed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Back Up Device Rides?'), content: Text( 'This will associate ${unclaimed.length} device-local ride(s) with your signed-in MotorCloud account. Rides already associated with another account will not be included.', ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('CANCEL'), ), FilledButton( onPressed: () => Navigator.pop(context, true), child: const Text('BACK UP'), ), ], ), ) ?? false; } var enqueued = 0; if (claimUnclaimed) { for (final ride in unclaimed) { if (await db.claimRideForCloudOwner(ride.id, user.uid)) { await syncManager.enqueueRide(ride.id, autoProcess: false); enqueued++; } } } for (final ride in owned) { await syncManager.enqueueRide(ride.id, autoProcess: false); enqueued++; } if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( 'Queued $enqueued ride(s). Skipped $otherOwned ride(s) associated with another account.', ), duration: const Duration(seconds: 3), ), ); } syncManager.processQueue(); } Color _restoreStatusColor(CloudRestoreStatus status) { final colors = context.motorColors; switch (status) { case CloudRestoreStatus.success: return colors.launchGreen; case CloudRestoreStatus.partialFailure: return colors.warningAmber; case CloudRestoreStatus.authRequired: case CloudRestoreStatus.unavailable: case CloudRestoreStatus.failure: return colors.raceRed; case CloudRestoreStatus.idle: case CloudRestoreStatus.restoring: return colors.cyberCyan; } } IconData _restoreStatusIcon(CloudRestoreStatus status) { switch (status) { case CloudRestoreStatus.success: return Icons.check_circle_outline; case CloudRestoreStatus.partialFailure: return Icons.warning_amber_rounded; case CloudRestoreStatus.authRequired: case CloudRestoreStatus.unavailable: case CloudRestoreStatus.failure: return Icons.error_outline; case CloudRestoreStatus.idle: case CloudRestoreStatus.restoring: return Icons.cloud_sync_outlined; } } Widget _buildOfflineNoticeCard(bool isFirebaseConfigured) { final colors = context.motorColors; return Material( color: colors.surfaceContainer, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide( color: isFirebaseConfigured ? colors.cyberCyan.withValues(alpha: 0.3) : colors.warningAmber.withValues(alpha: 0.4), width: 1.2, ), ), child: Padding( padding: const EdgeInsets.all(14), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon( isFirebaseConfigured ? Icons.cloud_done_outlined : Icons.offline_bolt_outlined, color: isFirebaseConfigured ? colors.cyberCyan : colors.warningAmber, size: 20, ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( isFirebaseConfigured ? 'OFFLINE-FIRST ARCHITECTURE' : 'STANDALONE OFFLINE MODE', style: TextStyle( fontFamily: 'Sora', fontSize: 11, fontWeight: FontWeight.w800, color: isFirebaseConfigured ? colors.cyberCyan : colors.warningAmber, letterSpacing: 0.6, ), ), const SizedBox(height: 4), Text( isFirebaseConfigured ? 'Signing in enables optional multi-device account sync. Offline riding, telemetry acquisition, local SQLite persistence, and forensic exports are always available without an account.' : 'MotorSignal is operating in local mode. All instruments, 20Hz sensor fusion, garage storage, and ride recordings remain 100% operational.', style: TextStyle( fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant, height: 1.35, ), ), ], ), ), ], ), ), ); } Widget _buildLocalRidingActiveCard() { final colors = context.motorColors; return Material( color: colors.surfaceContainer, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide(color: colors.outlineVariant, width: 1.2), ), child: Padding( padding: const EdgeInsets.all(14), child: Row( children: [ Icon(Icons.shield_outlined, color: colors.launchGreen, size: 20), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'LOCAL DATA ISOLATION ACTIVE', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w800, color: colors.launchGreen, letterSpacing: 0.6, ), ), const SizedBox(height: 2), Text( 'Your local ride history remains available on this device. MotorCloud backup is optional and never required for recording or offline use.', style: TextStyle(fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant), ), ], ), ), ], ), ), ); } Widget _buildAuthCard({required Widget child}) { final colors = context.motorColors; return Material( color: colors.surfaceContainer, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), side: BorderSide( color: colors.outlineVariant, width: 1.2, ), ), child: Padding( padding: const EdgeInsets.all(18), child: child, ), ); } Widget _buildInfoRow(String label, String value, {bool isMonospace = false}) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: TextStyle(fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant), ), Flexible( child: Text( value, textAlign: TextAlign.end, overflow: TextOverflow.ellipsis, style: TextStyle( fontFamily: isMonospace ? 'monospace' : null, fontSize: 11, fontWeight: FontWeight.w600, ), ), ), ], ), ); } void _handleGoogleSignIn() { setState(() => _statusMessage = null); ref.read(authNotifierProvider.notifier).signInWithGoogle(); } void _submitForm() async { if (_formKey.currentState?.validate() != true) return; setState(() => _statusMessage = null); final email = _emailController.text.trim(); final password = _passwordController.text; final displayName = _displayNameController.text.trim(); final notifier = ref.read(authNotifierProvider.notifier); if (_formMode == _AuthFormMode.signIn) { await notifier.signInWithEmailAndPassword(email, password); } else if (_formMode == _AuthFormMode.register) { await notifier.createUserWithEmailAndPassword( email, password, displayName: displayName.isNotEmpty ? displayName : null, ); } else if (_formMode == _AuthFormMode.forgotPassword) { try { await notifier.sendPasswordResetEmail(email); } catch (_) { return; } if (mounted && !ref.read(authNotifierProvider).hasError) { setState(() { _statusMessage = 'Password reset instructions sent. Check your email.'; }); } } } }