feat: login api
This commit is contained in:
@@ -1,30 +1,32 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import '../../../../application/auth/login_form/login_form_bloc.dart';
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../injection.dart';
|
||||
import '../../../components/button/button.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
import '../../../components/toast/flushbar.dart';
|
||||
import '../../../router/app_router.gr.dart';
|
||||
import 'widgets/email_field.dart';
|
||||
import 'widgets/password_field.dart';
|
||||
|
||||
@RoutePage()
|
||||
class LoginPage extends StatefulWidget {
|
||||
class LoginPage extends StatefulWidget implements AutoRouteWrapper {
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
State<LoginPage> createState() => _LoginPageState();
|
||||
|
||||
@override
|
||||
Widget wrappedRoute(BuildContext context) =>
|
||||
BlocProvider(create: (_) => getIt<LoginFormBloc>(), child: this);
|
||||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _emailController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
|
||||
bool _isLoading = false;
|
||||
|
||||
late AnimationController _fadeController;
|
||||
late AnimationController _slideController;
|
||||
late AnimationController _backgroundController;
|
||||
@@ -77,9 +79,6 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
||||
CurvedAnimation(parent: _floatingController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
_emailController.text = 'test@example.com';
|
||||
_passwordController.text = 'password';
|
||||
|
||||
_fadeController.forward();
|
||||
_slideController.forward();
|
||||
}
|
||||
@@ -90,78 +89,88 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
||||
_slideController.dispose();
|
||||
_backgroundController.dispose();
|
||||
_floatingController.dispose();
|
||||
_emailController.dispose();
|
||||
_passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _handleLogin() async {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
// Simulasi proses login
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
|
||||
context.router.replace(const MainRoute());
|
||||
}
|
||||
context.read<LoginFormBloc>().add(LoginFormEvent.submitted());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: AnimatedBuilder(
|
||||
animation: Listenable.merge([
|
||||
_backgroundController,
|
||||
_floatingController,
|
||||
]),
|
||||
builder: (context, child) {
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: AppColor.primaryGradient,
|
||||
return BlocListener<LoginFormBloc, LoginFormState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.failureOrAuthOption != current.failureOrAuthOption,
|
||||
listener: (context, state) {
|
||||
state.failureOrAuthOption.fold(
|
||||
() => null,
|
||||
(either) => either.fold(
|
||||
(f) => AppFlushbar.showAuthFailureToast(context, f),
|
||||
(user) {
|
||||
if (context.mounted) {
|
||||
context.router.replace(const MainRoute());
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Scaffold(
|
||||
body: AnimatedBuilder(
|
||||
animation: Listenable.merge([
|
||||
_backgroundController,
|
||||
_floatingController,
|
||||
]),
|
||||
builder: (context, child) {
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: AppColor.primaryGradient,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
// Animated background elements
|
||||
_buildAnimatedBackground(),
|
||||
child: Stack(
|
||||
children: [
|
||||
// Animated background elements
|
||||
_buildAnimatedBackground(),
|
||||
|
||||
// Main content
|
||||
SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: AppValue.padding,
|
||||
),
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_buildLogo(context),
|
||||
SpaceHeight(48),
|
||||
_buildLoginCard(context),
|
||||
],
|
||||
// Main content
|
||||
SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: AppValue.padding,
|
||||
),
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: BlocBuilder<LoginFormBloc, LoginFormState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_buildLogo(context),
|
||||
SpaceHeight(48),
|
||||
_buildLoginCard(
|
||||
context,
|
||||
state.isSubmitting,
|
||||
state.showErrorMessages,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -312,7 +321,11 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoginCard(BuildContext context) {
|
||||
Widget _buildLoginCard(
|
||||
BuildContext context,
|
||||
bool isLoading,
|
||||
bool showErrorMessages,
|
||||
) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 24),
|
||||
@@ -335,17 +348,20 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
||||
],
|
||||
),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
autovalidateMode: showErrorMessages
|
||||
? AutovalidateMode.always
|
||||
: AutovalidateMode.disabled,
|
||||
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
LoginEmailField(controller: _emailController),
|
||||
LoginEmailField(),
|
||||
const SpaceHeight(24),
|
||||
LoginPasswordField(controller: _passwordController),
|
||||
LoginPasswordField(),
|
||||
const SpaceHeight(16),
|
||||
_buildForgetPassword(context),
|
||||
const SpaceHeight(32),
|
||||
_buildLoginButton(),
|
||||
_buildLoginButton(isLoading),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -368,11 +384,11 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoginButton() {
|
||||
Widget _buildLoginButton(bool isLoading) {
|
||||
return AppElevatedButton(
|
||||
text: context.lang.sign_in,
|
||||
isLoading: _isLoading,
|
||||
onPressed: _isLoading ? null : _handleLogin,
|
||||
isLoading: isLoading,
|
||||
onPressed: _handleLogin,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
import '../../../../../application/auth/login_form/login_form_bloc.dart';
|
||||
import '../../../../../common/extension/extension.dart';
|
||||
import '../../../../../common/validator/validator.dart';
|
||||
import '../../../../components/field/field.dart';
|
||||
|
||||
class LoginEmailField extends StatelessWidget {
|
||||
final TextEditingController? controller;
|
||||
const LoginEmailField({super.key, this.controller});
|
||||
const LoginEmailField({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -15,8 +16,10 @@ class LoginEmailField extends StatelessWidget {
|
||||
title: context.lang.email,
|
||||
hintText: context.lang.email_placeholder,
|
||||
prefixIcon: LineIcons.envelope,
|
||||
validator: AppValidator.validateEmail,
|
||||
controller: controller,
|
||||
validator: (value) =>
|
||||
AppValidator.validateEmail(context.read<LoginFormBloc>().state.email),
|
||||
onChanged: (value) =>
|
||||
context.read<LoginFormBloc>().add(LoginFormEvent.emailChanged(value)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
import '../../../../../application/auth/login_form/login_form_bloc.dart';
|
||||
import '../../../../../common/extension/extension.dart';
|
||||
import '../../../../../common/validator/validator.dart';
|
||||
import '../../../../components/field/field.dart';
|
||||
|
||||
class LoginPasswordField extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
const LoginPasswordField({super.key, required this.controller});
|
||||
const LoginPasswordField({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -15,8 +16,12 @@ class LoginPasswordField extends StatelessWidget {
|
||||
title: context.lang.password,
|
||||
prefixIcon: LineIcons.lock,
|
||||
hintText: context.lang.password_placeholder,
|
||||
validator: AppValidator.validatePassword,
|
||||
controller: controller,
|
||||
validator: (value) => AppValidator.validatePassword(
|
||||
context.read<LoginFormBloc>().state.password,
|
||||
),
|
||||
onChanged: (value) => context.read<LoginFormBloc>().add(
|
||||
LoginFormEvent.passwordChanged(value),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user