Files
petitspas/frontend/lib/widgets/quotidien/quotidien_shell.dart
T

321 lines
9.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:p_tits_pas/widgets/app_footer.dart';
import 'package:p_tits_pas/widgets/quotidien/quotidien_bandeau.dart';
import 'package:p_tits_pas/widgets/quotidien/quotidien_theme.dart';
/// Coquille « Cahier de liaison » quotidien 3 colonnes + bandeau (#166).
/// LAM (#169) réutilise ce widget en injectant ses slots.
class QuotidienShell extends StatelessWidget {
final QuotidienNavSection selectedSection;
final ValueChanged<QuotidienNavSection> onSectionSelected;
final String userDisplayName;
final String? userEmail;
final VoidCallback? onProfileTap;
final VoidCallback? onSearchAmTap;
final VoidCallback? onSettingsTap;
final VoidCallback? onLogout;
/// Colonne gauche (couple + cartes + actions).
final Widget leftColumn;
/// Colonne milieu (blog).
final Widget centerColumn;
/// Colonne droite (messagerie).
final Widget rightColumn;
/// Corps affiché hors Cahier de liaison (Agenda / Contrat stubs).
final Widget? agendaBody;
final Widget? contratBody;
const QuotidienShell({
super.key,
required this.selectedSection,
required this.onSectionSelected,
required this.userDisplayName,
required this.leftColumn,
required this.centerColumn,
required this.rightColumn,
this.userEmail,
this.onProfileTap,
this.onSearchAmTap,
this.onSettingsTap,
this.onLogout,
this.agendaBody,
this.contratBody,
});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: QuotidienTheme.ivory,
body: Container(
decoration: QuotidienTheme.paperBackground(),
child: Column(
children: [
QuotidienBandeau(
selectedSection: selectedSection,
onSectionSelected: onSectionSelected,
userDisplayName: userDisplayName,
userEmail: userEmail,
onProfileTap: onProfileTap,
onSearchAmTap: onSearchAmTap,
onSettingsTap: onSettingsTap,
onLogout: onLogout,
),
const QuotidienPencilDivider(),
Expanded(child: _bodyForSection(context)),
const QuotidienPencilDivider(),
const AppFooter(showTopBorder: false),
],
),
),
);
}
Widget _bodyForSection(BuildContext context) {
switch (selectedSection) {
case QuotidienNavSection.liaison:
return _ThreeColumns(
left: leftColumn,
center: centerColumn,
right: rightColumn,
);
case QuotidienNavSection.agenda:
return agendaBody ??
const QuotidienStubPage(
title: 'Agenda',
message: 'Page Agenda — à venir.',
);
case QuotidienNavSection.contrat:
return contratBody ??
const QuotidienStubPage(
title: 'Contrat',
message: 'Page Contrat — à venir.',
);
}
}
}
/// Trait « crayon gris » dessiné à la main : sépare bandeau / corps / footer
/// (horizontal) et les 3 colonnes (vertical). Le PNG (2400×28) est étiré dans
/// le sens du trait seulement : sur un trait, c'est invisible.
class QuotidienPencilDivider extends StatelessWidget {
final Axis axis;
/// Épaisseur de la zone du trait (hauteur si horizontal, largeur sinon).
final double thickness;
final EdgeInsets padding;
const QuotidienPencilDivider({
super.key,
this.axis = Axis.horizontal,
this.thickness = 14,
this.padding = const EdgeInsets.symmetric(horizontal: 24),
});
const QuotidienPencilDivider.vertical({
super.key,
this.thickness = 14,
this.padding = const EdgeInsets.symmetric(vertical: 16),
}) : axis = Axis.vertical;
@override
Widget build(BuildContext context) {
final horizontal = axis == Axis.horizontal;
return Padding(
padding: padding,
child: SizedBox(
width: horizontal ? double.infinity : thickness,
height: horizontal ? thickness : double.infinity,
child: Image.asset(
horizontal
? QuotidienTheme.pencilLineAsset
: QuotidienTheme.pencilLineVerticalAsset,
fit: BoxFit.fill,
filterQuality: FilterQuality.medium,
),
),
);
}
}
class _ThreeColumns extends StatelessWidget {
final Widget left;
final Widget center;
final Widget right;
const _ThreeColumns({
required this.left,
required this.center,
required this.right,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final wide = constraints.maxWidth >= 900;
if (!wide) {
// Socle mobile temporaire (#188 = swipe dédié) : pile verticale.
return ListView(
padding: const EdgeInsets.fromLTRB(12, 4, 12, 12),
children: [
_ColumnPanel(child: left),
const QuotidienPencilDivider(
padding: EdgeInsets.symmetric(horizontal: 32, vertical: 4),
),
_ColumnPanel(child: center),
const QuotidienPencilDivider(
padding: EdgeInsets.symmetric(horizontal: 32, vertical: 4),
),
_ColumnPanel(child: right),
],
);
}
return Padding(
padding: const EdgeInsets.fromLTRB(16, 4, 16, 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(child: _ColumnPanel(child: left)),
const QuotidienPencilDivider.vertical(),
Expanded(child: _ColumnPanel(child: center)),
const QuotidienPencilDivider.vertical(),
Expanded(child: _ColumnPanel(child: right)),
],
),
);
},
);
}
}
/// Panneau colonne (carte pastel semi-transparente).
class _ColumnPanel extends StatelessWidget {
final Widget child;
const _ColumnPanel({required this.child});
@override
Widget build(BuildContext context) {
return Container(
// Pas de bordure Material : la séparation est assurée par les traits
// crayon, on garde juste un léger fond.
decoration: BoxDecoration(
color: QuotidienTheme.columnCardFill.withOpacity(0.82),
borderRadius: BorderRadius.circular(16),
),
clipBehavior: Clip.antiAlias,
child: child,
);
}
}
/// Placeholder de colonne métier (branchable par tickets C/D/E).
class QuotidienColumnPlaceholder extends StatelessWidget {
final String title;
final String subtitle;
final IconData icon;
const QuotidienColumnPlaceholder({
super.key,
required this.title,
required this.subtitle,
required this.icon,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
Icon(icon, color: QuotidienTheme.lavender, size: 22),
const SizedBox(width: 8),
Text(
title,
style: GoogleFonts.merienda(
fontSize: 18,
fontWeight: FontWeight.w600,
color: QuotidienTheme.ink,
),
),
],
),
const SizedBox(height: 12),
Expanded(
child: Center(
child: Text(
subtitle,
textAlign: TextAlign.center,
style: GoogleFonts.merriweather(
fontSize: 13,
color: QuotidienTheme.muted,
height: 1.4,
),
),
),
),
],
),
);
}
}
/// Stub pleine page (Agenda / Contrat) — contenu riche hors #166.
class QuotidienStubPage extends StatelessWidget {
final String title;
final String message;
const QuotidienStubPage({
super.key,
required this.title,
required this.message,
});
@override
Widget build(BuildContext context) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 420),
child: Container(
margin: const EdgeInsets.all(24),
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 32),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.85),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade300),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
title,
style: GoogleFonts.merienda(
fontSize: 22,
fontWeight: FontWeight.w600,
color: QuotidienTheme.ink,
),
),
const SizedBox(height: 12),
Text(
message,
textAlign: TextAlign.center,
style: GoogleFonts.merriweather(
fontSize: 14,
color: QuotidienTheme.muted,
),
),
],
),
),
),
);
}
}