import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:drift/drift.dart' hide Column; import 'package:slrpg_app/l10n/app_localizations.dart'; import '../../../../core/theme/app_theme.dart'; import '../../../../shared/data/repositories/user_repository.dart'; import '../../../../shared/data/repositories/cycle_repository.dart'; import '../../../gamification/domain/entities/avatar_config.dart'; import '../../../gamification/presentation/widgets/avatar_editor.dart'; import 'bodyweight_input_screen.dart'; class AvatarSetupScreen extends ConsumerStatefulWidget { const AvatarSetupScreen({super.key}); @override ConsumerState createState() => _AvatarSetupScreenState(); } class _AvatarSetupScreenState extends ConsumerState { AvatarConfig _config = const AvatarConfig(); bool _isLoading = false; Future _handleFinish() async { final password = await _showPasswordDialog(); if (password == null) return; setState(() => _isLoading = true); try { final onboardingData = ref.read(onboardingDataProvider); final userRepo = ref.read(userRepositoryProvider); final inventorySettings = (onboardingData['inventory_settings'] as Map?) ?? {}; final exerciseVariants = onboardingData['exercise_variants'] as Map?; var user = await userRepo.getLocalUser(); final avatarJson = _config.toJson(); if (user == null) { final email = onboardingData['email'] as String? ?? ''; final String username = onboardingData['username'] as String; final bodyweight = (onboardingData['bodyweight'] as num?)?.toDouble() ?? 80.0; if (email.isEmpty || password.isEmpty) { throw Exception('Email or password is missing!'); } user = await userRepo.register( email: email, username: username, password: password, bodyweight: bodyweight, inventorySettings: inventorySettings, exerciseVariants: exerciseVariants, avatarConfig: avatarJson, ); await Future.delayed(const Duration(milliseconds: 100)); user = await userRepo.getLocalUser(); await ref.read(apiClientProvider).requestVerification(email); if (user == null) { throw Exception( 'User registration succeeded but user not found in DB'); } } else { user = user.copyWith( currentBodyweight: (onboardingData['bodyweight'] as num?)?.toDouble() ?? user.currentBodyweight, inventorySettings: Value(inventorySettings), isDirty: true, ); await userRepo.saveLocalUser(user); } user = user.copyWith( avatarConfig: Value(avatarJson), isDirty: true, ); await userRepo.saveLocalUser(user); try { final trainingMaxes = onboardingData['training_maxes'] as Map?; if (trainingMaxes != null && trainingMaxes.isNotEmpty) { final cycleRepo = ref.read(cycleRepositoryProvider); final tmMap = { 'squat': (trainingMaxes['squat'] as num?)?.toDouble() ?? 100.0, 'pullup': (trainingMaxes['pullup'] as num?)?.toDouble() ?? 80.0, 'dip': (trainingMaxes['dip'] as num?)?.toDouble() ?? 90.0, }; final cycle = await cycleRepo.createCycle(tmMap); } } catch (e, stackTrace) { log('❌ CYCLE ERROR (non-critical): $e'); log(' Error type: ${e.runtimeType}'); log(' Stack:\n$stackTrace'); } if (mounted) { ref.read(onboardingDataProvider.notifier).clear(); context.go('/hub'); } } catch (e, stackTrace) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Setup failed: $e'), backgroundColor: AppTheme.errorColor, duration: const Duration(seconds: 5), ), ); } } finally { if (mounted) setState(() => _isLoading = false); } } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return Scaffold( appBar: AppBar( title: Text(l10n.setupAvatarTitle), actions: [ TextButton( onPressed: _isLoading ? null : _handleFinish, child: _isLoading ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2)) : Text(l10n.finishButton, style: TextStyle( fontWeight: FontWeight.bold, color: AppTheme.primaryColor)), ) ], ), body: Column( children: [ Container( padding: const EdgeInsets.all(16), color: AppTheme.surfaceColor, width: double.infinity, child: Text( l10n.setupAvatarSubtitle, textAlign: TextAlign.center, style: TextStyle(fontStyle: FontStyle.italic, color: Colors.grey), ), ), Expanded( child: AvatarEditor( initialConfig: _config, onChanged: (newConfig) => _config = newConfig, ), ), ], ), ); } Future _showPasswordDialog() async { final passwordController = TextEditingController(); final confirmController = TextEditingController(); final formKey = GlobalKey(); final l10n = AppLocalizations.of(context)!; return showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: Text(l10n.secureAccountTitle), content: Form( key: formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(l10n.secureAccountBody), const SizedBox(height: 16), TextFormField( controller: passwordController, obscureText: true, autofocus: true, decoration: InputDecoration( labelText: l10n.passwordLabel, prefixIcon: Icon(Icons.lock), ), validator: (v) => (v?.length ?? 0) < 8 ? 'Min 8 characters' : null, ), const SizedBox(height: 16), TextFormField( controller: confirmController, obscureText: true, decoration: InputDecoration( labelText: l10n.confirmPasswordLabel, prefixIcon: Icon(Icons.lock_outline), ), validator: (v) => v != passwordController.text ? l10n.passwordsDoNotMatch : null, ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text(l10n.cancelButton), ), ElevatedButton( onPressed: () { if (formKey.currentState!.validate()) { Navigator.pop(context, passwordController.text); } }, child: Text(l10n.confirmButton), ), ], ), ); } }