import 'package:flutter/material.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 '../../components/material_input.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 _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: Form( 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 MaterialInput( controller: _usernameController, labelText: '用戶名', hintText: '請輸入用戶名', prefixIcon: Icons.person_outline, validator: _validateUsername, ); } Widget _buildPasswordField() { return MaterialPasswordInput( controller: _passwordController, labelText: '密碼', hintText: '請輸入密碼', prefixIcon: Icons.lock_outline, validator: _validatePassword, ); } 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: ElevatedButton( onPressed: auth.isLoading ? null : () => _handleLogin(auth), style: ElevatedButton.styleFrom( backgroundColor: buttonColor, foregroundColor: textColor, disabledBackgroundColor: buttonColor.withValues(alpha: 0.5), disabledForegroundColor: textColor.withValues(alpha: 0.5), elevation: 0, shadowColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.lg), ), padding: const EdgeInsets.symmetric( horizontal: AppSpacing.xl, vertical: AppSpacing.md, ), ), 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!.validate()) return; final response = await auth.login( _usernameController.text.trim(), _passwordController.text, ); 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) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('登錄失敗'), content: Text(message), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('確定'), ), ], ), ); } }