[new look] first draft finished

This commit is contained in:
Piotr Domański 2024-04-04 19:03:41 +02:00
parent ac803d3e71
commit c716de3e02
32 changed files with 856 additions and 987 deletions

View file

@ -1,45 +1,13 @@
FROM debian:stable-slim AS build-env # BUILD
FROM node:16.14-alpine
# install all needed stuff COPY ./build/web /app
RUN apt-get update
RUN apt-get install -y curl git unzip
# define variables WORKDIR /app/
ARG FLUTTER_SDK=/usr/local/flutter
ARG FLUTTER_VERSION=3.10.5
ARG APP=/app/
#clone flutter RUN apk --no-cache add curl
RUN git clone https://github.com/flutter/flutter.git $FLUTTER_SDK RUN npm install --global http-server
# change dir to current flutter folder and make a checkout to the specific version
# setup the flutter path as an enviromental variable
ENV PATH="$FLUTTER_SDK/bin:$FLUTTER_SDK/bin/cache/dart-sdk/bin:${PATH}"
# Start to run Flutter commands
# doctor to see if all was installes ok
RUN flutter doctor -v
# create folder to copy source code
RUN mkdir $APP
# copy source code to folder
COPY . $APP
# stup new folder as the working directory
WORKDIR $APP
# Run build: 1 - clean, 2 - pub get, 3 - build web
RUN flutter clean
RUN flutter pub get
RUN flutter build web
# once heare the app will be compiled and ready to deploy
# use nginx to deploy
FROM nginx:1.25.2-alpine
# copy the info of the builded web app to nginx
COPY --from=build-env /app/build/web /usr/share/nginx/html
# Expose and run nginx
EXPOSE 80 EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
CMD ["npx", "http-server", "-p", "80"]

View file

@ -9,6 +9,7 @@ endif
.PHONY: build .PHONY: build
build: build:
flutter build web
$(DOCKER_BUILD) -t registry.domandoman.xyz/fooder/app -f Dockerfile . $(DOCKER_BUILD) -t registry.domandoman.xyz/fooder/app -f Dockerfile .
.PHONY: push .PHONY: push

View file

@ -11,6 +11,8 @@ class FAppBar extends StatelessWidget implements PreferredSizeWidget {
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
child: AppBar( child: AppBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
elevation: 0, elevation: 0,
actions: actions, actions: actions,
)); ));

View file

@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:blur/blur.dart';
class BlurContainer extends StatelessWidget {
final Widget? child;
final double? height;
final double? width;
const BlurContainer({super.key, this.height, this.width, this.child});
@override
Widget build(BuildContext context) {
var theme = Theme.of(context);
var colorScheme = theme.colorScheme;
var blured = Blur(
blur: 10,
blurColor: colorScheme.surface.withOpacity(0.1),
child: Container(
height: height,
width: width,
color: colorScheme.surface.withOpacity(0.1),
),
);
if (child == null) {
return blured;
}
return Stack(
children: [
blured,
child!,
],
);
}
}

View file

@ -27,20 +27,8 @@ class FButton extends StatelessWidget {
child: Container( child: Container(
padding: EdgeInsets.symmetric(vertical: insidePadding), padding: EdgeInsets.symmetric(vertical: insidePadding),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
colorScheme.primary.withOpacity(0.5),
colorScheme.secondary.withOpacity(0.5),
],
),
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
boxShadow: [ color: colorScheme.surfaceTint.withOpacity(0.85),
BoxShadow(
color: colorScheme.primary.withOpacity(0.3),
blurRadius: 5,
offset: const Offset(0, 5),
)
],
), ),
child: Center( child: Center(
child: Text( child: Text(
@ -48,6 +36,7 @@ class FButton extends StatelessWidget {
style: theme.textTheme.labelLarge!.copyWith( style: theme.textTheme.labelLarge!.copyWith(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: fontSize, fontSize: fontSize,
color: colorScheme.onPrimary,
), ),
), ),
), ),

View file

@ -40,7 +40,7 @@ class FDateItemWidget extends StatelessWidget {
width: 2, width: 2,
), ),
color: picked color: picked
? colorScheme.onPrimary.withOpacity(0.25) ? colorScheme.onSurfaceVariant.withOpacity(0.25)
: Colors.transparent, : Colors.transparent,
), ),
child: Column( child: Column(
@ -49,7 +49,7 @@ class FDateItemWidget extends StatelessWidget {
Text( Text(
dayOfTheWeekMap[date.weekday]!, dayOfTheWeekMap[date.weekday]!,
style: TextStyle( style: TextStyle(
color: colorScheme.onPrimary, color: colorScheme.onSurfaceVariant,
fontSize: picked ? 24 : 12, fontSize: picked ? 24 : 12,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@ -57,7 +57,7 @@ class FDateItemWidget extends StatelessWidget {
Text( Text(
'${date.day}.${date.month}', '${date.day}.${date.month}',
style: TextStyle( style: TextStyle(
color: colorScheme.onPrimary, color: colorScheme.onSurfaceVariant,
fontSize: picked ? 24 : 12, fontSize: picked ? 24 : 12,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@ -128,7 +128,7 @@ class _FDatePickerWidgetState extends State<FDatePickerWidget> {
child: IconButton( child: IconButton(
icon: Icon( icon: Icon(
Icons.calendar_month, Icons.calendar_month,
color: colorScheme.onPrimary, color: colorScheme.onSurfaceVariant,
size: 20, size: 20,
), ),
onPressed: () { onPressed: () {

View file

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
class FDropdown<T> extends StatelessWidget {
final String labelText;
final List<DropdownMenuItem<T>> items;
final Function(T?) onChanged;
final T? value;
final double padding;
const FDropdown(
{super.key,
required this.labelText,
this.padding = 8,
required this.items,
required this.onChanged,
this.value});
@override
Widget build(BuildContext context) {
var theme = Theme.of(context);
var colorScheme = theme.colorScheme;
return Padding(
padding: EdgeInsets.symmetric(vertical: padding, horizontal: padding),
child: DropdownButtonFormField<T>(
onChanged: onChanged,
items: items,
value: value,
decoration: InputDecoration(
labelText: labelText,
floatingLabelStyle:
theme.textTheme.bodyLarge!.copyWith(color: colorScheme.onSurface),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: colorScheme.primary),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: colorScheme.onPrimary),
),
),
),
);
}
}

View file

@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:fooder/components/blur_container.dart';
class FActionButton extends StatelessWidget {
final IconData icon;
final Function() onPressed;
final String tag;
const FActionButton(
{super.key,
required this.icon,
required this.onPressed,
this.tag = 'fap'});
@override
Widget build(BuildContext context) {
var theme = Theme.of(context);
var colorScheme = theme.colorScheme;
return Container(
height: 64,
width: 64,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(32),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(32),
child: BlurContainer(
height: 64,
width: 64,
child: SizedBox(
height: 64,
width: 64,
child: FloatingActionButton(
elevation: 0,
onPressed: onPressed,
heroTag: tag,
backgroundColor: Colors.transparent,
child: Icon(
icon,
color: colorScheme.onSurface,
),
),
),
),
),
);
}
}

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:blur/blur.dart'; import 'package:flutter/widgets.dart';
import 'package:fooder/components/blur_container.dart';
class FNavBar extends StatelessWidget { class FNavBar extends StatelessWidget {
static const maxWidth = 920.0; static const maxWidth = 920.0;
@ -7,61 +8,25 @@ class FNavBar extends StatelessWidget {
final List<Widget> children; final List<Widget> children;
final double height; final double height;
const FNavBar({super.key, required this.children, this.height = 56}); const FNavBar({super.key, required this.children, this.height = 78});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context);
var colorScheme = theme.colorScheme;
var widthAvail = MediaQuery.of(context).size.width; var widthAvail = MediaQuery.of(context).size.width;
// var width = widthAvail > maxWidth ? maxWidth : widthAvail; // var width = widthAvail > maxWidth ? maxWidth : widthAvail;
return SizedBox(
return SafeArea( width: widthAvail,
child: Padding( height: height * children.length,
padding: const EdgeInsets.all(12), child: BlurContainer(
child: Container( width: widthAvail,
decoration: BoxDecoration( height: height * children.length,
borderRadius: BorderRadius.circular(24), child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
boxShadow: [ ...children,
BoxShadow( Container(
color: colorScheme.primary.withOpacity(0.3), height: height / 3,
blurRadius: 5, color: Colors.transparent,
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: widthAvail,
height: height * children.length,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
colorScheme.primary.withOpacity(0.1),
colorScheme.secondary.withOpacity(0.1),
],
),
),
),
),
SizedBox(
width: widthAvail,
height: height * children.length,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: children,
),
),
],
)),
),
), ),
); );
} }

View file

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:blur/blur.dart'; import 'package:fooder/components/blur_container.dart';
class ClipShadowPath extends StatelessWidget { class ClipShadowPath extends StatelessWidget {
final Shadow shadow; final Shadow shadow;
@ -56,28 +56,11 @@ class BackgroundWave extends StatelessWidget {
return SizedBox( return SizedBox(
height: height, height: height,
child: ClipShadowPath( child: ClipPath(
clipper: BackgroundWaveClipper(), clipper: BackgroundWaveClipper(),
shadow: BoxShadow( child: BlurContainer(
blurRadius: 5, width: MediaQuery.of(context).size.width,
color: colorScheme.primary.withOpacity(0.3), height: height,
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.withOpacity(0.1),
colorScheme.secondary.withOpacity(0.1),
],
),
),
),
), ),
), ),
); );
@ -131,8 +114,9 @@ class BackgroundWaveClipper extends CustomClipper<Path> {
} }
class FSliverAppBar extends SliverPersistentHeaderDelegate { class FSliverAppBar extends SliverPersistentHeaderDelegate {
final double preferredHeight;
final Widget child; final Widget child;
const FSliverAppBar({required this.child}); const FSliverAppBar({required this.child, this.preferredHeight = 220});
@override @override
Widget build( Widget build(
@ -147,8 +131,11 @@ class FSliverAppBar extends SliverPersistentHeaderDelegate {
return Stack( return Stack(
children: [ children: [
const BackgroundWave( // const BackgroundWave(
height: 280, // height: preferredHeight,
// ),
BlurContainer(
height: preferredHeight,
), ),
Positioned( Positioned(
top: offset, top: offset,
@ -161,10 +148,10 @@ class FSliverAppBar extends SliverPersistentHeaderDelegate {
} }
@override @override
double get maxExtent => 280; double get maxExtent => preferredHeight;
@override @override
double get minExtent => 140; double get minExtent => preferredHeight / 2;
@override @override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) => bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class FTextInput extends StatelessWidget { class FTextInput extends StatelessWidget {
final String labelText; final String labelText;
@ -8,6 +9,9 @@ class FTextInput extends StatelessWidget {
final bool autofocus; final bool autofocus;
final bool obscureText; final bool obscureText;
final Function(String)? onFieldSubmitted; final Function(String)? onFieldSubmitted;
final TextInputType? keyboardType;
final List<TextInputFormatter>? inputFormatters;
final Function(String)? onChanged;
const FTextInput( const FTextInput(
{super.key, {super.key,
@ -17,7 +21,10 @@ class FTextInput extends StatelessWidget {
this.autofillHints, this.autofillHints,
this.autofocus = false, this.autofocus = false,
this.onFieldSubmitted, this.onFieldSubmitted,
this.obscureText = false}); this.obscureText = false,
this.keyboardType,
this.inputFormatters,
this.onChanged});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -30,6 +37,8 @@ class FTextInput extends StatelessWidget {
obscureText: obscureText, obscureText: obscureText,
decoration: InputDecoration( decoration: InputDecoration(
labelText: labelText, labelText: labelText,
floatingLabelStyle:
theme.textTheme.bodyLarge!.copyWith(color: colorScheme.onSurface),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: colorScheme.primary), borderSide: BorderSide(color: colorScheme.primary),
), ),
@ -41,6 +50,9 @@ class FTextInput extends StatelessWidget {
autofillHints: autofillHints, autofillHints: autofillHints,
autofocus: autofocus, autofocus: autofocus,
onFieldSubmitted: onFieldSubmitted, onFieldSubmitted: onFieldSubmitted,
keyboardType: keyboardType,
inputFormatters: inputFormatters,
onChanged: onChanged,
), ),
); );
} }

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fooder/screens/login.dart'; import 'package:fooder/screens/login.dart';
import 'package:fooder/client.dart'; import 'package:fooder/client.dart';
import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:fooder/theme.dart';
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
const MyApp({super.key}); const MyApp({super.key});
@ -10,8 +10,8 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
title: 'FOODER', title: 'FOODER',
theme: FlexThemeData.light(scheme: FlexScheme.brandBlue), theme: MainTheme.light(),
darkTheme: FlexThemeData.dark(scheme: FlexScheme.brandBlue), darkTheme: MainTheme.dark(),
themeMode: ThemeMode.system, themeMode: ThemeMode.system,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
home: LoginScreen( home: LoginScreen(

View file

@ -5,6 +5,9 @@ import 'package:fooder/models/product.dart';
import 'package:fooder/models/diary.dart'; import 'package:fooder/models/diary.dart';
import 'package:fooder/models/meal.dart'; import 'package:fooder/models/meal.dart';
import 'package:fooder/widgets/product.dart'; import 'package:fooder/widgets/product.dart';
import 'package:fooder/components/text.dart';
import 'package:fooder/components/dropdown.dart';
import 'package:fooder/components/floating_action_button.dart';
import 'package:fooder/screens/add_product.dart'; import 'package:fooder/screens/add_product.dart';
import 'package:simple_barcode_scanner/simple_barcode_scanner.dart'; import 'package:simple_barcode_scanner/simple_barcode_scanner.dart';
@ -132,7 +135,8 @@ class _AddEntryScreen extends BasedState<AddEntryScreen> {
constraints: const BoxConstraints(maxWidth: 720), constraints: const BoxConstraints(maxWidth: 720),
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: ListView(children: <Widget>[ child: ListView(children: <Widget>[
DropdownButton<Meal>( FDropdown<Meal>(
labelText: 'Meal',
value: meal, value: meal,
// Callback that sets the selected popup menu item. // Callback that sets the selected popup menu item.
onChanged: (Meal? meal) { onChanged: (Meal? meal) {
@ -151,19 +155,15 @@ class _AddEntryScreen extends BasedState<AddEntryScreen> {
), ),
], ],
), ),
TextFormField( FTextInput(
decoration: const InputDecoration( labelText: 'Product name',
labelText: 'Product name',
),
controller: productNameController, controller: productNameController,
onChanged: (_) => _getProducts(), onChanged: (_) => _getProducts(),
onFieldSubmitted: (_) => _addEntry(), onFieldSubmitted: (_) => _addEntry(),
autofocus: true, autofocus: true,
), ),
TextFormField( FTextInput(
decoration: const InputDecoration( labelText: 'Grams',
labelText: 'Grams',
),
keyboardType: keyboardType:
const TextInputType.numberWithOptions(decimal: true), const TextInputType.numberWithOptions(decimal: true),
inputFormatters: <TextInputFormatter>[ inputFormatters: <TextInputFormatter>[
@ -212,14 +212,15 @@ class _AddEntryScreen extends BasedState<AddEntryScreen> {
floatingActionButton: Row( floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[ children: <Widget>[
FloatingActionButton( FActionButton(
onPressed: _findProductByBarCode, onPressed: _findProductByBarCode,
heroTag: null, icon: Icons.photo_camera,
child: const Icon(Icons.photo_camera),
), ),
FloatingActionButton( const SizedBox(width: 10),
FActionButton(
onPressed: _addEntry, onPressed: _addEntry,
child: const Icon(Icons.add), icon: Icons.library_add,
tag: "fap2",
), ),
], ],
), ),

View file

@ -3,6 +3,8 @@ import 'package:fooder/screens/based.dart';
import 'package:fooder/models/diary.dart'; import 'package:fooder/models/diary.dart';
import 'package:fooder/models/preset.dart'; import 'package:fooder/models/preset.dart';
import 'package:fooder/widgets/preset.dart'; import 'package:fooder/widgets/preset.dart';
import 'package:fooder/components/text.dart';
import 'package:fooder/components/floating_action_button.dart';
class AddMealScreen extends BasedScreen { class AddMealScreen extends BasedScreen {
final Diary diary; final Diary diary;
@ -14,7 +16,7 @@ class AddMealScreen extends BasedScreen {
State<AddMealScreen> createState() => _AddMealScreen(); State<AddMealScreen> createState() => _AddMealScreen();
} }
class _AddMealScreen extends State<AddMealScreen> { class _AddMealScreen extends BasedState<AddMealScreen> {
final nameController = TextEditingController(); final nameController = TextEditingController();
final presetNameController = TextEditingController(); final presetNameController = TextEditingController();
bool nameChanged = false; bool nameChanged = false;
@ -37,7 +39,7 @@ class _AddMealScreen extends State<AddMealScreen> {
void initState() { void initState() {
super.initState(); super.initState();
setState(() { setState(() {
nameController.text = "Meal ${widget.diary.meals.length}"; nameController.text = "Meal ${widget.diary.meals.length + 1}";
}); });
_getPresets(); _getPresets();
} }
@ -54,15 +56,6 @@ class _AddMealScreen extends State<AddMealScreen> {
); );
} }
void showError(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message, textAlign: TextAlign.center),
backgroundColor: Theme.of(context).colorScheme.error,
),
);
}
Future<void> _addMeal() async { Future<void> _addMeal() async {
await widget.apiClient.addMeal( await widget.apiClient.addMeal(
name: nameController.text, name: nameController.text,
@ -121,28 +114,21 @@ class _AddMealScreen extends State<AddMealScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: appBar(),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text("🅵🅾🅾🅳🅴🆁", style: logoStyle(context)),
),
body: Center( body: Center(
child: Container( child: Container(
constraints: const BoxConstraints(maxWidth: 720), constraints: const BoxConstraints(maxWidth: 720),
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: ListView(children: <Widget>[ child: ListView(children: <Widget>[
TextFormField( FTextInput(
decoration: const InputDecoration( labelText: 'Meal name',
labelText: 'Meal name',
),
controller: nameController, controller: nameController,
onChanged: (_) => setState(() { onChanged: (_) => setState(() {
nameChanged = true; nameChanged = true;
}), }),
), ),
TextFormField( FTextInput(
decoration: const InputDecoration( labelText: 'Search presets',
hintText: 'Search presets',
),
controller: presetNameController, controller: presetNameController,
onChanged: (_) => _getPresets(), onChanged: (_) => _getPresets(),
), ),
@ -166,9 +152,9 @@ class _AddMealScreen extends State<AddMealScreen> {
]), ]),
), ),
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FActionButton(
onPressed: _addMealFromPreset, onPressed: _addMealFromPreset,
child: const Icon(Icons.add), icon: Icons.playlist_add_rounded,
), ),
); );
} }

View file

@ -2,6 +2,9 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:fooder/screens/based.dart'; import 'package:fooder/screens/based.dart';
import 'package:fooder/models/product.dart'; import 'package:fooder/models/product.dart';
import 'package:fooder/widgets/product.dart';
import 'package:fooder/components/text.dart';
import 'package:fooder/components/floating_action_button.dart';
class AddProductScreen extends BasedScreen { class AddProductScreen extends BasedScreen {
const AddProductScreen({super.key, required super.apiClient}); const AddProductScreen({super.key, required super.apiClient});
@ -10,7 +13,7 @@ class AddProductScreen extends BasedScreen {
State<AddProductScreen> createState() => _AddProductScreen(); State<AddProductScreen> createState() => _AddProductScreen();
} }
class _AddProductScreen extends State<AddProductScreen> { class _AddProductScreen extends BasedState<AddProductScreen> {
final nameController = TextEditingController(); final nameController = TextEditingController();
final carbController = TextEditingController(); final carbController = TextEditingController();
final fatController = TextEditingController(); final fatController = TextEditingController();
@ -34,15 +37,6 @@ class _AddProductScreen extends State<AddProductScreen> {
); );
} }
void showError(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message, textAlign: TextAlign.center),
backgroundColor: Theme.of(context).colorScheme.error,
),
);
}
Future<double?> _parseDouble(String text, String name, Future<double?> _parseDouble(String text, String name,
{bool silent = false}) async { {bool silent = false}) async {
try { try {
@ -113,25 +107,21 @@ class _AddProductScreen extends State<AddProductScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: appBar(),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text("🅵🅾🅾🅳🅴🆁", style: logoStyle(context)),
),
body: Center( body: Center(
child: Container( child: Container(
constraints: const BoxConstraints(maxWidth: 720), constraints: const BoxConstraints(maxWidth: 720),
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: Column(children: <Widget>[ child: Column(children: <Widget>[
TextFormField( FTextInput(
decoration: const InputDecoration( labelText: 'Product name',
labelText: 'Product name',
),
controller: nameController, controller: nameController,
onChanged: (String value) {
setState(() {});
},
), ),
TextFormField( FTextInput(
decoration: const InputDecoration( labelText: 'Carbs',
labelText: 'Carbs',
),
keyboardType: keyboardType:
const TextInputType.numberWithOptions(decimal: true), const TextInputType.numberWithOptions(decimal: true),
inputFormatters: <TextInputFormatter>[ inputFormatters: <TextInputFormatter>[
@ -143,10 +133,8 @@ class _AddProductScreen extends State<AddProductScreen> {
setState(() {}); setState(() {});
}, },
), ),
TextFormField( FTextInput(
decoration: const InputDecoration( labelText: 'Fat',
labelText: 'Fat',
),
keyboardType: keyboardType:
const TextInputType.numberWithOptions(decimal: true), const TextInputType.numberWithOptions(decimal: true),
inputFormatters: <TextInputFormatter>[ inputFormatters: <TextInputFormatter>[
@ -158,10 +146,8 @@ class _AddProductScreen extends State<AddProductScreen> {
setState(() {}); setState(() {});
}, },
), ),
TextFormField( FTextInput(
decoration: const InputDecoration( labelText: 'Protein',
labelText: 'Protein',
),
keyboardType: keyboardType:
const TextInputType.numberWithOptions(decimal: true), const TextInputType.numberWithOptions(decimal: true),
inputFormatters: <TextInputFormatter>[ inputFormatters: <TextInputFormatter>[
@ -173,10 +159,8 @@ class _AddProductScreen extends State<AddProductScreen> {
setState(() {}); setState(() {});
}, },
), ),
TextFormField( FTextInput(
decoration: const InputDecoration( labelText: 'Fiber',
labelText: 'Fiber',
),
keyboardType: keyboardType:
const TextInputType.numberWithOptions(decimal: true), const TextInputType.numberWithOptions(decimal: true),
inputFormatters: <TextInputFormatter>[ inputFormatters: <TextInputFormatter>[
@ -188,15 +172,30 @@ class _AddProductScreen extends State<AddProductScreen> {
setState(() {}); setState(() {});
}, },
), ),
Text( ProductWidget(
"${calculateCalories().toStringAsFixed(2)} kcal", product: Product(
textAlign: TextAlign.right, id: 0,
), name: nameController.text,
carb: double.tryParse(
carbController.text.replaceAll(",", ".")) ??
0.0,
fat: double.tryParse(
fatController.text.replaceAll(",", ".")) ??
0.0,
protein: double.tryParse(
proteinController.text.replaceAll(",", ".")) ??
0.0,
fiber: double.tryParse(
fiberController.text.replaceAll(",", ".")) ??
0.0,
calories: calculateCalories(),
),
)
])), ])),
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FActionButton(
onPressed: _addProduct, onPressed: _addProduct,
child: const Icon(Icons.add), icon: Icons.save,
), ),
); );
} }

View file

@ -43,7 +43,7 @@ abstract class BasedState<T extends BasedScreen> extends State<T> {
IconButton( IconButton(
icon: Icon( icon: Icon(
Icons.logout, Icons.logout,
color: Theme.of(context).colorScheme.onPrimary, color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
onPressed: _logout, onPressed: _logout,
), ),
@ -60,28 +60,28 @@ abstract class BasedState<T extends BasedScreen> extends State<T> {
IconButton( IconButton(
icon: Icon( icon: Icon(
Icons.menu_book, Icons.menu_book,
color: Theme.of(context).colorScheme.onPrimary, color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
onPressed: backToDiary, onPressed: backToDiary,
), ),
IconButton( IconButton(
icon: Icon( icon: Icon(
Icons.dinner_dining, Icons.dinner_dining,
color: Theme.of(context).colorScheme.onPrimary, color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
onPressed: () {}, onPressed: () {},
), ),
IconButton( IconButton(
icon: Icon( icon: Icon(
Icons.lunch_dining, Icons.lunch_dining,
color: Theme.of(context).colorScheme.onPrimary, color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
onPressed: () {}, onPressed: () {},
), ),
IconButton( IconButton(
icon: Icon( icon: Icon(
Icons.person, Icons.person,
color: Theme.of(context).colorScheme.onPrimary, color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
onPressed: () {}, onPressed: () {},
), ),

View file

@ -5,6 +5,8 @@ import 'package:fooder/models/product.dart';
import 'package:fooder/models/entry.dart'; import 'package:fooder/models/entry.dart';
import 'package:fooder/widgets/product.dart'; import 'package:fooder/widgets/product.dart';
import 'package:fooder/screens/add_product.dart'; import 'package:fooder/screens/add_product.dart';
import 'package:fooder/components/text.dart';
import 'package:fooder/components/floating_action_button.dart';
import 'package:simple_barcode_scanner/simple_barcode_scanner.dart'; import 'package:simple_barcode_scanner/simple_barcode_scanner.dart';
class EditEntryScreen extends BasedScreen { class EditEntryScreen extends BasedScreen {
@ -17,7 +19,7 @@ class EditEntryScreen extends BasedScreen {
State<EditEntryScreen> createState() => _EditEntryScreen(); State<EditEntryScreen> createState() => _EditEntryScreen();
} }
class _EditEntryScreen extends State<EditEntryScreen> { class _EditEntryScreen extends BasedState<EditEntryScreen> {
final gramsController = TextEditingController(); final gramsController = TextEditingController();
final productNameController = TextEditingController(); final productNameController = TextEditingController();
List<Product> products = []; List<Product> products = [];
@ -53,15 +55,6 @@ class _EditEntryScreen extends State<EditEntryScreen> {
}); });
} }
void showError(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message, textAlign: TextAlign.center),
backgroundColor: Theme.of(context).colorScheme.error,
),
);
}
Future<double?> _parseDouble(String text, String name) async { Future<double?> _parseDouble(String text, String name) async {
try { try {
return double.parse(text.replaceAll(",", ".")); return double.parse(text.replaceAll(",", "."));
@ -123,27 +116,20 @@ class _EditEntryScreen extends State<EditEntryScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: appBar(),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text("🅵🅾🅾🅳🅴🆁", style: logoStyle(context)),
),
body: Center( body: Center(
child: Container( child: Container(
constraints: const BoxConstraints(maxWidth: 720), constraints: const BoxConstraints(maxWidth: 720),
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: ListView(children: <Widget>[ child: ListView(children: <Widget>[
TextFormField( FTextInput(
decoration: const InputDecoration( labelText: 'Product name',
labelText: 'Product name',
),
controller: productNameController, controller: productNameController,
onChanged: (_) => _getProducts(), onChanged: (_) => _getProducts(),
autofocus: true, autofocus: true,
), ),
TextFormField( FTextInput(
decoration: const InputDecoration( labelText: 'Grams',
labelText: 'Grams',
),
keyboardType: keyboardType:
const TextInputType.numberWithOptions(decimal: true), const TextInputType.numberWithOptions(decimal: true),
inputFormatters: <TextInputFormatter>[ inputFormatters: <TextInputFormatter>[
@ -190,20 +176,21 @@ class _EditEntryScreen extends State<EditEntryScreen> {
floatingActionButton: Row( floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[ children: <Widget>[
FloatingActionButton( FActionButton(
onPressed: _findProductByBarCode, onPressed: _findProductByBarCode,
heroTag: null, icon: Icons.photo_camera,
child: const Icon(Icons.photo_camera),
), ),
FloatingActionButton( const SizedBox(width: 10),
FActionButton(
onPressed: _deleteEntry, onPressed: _deleteEntry,
heroTag: null, tag: "fap1",
child: const Icon(Icons.delete), icon: Icons.delete,
), ),
FloatingActionButton( const SizedBox(width: 10),
FActionButton(
onPressed: _saveEntry, onPressed: _saveEntry,
heroTag: null, tag: "fap2",
child: const Icon(Icons.save), icon: Icons.save,
), ),
], ],
), ),

View file

@ -1,12 +1,13 @@
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/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/summary.dart'; import 'package:fooder/widgets/summary.dart';
import 'package:fooder/widgets/meal.dart'; import 'package:fooder/widgets/meal.dart';
import 'package:fooder/components/sliver.dart'; import 'package:fooder/components/sliver.dart';
import 'package:fooder/components/date_picker.dart'; import 'package:fooder/components/date_picker.dart';
import 'package:blur/blur.dart'; import 'package:fooder/components/floating_action_button.dart';
class MainScreen extends BasedScreen { class MainScreen extends BasedScreen {
const MainScreen({super.key, required super.apiClient}); const MainScreen({super.key, required super.apiClient});
@ -43,6 +44,10 @@ class _MainScreen extends BasedState<MainScreen> {
} }
Future<void> _addEntry() async { Future<void> _addEntry() async {
if (diary == null) {
return;
}
await Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
@ -52,60 +57,20 @@ class _MainScreen extends BasedState<MainScreen> {
).then((_) => _asyncInitState()); ).then((_) => _asyncInitState());
} }
Widget floatingActionButton(BuildContext context) { Future<void> _addMeal(context) async {
var theme = Theme.of(context); if (diary == null) {
var colorScheme = theme.colorScheme; return;
}
return Container( await Navigator.push(
height: 64, context,
width: 64, MaterialPageRoute(
decoration: BoxDecoration( builder: (context) => AddMealScreen(
borderRadius: BorderRadius.circular(32), apiClient: widget.apiClient,
boxShadow: [ diary: diary!,
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),
],
),
),
),
),
SizedBox(
height: 64,
width: 64,
child: FloatingActionButton(
elevation: 0,
onPressed: _addEntry,
backgroundColor: Colors.transparent,
child: Icon(
Icons.library_add,
color: colorScheme.onPrimary,
),
),
),
],
), ),
), ),
); ).then((_) => _asyncInitState());
} }
@override @override
@ -133,7 +98,9 @@ class _MainScreen extends BasedState<MainScreen> {
apiClient: widget.apiClient, apiClient: widget.apiClient,
refreshParent: _asyncInitState, refreshParent: _asyncInitState,
initiallyExpanded: i == 0, initiallyExpanded: i == 0,
showText: showText,
), ),
const SizedBox(height: 200),
], ],
), ),
), ),
@ -149,7 +116,18 @@ class _MainScreen extends BasedState<MainScreen> {
appBar: appBar(), appBar: appBar(),
bottomNavigationBar: navBar(), bottomNavigationBar: navBar(),
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
floatingActionButton: floatingActionButton(context), floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FActionButton(
icon: Icons.playlist_add,
onPressed: () => _addMeal(context),
),
const SizedBox(width: 10),
FActionButton(
icon: Icons.library_add, onPressed: _addEntry, tag: "fap2"),
],
),
); );
} }
} }

View file

@ -1,181 +0,0 @@
import 'package:flutter/material.dart';
import 'package:fooder/screens/based.dart';
import 'package:fooder/models/meal.dart';
import 'package:fooder/widgets/macro.dart';
class MealScreen extends BasedScreen {
final Meal meal;
final Function() refresh;
const MealScreen(
{super.key,
required super.apiClient,
required this.refresh,
required this.meal});
@override
State<MealScreen> createState() => _AddMealScreen();
}
class _AddMealScreen extends State<MealScreen> {
Future<void> saveMeal(context) async {
TextEditingController textFieldController = TextEditingController();
textFieldController.text = widget.meal.name;
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Save Meal'),
content: TextField(
controller: textFieldController,
decoration: const InputDecoration(hintText: "Meal template name"),
),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.cancel),
onPressed: () {
Navigator.pop(context);
},
),
IconButton(
icon: const Icon(Icons.save),
onPressed: () {
widget.apiClient
.saveMeal(widget.meal, textFieldController.text);
Navigator.pop(context);
},
),
],
);
},
);
}
Future<void> _deleteMeal(Meal meal) async {
await widget.apiClient.deleteMeal(meal.id);
}
Future<void> deleteMeal(context) async {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Confirm deletion of the meal'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.cancel),
onPressed: () {
Navigator.pop(context);
},
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
_deleteMeal(widget.meal);
Navigator.pop(context);
Navigator.pop(context);
widget.refresh();
},
),
],
);
},
);
}
Widget buildButton(Icon icon, String text, Function() onPressed) {
return Card(
child: ListTile(
onTap: onPressed,
title: Container(
padding: const EdgeInsets.all(8),
child: Row(
children: <Widget>[
IconButton(
icon: icon,
onPressed: onPressed,
),
const Spacer(),
Text(text),
],
),
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text("🅵🅾🅾🅳🅴🆁", style: logoStyle(context)),
),
body: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 720),
padding: const EdgeInsets.all(10),
child: CustomScrollView(slivers: <Widget>[
SliverAppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
widget.meal.name,
style: Theme.of(context)
.textTheme
.headlineLarge!
.copyWith(
color: Theme.of(context).colorScheme.onSecondary,
fontWeight: FontWeight.bold,
),
),
const Spacer(),
Text(
"${widget.meal.calories.toStringAsFixed(1)} kcal",
style: Theme.of(context)
.textTheme
.headlineLarge!
.copyWith(
color: Theme.of(context).colorScheme.onSecondary,
fontWeight: FontWeight.bold,
),
),
]),
expandedHeight: 150,
backgroundColor: Theme.of(context).colorScheme.secondary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
floating: true,
flexibleSpace: FlexibleSpaceBar(
title: MacroWidget(
protein: widget.meal.protein,
carb: widget.meal.carb,
fat: widget.meal.fat,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSecondary,
fontWeight: FontWeight.bold,
),
),
)),
SliverList(
delegate: SliverChildListDelegate([
buildButton(
const Icon(Icons.save),
"Save as preset",
() => saveMeal(context),
),
buildButton(
const Icon(Icons.delete),
"Delete",
() => deleteMeal(context),
),
]))
]),
),
),
);
}
}

51
lib/theme.dart Normal file
View file

@ -0,0 +1,51 @@
import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class MainTheme {
static ThemeData light() {
return FlexThemeData.light(
scheme: FlexScheme.brandBlue,
surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold,
blendLevel: 40,
subThemesData: const FlexSubThemesData(
blendOnLevel: 40,
useTextTheme: true,
useM2StyleDividerInM3: true,
alignedDropdown: true,
useInputDecoratorThemeInDialogs: true,
),
keyColors: const FlexKeyColors(
useSecondary: true,
useTertiary: true,
),
visualDensity: FlexColorScheme.comfortablePlatformDensity,
useMaterial3: true,
swapLegacyOnMaterial3: true,
fontFamily: GoogleFonts.notoSans().fontFamily,
).copyWith(
dividerColor: Colors.transparent,
);
}
static ThemeData dark() {
return FlexThemeData.dark(
scheme: FlexScheme.brandBlue,
surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold,
blendLevel: 40,
subThemesData: const FlexSubThemesData(
blendOnLevel: 20,
useTextTheme: true,
useM2StyleDividerInM3: true,
alignedDropdown: true,
useInputDecoratorThemeInDialogs: true,
),
visualDensity: FlexColorScheme.comfortablePlatformDensity,
useMaterial3: true,
swapLegacyOnMaterial3: true,
fontFamily: GoogleFonts.notoSans().fontFamily,
).copyWith(
dividerColor: Colors.transparent,
);
}
}

View file

@ -1,97 +0,0 @@
import 'package:flutter/material.dart';
import 'package:fooder/models/diary.dart';
import 'package:fooder/widgets/meal.dart';
import 'package:fooder/widgets/macro.dart';
import 'package:fooder/client.dart';
import 'package:fooder/screens/add_meal.dart';
import 'dart:core';
class DiaryWidget extends StatelessWidget {
final Diary diary;
final ApiClient apiClient;
final Function() refreshParent;
const DiaryWidget(
{super.key,
required this.diary,
required this.apiClient,
required this.refreshParent});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(8),
child: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
const Spacer(),
Text(
"${diary.calories.toStringAsFixed(1)} kcal",
style: Theme.of(context)
.textTheme
.headlineLarge!
.copyWith(
color: Theme.of(context).colorScheme.onSecondary,
fontWeight: FontWeight.bold,
),
),
]),
expandedHeight: 150,
backgroundColor: Theme.of(context).colorScheme.secondary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
floating: true,
flexibleSpace: FlexibleSpaceBar(
title: MacroWidget(
protein: diary.protein,
carb: diary.carb,
fat: diary.fat,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSecondary,
fontWeight: FontWeight.bold,
),
),
)),
SliverToBoxAdapter(
child: Container(
padding: const EdgeInsets.all(8),
child: TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddMealScreen(
apiClient: apiClient,
diary: diary,
),
),
).then((_) {
refreshParent();
});
},
child: const Text("Add Meal"),
),
),
),
SliverList(
delegate: SliverChildListDelegate(
[
for (var (i, meal) in diary.meals.indexed)
MealWidget(
meal: meal,
apiClient: apiClient,
refreshParent: refreshParent,
initiallyExpanded: i == 0,
),
],
),
),
],
),
);
}
}

View file

@ -1,6 +1,6 @@
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/macroEntry.dart'; import 'package:fooder/widgets/macro.dart';
import 'dart:core'; import 'dart:core';
class EntryHeader extends StatelessWidget { class EntryHeader extends StatelessWidget {
@ -12,14 +12,17 @@ class EntryHeader extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Row(
children: <Widget>[ children: <Widget>[
Padding( Expanded(
padding: const EdgeInsets.all(8), child: Padding(
child: Text( padding: const EdgeInsets.all(8),
entry.product.name, child: Text(
style: Theme.of(context).textTheme.bodyLarge!.copyWith( entry.product.name,
color: Theme.of(context).colorScheme.onPrimary, overflow: TextOverflow.fade,
fontWeight: FontWeight.bold, style: Theme.of(context).textTheme.bodyLarge!.copyWith(
), color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.bold,
),
),
), ),
), ),
const Spacer(), const Spacer(),
@ -28,7 +31,7 @@ class EntryHeader extends StatelessWidget {
child: Text( child: Text(
"${entry.grams.toStringAsFixed(0)} g", "${entry.grams.toStringAsFixed(0)} g",
style: Theme.of(context).textTheme.bodyMedium!.copyWith( style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onPrimary, color: Theme.of(context).colorScheme.onSurface,
), ),
), ),
), ),

View file

@ -1,122 +1,163 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:core';
class MacroWidget extends StatelessWidget { class MacroHeaderWidget extends StatelessWidget {
final double? amount; static const double padY = 4;
final double? calories; static const double padX = 8;
final double? fiber;
final double protein;
final double carb;
final double fat;
final TextStyle style;
final Widget? child;
const MacroWidget({ final bool? fiber;
final bool? calories;
final Alignment alignment;
const MacroHeaderWidget({
super.key, super.key,
this.calories, this.fiber = false,
this.amount, this.calories = false,
this.child, this.alignment = Alignment.centerLeft,
this.fiber,
required this.protein,
required this.carb,
required this.fat,
required this.style,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var elements = <Widget>[ var elements = <String>[
Expanded( "C(g)",
flex: 1, "F(g)",
child: Text( "P(g)",
"C: ${carb.toStringAsFixed(1)}g", ];
style: style,
textAlign: TextAlign.center, if (fiber == true) {
elements.add(
"f(g)",
);
}
if (calories == true) {
elements.add(
"kcal",
);
}
var children = <Widget>[];
if (alignment == Alignment.centerRight) {
children.add(const Spacer());
}
for (var element in elements) {
children.add(
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 2,
),
child: SizedBox(
width: 55,
child: Text(
element,
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
color: Theme.of(context).colorScheme.onSurface,
),
textAlign: TextAlign.center,
),
),
), ),
);
}
if (alignment == Alignment.centerLeft) {
children.add(const Spacer());
}
return Padding(
padding: const EdgeInsets.symmetric(
vertical: padY,
horizontal: padX,
), ),
Expanded( child: Row(
flex: 1, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
child: Text( children: children,
"F: ${fat.toStringAsFixed(1)}g",
style: style,
textAlign: TextAlign.center,
),
),
Expanded(
flex: 1,
child: Text(
"P: ${protein.toStringAsFixed(1)}g",
style: style,
textAlign: TextAlign.center,
),
), ),
);
}
}
class MacroEntryWidget extends StatelessWidget {
static const double padY = 4;
static const double padX = 8;
final double protein;
final double carb;
final double fat;
final double? fiber;
final double? calories;
final Alignment alignment;
const MacroEntryWidget({
super.key,
required this.protein,
required this.carb,
required this.fat,
this.fiber,
this.calories,
this.alignment = Alignment.centerLeft,
});
@override
Widget build(BuildContext context) {
var elements = <String>[
(carb.toStringAsFixed(1)),
(fat.toStringAsFixed(1)),
(protein.toStringAsFixed(1)),
]; ];
if (fiber != null) { if (fiber != null) {
elements.add( elements.add(
Expanded( fiber!.toStringAsFixed(1),
flex: 1,
child: Text(
"f: ${fiber!.toStringAsFixed(1)}g",
style: style,
textAlign: TextAlign.center,
),
),
); );
} }
if (calories != null) { if (calories != null) {
elements.add( elements.add(
Expanded( calories!.toStringAsFixed(0),
flex: 1, );
child: Text( }
"${calories!.toStringAsFixed(1)} kcal",
style: style, var children = <Widget>[];
textAlign: TextAlign.center,
if (alignment == Alignment.centerRight) {
children.add(const Spacer());
}
for (var element in elements) {
children.add(
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 2,
),
child: SizedBox(
width: 55,
child: Text(
element,
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
color: Theme.of(context).colorScheme.onSurface,
),
textAlign: TextAlign.center,
),
), ),
), ),
); );
} }
if (amount != null) { if (alignment == Alignment.centerLeft) {
elements.add( children.add(const Spacer());
Expanded(
flex: 1,
child: Text(
"${amount!.toStringAsFixed(1)}g",
style: style,
textAlign: TextAlign.center,
),
),
);
}
if (child != null) {
elements.add(
Expanded(
flex: 1,
child: child!,
),
);
}
if (elements.length < 4) {
elements.add(
const Expanded(
flex: 1,
child: Text(""),
),
);
} }
return Padding( return Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.symmetric(
top: 4.0, vertical: padY,
bottom: 4.0, horizontal: padX,
left: 8.0,
right: 8.0,
), ),
child: Row( child: Row(
children: elements, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: children,
), ),
); );
} }

View file

@ -1,148 +0,0 @@
import 'package:flutter/material.dart';
import 'dart:core';
class MacroHeaderWidget extends StatelessWidget {
static const double PAD_Y = 4;
static const 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: const EdgeInsets.symmetric(
horizontal: 2,
),
child: SizedBox(
width: 55,
child: Text(
element,
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
textAlign: TextAlign.center,
),
),
),
);
}
children.add(const Spacer());
return Padding(
padding: const EdgeInsets.symmetric(
vertical: PAD_Y,
horizontal: PAD_X,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: children,
),
);
}
}
class MacroEntryWidget extends StatelessWidget {
static const double PAD_Y = 4;
static const 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: const EdgeInsets.symmetric(
horizontal: 2,
),
child: SizedBox(
width: 55,
child: Text(
element,
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
textAlign: TextAlign.center,
),
),
),
);
}
children.add(const Spacer());
return Padding(
padding: const EdgeInsets.symmetric(
vertical: PAD_Y,
horizontal: PAD_X,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: children,
),
);
}
}

View file

@ -1,9 +1,8 @@
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/macroEntry.dart'; import 'package:fooder/widgets/macro.dart';
import 'package:fooder/screens/edit_entry.dart'; import 'package:fooder/screens/edit_entry.dart';
import 'package:fooder/screens/meal.dart';
import 'package:fooder/client.dart'; import 'package:fooder/client.dart';
import 'dart:core'; import 'dart:core';
@ -16,14 +15,17 @@ class MealHeader extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Row(
children: <Widget>[ children: <Widget>[
Padding( Expanded(
padding: const EdgeInsets.all(8), child: Padding(
child: Text( padding: const EdgeInsets.all(8),
meal.name, child: Text(
style: Theme.of(context).textTheme.headlineSmall!.copyWith( meal.name,
color: Theme.of(context).colorScheme.onPrimary, overflow: TextOverflow.fade,
fontWeight: FontWeight.bold, style: Theme.of(context).textTheme.headlineSmall!.copyWith(
), color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.bold,
),
),
), ),
), ),
], ],
@ -37,6 +39,7 @@ class MealWidget extends StatelessWidget {
final Meal meal; final Meal meal;
final ApiClient apiClient; final ApiClient apiClient;
final Function() refreshParent; final Function() refreshParent;
final Function(String) showText;
final bool initiallyExpanded; final bool initiallyExpanded;
const MealWidget({ const MealWidget({
@ -45,19 +48,73 @@ class MealWidget extends StatelessWidget {
required this.apiClient, required this.apiClient,
required this.refreshParent, required this.refreshParent,
required this.initiallyExpanded, required this.initiallyExpanded,
required this.showText,
}); });
Future<void> _editMeal(context) async { Future<void> saveMeal(context) async {
await Navigator.push( TextEditingController textFieldController = TextEditingController();
context, textFieldController.text = meal.name;
MaterialPageRoute(
builder: (context) => MealScreen( showDialog(
apiClient: apiClient, context: context,
meal: meal, builder: (context) {
refresh: refreshParent, return AlertDialog(
), title: const Text('Save Meal'),
), content: TextField(
).then((_) => refreshParent()); controller: textFieldController,
decoration: const InputDecoration(hintText: "Meal template name"),
),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.cancel),
onPressed: () {
Navigator.pop(context);
},
),
IconButton(
icon: const Icon(Icons.save),
onPressed: () {
apiClient.saveMeal(meal, textFieldController.text);
Navigator.pop(context);
showText("Meal saved");
},
),
],
);
},
);
}
Future<void> _deleteMeal(Meal meal) async {
await apiClient.deleteMeal(meal.id);
}
Future<void> deleteMeal(context) async {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
title: const Text('Confirm deletion of the meal'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.cancel),
onPressed: () {
Navigator.pop(context);
},
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
_deleteMeal(meal).then((_) => refreshParent());
Navigator.pop(context);
showText("Meal deleted");
},
),
],
);
},
);
} }
Future<void> _editEntry(context, entry) async { Future<void> _editEntry(context, entry) async {
@ -84,61 +141,61 @@ class MealWidget extends StatelessWidget {
child: Padding( child: Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Card( child: Card(
elevation: 4,
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
shadowColor: colorScheme.primary.withOpacity(1.0),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
), ),
child: SizedBox( child: Container(
width: width, constraints: BoxConstraints(maxWidth: width),
child: Container( color: colorScheme.surface.withOpacity(0.2),
decoration: BoxDecoration( child: ExpansionTile(
gradient: LinearGradient( iconColor: colorScheme.onSurface,
colors: [ collapsedIconColor: colorScheme.onSurface,
colorScheme.primary.withOpacity(0.6), initiallyExpanded: initiallyExpanded,
colorScheme.secondary.withOpacity(0.5), enableFeedback: true,
], title: Padding(
), padding: const EdgeInsets.all(8),
), child: Column(
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),
const MacroHeaderWidget(
calories: true,
),
MacroEntryWidget(
protein: meal.protein,
carb: meal.carb,
fat: meal.fat,
calories: meal.calories,
),
],
),
),
children: <Widget>[ children: <Widget>[
for (var (i, entry) in meal.entries.indexed) MealHeader(meal: meal),
ListTile( const MacroHeaderWidget(
title: EntryWidget( calories: true,
entry: entry, ),
), MacroEntryWidget(
tileColor: i % 2 == 0 protein: meal.protein,
? colorScheme.secondary.withOpacity(0.1) carb: meal.carb,
: Colors.transparent, fat: meal.fat,
onTap: () => _editEntry(context, entry), calories: meal.calories,
) ),
], ],
), ),
), ),
children: <Widget>[
for (var (i, entry) in meal.entries.indexed)
ListTile(
title: EntryWidget(
entry: entry,
),
tileColor: i % 2 == 0
? colorScheme.surfaceVariant.withOpacity(0.1)
: Colors.transparent,
onTap: () => _editEntry(context, entry),
enableFeedback: true,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
icon: const Icon(Icons.save),
onPressed: () => saveMeal(context),
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () => deleteMeal(context),
),
],
),
],
), ),
), ),
), ),

View file

@ -3,6 +3,33 @@ import 'package:fooder/models/preset.dart';
import 'package:fooder/widgets/macro.dart'; import 'package:fooder/widgets/macro.dart';
import 'dart:core'; import 'dart:core';
class PresetHeader extends StatelessWidget {
final String title;
const PresetHeader({super.key, required this.title});
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text(
title,
overflow: TextOverflow.fade,
style: Theme.of(context).textTheme.headlineSmall!.copyWith(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.bold,
),
),
),
),
],
);
}
}
class PresetWidget extends StatelessWidget { class PresetWidget extends StatelessWidget {
final Preset preset; final Preset preset;
@ -14,24 +41,21 @@ class PresetWidget extends StatelessWidget {
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
Row( PresetHeader(
children: <Widget>[ title: preset.name,
Expanded(
child: Text(
preset.name,
style: Theme.of(context).textTheme.titleLarge,
),
),
Text("${preset.calories.toStringAsFixed(1)} kcal"),
],
), ),
MacroWidget( const MacroHeaderWidget(
fiber: true,
calories: true,
alignment: Alignment.center,
),
MacroEntryWidget(
protein: preset.protein, protein: preset.protein,
carb: preset.carb, carb: preset.carb,
fat: preset.fat, fat: preset.fat,
style: Theme.of(context).textTheme.bodyMedium!.copyWith( fiber: preset.fiber,
color: Theme.of(context).colorScheme.secondary, calories: preset.calories,
), alignment: Alignment.center,
), ),
], ],
), ),

View file

@ -3,6 +3,33 @@ import 'package:fooder/models/product.dart';
import 'package:fooder/widgets/macro.dart'; import 'package:fooder/widgets/macro.dart';
import 'dart:core'; import 'dart:core';
class ProductHeader extends StatelessWidget {
final String title;
const ProductHeader({super.key, required this.title});
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text(
title,
overflow: TextOverflow.fade,
style: Theme.of(context).textTheme.headlineSmall!.copyWith(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.bold,
),
),
),
),
],
);
}
}
class ProductWidget extends StatelessWidget { class ProductWidget extends StatelessWidget {
final Product product; final Product product;
@ -14,26 +41,21 @@ class ProductWidget extends StatelessWidget {
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
Row( ProductHeader(
children: <Widget>[ title: product.name,
Expanded(
child: Text(
product.name,
style: Theme.of(context).textTheme.titleLarge,
),
),
Text("${product.calories.toStringAsFixed(1)} kcal"),
],
), ),
MacroWidget( const MacroHeaderWidget(
fiber: true,
calories: true,
alignment: Alignment.center,
),
MacroEntryWidget(
protein: product.protein, protein: product.protein,
carb: product.carb, carb: product.carb,
fat: product.fat, fat: product.fat,
fiber: product.fiber, fiber: product.fiber,
style: Theme.of(context).textTheme.bodyMedium!.copyWith( calories: product.calories,
color: Theme.of(context).colorScheme.secondary, alignment: Alignment.center,
fontWeight: FontWeight.bold,
),
), ),
], ],
), ),

View file

@ -1,15 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fooder/models/diary.dart'; import 'package:fooder/models/diary.dart';
import 'package:fooder/widgets/macroEntry.dart'; import 'package:fooder/widgets/macro.dart';
import 'package:fooder/screens/add_meal.dart';
import 'package:fooder/client.dart'; import 'package:fooder/client.dart';
import 'dart:core'; import 'dart:core';
class SummaryHeader extends StatelessWidget { class SummaryHeader extends StatelessWidget {
final Diary diary; final Diary diary;
final Function addMeal;
const SummaryHeader({super.key, required this.addMeal, required this.diary}); const SummaryHeader({super.key, required this.diary});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -20,21 +18,12 @@ class SummaryHeader extends StatelessWidget {
child: Text( child: Text(
"Summary", "Summary",
style: Theme.of(context).textTheme.headlineSmall!.copyWith( style: Theme.of(context).textTheme.headlineSmall!.copyWith(
color: Theme.of(context).colorScheme.onPrimary, color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
), ),
const Spacer(), const Spacer(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: IconButton(
icon: const Icon(Icons.playlist_add_rounded),
iconSize: 32,
color: Theme.of(context).colorScheme.onPrimary,
onPressed: () => addMeal(context),
),
),
], ],
); );
} }
@ -53,18 +42,6 @@ class SummaryWidget extends StatelessWidget {
required this.apiClient, required this.apiClient,
required this.refreshParent}); required this.refreshParent});
Future<void> _addMeal(context) async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddMealScreen(
apiClient: apiClient,
diary: diary,
),
),
).then((_) => refreshParent());
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); var theme = Theme.of(context);
@ -77,40 +54,28 @@ class SummaryWidget extends StatelessWidget {
child: Padding( child: Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Card( child: Card(
elevation: 4,
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
shadowColor: colorScheme.primary.withOpacity(1.0),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
), ),
child: SizedBox( child: Container(
width: width, constraints: BoxConstraints(maxWidth: width),
child: Container( color: colorScheme.surface.withOpacity(0.2),
decoration: BoxDecoration( child: Padding(
gradient: LinearGradient( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24),
colors: [ child: Column(
colorScheme.primary.withOpacity(0.6), children: <Widget>[
colorScheme.secondary.withOpacity(0.5), SummaryHeader(diary: diary),
], const MacroHeaderWidget(
), calories: true,
), ),
child: Padding( MacroEntryWidget(
padding: protein: diary.protein,
const EdgeInsets.symmetric(vertical: 12, horizontal: 24), carb: diary.carb,
child: Column( fat: diary.fat,
children: <Widget>[ calories: diary.calories,
SummaryHeader(diary: diary, addMeal: _addMeal), ),
const MacroHeaderWidget( ],
calories: true,
),
MacroEntryWidget(
protein: diary.protein,
carb: diary.carb,
fat: diary.fat,
calories: diary.calories,
),
],
),
), ),
), ),
), ),

29
macos/Podfile.lock Normal file
View file

@ -0,0 +1,29 @@
PODS:
- flutter_secure_storage_macos (6.1.1):
- FlutterMacOS
- FlutterMacOS (1.0.0)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
EXTERNAL SOURCES:
flutter_secure_storage_macos:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos
FlutterMacOS:
:path: Flutter/ephemeral
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
SPEC CHECKSUMS:
flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
COCOAPODS: 1.15.2

View file

@ -27,6 +27,8 @@
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
47A87A2D935C9FD21BC18BA6 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 17BEB3BB725731AD7E5B80B1 /* Pods_Runner.framework */; };
DC768C2964C710CDD24D0DB4 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 465D02A290608CA511C1F4CA /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -60,11 +62,14 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
0834B9429B6D5467EDC30594 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
17BEB3BB725731AD7E5B80B1 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
19DEDDAC7F8C48D43A9E76CF /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* fooder_web.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "fooder_web.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10ED2044A3C60003C045 /* fooder_web.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = fooder_web.app; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
@ -76,8 +81,13 @@
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
465D02A290608CA511C1F4CA /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
A4E1FEE05E55FE57D150055B /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
A7C614E4C8DC6AE860A60034 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
D32BEBE6FA5B351434300418 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
F971A3061D014BCFE5A477A9 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -85,6 +95,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
DC768C2964C710CDD24D0DB4 /* Pods_RunnerTests.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -92,12 +103,27 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
47A87A2D935C9FD21BC18BA6 /* Pods_Runner.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
076AC79CB527EDDF03C6C1BF /* Pods */ = {
isa = PBXGroup;
children = (
0834B9429B6D5467EDC30594 /* Pods-Runner.debug.xcconfig */,
A7C614E4C8DC6AE860A60034 /* Pods-Runner.release.xcconfig */,
A4E1FEE05E55FE57D150055B /* Pods-Runner.profile.xcconfig */,
F971A3061D014BCFE5A477A9 /* Pods-RunnerTests.debug.xcconfig */,
D32BEBE6FA5B351434300418 /* Pods-RunnerTests.release.xcconfig */,
19DEDDAC7F8C48D43A9E76CF /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
331C80D6294CF71000263BE5 /* RunnerTests */ = { 331C80D6294CF71000263BE5 /* RunnerTests */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -125,6 +151,7 @@
331C80D6294CF71000263BE5 /* RunnerTests */, 331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */, 33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */, D73912EC22F37F3D000D13A0 /* Frameworks */,
076AC79CB527EDDF03C6C1BF /* Pods */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -175,6 +202,8 @@
D73912EC22F37F3D000D13A0 /* Frameworks */ = { D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
17BEB3BB725731AD7E5B80B1 /* Pods_Runner.framework */,
465D02A290608CA511C1F4CA /* Pods_RunnerTests.framework */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
@ -186,6 +215,7 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = ( buildPhases = (
EBBB44C74E6664565AAD7743 /* [CP] Check Pods Manifest.lock */,
331C80D1294CF70F00263BE5 /* Sources */, 331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */, 331C80D3294CF70F00263BE5 /* Resources */,
@ -204,11 +234,13 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = ( buildPhases = (
DD4A3E0D06F0EB3D92595679 /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */, 33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */, 33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */, 33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */, 3399D490228B24CF009A79C7 /* ShellScript */,
3020AFE6AB38B106E5D2E587 /* [CP] Embed Pods Frameworks */,
); );
buildRules = ( buildRules = (
); );
@ -227,7 +259,7 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastSwiftUpdateCheck = 0920; LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1430; LastUpgradeCheck = 1510;
ORGANIZATIONNAME = ""; ORGANIZATIONNAME = "";
TargetAttributes = { TargetAttributes = {
331C80D4294CF70F00263BE5 = { 331C80D4294CF70F00263BE5 = {
@ -290,6 +322,23 @@
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */
3020AFE6AB38B106E5D2E587 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
3399D490228B24CF009A79C7 /* ShellScript */ = { 3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1; alwaysOutOfDate = 1;
@ -328,6 +377,50 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
}; };
DD4A3E0D06F0EB3D92595679 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
EBBB44C74E6664565AAD7743 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
@ -379,6 +472,7 @@
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = { 331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = F971A3061D014BCFE5A477A9 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
@ -393,6 +487,7 @@
}; };
331C80DC294CF71000263BE5 /* Release */ = { 331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = D32BEBE6FA5B351434300418 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
@ -407,6 +502,7 @@
}; };
331C80DD294CF71000263BE5 /* Profile */ = { 331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 19DEDDAC7F8C48D43A9E76CF /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1430" LastUpgradeVersion = "1510"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View file

@ -4,4 +4,7 @@
<FileRef <FileRef
location = "group:Runner.xcodeproj"> location = "group:Runner.xcodeproj">
</FileRef> </FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace> </Workspace>