From b1320b876ed507f0b7e30d5693daa9702d76b659 Mon Sep 17 00:00:00 2001 From: Jonasz Bigda Date: Tue, 12 Jan 2021 18:29:25 +0100 Subject: [PATCH] Version 1.0.0 (first build) --- .vscode/launch.json | 13 ++ lib/components/dishCard.dart | 30 ++- lib/components/dishCardAsync.dart | 132 ++++++++++++ lib/components/dishView.dart | 263 +++++++++++++++++++++++- lib/components/favoritesView.dart | 5 +- lib/components/filters.dart | 76 +++++-- lib/components/lineOfIcons.dart | 18 +- lib/components/lineOfIconsSmall.dart | 18 +- lib/components/mapView.dart | 142 ++++++++----- lib/components/orderView.dart | 71 +++++-- lib/components/restaurantCardAsync.dart | 5 +- lib/components/restaurantView.dart | 2 - lib/components/searchResults.dart | 85 ++------ lib/services.dart | 89 ++++---- lib/settings.dart | 89 ++++++-- 15 files changed, 803 insertions(+), 235 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 lib/components/dishCardAsync.dart diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..afad12c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "menui_mobile", + "request": "launch", + "type": "dart" + } + ] +} \ No newline at end of file diff --git a/lib/components/dishCard.dart b/lib/components/dishCard.dart index 89ee7dd..22daedb 100644 --- a/lib/components/dishCard.dart +++ b/lib/components/dishCard.dart @@ -26,6 +26,14 @@ class DishCard extends StatelessWidget { width: 80, height: 80, fit: BoxFit.cover, + errorBuilder: (BuildContext context, Object exception, + StackTrace stackTrace) { + return Container( + width: 80, + height: 80, + decoration: BoxDecoration(color: Colors.grey[900]), + ); + }, ), borderRadius: BorderRadius.only( bottomLeft: Radius.circular(12), @@ -41,11 +49,15 @@ class DishCard extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - dish.name, - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: TextStyle(color: Colors.orange[600], fontSize: 15), + Expanded( + flex: 0, + child: Text( + dish.name, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: + TextStyle(color: Colors.orange[600], fontSize: 15), + ), ), Text( dish.weight, @@ -55,7 +67,9 @@ class DishCard extends StatelessWidget { ), ], ), - Prices(prices: dish.prices) + Expanded( + child: Prices(prices: dish.prices), + ) ], )), Container( @@ -85,21 +99,25 @@ class Prices extends StatelessWidget { if (prices.price1.priceName == "") Text( '${prices.price1.price} zł', + overflow: TextOverflow.ellipsis, style: TextStyle(color: Colors.white, fontSize: 14), ), if (prices.price1.priceName != "") Text( '${prices.price1.priceName}: ${prices.price1.price} zł', + overflow: TextOverflow.ellipsis, style: TextStyle(color: Colors.white, fontSize: 14), ), if (prices.price2.priceName != "") Text( '${prices.price2.priceName}: ${prices.price2.price} zł', + overflow: TextOverflow.ellipsis, style: TextStyle(color: Colors.white, fontSize: 14), ), if (prices.price3.priceName != "") Text( '${prices.price3.priceName}: ${prices.price3.price} zł', + overflow: TextOverflow.ellipsis, style: TextStyle(color: Colors.white, fontSize: 14), ), ], diff --git a/lib/components/dishCardAsync.dart b/lib/components/dishCardAsync.dart new file mode 100644 index 0000000..bb891b4 --- /dev/null +++ b/lib/components/dishCardAsync.dart @@ -0,0 +1,132 @@ +import 'package:flutter/material.dart'; +import 'package:menui_mobile/settings.dart'; +import '../services.dart'; +import 'dishView.dart'; + +class DishCardAsync extends StatelessWidget { + final OrderItem item; + final Function onRemoved; + final services = new MenuiServices(); + final settings = new MenuiSettings(); + final int index; + + DishCardAsync({@required this.item, this.onRemoved, this.index}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Card( + color: Colors.grey[850], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12)), + margin: EdgeInsets.only(left: 12, top: 5, bottom: 5), + child: FutureBuilder( + future: services.fetchDish(item.id), + builder: (context, snapshot) { + if (snapshot.hasData) { + Dish dish = snapshot.data; + return InkWell( + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DishView(dish: dish))), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Center( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 12), + child: Text( + '${item.quantity}x', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w600), + ), + ), + ), + Container( + child: ClipRRect( + child: Image.network( + dish.imgUrl, + width: 80, + height: 80, + fit: BoxFit.cover, + ), + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(12), + topLeft: Radius.circular(12)), + ), + padding: EdgeInsets.only(right: 8), + ), + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 0, + child: Text( + dish.name, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: TextStyle( + color: Colors.orange[600], + fontSize: 15), + ), + ), + if (item.priceName.isNotEmpty) + Text( + item.priceName, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: TextStyle( + color: Colors.white, fontSize: 12), + ) + ], + ), + Padding( + padding: EdgeInsets.only(right: 12), + child: Text( + '${item.price} zł', + style: TextStyle( + color: Colors.white, fontSize: 13), + ), + ) + ], + )), + ], + ), + ); + } else { + return Center( + child: Padding( + padding: EdgeInsets.all(24), + child: CircularProgressIndicator(), + ), + ); + } + }, + )), + ), + Expanded( + flex: 0, + child: IconButton( + icon: Icon( + Icons.delete_rounded, + color: Colors.white, + ), + onPressed: () { + settings.removeFromOrder(index); + onRemoved(); + }, + ), + ) + ], + ); + } +} diff --git a/lib/components/dishView.dart b/lib/components/dishView.dart index 66dcc46..488b8db 100644 --- a/lib/components/dishView.dart +++ b/lib/components/dishView.dart @@ -13,10 +13,22 @@ class DishView extends StatelessWidget { final Dish dish; DishView({@required this.dish}); final MenuiSettings settings = new MenuiSettings(); + final SnackBar snackbarAdded = new SnackBar( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), + backgroundColor: Colors.orange, + duration: Duration(seconds: 2), + behavior: SnackBarBehavior.floating, + content: Text( + "Dodano do zamówienia :)", + style: TextStyle(color: Colors.grey[850]), + ), + ); + final _scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) { return Scaffold( + key: _scaffoldKey, body: Container( decoration: BoxDecoration(color: Colors.grey[850]), child: ListView( @@ -260,7 +272,17 @@ class DishView extends StatelessWidget { icon: Icons.note_add_rounded, text: "Do zamówienia", onPressed: () { - settings.addToOrder(dish.id); + showDialog( + context: context, + builder: (context) { + return AddToOrderDialog( + dish: dish, + onSubmit: () { + _scaffoldKey.currentState.showSnackBar(snackbarAdded); + Navigator.pop(context); + }, + ); + }); }, ), ], @@ -269,6 +291,245 @@ class DishView extends StatelessWidget { } } +class AddToOrderDialog extends StatefulWidget { + final Dish dish; + final Function onSubmit; + final MenuiSettings settings = new MenuiSettings(); + + AddToOrderDialog({@required this.dish, @required this.onSubmit}); + + @override + State createState() => AddToOrderDialogState(); +} + +class AddToOrderDialogState extends State { + int quantity = 1; + String price; + String priceName; + int selectedVariant = 1; + + @override + void initState() { + super.initState(); + price = widget.dish.prices.price1.price; + priceName = widget.dish.prices.price1.priceName; + } + + @override + Widget build(BuildContext context) { + return SimpleDialog( + title: Text( + 'Dodaj do zamówienia', + style: TextStyle(color: Colors.white, fontSize: 16), + textAlign: TextAlign.center, + ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), + backgroundColor: Colors.grey[850], + children: [ + Text( + 'Ilość', + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + iconSize: 16, + icon: Icon( + Icons.remove, + color: Colors.white, + ), + onPressed: () { + if (quantity > 1) { + setState(() { + quantity = quantity - 1; + }); + } + }, + ), + Text( + "$quantity", + style: TextStyle(color: Colors.orange, fontSize: 16), + ), + IconButton( + iconSize: 16, + icon: Icon( + Icons.add, + color: Colors.white, + ), + onPressed: () { + setState(() { + quantity = quantity + 1; + }); + }, + ) + ], + ), + if (widget.dish.prices.price1.priceName != "") + Padding( + padding: EdgeInsets.only(bottom: 8), + child: Text( + 'Wariant', + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey), + ), + ), + if (widget.dish.prices.price1.priceName != "") + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.all(4), + child: ButtonTheme( + minWidth: 20, + child: RaisedButton( + onPressed: () { + setState(() { + selectedVariant = 1; + price = widget.dish.prices.price1.price; + priceName = widget.dish.prices.price1.priceName; + }); + }, + color: (() { + if (selectedVariant == 1) { + return Colors.grey[600]; + } else { + return Colors.grey[800]; + } + }()), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14)), + child: Padding( + padding: + EdgeInsets.symmetric(vertical: 12, horizontal: 2), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '(${widget.dish.prices.price1.priceName})', + style: + TextStyle(color: Colors.white, fontSize: 11), + ), + Text( + '${widget.dish.prices.price1.price} zł', + style: + TextStyle(color: Colors.white, fontSize: 14), + ), + ]), + ), + ), + ), + ), + Padding( + padding: EdgeInsets.all(4), + child: ButtonTheme( + minWidth: 20, + child: RaisedButton( + onPressed: () { + setState(() { + selectedVariant = 2; + price = widget.dish.prices.price2.price; + priceName = widget.dish.prices.price2.priceName; + }); + }, + color: (() { + if (selectedVariant == 2) { + return Colors.grey[600]; + } else { + return Colors.grey[800]; + } + }()), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14)), + child: Padding( + padding: + EdgeInsets.symmetric(vertical: 12, horizontal: 2), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '(${widget.dish.prices.price2.priceName})', + style: + TextStyle(color: Colors.white, fontSize: 11), + ), + Text( + '${widget.dish.prices.price2.price} zł', + style: + TextStyle(color: Colors.white, fontSize: 14), + ), + ]), + ), + ), + ), + ), + Padding( + padding: EdgeInsets.all(4), + child: ButtonTheme( + minWidth: 20, + child: RaisedButton( + onPressed: () { + setState(() { + selectedVariant = 3; + price = widget.dish.prices.price3.price; + priceName = widget.dish.prices.price3.priceName; + }); + }, + color: (() { + if (selectedVariant == 3) { + return Colors.grey[600]; + } else { + return Colors.grey[800]; + } + }()), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14)), + child: Padding( + padding: + EdgeInsets.symmetric(vertical: 12, horizontal: 2), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '(${widget.dish.prices.price3.priceName})', + style: + TextStyle(color: Colors.white, fontSize: 11), + ), + Text( + '${widget.dish.prices.price3.price} zł', + style: + TextStyle(color: Colors.white, fontSize: 14), + ), + ]), + ), + ), + ), + ) + ], + ), + Padding( + padding: EdgeInsets.only(top: 12), + child: Center( + child: RaisedButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14)), + onPressed: () { + widget.settings.addToOrder(new OrderItem( + id: widget.dish.id, + quantity: quantity, + price: price, + priceName: priceName)); + widget.onSubmit(); + }, + child: Text('Dodaj'), + ), + ), + ) + ], + ); + } +} + class Prices extends StatelessWidget { final MenuiPrices prices; diff --git a/lib/components/favoritesView.dart b/lib/components/favoritesView.dart index 50dea12..bf7c3b5 100644 --- a/lib/components/favoritesView.dart +++ b/lib/components/favoritesView.dart @@ -43,7 +43,10 @@ class _FavoritesViewState extends State { }, ); } else { - return null; + return Container( + width: 0, + height: 0, + ); } }, )), diff --git a/lib/components/filters.dart b/lib/components/filters.dart index c9f42a2..c58eebc 100644 --- a/lib/components/filters.dart +++ b/lib/components/filters.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:menui_mobile/services.dart'; enum Tags { cardPayments, @@ -11,13 +12,7 @@ enum Tags { } class Filters { - bool cardPayments = false; - bool petFriendly = false; - bool glutenFree = false; - bool vegan = false; - bool vegetarian = false; - bool alcohol = false; - bool delivery = false; + List tags = []; bool onlyOpen = false; List selectedTypes = []; final List availableTypes = [ @@ -43,6 +38,41 @@ class Filters { 'mieszane', 'inna' ]; + + List filterByTypes( + List restaurants, List types) { + if (types.isEmpty) { + return restaurants; + } else { + List result = restaurants.where((restaurant) { + return types.contains(restaurant.type); + }).toList(); + return result; + } + } + + List filterByTags(List restaurants, Filters filters) { + if (filters.tags.isEmpty) { + return restaurants; + } else { + List result = []; + restaurants.forEach((restaurant) => { + if (filters.tags.every((tag) { + return restaurant.tags.contains(tag); + })) + {result.add(restaurant)} + }); + return result; + } + } + + List filterRestaurants( + List restaurants, Filters filters) { + List result = []; + result = filterByTypes(restaurants, filters.selectedTypes); + result = filterByTags(result, filters); + return result; + } } class RestaurantFilters extends StatelessWidget { @@ -112,45 +142,52 @@ class RestaurantFilters extends StatelessWidget { children: [ RestaurantTag( name: "Płatność kartą", - active: filters.cardPayments, img: 'img/i_card_black.png', onTapped: () => onSelectTag(Tags.cardPayments), + filters: filters, + filterTag: Tags.cardPayments, ), RestaurantTag( name: "Lubimy zwierzaki", - active: filters.petFriendly, img: 'img/i_pets_black.png', onTapped: () => onSelectTag(Tags.petFriendly), + filters: filters, + filterTag: Tags.petFriendly, ), RestaurantTag( name: "Bez glutenu", - active: filters.glutenFree, img: 'img/i_glutenFree_black.png', onTapped: () => onSelectTag(Tags.glutenFree), + filters: filters, + filterTag: Tags.glutenFree, ), RestaurantTag( name: "Wegańskie", - active: filters.vegan, img: 'img/i_vegan_black.png', onTapped: () => onSelectTag(Tags.vegan), + filters: filters, + filterTag: Tags.vegan, ), RestaurantTag( name: "Wegetariańskie", - active: filters.vegetarian, img: 'img/i_vegetarian_black.png', onTapped: () => onSelectTag(Tags.vegetarian), + filters: filters, + filterTag: Tags.vegetarian, ), RestaurantTag( name: "Alkohol", - active: filters.alcohol, img: 'img/i_alcohol_black.png', onTapped: () => onSelectTag(Tags.alcohol), + filters: filters, + filterTag: Tags.alcohol, ), RestaurantTag( name: "Dowozimy", - active: filters.delivery, img: 'img/i_delivery_black.png', onTapped: () => onSelectTag(Tags.delivery), + filters: filters, + filterTag: Tags.delivery, ), ], ) @@ -163,13 +200,20 @@ class RestaurantFilters extends StatelessWidget { class RestaurantTag extends StatelessWidget { final String name; final String img; - final bool active; final Function onTapped; + final Filters filters; + final Tags filterTag; - RestaurantTag({this.name, this.active, this.img, this.onTapped}); + RestaurantTag( + {this.name, this.img, this.onTapped, this.filters, this.filterTag}); @override Widget build(BuildContext context) { + bool active = false; + if (filters.tags.contains(filterTag)) { + active = true; + } + return ButtonTheme( height: 26, minWidth: 60, diff --git a/lib/components/lineOfIcons.dart b/lib/components/lineOfIcons.dart index 4d26f9e..6c43c86 100644 --- a/lib/components/lineOfIcons.dart +++ b/lib/components/lineOfIcons.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import '../services.dart'; +import 'filters.dart'; class LineOfIcons extends StatelessWidget { - final MenuiTags tags; + final List tags; final double edgeInsets = 3; final double imagesWidth = 14; final double fontSize = 8; @@ -17,7 +17,7 @@ class LineOfIcons extends StatelessWidget { alignment: WrapAlignment.center, direction: Axis.horizontal, children: [ - if (tags.alcohol == true) + if (tags.contains(Tags.alcohol)) Container( margin: EdgeInsets.all(edgeInsets), child: Column( @@ -38,7 +38,7 @@ class LineOfIcons extends StatelessWidget { ) ], )), - if (tags.cardPayments == true) + if (tags.contains(Tags.cardPayments)) Container( margin: EdgeInsets.all(edgeInsets), child: Column( @@ -65,7 +65,7 @@ class LineOfIcons extends StatelessWidget { ) ], )), - if (tags.delivery == true) + if (tags.contains(Tags.delivery)) Container( margin: EdgeInsets.all(edgeInsets), child: Column( @@ -86,7 +86,7 @@ class LineOfIcons extends StatelessWidget { ) ], )), - if (tags.glutenFree == true) + if (tags.contains(Tags.glutenFree)) Container( margin: EdgeInsets.all(edgeInsets), child: Column( @@ -107,7 +107,7 @@ class LineOfIcons extends StatelessWidget { ) ], )), - if (tags.petFriendly == true) + if (tags.contains(Tags.petFriendly)) Container( margin: EdgeInsets.all(edgeInsets), child: Column( @@ -133,7 +133,7 @@ class LineOfIcons extends StatelessWidget { ) ], )), - if (tags.vegan == true) + if (tags.contains(Tags.vegan)) Container( margin: EdgeInsets.all(edgeInsets), child: Column( @@ -154,7 +154,7 @@ class LineOfIcons extends StatelessWidget { ) ], )), - if (tags.vegetarian == true) + if (tags.contains(Tags.vegetarian)) Container( margin: EdgeInsets.all(edgeInsets), child: Column( diff --git a/lib/components/lineOfIconsSmall.dart b/lib/components/lineOfIconsSmall.dart index 76881e3..8ed7a37 100644 --- a/lib/components/lineOfIconsSmall.dart +++ b/lib/components/lineOfIconsSmall.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import '../services.dart'; +import 'filters.dart'; class LineOfIconsSmall extends StatelessWidget { - final MenuiTags tags; + final List tags; LineOfIconsSmall({@required this.tags}); @@ -12,7 +12,7 @@ class LineOfIconsSmall extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (tags.alcohol == true) + if (tags.contains(Tags.alcohol)) Container( margin: EdgeInsets.only(top: 4, bottom: 4, right: 9), child: Column( @@ -27,7 +27,7 @@ class LineOfIconsSmall extends StatelessWidget { ), ], )), - if (tags.cardPayments == true) + if (tags.contains(Tags.cardPayments)) Container( margin: EdgeInsets.only(top: 4, bottom: 4, right: 9), child: Column( @@ -42,7 +42,7 @@ class LineOfIconsSmall extends StatelessWidget { ), ], )), - if (tags.delivery == true) + if (tags.contains(Tags.delivery)) Container( margin: EdgeInsets.only(top: 4, bottom: 4, right: 9), child: Column( @@ -57,7 +57,7 @@ class LineOfIconsSmall extends StatelessWidget { ), ], )), - if (tags.glutenFree == true) + if (tags.contains(Tags.glutenFree)) Container( margin: EdgeInsets.only(top: 4, bottom: 4, right: 9), child: Column( @@ -72,7 +72,7 @@ class LineOfIconsSmall extends StatelessWidget { ), ], )), - if (tags.petFriendly == true) + if (tags.contains(Tags.petFriendly)) Container( margin: EdgeInsets.only(top: 4, bottom: 4, right: 9), child: Column( @@ -87,7 +87,7 @@ class LineOfIconsSmall extends StatelessWidget { ), ], )), - if (tags.vegan == true) + if (tags.contains(Tags.vegan)) Container( margin: EdgeInsets.only(top: 4, bottom: 4, right: 9), child: Column( @@ -102,7 +102,7 @@ class LineOfIconsSmall extends StatelessWidget { ), ], )), - if (tags.vegetarian == true) + if (tags.contains(Tags.vegetarian)) Container( margin: EdgeInsets.only(top: 4, bottom: 4, right: 9), child: Column( diff --git a/lib/components/mapView.dart b/lib/components/mapView.dart index 1687b53..9087b98 100644 --- a/lib/components/mapView.dart +++ b/lib/components/mapView.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:menui_mobile/components/filters.dart'; import 'package:menui_mobile/settings.dart'; import '../services.dart'; import 'package:geolocator/geolocator.dart'; @@ -14,19 +15,54 @@ class MapView extends StatefulWidget { State createState() => MapViewState(); } -class MapViewState extends State { +class MapViewState extends State with SingleTickerProviderStateMixin { + Filters filters = new Filters(); Completer _controller = Completer(); + bool expand; + AnimationController animationController; + Animation animation; MenuiServices services = new MenuiServices(); final MenuiSettings settings = new MenuiSettings(); Position position; + void prepareAnimations() { + animationController = + AnimationController(vsync: this, duration: Duration(milliseconds: 500)); + animation = CurvedAnimation( + parent: animationController, curve: Curves.fastOutSlowIn); + } + + void checkExpand() { + if (expand) { + animationController.forward(); + } else { + animationController.reverse(); + } + } + + @override + void initState() { + super.initState(); + expand = false; + prepareAnimations(); + checkExpand(); + } + + @override + void dispose() { + animationController.dispose(); + super.dispose(); + } + Future createMarkers() async { Map markers = {}; Position position = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high); LatLng location = new LatLng(position.latitude, position.longitude); - List restaurants = await services.fetchRestaurantsByLocation( - position.latitude, position.longitude); + List fetchedRestaurants = await services + .fetchRestaurantsByLocation(position.latitude, position.longitude); + List restaurants = + filters.filterRestaurants(fetchedRestaurants, filters); if (restaurants.isNotEmpty) { for (Restaurant thisRestaurant in restaurants) { final MarkerId markerId = MarkerId(thisRestaurant.name); @@ -51,6 +87,7 @@ class MapViewState extends State { @override Widget build(BuildContext context) { + checkExpand(); return Scaffold( body: FutureBuilder( future: createMarkers(), @@ -107,7 +144,10 @@ class MapViewState extends State { padding: EdgeInsets.symmetric( vertical: 12, horizontal: 4), onPressed: () { - Navigator.pop(context); + showRadiusSelectionDialog(context, settings, + () { + setState(() {}); + }); }, child: Column( mainAxisSize: MainAxisSize.min, @@ -120,13 +160,22 @@ class MapViewState extends State { 'Promień', style: TextStyle( color: Colors.grey[200], - fontSize: 12, + fontSize: 11, fontWeight: FontWeight.w400), ), - Text( - '600m', - style: TextStyle( - color: Colors.grey, fontSize: 10), + FutureBuilder( + future: settings.getRadius(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return Text( + '${snapshot.data}m', + style: TextStyle( + color: Colors.grey, fontSize: 10), + ); + } else { + return null; + } + }, ) ], ), @@ -137,37 +186,9 @@ class MapViewState extends State { padding: EdgeInsets.symmetric( vertical: 12, horizontal: 4), onPressed: () { - Navigator.pop(context); - }, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.map_rounded, - color: Colors.orange, - ), - Text( - 'Kuchnia', - style: TextStyle( - color: Colors.grey[200], - fontSize: 12, - fontWeight: FontWeight.w400), - ), - Text( - 'Wszystkie', - style: TextStyle( - color: Colors.grey, fontSize: 10), - ) - ], - ), - ), - RaisedButton( - color: Colors.grey[900], - elevation: 0, - padding: EdgeInsets.symmetric( - vertical: 12, horizontal: 4), - onPressed: () { - Navigator.pop(context); + setState(() { + expand = !expand; + }); }, child: Column( mainAxisSize: MainAxisSize.min, @@ -180,20 +201,49 @@ class MapViewState extends State { 'Filtry', style: TextStyle( color: Colors.grey[200], - fontSize: 12, + fontSize: 11, fontWeight: FontWeight.w400), ), - Text( - 'Brak', - style: TextStyle( - color: Colors.grey, fontSize: 10), - ) ], ), ), ], ), ), + SizeTransition( + sizeFactor: animation, + child: RestaurantFilters( + filters: filters, + onSelectType: (value) { + if (filters.selectedTypes.contains(value)) { + final List result = + List.from(filters.selectedTypes); + result.remove(value); + setState(() { + filters.selectedTypes = result; + }); + } else { + final List result = + List.from(filters.selectedTypes); + result.add(value); + setState(() { + filters.selectedTypes = result; + }); + } + }, + onSelectTag: (tag) { + List result = List.from(filters.tags); + if (filters.tags.contains(tag)) { + result.remove(tag); + } else { + result.add(tag); + } + setState(() { + filters.tags = result; + }); + }, + ), + ), Container( decoration: BoxDecoration(color: Colors.grey[800]), child: Row( diff --git a/lib/components/orderView.dart b/lib/components/orderView.dart index bafc563..65a3daa 100644 --- a/lib/components/orderView.dart +++ b/lib/components/orderView.dart @@ -1,12 +1,18 @@ import 'package:flutter/material.dart'; +import 'package:menui_mobile/components/dishCardAsync.dart'; import '../settings.dart'; import 'homeScreen.dart'; import 'favoritesView.dart'; import 'menuiButton.dart'; -class OrderView extends StatelessWidget { +class OrderView extends StatefulWidget { final settings = new MenuiSettings(); + @override + State createState() => OrderViewState(); +} + +class OrderViewState extends State { @override Widget build(BuildContext context) { return Scaffold( @@ -16,21 +22,46 @@ class OrderView extends StatelessWidget { image: AssetImage("img/bg_tile.jpg"), fit: BoxFit.cover)), child: Column( children: [ - Container( - decoration: BoxDecoration(color: Colors.grey[800]), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.attach_money_rounded, - color: Colors.orange, - ), - Text( - 'Suma: 0zł', - style: TextStyle(color: Colors.white, fontSize: 12), - ), - ], - ), + FutureBuilder( + future: widget.settings.getOrder(), + builder: (context, snapshot) { + if (snapshot.hasData) { + List order = snapshot.data; + if (order.isNotEmpty) { + return Expanded( + child: ListView.builder( + itemCount: order.length, + itemBuilder: (context, index) { + return DishCardAsync( + item: order[index], + index: index, + onRemoved: () { + setState(() {}); + }, + ); + }, + ), + ); + } else { + return Container( + child: Center( + child: Padding( + padding: EdgeInsets.only(top: 50), + child: Text( + "Zamówienie jest puste.", + style: TextStyle(color: Colors.grey), + ), + )), + ); + } + } else { + return Container( + child: Center( + child: CircularProgressIndicator(), + ), + ); + } + }, ) ], ), @@ -66,7 +97,7 @@ class OrderView extends StatelessWidget { icon: Icons.settings, text: "Ustawienia", onPressed: () { - showSettings(context, settings); + showSettings(context, widget.settings); }, ), ], @@ -75,8 +106,7 @@ class OrderView extends StatelessWidget { appBar: AppBar( title: Text( 'Zamówienie', - style: TextStyle( - color: Colors.white, fontWeight: FontWeight.w400, fontSize: 14), + style: TextStyle(color: Colors.white, fontWeight: FontWeight.w400), ), backgroundColor: Colors.grey[900], leading: IconButton( @@ -90,7 +120,8 @@ class OrderView extends StatelessWidget { MenuiButton( color: Colors.orange, onPressed: () { - settings.clearOrder(); + widget.settings.clearOrder(); + setState(() {}); }, text: "Wyczyść", icon: Icons.delete_forever_rounded, diff --git a/lib/components/restaurantCardAsync.dart b/lib/components/restaurantCardAsync.dart index 647446f..2bf9ad3 100644 --- a/lib/components/restaurantCardAsync.dart +++ b/lib/components/restaurantCardAsync.dart @@ -128,7 +128,10 @@ class RestaurantCardAsync extends StatelessWidget { )); } else { return Center( - child: CircularProgressIndicator(), + child: Padding( + padding: EdgeInsets.all(24), + child: CircularProgressIndicator(), + ), ); } }, diff --git a/lib/components/restaurantView.dart b/lib/components/restaurantView.dart index 247909c..6ba0c9d 100644 --- a/lib/components/restaurantView.dart +++ b/lib/components/restaurantView.dart @@ -27,13 +27,11 @@ class _RestaurantViewState extends State { @override Widget build(BuildContext context) { - List categories = []; return FutureBuilder( future: services.fetchRestaurant(widget.id), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData) { restaurant = snapshot.data; - categories = restaurant.categories; return Scaffold( body: Container( decoration: BoxDecoration(color: Colors.grey[850]), diff --git a/lib/components/searchResults.dart b/lib/components/searchResults.dart index 6dbaea0..39b658f 100644 --- a/lib/components/searchResults.dart +++ b/lib/components/searchResults.dart @@ -24,7 +24,6 @@ class SearchResults extends StatefulWidget { class _SearchResultsState extends State with SingleTickerProviderStateMixin { - GlobalKey _drawerKey = GlobalKey(); bool expand; AnimationController animationController; Animation animation; @@ -62,8 +61,9 @@ class _SearchResultsState extends State @override Widget build(BuildContext context) { checkExpand(); + List filteredRestaurants = + filters.filterRestaurants(widget.restaurants, filters); return Scaffold( - key: _drawerKey, body: Container( decoration: BoxDecoration( image: DecorationImage( @@ -93,57 +93,15 @@ class _SearchResultsState extends State } }, onSelectTag: (tag) { - switch (tag) { - case Tags.alcohol: - { - setState(() { - filters.alcohol = !filters.alcohol; - }); - } - break; - case Tags.cardPayments: - { - setState(() { - filters.cardPayments = !filters.cardPayments; - }); - } - break; - case Tags.delivery: - { - setState(() { - filters.delivery = !filters.delivery; - }); - } - break; - case Tags.glutenFree: - { - setState(() { - filters.glutenFree = !filters.glutenFree; - }); - } - break; - case Tags.petFriendly: - { - setState(() { - filters.petFriendly = !filters.petFriendly; - }); - } - break; - case Tags.vegan: - { - setState(() { - filters.vegan = !filters.vegan; - }); - } - break; - case Tags.vegetarian: - { - setState(() { - filters.vegetarian = !filters.vegetarian; - }); - } - break; + List result = List.from(filters.tags); + if (filters.tags.contains(tag)) { + result.remove(tag); + } else { + result.add(tag); } + setState(() { + filters.tags = result; + }); }, )), Container( @@ -161,10 +119,10 @@ class _SearchResultsState extends State ), Expanded( child: ListView.builder( - itemCount: widget.restaurants.length, + itemCount: filteredRestaurants.length, itemBuilder: (context, index) { return RestaurantCard( - restaurant: widget.restaurants[index], + restaurant: filteredRestaurants[index], ); }, )) @@ -211,7 +169,7 @@ class _SearchResultsState extends State ), appBar: AppBar( title: Text( - 'Znaleziono: ${widget.restaurants.length}', + 'Znaleziono: ${filteredRestaurants.length}', style: TextStyle( color: Colors.white, fontSize: 14, fontWeight: FontWeight.w400), ), @@ -236,23 +194,6 @@ class _SearchResultsState extends State ), ], ), - drawer: Drawer( - child: ListView( - padding: EdgeInsets.zero, - children: [ - DrawerHeader( - decoration: BoxDecoration(color: Colors.grey[850]), - child: Text( - 'Filtry', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w400, - color: Colors.orange), - ), - ) - ], - ), - ), ); } } diff --git a/lib/services.dart b/lib/services.dart index 53ec403..d802833 100644 --- a/lib/services.dart +++ b/lib/services.dart @@ -3,6 +3,7 @@ import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:http/http.dart' as http; import 'package:flutter/foundation.dart'; import './settings.dart'; +import './components/filters.dart'; class MenuiServices { final String backendURL = 'https://api.menui.pl/'; @@ -29,11 +30,39 @@ class MenuiServices { final response = await http.get('${backendURL}dish?dishId=$id'); if (response.statusCode == 200 || response.statusCode == 304) { final decoded = jsonDecode(response.body); + final thisAllergens = decoded['allergens']; + final thisPrices = decoded['prices']; + final thisPrice1 = thisPrices['price1']; + final thisPrice2 = thisPrices['price2']; + final thisPrice3 = thisPrices['price3']; final result = new Dish( id: decoded['_id'], restaurantId: decoded['restaurantId'], name: decoded['name'], - category: decoded['category']); + category: decoded['category'], + prices: new MenuiPrices( + price1: + new MenuiPrice(thisPrice1['priceName'], thisPrice1['price']), + price2: + new MenuiPrice(thisPrice2['priceName'], thisPrice2['price']), + price3: + new MenuiPrice(thisPrice3['priceName'], thisPrice3['price'])), + notes: decoded['notes'], + imgUrl: decoded['imgUrl'], + weight: decoded['weight'], + ingredients: decoded['ingredients'], + vegetarian: decoded['vegetarian'], + vegan: decoded['vegan'], + glicemicIndex: decoded['glicemicIndex'], + kCal: decoded['kCal'], + allergens: new MenuiAllergens( + thisAllergens['gluten'], + thisAllergens['lactose'], + thisAllergens['soy'], + thisAllergens['eggs'], + thisAllergens['seaFood'], + thisAllergens['peanuts'], + thisAllergens['sesame'])); return result; } else { throw "Nie udało się pobrać"; @@ -87,6 +116,8 @@ class MenuiServices { } } return dishes; + } else { + return List(); } } @@ -131,18 +162,13 @@ class MenuiServices { links: new MenuiLinks( links['facebook'], links['instagram'], links['www']), categories: categories, - tags: new MenuiTags( - tags['cardPayments'], - tags['petFriendly'], - tags['glutenFree'], - tags['vegan'], - tags['vegetarian'], - tags['alcohol'], - tags['delivery']), + tags: decodeTags(tags), lunchHours: decoded['lunchHours'], lunchMenu: lunchMenu, dishes: decoded['dishes']); return result; + } else { + return null; } } @@ -186,14 +212,7 @@ class MenuiServices { workingHours['sb'], workingHours['nd']), description: restaurant['description'], - tags: new MenuiTags( - tags['cardPayments'], - tags['petFriendly'], - tags['glutenFree'], - tags['vegan'], - tags['vegetarian'], - tags['alcohol'], - tags['delivery']), + tags: decodeTags(tags), lunchHours: restaurant['lunchHours'], lunchMenu: lunchMenu, dishes: restaurant['dishes']); @@ -242,14 +261,7 @@ class MenuiServices { workingHours['sb'], workingHours['nd']), description: restaurant['description'], - tags: new MenuiTags( - tags['cardPayments'], - tags['petFriendly'], - tags['glutenFree'], - tags['vegan'], - tags['vegetarian'], - tags['alcohol'], - tags['delivery']), + tags: decodeTags(tags), lunchHours: restaurant['lunchHours'], lunchMenu: lunchMenu, dishes: restaurant['dishes']); @@ -297,6 +309,18 @@ class MenuiServices { return hours; } } + + List decodeTags(dynamic tags) { + List result = []; + if (tags['cardPayments']) result.add(Tags.cardPayments); + if (tags['petFriendly']) result.add(Tags.petFriendly); + if (tags['glutenFree']) result.add(Tags.glutenFree); + if (tags['vegan']) result.add(Tags.vegan); + if (tags['vegetarian']) result.add(Tags.vegetarian); + if (tags['alcohol']) result.add(Tags.alcohol); + if (tags['delivery']) result.add(Tags.delivery); + return result; + } } // DATA TYPES @@ -312,7 +336,7 @@ class Restaurant { String imgUrl; MenuiWorkingHours workingHours; String description; - MenuiTags tags; + List tags; MenuiLinks links; String phone; List categories; @@ -450,19 +474,6 @@ class MenuiLinks { MenuiLinks(this.facebook, this.instagram, this.www); } -class MenuiTags { - bool cardPayments; - bool petFriendly; - bool glutenFree; - bool vegan; - bool vegetarian; - bool alcohol; - bool delivery; - - MenuiTags(this.cardPayments, this.petFriendly, this.glutenFree, this.vegan, - this.vegetarian, this.alcohol, this.delivery); -} - class MenuiWorkingHours { String pn; String wt; diff --git a/lib/settings.dart b/lib/settings.dart index 76ae242..606fe41 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter/material.dart'; import 'package:package_info/package_info.dart'; @@ -86,33 +88,70 @@ class MenuiSettings { } } - // ADD DISH TO ORDER --- TODO - void addToOrder(String id) async { + // DECODE ORDER + List decodeOrder(String orderJson) { + print(orderJson); + final List decoded = jsonDecode(orderJson); + List order = []; + decoded.forEach((item) => { + order.add(new OrderItem( + id: item['id'], + quantity: item['quantity'], + price: item['price'], + priceName: item['priceName'])) + }); + return order; + } + + // ENCODE ORDER + String encodeOrder(List order) { + return jsonEncode(order); + } + + // ADD DISH TO ORDER + void addToOrder(OrderItem item) async { final settings = await SharedPreferences.getInstance(); if (settings.containsKey('order')) { - List order = settings.getStringList('order'); - order.add(id); + String rawOrder = settings.getString('order'); + List order = decodeOrder(rawOrder); + order.add(item); + String encodedOrder = encodeOrder(order); + settings.setString('order', encodedOrder); } else { - final List order = new List(); - order.add(id); + final List order = new List(); + order.add(item); + String encodedOrder = encodeOrder(order); + settings.setString('order', encodedOrder); } } + // REMOVE FROM ORDER + void removeFromOrder(int index) async { + final settings = await SharedPreferences.getInstance(); + String rawOrder = settings.getString('order'); + List order = decodeOrder(rawOrder); + order.removeAt(index); + String encodedOrder = encodeOrder(order); + settings.setString('order', encodedOrder); + } + // GET ORDER - Future> getOrder() async { + Future> getOrder() async { final settings = await SharedPreferences.getInstance(); if (settings.containsKey('order')) { - List order = settings.getStringList('order'); + String rawOrder = settings.getString('order'); + List order = decodeOrder(rawOrder); return order; } else { - return new List(); + return new List(); } } // CLEAR ORDER void clearOrder() async { final settings = await SharedPreferences.getInstance(); - settings.setStringList('order', new List()); + String cleanOrder = encodeOrder(new List()); + settings.setString('order', cleanOrder); } // ADD TO FAVORITES (OR REMOVE) @@ -211,7 +250,7 @@ void showSettings(BuildContext context, MenuiSettings settings) async { ), onTap: () { Navigator.pop(context); - showRadiusSelectionDialog(context, settings); + showRadiusSelectionDialog(context, settings, () {}); }), ListTile( title: Text( @@ -390,13 +429,14 @@ void showAppInfoDialog(BuildContext context) async { // SELECT RADIUS void showRadiusSelectionDialog( - BuildContext context, MenuiSettings settings) async { + BuildContext context, MenuiSettings settings, Function onSaved) async { final int currentRadius = await settings.getRadius(); showDialog( context: context, builder: (BuildContext context) { return RadiusSlider( initialValue: currentRadius.toDouble(), + onSaved: onSaved, ); }); } @@ -411,7 +451,9 @@ Color getOptionColor(targetOption, thisOption) { class RadiusSlider extends StatefulWidget { final double initialValue; - RadiusSlider({Key key, @required this.initialValue}) : super(key: key); + final Function onSaved; + RadiusSlider({Key key, @required this.initialValue, this.onSaved}) + : super(key: key); @override _RadiusSliderState createState() => @@ -449,6 +491,9 @@ class _RadiusSliderState extends State { onPressed: () async { final MenuiSettings settings = new MenuiSettings(); settings.setRadius(sliderValue.toInt()); + if (widget.onSaved != null) { + widget.onSaved(); + } Navigator.pop(context); }, child: const Text( @@ -470,3 +515,21 @@ class _RadiusSliderState extends State { } } } + +class OrderItem { + final int quantity; + final String price; + final String priceName; + final String id; + + OrderItem({this.id, this.price, this.priceName, this.quantity}); + + Map toJson() { + return { + "quantity": quantity, + "price": price, + "priceName": priceName, + "id": id + }; + } +}