[UI craze] main screen implemented?
This commit is contained in:
		
							parent
							
								
									90fad4a0ac
								
							
						
					
					
						commit
						66b88f64fe
					
				
					 17 changed files with 815 additions and 142 deletions
				
			
		| 
						 | 
				
			
			@ -1,7 +1,9 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class FAppBar extends StatelessWidget implements PreferredSizeWidget {
 | 
			
		||||
  const FAppBar({super.key});
 | 
			
		||||
  final List<Widget> actions;
 | 
			
		||||
 | 
			
		||||
  const FAppBar({super.key, required this.actions});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +15,7 @@ class FAppBar extends StatelessWidget implements PreferredSizeWidget {
 | 
			
		|||
      child: AppBar(
 | 
			
		||||
        backgroundColor: Colors.transparent,
 | 
			
		||||
        elevation: 0,
 | 
			
		||||
        actions: actions,
 | 
			
		||||
      )
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,8 +21,20 @@ class FButton extends StatelessWidget {
 | 
			
		|||
        child: Container(
 | 
			
		||||
          padding: EdgeInsets.symmetric(vertical: insidePadding),
 | 
			
		||||
          decoration: BoxDecoration(
 | 
			
		||||
            color: colorScheme.primary,
 | 
			
		||||
            gradient: LinearGradient(
 | 
			
		||||
              colors: [
 | 
			
		||||
                colorScheme.primary.withOpacity(0.5),
 | 
			
		||||
                colorScheme.secondary.withOpacity(0.5),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
            borderRadius: BorderRadius.circular(4),
 | 
			
		||||
            boxShadow: [
 | 
			
		||||
              BoxShadow(
 | 
			
		||||
                color: colorScheme.primary.withOpacity(0.3),
 | 
			
		||||
                blurRadius: 5,
 | 
			
		||||
                offset: const Offset(0, 5),
 | 
			
		||||
              )
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
          child: Center(
 | 
			
		||||
            child: Text(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,11 +28,12 @@ class FDateItemWidget extends StatelessWidget {
 | 
			
		|||
        onDatePicked(date);
 | 
			
		||||
      },
 | 
			
		||||
      child: Container(
 | 
			
		||||
        width: 100,
 | 
			
		||||
        width: picked? 100: 50,
 | 
			
		||||
        decoration: BoxDecoration(
 | 
			
		||||
          borderRadius: BorderRadius.circular(50),
 | 
			
		||||
          border: Border.all(
 | 
			
		||||
            color: colorScheme.onPrimary,
 | 
			
		||||
            // color: picked ? colorScheme.onPrimary : Colors.transparent,
 | 
			
		||||
            color: Colors.transparent,
 | 
			
		||||
            width: 2,
 | 
			
		||||
          ),
 | 
			
		||||
          color: picked ? colorScheme.onPrimary.withOpacity(0.25) : Colors.transparent,
 | 
			
		||||
| 
						 | 
				
			
			@ -44,7 +45,7 @@ class FDateItemWidget extends StatelessWidget {
 | 
			
		|||
              dayOfTheWeekMap[date.weekday]!,
 | 
			
		||||
              style: TextStyle(
 | 
			
		||||
                color: colorScheme.onPrimary,
 | 
			
		||||
                fontSize: 24,
 | 
			
		||||
                fontSize: picked ? 24: 12,
 | 
			
		||||
                fontWeight: FontWeight.bold,
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
| 
						 | 
				
			
			@ -52,7 +53,7 @@ class FDateItemWidget extends StatelessWidget {
 | 
			
		|||
              '${date.day}.${date.month}',
 | 
			
		||||
              style: TextStyle(
 | 
			
		||||
                color: colorScheme.onPrimary,
 | 
			
		||||
                fontSize: 24,
 | 
			
		||||
                fontSize: picked ? 24: 12,
 | 
			
		||||
                fontWeight: FontWeight.bold,
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
| 
						 | 
				
			
			@ -102,28 +103,35 @@ class _FDatePickerWidgetState extends State<FDatePickerWidget> {
 | 
			
		|||
      child: Center(
 | 
			
		||||
        child: ListView(
 | 
			
		||||
          scrollDirection: Axis.horizontal,
 | 
			
		||||
          padding: const EdgeInsets.symmetric(horizontal: 20),
 | 
			
		||||
          shrinkWrap: true,
 | 
			
		||||
          children: <Widget>[
 | 
			
		||||
            SizedBox(width: 25),
 | 
			
		||||
            FDateItemWidget(date: date.add(Duration(days: -3)), onDatePicked: onDatePicked),
 | 
			
		||||
            FDateItemWidget(date: date.add(Duration(days: -2)), onDatePicked: onDatePicked),
 | 
			
		||||
            SizedBox(width: 25),
 | 
			
		||||
            FDateItemWidget(date: date.add(Duration(days: -1)), onDatePicked: onDatePicked),
 | 
			
		||||
            SizedBox(width: 25),
 | 
			
		||||
            FDateItemWidget(date: date, onDatePicked: onDatePicked, picked: true),
 | 
			
		||||
            SizedBox(width: 25),
 | 
			
		||||
            FDateItemWidget(date: date.add(Duration(days: 1)), onDatePicked: onDatePicked),
 | 
			
		||||
            SizedBox(width: 25),
 | 
			
		||||
            FDateItemWidget(date: date.add(Duration(days: 2)), onDatePicked: onDatePicked),
 | 
			
		||||
            Container(
 | 
			
		||||
              width: 100,
 | 
			
		||||
              width: 50,
 | 
			
		||||
              child: IconButton(
 | 
			
		||||
                icon: Icon(
 | 
			
		||||
                  Icons.calendar_month,
 | 
			
		||||
                  color: colorScheme.onPrimary,
 | 
			
		||||
                  size: 40,
 | 
			
		||||
                  size: 20,
 | 
			
		||||
                ),
 | 
			
		||||
                onPressed: () {
 | 
			
		||||
                  onDatePicked(date.add(Duration(days: 1)));
 | 
			
		||||
                  // open date picker
 | 
			
		||||
                  showDatePicker(
 | 
			
		||||
                    context: context,
 | 
			
		||||
                    initialDate: date,
 | 
			
		||||
                    firstDate: date.add(Duration(days: -365)),
 | 
			
		||||
                    lastDate: date.add(Duration(days: 365)),
 | 
			
		||||
                  ).then((value) {
 | 
			
		||||
                    if (value != null) {
 | 
			
		||||
                      onDatePicked(value);
 | 
			
		||||
                    }
 | 
			
		||||
                  });
 | 
			
		||||
                },
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										64
									
								
								lib/components/navigationBar.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								lib/components/navigationBar.dart
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,64 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:blur/blur.dart';
 | 
			
		||||
 | 
			
		||||
class FNavBar extends StatelessWidget  {
 | 
			
		||||
  final List<Widget> children;
 | 
			
		||||
  final double height;
 | 
			
		||||
 | 
			
		||||
  const FNavBar ({super.key, required this.children, this.height = 56});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    var theme = Theme.of(context);
 | 
			
		||||
    var colorScheme = theme.colorScheme;
 | 
			
		||||
 | 
			
		||||
    return SafeArea(
 | 
			
		||||
      child: Padding(
 | 
			
		||||
        padding: EdgeInsets.all(12),
 | 
			
		||||
        child: Container(
 | 
			
		||||
          decoration: BoxDecoration(
 | 
			
		||||
            borderRadius: BorderRadius.circular(24),
 | 
			
		||||
            boxShadow: [
 | 
			
		||||
              BoxShadow(
 | 
			
		||||
                color: colorScheme.primary.withOpacity(0.3),
 | 
			
		||||
                blurRadius: 5,
 | 
			
		||||
                offset: const Offset(0, 5),
 | 
			
		||||
              )
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
          child: ClipRRect(
 | 
			
		||||
            borderRadius: BorderRadius.circular(24),
 | 
			
		||||
            child: Stack(
 | 
			
		||||
              children: [
 | 
			
		||||
                Blur(
 | 
			
		||||
                  blur: 10,
 | 
			
		||||
                  blurColor: colorScheme.primary.withOpacity(0.1),
 | 
			
		||||
                  child: Container(
 | 
			
		||||
                    width: MediaQuery.of(context).size.width,
 | 
			
		||||
                    height: height * children.length,
 | 
			
		||||
                    decoration: BoxDecoration(
 | 
			
		||||
                      gradient: LinearGradient(
 | 
			
		||||
                        colors: [
 | 
			
		||||
                          colorScheme.primary.withOpacity(0.1),
 | 
			
		||||
                          colorScheme.secondary.withOpacity(0.1),
 | 
			
		||||
                        ],
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
                Container(
 | 
			
		||||
                  width: MediaQuery.of(context).size.width,
 | 
			
		||||
                  height: height * children.length,
 | 
			
		||||
                  child: Column(
 | 
			
		||||
                    mainAxisAlignment: MainAxisAlignment.center,
 | 
			
		||||
                    children: children,
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            )
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,48 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:blur/blur.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClipShadowPath extends StatelessWidget {
 | 
			
		||||
  final Shadow shadow;
 | 
			
		||||
  final CustomClipper<Path> clipper;
 | 
			
		||||
  final Widget child;
 | 
			
		||||
 | 
			
		||||
  ClipShadowPath({
 | 
			
		||||
    required this.shadow,
 | 
			
		||||
    required this.clipper,
 | 
			
		||||
    required this.child,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return CustomPaint(
 | 
			
		||||
      painter: _ClipShadowShadowPainter(
 | 
			
		||||
        clipper: this.clipper,
 | 
			
		||||
        shadow: this.shadow,
 | 
			
		||||
      ),
 | 
			
		||||
      child: ClipPath(child: child, clipper: this.clipper),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _ClipShadowShadowPainter extends CustomPainter {
 | 
			
		||||
  final Shadow shadow;
 | 
			
		||||
  final CustomClipper<Path> clipper;
 | 
			
		||||
 | 
			
		||||
  _ClipShadowShadowPainter({required this.shadow, required this.clipper});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void paint(Canvas canvas, Size size) {
 | 
			
		||||
    var paint = shadow.toPaint();
 | 
			
		||||
    var clipPath = clipper.getClip(size).shift(shadow.offset);
 | 
			
		||||
    canvas.drawPath(clipPath, paint);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool shouldRepaint(CustomPainter oldDelegate) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class BackgroundWave extends StatelessWidget {
 | 
			
		||||
  final double height;
 | 
			
		||||
| 
						 | 
				
			
			@ -12,16 +56,30 @@ class BackgroundWave extends StatelessWidget {
 | 
			
		|||
 | 
			
		||||
    return SizedBox(
 | 
			
		||||
      height: height,
 | 
			
		||||
      child: ClipPath(
 | 
			
		||||
      child: ClipShadowPath(
 | 
			
		||||
        clipper: BackgroundWaveClipper(),
 | 
			
		||||
        shadow: BoxShadow(
 | 
			
		||||
          blurRadius: 5,
 | 
			
		||||
          color: colorScheme.primary.withOpacity(0.3),
 | 
			
		||||
          offset: const Offset(0, 5),
 | 
			
		||||
        ),
 | 
			
		||||
        child: Blur(
 | 
			
		||||
          blur: 10,
 | 
			
		||||
          blurColor: colorScheme.primary.withOpacity(0.1),
 | 
			
		||||
          child: Container(
 | 
			
		||||
            width: MediaQuery.of(context).size.width,
 | 
			
		||||
            height: height,
 | 
			
		||||
            decoration: BoxDecoration(
 | 
			
		||||
              gradient: LinearGradient(
 | 
			
		||||
              colors: [colorScheme.primary, colorScheme.secondary],
 | 
			
		||||
            )),
 | 
			
		||||
          )),
 | 
			
		||||
                colors: [
 | 
			
		||||
                  colorScheme.primary.withOpacity(0.1),
 | 
			
		||||
                  colorScheme.secondary.withOpacity(0.1),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -33,14 +91,14 @@ class BackgroundWaveClipper extends CustomClipper<Path> {
 | 
			
		|||
    path.lineTo(0.0, size.height);
 | 
			
		||||
 | 
			
		||||
    var firstCurve = Offset(0, size.height - 20);
 | 
			
		||||
    var lastCurve = Offset(30, size.height - 20);
 | 
			
		||||
    var lastCurve = Offset(40, size.height - 20);
 | 
			
		||||
 | 
			
		||||
    path.quadraticBezierTo(
 | 
			
		||||
        firstCurve.dx, firstCurve.dy, lastCurve.dx, lastCurve.dy,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    firstCurve = Offset(0, size.height - 20);
 | 
			
		||||
    lastCurve = Offset(size.width - 30, size.height - 20);
 | 
			
		||||
    lastCurve = Offset(size.width - 40, size.height - 20);
 | 
			
		||||
 | 
			
		||||
    path.quadraticBezierTo(
 | 
			
		||||
        firstCurve.dx, firstCurve.dy, lastCurve.dx, lastCurve.dy,
 | 
			
		||||
| 
						 | 
				
			
			@ -72,13 +130,17 @@ class FSliverAppBar extends SliverPersistentHeaderDelegate {
 | 
			
		|||
    var adjustedShrinkOffset = shrinkOffset > minExtent ? minExtent : shrinkOffset;
 | 
			
		||||
    double offset = (minExtent - adjustedShrinkOffset);
 | 
			
		||||
 | 
			
		||||
    if (offset < 4) {
 | 
			
		||||
      offset = 4;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return Stack(
 | 
			
		||||
      children: [
 | 
			
		||||
        const BackgroundWave(
 | 
			
		||||
          height: 280,
 | 
			
		||||
        ),
 | 
			
		||||
        Positioned(
 | 
			
		||||
          top: offset + 8,
 | 
			
		||||
          top: offset,
 | 
			
		||||
          child: child,
 | 
			
		||||
          left: 16,
 | 
			
		||||
          right: 16,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ class AddEntryScreen extends BasedScreen {
 | 
			
		|||
  State<AddEntryScreen> createState() => _AddEntryScreen();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _AddEntryScreen extends State<AddEntryScreen> {
 | 
			
		||||
class _AddEntryScreen extends BasedState<AddEntryScreen> {
 | 
			
		||||
  final gramsController = TextEditingController();
 | 
			
		||||
  final productNameController = TextEditingController();
 | 
			
		||||
  Meal? meal;
 | 
			
		||||
| 
						 | 
				
			
			@ -129,7 +129,7 @@ class _AddEntryScreen extends State<AddEntryScreen> {
 | 
			
		|||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      extendBodyBehindAppBar: true,
 | 
			
		||||
      appBar: FAppBar(),
 | 
			
		||||
      appBar: appBar(),
 | 
			
		||||
      body: Center(
 | 
			
		||||
        child: Container(
 | 
			
		||||
            constraints: const BoxConstraints(maxWidth: 720),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,9 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:fooder/client.dart';
 | 
			
		||||
import 'package:fooder/components/appBar.dart';
 | 
			
		||||
import 'package:fooder/components/navigationBar.dart';
 | 
			
		||||
import 'package:fooder/screens/login.dart';
 | 
			
		||||
import 'package:fooder/screens/main.dart';
 | 
			
		||||
 | 
			
		||||
TextStyle logoStyle(context) {
 | 
			
		||||
  return Theme.of(context).textTheme.labelLarge!.copyWith(
 | 
			
		||||
| 
						 | 
				
			
			@ -14,6 +18,79 @@ abstract class BasedScreen extends StatefulWidget {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
abstract class BasedState<T extends BasedScreen> extends State<T> {
 | 
			
		||||
  void _logout() async {
 | 
			
		||||
    await widget.apiClient.logout();
 | 
			
		||||
    Navigator.pushReplacement(
 | 
			
		||||
      context,
 | 
			
		||||
      MaterialPageRoute(
 | 
			
		||||
        builder: (context) => LoginScreen(apiClient: widget.apiClient),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void backToDiary() {
 | 
			
		||||
    Navigator.pushReplacement(
 | 
			
		||||
      context,
 | 
			
		||||
      MaterialPageRoute(
 | 
			
		||||
        builder: (context) => MainScreen(apiClient: widget.apiClient),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  FAppBar appBar() {
 | 
			
		||||
    return FAppBar(
 | 
			
		||||
      actions: [
 | 
			
		||||
        IconButton(
 | 
			
		||||
          icon: Icon(
 | 
			
		||||
            Icons.logout,
 | 
			
		||||
            color: Theme.of(context).colorScheme.onPrimary,
 | 
			
		||||
          ),
 | 
			
		||||
          onPressed: _logout,
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  FNavBar navBar() {
 | 
			
		||||
    return FNavBar(
 | 
			
		||||
      children: [
 | 
			
		||||
        Row(
 | 
			
		||||
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
 | 
			
		||||
          children: [
 | 
			
		||||
            IconButton(
 | 
			
		||||
              icon: Icon(
 | 
			
		||||
                Icons.menu_book,
 | 
			
		||||
                color: Theme.of(context).colorScheme.onPrimary,
 | 
			
		||||
              ),
 | 
			
		||||
              onPressed: backToDiary,
 | 
			
		||||
            ),
 | 
			
		||||
            IconButton(
 | 
			
		||||
              icon: Icon(
 | 
			
		||||
                Icons.dinner_dining,
 | 
			
		||||
                color: Theme.of(context).colorScheme.onPrimary,
 | 
			
		||||
              ),
 | 
			
		||||
              onPressed: () => null,
 | 
			
		||||
            ),
 | 
			
		||||
            IconButton(
 | 
			
		||||
              icon: Icon(
 | 
			
		||||
                Icons.lunch_dining,
 | 
			
		||||
                color: Theme.of(context).colorScheme.onPrimary,
 | 
			
		||||
              ),
 | 
			
		||||
              onPressed: () => null,
 | 
			
		||||
            ),
 | 
			
		||||
            IconButton(
 | 
			
		||||
              icon: Icon(
 | 
			
		||||
                Icons.person,
 | 
			
		||||
                color: Theme.of(context).colorScheme.onPrimary,
 | 
			
		||||
              ),
 | 
			
		||||
              onPressed: () => null,
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void showError(String message) {
 | 
			
		||||
    ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
      SnackBar(
 | 
			
		||||
| 
						 | 
				
			
			@ -22,9 +99,10 @@ abstract class BasedState<T extends BasedScreen> extends State<T> {
 | 
			
		|||
          textAlign: TextAlign.center,
 | 
			
		||||
          style: Theme.of(context).textTheme.bodyLarge!.copyWith(
 | 
			
		||||
            fontWeight: FontWeight.bold,
 | 
			
		||||
            color: Theme.of(context).colorScheme.onError,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        backgroundColor: Theme.of(context).colorScheme.error,
 | 
			
		||||
        backgroundColor: Theme.of(context).colorScheme.error.withOpacity(0.8),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -37,9 +115,10 @@ abstract class BasedState<T extends BasedScreen> extends State<T> {
 | 
			
		|||
          textAlign: TextAlign.center,
 | 
			
		||||
          style: Theme.of(context).textTheme.bodyLarge!.copyWith(
 | 
			
		||||
            fontWeight: FontWeight.bold,
 | 
			
		||||
            color: Theme.of(context).colorScheme.onPrimary,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        backgroundColor: Theme.of(context).colorScheme.primary,
 | 
			
		||||
        backgroundColor: Theme.of(context).colorScheme.primary.withOpacity(0.8),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -94,7 +94,7 @@ class _LoginScreen extends BasedState<LoginScreen> {
 | 
			
		|||
                Icon(
 | 
			
		||||
                  Icons.lock,
 | 
			
		||||
                  size: 100,
 | 
			
		||||
                  color: colorScheme.primary,
 | 
			
		||||
                  color: colorScheme.primary.withOpacity(0.85),
 | 
			
		||||
                ),
 | 
			
		||||
                FTextInput(
 | 
			
		||||
                  labelText: 'Username',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,13 +1,15 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:fooder/screens/based.dart';
 | 
			
		||||
import 'package:fooder/screens/login.dart';
 | 
			
		||||
import 'package:fooder/screens/add_entry.dart';
 | 
			
		||||
import 'package:fooder/screens/add_meal.dart';
 | 
			
		||||
import 'package:fooder/models/diary.dart';
 | 
			
		||||
import 'package:fooder/widgets/diary.dart';
 | 
			
		||||
import 'package:fooder/widgets/summary.dart';
 | 
			
		||||
import 'package:fooder/widgets/meal.dart';
 | 
			
		||||
import 'package:fooder/components/appBar.dart';
 | 
			
		||||
import 'package:fooder/widgets/macroEntry.dart';
 | 
			
		||||
import 'package:fooder/components/sliver.dart';
 | 
			
		||||
import 'package:fooder/components/datePicker.dart';
 | 
			
		||||
import 'package:blur/blur.dart';
 | 
			
		||||
 | 
			
		||||
class MainScreen extends BasedScreen {
 | 
			
		||||
  const MainScreen({super.key, required super.apiClient});
 | 
			
		||||
| 
						 | 
				
			
			@ -43,16 +45,6 @@ class _MainScreen extends BasedState<MainScreen> {
 | 
			
		|||
    await _asyncInitState();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _logout() async {
 | 
			
		||||
    await widget.apiClient.logout();
 | 
			
		||||
    Navigator.pushReplacement(
 | 
			
		||||
      context,
 | 
			
		||||
      MaterialPageRoute(
 | 
			
		||||
        builder: (context) => LoginScreen(apiClient: widget.apiClient),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _addEntry() async {
 | 
			
		||||
    await Navigator.push(
 | 
			
		||||
      context,
 | 
			
		||||
| 
						 | 
				
			
			@ -63,10 +55,65 @@ class _MainScreen extends BasedState<MainScreen> {
 | 
			
		|||
    ).then((_) => _asyncInitState());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget floatingActionButton(BuildContext context) {
 | 
			
		||||
    var theme = Theme.of(context);
 | 
			
		||||
    var colorScheme = theme.colorScheme;
 | 
			
		||||
 | 
			
		||||
    return Container(
 | 
			
		||||
        height: 64,
 | 
			
		||||
        width: 64,
 | 
			
		||||
        decoration: BoxDecoration(
 | 
			
		||||
          borderRadius: BorderRadius.circular(32),
 | 
			
		||||
          boxShadow: [
 | 
			
		||||
            BoxShadow(
 | 
			
		||||
              color: colorScheme.primary.withOpacity(0.3),
 | 
			
		||||
              blurRadius: 5,
 | 
			
		||||
              offset: const Offset(0, 5),
 | 
			
		||||
            )
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
        child: ClipRRect(
 | 
			
		||||
          borderRadius: BorderRadius.circular(32),
 | 
			
		||||
          child: Stack(
 | 
			
		||||
            children: [
 | 
			
		||||
              Blur(
 | 
			
		||||
                blur: 10,
 | 
			
		||||
                blurColor: colorScheme.primary.withOpacity(0.1),
 | 
			
		||||
                child: Container(
 | 
			
		||||
                  height: 64,
 | 
			
		||||
                  width: 64,
 | 
			
		||||
                  decoration: BoxDecoration(
 | 
			
		||||
                    gradient: LinearGradient(
 | 
			
		||||
                      colors: [
 | 
			
		||||
                        colorScheme.primary.withOpacity(0.1),
 | 
			
		||||
                        colorScheme.secondary.withOpacity(0.1),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              Container(
 | 
			
		||||
                height: 64,
 | 
			
		||||
                width: 64,
 | 
			
		||||
                child: FloatingActionButton(
 | 
			
		||||
                  elevation: 0,
 | 
			
		||||
                  onPressed: _addEntry,
 | 
			
		||||
                  backgroundColor: Colors.transparent,
 | 
			
		||||
                  child: Icon(
 | 
			
		||||
                    Icons.library_add,
 | 
			
		||||
                    color: colorScheme.onPrimary,
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    Widget content;
 | 
			
		||||
    Widget title;
 | 
			
		||||
 | 
			
		||||
    if (diary != null) {
 | 
			
		||||
      content = CustomScrollView(
 | 
			
		||||
| 
						 | 
				
			
			@ -78,11 +125,17 @@ class _MainScreen extends BasedState<MainScreen> {
 | 
			
		|||
          SliverList(
 | 
			
		||||
            delegate: SliverChildListDelegate(
 | 
			
		||||
              [
 | 
			
		||||
                for (var meal in diary!.meals)
 | 
			
		||||
                SummaryWidget(
 | 
			
		||||
                  diary: diary!,
 | 
			
		||||
                  apiClient: widget.apiClient,
 | 
			
		||||
                  refreshParent: _asyncInitState,
 | 
			
		||||
                ),
 | 
			
		||||
                for (var (i, meal) in diary!.meals.indexed)
 | 
			
		||||
                  MealWidget(
 | 
			
		||||
                    meal: meal,
 | 
			
		||||
                    apiClient: widget.apiClient,
 | 
			
		||||
                    refreshParent: _asyncInitState,
 | 
			
		||||
                    initiallyExpanded: i == 0,
 | 
			
		||||
                  ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
| 
						 | 
				
			
			@ -93,14 +146,15 @@ class _MainScreen extends BasedState<MainScreen> {
 | 
			
		|||
      content = const Center(child: const CircularProgressIndicator());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      body: content,
 | 
			
		||||
      extendBodyBehindAppBar: true,
 | 
			
		||||
      appBar: FAppBar(),
 | 
			
		||||
      floatingActionButton: FloatingActionButton(
 | 
			
		||||
        onPressed: _addEntry,
 | 
			
		||||
        child: const Icon(Icons.add),
 | 
			
		||||
      ),
 | 
			
		||||
      extendBody: true,
 | 
			
		||||
      appBar: appBar(),
 | 
			
		||||
      bottomNavigationBar: navBar(),
 | 
			
		||||
      floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
 | 
			
		||||
      floatingActionButton: floatingActionButton(context),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -77,7 +77,7 @@ class _RegisterScreen extends BasedState<RegisterScreen> {
 | 
			
		|||
                Icon(
 | 
			
		||||
                  Icons.group_add,
 | 
			
		||||
                  size: 100,
 | 
			
		||||
                  color: colorScheme.primary,
 | 
			
		||||
                  color: colorScheme.primary.withOpacity(0.85),
 | 
			
		||||
                ),
 | 
			
		||||
                FTextInput(
 | 
			
		||||
                  labelText: 'Username',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,11 +80,12 @@ class DiaryWidget extends StatelessWidget {
 | 
			
		|||
          SliverList(
 | 
			
		||||
            delegate: SliverChildListDelegate(
 | 
			
		||||
              [
 | 
			
		||||
                for (var meal in diary.meals)
 | 
			
		||||
                for (var (i, meal) in diary.meals.indexed)
 | 
			
		||||
                  MealWidget(
 | 
			
		||||
                    meal: meal,
 | 
			
		||||
                    apiClient: apiClient,
 | 
			
		||||
                    refreshParent: refreshParent,
 | 
			
		||||
                    initiallyExpanded: i == 0,
 | 
			
		||||
                  ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,45 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:fooder/models/entry.dart';
 | 
			
		||||
import 'package:fooder/widgets/macro.dart';
 | 
			
		||||
import 'package:fooder/widgets/macroEntry.dart';
 | 
			
		||||
import 'dart:core';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EntryHeader extends StatelessWidget {
 | 
			
		||||
  final Entry entry;
 | 
			
		||||
 | 
			
		||||
  const EntryHeader(
 | 
			
		||||
      {super.key,
 | 
			
		||||
      required this.entry});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Row(
 | 
			
		||||
      children: <Widget>[
 | 
			
		||||
        Padding(
 | 
			
		||||
          padding: const EdgeInsets.all(8),
 | 
			
		||||
          child: Text(
 | 
			
		||||
            entry.product.name,
 | 
			
		||||
            style: Theme.of(context).textTheme.bodyLarge!.copyWith(
 | 
			
		||||
              color: Theme.of(context).colorScheme.onPrimary,
 | 
			
		||||
              fontWeight: FontWeight.bold,
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        Spacer(),
 | 
			
		||||
        Padding(
 | 
			
		||||
          padding: const EdgeInsets.all(8),
 | 
			
		||||
          child: Text(
 | 
			
		||||
            entry.grams.toStringAsFixed(0) + " g",
 | 
			
		||||
            style: Theme.of(context).textTheme.bodyText2!.copyWith(
 | 
			
		||||
              color: Theme.of(context).colorScheme.onPrimary,
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class EntryWidget extends StatelessWidget {
 | 
			
		||||
  final Entry entry;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -14,25 +51,12 @@ class EntryWidget extends StatelessWidget {
 | 
			
		|||
      padding: const EdgeInsets.all(8),
 | 
			
		||||
      child: Column(
 | 
			
		||||
        children: <Widget>[
 | 
			
		||||
          Row(
 | 
			
		||||
            children: <Widget>[
 | 
			
		||||
              Expanded(
 | 
			
		||||
                child: Text(
 | 
			
		||||
                  entry.product.name,
 | 
			
		||||
                  style: Theme.of(context).textTheme.titleLarge,
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              Text("${entry.calories.toStringAsFixed(1)} kcal"),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
          MacroWidget(
 | 
			
		||||
          EntryHeader(entry: entry),
 | 
			
		||||
          MacroEntryWidget(
 | 
			
		||||
            protein: entry.protein,
 | 
			
		||||
            carb: entry.carb,
 | 
			
		||||
            fat: entry.fat,
 | 
			
		||||
            amount: entry.grams,
 | 
			
		||||
            style: Theme.of(context).textTheme.bodyMedium!.copyWith(
 | 
			
		||||
                  color: Theme.of(context).colorScheme.secondary,
 | 
			
		||||
                ),
 | 
			
		||||
            calories: entry.calories,
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										156
									
								
								lib/widgets/macroEntry.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								lib/widgets/macroEntry.dart
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,156 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'dart:core';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MacroHeaderWidget extends StatelessWidget {
 | 
			
		||||
  static final double PAD_Y = 4;
 | 
			
		||||
  static final double PAD_X = 8;
 | 
			
		||||
 | 
			
		||||
  final bool? fiber;
 | 
			
		||||
  final bool? calories;
 | 
			
		||||
 | 
			
		||||
  const MacroHeaderWidget(
 | 
			
		||||
    {
 | 
			
		||||
      super.key,
 | 
			
		||||
      this.fiber = false,
 | 
			
		||||
      this.calories = false,
 | 
			
		||||
    }
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    var elements = <String>[
 | 
			
		||||
      "C(g)",
 | 
			
		||||
      "F(g)",
 | 
			
		||||
      "P(g)",
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    if (fiber == true) {
 | 
			
		||||
      elements.add(
 | 
			
		||||
        "F(g)",
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (calories == true) {
 | 
			
		||||
      elements.add(
 | 
			
		||||
        "kcal",
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var children = <Widget>[];
 | 
			
		||||
 | 
			
		||||
    for (var element in elements) {
 | 
			
		||||
      children.add(
 | 
			
		||||
        Padding(
 | 
			
		||||
          padding: EdgeInsets.symmetric(
 | 
			
		||||
            horizontal: 2,
 | 
			
		||||
          ),
 | 
			
		||||
          child: SizedBox(
 | 
			
		||||
            width: 55,
 | 
			
		||||
            child: Text(
 | 
			
		||||
              element,
 | 
			
		||||
              style: Theme.of(context).textTheme.bodyText1!.copyWith(
 | 
			
		||||
                color: Theme.of(context).colorScheme.onPrimary,
 | 
			
		||||
              ),
 | 
			
		||||
              textAlign: TextAlign.center,
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    children.add(Spacer());
 | 
			
		||||
 | 
			
		||||
    return Padding(
 | 
			
		||||
      padding: EdgeInsets.symmetric(
 | 
			
		||||
        vertical: PAD_Y,
 | 
			
		||||
        horizontal: PAD_X,
 | 
			
		||||
      ),
 | 
			
		||||
      child: Row(
 | 
			
		||||
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
 | 
			
		||||
        children: children,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MacroEntryWidget extends StatelessWidget {
 | 
			
		||||
  static final double PAD_Y = 4;
 | 
			
		||||
  static final double PAD_X = 8;
 | 
			
		||||
 | 
			
		||||
  final double protein;
 | 
			
		||||
  final double carb;
 | 
			
		||||
  final double fat;
 | 
			
		||||
  final double? fiber;
 | 
			
		||||
  final double? calories;
 | 
			
		||||
 | 
			
		||||
  const MacroEntryWidget(
 | 
			
		||||
    {
 | 
			
		||||
      super.key,
 | 
			
		||||
      required this.protein,
 | 
			
		||||
      required this.carb,
 | 
			
		||||
      required this.fat,
 | 
			
		||||
      this.fiber,
 | 
			
		||||
      this.calories,
 | 
			
		||||
    }
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    var elements = <String>[
 | 
			
		||||
      "${carb.toStringAsFixed(1)}",
 | 
			
		||||
      "${fat.toStringAsFixed(1)}",
 | 
			
		||||
      "${protein.toStringAsFixed(1)}",
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    if (fiber != null) {
 | 
			
		||||
      elements.add(
 | 
			
		||||
        "${fiber!.toStringAsFixed(1)}",
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (calories != null) {
 | 
			
		||||
      elements.add(
 | 
			
		||||
        "${calories!.toStringAsFixed(0)}",
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var children = <Widget>[];
 | 
			
		||||
 | 
			
		||||
    for (var element in elements) {
 | 
			
		||||
      children.add(
 | 
			
		||||
        Padding(
 | 
			
		||||
          padding: EdgeInsets.symmetric(
 | 
			
		||||
            horizontal: 2,
 | 
			
		||||
          ),
 | 
			
		||||
          child: SizedBox(
 | 
			
		||||
            width: 55,
 | 
			
		||||
            child: Text(
 | 
			
		||||
              element,
 | 
			
		||||
              style: Theme.of(context).textTheme.bodyText1!.copyWith(
 | 
			
		||||
                color: Theme.of(context).colorScheme.onPrimary,
 | 
			
		||||
              ),
 | 
			
		||||
              textAlign: TextAlign.center,
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    children.add(Spacer());
 | 
			
		||||
 | 
			
		||||
    return Padding(
 | 
			
		||||
      padding: EdgeInsets.symmetric(
 | 
			
		||||
        vertical: PAD_Y,
 | 
			
		||||
        horizontal: PAD_X,
 | 
			
		||||
      ),
 | 
			
		||||
      child: Row(
 | 
			
		||||
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
 | 
			
		||||
        children: children,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,29 +1,60 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:fooder/models/meal.dart';
 | 
			
		||||
import 'package:fooder/widgets/entry.dart';
 | 
			
		||||
import 'package:fooder/widgets/macro.dart';
 | 
			
		||||
import 'package:fooder/widgets/macroEntry.dart';
 | 
			
		||||
import 'package:fooder/screens/edit_entry.dart';
 | 
			
		||||
import 'package:fooder/screens/meal.dart';
 | 
			
		||||
import 'package:fooder/client.dart';
 | 
			
		||||
import 'dart:core';
 | 
			
		||||
 | 
			
		||||
class MealWidget extends StatelessWidget {
 | 
			
		||||
  final Meal meal;
 | 
			
		||||
  final ApiClient apiClient;
 | 
			
		||||
  final Function() refreshParent;
 | 
			
		||||
 | 
			
		||||
  const MealWidget(
 | 
			
		||||
class MealHeader extends StatelessWidget {
 | 
			
		||||
  final Meal meal;
 | 
			
		||||
 | 
			
		||||
  const MealHeader(
 | 
			
		||||
      {super.key,
 | 
			
		||||
      required this.meal,
 | 
			
		||||
      required this.apiClient,
 | 
			
		||||
      required this.refreshParent});
 | 
			
		||||
      required this.meal});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Card(
 | 
			
		||||
        child: GestureDetector(
 | 
			
		||||
      onLongPress: () {
 | 
			
		||||
        Navigator.push(
 | 
			
		||||
    return Row(
 | 
			
		||||
      children: <Widget>[
 | 
			
		||||
        Padding(
 | 
			
		||||
          padding: const EdgeInsets.all(8),
 | 
			
		||||
          child: Text(
 | 
			
		||||
            meal.name,
 | 
			
		||||
            style: Theme.of(context).textTheme.headlineSmall!.copyWith(
 | 
			
		||||
              color: Theme.of(context).colorScheme.onPrimary,
 | 
			
		||||
              fontWeight: FontWeight.bold,
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MealWidget extends StatelessWidget {
 | 
			
		||||
  static final MAX_WIDTH = 920.0;
 | 
			
		||||
 | 
			
		||||
  final Meal meal;
 | 
			
		||||
  final ApiClient apiClient;
 | 
			
		||||
  final Function() refreshParent;
 | 
			
		||||
  final bool initiallyExpanded;
 | 
			
		||||
 | 
			
		||||
  const MealWidget(
 | 
			
		||||
    {
 | 
			
		||||
      super.key,
 | 
			
		||||
      required this.meal,
 | 
			
		||||
      required this.apiClient,
 | 
			
		||||
      required this.refreshParent,
 | 
			
		||||
      required this.initiallyExpanded,
 | 
			
		||||
    }
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  Future<void> _editMeal(context) async {
 | 
			
		||||
    await Navigator.push(
 | 
			
		||||
      context,
 | 
			
		||||
      MaterialPageRoute(
 | 
			
		||||
        builder: (context) => MealScreen(
 | 
			
		||||
| 
						 | 
				
			
			@ -32,47 +63,11 @@ class MealWidget extends StatelessWidget {
 | 
			
		|||
          refresh: refreshParent,
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
        ).then((_) {
 | 
			
		||||
          refreshParent();
 | 
			
		||||
        });
 | 
			
		||||
      },
 | 
			
		||||
      child: Padding(
 | 
			
		||||
        padding: const EdgeInsets.only(
 | 
			
		||||
            top: 36.0, left: 6.0, right: 6.0, bottom: 6.0),
 | 
			
		||||
        child: ExpansionTile(
 | 
			
		||||
          title: Column(
 | 
			
		||||
            children: <Widget>[
 | 
			
		||||
              Row(
 | 
			
		||||
                children: <Widget>[
 | 
			
		||||
                  Expanded(
 | 
			
		||||
                    child: Text(
 | 
			
		||||
                      meal.name,
 | 
			
		||||
                      style: Theme.of(context).textTheme.titleLarge?.copyWith(
 | 
			
		||||
                            color: Theme.of(context).colorScheme.primary,
 | 
			
		||||
                          ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  Text("${meal.calories.toStringAsFixed(1)} kcal"),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
              MacroWidget(
 | 
			
		||||
                protein: meal.protein,
 | 
			
		||||
                carb: meal.carb,
 | 
			
		||||
                fat: meal.fat,
 | 
			
		||||
                style: Theme.of(context).textTheme.bodyMedium!.copyWith(
 | 
			
		||||
                      color: Theme.of(context).colorScheme.secondary,
 | 
			
		||||
                    ),
 | 
			
		||||
              ),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
          children: <Widget>[
 | 
			
		||||
            for (var entry in meal.entries)
 | 
			
		||||
              ListTile(
 | 
			
		||||
                title: EntryWidget(
 | 
			
		||||
                  entry: entry,
 | 
			
		||||
                ),
 | 
			
		||||
                onTap: () {
 | 
			
		||||
                  Navigator.push(
 | 
			
		||||
    ).then((_) => refreshParent());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _editEntry(context, entry) async {
 | 
			
		||||
    await Navigator.push(
 | 
			
		||||
      context,
 | 
			
		||||
      MaterialPageRoute(
 | 
			
		||||
        builder: (context) => EditEntryScreen(
 | 
			
		||||
| 
						 | 
				
			
			@ -80,14 +75,78 @@ class MealWidget extends StatelessWidget {
 | 
			
		|||
          entry: entry,
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
                  ).then((_) {
 | 
			
		||||
                    refreshParent();
 | 
			
		||||
                  });
 | 
			
		||||
                },
 | 
			
		||||
    ).then((_) => refreshParent());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    var theme = Theme.of(context);
 | 
			
		||||
    var colorScheme = theme.colorScheme;
 | 
			
		||||
 | 
			
		||||
    var width_avail = MediaQuery.of(context).size.width;
 | 
			
		||||
    var width = width_avail > MAX_WIDTH ? MAX_WIDTH : width_avail;
 | 
			
		||||
 | 
			
		||||
    return Center(
 | 
			
		||||
      child: Padding(
 | 
			
		||||
        padding: const EdgeInsets.all(8),
 | 
			
		||||
        child: Card(
 | 
			
		||||
          elevation: 4,
 | 
			
		||||
          clipBehavior: Clip.antiAlias,
 | 
			
		||||
          shadowColor: colorScheme.primary.withOpacity(1.0),
 | 
			
		||||
          shape: RoundedRectangleBorder(
 | 
			
		||||
            borderRadius: BorderRadius.circular(24),
 | 
			
		||||
          ),
 | 
			
		||||
          child: SizedBox(
 | 
			
		||||
            width: width,
 | 
			
		||||
            child: Container(
 | 
			
		||||
              decoration: BoxDecoration(
 | 
			
		||||
                gradient: LinearGradient(
 | 
			
		||||
                  colors: [
 | 
			
		||||
                    colorScheme.primary.withOpacity(0.6),
 | 
			
		||||
                    colorScheme.secondary.withOpacity(0.5),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              child: InkWell(
 | 
			
		||||
                splashColor: Colors.blue.withAlpha(30),
 | 
			
		||||
                onLongPress: () => _editMeal(context),
 | 
			
		||||
                child: ExpansionTile(
 | 
			
		||||
                  iconColor: colorScheme.onPrimary,
 | 
			
		||||
                  collapsedIconColor: colorScheme.onPrimary,
 | 
			
		||||
                  initiallyExpanded: initiallyExpanded,
 | 
			
		||||
                  title: Padding(
 | 
			
		||||
                    padding: const EdgeInsets.all(8),
 | 
			
		||||
                    child: Column(
 | 
			
		||||
                      children: <Widget>[
 | 
			
		||||
                        MealHeader(meal: meal),
 | 
			
		||||
                        MacroHeaderWidget(
 | 
			
		||||
                          calories: true,
 | 
			
		||||
                        ),
 | 
			
		||||
                        MacroEntryWidget(
 | 
			
		||||
                          protein: meal.protein,
 | 
			
		||||
                          carb: meal.carb,
 | 
			
		||||
                          fat: meal.fat,
 | 
			
		||||
                          calories: meal.calories,
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  children: <Widget>[
 | 
			
		||||
                    for (var (i, entry) in meal.entries.indexed)
 | 
			
		||||
                      ListTile(
 | 
			
		||||
                        title: EntryWidget(
 | 
			
		||||
                          entry: entry,
 | 
			
		||||
                        ),
 | 
			
		||||
                        tileColor: i % 2 == 0 ? colorScheme.secondary.withOpacity(0.1): Colors.transparent,
 | 
			
		||||
                        onTap: () => _editEntry(context, entry),
 | 
			
		||||
                      )
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
    ));
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										125
									
								
								lib/widgets/summary.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								lib/widgets/summary.dart
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,125 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:fooder/models/diary.dart';
 | 
			
		||||
import 'package:fooder/widgets/macroEntry.dart';
 | 
			
		||||
import 'package:fooder/screens/add_meal.dart';
 | 
			
		||||
import 'package:fooder/client.dart';
 | 
			
		||||
import 'dart:core';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SummaryHeader extends StatelessWidget {
 | 
			
		||||
  final Diary diary;
 | 
			
		||||
  final Function addMeal;
 | 
			
		||||
 | 
			
		||||
  const SummaryHeader(
 | 
			
		||||
      {super.key,
 | 
			
		||||
      required this.addMeal,
 | 
			
		||||
      required this.diary});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Row(
 | 
			
		||||
      children: <Widget>[
 | 
			
		||||
        Padding(
 | 
			
		||||
          padding: const EdgeInsets.symmetric(horizontal: 8),
 | 
			
		||||
          child: Text(
 | 
			
		||||
            "Summary",
 | 
			
		||||
            style: Theme.of(context).textTheme.headlineSmall!.copyWith(
 | 
			
		||||
              color: Theme.of(context).colorScheme.onPrimary,
 | 
			
		||||
              fontWeight: FontWeight.bold,
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        Spacer(),
 | 
			
		||||
        Padding(
 | 
			
		||||
          padding: const EdgeInsets.symmetric(horizontal: 8),
 | 
			
		||||
          child: IconButton(
 | 
			
		||||
            icon: Icon(Icons.playlist_add_rounded),
 | 
			
		||||
            iconSize: 32,
 | 
			
		||||
            color: Theme.of(context).colorScheme.onPrimary,
 | 
			
		||||
            onPressed: () => addMeal(context),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SummaryWidget extends StatelessWidget {
 | 
			
		||||
  static final MAX_WIDTH = 920.0;
 | 
			
		||||
 | 
			
		||||
  final Diary diary;
 | 
			
		||||
  final ApiClient apiClient;
 | 
			
		||||
  final Function() refreshParent;
 | 
			
		||||
 | 
			
		||||
  const SummaryWidget(
 | 
			
		||||
      {super.key,
 | 
			
		||||
      required this.diary,
 | 
			
		||||
      required this.apiClient,
 | 
			
		||||
      required this.refreshParent});
 | 
			
		||||
 | 
			
		||||
  Future<void> _addMeal(context) async {
 | 
			
		||||
    await Navigator.push(
 | 
			
		||||
      context,
 | 
			
		||||
      MaterialPageRoute(
 | 
			
		||||
        builder: (context) => AddMealScreen(
 | 
			
		||||
          apiClient: apiClient,
 | 
			
		||||
          diary: diary,
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    ).then((_) => refreshParent());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    var theme = Theme.of(context);
 | 
			
		||||
    var colorScheme = theme.colorScheme;
 | 
			
		||||
 | 
			
		||||
    var width_avail = MediaQuery.of(context).size.width;
 | 
			
		||||
    var width = width_avail > MAX_WIDTH ? MAX_WIDTH : width_avail;
 | 
			
		||||
 | 
			
		||||
    return Center(
 | 
			
		||||
      child: Padding(
 | 
			
		||||
        padding: const EdgeInsets.all(8),
 | 
			
		||||
        child: Card(
 | 
			
		||||
          elevation: 4,
 | 
			
		||||
          clipBehavior: Clip.antiAlias,
 | 
			
		||||
          shadowColor: colorScheme.primary.withOpacity(1.0),
 | 
			
		||||
          shape: RoundedRectangleBorder(
 | 
			
		||||
            borderRadius: BorderRadius.circular(24),
 | 
			
		||||
          ),
 | 
			
		||||
          child: SizedBox(
 | 
			
		||||
            width: width,
 | 
			
		||||
            child: Container(
 | 
			
		||||
              decoration: BoxDecoration(
 | 
			
		||||
                gradient: LinearGradient(
 | 
			
		||||
                  colors: [
 | 
			
		||||
                    colorScheme.primary.withOpacity(0.6),
 | 
			
		||||
                    colorScheme.secondary.withOpacity(0.5),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              child: Padding(
 | 
			
		||||
                padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24),
 | 
			
		||||
                child: Column(
 | 
			
		||||
                  children: <Widget>[
 | 
			
		||||
                    SummaryHeader(diary: diary, addMeal: _addMeal),
 | 
			
		||||
                    MacroHeaderWidget(
 | 
			
		||||
                      calories: true,
 | 
			
		||||
                    ),
 | 
			
		||||
                    MacroEntryWidget(
 | 
			
		||||
                      protein: diary.protein,
 | 
			
		||||
                      carb: diary.carb,
 | 
			
		||||
                      fat: diary.fat,
 | 
			
		||||
                      calories: diary.calories,
 | 
			
		||||
                    ),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								pubspec.lock
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -9,6 +9,14 @@ packages:
 | 
			
		|||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.11.0"
 | 
			
		||||
  blur:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: blur
 | 
			
		||||
      sha256: fd23f1247faee4a7d1a3efb6b7c3cea134f3b939d72e5f8d45233deb0776259f
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.1.0"
 | 
			
		||||
  boolean_selector:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
| 
						 | 
				
			
			@ -41,6 +49,14 @@ packages:
 | 
			
		|||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.18.0"
 | 
			
		||||
  crypto:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: crypto
 | 
			
		||||
      sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.0.3"
 | 
			
		||||
  cupertino_icons:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
| 
						 | 
				
			
			@ -168,6 +184,14 @@ packages:
 | 
			
		|||
    description: flutter
 | 
			
		||||
    source: sdk
 | 
			
		||||
    version: "0.0.0"
 | 
			
		||||
  google_fonts:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: google_fonts
 | 
			
		||||
      sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "6.2.1"
 | 
			
		||||
  http:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
| 
						 | 
				
			
			@ -495,4 +519,4 @@ packages:
 | 
			
		|||
    version: "1.0.4"
 | 
			
		||||
sdks:
 | 
			
		||||
  dart: ">=3.3.0 <4.0.0"
 | 
			
		||||
  flutter: ">=3.16.0"
 | 
			
		||||
  flutter: ">=3.19.2"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,7 +39,9 @@ dependencies:
 | 
			
		|||
  intl: ^0.19.0
 | 
			
		||||
  flutter_secure_storage: ^9.0.0
 | 
			
		||||
  simple_barcode_scanner: ^0.1.1
 | 
			
		||||
  google_fonts: ^6.2.1
 | 
			
		||||
  flex_color_scheme: ^7.3.1
 | 
			
		||||
  blur: ^3.1.0
 | 
			
		||||
 | 
			
		||||
dev_dependencies:
 | 
			
		||||
  flutter_test:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue