[UI craze] main screen implemented?

This commit is contained in:
Piotr Domański 2024-03-29 16:47:25 +01:00
parent 90fad4a0ac
commit 66b88f64fe
17 changed files with 815 additions and 142 deletions

View file

@ -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,
)
);
}

View file

@ -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(

View file

@ -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);
}
});
},
),
),

View 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,
),
),
],
)
),
),
),
);
}
}

View file

@ -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(
clipper: BackgroundWaveClipper(),
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],
)),
)),
gradient: LinearGradient(
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,

View file

@ -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),

View file

@ -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),
),
);
}

View file

@ -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',

View file

@ -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),
);
}
}

View file

@ -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',

View file

@ -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,
),
],
),

View file

@ -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
View 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,
),
);
}
}

View file

@ -1,93 +1,152 @@
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(
context,
MaterialPageRoute(
builder: (context) => MealScreen(
apiClient: apiClient,
meal: meal,
refresh: refreshParent,
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,
),
),
).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
View 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,
),
],
),
),
),
),
),
),
);
}
}

View file

@ -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"

View file

@ -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: