import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../core/data/remote/api_client.dart'; import '../../core/data/services/auth_controller.dart'; import '../../core/theme/app_colors.dart'; import '../../core/theme/app_theme.dart'; import '../../l10n/app_localizations.dart'; import 'auth_logic.dart'; /// 登录 / 注册页:邮箱 + 密码。 /// /// 风格沿用 FruitShorts 现有页面(深色卡片 + 圆角 + 主色蓝),不另起一套视觉。 class AuthPage extends StatefulWidget { const AuthPage({super.key, this.signUpMode = false}); final bool signUpMode; @override State createState() => _AuthPageState(); } class _AuthPageState extends State { late AuthLogic _logic; @override void initState() { super.initState(); _logic = AuthLogic(signUpMode: widget.signUpMode); } String _errorText(AppLocalizations l10n, AuthFieldError error) { switch (error) { case AuthFieldError.emailRequired: return l10n.authEmailRequired; case AuthFieldError.emailInvalid: return l10n.authEmailInvalid; case AuthFieldError.passwordRequired: return l10n.authPasswordRequired; case AuthFieldError.passwordLength: return l10n.authPasswordLength; case AuthFieldError.passwordWeak: return l10n.authPasswordWeak; } } Future _submit() async { final l10n = AppLocalizations.of(context)!; final invalid = _logic.validate(); if (invalid != null) { setState(() => _logic.errorMessage = _errorText(l10n, invalid)); return; } setState(() { _logic.busy = true; _logic.errorMessage = null; }); final auth = context.read(); try { if (_logic.signUpMode) { await auth.register( email: _logic.normalizedEmail, password: _logic.password, ); } else { await auth.signIn( email: _logic.normalizedEmail, password: _logic.password, ); } if (!mounted) return; Navigator.of(context).maybePop(); } on ApiException catch (error) { // 后端文案已经是可直接展示的中文/印尼语提示,错误码不外露 setState(() => _logic.errorMessage = error.message); } catch (_) { setState(() => _logic.errorMessage = l10n.authNetworkError); } finally { if (mounted) setState(() => _logic.busy = false); } } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return Theme( data: AppTheme.dark(), child: Scaffold( backgroundColor: AppColors.ink, appBar: AppBar( backgroundColor: AppColors.ink, elevation: 0, foregroundColor: Colors.white, title: Text(_logic.signUpMode ? l10n.signUp : l10n.signIn), ), body: SafeArea( child: ListView( padding: const EdgeInsets.fromLTRB(20, 12, 20, 28), children: [ Text( _logic.signUpMode ? l10n.authSignUpTitle : l10n.authSignInTitle, style: const TextStyle( color: Colors.white, fontSize: 22, fontWeight: FontWeight.w800, ), ), const SizedBox(height: 6), Text( l10n.authSubtitle, style: const TextStyle(color: AppColors.muted, fontSize: 13), ), const SizedBox(height: 22), _field( label: l10n.authEmailLabel, hint: 'name@example.com', keyboardType: TextInputType.emailAddress, onChanged: (value) => _logic.email = value, autofocus: true, ), const SizedBox(height: 14), _field( label: l10n.authPasswordLabel, hint: l10n.authPasswordHint, obscure: true, onChanged: (value) => _logic.password = value, ), if (_logic.errorMessage != null) ...[ const SizedBox(height: 12), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Icon( Icons.error_outline, color: Color(0xFFFF6B6B), size: 16, ), const SizedBox(width: 6), Expanded( child: Text( _logic.errorMessage!, style: const TextStyle( color: Color(0xFFFF6B6B), fontSize: 13, ), ), ), ], ), ], const SizedBox(height: 22), SizedBox( height: 48, child: FilledButton( style: FilledButton.styleFrom( backgroundColor: AppColors.blue, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24), ), ), onPressed: _logic.busy ? null : _submit, child: _logic.busy ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : Text( _logic.signUpMode ? l10n.signUp : l10n.signIn, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w700, ), ), ), ), const SizedBox(height: 14), TextButton( onPressed: _logic.busy ? null : () => setState(() => _logic.toggleMode()), child: Text( _logic.signUpMode ? l10n.authSwitchToSignIn : l10n.authSwitchToSignUp, style: const TextStyle( color: AppColors.blueSoft, fontSize: 13, ), ), ), ], ), ), ), ); } Widget _field({ required String label, required String hint, required ValueChanged onChanged, bool obscure = false, bool autofocus = false, TextInputType? keyboardType, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: const TextStyle( color: AppColors.muted, fontSize: 12, fontWeight: FontWeight.w700, ), ), const SizedBox(height: 6), TextField( autofocus: autofocus, obscureText: obscure, keyboardType: keyboardType, onChanged: onChanged, style: const TextStyle(color: Colors.white, fontSize: 15), decoration: InputDecoration( hintText: hint, hintStyle: const TextStyle(color: Color(0xFF6C7280), fontSize: 14), filled: true, fillColor: AppColors.card, contentPadding: const EdgeInsets.symmetric( horizontal: 14, vertical: 14, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), ), ), ], ); } }