import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import '../../core/app_theme.dart'; import '../../core/navigation_provider.dart'; import '../../providers/auth_provider.dart'; class LoginPage extends ConsumerStatefulWidget { const LoginPage({super.key}); @override ConsumerState createState() => _LoginPageState(); } class _LoginPageState extends ConsumerState { final _phoneCtrl = TextEditingController(); final _codeCtrl = TextEditingController(); bool _agreed = false; bool _sending = false; int _countdown = 0; bool _loading = false; String? _error; @override void dispose() { _phoneCtrl.dispose(); _codeCtrl.dispose(); super.dispose(); } Future _sendSms() async { final phone = _phoneCtrl.text.trim(); if (phone.length != 11 || !phone.startsWith('1')) { setState(() => _error = '请输入正确的手机号'); return; } setState(() { _sending = true; _error = null; }); final result = await ref.read(authProvider.notifier).sendSms(phone); setState(() { _sending = false; }); if (result.error != null) { setState(() => _error = result.error); return; } if (result.devCode != null) _codeCtrl.text = result.devCode!; setState(() => _countdown = 60); _startCountdown(); } void _startCountdown() async { for (var i = 60; i > 0; i--) { await Future.delayed(const Duration(seconds: 1)); if (!mounted) return; setState(() => _countdown = i - 1); } } Future _login() async { if (!_agreed) { setState(() => _error = '请阅读并同意服务协议和隐私政策'); return; } setState(() { _loading = true; _error = null; }); final err = await ref.read(authProvider.notifier).login(_phoneCtrl.text.trim(), _codeCtrl.text.trim()); setState(() => _loading = false); if (err != null) { setState(() => _error = err); return; } goRoute(ref, 'home'); } @override Widget build(BuildContext context) { final authState = ref.watch(authProvider); final theme = ShadTheme.of(context); if (authState.isLoggedIn && !authState.isLoading) { WidgetsBinding.instance.addPostFrameCallback((_) => goRoute(ref, 'home')); } return Scaffold( body: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [AppTheme.bg, AppTheme.bg, AppTheme.primaryLight], ), ), child: SafeArea(child: Center(child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 32), child: Column(mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 24), // Logo Container( width: 100, height: 100, decoration: BoxDecoration( color: AppTheme.primary.withAlpha(25), borderRadius: BorderRadius.circular(50), ), child: Stack(alignment: Alignment.center, children: [ Container( width: 72, height: 72, decoration: BoxDecoration( color: Colors.white.withAlpha(200), borderRadius: BorderRadius.circular(36), ), child: const Icon(LucideIcons.heart, size: 36, color: AppTheme.primary), ), ]), ), const SizedBox(height: 20), Text('健康管家', style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: theme.colorScheme.foreground)), const SizedBox(height: 8), Text('你的 AI 心脏健康管家', style: TextStyle(fontSize: 18, color: theme.colorScheme.mutedForeground)), const SizedBox(height: 36), // 手机号输入 _buildPhoneInput(theme), const SizedBox(height: AppTheme.sLg), // 验证码输入 _buildCodeRow(theme), const SizedBox(height: AppTheme.sSm), // 协议勾选 _buildAgreement(theme), // 错误提示 if (_error != null) Padding(padding: const EdgeInsets.only(top: AppTheme.sMd), child: Text(_error!, style: TextStyle(color: theme.colorScheme.destructive, fontSize: 16))), const SizedBox(height: AppTheme.rXl), // 登录按钮 _buildLoginButton(theme), const SizedBox(height: AppTheme.sXl), ]), ))), ), ); } Widget _buildPhoneInput(ShadThemeData theme) { return Container( decoration: BoxDecoration( color: theme.colorScheme.card, borderRadius: BorderRadius.circular(AppTheme.rSm), boxShadow: [AppTheme.shadowLight], ), child: Row(children: [ const Padding(padding: EdgeInsets.only(left: AppTheme.sLg), child: Text('+86', style: TextStyle(fontSize: 19, fontWeight: FontWeight.w500, color: AppTheme.text))), Container(width: 1, height: 24, color: theme.colorScheme.border, margin: const EdgeInsets.symmetric(horizontal: AppTheme.sMd)), Expanded( child: TextField( controller: _phoneCtrl, keyboardType: TextInputType.phone, maxLength: 11, style: TextStyle(fontSize: 19, color: theme.colorScheme.foreground), decoration: InputDecoration( hintText: '请输入手机号', hintStyle: TextStyle(color: theme.colorScheme.mutedForeground), border: InputBorder.none, counterText: '', contentPadding: const EdgeInsets.symmetric(vertical: AppTheme.sLg), ), ), ), ]), ); } Widget _buildCodeRow(ShadThemeData theme) { final codeDisabled = _countdown > 0 || _sending; return Row(children: [ Expanded( child: Container( decoration: BoxDecoration( color: theme.colorScheme.card, borderRadius: BorderRadius.circular(AppTheme.rSm), boxShadow: [AppTheme.shadowLight], ), child: TextField( controller: _codeCtrl, keyboardType: TextInputType.number, maxLength: 6, style: TextStyle(fontSize: 19, color: theme.colorScheme.foreground), decoration: InputDecoration( hintText: '验证码', hintStyle: TextStyle(color: theme.colorScheme.mutedForeground), border: InputBorder.none, counterText: '', contentPadding: const EdgeInsets.symmetric(horizontal: AppTheme.sLg, vertical: AppTheme.sLg), ), ), ), ), const SizedBox(width: AppTheme.sMd), GestureDetector( onTap: codeDisabled ? null : _sendSms, child: AnimatedContainer( duration: const Duration(milliseconds: 200), width: 110, height: 52, alignment: Alignment.center, decoration: BoxDecoration( color: codeDisabled ? theme.colorScheme.muted : AppTheme.primary, borderRadius: BorderRadius.circular(AppTheme.rSm), ), child: Text( _sending ? '发送中' : _countdown > 0 ? '${_countdown}s' : '获取验证码', style: TextStyle( fontSize: 17, color: codeDisabled ? theme.colorScheme.mutedForeground : Colors.white, fontWeight: FontWeight.w500, ), ), ), ), ]); } Widget _buildAgreement(ShadThemeData theme) { return Align( alignment: Alignment.centerLeft, child: GestureDetector( onTap: () => setState(() => _agreed = !_agreed), child: Row(mainAxisSize: MainAxisSize.min, children: [ Container( width: 18, height: 18, margin: const EdgeInsets.only(right: 6), decoration: BoxDecoration( color: _agreed ? AppTheme.primary : Colors.transparent, borderRadius: BorderRadius.circular(4), border: Border.all( color: _agreed ? AppTheme.primary : theme.colorScheme.border, width: 1.5, ), ), child: _agreed ? Icon(LucideIcons.check, size: 15, color: theme.colorScheme.primaryForeground) : null, ), RichText(text: TextSpan(children: [ TextSpan(text: '已阅读并同意', style: TextStyle(fontSize: 16, color: theme.colorScheme.mutedForeground)), TextSpan(text: '《服务协议》', style: const TextStyle(fontSize: 16, color: AppTheme.primary)), TextSpan(text: '和', style: TextStyle(fontSize: 16, color: theme.colorScheme.mutedForeground)), TextSpan(text: '《隐私政策》', style: const TextStyle(fontSize: 16, color: AppTheme.primary)), ])), ]), ), ); } Widget _buildLoginButton(ShadThemeData theme) { return GestureDetector( onTap: _loading ? null : _login, child: Container( width: double.infinity, height: 52, alignment: Alignment.center, decoration: BoxDecoration( gradient: const LinearGradient(colors: [Color(0xFF9B8FEF), AppTheme.primary]), borderRadius: BorderRadius.circular(AppTheme.rPill), boxShadow: [BoxShadow(color: AppTheme.primary.withAlpha(80), blurRadius: 16, offset: const Offset(0, 8))], ), child: _loading ? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2.5, color: Colors.white)) : const Text('登 录', style: TextStyle(fontSize: 20, color: Colors.white, fontWeight: FontWeight.w600, letterSpacing: 2)), ), ); } }