Agrego frontend app
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gimnasio_soma/core/theme/soma_colors.dart';
|
||||
import 'package:gimnasio_soma/features/auth/presentation/widgets/login_form.dart';
|
||||
import 'package:gimnasio_soma/features/auth/presentation/widgets/login_hero.dart';
|
||||
|
||||
class LoginDesktop extends StatelessWidget {
|
||||
final Future<void> Function(String dni, String password) onSubmit;
|
||||
final Animation<double> fadeIn;
|
||||
final Animation<Offset> slideIn;
|
||||
|
||||
const LoginDesktop({
|
||||
super.key,
|
||||
required this.onSubmit,
|
||||
required this.fadeIn,
|
||||
required this.slideIn,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
// ── Hero (left half) ──
|
||||
const Expanded(
|
||||
flex: 5,
|
||||
child: LoginHero(),
|
||||
),
|
||||
|
||||
// ── Form (right half) ──
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: FadeTransition(
|
||||
opacity: fadeIn,
|
||||
child: SlideTransition(
|
||||
position: slideIn,
|
||||
child: Container(
|
||||
height: double.infinity,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 48,
|
||||
vertical: 40,
|
||||
),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
LoginForm(onSubmit: onSubmit),
|
||||
const SizedBox(height: 32),
|
||||
Center(
|
||||
child: Text(
|
||||
'v0.1.0',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: SomaColors.darkOnSurfaceVariant
|
||||
.withAlpha(100),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:gimnasio_soma/core/theme/soma_colors.dart';
|
||||
import 'package:gimnasio_soma/core/widgets/soma_primary_button.dart';
|
||||
import 'package:gimnasio_soma/core/widgets/soma_text_field.dart';
|
||||
|
||||
class LoginForm extends StatefulWidget {
|
||||
final Future<void> Function(String dni, String password) onSubmit;
|
||||
|
||||
const LoginForm({super.key, required this.onSubmit});
|
||||
|
||||
@override
|
||||
State<LoginForm> createState() => _LoginFormState();
|
||||
}
|
||||
|
||||
class _LoginFormState extends State<LoginForm> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _dniController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
bool _obscurePassword = true;
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_dniController.dispose();
|
||||
_passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _handleSubmit() async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
|
||||
setState(() => _isLoading = true);
|
||||
|
||||
await widget.onSubmit(
|
||||
_dniController.text.trim(),
|
||||
_passwordController.text,
|
||||
);
|
||||
|
||||
if (mounted) setState(() => _isLoading = false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
'Iniciar sesión',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'Ingresá tus datos para continuar',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: SomaColors.darkOnSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 28),
|
||||
SomaTextField(
|
||||
controller: _dniController,
|
||||
hintText: 'Ej: 12345678',
|
||||
labelText: 'DNI',
|
||||
prefixIcon: Icons.badge_outlined,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
LengthLimitingTextInputFormatter(8),
|
||||
],
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'El DNI es requerido';
|
||||
}
|
||||
if (value.length < 7) {
|
||||
return 'DNI inválido';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SomaTextField(
|
||||
controller: _passwordController,
|
||||
hintText: 'Tu contraseña',
|
||||
labelText: 'Contraseña',
|
||||
prefixIcon: Icons.lock_outline,
|
||||
obscureText: _obscurePassword,
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_obscurePassword
|
||||
? Icons.visibility_outlined
|
||||
: Icons.visibility_off_outlined,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withAlpha(130),
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() => _obscurePassword = !_obscurePassword);
|
||||
},
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'La contraseña es requerida';
|
||||
}
|
||||
if (value.length < 4) {
|
||||
return 'La contraseña es muy corta';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
SomaPrimaryButton(
|
||||
text: 'Ingresar',
|
||||
onPressed: _handleSubmit,
|
||||
isLoading: _isLoading,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gimnasio_soma/core/theme/soma_colors.dart';
|
||||
|
||||
class LoginHero extends StatelessWidget {
|
||||
const LoginHero({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFF1A1A1A), Color(0xFF121212)],
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
// Decorative circles
|
||||
Positioned(
|
||||
top: -60,
|
||||
left: -60,
|
||||
child: Container(
|
||||
width: 200,
|
||||
height: 200,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: SomaColors.primary.withAlpha(15),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: -40,
|
||||
right: -40,
|
||||
child: Container(
|
||||
width: 150,
|
||||
height: 150,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: SomaColors.primary.withAlpha(10),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Content
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/logo.png',
|
||||
width: 180,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Gimnasio',
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w300,
|
||||
letterSpacing: 6,
|
||||
color: SomaColors.darkOnSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:gimnasio_soma/core/theme/soma_colors.dart';
|
||||
import 'package:gimnasio_soma/features/auth/presentation/widgets/login_form.dart';
|
||||
import 'package:gimnasio_soma/features/auth/presentation/widgets/login_hero.dart';
|
||||
|
||||
class LoginMobile extends StatelessWidget {
|
||||
final Future<void> Function(String dni, String password) onSubmit;
|
||||
final Animation<double> fadeIn;
|
||||
final Animation<Offset> slideUp;
|
||||
|
||||
const LoginMobile({
|
||||
super.key,
|
||||
required this.onSubmit,
|
||||
required this.fadeIn,
|
||||
required this.slideUp,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenHeight = MediaQuery.of(context).size.height;
|
||||
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle.light,
|
||||
child: SingleChildScrollView(
|
||||
child: SizedBox(
|
||||
height: screenHeight,
|
||||
child: Column(
|
||||
children: [
|
||||
// ── Hero (top 35%) ──
|
||||
const Expanded(
|
||||
flex: 35,
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: LoginHero(),
|
||||
),
|
||||
),
|
||||
|
||||
// ── Form (bottom 65%) ──
|
||||
Expanded(
|
||||
flex: 65,
|
||||
child: FadeTransition(
|
||||
opacity: fadeIn,
|
||||
child: SlideTransition(
|
||||
position: slideUp,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(28, 36, 28, 24),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(32),
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(80),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, -4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: LoginForm(onSubmit: onSubmit),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Center(
|
||||
child: Text(
|
||||
'v0.1.0',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: SomaColors.darkOnSurfaceVariant
|
||||
.withAlpha(100),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user