feat(frontend): scaffold Flutter Web app with ENSUP home screen

This commit is contained in:
SaidSoighiri94
2026-07-06 10:37:21 +02:00
parent be7d095fc3
commit d12302e8a6
22 changed files with 1096 additions and 1 deletions
+21
View File
@@ -0,0 +1,21 @@
import "package:flutter/material.dart";
import "theme/ensup_theme.dart";
import "screens/home_screen.dart";
void main() {
runApp(const EmeApp());
}
class EmeApp extends StatelessWidget {
const EmeApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "EME — Emprunt Matériel ENSUP",
debugShowCheckedModeBanner: false,
theme: buildEnsupTheme(),
home: const HomeScreen(),
);
}
}
+105
View File
@@ -0,0 +1,105 @@
import "package:flutter/material.dart";
import "package:google_fonts/google_fonts.dart";
import "../theme/ensup_colors.dart";
import "../widgets/ensup_top_bar.dart";
import "../widgets/profile_card.dart";
import "../widgets/action_card.dart";
/// Écran d'accueil étudiant : profil + choix Emprunter / Restituer.
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
void _bientot(BuildContext context, String action) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("$action — écran à venir")),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: EnsupColors.soft,
body: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 1280, maxHeight: 820),
child: Container(
margin: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: EnsupColors.text.withValues(alpha: 0.14),
blurRadius: 60,
offset: const Offset(0, 18),
),
],
),
clipBehavior: Clip.antiAlias,
child: Column(
children: [
const EnsupTopBar(
campusTag: "Paris · Ensitech",
userName: "Lucas Martin",
userInitials: "LM",
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 28),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ProfileCard(
name: "Lucas Martin",
subtitle: "Étudiant · BTS SIO 2ème année · Ensitech Paris",
initials: "LM",
),
const SizedBox(height: 24),
Text(
"Que souhaitez-vous faire ?",
style: GoogleFonts.darkerGrotesque(
fontSize: 28,
fontWeight: FontWeight.w900,
color: EnsupColors.text,
),
),
const SizedBox(height: 18),
IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: ActionCard(
icon: Icons.inventory_2_outlined,
title: "EMPRUNTER",
description:
"Accéder au catalogue et emprunter du matériel éducatif",
color: EnsupColors.cyan,
onTap: () => _bientot(context, "Emprunter"),
),
),
const SizedBox(width: 16),
Expanded(
child: ActionCard(
icon: Icons.assignment_return_outlined,
title: "RESTITUER",
description: "Restituer un matériel que vous avez emprunté",
color: EnsupColors.teal,
onTap: () => _bientot(context, "Restituer"),
),
),
],
),
),
],
),
),
),
],
),
),
),
),
);
}
}
+22
View File
@@ -0,0 +1,22 @@
import "package:flutter/material.dart";
/// Couleurs de la charte graphique ENSUP.
class EnsupColors {
EnsupColors._();
static const Color cyan = Color(0xFF009CE0);
static const Color blue1 = Color(0xFF006DAC);
static const Color blue2 = Color(0xFF0090D7);
static const Color blue3 = Color(0xFF005CAA);
static const Color teal = Color(0xFF198AB4);
static const Color purple = Color(0xFF7E5AA2);
static const Color red = Color(0xFFDD1B3D);
static const Color green = Color(0xFF16A34A);
static const Color text = Color(0xFF0F172A);
static const Color text2 = Color(0xFF334155);
static const Color muted = Color(0xFF64748B);
static const Color line = Color(0xFFE2E8F0);
static const Color soft = Color(0xFFF8FAFC);
static const Color white = Color(0xFFFFFFFF);
}
+23
View File
@@ -0,0 +1,23 @@
import "package:flutter/material.dart";
import "package:google_fonts/google_fonts.dart";
import "ensup_colors.dart";
/// Thème global : couleurs ENSUP et police de corps Titillium Web.
/// Les titres utilisent Darker Grotesque, appliqué directement dans les widgets.
ThemeData buildEnsupTheme() {
final base = ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: EnsupColors.cyan,
primary: EnsupColors.cyan,
),
scaffoldBackgroundColor: EnsupColors.soft,
);
return base.copyWith(
textTheme: GoogleFonts.titilliumWebTextTheme(base.textTheme).apply(
bodyColor: EnsupColors.text,
displayColor: EnsupColors.text,
),
);
}
+76
View File
@@ -0,0 +1,76 @@
import "package:flutter/material.dart";
import "package:google_fonts/google_fonts.dart";
import "../theme/ensup_colors.dart";
/// Grande carte d'action de l'accueil (Emprunter / Restituer).
class ActionCard extends StatelessWidget {
const ActionCard({
super.key,
required this.icon,
required this.title,
required this.description,
required this.color,
this.onTap,
});
final IconData icon;
final String title;
final String description;
final Color color;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(16),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.07),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: color.withValues(alpha: 0.2), width: 2),
),
child: Column(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
gradient: LinearGradient(colors: [color, color.withValues(alpha: 0.75)]),
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(
color: color.withValues(alpha: 0.35),
blurRadius: 16,
offset: const Offset(0, 4),
),
],
),
child: Icon(icon, color: Colors.white, size: 28),
),
const SizedBox(height: 14),
Text(
title,
style: GoogleFonts.darkerGrotesque(
fontSize: 26,
fontWeight: FontWeight.w900,
color: EnsupColors.text,
),
),
const SizedBox(height: 6),
Text(
description,
textAlign: TextAlign.center,
style: GoogleFonts.titilliumWeb(
fontSize: 13,
color: EnsupColors.muted,
height: 1.5,
),
),
],
),
),
);
}
}
+105
View File
@@ -0,0 +1,105 @@
import "package:flutter/material.dart";
import "package:google_fonts/google_fonts.dart";
import "../theme/ensup_colors.dart";
/// Barre supérieure ENSUP : marque EME, campus et utilisateur connecté.
class EnsupTopBar extends StatelessWidget {
const EnsupTopBar({
super.key,
required this.campusTag,
required this.userName,
required this.userInitials,
});
final String campusTag;
final String userName;
final String userInitials;
@override
Widget build(BuildContext context) {
return Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 28),
color: EnsupColors.blue3,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const Icon(Icons.devices_other, color: Colors.white, size: 18),
const SizedBox(width: 10),
Text(
"EME",
style: GoogleFonts.darkerGrotesque(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 22,
letterSpacing: 0.3,
),
),
],
),
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(999),
border: Border.all(color: Colors.white.withValues(alpha: 0.2)),
),
child: Text(
campusTag,
style: GoogleFonts.titilliumWeb(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(width: 12),
_Avatar(initials: userInitials),
const SizedBox(width: 8),
Text(
userName,
style: GoogleFonts.titilliumWeb(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
],
),
],
),
);
}
}
class _Avatar extends StatelessWidget {
const _Avatar({required this.initials});
final String initials;
@override
Widget build(BuildContext context) {
return Container(
width: 30,
height: 30,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: const LinearGradient(colors: [EnsupColors.teal, EnsupColors.cyan]),
border: Border.all(color: Colors.white.withValues(alpha: 0.3), width: 2),
),
alignment: Alignment.center,
child: Text(
initials,
style: GoogleFonts.darkerGrotesque(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 12,
),
),
);
}
}
@@ -0,0 +1,91 @@
import "package:flutter/material.dart";
import "package:google_fonts/google_fonts.dart";
import "../theme/ensup_colors.dart";
/// Carte de profil de l'étudiant identifié (accueil).
class ProfileCard extends StatelessWidget {
const ProfileCard({
super.key,
required this.name,
required this.subtitle,
required this.initials,
});
final String name;
final String subtitle;
final String initials;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
EnsupColors.cyan.withValues(alpha: 0.06),
EnsupColors.teal.withValues(alpha: 0.04),
],
),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: EnsupColors.cyan.withValues(alpha: 0.15), width: 1.5),
),
child: Row(
children: [
Container(
width: 48,
height: 48,
decoration: const BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(colors: [EnsupColors.teal, EnsupColors.cyan]),
),
alignment: Alignment.center,
child: Text(
initials,
style: GoogleFonts.darkerGrotesque(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 18,
),
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: GoogleFonts.darkerGrotesque(
fontWeight: FontWeight.w800,
fontSize: 20,
color: EnsupColors.text,
),
),
const SizedBox(height: 2),
Text(
subtitle,
style: GoogleFonts.titilliumWeb(fontSize: 12, color: EnsupColors.muted),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
decoration: BoxDecoration(
color: EnsupColors.green.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(999),
),
child: Text(
"Connecté",
style: GoogleFonts.titilliumWeb(
fontSize: 12,
fontWeight: FontWeight.w600,
color: EnsupColors.green,
),
),
),
],
),
);
}
}