import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:miaoji_zhang/shared/api/api_client.dart'; import 'package:miaoji_zhang/shared/api/auth_api.dart'; import 'package:miaoji_zhang/shared/api/config_api.dart'; import 'package:miaoji_zhang/features/auth/local_data_recovery.dart'; import 'package:miaoji_zhang/shared/services/current_ledger_store.dart'; import 'package:miaoji_zhang/shared/services/local_database.dart'; import 'package:miaoji_zhang/shared/services/session_store.dart'; import 'package:miaoji_zhang/shared/services/push_service.dart'; import 'package:miaoji_zhang/shared/theme/app_theme.dart'; import 'package:miaoji_zhang/shared/version.dart'; import 'package:miaoji_zhang/shared/widgets/app_controls.dart'; import 'package:miaoji_zhang/shared/widgets/brand_logo.dart'; class LoginPage extends StatefulWidget { final String? notice; const LoginPage({super.key, this.notice}); @override State createState() => _LoginPageState(); } class _LoginPageState extends State { final _user = TextEditingController(); final _pass = TextEditingController(); bool _loading = false; @override void initState() { super.initState(); PublicConfigApi.init().ignore(); if (widget.notice case final notice? when notice.isNotEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(notice))); } }); } } @override void dispose() { _user.dispose(); _pass.dispose(); super.dispose(); } Future _login() async { if (_user.text.trim().isEmpty || _pass.text.isEmpty) { _showMessage('请输入用户名和密码'); return; } final guestSnapshot = SessionStore.instance.isGuest ? LocalDatabase.instance.guestSnapshot() : null; setState(() => _loading = true); try { final result = await AuthApi.login(_user.text.trim(), _pass.text); final profile = result.profile; await PushService.instance.refresh(); await CurrentLedgerStore.instance.ensureLoaded(force: true); if (!mounted) return; await offerLocalDataRecovery(context, guestSnapshot: guestSnapshot); if (!mounted) return; if (result.accountClosureCancelled) _showMessage('已取消账号注销,欢迎回来'); final destination = !profile.onboardingDone ? '/onboarding' : profile.appMode == 'ai' ? '/ai-mode' : '/home'; context.go(destination); } catch (error) { if (mounted) _showMessage(apiErrorMessage(error)); } finally { if (mounted) setState(() => _loading = false); } } Future _startGuest() async { setState(() => _loading = true); try { await SessionStore.instance.startGuest(); await CurrentLedgerStore.instance.ensureLoaded(force: true); if (mounted) context.go('/home'); } catch (error) { if (mounted) _showMessage(apiErrorMessage(error)); } finally { if (mounted) setState(() => _loading = false); } } void _showMessage(String message) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(message))); } @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: ListView( padding: const EdgeInsets.symmetric(horizontal: 28), children: [ SizedBox(height: 48), Center( child: Column( children: [ const BrandLogo(size: 82), SizedBox(height: 16), Text( PublicConfigApi.appName, style: TextStyle( fontSize: 26, fontWeight: FontWeight.w800, letterSpacing: 1, ), ), SizedBox(height: 8), Text( PublicConfigApi.slogan, style: TextStyle(fontSize: 13, color: context.jz.text2), ), SizedBox(height: 48), ], ), ), TextField( controller: _user, textInputAction: TextInputAction.next, decoration: InputDecoration(labelText: '用户名', hintText: '请输入用户名'), ), SizedBox(height: 12), TextField( controller: _pass, obscureText: true, decoration: InputDecoration(labelText: '密码', hintText: '请输入密码'), onSubmitted: (_) => _loading ? null : _login(), ), SizedBox(height: 22), JzActionButton( label: '登录', loading: _loading, onPressed: _loading ? null : _login, ), SizedBox(height: 10), JzActionButton( label: '游客模式', secondary: true, onPressed: _loading ? null : _startGuest, ), SizedBox(height: 7), Text( '游客数据仅保存在本机;AI、语音解析和图片识别需登录联网', textAlign: TextAlign.center, style: TextStyle( fontSize: 10.5, color: context.jz.text3, height: 1.45, ), ), SizedBox(height: 8), Center( child: TextButton( onPressed: _loading ? null : () => context.go('/register'), child: Text('没有账号?去注册'), ), ), const LoginLegalLinks(), SizedBox(height: 20), Center( child: Text( AppVersion.display, style: TextStyle(fontSize: 10, color: context.jz.text3), ), ), ], ), ), ); } } class LoginLegalLinks extends StatelessWidget { const LoginLegalLinks({super.key}); @override Widget build(BuildContext context) { return Wrap( alignment: WrapAlignment.center, spacing: 2, runSpacing: 0, children: [ _link(context, '用户协议', 'terms'), _link(context, '隐私政策', 'privacy'), ], ); } Widget _link(BuildContext context, String label, String kind) => TextButton( onPressed: () => context.push('/legal/$kind'), child: Text(label, style: TextStyle(fontSize: 11.5)), ); }