[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
RUN apt-get update
RUN apt-get install -y curl git unzip
COPY ./build/web /app
# define variables
ARG FLUTTER_SDK=/usr/local/flutter
ARG FLUTTER_VERSION=3.10.5
ARG APP=/app/
WORKDIR /app/
#clone flutter
RUN git clone https://github.com/flutter/flutter.git $FLUTTER_SDK
# change dir to current flutter folder and make a checkout to the specific version
RUN apk --no-cache add curl
RUN npm install --global http-server
# 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
CMD ["nginx", "-g", "daemon off;"]
CMD ["npx", "http-server", "-p", "80"]

View file

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

View file

@ -11,6 +11,8 @@ class FAppBar extends StatelessWidget implements PreferredSizeWidget {
padding: const EdgeInsets.symmetric(horizontal: 8),
child: AppBar(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
elevation: 0,
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(
padding: EdgeInsets.symmetric(vertical: insidePadding),
decoration: BoxDecoration(
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),
)
],
color: colorScheme.surfaceTint.withOpacity(0.85),
),
child: Center(
child: Text(
@ -48,6 +36,7 @@ class FButton extends StatelessWidget {
style: theme.textTheme.labelLarge!.copyWith(
fontWeight: FontWeight.bold,
fontSize: fontSize,
color: colorScheme.onPrimary,
),
),
),

View file

@ -40,7 +40,7 @@ class FDateItemWidget extends StatelessWidget {
width: 2,
),
color: picked
? colorScheme.onPrimary.withOpacity(0.25)
? colorScheme.onSurfaceVariant.withOpacity(0.25)
: Colors.transparent,
),
child: Column(
@ -49,7 +49,7 @@ class FDateItemWidget extends StatelessWidget {
Text(
dayOfTheWeekMap[date.weekday]!,
style: TextStyle(
color: colorScheme.onPrimary,
color: colorScheme.onSurfaceVariant,
fontSize: picked ? 24 : 12,
fontWeight: FontWeight.bold,
),
@ -57,7 +57,7 @@ class FDateItemWidget extends StatelessWidget {
Text(
'${date.day}.${date.month}',
style: TextStyle(
color: colorScheme.onPrimary,
color: colorScheme.onSurfaceVariant,
fontSize: picked ? 24 : 12,
fontWeight: FontWeight.bold,
),
@ -128,7 +128,7 @@ class _FDatePickerWidgetState extends State<FDatePickerWidget> {
child: IconButton(
icon: Icon(
Icons.calendar_month,
color: colorScheme.onPrimary,
color: colorScheme.onSurfaceVariant,
size: 20,
),
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:blur/blur.dart';
import 'package:flutter/widgets.dart';
import 'package:fooder/components/blur_container.dart';
class FNavBar extends StatelessWidget {
static const maxWidth = 920.0;
@ -7,61 +8,25 @@ class FNavBar extends StatelessWidget {
final List<Widget> children;
final double height;
const FNavBar({super.key, required this.children, this.height = 56});
const FNavBar({super.key, required this.children, this.height = 78});
@override
Widget build(BuildContext context) {
var theme = Theme.of(context);
var colorScheme = theme.colorScheme;
var widthAvail = MediaQuery.of(context).size.width;
// var width = widthAvail > maxWidth ? maxWidth : widthAvail;
return SafeArea(
child: Padding(
padding: const 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(
return SizedBox(
width: widthAvail,
height: height * children.length,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
colorScheme.primary.withOpacity(0.1),
colorScheme.secondary.withOpacity(0.1),
],
),
),
),
),
SizedBox(
child: BlurContainer(
width: widthAvail,
height: height * children.length,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: children,
),
),
],
)),
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
...children,
Container(
height: height / 3,
color: Colors.transparent,
),
]),
),
);
}

View file

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

View file

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

View file

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

View file

@ -5,6 +5,9 @@ import 'package:fooder/models/product.dart';
import 'package:fooder/models/diary.dart';
import 'package:fooder/models/meal.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:simple_barcode_scanner/simple_barcode_scanner.dart';
@ -132,7 +135,8 @@ class _AddEntryScreen extends BasedState<AddEntryScreen> {
constraints: const BoxConstraints(maxWidth: 720),
padding: const EdgeInsets.all(10),
child: ListView(children: <Widget>[
DropdownButton<Meal>(
FDropdown<Meal>(
labelText: 'Meal',
value: meal,
// Callback that sets the selected popup menu item.
onChanged: (Meal? meal) {
@ -151,19 +155,15 @@ class _AddEntryScreen extends BasedState<AddEntryScreen> {
),
],
),
TextFormField(
decoration: const InputDecoration(
FTextInput(
labelText: 'Product name',
),
controller: productNameController,
onChanged: (_) => _getProducts(),
onFieldSubmitted: (_) => _addEntry(),
autofocus: true,
),
TextFormField(
decoration: const InputDecoration(
FTextInput(
labelText: 'Grams',
),
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
inputFormatters: <TextInputFormatter>[
@ -212,14 +212,15 @@ class _AddEntryScreen extends BasedState<AddEntryScreen> {
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
FActionButton(
onPressed: _findProductByBarCode,
heroTag: null,
child: const Icon(Icons.photo_camera),
icon: Icons.photo_camera,
),
FloatingActionButton(
const SizedBox(width: 10),
FActionButton(
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/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 {
final Diary diary;
@ -14,7 +16,7 @@ class AddMealScreen extends BasedScreen {
State<AddMealScreen> createState() => _AddMealScreen();
}
class _AddMealScreen extends State<AddMealScreen> {
class _AddMealScreen extends BasedState<AddMealScreen> {
final nameController = TextEditingController();
final presetNameController = TextEditingController();
bool nameChanged = false;
@ -37,7 +39,7 @@ class _AddMealScreen extends State<AddMealScreen> {
void initState() {
super.initState();
setState(() {
nameController.text = "Meal ${widget.diary.meals.length}";
nameController.text = "Meal ${widget.diary.meals.length + 1}";
});
_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 {
await widget.apiClient.addMeal(
name: nameController.text,
@ -121,28 +114,21 @@ class _AddMealScreen extends State<AddMealScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text("🅵🅾🅾🅳🅴🆁", style: logoStyle(context)),
),
appBar: appBar(),
body: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 720),
padding: const EdgeInsets.all(10),
child: ListView(children: <Widget>[
TextFormField(
decoration: const InputDecoration(
FTextInput(
labelText: 'Meal name',
),
controller: nameController,
onChanged: (_) => setState(() {
nameChanged = true;
}),
),
TextFormField(
decoration: const InputDecoration(
hintText: 'Search presets',
),
FTextInput(
labelText: 'Search presets',
controller: presetNameController,
onChanged: (_) => _getPresets(),
),
@ -166,9 +152,9 @@ class _AddMealScreen extends State<AddMealScreen> {
]),
),
),
floatingActionButton: FloatingActionButton(
floatingActionButton: FActionButton(
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:fooder/screens/based.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 {
const AddProductScreen({super.key, required super.apiClient});
@ -10,7 +13,7 @@ class AddProductScreen extends BasedScreen {
State<AddProductScreen> createState() => _AddProductScreen();
}
class _AddProductScreen extends State<AddProductScreen> {
class _AddProductScreen extends BasedState<AddProductScreen> {
final nameController = TextEditingController();
final carbController = 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,
{bool silent = false}) async {
try {
@ -113,25 +107,21 @@ class _AddProductScreen extends State<AddProductScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text("🅵🅾🅾🅳🅴🆁", style: logoStyle(context)),
),
appBar: appBar(),
body: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 720),
padding: const EdgeInsets.all(10),
child: Column(children: <Widget>[
TextFormField(
decoration: const InputDecoration(
FTextInput(
labelText: 'Product name',
),
controller: nameController,
onChanged: (String value) {
setState(() {});
},
),
TextFormField(
decoration: const InputDecoration(
FTextInput(
labelText: 'Carbs',
),
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
inputFormatters: <TextInputFormatter>[
@ -143,10 +133,8 @@ class _AddProductScreen extends State<AddProductScreen> {
setState(() {});
},
),
TextFormField(
decoration: const InputDecoration(
FTextInput(
labelText: 'Fat',
),
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
inputFormatters: <TextInputFormatter>[
@ -158,10 +146,8 @@ class _AddProductScreen extends State<AddProductScreen> {
setState(() {});
},
),
TextFormField(
decoration: const InputDecoration(
FTextInput(
labelText: 'Protein',
),
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
inputFormatters: <TextInputFormatter>[
@ -173,10 +159,8 @@ class _AddProductScreen extends State<AddProductScreen> {
setState(() {});
},
),
TextFormField(
decoration: const InputDecoration(
FTextInput(
labelText: 'Fiber',
),
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
inputFormatters: <TextInputFormatter>[
@ -188,15 +172,30 @@ class _AddProductScreen extends State<AddProductScreen> {
setState(() {});
},
),
Text(
"${calculateCalories().toStringAsFixed(2)} kcal",
textAlign: TextAlign.right,
ProductWidget(
product: Product(
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,
child: const Icon(Icons.add),
icon: Icons.save,
),
);
}

View file

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

View file

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

View file

@ -1,12 +1,13 @@
import 'package:flutter/material.dart';
import 'package:fooder/screens/based.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/summary.dart';
import 'package:fooder/widgets/meal.dart';
import 'package:fooder/components/sliver.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 {
const MainScreen({super.key, required super.apiClient});
@ -43,6 +44,10 @@ class _MainScreen extends BasedState<MainScreen> {
}
Future<void> _addEntry() async {
if (diary == null) {
return;
}
await Navigator.push(
context,
MaterialPageRoute(
@ -52,60 +57,20 @@ class _MainScreen extends BasedState<MainScreen> {
).then((_) => _asyncInitState());
}
Widget floatingActionButton(BuildContext context) {
var theme = Theme.of(context);
var colorScheme = theme.colorScheme;
Future<void> _addMeal(context) async {
if (diary == null) {
return;
}
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),
],
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddMealScreen(
apiClient: widget.apiClient,
diary: diary!,
),
),
),
),
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
@ -133,7 +98,9 @@ class _MainScreen extends BasedState<MainScreen> {
apiClient: widget.apiClient,
refreshParent: _asyncInitState,
initiallyExpanded: i == 0,
showText: showText,
),
const SizedBox(height: 200),
],
),
),
@ -149,7 +116,18 @@ class _MainScreen extends BasedState<MainScreen> {
appBar: appBar(),
bottomNavigationBar: navBar(),
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:fooder/models/entry.dart';
import 'package:fooder/widgets/macroEntry.dart';
import 'package:fooder/widgets/macro.dart';
import 'dart:core';
class EntryHeader extends StatelessWidget {
@ -12,23 +12,26 @@ class EntryHeader extends StatelessWidget {
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Padding(
Expanded(
child: Padding(
padding: const EdgeInsets.all(8),
child: Text(
entry.product.name,
overflow: TextOverflow.fade,
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.bold,
),
),
),
),
const Spacer(),
Padding(
padding: const EdgeInsets.all(8),
child: Text(
"${entry.grams.toStringAsFixed(0)} g",
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 'dart:core';
class MacroWidget extends StatelessWidget {
final double? amount;
final double? calories;
final double? fiber;
final double protein;
final double carb;
final double fat;
final TextStyle style;
final Widget? child;
class MacroHeaderWidget extends StatelessWidget {
static const double padY = 4;
static const double padX = 8;
const MacroWidget({
final bool? fiber;
final bool? calories;
final Alignment alignment;
const MacroHeaderWidget({
super.key,
this.calories,
this.amount,
this.child,
this.fiber,
required this.protein,
required this.carb,
required this.fat,
required this.style,
this.fiber = false,
this.calories = false,
this.alignment = Alignment.centerLeft,
});
@override
Widget build(BuildContext context) {
var elements = <Widget>[
Expanded(
flex: 1,
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>[];
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(
"C: ${carb.toStringAsFixed(1)}g",
style: style,
element,
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
color: Theme.of(context).colorScheme.onSurface,
),
textAlign: TextAlign.center,
),
),
Expanded(
flex: 1,
child: Text(
"F: ${fat.toStringAsFixed(1)}g",
style: style,
textAlign: TextAlign.center,
),
);
}
if (alignment == Alignment.centerLeft) {
children.add(const Spacer());
}
return Padding(
padding: const EdgeInsets.symmetric(
vertical: padY,
horizontal: padX,
),
Expanded(
flex: 1,
child: Text(
"P: ${protein.toStringAsFixed(1)}g",
style: style,
textAlign: TextAlign.center,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: children,
),
);
}
}
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) {
elements.add(
Expanded(
flex: 1,
child: Text(
"f: ${fiber!.toStringAsFixed(1)}g",
style: style,
textAlign: TextAlign.center,
),
),
fiber!.toStringAsFixed(1),
);
}
if (calories != null) {
elements.add(
Expanded(
flex: 1,
calories!.toStringAsFixed(0),
);
}
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(
"${calories!.toStringAsFixed(1)} kcal",
style: style,
element,
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
color: Theme.of(context).colorScheme.onSurface,
),
textAlign: TextAlign.center,
),
),
);
}
if (amount != null) {
elements.add(
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(""),
),
);
if (alignment == Alignment.centerLeft) {
children.add(const Spacer());
}
return Padding(
padding: const EdgeInsets.only(
top: 4.0,
bottom: 4.0,
left: 8.0,
right: 8.0,
padding: const EdgeInsets.symmetric(
vertical: padY,
horizontal: padX,
),
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:fooder/models/meal.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/meal.dart';
import 'package:fooder/client.dart';
import 'dart:core';
@ -16,16 +15,19 @@ class MealHeader extends StatelessWidget {
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Padding(
Expanded(
child: Padding(
padding: const EdgeInsets.all(8),
child: Text(
meal.name,
overflow: TextOverflow.fade,
style: Theme.of(context).textTheme.headlineSmall!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.bold,
),
),
),
),
],
);
}
@ -37,6 +39,7 @@ class MealWidget extends StatelessWidget {
final Meal meal;
final ApiClient apiClient;
final Function() refreshParent;
final Function(String) showText;
final bool initiallyExpanded;
const MealWidget({
@ -45,19 +48,73 @@ class MealWidget extends StatelessWidget {
required this.apiClient,
required this.refreshParent,
required this.initiallyExpanded,
required this.showText,
});
Future<void> _editMeal(context) async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MealScreen(
apiClient: apiClient,
meal: meal,
refresh: refreshParent,
Future<void> saveMeal(context) async {
TextEditingController textFieldController = TextEditingController();
textFieldController.text = 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);
},
),
).then((_) => refreshParent());
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 {
@ -84,30 +141,18 @@ class MealWidget extends StatelessWidget {
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),
constraints: BoxConstraints(maxWidth: width),
color: colorScheme.surface.withOpacity(0.2),
child: ExpansionTile(
iconColor: colorScheme.onPrimary,
collapsedIconColor: colorScheme.onPrimary,
iconColor: colorScheme.onSurface,
collapsedIconColor: colorScheme.onSurface,
initiallyExpanded: initiallyExpanded,
enableFeedback: true,
title: Padding(
padding: const EdgeInsets.all(8),
child: Column(
@ -132,13 +177,25 @@ class MealWidget extends StatelessWidget {
entry: entry,
),
tileColor: i % 2 == 0
? colorScheme.secondary.withOpacity(0.1)
? 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 '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 {
final Preset preset;
@ -14,24 +41,21 @@ class PresetWidget extends StatelessWidget {
padding: const EdgeInsets.all(8),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: Text(
preset.name,
style: Theme.of(context).textTheme.titleLarge,
PresetHeader(
title: preset.name,
),
const MacroHeaderWidget(
fiber: true,
calories: true,
alignment: Alignment.center,
),
Text("${preset.calories.toStringAsFixed(1)} kcal"),
],
),
MacroWidget(
MacroEntryWidget(
protein: preset.protein,
carb: preset.carb,
fat: preset.fat,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
fiber: preset.fiber,
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 '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 {
final Product product;
@ -14,26 +41,21 @@ class ProductWidget extends StatelessWidget {
padding: const EdgeInsets.all(8),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: Text(
product.name,
style: Theme.of(context).textTheme.titleLarge,
ProductHeader(
title: product.name,
),
const MacroHeaderWidget(
fiber: true,
calories: true,
alignment: Alignment.center,
),
Text("${product.calories.toStringAsFixed(1)} kcal"),
],
),
MacroWidget(
MacroEntryWidget(
protein: product.protein,
carb: product.carb,
fat: product.fat,
fiber: product.fiber,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
),
calories: product.calories,
alignment: Alignment.center,
),
],
),

View file

@ -1,15 +1,13 @@
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/widgets/macro.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});
const SummaryHeader({super.key, required this.diary});
@override
Widget build(BuildContext context) {
@ -20,21 +18,12 @@ class SummaryHeader extends StatelessWidget {
child: Text(
"Summary",
style: Theme.of(context).textTheme.headlineSmall!.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.bold,
),
),
),
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.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);
@ -77,29 +54,18 @@ class SummaryWidget extends StatelessWidget {
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),
],
),
),
constraints: BoxConstraints(maxWidth: width),
color: colorScheme.surface.withOpacity(0.2),
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 12, horizontal: 24),
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24),
child: Column(
children: <Widget>[
SummaryHeader(diary: diary, addMeal: _addMeal),
SummaryHeader(diary: diary),
const MacroHeaderWidget(
calories: true,
),
@ -115,7 +81,6 @@ class SummaryWidget extends StatelessWidget {
),
),
),
),
);
}
}

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 */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
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 */
/* Begin PBXContainerItemProxy section */
@ -60,11 +62,14 @@
/* End PBXCopyFilesBuildPhase 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; };
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>"; };
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>"; };
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>"; };
@ -76,8 +81,13 @@
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>"; };
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>"; };
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 */
/* Begin PBXFrameworksBuildPhase section */
@ -85,6 +95,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DC768C2964C710CDD24D0DB4 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -92,12 +103,27 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
47A87A2D935C9FD21BC18BA6 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase 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 */ = {
isa = PBXGroup;
children = (
@ -125,6 +151,7 @@
331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
076AC79CB527EDDF03C6C1BF /* Pods */,
);
sourceTree = "<group>";
};
@ -175,6 +202,8 @@
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
17BEB3BB725731AD7E5B80B1 /* Pods_Runner.framework */,
465D02A290608CA511C1F4CA /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -186,6 +215,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
EBBB44C74E6664565AAD7743 /* [CP] Check Pods Manifest.lock */,
331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */,
@ -204,11 +234,13 @@
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
DD4A3E0D06F0EB3D92595679 /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
3020AFE6AB38B106E5D2E587 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -227,7 +259,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C80D4294CF70F00263BE5 = {
@ -290,6 +322,23 @@
/* End PBXResourcesBuildPhase 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 */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@ -328,6 +377,50 @@
shellPath = /bin/sh;
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 */
/* Begin PBXSourcesBuildPhase section */
@ -379,6 +472,7 @@
/* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F971A3061D014BCFE5A477A9 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
@ -393,6 +487,7 @@
};
331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D32BEBE6FA5B351434300418 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
@ -407,6 +502,7 @@
};
331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 19DEDDAC7F8C48D43A9E76CF /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;

View file

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

View file

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