Files
geo/flutt/lib/presentation/public/landing_page.dart

1297 lines
56 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
import 'package:geosector_app/core/theme/app_theme.dart';
import 'package:url_launcher/url_launcher.dart';
class LandingPage extends StatefulWidget {
const LandingPage({super.key});
@override
State<LandingPage> createState() => _LandingPageState();
}
class _LandingPageState extends State<LandingPage>
with TickerProviderStateMixin {
final PageController _pageController = PageController();
int _currentPage = 0;
// Contrôleurs d'animation
late List<AnimationController> _controllers;
late List<Animation<double>> _opacityAnimations;
late List<Animation<Offset>> _slideAnimations;
late List<Animation<double>> _scaleAnimations;
final List<FeatureItem> _features = [
FeatureItem(
title: 'Gestion intuitive',
description:
'Dessinez vos secteurs de distribution directement sur une carte interactive. Les adresses des habitants sont automatiquement intégrées pour une planification optimale.',
icon: Icons.map,
color: const Color(0xFF2E4057), // Couleur primaire
),
FeatureItem(
title: 'Suivi en temps réel et traçabilité',
description:
'Suivez en temps réel la progression des tournées grâce à un tableau de bord complet. Visualisez les passages effectués et les dons collectés pour une gestion efficace.',
icon: Icons.track_changes,
color: const Color(0xFF048BA8), // Couleur secondaire
),
FeatureItem(
title: 'Génération automatique PDF',
description:
'Générez des reçus au format PDF instantanément après chaque collecte. Envoyez-les automatiquement par email pour une transparence totale.',
icon: Icons.picture_as_pdf,
color: const Color(0xFFF18F01), // Couleur accent
),
];
@override
void initState() {
super.initState();
// Initialiser les contrôleurs d'animation
_controllers = List.generate(
_features.length,
(index) => AnimationController(
vsync: this,
duration: const Duration(milliseconds: 800),
),
);
// Créer les animations
_opacityAnimations = _controllers
.map((controller) => Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: controller, curve: Curves.easeInOut)))
.toList();
_slideAnimations = _controllers
.map((controller) =>
Tween<Offset>(begin: const Offset(0, 0.2), end: Offset.zero)
.animate(CurvedAnimation(
parent: controller, curve: Curves.easeOutQuart)))
.toList();
_scaleAnimations = _controllers
.map((controller) => Tween<double>(begin: 0.8, end: 1.0).animate(
CurvedAnimation(parent: controller, curve: Curves.easeOutBack)))
.toList();
// Démarrer les animations avec délai
_startAnimationsWithDelay();
}
void _startAnimationsWithDelay() {
for (int i = 0; i < _controllers.length; i++) {
Future.delayed(Duration(milliseconds: 300 * (i + 1)), () {
if (mounted) {
_controllers[i].forward();
}
});
}
}
@override
void dispose() {
_pageController.dispose();
for (var controller in _controllers) {
controller.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final screenSize = MediaQuery.of(context).size;
bool isWebOrTablet = screenSize.width > 600;
return Scaffold(
// Utilisation d'un ListView pour rendre toute la page défilable, y compris le footer
body: SafeArea(
child: ListView(
children: [
// Header avec logo et navigation
Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Logo
Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.asset(
'assets/images/geosector-logo-80.png',
fit: BoxFit.cover,
),
),
),
const SizedBox(width: 8),
Text(
'GEOSECTOR',
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
),
),
],
),
// Navigation
const SizedBox(width: 16),
// Boutons Contact/Connexion/Inscription
if (isWebOrTablet)
Row(
children: [
TextButton(
onPressed: () {},
style: TextButton.styleFrom(
foregroundColor: theme.colorScheme.primary,
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
),
child: const Text('Contact'),
),
const SizedBox(width: 8),
OutlinedButton(
onPressed: () {
// Naviguer avec debug
print('DEBUG: Navigation vers login admin');
// Utiliser directement les paramètres avec go
context.go('/login', extra: {'type': 'admin'});
print('DEBUG: Navigation admin avec extra');
},
style: OutlinedButton.styleFrom(
foregroundColor: Colors.red,
side: const BorderSide(color: Colors.red),
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
),
child: const Text('Connexion Administrateur'),
),
const SizedBox(width: 8),
OutlinedButton(
onPressed: () {
// Naviguer avec debug
print('DEBUG: Navigation vers login user');
// Utiliser directement les paramètres avec go
context.go('/login', extra: {'type': 'user'});
print('DEBUG: Navigation user avec extra');
},
style: OutlinedButton.styleFrom(
foregroundColor: Colors.green,
side: const BorderSide(color: Colors.green),
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
),
child: const Text('Connexion Utilisateur'),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () {
context.go('/register?from=landing');
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFF18F01),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text('S\'inscrire'),
),
],
)
else
Row(
children: [
// Bouton de login administrateur
IconButton(
icon: const Icon(Icons.admin_panel_settings),
tooltip: 'Connexion Administrateur',
color: Colors.red,
onPressed: () {
print('DEBUG: Navigation mobile vers login admin');
context.go('/login', extra: {'type': 'admin'});
print('DEBUG: Navigation mobile admin avec extra');
},
),
// Bouton de login utilisateur
IconButton(
icon: const Icon(Icons.person),
tooltip: 'Connexion Utilisateur',
color: Colors.green,
onPressed: () {
print('DEBUG: Navigation mobile vers login user');
context.go('/login', extra: {'type': 'user'});
print('DEBUG: Navigation mobile user avec extra');
},
),
IconButton(
icon: const Icon(Icons.menu),
onPressed: () {
Scaffold.of(context).openDrawer();
},
),
],
),
],
),
),
// Contenu principal
if (isWebOrTablet)
_buildWebLayout(theme, screenSize)
else
_buildMobileLayout(theme, screenSize),
// Footer
Container(
padding: const EdgeInsets.all(32),
color: const Color(0xFFF5F5F5),
child: Column(
children: [
// Trois colonnes du footer
isWebOrTablet
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Première colonne - Coordonnées
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 30,
height: 30,
margin: const EdgeInsets.only(right: 8),
child: Image.asset(
'assets/images/geosector-logo-80.png',
fit: BoxFit.contain,
),
),
Text(
'GEOSECTOR',
style: theme.textTheme.titleLarge
?.copyWith(
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
],
),
const SizedBox(height: 16),
Row(
children: [
Icon(Icons.location_on_outlined,
color: Colors.black87, size: 20),
const SizedBox(width: 8),
Expanded(
child: Text(
'16 Rue des marguerites, 56930 Pluméliau-Bieuzy',
style: theme.textTheme.bodyMedium
?.copyWith(color: Colors.black87),
),
),
],
),
const SizedBox(height: 8),
Row(
children: [
Icon(Icons.phone_outlined,
color: Colors.black87, size: 20),
const SizedBox(width: 8),
Text(
'+33 7 69 09 17 06',
style: theme.textTheme.bodyMedium
?.copyWith(color: Colors.black87),
),
],
),
const SizedBox(height: 8),
Row(
children: [
Icon(Icons.email_outlined,
color: Colors.black87, size: 20),
const SizedBox(width: 8),
Text(
'contactgeosector@gmail.com',
style: theme.textTheme.bodyMedium
?.copyWith(color: Colors.black87),
),
],
),
const SizedBox(height: 16),
OutlinedButton.icon(
onPressed: () async {
final Uri url = Uri.parse(
'https://www.facebook.com/geosector/');
if (await canLaunchUrl(url)) {
await launchUrl(url,
mode:
LaunchMode.externalApplication);
}
},
icon: const Icon(Icons.facebook),
label:
const Text('Suivez-nous sur Facebook'),
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFF1877F2),
side: const BorderSide(
color: Color(0xFF1877F2)),
),
),
],
),
),
const SizedBox(width: 32),
// Deuxième colonne - Liens
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Téléchargement',
style:
theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Row(
children: [
OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.apple),
label: const Text('App Store'),
style: OutlinedButton.styleFrom(
foregroundColor: Colors.black,
),
),
const SizedBox(width: 8),
OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.android),
label: const Text('Play Store'),
style: OutlinedButton.styleFrom(
foregroundColor:
const Color(0xFF3DDC84),
side: const BorderSide(
color: Color(0xFF3DDC84)),
),
),
],
),
const SizedBox(height: 24),
Text(
'Informations légales',
style:
theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
TextButton.icon(
onPressed: () {},
icon: const Icon(Icons.gavel_outlined,
size: 18),
label: const Text('Mentions légales'),
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
alignment: Alignment.centerLeft,
),
),
TextButton.icon(
onPressed: () {},
icon: const Icon(Icons.privacy_tip_outlined,
size: 18),
label: const Text(
'Politique de confidentialité'),
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
alignment: Alignment.centerLeft,
),
),
TextButton.icon(
onPressed: () {},
icon: const Icon(Icons.description_outlined,
size: 18),
label:
const Text('Conditions d\'utilisation'),
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
alignment: Alignment.centerLeft,
),
),
],
),
),
const SizedBox(width: 32),
// Troisième colonne - Formulaire de contact
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Contactez-nous',
style:
theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
TextField(
decoration: InputDecoration(
hintText: 'Votre nom',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
contentPadding:
const EdgeInsets.symmetric(
horizontal: 16, vertical: 12),
),
),
const SizedBox(height: 8),
TextField(
decoration: InputDecoration(
hintText: 'Votre email',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
contentPadding:
const EdgeInsets.symmetric(
horizontal: 16, vertical: 12),
),
),
const SizedBox(height: 8),
TextField(
maxLines: 3,
decoration: InputDecoration(
hintText: 'Votre message',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
contentPadding:
const EdgeInsets.symmetric(
horizontal: 16, vertical: 12),
),
),
const SizedBox(height: 12),
ElevatedButton.icon(
onPressed: () {},
icon: const Icon(Icons.send),
label: const Text('Envoyer'),
style: ElevatedButton.styleFrom(
backgroundColor:
theme.colorScheme.primary,
foregroundColor: Colors.white,
minimumSize:
const Size(double.infinity, 48),
),
),
],
),
),
],
)
: Column(
// Version mobile du footer
children: [
// Première colonne - Coordonnées
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'GEOSECTOR',
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
),
),
const SizedBox(height: 16),
Row(
children: [
Icon(Icons.location_on_outlined,
color: theme.colorScheme.primary,
size: 20),
const SizedBox(width: 8),
Expanded(
child: Text(
'123 Rue de la Distribution, 75000 Paris',
style: theme.textTheme.bodyMedium
?.copyWith(color: Colors.black87),
),
),
],
),
const SizedBox(height: 8),
Row(
children: [
Icon(Icons.phone_outlined,
color: theme.colorScheme.primary,
size: 20),
const SizedBox(width: 8),
Text(
'+33 1 23 45 67 89',
style: theme.textTheme.bodyMedium,
),
],
),
const SizedBox(height: 8),
Row(
children: [
Icon(Icons.email_outlined,
color: theme.colorScheme.primary,
size: 20),
const SizedBox(width: 8),
Text(
'contact@geosector.com',
style: theme.textTheme.bodyMedium,
),
],
),
const SizedBox(height: 16),
OutlinedButton.icon(
onPressed: () async {
final Uri url = Uri.parse(
'https://www.facebook.com/geosector/');
if (await canLaunchUrl(url)) {
await launchUrl(url,
mode: LaunchMode.externalApplication);
}
},
icon: const Icon(Icons.facebook),
label: const Text('Suivez-nous sur Facebook'),
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFF1877F2),
side: const BorderSide(
color: Color(0xFF1877F2)),
minimumSize:
const Size(double.infinity, 40),
),
),
],
),
const SizedBox(height: 32),
// Deuxième colonne - Liens
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Téléchargement',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.apple),
label: const Text('App Store'),
style: OutlinedButton.styleFrom(
foregroundColor: Colors.black,
),
),
),
const SizedBox(width: 8),
Expanded(
child: OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.android),
label: const Text('Play Store'),
style: OutlinedButton.styleFrom(
foregroundColor:
const Color(0xFF3DDC84),
side: const BorderSide(
color: Color(0xFF3DDC84)),
),
),
),
],
),
const SizedBox(height: 24),
Text(
'Informations légales',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
TextButton.icon(
onPressed: () {},
icon: const Icon(Icons.gavel_outlined,
size: 18),
label: const Text('Mentions légales'),
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
alignment: Alignment.centerLeft,
),
),
TextButton.icon(
onPressed: () {},
icon: const Icon(Icons.privacy_tip_outlined,
size: 18),
label: const Text(
'Politique de confidentialité'),
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
alignment: Alignment.centerLeft,
),
),
TextButton.icon(
onPressed: () {},
icon: const Icon(Icons.description_outlined,
size: 18),
label:
const Text('Conditions d\'utilisation'),
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
alignment: Alignment.centerLeft,
),
),
],
),
const SizedBox(height: 32),
// Troisième colonne - Formulaire de contact
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Contactez-nous',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
TextField(
decoration: InputDecoration(
hintText: 'Votre nom',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 12),
),
),
const SizedBox(height: 8),
TextField(
decoration: InputDecoration(
hintText: 'Votre email',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 12),
),
),
const SizedBox(height: 8),
TextField(
maxLines: 3,
decoration: InputDecoration(
hintText: 'Votre message',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 12),
),
),
const SizedBox(height: 12),
ElevatedButton.icon(
onPressed: () {},
icon: const Icon(Icons.send),
label: const Text('Envoyer'),
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
foregroundColor: Colors.white,
minimumSize:
const Size(double.infinity, 48),
),
),
],
),
],
),
const SizedBox(height: 32),
// Copyright
Divider(color: Colors.black38),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'© ${DateTime.now().year} GEOSECTOR. Tous droits réservés. ',
style: theme.textTheme.bodySmall
?.copyWith(color: Colors.black87),
),
InkWell(
onTap: () async {
// Ouvrir le lien vers D6SOFT
final Uri url = Uri.parse('https://d6soft.fr');
if (await canLaunchUrl(url)) {
await launchUrl(url,
mode: LaunchMode.externalApplication);
}
},
child: Text(
'Conception D6SOFT',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.primary,
decoration: TextDecoration.underline,
),
),
),
],
),
],
),
),
],
),
),
drawer: !isWebOrTablet
? Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
DrawerHeader(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF2E4057), Color(0xFF048BA8)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 40,
height: 40,
child: Image.asset(
'assets/images/geosector-logo-80.png',
fit: BoxFit.contain,
),
),
const SizedBox(height: 8),
Text(
'GEOSECTOR',
style: theme.textTheme.headlineSmall?.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
],
),
),
ListTile(
leading: const Icon(Icons.star_outline),
title: const Text('Fonctionnalités'),
onTap: () {
Navigator.pop(context);
},
),
ListTile(
leading: const Icon(Icons.info_outline),
title: const Text('À propos'),
onTap: () {
Navigator.pop(context);
},
),
ListTile(
leading: const Icon(Icons.email_outlined),
title: const Text('Contact'),
onTap: () {
Navigator.pop(context);
},
),
const Divider(),
ListTile(
leading: const Icon(Icons.login),
title: const Text('Se connecter'),
onTap: () {
Navigator.pop(context);
context.go('/login', extra: {'type': 'admin'});
print('DEBUG: Navigation drawer avec extra');
},
),
ListTile(
leading: const Icon(Icons.person_add_outlined),
title: const Text('S\'inscrire'),
onTap: () {
Navigator.pop(context);
context.go('/register?from=landing');
},
),
],
),
)
: null,
);
}
Widget _buildWebLayout(ThemeData theme, Size screenSize) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Section d'en-tête
Container(
padding: const EdgeInsets.all(48),
decoration: BoxDecoration(
color: theme.colorScheme.background,
),
child: Stack(
children: [
// Fond de carte
Positioned.fill(
child: Opacity(
opacity: 0.1,
child: SvgPicture.asset(
'assets/images/city-map-bg-fixed.svg',
fit: BoxFit.cover,
),
),
),
// Contenu
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Suppression du second menu
const SizedBox(height: 80),
// Titre principal
Text(
'Une application puissante et intuitive, pour une gestion efficace de vos distributions',
style: theme.textTheme.displayMedium?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
),
),
const SizedBox(height: 16),
// Sous-titre
Text(
'Simplifiez vos distributions, optimisez vos collectes.',
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w300,
color: theme.colorScheme.onBackground.withOpacity(0.8),
),
),
const SizedBox(height: 32),
// Boutons d'action
Row(
children: [
ElevatedButton.icon(
onPressed: () {
context.go('/register?from=landing');
},
icon: const Icon(Icons.person_add_outlined),
label: const Text('Créer un compte gratuit'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFF18F01),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 16),
elevation: 2,
),
),
const SizedBox(width: 16),
OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.play_circle_outline),
label: const Text('Voir la démo'),
style: OutlinedButton.styleFrom(
foregroundColor: theme.colorScheme.primary,
padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 16),
side: BorderSide(
color: theme.colorScheme.primary,
),
),
),
],
),
],
),
],
),
),
// Section des fonctionnalités
Container(
padding: const EdgeInsets.all(48),
color: theme.colorScheme.surface,
child: Column(
children: [
Text(
'Fonctionnalités principales',
style: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 48),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildFeatureItems(theme, true),
),
],
),
),
// Appel à l'action
Container(
padding: const EdgeInsets.all(48),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF2E4057), Color(0xFF048BA8)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Column(
children: [
Text(
'Prêt à tester GEOSECTOR ?',
style: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
color: Colors.white,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
Text(
'Rejoignez notre communauté et commencez à transformer vos distributions dès aujourd\'hui.',
style: theme.textTheme.bodyLarge?.copyWith(
color: Colors.white.withOpacity(0.9),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
ElevatedButton.icon(
onPressed: () {
context.go('/register?from=landing');
},
icon: const Icon(Icons.person_add_outlined),
label: const Text('Créer un compte gratuitement'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFF18F01),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 32, vertical: 16),
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
],
),
),
],
),
);
}
Widget _buildMobileLayout(ThemeData theme, Size screenSize) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Section d'en-tête
Container(
padding: const EdgeInsets.all(24),
color: theme.colorScheme.background,
child: Column(
children: [
Container(
width: 150,
height: 150,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 3),
),
],
),
padding: const EdgeInsets.all(15),
child: Image.asset(
'assets/images/geosector-logo.png',
fit: BoxFit.contain,
),
),
const SizedBox(height: 24),
Text(
'Une application puissante et intuitive, pour une gestion efficace de vos distributions',
style: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
Text(
'Simplifiez vos distributions, optimisez vos collectes.',
style: theme.textTheme.bodyMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: () {
context.go('/register?from=landing');
},
icon: const Icon(Icons.arrow_forward),
label: const Text('Commencer gratuitement'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFF18F01),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 16),
minimumSize: const Size(double.infinity, 48),
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
const SizedBox(height: 12),
OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.play_circle_outline),
label: const Text('Voir la démo'),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 12),
minimumSize: const Size(double.infinity, 48),
),
),
],
),
),
// Section des fonctionnalités
Container(
padding: const EdgeInsets.all(24),
color: theme.colorScheme.surface,
child: Column(
children: [
Text(
'Fonctionnalités principales',
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
SizedBox(
height: 350,
child: PageView.builder(
controller: _pageController,
itemCount: _features.length,
onPageChanged: (int page) {
setState(() {
_currentPage = page;
});
},
itemBuilder: (context, index) {
return AnimatedBuilder(
animation: _controllers[index],
builder: (context, child) {
return Opacity(
opacity: _opacityAnimations[index].value,
child: Transform.translate(
offset: Offset(0,
20 * (1 - _slideAnimations[index].value.dy)),
child: Transform.scale(
scale: _scaleAnimations[index].value,
child:
_buildFeatureCard(_features[index], theme),
),
),
);
},
);
},
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
_features.length,
(index) => Container(
width: 10,
height: 10,
margin: const EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _currentPage == index
? theme.colorScheme.primary
: theme.colorScheme.primary.withOpacity(0.3),
),
),
),
),
],
),
),
// Appel à l'action
Container(
padding: const EdgeInsets.all(24),
color: theme.colorScheme.primary,
child: Column(
children: [
Text(
'Prêt à tester GEOSECTOR ?',
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
color: Colors.white,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
Text(
'Rejoignez notre communauté dès aujourd\'hui.',
style: theme.textTheme.bodyMedium?.copyWith(
color: Colors.white.withOpacity(0.9),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: () {
context.go('/register?from=landing');
},
icon: const Icon(Icons.person_add_outlined),
label: const Text('Créer un compte'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: theme.colorScheme.primary,
padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 12),
minimumSize: const Size(double.infinity, 48),
),
),
],
),
),
],
),
);
}
List<Widget> _buildFeatureItems(ThemeData theme, bool isWeb) {
return List.generate(_features.length, (index) {
return Expanded(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: AnimatedBuilder(
animation: _controllers[index],
builder: (context, child) {
return Opacity(
opacity: _opacityAnimations[index].value,
child: Transform.translate(
offset:
Offset(0, 20 * (1 - _slideAnimations[index].value.dy)),
child: Transform.scale(
scale: _scaleAnimations[index].value,
child: _buildFeatureCard(_features[index], theme),
),
),
);
},
),
),
);
});
}
Widget _buildFeatureCard(FeatureItem feature, ThemeData theme) {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: feature.color.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
feature.icon,
size: 60,
color: feature.color,
),
),
const SizedBox(height: 16),
Text(
feature.title,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
feature.description,
style: theme.textTheme.bodyMedium,
textAlign: TextAlign.center,
),
],
),
),
);
}
}
class FeatureItem {
final String title;
final String description;
final IconData icon;
final Color color;
FeatureItem({
required this.title,
required this.description,
required this.icon,
required this.color,
});
}