import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import '../../core/app_colors.dart'; import '../../core/app_theme.dart'; import '../../core/navigation_provider.dart'; import '../../providers/auth_provider.dart'; import '../../providers/data_providers.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; } ref.invalidate(latestHealthProvider); ref.invalidate(medicationListProvider); ref.invalidate(medicationReminderProvider); ref.invalidate(currentExercisePlanProvider); goRoute(ref, 'home'); } @override Widget build(BuildContext context) { final authState = ref.watch(authProvider); if (authState.isLoggedIn && !authState.isLoading) { WidgetsBinding.instance.addPostFrameCallback((_) => goRoute(ref, 'home')); } return Scaffold( body: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Color(0xFFEEF2FF), Color(0xFFF5F3FF)], ), ), child: SafeArea(child: Center(child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 32), child: Column(mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 40), // Logo Container( width: 88, height: 88, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(44), boxShadow: [BoxShadow(color: AppColors.primary.withOpacity(0.15), blurRadius: 20, offset: const Offset(0, 8))], ), child: const Icon(LucideIcons.heart, size: 40, color: AppColors.primary), ), const SizedBox(height: 20), const Text('健康管家', style: TextStyle(fontSize: 28, fontWeight: FontWeight.w700, color: AppColors.textPrimary)), const SizedBox(height: 6), const Text('你的 AI 心脏健康管家', style: TextStyle(fontSize: 15, color: AppColors.textSecondary)), const SizedBox(height: 40), // 手机号 Container( decoration: BoxDecoration( gradient: AppColors.primaryGradient, borderRadius: BorderRadius.circular(10), ), padding: const EdgeInsets.all(1.5), child: Container( height: 52, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(9), ), child: Row(children: [ const Padding(padding: EdgeInsets.only(left: 16), child: Text('+86', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: AppColors.textPrimary))), Container(width: 1, height: 24, color: AppColors.border, margin: const EdgeInsets.symmetric(horizontal: 12)), Expanded( child: TextField( controller: _phoneCtrl, keyboardType: TextInputType.phone, maxLength: 11, style: const TextStyle(fontSize: 16, color: AppColors.textPrimary), decoration: const InputDecoration( filled: false, hintText: '请输入手机号', hintStyle: TextStyle(fontSize: 15, color: AppColors.textHint), border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, counterText: '', contentPadding: EdgeInsets.symmetric(vertical: 14), ), ), ), ]), ), ), const SizedBox(height: 16), // 验证码 Row(children: [ Expanded( child: Container( decoration: BoxDecoration( gradient: AppColors.primaryGradient, borderRadius: BorderRadius.circular(10), ), padding: const EdgeInsets.all(1.5), child: Container( height: 52, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(9), ), child: TextField( controller: _codeCtrl, keyboardType: TextInputType.number, maxLength: 6, style: const TextStyle(fontSize: 16, color: AppColors.textPrimary), decoration: const InputDecoration( filled: false, hintText: '验证码', hintStyle: TextStyle(fontSize: 15, color: AppColors.textHint), border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, counterText: '', contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), ), ), ), ), ), const SizedBox(width: 12), GestureDetector( onTap: (_countdown > 0 || _sending) ? null : _sendSms, child: Container( width: 120, height: 52, alignment: Alignment.center, decoration: BoxDecoration( color: (_countdown > 0 || _sending) ? Colors.white : AppColors.primary, borderRadius: BorderRadius.circular(10), ), child: Text( _sending ? '发送中' : _countdown > 0 ? '${_countdown}s' : '获取验证码', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: (_countdown > 0 || _sending) ? AppColors.textHint : Colors.white), ), ), ), ]), const SizedBox(height: 12), // 协议 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 ? AppColors.primary : Colors.white, borderRadius: BorderRadius.circular(4), border: Border.all(color: _agreed ? AppColors.primary : AppColors.border, width: 1.5), ), child: _agreed ? const Icon(Icons.check, size: 14, color: Colors.white) : null, ), RichText(text: TextSpan(children: [ TextSpan(text: '已阅读并同意', style: TextStyle(fontSize: 13, color: AppColors.textHint)), TextSpan(text: '《服务协议》', style: TextStyle(fontSize: 13, color: AppColors.primary)), TextSpan(text: '和', style: TextStyle(fontSize: 13, color: AppColors.textHint)), TextSpan(text: '《隐私政策》', style: TextStyle(fontSize: 13, color: AppColors.primary)), ])), ]), ), if (_error != null) Padding(padding: const EdgeInsets.only(top: 12), child: Text(_error!, style: const TextStyle(color: AppColors.error, fontSize: 14))), const SizedBox(height: 32), // 登录按钮 GestureDetector( onTap: _loading ? null : _login, child: Container( width: double.infinity, height: 52, alignment: Alignment.center, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.primary, width: 1.5), ), child: _loading ? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2.5, color: AppColors.primary)) : const Text('登 录', style: TextStyle(fontSize: 18, color: AppColors.primary, fontWeight: FontWeight.w600, letterSpacing: 2)), ), ), const SizedBox(height: 40), ]), ))), ), ); } }