import 'package:flutter/material.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:provider/provider.dart'; import '../../../core/theme/app_color_scheme.dart'; import '../../../core/theme/app_spacing.dart'; import '../../../core/theme/app_theme.dart'; import '../../../core/theme/app_theme_extension.dart'; import '../../../providers/auth_provider.dart'; import '../main/main_page.dart'; import 'register_page.dart'; class LoginPage extends StatefulWidget { const LoginPage({super.key}); @override State createState() => _LoginPageState(); } class _LoginPageState extends State { final formKey = GlobalKey(); final _usernameController = TextEditingController(); final _passwordController = TextEditingController(); bool _obscurePassword = true; static const _loadingIndicatorSize = 16.0; static const _logoCircleSize = 80.0; static const _inputHeight = 52.0; static const _buttonHeight = 52.0; @override void dispose() { _usernameController.dispose(); _passwordController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: context.colors.surface, body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.symmetric( horizontal: AppSpacing.xl, vertical: AppSpacing.xxl, ), child: ShadForm( key: formKey, child: Column( children: [ // 顶部品牌区域 _buildBrandSection(), const SizedBox(height: AppSpacing.xxl), // 表单区域 _buildFormSection(), const SizedBox(height: AppSpacing.xl), // 底部注册链接 _buildRegisterRow(), ], ), ), ), ), ); } // ============================================ // 品牌区域 - Logo + 品牌名 + 标语 // ============================================ Widget _buildBrandSection() { return Column( children: [ // Logo 圆形:渐变 #1F2937 → #374151,内含 "M" Container( width: _logoCircleSize, height: _logoCircleSize, decoration: const BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [AppColorScheme.darkSurfaceContainerHigh, AppColorScheme.darkOutline], ), ), alignment: Alignment.center, child: Text( 'M', style: AppTextStyles.displayLarge(context).copyWith( fontSize: 32, fontWeight: FontWeight.w800, color: context.colors.onPrimary, ), ), ), const SizedBox(height: AppSpacing.md), // 品牌名 "MONISUO" Text( 'MONISUO', style: AppTextStyles.displayLarge(context).copyWith( letterSpacing: 3, color: context.colors.onSurface, ), textAlign: TextAlign.center, ), const SizedBox(height: AppSpacing.md), // 标语 Text( '虚拟货币模拟交易平台', style: AppTextStyles.bodyLarge(context).copyWith( color: context.colors.onSurfaceVariant, ), textAlign: TextAlign.center, ), ], ); } // ============================================ // 表单区域 - 用户名 + 密码 + 登录按钮 // ============================================ Widget _buildFormSection() { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildUsernameField(), const SizedBox(height: AppSpacing.md), _buildPasswordField(), const SizedBox(height: AppSpacing.sm), _buildLoginButton(), ], ); } Widget _buildUsernameField() { return SizedBox( height: _inputHeight, child: ShadInputFormField( id: 'username', placeholder: const Text('请输入用户名'), leading: Padding( padding: const EdgeInsets.only(right: AppSpacing.sm), child: Icon(LucideIcons.user, size: 18, color: context.appColors.onSurfaceMuted), ), validator: _validateUsername, controller: _usernameController, decoration: ShadDecoration( border: ShadBorder.all( color: context.colors.outlineVariant, radius: AppRadius.radiusLg, ), ), style: AppTextStyles.headlineMedium(context).copyWith( color: context.colors.onSurface, ), ), ); } Widget _buildPasswordField() { final iconColor = context.appColors.onSurfaceMuted; return SizedBox( height: _inputHeight, child: ShadInputFormField( id: 'password', placeholder: const Text('请输入密码'), obscureText: _obscurePassword, leading: Padding( padding: const EdgeInsets.only(right: AppSpacing.sm), child: Icon(LucideIcons.lock, size: 18, color: iconColor), ), trailing: GestureDetector( onTap: () => setState(() => _obscurePassword = !_obscurePassword), child: Icon( _obscurePassword ? LucideIcons.eyeOff : LucideIcons.eye, size: 18, color: iconColor, ), ), validator: _validatePassword, controller: _passwordController, decoration: ShadDecoration( border: ShadBorder.all( color: context.colors.outlineVariant, radius: AppRadius.radiusLg, ), ), style: AppTextStyles.headlineMedium(context).copyWith( color: context.colors.onSurface, ), ), ); } Widget _buildLoginButton() { // 设计稿: accent-primary = light:#1F2937 / dark:#D4AF37 final buttonColor = context.appColors.accentPrimary; final textColor = context.colors.onPrimary; return Consumer( builder: (context, auth, _) { return SizedBox( height: _buttonHeight, child: ShadButton( onPressed: auth.isLoading ? null : () => _handleLogin(auth), backgroundColor: buttonColor, foregroundColor: textColor, decoration: ShadDecoration( border: ShadBorder.all( radius: AppRadius.radiusLg, ), ), child: auth.isLoading ? SizedBox.square( dimension: _loadingIndicatorSize, child: CircularProgressIndicator( strokeWidth: 2, color: textColor, ), ) : Text( '登录', style: AppTextStyles.headlineLarge(context).copyWith( fontWeight: FontWeight.w700, color: textColor, ), ), ), ); }, ); } // ============================================ // 底部注册链接 // ============================================ Widget _buildRegisterRow() { // gold-accent: light=#F59E0B / dark=#D4AF37 final goldColor = context.appColors.accentPrimary; final secondaryTextColor = context.colors.onSurfaceVariant; return Padding( padding: const EdgeInsets.only(bottom: AppSpacing.xl), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( '还没有账户?', style: AppTextStyles.bodyLarge(context).copyWith( color: secondaryTextColor, ), ), const SizedBox(width: AppSpacing.xs), GestureDetector( onTap: _navigateToRegister, child: Text( '立即注册', style: AppTextStyles.bodyLarge(context).copyWith( fontWeight: FontWeight.w600, color: goldColor, ), ), ), ], ), ); } // ============================================ // Validators // ============================================ String? _validateUsername(String? value) { if (value == null || value.isEmpty) { return '请输入用户名'; } if (value.length < 3) { return '用户名至少 3 个字符'; } return null; } String? _validatePassword(String? value) { if (value == null || value.isEmpty) { return '请输入密码'; } if (value.length < 6) { return '密码至少 6 个字符'; } return null; } // ============================================ // Actions // ============================================ Future _handleLogin(AuthProvider auth) async { if (!formKey.currentState!.saveAndValidate()) return; final values = formKey.currentState!.value; final response = await auth.login( values['username'], values['password'], ); if (!mounted) return; if (response.success) { _navigateToMainPage(); } else { _showErrorDialog(response.message ?? '用户名或密码错误'); } } void _navigateToMainPage() { Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute(builder: (context) => const MainPage()), (route) => false, ); } void _navigateToRegister() { Navigator.push( context, MaterialPageRoute(builder: (context) => const RegisterPage()), ); } void _showErrorDialog(String message) { showShadDialog( context: context, builder: (context) => ShadDialog.alert( title: const Text('登录失败'), description: Text(message), actions: [ ShadButton( child: const Text('确定'), onPressed: () => Navigator.of(context).pop(), ), ], ), ); } }