Files
social-app/apps/lib/features/auth/ui/screens/login_screen.dart
T

184 lines
6.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:formz/formz.dart';
import 'package:go_router/go_router.dart';
import '../../../../core/di/injection.dart';
import '../../../../core/router/app_routes.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../../shared/widgets/app_button.dart';
import '../../../../shared/widgets/banner/app_banner.dart';
import '../../../../shared/widgets/link_button.dart';
import '../../../../shared/widgets/toast/toast_type.dart';
import '../../data/auth_repository.dart';
import '../../presentation/bloc/auth_bloc.dart';
import '../../presentation/bloc/auth_event.dart';
import '../../presentation/cubits/login_cubit.dart';
import '../widgets/auth_field.dart';
import '../widgets/auth_page_scaffold.dart';
import '../widgets/password_field.dart';
class LoginScreen extends StatelessWidget {
const LoginScreen({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LoginCubit(sl<AuthRepository>()),
child: const LoginView(),
);
}
}
class LoginView extends StatefulWidget {
const LoginView({super.key});
@override
State<LoginView> createState() => _LoginViewState();
}
class _LoginViewState extends State<LoginView> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
Future<void> _handleLogin() async {
final cubit = context.read<LoginCubit>();
cubit.emailChanged(_emailController.text);
cubit.passwordChanged(_passwordController.text);
if (!cubit.state.isValid) {
return;
}
final response = await cubit.submit();
if (response != null && mounted) {
context.read<AuthBloc>().add(AuthLoggedIn(user: response.user));
context.go(AppRoutes.homeMain);
}
}
@override
Widget build(BuildContext context) {
return AuthPageScaffold(
mainContentKey: const Key('login_main_content'),
footerKey: const Key('login_footer'),
mainContent: Padding(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.sm),
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 380),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const AuthHeroHeader(showBrand: true),
SizedBox(height: AppSpacing.xxl),
BlocBuilder<LoginCubit, LoginState>(
builder: (context, state) {
final fieldError = state.email.displayError != null
? state.email.error
: state.password.displayError != null
? state.password.error
: null;
return AuthSurfaceCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'登录账号',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: AppColors.slate900,
),
),
SizedBox(height: AppSpacing.xs),
SizedBox(height: AppSpacing.xl),
AuthSection(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
AuthField(
label: '邮箱',
hint: 'name@example.com',
controller: _emailController,
keyboardType: TextInputType.emailAddress,
),
SizedBox(height: AppSpacing.lg),
PasswordField(
controller: _passwordController,
label: '密码',
hint: '请输入密码',
),
if (state.errorMessage != null ||
fieldError != null)
Padding(
padding: const EdgeInsets.only(
top: AppSpacing.lg,
),
child: AppBanner(
message:
state.errorMessage ?? fieldError!,
type: state.errorMessage != null
? ToastType.error
: ToastType.warning,
title: state.errorMessage != null
? '登录失败'
: '请检查输入',
),
),
],
),
),
SizedBox(height: AppSpacing.xl),
AppButton(
text: '登录',
onPressed:
state.status == FormzSubmissionStatus.inProgress
? null
: _handleLogin,
isLoading:
state.status ==
FormzSubmissionStatus.inProgress,
),
SizedBox(height: AppSpacing.sm),
Align(
alignment: Alignment.centerRight,
child: LinkButton(
text: '忘记密码?',
onTap: () => context.push('/reset-password'),
),
),
],
),
);
},
),
],
),
),
),
),
footer: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text(
'还没有账号?',
style: TextStyle(fontSize: 14, color: AppColors.authLinkMuted),
),
LinkButton(text: '去注册', onTap: () => context.push('/register')),
],
),
);
}
}