[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';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class FAppBar extends StatelessWidget implements PreferredSizeWidget {
|
class FAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
const FAppBar({super.key});
|
final List<Widget> actions;
|
||||||
|
|
||||||
|
const FAppBar({super.key, required this.actions});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -13,6 +15,7 @@ class FAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
child: AppBar(
|
child: AppBar(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
|
actions: actions,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,20 @@ class FButton extends StatelessWidget {
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.symmetric(vertical: insidePadding),
|
padding: EdgeInsets.symmetric(vertical: insidePadding),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: colorScheme.primary,
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
colorScheme.primary.withOpacity(0.5),
|
||||||
|
colorScheme.secondary.withOpacity(0.5),
|
||||||
|
],
|
||||||
|
),
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: colorScheme.primary.withOpacity(0.3),
|
||||||
|
blurRadius: 5,
|
||||||
|
offset: const Offset(0, 5),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
|
@ -28,11 +28,12 @@ class FDateItemWidget extends StatelessWidget {
|
||||||
onDatePicked(date);
|
onDatePicked(date);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 100,
|
width: picked? 100: 50,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(50),
|
borderRadius: BorderRadius.circular(50),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: colorScheme.onPrimary,
|
// color: picked ? colorScheme.onPrimary : Colors.transparent,
|
||||||
|
color: Colors.transparent,
|
||||||
width: 2,
|
width: 2,
|
||||||
),
|
),
|
||||||
color: picked ? colorScheme.onPrimary.withOpacity(0.25) : Colors.transparent,
|
color: picked ? colorScheme.onPrimary.withOpacity(0.25) : Colors.transparent,
|
||||||
|
@ -44,7 +45,7 @@ class FDateItemWidget extends StatelessWidget {
|
||||||
dayOfTheWeekMap[date.weekday]!,
|
dayOfTheWeekMap[date.weekday]!,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: colorScheme.onPrimary,
|
color: colorScheme.onPrimary,
|
||||||
fontSize: 24,
|
fontSize: picked ? 24: 12,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -52,7 +53,7 @@ class FDateItemWidget extends StatelessWidget {
|
||||||
'${date.day}.${date.month}',
|
'${date.day}.${date.month}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: colorScheme.onPrimary,
|
color: colorScheme.onPrimary,
|
||||||
fontSize: 24,
|
fontSize: picked ? 24: 12,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -102,28 +103,35 @@ class _FDatePickerWidgetState extends State<FDatePickerWidget> {
|
||||||
child: Center(
|
child: Center(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
SizedBox(width: 25),
|
FDateItemWidget(date: date.add(Duration(days: -3)), onDatePicked: onDatePicked),
|
||||||
FDateItemWidget(date: date.add(Duration(days: -2)), onDatePicked: onDatePicked),
|
FDateItemWidget(date: date.add(Duration(days: -2)), onDatePicked: onDatePicked),
|
||||||
SizedBox(width: 25),
|
|
||||||
FDateItemWidget(date: date.add(Duration(days: -1)), onDatePicked: onDatePicked),
|
FDateItemWidget(date: date.add(Duration(days: -1)), onDatePicked: onDatePicked),
|
||||||
SizedBox(width: 25),
|
|
||||||
FDateItemWidget(date: date, onDatePicked: onDatePicked, picked: true),
|
FDateItemWidget(date: date, onDatePicked: onDatePicked, picked: true),
|
||||||
SizedBox(width: 25),
|
|
||||||
FDateItemWidget(date: date.add(Duration(days: 1)), onDatePicked: onDatePicked),
|
FDateItemWidget(date: date.add(Duration(days: 1)), onDatePicked: onDatePicked),
|
||||||
SizedBox(width: 25),
|
|
||||||
FDateItemWidget(date: date.add(Duration(days: 2)), onDatePicked: onDatePicked),
|
FDateItemWidget(date: date.add(Duration(days: 2)), onDatePicked: onDatePicked),
|
||||||
Container(
|
Container(
|
||||||
width: 100,
|
width: 50,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.calendar_month,
|
Icons.calendar_month,
|
||||||
color: colorScheme.onPrimary,
|
color: colorScheme.onPrimary,
|
||||||
size: 40,
|
size: 20,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
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: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 {
|
class BackgroundWave extends StatelessWidget {
|
||||||
final double height;
|
final double height;
|
||||||
|
@ -12,16 +56,30 @@ class BackgroundWave extends StatelessWidget {
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: height,
|
height: height,
|
||||||
child: ClipPath(
|
child: ClipShadowPath(
|
||||||
clipper: BackgroundWaveClipper(),
|
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(
|
child: Container(
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery.of(context).size.width,
|
||||||
height: height,
|
height: height,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
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);
|
path.lineTo(0.0, size.height);
|
||||||
|
|
||||||
var firstCurve = Offset(0, size.height - 20);
|
var firstCurve = Offset(0, size.height - 20);
|
||||||
var lastCurve = Offset(30, size.height - 20);
|
var lastCurve = Offset(40, size.height - 20);
|
||||||
|
|
||||||
path.quadraticBezierTo(
|
path.quadraticBezierTo(
|
||||||
firstCurve.dx, firstCurve.dy, lastCurve.dx, lastCurve.dy,
|
firstCurve.dx, firstCurve.dy, lastCurve.dx, lastCurve.dy,
|
||||||
);
|
);
|
||||||
|
|
||||||
firstCurve = Offset(0, size.height - 20);
|
firstCurve = Offset(0, size.height - 20);
|
||||||
lastCurve = Offset(size.width - 30, size.height - 20);
|
lastCurve = Offset(size.width - 40, size.height - 20);
|
||||||
|
|
||||||
path.quadraticBezierTo(
|
path.quadraticBezierTo(
|
||||||
firstCurve.dx, firstCurve.dy, lastCurve.dx, lastCurve.dy,
|
firstCurve.dx, firstCurve.dy, lastCurve.dx, lastCurve.dy,
|
||||||
|
@ -72,13 +130,17 @@ class FSliverAppBar extends SliverPersistentHeaderDelegate {
|
||||||
var adjustedShrinkOffset = shrinkOffset > minExtent ? minExtent : shrinkOffset;
|
var adjustedShrinkOffset = shrinkOffset > minExtent ? minExtent : shrinkOffset;
|
||||||
double offset = (minExtent - adjustedShrinkOffset);
|
double offset = (minExtent - adjustedShrinkOffset);
|
||||||
|
|
||||||
|
if (offset < 4) {
|
||||||
|
offset = 4;
|
||||||
|
}
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
const BackgroundWave(
|
const BackgroundWave(
|
||||||
height: 280,
|
height: 280,
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
top: offset + 8,
|
top: offset,
|
||||||
child: child,
|
child: child,
|
||||||
left: 16,
|
left: 16,
|
||||||
right: 16,
|
right: 16,
|
||||||
|
|
|
@ -20,7 +20,7 @@ class AddEntryScreen extends BasedScreen {
|
||||||
State<AddEntryScreen> createState() => _AddEntryScreen();
|
State<AddEntryScreen> createState() => _AddEntryScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AddEntryScreen extends State<AddEntryScreen> {
|
class _AddEntryScreen extends BasedState<AddEntryScreen> {
|
||||||
final gramsController = TextEditingController();
|
final gramsController = TextEditingController();
|
||||||
final productNameController = TextEditingController();
|
final productNameController = TextEditingController();
|
||||||
Meal? meal;
|
Meal? meal;
|
||||||
|
@ -129,7 +129,7 @@ class _AddEntryScreen extends State<AddEntryScreen> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
extendBodyBehindAppBar: true,
|
extendBodyBehindAppBar: true,
|
||||||
appBar: FAppBar(),
|
appBar: appBar(),
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 720),
|
constraints: const BoxConstraints(maxWidth: 720),
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fooder/client.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) {
|
TextStyle logoStyle(context) {
|
||||||
return Theme.of(context).textTheme.labelLarge!.copyWith(
|
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> {
|
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) {
|
void showError(String message) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
|
@ -22,9 +99,10 @@ abstract class BasedState<T extends BasedScreen> extends State<T> {
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
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,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
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(
|
Icon(
|
||||||
Icons.lock,
|
Icons.lock,
|
||||||
size: 100,
|
size: 100,
|
||||||
color: colorScheme.primary,
|
color: colorScheme.primary.withOpacity(0.85),
|
||||||
),
|
),
|
||||||
FTextInput(
|
FTextInput(
|
||||||
labelText: 'Username',
|
labelText: 'Username',
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fooder/screens/based.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_entry.dart';
|
||||||
|
import 'package:fooder/screens/add_meal.dart';
|
||||||
import 'package:fooder/models/diary.dart';
|
import 'package:fooder/models/diary.dart';
|
||||||
import 'package:fooder/widgets/diary.dart';
|
import 'package:fooder/widgets/diary.dart';
|
||||||
|
import 'package:fooder/widgets/summary.dart';
|
||||||
import 'package:fooder/widgets/meal.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/sliver.dart';
|
||||||
import 'package:fooder/components/datePicker.dart';
|
import 'package:fooder/components/datePicker.dart';
|
||||||
|
import 'package:blur/blur.dart';
|
||||||
|
|
||||||
class MainScreen extends BasedScreen {
|
class MainScreen extends BasedScreen {
|
||||||
const MainScreen({super.key, required super.apiClient});
|
const MainScreen({super.key, required super.apiClient});
|
||||||
|
@ -43,16 +45,6 @@ class _MainScreen extends BasedState<MainScreen> {
|
||||||
await _asyncInitState();
|
await _asyncInitState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _logout() async {
|
|
||||||
await widget.apiClient.logout();
|
|
||||||
Navigator.pushReplacement(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => LoginScreen(apiClient: widget.apiClient),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _addEntry() async {
|
Future<void> _addEntry() async {
|
||||||
await Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
|
@ -63,10 +55,65 @@ class _MainScreen extends BasedState<MainScreen> {
|
||||||
).then((_) => _asyncInitState());
|
).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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget content;
|
Widget content;
|
||||||
Widget title;
|
|
||||||
|
|
||||||
if (diary != null) {
|
if (diary != null) {
|
||||||
content = CustomScrollView(
|
content = CustomScrollView(
|
||||||
|
@ -78,11 +125,17 @@ class _MainScreen extends BasedState<MainScreen> {
|
||||||
SliverList(
|
SliverList(
|
||||||
delegate: SliverChildListDelegate(
|
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(
|
MealWidget(
|
||||||
meal: meal,
|
meal: meal,
|
||||||
apiClient: widget.apiClient,
|
apiClient: widget.apiClient,
|
||||||
refreshParent: _asyncInitState,
|
refreshParent: _asyncInitState,
|
||||||
|
initiallyExpanded: i == 0,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -93,14 +146,15 @@ class _MainScreen extends BasedState<MainScreen> {
|
||||||
content = const Center(child: const CircularProgressIndicator());
|
content = const Center(child: const CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: content,
|
body: content,
|
||||||
extendBodyBehindAppBar: true,
|
extendBodyBehindAppBar: true,
|
||||||
appBar: FAppBar(),
|
extendBody: true,
|
||||||
floatingActionButton: FloatingActionButton(
|
appBar: appBar(),
|
||||||
onPressed: _addEntry,
|
bottomNavigationBar: navBar(),
|
||||||
child: const Icon(Icons.add),
|
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
|
||||||
),
|
floatingActionButton: floatingActionButton(context),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ class _RegisterScreen extends BasedState<RegisterScreen> {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.group_add,
|
Icons.group_add,
|
||||||
size: 100,
|
size: 100,
|
||||||
color: colorScheme.primary,
|
color: colorScheme.primary.withOpacity(0.85),
|
||||||
),
|
),
|
||||||
FTextInput(
|
FTextInput(
|
||||||
labelText: 'Username',
|
labelText: 'Username',
|
||||||
|
|
|
@ -80,11 +80,12 @@ class DiaryWidget extends StatelessWidget {
|
||||||
SliverList(
|
SliverList(
|
||||||
delegate: SliverChildListDelegate(
|
delegate: SliverChildListDelegate(
|
||||||
[
|
[
|
||||||
for (var meal in diary.meals)
|
for (var (i, meal) in diary.meals.indexed)
|
||||||
MealWidget(
|
MealWidget(
|
||||||
meal: meal,
|
meal: meal,
|
||||||
apiClient: apiClient,
|
apiClient: apiClient,
|
||||||
refreshParent: refreshParent,
|
refreshParent: refreshParent,
|
||||||
|
initiallyExpanded: i == 0,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,8 +1,45 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fooder/models/entry.dart';
|
import 'package:fooder/models/entry.dart';
|
||||||
import 'package:fooder/widgets/macro.dart';
|
import 'package:fooder/widgets/macroEntry.dart';
|
||||||
import 'dart:core';
|
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 {
|
class EntryWidget extends StatelessWidget {
|
||||||
final Entry entry;
|
final Entry entry;
|
||||||
|
|
||||||
|
@ -14,25 +51,12 @@ class EntryWidget extends StatelessWidget {
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Row(
|
EntryHeader(entry: entry),
|
||||||
children: <Widget>[
|
MacroEntryWidget(
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
entry.product.name,
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text("${entry.calories.toStringAsFixed(1)} kcal"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
MacroWidget(
|
|
||||||
protein: entry.protein,
|
protein: entry.protein,
|
||||||
carb: entry.carb,
|
carb: entry.carb,
|
||||||
fat: entry.fat,
|
fat: entry.fat,
|
||||||
amount: entry.grams,
|
calories: entry.calories,
|
||||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
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,93 +1,152 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fooder/models/meal.dart';
|
import 'package:fooder/models/meal.dart';
|
||||||
import 'package:fooder/widgets/entry.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/edit_entry.dart';
|
||||||
import 'package:fooder/screens/meal.dart';
|
import 'package:fooder/screens/meal.dart';
|
||||||
import 'package:fooder/client.dart';
|
import 'package:fooder/client.dart';
|
||||||
import 'dart:core';
|
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,
|
{super.key,
|
||||||
required this.meal,
|
required this.meal});
|
||||||
required this.apiClient,
|
|
||||||
required this.refreshParent});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Row(
|
||||||
child: GestureDetector(
|
children: <Widget>[
|
||||||
onLongPress: () {
|
Padding(
|
||||||
Navigator.push(
|
padding: const EdgeInsets.all(8),
|
||||||
context,
|
child: Text(
|
||||||
MaterialPageRoute(
|
meal.name,
|
||||||
builder: (context) => MealScreen(
|
style: Theme.of(context).textTheme.headlineSmall!.copyWith(
|
||||||
apiClient: apiClient,
|
color: Theme.of(context).colorScheme.onPrimary,
|
||||||
meal: meal,
|
fontWeight: FontWeight.bold,
|
||||||
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(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => EditEntryScreen(
|
|
||||||
apiClient: apiClient,
|
|
||||||
entry: entry,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
).then((_) {
|
|
||||||
refreshParent();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
));
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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(
|
||||||
|
apiClient: apiClient,
|
||||||
|
meal: meal,
|
||||||
|
refresh: refreshParent,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).then((_) => refreshParent());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _editEntry(context, entry) async {
|
||||||
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => EditEntryScreen(
|
||||||
|
apiClient: apiClient,
|
||||||
|
entry: entry,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).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"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.11.0"
|
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:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -41,6 +49,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.18.0"
|
version: "1.18.0"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.3"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -168,6 +184,14 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
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:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -495,4 +519,4 @@ packages:
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.3.0 <4.0.0"
|
dart: ">=3.3.0 <4.0.0"
|
||||||
flutter: ">=3.16.0"
|
flutter: ">=3.19.2"
|
||||||
|
|
|
@ -39,7 +39,9 @@ dependencies:
|
||||||
intl: ^0.19.0
|
intl: ^0.19.0
|
||||||
flutter_secure_storage: ^9.0.0
|
flutter_secure_storage: ^9.0.0
|
||||||
simple_barcode_scanner: ^0.1.1
|
simple_barcode_scanner: ^0.1.1
|
||||||
|
google_fonts: ^6.2.1
|
||||||
flex_color_scheme: ^7.3.1
|
flex_color_scheme: ^7.3.1
|
||||||
|
blur: ^3.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in a new issue