guidesoutenance
This commit is contained in:
109
swiftyCompanion/swifty_companion/lib/theme/swifty_ui.dart
Normal file
109
swiftyCompanion/swifty_companion/lib/theme/swifty_ui.dart
Normal file
@@ -0,0 +1,109 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Constantes UI partagées (étape 5 — layout).
|
||||
abstract final class SwiftyUi {
|
||||
static const Color brand = Color(0xFF00BABC);
|
||||
|
||||
/// Largeur max du contenu sur tablette / paysage.
|
||||
static const double contentMaxWidth = 960;
|
||||
|
||||
/// Largeur max vue recherche (formulaire centré).
|
||||
static const double searchMaxWidth = 480;
|
||||
|
||||
static const double wideBreakpoint = 720;
|
||||
|
||||
static EdgeInsets pagePadding(BoxConstraints constraints) {
|
||||
final horizontal = constraints.maxWidth >= 600 ? 32.0 : 16.0;
|
||||
return EdgeInsets.fromLTRB(horizontal, 24, horizontal, 24);
|
||||
}
|
||||
|
||||
static PreferredSizeWidget appBar({
|
||||
required String title,
|
||||
List<Widget>? actions,
|
||||
bool showBack = false,
|
||||
}) {
|
||||
return AppBar(
|
||||
title: Text(title),
|
||||
backgroundColor: brand,
|
||||
foregroundColor: Colors.black,
|
||||
automaticallyImplyLeading: showBack,
|
||||
actions: actions,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Corps scrollable centré avec largeur max (clavier + petits écrans).
|
||||
class ResponsiveScrollBody extends StatelessWidget {
|
||||
const ResponsiveScrollBody({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.maxWidth = SwiftyUi.contentMaxWidth,
|
||||
this.center = true,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final double maxWidth;
|
||||
final bool center;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final content = ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: maxWidth),
|
||||
child: child,
|
||||
);
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: SwiftyUi.pagePadding(constraints),
|
||||
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
||||
child: center
|
||||
? Align(alignment: Alignment.topCenter, child: content)
|
||||
: content,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorMessage extends StatelessWidget {
|
||||
const ErrorMessage({super.key, required this.message});
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SelectableText(
|
||||
message,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LoadingFilledButton extends StatelessWidget {
|
||||
const LoadingFilledButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.loading,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final bool loading;
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FilledButton(
|
||||
onPressed: loading ? null : onPressed,
|
||||
child: loading
|
||||
? const SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: Text(label),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user