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