feat: login page
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../components/button/button.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
import 'widgets/email_field.dart';
|
||||
import 'widgets/password_field.dart';
|
||||
|
||||
@RoutePage()
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
State<LoginPage> createState() => _LoginPageState();
|
||||
}
|
||||
|
||||
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 Animation<double> _fadeAnimation;
|
||||
late Animation<Offset> _slideAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_fadeController = AnimationController(
|
||||
duration: const Duration(milliseconds: 1500),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_slideController = AnimationController(
|
||||
duration: const Duration(milliseconds: 1200),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
_slideAnimation =
|
||||
Tween<Offset>(begin: const Offset(0, 0.3), end: Offset.zero).animate(
|
||||
CurvedAnimation(parent: _slideController, curve: Curves.easeOutCubic),
|
||||
);
|
||||
_emailController.text = 'test@example.com';
|
||||
_passwordController.text = 'password';
|
||||
_fadeController.forward();
|
||||
_slideController.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_fadeController.dispose();
|
||||
_slideController.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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: AppColor.primaryGradient,
|
||||
),
|
||||
),
|
||||
child: 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(),
|
||||
SpaceHeight(48),
|
||||
_buildLoginCard(),
|
||||
SpaceHeight(24),
|
||||
_buildFooter(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLogo() {
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
'Welcome Back',
|
||||
style: AppStyle.h1.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.white,
|
||||
),
|
||||
),
|
||||
const SpaceHeight(8),
|
||||
Text(
|
||||
'Sign in to your account',
|
||||
style: AppStyle.lg.copyWith(color: AppColor.textLight),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoginCard() {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 24),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.black.withOpacity(0.1),
|
||||
blurRadius: 30,
|
||||
offset: const Offset(0, 15),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
LoginEmailField(controller: _emailController),
|
||||
const SpaceHeight(24),
|
||||
LoginPasswordField(controller: _passwordController),
|
||||
const SpaceHeight(16),
|
||||
_buildForgetPassword(),
|
||||
const SpaceHeight(32),
|
||||
_buildLoginButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildForgetPassword() {
|
||||
return Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: GestureDetector(
|
||||
onTap: () {},
|
||||
child: Text(
|
||||
'Forgot Password?',
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoginButton() {
|
||||
return AppElevatedButton(
|
||||
text: 'Sign In',
|
||||
isLoading: _isLoading,
|
||||
onPressed: _isLoading ? null : _handleLogin,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFooter() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Don't have an account? ",
|
||||
style: AppStyle.md.copyWith(color: AppColor.textLight),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {},
|
||||
child: Text(
|
||||
'Sign Up',
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
import '../../../../../common/validator/validator.dart';
|
||||
import '../../../../components/field/field.dart';
|
||||
|
||||
class LoginEmailField extends StatelessWidget {
|
||||
final TextEditingController? controller;
|
||||
const LoginEmailField({super.key, this.controller});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppTextFormField(
|
||||
title: 'Email',
|
||||
hintText: 'Enter your email',
|
||||
prefixIcon: LineIcons.envelope,
|
||||
validator: AppValidator.validateEmail,
|
||||
controller: controller,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:line_icons/line_icons.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});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppPasswordTextFormField(
|
||||
title: 'Password',
|
||||
prefixIcon: LineIcons.lock,
|
||||
hintText: 'Enter your password',
|
||||
validator: AppValidator.validatePassword,
|
||||
controller: controller,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import '../../common/theme/theme.dart';
|
||||
import '../components/assets/assets.gen.dart';
|
||||
import '../router/app_router.gr.dart';
|
||||
|
||||
@RoutePage()
|
||||
class SplashPage extends StatefulWidget {
|
||||
@@ -79,7 +80,7 @@ class _SplashPageState extends State<SplashPage> with TickerProviderStateMixin {
|
||||
|
||||
void _navigateToHome() {
|
||||
// Uncomment dan sesuaikan dengan route yang ada
|
||||
// context.router.replace(const HomeRoute());
|
||||
context.router.replace(const LoginRoute());
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
Reference in New Issue
Block a user