From 454c6c718df4a3a3eb58934623b8789b44a14939 Mon Sep 17 00:00:00 2001 From: MapleSyyrup Date: Mon, 15 Mar 2021 18:02:37 +0800 Subject: [PATCH 1/2] Adding authentication for users - For the first time user, the user will be asked to sign up - For the exiting users after their email is authenticated, they will be able to login anytime --- lib/main.dart | 30 ++-- lib/models/constants.dart | 4 + lib/models/router.dart | 3 + lib/providers/auth.dart | 66 ++++++++ lib/screens/auth_screen.dart | 281 +++++++++++++++++++++++++++++++++++ pubspec.lock | 36 ++--- 6 files changed, 390 insertions(+), 30 deletions(-) create mode 100644 lib/providers/auth.dart create mode 100644 lib/screens/auth_screen.dart diff --git a/lib/main.dart b/lib/main.dart index 2ae875b..e154a2f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,12 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:myshop_app/providers/auth.dart'; import 'package:myshop_app/providers/orders.dart'; +import 'package:myshop_app/screens/auth_screen.dart'; +import 'package:myshop_app/screens/products_overview_screen.dart'; import 'package:provider/provider.dart'; import './models/custom_colors.dart'; import './models/router.dart'; import './providers/cart.dart'; import './providers/products_provider.dart'; -import './screens/products_overview_screen.dart'; void main() => runApp(MyApp()); @@ -15,22 +17,26 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiProvider( providers: [ + ChangeNotifierProvider(create: (ctx) => Auth()), ChangeNotifierProvider(create: (ctx) => ProductsProvider()), ChangeNotifierProvider(create: (ctx) => Cart()), ChangeNotifierProvider(create: (ctx) => Orders()), ], - child: MaterialApp( - title: 'MyShop', - theme: ThemeData( - primarySwatch: primaryTheme, - accentColor: accentTheme, - canvasColor: canvasTheme, - fontFamily: 'Lato', - ), + child: Consumer( + builder: (context, auth, _) => MaterialApp( + title: 'MyShop', + theme: ThemeData( + primarySwatch: primaryTheme, + accentColor: accentTheme, + canvasColor: canvasTheme, + fontFamily: 'Lato', + ), - ///First screen to show - initialRoute: ProductsOverviewScreen.routeName, - onGenerateRoute: Routers.generateRoute, + ///First screen to show + initialRoute: auth.isAuth ? ProductsOverviewScreen.routeName : AuthScreen.routeName, + // home: auth.isAuth ? ProductsOverviewScreen() : AuthScreen(), + onGenerateRoute: Routers.generateRoute, + ), ), ); } diff --git a/lib/models/constants.dart b/lib/models/constants.dart index 847ec12..14c0461 100644 --- a/lib/models/constants.dart +++ b/lib/models/constants.dart @@ -1,3 +1,7 @@ class Constants { + static const url = 'https://shop-app-4ed38-default-rtdb.firebaseio.com'; + // String authUrl = + // 'https://identitytoolkit.googleapis.com/v1/accounts:$urlSegment?key=AIzaSyDDS9QGht-1JD8bUjcqP-0BQ0cgibbacAQ'; + // static const signInUrl = 'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=AIzaSyDDS9QGht-1JD8bUjcqP-0BQ0cgibbacAQ'; } diff --git a/lib/models/router.dart b/lib/models/router.dart index 7a5d5d1..ff5a64f 100644 --- a/lib/models/router.dart +++ b/lib/models/router.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:myshop_app/screens/auth_screen.dart'; import '../screens/cart_screen.dart'; import '../screens/edit_product_screen.dart'; @@ -16,6 +17,8 @@ Arguments are used to pass the information to that route class Routers { static Route generateRoute(RouteSettings setting) { switch (setting.name) { + case AuthScreen.routeName: + return MaterialPageRoute(builder: (BuildContext context) => AuthScreen()); case ProductsOverviewScreen.routeName: return MaterialPageRoute(builder: (BuildContext context) => ProductsOverviewScreen()); case ProductDetailScreen.routeName: diff --git a/lib/providers/auth.dart b/lib/providers/auth.dart new file mode 100644 index 0000000..a80217d --- /dev/null +++ b/lib/providers/auth.dart @@ -0,0 +1,66 @@ +import 'dart:convert'; + +import 'package:flutter/widgets.dart'; +import 'package:http/http.dart' as http; +import 'package:myshop_app/models/http_exception.dart'; + +class Auth with ChangeNotifier { + String _token; + DateTime _expiryDate; + String _userId; + + bool get isAuth { + return token != null; + } + + String get token { + if (_expiryDate != null && _expiryDate.isAfter(DateTime.now()) && _token != null) { + return _token; + } + return null; + } + + Future _authenticate(String email, String password, String urlSegment) async { + final url = + 'https://identitytoolkit.googleapis.com/v1/accounts:$urlSegment?key=AIzaSyDDS9QGht-1JD8bUjcqP-0BQ0cgibbacAQ'; + + try { + final response = await http.post( + url, + body: json.encode( + { + 'email': email, + 'password': password, + 'returnSecureToken': true, + }, + ), + ); + final dynamic responseData = json.decode(response.body); + if (responseData['error'] != null) { + throw HttpException((responseData['error']['message']).toString()); + } + _token = responseData['idToken'].toString(); + _userId = responseData['localId'].toString(); + _expiryDate = DateTime.now().add( + Duration( + seconds: int.parse( + responseData['expiresIn'].toString(), + ), + ), + ); + notifyListeners(); + } catch (error) { + rethrow; + } + + // print(json.decode(response.body)); + } + + Future signUp(String email, String password) async { + return _authenticate(email, password, 'signUp'); + } + + Future login(String email, String password) async { + return _authenticate(email, password, 'signInWithPassword'); + } +} diff --git a/lib/screens/auth_screen.dart b/lib/screens/auth_screen.dart new file mode 100644 index 0000000..12e845f --- /dev/null +++ b/lib/screens/auth_screen.dart @@ -0,0 +1,281 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:myshop_app/models/http_exception.dart'; +import 'package:myshop_app/providers/auth.dart'; +import 'package:myshop_app/screens/products_overview_screen.dart'; +import 'package:provider/provider.dart'; + +enum AuthMode { Signup, Login } + +class AuthScreen extends StatelessWidget { + static const routeName = '/auth'; + + @override + Widget build(BuildContext context) { + final deviceSize = MediaQuery.of(context).size; + // final transformConfig = Matrix4.rotationZ(-8 * pi / 180); + // transformConfig.translate(-10.0); + return Scaffold( + // resizeToAvoidBottomInset: false, + body: Stack( + children: [ + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color.fromRGBO(215, 117, 255, 1).withOpacity(0.5), + Color.fromRGBO(255, 188, 117, 1).withOpacity(0.9), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + stops: const [0, 1], + ), + ), + ), + SingleChildScrollView( + child: SizedBox( + height: deviceSize.height, + width: deviceSize.width, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + // crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible( + child: Container( + margin: EdgeInsets.only(bottom: 20.0), + padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 94.0), + transform: Matrix4.rotationZ(-8 * pi / 180)..translate(-10.0), + // ..translate(-10.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.deepOrange.shade900, + boxShadow: [ + BoxShadow( + blurRadius: 8, + color: Colors.black26, + offset: Offset(0, 2), + ) + ], + ), + child: Text( + 'MyShop', + style: TextStyle( + color: Theme.of(context).accentTextTheme.headline6.color, + fontSize: 50, + fontFamily: 'Anton', + fontWeight: FontWeight.normal, + ), + ), + ), + ), + Flexible( + flex: deviceSize.width > 600 ? 2 : 1, + child: AuthCard(), + ), + ], + ), + ), + ), + ], + ), + ); + } +} + +class AuthCard extends StatefulWidget { + const AuthCard({ + Key key, + }) : super(key: key); + + @override + _AuthCardState createState() => _AuthCardState(); +} + +class _AuthCardState extends State { + final GlobalKey _formKey = GlobalKey(); + AuthMode _authMode = AuthMode.Login; + final Map _authData = { + 'email': '', + 'password': '', + }; + var _isLoading = false; + final _passwordController = TextEditingController(); + + void _showErrorDialog(String message) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('An error occured!'), + content: Text(message), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text('Okay')) + ], + )); + } + + Future _submit() async { + if (!_formKey.currentState.validate()) { + // Invalid! + return; + } + _formKey.currentState.save(); + setState(() { + _isLoading = true; + }); + + try { + if (_authMode == AuthMode.Login) { + // Log user in + await Provider.of(context, listen: false).login( + _authData['email'], + _authData['password'], + ); + } else { + // Sign user up + await Provider.of(context, listen: false).signUp( + _authData['email'], + _authData['password'], + ); + } + // Navigator.of(context).pushReplacementNamed(ProductsOverviewScreen.routeName); + } on HttpException catch (error) { + var errorMessage = 'Authentication failed'; + if (error.toString().contains('EMAIL_EXISTS')) { + errorMessage = 'This email address is already in use'; + } else if (error.toString().contains('INVALID_EMAIL')) { + errorMessage = 'This is not a valid email address'; + } else if (error.toString().contains('WEAK_PASSWORD')) { + errorMessage = 'This password is too weak'; + } else if (error.toString().contains('EMAIL_NOT_FOUND')) { + errorMessage = 'Could not find a user with that email'; + } else if (error.toString().contains('INVALID_PASSWORD')) { + errorMessage = 'Invalid password'; + } + _showErrorDialog(errorMessage); + } catch (error) { + const errorMessage = 'Could not authenticate you. Please try again later'; + _showErrorDialog(errorMessage); + } + + setState(() { + _isLoading = false; + }); + } + + void _switchAuthMode() { + if (_authMode == AuthMode.Login) { + setState(() { + _authMode = AuthMode.Signup; + }); + } else { + setState(() { + _authMode = AuthMode.Login; + }); + } + } + + @override + Widget build(BuildContext context) { + final deviceSize = MediaQuery.of(context).size; + return Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + elevation: 8.0, + child: Container( + height: _authMode == AuthMode.Signup ? 320 : 260, + constraints: BoxConstraints(minHeight: _authMode == AuthMode.Signup ? 320 : 260), + width: deviceSize.width * 0.75, + padding: EdgeInsets.all(16.0), + child: Form( + key: _formKey, + child: SingleChildScrollView( + child: Column( + children: [ + TextFormField( + decoration: InputDecoration(labelText: 'E-Mail'), + keyboardType: TextInputType.emailAddress, + validator: (value) { + if (value.isEmpty || !value.contains('@')) { + return 'Invalid email!'; + } + return null; + // return null; + }, + onSaved: (value) { + _authData['email'] = value; + }, + ), + TextFormField( + decoration: InputDecoration(labelText: 'Password'), + obscureText: true, + controller: _passwordController, + validator: (value) { + if (value.isEmpty || value.length < 5) { + return 'Password is too short!'; + } else { + return null; + } + }, + onSaved: (value) { + _authData['password'] = value; + }, + ), + if (_authMode == AuthMode.Signup) + TextFormField( + enabled: _authMode == AuthMode.Signup, + decoration: InputDecoration(labelText: 'Confirm Password'), + obscureText: true, + validator: _authMode == AuthMode.Signup + ? (value) { + if (value != _passwordController.text) { + return 'Passwords do not match!'; + } else { + return null; + } + } + : null, + ), + SizedBox( + height: 20, + ), + if (_isLoading) + CircularProgressIndicator() + else + ElevatedButton( + onPressed: _submit, + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30), + ), + padding: EdgeInsets.symmetric(horizontal: 30.0, vertical: 8.0), + textStyle: TextStyle( + color: Theme.of(context).primaryColor, + // textColor: Theme.of(context).primaryTextTheme.button.color, + ), + ), + child: Text(_authMode == AuthMode.Login ? 'LOGIN' : 'SIGN UP'), + ), + TextButton( + onPressed: _switchAuthMode, + style: TextButton.styleFrom( + padding: EdgeInsets.symmetric(horizontal: 30.0, vertical: 4), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + textStyle: TextStyle(color: Theme.of(context).primaryColor), + // textColor: Theme.of(context).primaryColor, + ), + child: Text('${_authMode == AuthMode.Login ? 'SIGNUP' : 'LOGIN'} INSTEAD'), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index cf056ca..cf086b3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,42 +7,42 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety.3" + version: "2.5.0" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.5" + version: "1.1.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.3" + version: "1.2.0" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.5" + version: "1.15.0" cupertino_icons: dependency: "direct main" description: @@ -56,7 +56,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.3" + version: "1.2.0" flutter: dependency: "direct main" description: flutter @@ -101,14 +101,14 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety.3" + version: "0.12.10" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.6" + version: "1.3.0" nested: dependency: transitive description: @@ -122,7 +122,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.3" + version: "1.8.0" pedantic: dependency: transitive description: @@ -148,56 +148,56 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.4" + version: "1.8.0" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.6" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.3" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety.6" + version: "0.2.19" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.5" + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.5" + version: "2.1.0" sdks: dart: ">=2.12.0-0.0 <3.0.0" flutter: ">=1.16.0" From 71b78802cb8f635518a56151809846e6bfae9a78 Mon Sep 17 00:00:00 2001 From: MapleSyyrup Date: Sun, 9 May 2021 22:24:55 +0800 Subject: [PATCH 2/2] Adding authentication to users - Favorited items will be specific for each user - A log out button is added --- lib/main.dart | 27 ++++++++-- lib/models/router.dart | 4 +- lib/providers/auth.dart | 40 +++++++++----- lib/providers/cart.dart | 32 +++++------ lib/providers/orders.dart | 26 +++++---- lib/providers/product.dart | 30 +++++------ lib/providers/products_provider.dart | 75 ++++++++++++++++---------- lib/screens/auth_screen.dart | 18 ++++--- lib/screens/cart_screen.dart | 6 +-- lib/screens/edit_product_screen.dart | 28 +++++----- lib/screens/orders_screen.dart | 2 +- lib/screens/product_detail_screen.dart | 4 +- lib/screens/user_products_screen.dart | 54 +++++++++++-------- lib/widgets/app_drawer.dart | 13 +++++ lib/widgets/badge.dart | 12 ++--- lib/widgets/cart_item.dart | 18 +++---- lib/widgets/order_item.dart | 4 +- lib/widgets/product_item.dart | 9 +++- lib/widgets/products_grid.dart | 4 +- lib/widgets/user_product_item.dart | 8 +-- pubspec.lock | 16 +++--- pubspec.yaml | 21 +++----- 22 files changed, 262 insertions(+), 189 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index e154a2f..99116ae 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,9 +18,27 @@ class MyApp extends StatelessWidget { return MultiProvider( providers: [ ChangeNotifierProvider(create: (ctx) => Auth()), - ChangeNotifierProvider(create: (ctx) => ProductsProvider()), + ChangeNotifierProxyProvider( + update: (ctx, auth, previousProducts) { + return ProductsProvider(auth.token, auth.userId, previousProducts == null ? [] : previousProducts.items); + }, + // ProductsProvider(auth.token ), + create: (_) => ProductsProvider( + null, + null, + [], + ), + ), ChangeNotifierProvider(create: (ctx) => Cart()), - ChangeNotifierProvider(create: (ctx) => Orders()), + ChangeNotifierProxyProvider( + create: (_) => Orders( + null, + null, + [], + ), + update: (ctx, auth, previousOrder) => + Orders(auth.token, auth.userId, previousOrder == null ? [] : previousOrder.orders)), + // ChangeNotifierProvider(create: (ctx) => Orders()), ], child: Consumer( builder: (context, auth, _) => MaterialApp( @@ -33,11 +51,12 @@ class MyApp extends StatelessWidget { ), ///First screen to show - initialRoute: auth.isAuth ? ProductsOverviewScreen.routeName : AuthScreen.routeName, - // home: auth.isAuth ? ProductsOverviewScreen() : AuthScreen(), + initialRoute: firstScreen(auth), onGenerateRoute: Routers.generateRoute, ), ), ); } + + String firstScreen(Auth auth) => auth.isAuth ? ProductsOverviewScreen.routeName : AuthScreen.routeName; } diff --git a/lib/models/router.dart b/lib/models/router.dart index ff5a64f..5f938b3 100644 --- a/lib/models/router.dart +++ b/lib/models/router.dart @@ -22,7 +22,7 @@ class Routers { case ProductsOverviewScreen.routeName: return MaterialPageRoute(builder: (BuildContext context) => ProductsOverviewScreen()); case ProductDetailScreen.routeName: - final argument = setting.arguments as String; + final argument = setting.arguments as String?; return MaterialPageRoute( builder: (BuildContext context) => ProductDetailScreen(productDetailArgs: argument)); case CartScreen.routeName: @@ -32,7 +32,7 @@ class Routers { case UserProductsScreen.routeName: return MaterialPageRoute(builder: (BuildContext context) => UserProductsScreen()); case EditProductScreen.routeName: - final args = setting.arguments as EditProductArguments; + final args = setting.arguments as EditProductArguments?; return MaterialPageRoute( builder: (BuildContext context) => EditProductScreen(editProductarguments: args)); default: diff --git a/lib/providers/auth.dart b/lib/providers/auth.dart index a80217d..09337a7 100644 --- a/lib/providers/auth.dart +++ b/lib/providers/auth.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'package:flutter/widgets.dart'; @@ -5,24 +6,29 @@ import 'package:http/http.dart' as http; import 'package:myshop_app/models/http_exception.dart'; class Auth with ChangeNotifier { - String _token; - DateTime _expiryDate; - String _userId; + String? _token; + DateTime? _expiryDate; + String? _userId; + Timer? _authTimer; bool get isAuth { return token != null; } - String get token { - if (_expiryDate != null && _expiryDate.isAfter(DateTime.now()) && _token != null) { + String? get token { + if (_expiryDate != null && _expiryDate!.isAfter(DateTime.now()) && _token != null) { return _token; } return null; } - Future _authenticate(String email, String password, String urlSegment) async { - final url = - 'https://identitytoolkit.googleapis.com/v1/accounts:$urlSegment?key=AIzaSyDDS9QGht-1JD8bUjcqP-0BQ0cgibbacAQ'; + String? get userId { + return _userId; + } + + Future _authenticate(String? email, String? password, String urlSegment) async { + final url = Uri.parse( + 'https://identitytoolkit.googleapis.com/v1/accounts:$urlSegment?key=AIzaSyDDS9QGht-1JD8bUjcqP-0BQ0cgibbacAQ'); try { final response = await http.post( @@ -48,19 +54,29 @@ class Auth with ChangeNotifier { ), ), ); + notifyListeners(); } catch (error) { rethrow; } - - // print(json.decode(response.body)); } - Future signUp(String email, String password) async { + Future signUp(String? email, String? password) async { return _authenticate(email, password, 'signUp'); } - Future login(String email, String password) async { + Future login(String? email, String? password) async { return _authenticate(email, password, 'signInWithPassword'); } + + void logout() { + _token = null; + _userId = null; + _expiryDate = null; + if (_authTimer != null) { + _authTimer?.cancel(); + _authTimer = null; + } + notifyListeners(); + } } diff --git a/lib/providers/cart.dart b/lib/providers/cart.dart index 36323d9..6ec51b1 100644 --- a/lib/providers/cart.dart +++ b/lib/providers/cart.dart @@ -3,23 +3,23 @@ import 'package:flutter/foundation.dart'; class CartItem { final String id; final String title; - final int quantity; - final double price; + final int? quantity; + final double? price; CartItem({ - @required this.id, - @required this.title, - @required this.quantity, - @required this.price, + required this.id, + required this.title, + required this.quantity, + required this.price, }); } class Cart with ChangeNotifier { ///Map for cart items - Map _items = {}; + Map _items = {}; ///getter of _items - Map get items => {..._items}; + Map get items => {..._items}; ///Counts the number of _items in the cart int get itemCount => _items.length; @@ -28,15 +28,15 @@ class Cart with ChangeNotifier { double get totalAmount { var total = 0.0; _items.forEach((key, cartItem) { - total += cartItem.price * cartItem.quantity; + total += cartItem.price! * cartItem.quantity!; }); return total; } ///Function for adding items in the cart void addItem( - String productId, - double price, + String? productId, + double? price, String title, ) { if (_items.containsKey(productId)) { @@ -48,7 +48,7 @@ class Cart with ChangeNotifier { id: existingCartItem.id, title: existingCartItem.title, price: existingCartItem.price, - quantity: existingCartItem.quantity + 1, + quantity: existingCartItem.quantity! + 1, )); } else { ///if there is no existing productId, a new item will be added @@ -68,25 +68,25 @@ class Cart with ChangeNotifier { } ///Removes an item in the cart - void removeItem(String productId) { + void removeItem(String? productId) { _items.remove(productId); notifyListeners(); } ///This function is used by the snackbar when the UNDO button is pressed - void removeSingleItem(String productId) { + void removeSingleItem(String? productId) { if (!_items.containsKey(productId)) { ///If the productId is not present in the cart items, it will return nothing return; } - if (_items[productId].quantity > 1) { + if (_items[productId]!.quantity! > 1) { ///If the productId is present in the cart items, when the UNDO button is pressed, it will deduct the recently added item _items.update(productId, (existingCartItem) { return CartItem( id: existingCartItem.id, title: existingCartItem.title, price: existingCartItem.price, - quantity: existingCartItem.quantity - 1, + quantity: existingCartItem.quantity! - 1, ); }); } else { diff --git a/lib/providers/orders.dart b/lib/providers/orders.dart index 79f7a0b..3291c97 100644 --- a/lib/providers/orders.dart +++ b/lib/providers/orders.dart @@ -8,21 +8,25 @@ import './cart.dart'; class OrderItem { final String id; - final double amount; + final double? amount; final List products; final DateTime dateTime; OrderItem({ - @required this.id, - @required this.amount, - @required this.products, - @required this.dateTime, + required this.id, + required this.amount, + required this.products, + required this.dateTime, }); } ///List of _orders class Orders with ChangeNotifier { List _orders = []; + final String? authToken; + final String? userId; + + Orders(this.authToken, this.userId, this._orders); ///getter of _orders List get orders { @@ -30,11 +34,11 @@ class Orders with ChangeNotifier { } Future fetchAndSetOrders() async { - const url = '${Constants.url}/orders.json'; + final url = Uri.parse('${Constants.url}/orders/$userId.json?auth=$authToken'); final List loadedOrders = []; final response = await http.get(url); - final extractedData = json.decode(response.body) as Map; + final extractedData = json.decode(response.body) as Map?; if (extractedData == null) { return; } @@ -42,15 +46,15 @@ class Orders with ChangeNotifier { loadedOrders.add( OrderItem( id: orderId, - amount: orderData['amount'] as double, + amount: orderData['amount'] as double?, dateTime: DateTime.parse(orderData['dateTime'].toString()), products: (orderData['products'] as List) .map( (dynamic item) => CartItem( id: item['id'].toString(), title: item['title'].toString(), - quantity: item['quantity'] as int, - price: item['price'] as double, + quantity: item['quantity'] as int?, + price: item['price'] as double?, ), ) .toList(), @@ -63,7 +67,7 @@ class Orders with ChangeNotifier { ///Function for adding an order Future addOrder(List cartProducts, double total) async { - const url = '${Constants.url}/orders.json'; + final url = Uri.parse('${Constants.url}/orders/$userId.json?auth=$authToken'); final timestamp = DateTime.now(); try { final response = await http.post(url, diff --git a/lib/providers/product.dart b/lib/providers/product.dart index 29e0626..d67562f 100644 --- a/lib/providers/product.dart +++ b/lib/providers/product.dart @@ -5,19 +5,19 @@ import 'package:http/http.dart' as http; import 'package:myshop_app/models/constants.dart'; class Product with ChangeNotifier { - final String id; + final String? id; final String title; final String description; - final double price; + final double? price; final String imageUrl; - bool isFavorite; + bool isFavorite = false; Product({ - @required this.id, - @required this.title, - @required this.description, - @required this.price, - @required this.imageUrl, + required this.id, + required this.title, + required this.description, + required this.price, + required this.imageUrl, this.isFavorite = false, }); @@ -27,20 +27,16 @@ class Product with ChangeNotifier { } ///If the favorite button is pressed, the item will be added as favorite - Future toggleFavoriteStatus() async { + Future toggleFavoriteStatus(String? token, String? userId) async { final oldStatus = isFavorite; final newValue = !isFavorite; - final url = '${Constants.url}/products/$id.json'; + final url = Uri.parse('${Constants.url}/userFavorites/$userId/$id.json?auth=$token'); try { - final response = await http.patch( + final response = await http.put( url, - body: json.encode({'isFavorite': !isFavorite}), + body: json.encode(newValue), ); - if (response.statusCode >= 400) { - _setFavValue(oldStatus); - } else { - _setFavValue(newValue); - } + _setFavValue(response.statusCode >= 400 ? oldStatus : newValue); } catch (error) { _setFavValue(oldStatus); } diff --git a/lib/providers/products_provider.dart b/lib/providers/products_provider.dart index 4641c38..c16c6f8 100644 --- a/lib/providers/products_provider.dart +++ b/lib/providers/products_provider.dart @@ -11,6 +11,15 @@ import 'product.dart'; class ProductsProvider with ChangeNotifier { List _items = []; + final String? authToken; + final String? userId; + + ProductsProvider( + this.authToken, + this.userId, + this._items, + ); + ///getter of _items List get items { return [..._items]; @@ -20,31 +29,40 @@ class ProductsProvider with ChangeNotifier { List get favoriteItems => _items.where((prodItem) => prodItem.isFavorite).toList(); ///Returns the first product id that is the same with id - Product findById(String id) => _items.firstWhere((prod) => prod.id == id); + Product findById(String? id) => _items.firstWhere((prod) => prod.id == id); - Future fetchAndSetProducts() async { - const url = '${Constants.url}/products.json'; - try { - final response = await http.get(url); - final extractedData = json.decode(response.body) as Map; - if (extractedData == null) { - return; + Future fetchAndSetProducts([bool filterByUser = false]) async { + final filterString = filterByUser ? 'orderBy="creatorId"&equalTo="$userId"' : ''; + if (authToken != null) { + var url = Uri.parse('${Constants.url}/products.json?auth=$authToken&$filterString'); + try { + final response = await http.get(url); + final extractedData = json.decode(response.body) as Map?; + if (extractedData == null) { + return; + } + + url = Uri.parse('${Constants.url}/userFavorites/$userId.json?auth=$authToken'); + final favoriteResponse = await http.get(url); + final favoriteData = json.decode(favoriteResponse.body) as Map?; + final List loadedProducts = []; + extractedData.forEach((String prodId, dynamic prodData) { + final favorite = favoriteData != null ? favoriteData[prodId] as bool? : false; + + loadedProducts.add(Product( + id: prodId, + title: prodData['title'].toString(), + description: prodData['description'].toString(), + price: prodData['price'] as double?, + isFavorite: favorite == true, + imageUrl: prodData['imageUrl'].toString(), + )); + }); + _items = loadedProducts; + notifyListeners(); + } catch (error) { + rethrow; } - final List loadedProducts = []; - extractedData.forEach((String prodId, dynamic prodData) { - loadedProducts.add(Product( - id: prodId, - title: prodData['title'].toString(), - description: prodData['description'].toString(), - price: prodData['price'] as double, - isFavorite: prodData['isFavorite'] as bool, - imageUrl: prodData['imageUrl'].toString(), - )); - }); - _items = loadedProducts; - notifyListeners(); - } catch (error) { - rethrow; } } @@ -54,8 +72,7 @@ class ProductsProvider with ChangeNotifier { final description = product.description; final imageUrl = product.imageUrl; final price = product.price; - final isFavorite = product.isFavorite; - const url = '${Constants.url}/products.json'; + final url = Uri.parse('${Constants.url}/products.json?auth=$authToken'); try { final response = await http.post( url, @@ -64,7 +81,7 @@ class ProductsProvider with ChangeNotifier { 'description': description, 'imageUrl': imageUrl, 'price': price, - 'isFavorite': isFavorite, + 'creatorId': userId, }), ); final newProduct = Product( @@ -86,7 +103,7 @@ class ProductsProvider with ChangeNotifier { final prodIndex = _items.indexWhere((prod) => prod.id == product.id); if (prodIndex >= 0) { - final urlProdId = '${Constants.url}/products/${product.id}.json'; + final urlProdId = Uri.parse('${Constants.url}/products/${product.id}.json?auth=$authToken'); await http.patch(urlProdId, body: json.encode({ 'title': product.title, @@ -100,8 +117,8 @@ class ProductsProvider with ChangeNotifier { } ///Deletes a product in the list and in the database - Future deleteProduct(String id) async { - final urlId = '${Constants.url}/products/$id.json'; + Future deleteProduct(String? id) async { + final urlId = Uri.parse('${Constants.url}/products/$id.json?auth=$authToken'); final response = await http.delete(urlId); if (response.statusCode >= 400) { throw HttpException('Could not delete product.'); diff --git a/lib/screens/auth_screen.dart b/lib/screens/auth_screen.dart index 12e845f..61c3add 100644 --- a/lib/screens/auth_screen.dart +++ b/lib/screens/auth_screen.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:myshop_app/models/http_exception.dart'; import 'package:myshop_app/providers/auth.dart'; import 'package:myshop_app/screens/products_overview_screen.dart'; +// import 'package:myshop_app/screens/products_overview_screen.dart'; import 'package:provider/provider.dart'; enum AuthMode { Signup, Login } @@ -61,7 +62,7 @@ class AuthScreen extends StatelessWidget { child: Text( 'MyShop', style: TextStyle( - color: Theme.of(context).accentTextTheme.headline6.color, + color: Theme.of(context).accentTextTheme.headline6!.color, fontSize: 50, fontFamily: 'Anton', fontWeight: FontWeight.normal, @@ -85,7 +86,7 @@ class AuthScreen extends StatelessWidget { class AuthCard extends StatefulWidget { const AuthCard({ - Key key, + Key? key, }) : super(key: key); @override @@ -95,7 +96,7 @@ class AuthCard extends StatefulWidget { class _AuthCardState extends State { final GlobalKey _formKey = GlobalKey(); AuthMode _authMode = AuthMode.Login; - final Map _authData = { + final Map _authData = { 'email': '', 'password': '', }; @@ -119,11 +120,11 @@ class _AuthCardState extends State { } Future _submit() async { - if (!_formKey.currentState.validate()) { + if (!_formKey.currentState!.validate()) { // Invalid! return; } - _formKey.currentState.save(); + _formKey.currentState!.save(); setState(() { _isLoading = true; }); @@ -135,6 +136,7 @@ class _AuthCardState extends State { _authData['email'], _authData['password'], ); + // Navigator.of(context).pushReplacementNamed(ProductsOverviewScreen.routeName); } else { // Sign user up await Provider.of(context, listen: false).signUp( @@ -142,7 +144,7 @@ class _AuthCardState extends State { _authData['password'], ); } - // Navigator.of(context).pushReplacementNamed(ProductsOverviewScreen.routeName); + Navigator.of(context).pushReplacementNamed(ProductsOverviewScreen.routeName); } on HttpException catch (error) { var errorMessage = 'Authentication failed'; if (error.toString().contains('EMAIL_EXISTS')) { @@ -201,7 +203,7 @@ class _AuthCardState extends State { decoration: InputDecoration(labelText: 'E-Mail'), keyboardType: TextInputType.emailAddress, validator: (value) { - if (value.isEmpty || !value.contains('@')) { + if (value!.isEmpty || !value.contains('@')) { return 'Invalid email!'; } return null; @@ -216,7 +218,7 @@ class _AuthCardState extends State { obscureText: true, controller: _passwordController, validator: (value) { - if (value.isEmpty || value.length < 5) { + if (value!.isEmpty || value.length < 5) { return 'Password is too short!'; } else { return null; diff --git a/lib/screens/cart_screen.dart b/lib/screens/cart_screen.dart index 4e6de3d..ce5ba53 100644 --- a/lib/screens/cart_screen.dart +++ b/lib/screens/cart_screen.dart @@ -12,7 +12,7 @@ class CartScreen extends StatelessWidget { Widget build(BuildContext context) { final cart = Provider.of(context); final primaryColor = Theme.of(context).primaryColor; - final primaryTextTheme = Theme.of(context).primaryTextTheme.headline1.color; + final primaryTextTheme = Theme.of(context).primaryTextTheme.headline1!.color; return Scaffold( appBar: AppBar(title: Text('Your Cart')), body: Column( @@ -66,8 +66,8 @@ class CartScreen extends StatelessWidget { class OrderButton extends StatefulWidget { const OrderButton({ - Key key, - @required this.cart, + Key? key, + required this.cart, }) : super(key: key); final Cart cart; diff --git a/lib/screens/edit_product_screen.dart b/lib/screens/edit_product_screen.dart index e9361ad..2064b11 100644 --- a/lib/screens/edit_product_screen.dart +++ b/lib/screens/edit_product_screen.dart @@ -7,9 +7,9 @@ import '../providers/products_provider.dart'; class EditProductScreen extends StatefulWidget { static const routeName = '/edit-product'; - final EditProductArguments editProductarguments; + final EditProductArguments? editProductarguments; - const EditProductScreen({@required this.editProductarguments}); + const EditProductScreen({required this.editProductarguments}); @override _EditProductScreenState createState() => _EditProductScreenState(); @@ -17,9 +17,9 @@ class EditProductScreen extends StatefulWidget { ///Class for arguments that the navigator needs class EditProductArguments { - final String productId; + final String? productId; - EditProductArguments({@required this.productId}); + EditProductArguments({required this.productId}); } class _EditProductScreenState extends State { @@ -101,13 +101,13 @@ class _EditProductScreenState extends State { ///Function for saving the product details Future _saveForm() async { - final isValid = _formKey.currentState.validate(); + final isValid = _formKey.currentState!.validate(); ///Id the inputs of the user are not valid, it will not be saved if (!isValid) { return; } - _formKey.currentState.save(); + _formKey.currentState!.save(); setState(() => _isLoading = true); final product = Product( @@ -147,8 +147,8 @@ class _EditProductScreenState extends State { } ///Validator condition for ImageUrl - String validatorImageUrl(String value) { - if (value.isEmpty) { + String? validatorImageUrl(String? value) { + if (value!.isEmpty) { return 'Please enter an image URL'; } if (value.startsWith('http') && !value.startsWith('https')) { @@ -161,8 +161,8 @@ class _EditProductScreenState extends State { } ///Validator condition for Description - String validatorDescription(String value) { - if (value.isEmpty) { + String? validatorDescription(String? value) { + if (value!.isEmpty) { return 'Please enter a description'; } if (value.length < 10) { @@ -172,8 +172,8 @@ class _EditProductScreenState extends State { } ///Validator condition for Price - String validatorPrice(String value) { - if (value.isEmpty) { + String? validatorPrice(String? value) { + if (value!.isEmpty) { return 'Please enter a price.'; } if (double.tryParse(value) == null) { @@ -186,8 +186,8 @@ class _EditProductScreenState extends State { } ///Validator condition for Title - String validatorTitle(String value) { - return value.isEmpty ? 'Error description example' : null; + String? validatorTitle(String? value) { + return value!.isEmpty ? 'Error description example' : null; } @override diff --git a/lib/screens/orders_screen.dart b/lib/screens/orders_screen.dart index 9f80baf..3d26194 100644 --- a/lib/screens/orders_screen.dart +++ b/lib/screens/orders_screen.dart @@ -14,7 +14,7 @@ class OrdersScreen extends StatefulWidget { } class _OrdersScreenState extends State { - Future _ordersFuture; + Future? _ordersFuture; Future _obtainOrdersFuture() { return Provider.of(context, listen: false).fetchAndSetOrders(); diff --git a/lib/screens/product_detail_screen.dart b/lib/screens/product_detail_screen.dart index 205297a..1095852 100644 --- a/lib/screens/product_detail_screen.dart +++ b/lib/screens/product_detail_screen.dart @@ -6,9 +6,9 @@ import '../providers/products_provider.dart'; ///Screen for the product details class ProductDetailScreen extends StatelessWidget { static const routeName = '/product-detail'; - final String productDetailArgs; + final String? productDetailArgs; - const ProductDetailScreen({@required this.productDetailArgs}); + const ProductDetailScreen({required this.productDetailArgs}); @override Widget build(BuildContext context) { diff --git a/lib/screens/user_products_screen.dart b/lib/screens/user_products_screen.dart index d379ace..8c115a2 100644 --- a/lib/screens/user_products_screen.dart +++ b/lib/screens/user_products_screen.dart @@ -12,12 +12,13 @@ class UserProductsScreen extends StatelessWidget { ///Refreshes the list of products from the database Future _refreshProducts(BuildContext context) async { - await Provider.of(context, listen: false).fetchAndSetProducts(); + await Provider.of(context, listen: false).fetchAndSetProducts(true); } @override Widget build(BuildContext context) { - final productsData = Provider.of(context); + // print('rebuilding...'); + // final productsData = Provider.of(context); return Scaffold( appBar: AppBar( title: const Text('Your Products'), @@ -29,27 +30,36 @@ class UserProductsScreen extends StatelessWidget { ], ), drawer: AppDrawer(), - body: RefreshIndicator( - onRefresh: () => _refreshProducts(context), - child: Padding( - padding: const EdgeInsets.all(8), - child: ListView.builder( - itemCount: productsData.items.length, - itemBuilder: (_, i) { - final productsDt = productsData.items[i]; - return Column( - children: [ - UserProductItem( - id: productsDt.id, - title: productsDt.title, - imageUrl: productsDt.imageUrl, + body: FutureBuilder( + future: _refreshProducts(context), + builder: (ctx, snapshot) => snapshot.connectionState == ConnectionState.waiting + ? Center( + child: CircularProgressIndicator(), + ) + : RefreshIndicator( + onRefresh: () => _refreshProducts(context), + child: Consumer( + builder: (ctx, productsData, _) => Padding( + padding: const EdgeInsets.all(8), + child: ListView.builder( + itemCount: productsData.items.length, + itemBuilder: (_, i) { + final productsDt = productsData.items[i]; + return Column( + children: [ + UserProductItem( + id: productsDt.id, + title: productsDt.title, + imageUrl: productsDt.imageUrl, + ), + Divider(), + ], + ); + }, + ), ), - Divider(), - ], - ); - }, - ), - ), + ), + ), ), ); } diff --git a/lib/widgets/app_drawer.dart b/lib/widgets/app_drawer.dart index 74492bf..3231e16 100644 --- a/lib/widgets/app_drawer.dart +++ b/lib/widgets/app_drawer.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:myshop_app/providers/auth.dart'; +import 'package:myshop_app/screens/auth_screen.dart'; +import 'package:provider/provider.dart'; import '../screens/orders_screen.dart'; import '../screens/products_overview_screen.dart'; @@ -33,6 +36,16 @@ class AppDrawer extends StatelessWidget { title: Text('Manage Products'), onTap: () => Navigator.of(context).popAndPushNamed(UserProductsScreen.routeName), ), + Divider(), + ListTile( + leading: Icon(Icons.exit_to_app), + title: Text('Logout'), + onTap: () { + // Navigator.of(context).pop(); + Provider.of(context, listen: false).logout(); + Navigator.of(context).popAndPushNamed(AuthScreen.routeName); + }, + ), ], ), ); diff --git a/lib/widgets/badge.dart b/lib/widgets/badge.dart index 2d4956d..660b213 100644 --- a/lib/widgets/badge.dart +++ b/lib/widgets/badge.dart @@ -3,22 +3,22 @@ import 'package:flutter/material.dart'; ///Shows total number of items in the shopping cart class Badge extends StatelessWidget { const Badge({ - Key key, - @required this.child, - @required this.value, + Key? key, + required this.child, + required this.value, this.color, }) : super(key: key); - final Widget child; + final Widget? child; final String value; - final Color color; + final Color? color; @override Widget build(BuildContext context) { return Stack( alignment: Alignment.center, children: [ - child, + child!, Positioned( right: 8, top: 8, diff --git a/lib/widgets/cart_item.dart b/lib/widgets/cart_item.dart index 7fe6703..6532bc3 100644 --- a/lib/widgets/cart_item.dart +++ b/lib/widgets/cart_item.dart @@ -4,17 +4,17 @@ import '../providers/cart.dart'; class CartItem extends StatelessWidget { final String id; - final String productId; - final double price; - final int quantity; + final String? productId; + final double? price; + final int? quantity; final String title; const CartItem({ - @required this.id, - @required this.productId, - @required this.price, - @required this.quantity, - @required this.title, + required this.id, + required this.productId, + required this.price, + required this.quantity, + required this.title, }); @override @@ -76,7 +76,7 @@ class CartItem extends StatelessWidget { ), ), title: Text(title), - subtitle: Text('Total: \$${(price * quantity).toStringAsFixed(2)}'), + subtitle: Text('Total: \$${(price! * quantity!).toStringAsFixed(2)}'), trailing: Text('$quantity x'), ), ), diff --git a/lib/widgets/order_item.dart b/lib/widgets/order_item.dart index 8eb5542..d3ba968 100644 --- a/lib/widgets/order_item.dart +++ b/lib/widgets/order_item.dart @@ -6,7 +6,7 @@ import '../providers/orders.dart' as ord; class OrderItem extends StatelessWidget { final ord.OrderItem order; - const OrderItem({@required this.order}); + const OrderItem({required this.order}); ///Shows the order items @override @@ -14,7 +14,7 @@ class OrderItem extends StatelessWidget { return Card( margin: const EdgeInsets.all(5), child: ExpansionTile( - title: Text('\$${order.amount.toStringAsFixed(2)}'), + title: Text('\$${order.amount!.toStringAsFixed(2)}'), subtitle: Text(DateFormat('dd/MM/yyyy hh:mm').format(order.dateTime)), children: [ ...order.products.map( diff --git a/lib/widgets/product_item.dart b/lib/widgets/product_item.dart index 6385af8..37ca016 100644 --- a/lib/widgets/product_item.dart +++ b/lib/widgets/product_item.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:myshop_app/providers/auth.dart'; import 'package:provider/provider.dart'; import '../providers/cart.dart'; @@ -11,6 +12,7 @@ class ProductItem extends StatelessWidget { Widget build(BuildContext context) { final cart = Provider.of(context, listen: false); final accentColor = Theme.of(context).accentColor; + final authData = Provider.of(context, listen: false); return Consumer( builder: (ctx, product, child) => ClipRRect( borderRadius: BorderRadius.circular(10), @@ -18,8 +20,11 @@ class ProductItem extends StatelessWidget { footer: GridTileBar( backgroundColor: Colors.black87, leading: IconButton( - icon: Icon(product.isFavorite ? Icons.favorite : Icons.favorite_border), - onPressed: () => product.toggleFavoriteStatus(), + icon: Icon(product.isFavorite == true ? Icons.favorite : Icons.favorite_border), + onPressed: () => product.toggleFavoriteStatus( + authData.token, + authData.userId, + ), color: accentColor, ), title: Text( diff --git a/lib/widgets/products_grid.dart b/lib/widgets/products_grid.dart index f5817ab..932c1b4 100644 --- a/lib/widgets/products_grid.dart +++ b/lib/widgets/products_grid.dart @@ -7,14 +7,14 @@ import './product_item.dart'; /// Gridview builder for the products class ProductsGrid extends StatelessWidget { - final bool showFavs; + final bool? showFavs; const ProductsGrid({this.showFavs}); @override Widget build(BuildContext context) { final productsData = Provider.of(context); - final products = showFavs ? productsData.favoriteItems : productsData.items; + final products = showFavs! ? productsData.favoriteItems : productsData.items; return GridView.builder( padding: const EdgeInsets.all(10.0), diff --git a/lib/widgets/user_product_item.dart b/lib/widgets/user_product_item.dart index 2b8bccc..324f181 100644 --- a/lib/widgets/user_product_item.dart +++ b/lib/widgets/user_product_item.dart @@ -6,14 +6,14 @@ import '../screens/edit_product_screen.dart'; ///Shows the user product items class UserProductItem extends StatelessWidget { - final String id; + final String? id; final String title; final String imageUrl; const UserProductItem({ - @required this.title, - @required this.imageUrl, - @required this.id, + required this.title, + required this.imageUrl, + required this.id, }); @override diff --git a/pubspec.lock b/pubspec.lock index cf086b3..cd3b8ff 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -73,28 +73,28 @@ packages: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.2" + version: "0.13.1" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.4" + version: "4.0.0" intl: dependency: "direct main" description: name: intl url: "https://pub.dartlang.org" source: hosted - version: "0.16.1" + version: "0.17.0" lint: dependency: "direct dev" description: name: lint url: "https://pub.dartlang.org" source: hosted - version: "1.5.1" + version: "1.5.3" matcher: dependency: transitive description: @@ -115,7 +115,7 @@ packages: name: nested url: "https://pub.dartlang.org" source: hosted - version: "0.0.4" + version: "1.0.0" path: dependency: transitive description: @@ -129,14 +129,14 @@ packages: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.10.0" + version: "1.11.0" provider: dependency: "direct main" description: name: provider url: "https://pub.dartlang.org" source: hosted - version: "4.3.2+3" + version: "5.0.0" sky_engine: dependency: transitive description: flutter @@ -199,5 +199,5 @@ packages: source: hosted version: "2.1.0" sdks: - dart: ">=2.12.0-0.0 <3.0.0" + dart: ">=2.12.0 <3.0.0" flutter: ">=1.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index e417ff6..13de614 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,29 +18,23 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.1 + cupertino_icons: ^1.0.2 flutter: sdk: flutter - http: ^0.12.2 - intl: ^0.16.1 - provider: ^4.3.2+3 - - - + http: ^0.13.1 + intl: ^0.17.0 + provider: ^5.0.0 dev_dependencies: flutter_test: sdk: flutter - lint: ^1.5.1 + lint: ^1.5.3 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec - # The following section is specific to Flutter. flutter: @@ -53,13 +47,10 @@ flutter: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg - # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. - # For details regarding adding assets from package dependencies, see # https://flutter.dev/assets-and-images/#from-packages - # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a