feat: login api

This commit is contained in:
efrilm
2025-08-16 17:27:57 +07:00
parent 6019036cd7
commit 255b10d5a9
31 changed files with 2997 additions and 86 deletions
@@ -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,
);
}
}