Files
unionflow-mobile-apps/lib/features/authentication/presentation/pages/login_page.dart
dahoud d094d6db9c Initial commit: unionflow-mobile-apps
Application Flutter complète (sans build artifacts).

Signed-off-by: lions dev Team
2026-03-15 16:30:08 +00:00

170 lines
6.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:url_launcher/url_launcher.dart';
import '../bloc/auth_bloc.dart';
import '../../../../core/config/environment.dart';
import '../../../../shared/widgets/core_text_field.dart';
import '../../../../shared/widgets/dynamic_fab.dart';
import '../../../../shared/design_system/tokens/app_typography.dart';
import '../../../../shared/design_system/tokens/app_colors.dart';
/// UnionFlow Mobile - Écran de connexion (Mode DRY & Minimaliste)
class LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
Future<void> _openForgotPassword(BuildContext context) async {
final url = Uri.parse(
'${AppConfig.keycloakRealmUrl}/protocol/openid-connect/auth'
'?client_id=unionflow-mobile'
'&redirect_uri=${Uri.encodeComponent('http://localhost')}'
'&response_type=code'
'&scope=openid'
'&kc_action=reset_credentials',
);
try {
if (await canLaunchUrl(url)) {
await launchUrl(url, mode: LaunchMode.externalApplication);
} else {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Impossible d\'ouvrir la page de réinitialisation')),
);
}
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Erreur lors de l\'ouverture du lien')),
);
}
}
}
void _onLogin() {
final email = _emailController.text;
final password = _passwordController.text;
if (email.isNotEmpty && password.isNotEmpty) {
context.read<AuthBloc>().add(AuthLoginRequested(email, password));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocConsumer<AuthBloc, AuthState>(
listener: (context, state) {
if (state is AuthAuthenticated) {
// Navigator 1.0 : Le BlocBuilder dans AppRouter gérera la transition vers MainNavigationLayout
} else if (state is AuthError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.message, style: AppTypography.bodyTextSmall),
backgroundColor: AppColors.error,
),
);
}
},
builder: (context, state) {
final isLoading = state is AuthLoading;
return SafeArea(
child: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Logo minimaliste (Texte seul)
Center(
child: Text(
'UnionFlow',
style: AppTypography.headerSmall.copyWith(
fontSize: 24, // Exception unique pour le logo
color: AppColors.primaryGreen,
letterSpacing: 1.2,
),
),
),
const SizedBox(height: 8),
Center(
child: Text(
'Connexion à votre espace.',
style: AppTypography.subtitleSmall,
),
),
const SizedBox(height: 48),
// Champs de texte DRY
CoreTextField(
controller: _emailController,
hintText: 'Email ou Identifiant',
prefixIcon: Icons.person_outline,
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: 16),
CoreTextField(
controller: _passwordController,
hintText: 'Mot de passe',
prefixIcon: Icons.lock_outline,
obscureText: true,
),
const SizedBox(height: 12),
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () => _openForgotPassword(context),
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
minimumSize: const Size(0, 0),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
child: Text(
'Oublié ?',
style: AppTypography.subtitleSmall.copyWith(
color: AppColors.primaryGreen,
),
),
),
),
const SizedBox(height: 32),
// Bouton centralisé avec chargement intégré
Center(
child: isLoading
? const CircularProgressIndicator(color: AppColors.primaryGreen)
: DynamicFAB(
icon: Icons.arrow_forward,
label: 'Se Connecter',
onPressed: _onLogin,
),
),
],
),
),
),
);
},
),
);
}
}