mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-10 18:07:20 +01:00
Some checks failed
file-length / file length <= 250 lines (push) Has been cancelled
md-naming / markdown naming convention (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
Tests / test (3.10) (push) Has been cancelled
Tests / test (3.11) (push) Has been cancelled
Tests / test (3.12) (push) Has been cancelled
release-apk / release (APK) (push) Has been cancelled
Renames this repo's markdown onto README.md / CLAUDE*.md / DOCS*.md / TODO*.md so a future session can tell a live task from a finished record without reading the file to find out. TODO files carry the literal "REMOVE ME AFTER FINISH" and are deleted when the work lands. Enforced by the md-naming pre-commit hook and CI workflow, both delegating to the shared gate in kuhyx/utils so this repo's idea of a valid name cannot drift from every other repo's. Includes the patch bump this repo's bump-patch-version hook applies to app/pubspec.yaml on every commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A47aX5DJwmA3QFn3pkEWi7
6.4 KiB
6.4 KiB
UI: theming, layout and overlays
Vendored Flutter/Dart AI rules. See CLAUDE.md.
UI Theming and Styling Code
- Responsiveness: Use
LayoutBuilderorMediaQueryto create responsive UIs. - Text: Use
Theme.of(context).textThemefor text styles. - Text Fields: Configure
textCapitalization,keyboardType, andplaceholder.
Material Theming Best Practices
Embrace ThemeData and Material 3
- Use
ColorScheme.fromSeed(): Use this to generate a complete, harmonious color palette for both light and dark modes from a single seed color. - Define Light and Dark Themes: Provide both
themeanddarkThemeto yourMaterialAppto support system brightness settings seamlessly. - Centralize Component Styles: Customize specific component themes (e.g.,
elevatedButtonTheme,cardTheme,appBarTheme) withinThemeDatato ensure consistency. - Dark/Light Mode and Theme Toggle: Implement support for both light and
dark themes using
themeanddarkThemeproperties ofMaterialApp. ThethemeModeproperty can be dynamically controlled (e.g., via aChangeNotifierProvider) to allow for toggling betweenThemeMode.light,ThemeMode.dark, orThemeMode.system.
// main.dart
MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.light,
),
textTheme: const TextTheme(
displayLarge: TextStyle(fontSize: 57.0, fontWeight: FontWeight.bold),
bodyMedium: TextStyle(fontSize: 14.0, height: 1.4),
),
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.dark,
),
),
home: const MyHomePage(),
);
Implement Design Tokens with ThemeExtension
For custom styles that aren't part of the standard ThemeData, use
ThemeExtension to define reusable design tokens.
- Create a Custom Theme Extension: Define a class that extends
ThemeExtension<T>and include your custom properties. - Implement
copyWithandlerp: These methods are required for the extension to work correctly with theme transitions. - Register in
ThemeData: Add your custom extension to theextensionslist in yourThemeData. - Access Tokens in Widgets: Use
Theme.of(context).extension<MyColors>()!to access your custom tokens.
// 1. Define the extension
@immutable
class MyColors extends ThemeExtension<MyColors> {
const MyColors({required this.success, required this.danger});
final Color? success;
final Color? danger;
@override
ThemeExtension<MyColors> copyWith({Color? success, Color? danger}) {
return MyColors(success: success ?? this.success, danger: danger ?? this.danger);
}
@override
ThemeExtension<MyColors> lerp(ThemeExtension<MyColors>? other, double t) {
if (other is! MyColors) return this;
return MyColors(
success: Color.lerp(success, other.success, t),
danger: Color.lerp(danger, other.danger, t),
);
}
}
// 2. Register it in ThemeData
theme: ThemeData(
extensions: const <ThemeExtension<dynamic>>[
MyColors(success: Colors.green, danger: Colors.red),
],
),
// 3. Use it in a widget
Container(
color: Theme.of(context).extension<MyColors>()!.success,
)
Styling with WidgetStateProperty
WidgetStateProperty.resolveWith: Provide a function that receives aSet<WidgetState>and returns the appropriate value for the current state.WidgetStateProperty.all: A shorthand for when the value is the same for all states.
// Example: Creating a button style that changes color when pressed.
final ButtonStyle myButtonStyle = ButtonStyle(
backgroundColor: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> states) {
if (states.contains(WidgetState.pressed)) {
return Colors.green; // Color when pressed
}
return Colors.red; // Default color
},
),
);
Layout Best Practices
Building Flexible and Overflow-Safe Layouts
For Rows and Columns
Expanded: Use to make a child widget fill the remaining available space along the main axis.Flexible: Use when you want a widget to shrink to fit, but not necessarily grow. Don't combineFlexibleandExpandedin the sameRoworColumn.Wrap: Use when you have a series of widgets that would overflow aRoworColumn, and you want them to move to the next line.
For General Content
SingleChildScrollView: Use when your content is intrinsically larger than the viewport, but is a fixed size.ListView/GridView: For long lists or grids of content, always use a builder constructor (.builder).FittedBox: Use to scale or fit a single child widget within its parent.LayoutBuilder: Use for complex, responsive layouts to make decisions based on the available space.
Layering Widgets with Stack
Positioned: Use to precisely place a child within aStackby anchoring it to the edges.Align: Use to position a child within aStackusing alignments likeAlignment.center.
Advanced Layout with Overlays
-
OverlayPortal: Use this widget to show UI elements (like custom dropdowns or tooltips) "on top" of everything else. It manages theOverlayEntryfor you.class MyDropdown extends StatefulWidget { const MyDropdown({super.key}); @override State<MyDropdown> createState() => _MyDropdownState(); } class _MyDropdownState extends State<MyDropdown> { final _controller = OverlayPortalController(); @override Widget build(BuildContext context) { return OverlayPortal( controller: _controller, overlayChildBuilder: (BuildContext context) { return const Positioned( top: 50, left: 10, child: Card( child: Padding( padding: EdgeInsets.all(8.0), child: Text('I am an overlay!'), ), ), ); }, child: ElevatedButton( onPressed: _controller.toggle, child: const Text('Toggle Overlay'), ), ); } }