import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/localization/app_localizations.dart'; import '../../../core/theme/app_theme.dart'; import '../providers/auth_provider.dart'; /// 启动密码验证页面 class LoginPage extends ConsumerStatefulWidget { const LoginPage({super.key}); @override ConsumerState createState() => _LoginPageState(); } class _LoginPageState extends ConsumerState { final _passwordCtrl = TextEditingController(); final _focusNode = FocusNode(); String? _errorMsg; bool _submitting = false; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _focusNode.requestFocus(); }); } @override void dispose() { _passwordCtrl.dispose(); _focusNode.dispose(); super.dispose(); } Future _submit() async { if (_submitting) return; final password = _passwordCtrl.text.trim(); if (password.isEmpty) return; setState(() { _submitting = true; _errorMsg = null; }); final success = await ref.read(authProvider.notifier).verify(password); if (!mounted) return; _passwordCtrl.clear(); if (!success) { final l10n = AppLocalizations.of(context); setState(() { _submitting = false; _errorMsg = l10n?.passwordError ?? '密码错误'; }); } } void _onKeyEvent(KeyEvent event) { if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.enter) { _submit(); } } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); final authState = ref.watch(authProvider); final isLocked = authState.isLocked; final lockSeconds = authState.lockSecondsRemaining; return Scaffold( backgroundColor: AppTheme.bgPage, body: Center( child: Container( constraints: const BoxConstraints(maxWidth: 420), padding: const EdgeInsets.all(32), decoration: BoxDecoration( color: AppTheme.bgSurface, borderRadius: BorderRadius.circular(AppTheme.radiusLg), border: Border.all(color: AppTheme.borderLight), boxShadow: AppTheme.shadowCard, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // 设备名称标识 Icon( Icons.shield_outlined, size: 48, color: AppTheme.primaryColor, ), const SizedBox(height: 16), Text( l10n?.authTitle ?? '身份验证', style: TextStyle( color: AppTheme.textHeading, fontSize: 22, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 8), Text( l10n?.authSubtitle ?? '请输入操作员密码以继续使用', style: TextStyle( color: AppTheme.textSecondary, fontSize: 14, ), ), const SizedBox(height: 32), // 密码输入框 KeyboardListener( focusNode: FocusNode(), onKeyEvent: _onKeyEvent, child: TextField( controller: _passwordCtrl, focusNode: _focusNode, obscureText: true, enabled: !isLocked && !_submitting, decoration: InputDecoration( labelText: l10n?.enterPassword ?? '请输入密码', border: const OutlineInputBorder(), errorText: _errorMsg, prefixIcon: const Icon(Icons.lock_outline), ), ), ), const SizedBox(height: 24), // 确认按钮 / 锁定提示 if (isLocked) Column( children: [ Icon(Icons.lock_clock, size: 32, color: AppTheme.warningColor), const SizedBox(height: 8), Text( l10n?.lockCountdown(lockSeconds) ?? '请等待 $lockSeconds 秒后重试', style: TextStyle( color: AppTheme.warningColor, fontSize: 14, fontWeight: FontWeight.w500, ), ), ], ) else SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _submitting ? null : _submit, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 14), ), child: _submitting ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : Text(l10n?.confirm ?? '确认'), ), ), // 剩余尝试次数提示 if (!isLocked && authState.remainingAttempts < 5) Padding( padding: const EdgeInsets.only(top: 12), child: Text( l10n?.remainingAttempts(authState.remainingAttempts) ?? '剩余 ${authState.remainingAttempts} 次尝试机会', style: TextStyle( color: AppTheme.errorColor, fontSize: 12, ), ), ), ], ), ), ), ); } }