import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/theme/evolution_theme_config.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'; 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 themeMode = ref.watch(dashboardThemeModeProvider); final isEvolution = themeMode == DashboardThemeMode.evolution; final isLandscape = MediaQuery.of(context).orientation == Orientation.landscape; final bgColor = isEvolution ? Colors.black : Theme.of(context).scaffoldBackgroundColor; final titleFont = isEvolution ? 'Sora' : null; return Scaffold( backgroundColor: bgColor, appBar: AppBar( backgroundColor: isEvolution ? Colors.transparent : null, elevation: 0, title: Text( 'Account & Cloud Identity', style: TextStyle( fontFamily: titleFont, fontWeight: FontWeight.w800, fontSize: isLandscape ? 14 : 18, ), ), ), body: SafeArea( child: LayoutBuilder( builder: (context, constraints) { final isWide = constraints.maxWidth > 700; Widget content; if (authState.isAuthenticated) { content = _buildSignedInView( context, authState.user!, isEvolution, isWide, ); } else if (authState.isAnonymous && !_showAnonymousUpgrade) { content = _buildAnonymousView( context, authState.user!, isEvolution, isWide, ); } else { content = _buildSignedOutView( context, authState, isEvolution, isWide, ); } return SingleChildScrollView( padding: const EdgeInsets.symmetric( horizontal: 16.0, vertical: 12.0, ), child: Center( child: ConstrainedBox( constraints: BoxConstraints( maxWidth: isWide ? 640 : double.infinity, ), child: content, ), ), ); }, ), ), ); } Widget _buildSignedOutView( BuildContext context, AuthState authState, bool isEvolution, bool isWide, ) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildOfflineNoticeCard(isEvolution, authState.isFirebaseConfigured), const SizedBox(height: 16), _buildAuthCard( isEvolution: isEvolution, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( children: [ Icon( Icons.account_circle_outlined, color: const Color(0xFF00E5FF), size: 22, ), const SizedBox(width: 10), Text( _formMode == _AuthFormMode.signIn ? 'SIGN IN TO MOTORCLOUD' : (_formMode == _AuthFormMode.register ? 'CREATE ACCOUNT' : 'RESET PASSWORD'), style: TextStyle( fontFamily: isEvolution ? 'Sora' : null, fontSize: 13, fontWeight: FontWeight.w800, color: const Color(0xFF00E5FF), 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( fontFamily: isEvolution ? 'Inter' : null, fontSize: 11, color: Colors.white70, height: 1.35, ), ), const SizedBox(height: 16), if (authState.hasError) ...[ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.redAccent.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(8), border: Border.all( color: Colors.redAccent.withValues(alpha: 0.6), ), ), child: Row( children: [ const Icon( Icons.error_outline, color: Colors.redAccent, size: 18, ), const SizedBox(width: 8), Expanded( child: Text( authState.errorMessage!, style: const TextStyle( fontSize: 11, color: Colors.redAccent, ), ), ), ], ), ), const SizedBox(height: 16), ], if (_statusMessage != null) ...[ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.greenAccent.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(8), border: Border.all( color: Colors.greenAccent.withValues(alpha: 0.6), ), ), child: Row( children: [ const Icon( Icons.check_circle_outline, color: Colors.greenAccent, size: 18, ), const SizedBox(width: 8), Expanded( child: Text( _statusMessage!, style: const TextStyle( fontSize: 11, color: Colors.greenAccent, ), ), ), ], ), ), const SizedBox(height: 16), ], if (_formMode != _AuthFormMode.forgotPassword) ...[ ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: Colors.black87, padding: const EdgeInsets.symmetric(vertical: 12), 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), const Row( children: [ Expanded(child: Divider(color: Colors.white24)), Padding( padding: EdgeInsets.symmetric(horizontal: 10), child: Text( 'OR', style: TextStyle( color: Colors.white38, fontSize: 10, fontWeight: FontWeight.bold, ), ), ), Expanded(child: Divider(color: Colors.white24)), ], ), 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, fillColor: isEvolution ? const Color(0xFF141414) : Colors.white10, 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, fillColor: isEvolution ? const Color(0xFF141414) : Colors.white10, 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, color: Colors.white54, ), onPressed: () => setState( () => _obscurePassword = !_obscurePassword, ), ), filled: true, fillColor: isEvolution ? const Color(0xFF141414) : Colors.white10, 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: const Color(0xFF00E5FF), foregroundColor: Colors.black, padding: const EdgeInsets.symmetric(vertical: 12), 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), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ if (_formMode == _AuthFormMode.signIn) ...[ TextButton( onPressed: () => setState(() { _formMode = _AuthFormMode.forgotPassword; _statusMessage = null; }), child: const Text( 'Forgot Password?', style: TextStyle(fontSize: 11, color: Colors.white70), ), ), TextButton( onPressed: () => setState(() { _formMode = _AuthFormMode.register; _statusMessage = null; }), child: const Text( 'Create Account', style: TextStyle( fontSize: 11, color: Color(0xFF00E5FF), fontWeight: FontWeight.bold, ), ), ), ] else if (_formMode == _AuthFormMode.register) ...[ TextButton( onPressed: () => setState(() { _formMode = _AuthFormMode.signIn; _statusMessage = null; }), child: const Text( 'Already have an account? Sign In', style: TextStyle( fontSize: 11, color: Color(0xFF00E5FF), ), ), ), ] else ...[ TextButton( onPressed: () => setState(() { _formMode = _AuthFormMode.signIn; _statusMessage = null; }), child: const Text( 'Back to Sign In', style: TextStyle(fontSize: 11, color: Colors.white70), ), ), ], ], ), const Divider(color: Colors.white12, height: 24), OutlinedButton.icon( style: OutlinedButton.styleFrom( foregroundColor: Colors.white70, side: const BorderSide(color: Colors.white24), padding: const EdgeInsets.symmetric(vertical: 10), 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(), ), ], ), ), ], ); } Widget _buildSignedInView( BuildContext context, AuthUser user, bool isEvolution, bool isWide, ) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildAuthCard( isEvolution: isEvolution, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ CircleAvatar( radius: 28, backgroundColor: const Color( 0xFF00E5FF, ).withValues(alpha: 0.2), backgroundImage: user.photoUrl != null ? NetworkImage(user.photoUrl!) : null, child: user.photoUrl == null ? const Icon( Icons.person, color: Color(0xFF00E5FF), size: 32, ) : null, ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( user.displayLabel, style: TextStyle( fontFamily: isEvolution ? 'Sora' : null, fontSize: 18, fontWeight: FontWeight.w800, color: Colors.white, ), ), const SizedBox(height: 2), Text( user.email ?? 'No email associated', style: const TextStyle( fontSize: 12, color: Colors.white70, ), ), const SizedBox(height: 6), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2, ), decoration: BoxDecoration( color: const Color( 0xFF00E5FF, ).withValues(alpha: 0.15), borderRadius: BorderRadius.circular(4), border: Border.all( color: const Color(0xFF00E5FF), width: 0.8, ), ), child: Text( 'AUTHENTICATED (${user.providerId ?? "cloud"})', style: const TextStyle( fontFamily: 'Inter', fontSize: 9, fontWeight: FontWeight.w900, color: Color(0xFF00E5FF), letterSpacing: 0.5, ), ), ), ], ), ), ], ), const Divider(height: 32, color: Colors.white12), _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: Colors.amberAccent.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.amberAccent.withValues(alpha: 0.5)), ), child: Row( children: [ const Expanded(child: Text('Your email is not verified. Verify it to protect account recovery.', style: TextStyle(fontSize: 11, color: Colors.white))), TextButton(onPressed: () => _sendVerification(), child: const Text('VERIFY')), IconButton(tooltip: 'Refresh verification status', onPressed: () => _reloadUser(), icon: const Icon(Icons.refresh, size: 18)), ], ), ), ], const SizedBox(height: 24), SizedBox( width: double.infinity, child: OutlinedButton.icon( style: OutlinedButton.styleFrom( foregroundColor: Colors.redAccent, side: const BorderSide(color: Colors.redAccent), padding: const EdgeInsets.symmetric(vertical: 12), 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: Colors.redAccent), icon: const Icon(Icons.delete_forever_outlined, size: 18), label: const Text('DELETE ACCOUNT'), onPressed: () => _handleDeleteAccount(user), ), ), ], ), ), const SizedBox(height: 16), _buildCloudSyncSection( context: context, isEvolution: isEvolution, isAuthenticated: true, ), const SizedBox(height: 16), _buildLocalRidingActiveCard(isEvolution), ], ); } Widget _buildAnonymousView( BuildContext context, AuthUser user, bool isEvolution, bool isWide, ) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildAuthCard( isEvolution: isEvolution, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ CircleAvatar( radius: 28, backgroundColor: Colors.amberAccent.withValues(alpha: 0.2), child: const Icon( Icons.person_outline, color: Colors.amberAccent, size: 32, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Guest / Anonymous Rider', style: TextStyle( fontFamily: isEvolution ? 'Sora' : null, fontSize: 17, fontWeight: FontWeight.w800, color: Colors.white, ), ), const SizedBox(height: 4), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2, ), decoration: BoxDecoration( color: Colors.amberAccent.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(4), border: Border.all( color: Colors.amberAccent, width: 0.8, ), ), child: const Text( 'OFFLINE GUEST SESSION', style: TextStyle( fontFamily: 'Inter', fontSize: 9, fontWeight: FontWeight.w900, color: Colors.amberAccent, letterSpacing: 0.5, ), ), ), ], ), ), ], ), const Divider(height: 24, color: Colors.white12), _buildInfoRow('Session ID', user.uid, isMonospace: true), const SizedBox(height: 16), const 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: Colors.white70, height: 1.4, ), ), const SizedBox(height: 20), SizedBox( width: double.infinity, child: ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF00E5FF), foregroundColor: Colors.black, padding: const EdgeInsets.symmetric(vertical: 12), 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), ), ), ], ), ), const SizedBox(height: 16), _buildCloudSyncSection( context: context, isEvolution: isEvolution, isAuthenticated: false, ), const SizedBox(height: 16), _buildLocalRidingActiveCard(isEvolution), ], ); } Widget _buildCloudSyncSection({ required BuildContext context, required bool isEvolution, required bool isAuthenticated, }) { final syncState = ref.watch(syncStateProvider); final syncManager = ref.watch(syncManagerProvider); final restoreState = ref.watch(cloudRestoreControllerProvider); final restoreController = ref.read(cloudRestoreControllerProvider.notifier); return _buildAuthCard( isEvolution: isEvolution, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Icon( Icons.cloud_sync_outlined, color: isEvolution ? const Color(0xFF00E5FF) : Colors.blueAccent, size: 20, ), const SizedBox(width: 8), Text( 'MOTORCLOUD SYNC', style: TextStyle( fontFamily: isEvolution ? 'Sora' : null, fontSize: 12, fontWeight: FontWeight.w800, color: isEvolution ? const Color(0xFF00E5FF) : Colors.white, letterSpacing: 0.5, ), ), ], ), if (syncState.isProcessing) const Row( children: [ SizedBox( width: 12, height: 12, child: CircularProgressIndicator( strokeWidth: 2, color: Color(0xFF00E5FF), ), ), SizedBox(width: 6), Text( 'Syncing...', style: TextStyle(fontSize: 10, color: Colors.white70), ), ], ), ], ), const SizedBox(height: 12), if (!isAuthenticated) ...[ const 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: Colors.white70, height: 1.4, ), ), ] else ...[ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.04), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.white12), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildSyncStatItem( 'Synced', '${syncState.syncedCount}', Colors.greenAccent, ), _buildSyncStatItem( 'Pending', '${syncState.pendingCount}', Colors.amberAccent, ), _buildSyncStatItem( 'Failed', '${syncState.failedCount}', Colors.redAccent, ), ], ), ), const SizedBox(height: 12), if (syncState.failedCount > 0) ...[ Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 6, ), decoration: BoxDecoration( color: Colors.redAccent.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(6), border: Border.all( color: Colors.redAccent.withValues(alpha: 0.4), ), ), child: Row( children: [ const Icon( Icons.warning_amber_rounded, color: Colors.redAccent, size: 16, ), const SizedBox(width: 8), Expanded( child: Text( '${syncState.failedCount} ride(s) need attention. Tap Retry to process.', style: const TextStyle( fontSize: 11, color: Colors.white, ), ), ), TextButton( style: TextButton.styleFrom( visualDensity: VisualDensity.compact, foregroundColor: Colors.redAccent, ), 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: const Color(0xFF00E5FF), foregroundColor: Colors.black, padding: const EdgeInsets.symmetric(vertical: 10), 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: Colors.white, side: const BorderSide(color: Colors.white24), padding: const EdgeInsets.symmetric(vertical: 10), 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), const Text( 'Download your backed-up ride history and telemetry to this device.', style: TextStyle( fontSize: 11, color: Colors.white70, height: 1.35, ), ), const SizedBox(height: 10), SizedBox( width: double.infinity, child: OutlinedButton.icon( style: OutlinedButton.styleFrom( foregroundColor: Colors.white, side: const BorderSide(color: Colors.white24), padding: const EdgeInsets.symmetric(vertical: 10), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), icon: restoreState.isRestoring ? const SizedBox( width: 14, height: 14, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : 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, color: Colors.white, 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: const TextStyle( fontSize: 9, fontWeight: FontWeight.w700, color: Colors.white60, 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.'), 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) { switch (status) { case CloudRestoreStatus.success: return Colors.greenAccent; case CloudRestoreStatus.partialFailure: return Colors.amberAccent; case CloudRestoreStatus.authRequired: case CloudRestoreStatus.unavailable: case CloudRestoreStatus.failure: return Colors.redAccent; case CloudRestoreStatus.idle: case CloudRestoreStatus.restoring: return const Color(0xFF00E5FF); } } 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 isEvolution, bool isFirebaseConfigured) { return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: isEvolution ? const Color(0xFF0D0D0D) : const Color(0xFF1B2838), borderRadius: BorderRadius.circular(12), border: Border.all( color: isFirebaseConfigured ? const Color(0xFF00E5FF).withValues(alpha: 0.3) : Colors.amberAccent.withValues(alpha: 0.4), width: 1.2, ), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon( isFirebaseConfigured ? Icons.cloud_done_outlined : Icons.offline_bolt_outlined, color: isFirebaseConfigured ? const Color(0xFF00E5FF) : Colors.amberAccent, size: 20, ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( isFirebaseConfigured ? 'OFFLINE-FIRST ARCHITECTURE' : 'STANDALONE OFFLINE MODE', style: TextStyle( fontFamily: isEvolution ? 'Sora' : null, fontSize: 11, fontWeight: FontWeight.w800, color: isFirebaseConfigured ? const Color(0xFF00E5FF) : Colors.amberAccent, 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: const TextStyle( fontSize: 11, color: Colors.white70, height: 1.35, ), ), ], ), ), ], ), ); } Widget _buildLocalRidingActiveCard(bool isEvolution) { return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: isEvolution ? const Color(0xFF0D0D0D) : const Color(0xFF141414), borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFF1F1F1F), width: 1.2), ), child: const Row( children: [ Icon(Icons.shield_outlined, color: Colors.greenAccent, size: 20), SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'LOCAL DATA ISOLATION ACTIVE', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w800, color: Colors.greenAccent, letterSpacing: 0.6, ), ), 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: Colors.white60), ), ], ), ), ], ), ); } Widget _buildAuthCard({required bool isEvolution, required Widget child}) { return Container( padding: const EdgeInsets.all(18), decoration: BoxDecoration( color: isEvolution ? const Color(0xFF0D0D0D) : null, borderRadius: BorderRadius.circular(14), border: Border.all( color: isEvolution ? const Color(0xFF1F1F1F) : Colors.white12, width: 1.2, ), ), 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: const TextStyle(fontSize: 11, color: Colors.white54), ), Flexible( child: Text( value, textAlign: TextAlign.end, overflow: TextOverflow.ellipsis, style: TextStyle( fontFamily: isMonospace ? 'monospace' : null, fontSize: 11, fontWeight: FontWeight.w600, color: Colors.white, ), ), ), ], ), ); } 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.'; }); } } } }