diff --git a/e-commerce/config/route.dart b/e-commerce/config/route.dart new file mode 100644 index 0000000..e8c08b7 --- /dev/null +++ b/e-commerce/config/route.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_ecommerce_app/src/pages/mainPage.dart'; + +class Routes { + static Map getRoute() { + return { + '/': (_) => MainPage(), + // '/detail': (_) => ProductDetailPage() + }; + } +} diff --git a/e-commerce/demo.txt b/e-commerce/demo.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/e-commerce/demo.txt @@ -0,0 +1 @@ + diff --git a/e-commerce/home_page.dart b/e-commerce/home_page.dart new file mode 100644 index 0000000..6689aee --- /dev/null +++ b/e-commerce/home_page.dart @@ -0,0 +1,141 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_ecommerce_app/src/model/data.dart'; +import 'package:flutter_ecommerce_app/src/themes/light_color.dart'; +import 'package:flutter_ecommerce_app/src/themes/theme.dart'; +import 'package:flutter_ecommerce_app/src/widgets/product_card.dart'; +import 'package:flutter_ecommerce_app/src/widgets/product_icon.dart'; +import 'package:flutter_ecommerce_app/src/widgets/extentions.dart'; + +class MyHomePage extends StatefulWidget { + MyHomePage({Key key, this.title}) : super(key: key); + + final String title; + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + Widget _icon(IconData icon, {Color color = LightColor.iconColor}) { + return Container( + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(13)), + color: Theme.of(context).backgroundColor, + boxShadow: AppTheme.shadow), + child: Icon( + icon, + color: color, + ), + ).ripple(() {}, borderRadius: BorderRadius.all(Radius.circular(13))); + } + + Widget _categoryWidget() { + return Container( + margin: EdgeInsets.symmetric(vertical: 10), + width: AppTheme.fullWidth(context), + height: 80, + child: ListView( + scrollDirection: Axis.horizontal, + children: AppData.categoryList + .map( + (category) => ProductIcon( + model: category, + onSelected: (model) { + setState(() { + AppData.categoryList.forEach((item) { + item.isSelected = false; + }); + model.isSelected = true; + }); + }, + ), + ) + .toList(), + ), + ); + } + + Widget _productWidget() { + return Container( + margin: EdgeInsets.symmetric(vertical: 10), + width: AppTheme.fullWidth(context), + height: AppTheme.fullWidth(context) * .7, + child: GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 1, + childAspectRatio: 4 / 3, + mainAxisSpacing: 30, + crossAxisSpacing: 20), + padding: EdgeInsets.only(left: 20), + scrollDirection: Axis.horizontal, + children: AppData.productList + .map( + (product) => ProductCard( + product: product, + onSelected: (model) { + setState(() { + AppData.productList.forEach((item) { + item.isSelected = false; + }); + model.isSelected = true; + }); + }, + ), + ) + .toList(), + ), + ); + } + + Widget _search() { + return Container( + margin: AppTheme.padding, + child: Row( + children: [ + Expanded( + child: Container( + height: 40, + alignment: Alignment.center, + decoration: BoxDecoration( + color: LightColor.lightGrey.withAlpha(100), + borderRadius: BorderRadius.all(Radius.circular(10))), + child: TextField( + decoration: InputDecoration( + border: InputBorder.none, + hintText: "Search Products", + hintStyle: TextStyle(fontSize: 12), + contentPadding: + EdgeInsets.only(left: 10, right: 10, bottom: 0, top: 5), + prefixIcon: Icon(Icons.search, color: Colors.black54)), + ), + ), + ), + SizedBox(width: 20), + _icon(Icons.filter_list, color: Colors.black54) + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Container( + height: MediaQuery.of(context).size.height - 210, + child: SingleChildScrollView( + physics: BouncingScrollPhysics(), + dragStartBehavior: DragStartBehavior.down, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + _search(), + _categoryWidget(), + _productWidget(), + ], + ), + ), + ); + } +} diff --git a/e-commerce/login_page.dart b/e-commerce/login_page.dart new file mode 100644 index 0000000..0bfe2d2 --- /dev/null +++ b/e-commerce/login_page.dart @@ -0,0 +1,145 @@ +import 'package:flutter/material.dart'; + +void main() => runApp( + MaterialApp( + debugShowCheckedModeBanner: false, + home: HomePage(), + ) +); + +class HomePage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: SingleChildScrollView( + child: Container( + child: Column( + children: [ + Container( + height: 400, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/background.png'), + fit: BoxFit.fill + ) + ), + child: Stack( + children: [ + Positioned( + left: 30, + width: 80, + height: 200, + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/light-1.png') + ) + ), + ), + ), + Positioned( + left: 140, + width: 80, + height: 150, + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/light-2.png') + ) + ), + )), + Positioned( + right: 40, + top: 40, + width: 80, + height: 150, + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/clock.png') + ) + ), + )), + Positioned( + child: Container( + margin: EdgeInsets.only(top: 50), + child: Center( + child: Text("Login", style: TextStyle(color: Colors.white, fontSize: 40, fontWeight: FontWeight.bold),), + ), + )), + ], + ), + ), + Padding( + padding: EdgeInsets.all(30.0), + child: Column( + children: [ + Container( + padding: EdgeInsets.all(5), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Color.fromRGBO(143, 148, 251, .2), + blurRadius: 20.0, + offset: Offset(0, 10) + ) + ] + ), + child: Column( + children: [ + Container( + padding: EdgeInsets.all(8.0), + decoration: BoxDecoration( + border: Border(bottom: BorderSide(color: Colors.grey[100])) + ), + child: TextField( + decoration: InputDecoration( + border: InputBorder.none, + hintText: "Email or Phone number", + hintStyle: TextStyle(color: Colors.grey[400]) + ), + ), + ), + Container( + padding: EdgeInsets.all(8.0), + child: TextField( + decoration: InputDecoration( + border: InputBorder.none, + hintText: "Password", + hintStyle: TextStyle(color: Colors.grey[400]) + ), + ), + ) + ], + ), + ), + SizedBox(height: 30,), + Container( + height: 50, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + gradient: LinearGradient( + colors: [ + Color.fromRGBO(143, 148, 251, 1), + Color.fromRGBO(143, 148, 251, .6), + ] + ) + ), + child: Center( + child: Text("Login", style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),), + ), + ), + SizedBox(height: 70,), + ], + ), + ) + ], + ), + ), + ) + ); + } +} \ No newline at end of file diff --git a/e-commerce/mainPage.dart b/e-commerce/mainPage.dart new file mode 100644 index 0000000..3f5eccf --- /dev/null +++ b/e-commerce/mainPage.dart @@ -0,0 +1,166 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_ecommerce_app/src/pages/home_page.dart'; +import 'package:flutter_ecommerce_app/src/pages/shopping_cart_page.dart'; +import 'package:flutter_ecommerce_app/src/themes/light_color.dart'; +import 'package:flutter_ecommerce_app/src/themes/theme.dart'; +import 'package:flutter_ecommerce_app/src/widgets/BottomNavigationBar/bottom_navigation_bar.dart'; +import 'package:flutter_ecommerce_app/src/widgets/title_text.dart'; +import 'package:flutter_ecommerce_app/src/widgets/extentions.dart'; + +class MainPage extends StatefulWidget { + MainPage({Key key, this.title}) : super(key: key); + + final String title; + + @override + _MainPageState createState() => _MainPageState(); +} + +class _MainPageState extends State { + bool isHomePageSelected = true; + Widget _appBar() { + return Container( + padding: AppTheme.padding, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + RotatedBox( + quarterTurns: 4, + child: _icon(Icons.sort, color: Colors.black54), + ), + ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(13)), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).backgroundColor, + boxShadow: [ + BoxShadow( + color: Color(0xfff8f8f8), + blurRadius: 10, + spreadRadius: 10), + ], + ), + child: Image.asset("assets/user.png"), + ), + ).ripple(() {}, borderRadius: BorderRadius.all(Radius.circular(13))) + ], + ), + ); + } + + Widget _icon(IconData icon, {Color color = LightColor.iconColor}) { + return Container( + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(13)), + color: Theme.of(context).backgroundColor, + boxShadow: AppTheme.shadow), + child: Icon( + icon, + color: color, + ), + ).ripple(() {}, borderRadius: BorderRadius.all(Radius.circular(13))); + } + + Widget _title() { + return Container( + margin: AppTheme.padding, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TitleText( + text: isHomePageSelected ? 'Our' : 'Shopping', + fontSize: 27, + fontWeight: FontWeight.w400, + ), + TitleText( + text: isHomePageSelected ? 'Products' : 'Cart', + fontSize: 27, + fontWeight: FontWeight.w700, + ), + ], + ), + Spacer(), + !isHomePageSelected + ? Container( + padding: EdgeInsets.all(10), + child: Icon( + Icons.delete_outline, + color: LightColor.orange, + ), + ).ripple(() {}, borderRadius: BorderRadius.all(Radius.circular(13))) + : SizedBox() + ], + )); + } + + void onBottomIconPressed(int index) { + if (index == 0 || index == 1) { + setState(() { + isHomePageSelected = true; + }); + } else { + setState(() { + isHomePageSelected = false; + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: Stack( + fit: StackFit.expand, + children: [ + SingleChildScrollView( + child: Container( + height: AppTheme.fullHeight(context) - 50, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xfffbfbfb), + Color(0xfff7f7f7), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _appBar(), + _title(), + Expanded( + child: AnimatedSwitcher( + duration: Duration(milliseconds: 300), + switchInCurve: Curves.easeInToLinear, + switchOutCurve: Curves.easeOutBack, + child: isHomePageSelected + ? MyHomePage() + : Align( + alignment: Alignment.topCenter, + child: ShoppingCartPage(), + ), + ), + ) + ], + ), + ), + ), + Positioned( + bottom: 0, + right: 0, + child: CustomBottomNavigationBar( + onIconPresedCallback: onBottomIconPressed, + ), + ) + ], + ), + ), + ); + } +} diff --git a/e-commerce/model/category.dart b/e-commerce/model/category.dart new file mode 100644 index 0000000..9134dbc --- /dev/null +++ b/e-commerce/model/category.dart @@ -0,0 +1,7 @@ +class Category{ + int id ; + String name ; + String image ; + bool isSelected ; + Category({this.id,this.name,this.isSelected = false,this.image}); +} \ No newline at end of file diff --git a/e-commerce/model/data.dart b/e-commerce/model/data.dart new file mode 100644 index 0000000..4d29912 --- /dev/null +++ b/e-commerce/model/data.dart @@ -0,0 +1,80 @@ +import 'package:flutter_ecommerce_app/src/model/category.dart'; +import 'package:flutter_ecommerce_app/src/model/product.dart'; + +class AppData { + static List productList = [ + Product( + id: 1, + name: 'Nike Air Max 200', + price: 240.00, + isSelected: true, + isliked: false, + image: 'assets/shooe_tilt_1.png', + category: "Trending Now"), + Product( + id: 2, + name: 'Nike Air Max 97', + price: 220.00, + isliked: false, + image: 'assets/shoe_tilt_2.png', + category: "Trending Now"), + ]; + static List cartList = [ + Product( + id: 1, + name: 'Nike Air Max 200', + price: 240.00, + isSelected: true, + isliked: false, + image: 'assets/small_tilt_shoe_1.png', + category: "Trending Now"), + Product( + id: 2, + name: 'Nike Air Max 97', + price: 190.00, + isliked: false, + image: 'assets/small_tilt_shoe_2.png', + category: "Trending Now"), + Product( + id: 1, + name: 'Nike Air Max 92607', + price: 220.00, + isliked: false, + image: 'assets/small_tilt_shoe_3.png', + category: "Trending Now"), + Product( + id: 2, + name: 'Nike Air Max 200', + price: 240.00, + isSelected: true, + isliked: false, + image: 'assets/small_tilt_shoe_1.png', + category: "Trending Now"), + // Product( + // id:1, + // name: 'Nike Air Max 97', + // price: 190.00, + // isliked: false, + // image: 'assets/small_tilt_shoe_2.png', + // category: "Trending Now"), + ]; + static List categoryList = [ + Category(), + Category( + id: 1, + name: "Sneakers", + image: 'assets/shoe_thumb_2.png', + isSelected: true), + Category(id: 2, name: "Jacket", image: 'assets/jacket.png'), + Category(id: 3, name: "Watch", image: 'assets/watch.png'), + Category(id: 4, name: "Watch", image: 'assets/watch.png'), + ]; + static List showThumbnailList = [ + "assets/shoe_thumb_5.png", + "assets/shoe_thumb_1.png", + "assets/shoe_thumb_4.png", + "assets/shoe_thumb_3.png", + ]; + static String description = + "Clean lines, versatile and timeless—the people shoe returns with the Nike Air Max 90. Featuring the same iconic Waffle sole, stitched overlays and classic TPU accents you come to love, it lets you walk among the pantheon of Air. ßNothing as fly, nothing as comfortable, nothing as proven. The Nike Air Max 90 stays true to its OG running roots with the iconic Waffle sole, stitched overlays and classic TPU details. Classic colours celebrate your fresh look while Max Air cushioning adds comfort to the journey."; +} diff --git a/e-commerce/model/product.dart b/e-commerce/model/product.dart new file mode 100644 index 0000000..f3e065a --- /dev/null +++ b/e-commerce/model/product.dart @@ -0,0 +1,10 @@ +class Product{ + int id; + String name ; + String category ; + String image ; + double price ; + bool isliked ; + bool isSelected ; + Product({this.id,this.name, this.category, this.price, this.isliked,this.isSelected = false,this.image}); +} \ No newline at end of file diff --git a/e-commerce/product_detail.dart b/e-commerce/product_detail.dart new file mode 100644 index 0000000..5526a19 --- /dev/null +++ b/e-commerce/product_detail.dart @@ -0,0 +1,403 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_ecommerce_app/src/model/data.dart'; +import 'package:flutter_ecommerce_app/src/themes/light_color.dart'; +import 'package:flutter_ecommerce_app/src/themes/theme.dart'; +import 'package:flutter_ecommerce_app/src/widgets/title_text.dart'; +import 'package:flutter_ecommerce_app/src/widgets/extentions.dart'; + +class ProductDetailPage extends StatefulWidget { + ProductDetailPage({Key key}) : super(key: key); + + @override + _ProductDetailPageState createState() => _ProductDetailPageState(); +} + +class _ProductDetailPageState extends State + with TickerProviderStateMixin { + AnimationController controller; + Animation animation; + @override + void initState() { + super.initState(); + controller = + AnimationController(vsync: this, duration: Duration(milliseconds: 300)); + animation = Tween(begin: 0, end: 1).animate( + CurvedAnimation(parent: controller, curve: Curves.easeInToLinear)); + controller.forward(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + bool isLiked = true; + Widget _appBar() { + return Container( + padding: AppTheme.padding, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _icon( + Icons.arrow_back_ios, + color: Colors.black54, + size: 15, + padding: 12, + isOutLine: true, + onPressed: () { + Navigator.of(context).pop(); + }, + ), + _icon(isLiked ? Icons.favorite : Icons.favorite_border, + color: isLiked ? LightColor.red : LightColor.lightGrey, + size: 15, + padding: 12, + isOutLine: false, onPressed: () { + setState(() { + isLiked = !isLiked; + }); + }), + ], + ), + ); + } + + Widget _icon( + IconData icon, { + Color color = LightColor.iconColor, + double size = 20, + double padding = 10, + bool isOutLine = false, + Function onPressed, + }) { + return Container( + height: 40, + width: 40, + padding: EdgeInsets.all(padding), + // margin: EdgeInsets.all(padding), + decoration: BoxDecoration( + border: Border.all( + color: LightColor.iconColor, + style: isOutLine ? BorderStyle.solid : BorderStyle.none), + borderRadius: BorderRadius.all(Radius.circular(13)), + color: + isOutLine ? Colors.transparent : Theme.of(context).backgroundColor, + boxShadow: [ + BoxShadow( + color: Color(0xfff8f8f8), + blurRadius: 5, + spreadRadius: 10, + offset: Offset(5, 5)), + ], + ), + child: Icon(icon, color: color, size: size), + ).ripple(() { + if (onPressed != null) { + onPressed(); + } + }, borderRadius: BorderRadius.all(Radius.circular(13))); + } + + Widget _productImage() { + return AnimatedBuilder( + builder: (context, child) { + return AnimatedOpacity( + duration: Duration(milliseconds: 500), + opacity: animation.value, + child: child, + ); + }, + animation: animation, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + TitleText( + text: "AIP", + fontSize: 160, + color: LightColor.lightGrey, + ), + Image.asset('assets/show_1.png') + ], + ), + ); + } + + Widget _categoryWidget() { + return Container( + margin: EdgeInsets.symmetric(vertical: 0), + width: AppTheme.fullWidth(context), + height: 80, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: + AppData.showThumbnailList.map((x) => _thumbnail(x)).toList()), + ); + } + + Widget _thumbnail(String image) { + return AnimatedBuilder( + animation: animation, + // builder: null, + builder: (context, child) => AnimatedOpacity( + opacity: animation.value, + duration: Duration(milliseconds: 500), + child: child, + ), + child: Container( + margin: EdgeInsets.symmetric(horizontal: 10), + child: Container( + height: 40, + width: 50, + decoration: BoxDecoration( + border: Border.all( + color: LightColor.grey, + ), + borderRadius: BorderRadius.all(Radius.circular(13)), + // color: Theme.of(context).backgroundColor, + ), + child: Image.asset(image), + ).ripple(() {}, borderRadius: BorderRadius.all(Radius.circular(13))), + ), + ); + } + + Widget _detailWidget() { + return DraggableScrollableSheet( + maxChildSize: .8, + initialChildSize: .53, + minChildSize: .53, + builder: (context, scrollController) { + return Container( + padding: AppTheme.padding.copyWith(bottom: 0), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(40), + topRight: Radius.circular(40), + ), + color: Colors.white), + child: SingleChildScrollView( + controller: scrollController, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + SizedBox(height: 5), + Container( + alignment: Alignment.center, + child: Container( + width: 50, + height: 5, + decoration: BoxDecoration( + color: LightColor.iconColor, + borderRadius: BorderRadius.all(Radius.circular(10))), + ), + ), + SizedBox(height: 10), + Container( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TitleText(text: "NIKE AIR MAX 200", fontSize: 25), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + TitleText( + text: "\$ ", + fontSize: 18, + color: LightColor.red, + ), + TitleText( + text: "240", + fontSize: 25, + ), + ], + ), + Row( + children: [ + Icon(Icons.star, + color: LightColor.yellowColor, size: 17), + Icon(Icons.star, + color: LightColor.yellowColor, size: 17), + Icon(Icons.star, + color: LightColor.yellowColor, size: 17), + Icon(Icons.star, + color: LightColor.yellowColor, size: 17), + Icon(Icons.star_border, size: 17), + ], + ), + ], + ), + ], + ), + ), + SizedBox( + height: 20, + ), + _availableSize(), + SizedBox( + height: 20, + ), + _availableColor(), + SizedBox( + height: 20, + ), + _description(), + ], + ), + ), + ); + }, + ); + } + + Widget _availableSize() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TitleText( + text: "Available Size", + fontSize: 14, + ), + SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _sizeWidget("US 6"), + _sizeWidget("US 7", isSelected: true), + _sizeWidget("US 8"), + _sizeWidget("US 9"), + ], + ) + ], + ); + } + + Widget _sizeWidget(String text, {bool isSelected = false}) { + return Container( + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + border: Border.all( + color: LightColor.iconColor, + style: !isSelected ? BorderStyle.solid : BorderStyle.none), + borderRadius: BorderRadius.all(Radius.circular(13)), + color: + isSelected ? LightColor.orange : Theme.of(context).backgroundColor, + ), + child: TitleText( + text: text, + fontSize: 16, + color: isSelected ? LightColor.background : LightColor.titleTextColor, + ), + ).ripple(() {}, borderRadius: BorderRadius.all(Radius.circular(13))); + } + + Widget _availableColor() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TitleText( + text: "Available Size", + fontSize: 14, + ), + SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + _colorWidget(LightColor.yellowColor, isSelected: true), + SizedBox( + width: 30, + ), + _colorWidget(LightColor.lightBlue), + SizedBox( + width: 30, + ), + _colorWidget(LightColor.black), + SizedBox( + width: 30, + ), + _colorWidget(LightColor.red), + SizedBox( + width: 30, + ), + _colorWidget(LightColor.skyBlue), + ], + ) + ], + ); + } + + Widget _colorWidget(Color color, {bool isSelected = false}) { + return CircleAvatar( + radius: 12, + backgroundColor: color.withAlpha(150), + child: isSelected + ? Icon( + Icons.check_circle, + color: color, + size: 18, + ) + : CircleAvatar(radius: 7, backgroundColor: color), + ); + } + + Widget _description() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TitleText( + text: "Available Size", + fontSize: 14, + ), + SizedBox(height: 20), + Text(AppData.description), + ], + ); + } + + FloatingActionButton _flotingButton() { + return FloatingActionButton( + onPressed: () {}, + backgroundColor: LightColor.orange, + child: Icon(Icons.shopping_basket, + color: Theme.of(context).floatingActionButtonTheme.backgroundColor), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: _flotingButton(), + body: SafeArea( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xfffbfbfb), + Color(0xfff7f7f7), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + )), + child: Stack( + children: [ + Column( + children: [ + _appBar(), + _productImage(), + _categoryWidget(), + ], + ), + _detailWidget() + ], + ), + ), + ), + ); + } +} diff --git a/e-commerce/shopping_cart_page.dart b/e-commerce/shopping_cart_page.dart new file mode 100644 index 0000000..e1bf76b --- /dev/null +++ b/e-commerce/shopping_cart_page.dart @@ -0,0 +1,156 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_ecommerce_app/src/model/data.dart'; +import 'package:flutter_ecommerce_app/src/model/product.dart'; +import 'package:flutter_ecommerce_app/src/themes/light_color.dart'; +import 'package:flutter_ecommerce_app/src/themes/theme.dart'; +import 'package:flutter_ecommerce_app/src/widgets/title_text.dart'; + +class ShoppingCartPage extends StatelessWidget { + const ShoppingCartPage({Key key}) : super(key: key); + + Widget _cartItems() { + return Column(children: AppData.cartList.map((x) => _item(x)).toList()); + } + + Widget _item(Product model) { + return Container( + height: 80, + child: Row( + children: [ + AspectRatio( + aspectRatio: 1.2, + child: Stack( + children: [ + Align( + alignment: Alignment.bottomLeft, + child: Container( + height: 70, + width: 70, + child: Stack( + children: [ + Align( + alignment: Alignment.bottomLeft, + child: Container( + decoration: BoxDecoration( + color: LightColor.lightGrey, + borderRadius: BorderRadius.circular(10)), + ), + ), + ], + ), + ), + ), + Positioned( + left: -20, + bottom: -20, + child: Image.asset(model.image), + ) + ], + ), + ), + Expanded( + child: ListTile( + title: TitleText( + text: model.name, + fontSize: 15, + fontWeight: FontWeight.w700, + ), + subtitle: Row( + children: [ + TitleText( + text: '\$ ', + color: LightColor.red, + fontSize: 12, + ), + TitleText( + text: model.price.toString(), + fontSize: 14, + ), + ], + ), + trailing: Container( + width: 35, + height: 35, + alignment: Alignment.center, + decoration: BoxDecoration( + color: LightColor.lightGrey.withAlpha(150), + borderRadius: BorderRadius.circular(10)), + child: TitleText( + text: 'x${model.id}', + fontSize: 12, + ), + ))) + ], + ), + ); + } + + Widget _price() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TitleText( + text: '${AppData.cartList.length} Items', + color: LightColor.grey, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + TitleText( + text: '\$${getPrice()}', + fontSize: 18, + ), + ], + ); + } + + Widget _submitButton(BuildContext context) { + return TextButton( + onPressed: () {}, + style: ButtonStyle( + shape: MaterialStateProperty.all( + RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), + ), + backgroundColor: MaterialStateProperty.all(LightColor.orange), + ), + child: Container( + alignment: Alignment.center, + padding: EdgeInsets.symmetric(vertical: 4), + width: AppTheme.fullWidth(context) * .75, + child: TitleText( + text: 'Next', + color: LightColor.background, + fontWeight: FontWeight.w500, + ), + ), + ); + } + + double getPrice() { + double price = 0; + AppData.cartList.forEach((x) { + price += x.price * x.id; + }); + return price; + } + + @override + Widget build(BuildContext context) { + return Container( + padding: AppTheme.padding, + child: SingleChildScrollView( + child: Column( + children: [ + _cartItems(), + Divider( + thickness: 1, + height: 70, + ), + _price(), + SizedBox(height: 30), + _submitButton(context), + ], + ), + ), + ); + } +} diff --git a/e-commerce/themes/light_color.dart b/e-commerce/themes/light_color.dart new file mode 100644 index 0000000..d87939f --- /dev/null +++ b/e-commerce/themes/light_color.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +class LightColor { + static const Color background = Color(0XFFFFFFFF); + + static const Color titleTextColor = const Color(0xff1d2635); + static const Color subTitleTextColor = const Color(0xff797878); + + static const Color skyBlue = Color(0xff2890c8); + static const Color lightBlue = Color(0xff5c3dff); + + + static const Color orange = Color(0xffE65829); + static const Color red = Color(0xffF72804); + + static const Color lightGrey = Color(0xffE1E2E4); + static const Color grey = Color(0xffA1A3A6); + static const Color darkgrey = Color(0xff747F8F); + + static const Color iconColor = Color(0xffa8a09b); + static const Color yellowColor = Color(0xfffbba01); + + static const Color black = Color(0xff20262C); + static const Color lightblack = Color(0xff5F5F60); +} \ No newline at end of file diff --git a/e-commerce/themes/theme.dart b/e-commerce/themes/theme.dart new file mode 100644 index 0000000..8b3b614 --- /dev/null +++ b/e-commerce/themes/theme.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; + +import 'light_color.dart'; + +class AppTheme { + const AppTheme(); + static ThemeData lightTheme = ThemeData( + backgroundColor: LightColor.background, + primaryColor: LightColor.background, + cardTheme: CardTheme(color: LightColor.background), + textTheme: TextTheme(bodyText1: TextStyle(color: LightColor.black)), + iconTheme: IconThemeData(color: LightColor.iconColor), + bottomAppBarColor: LightColor.background, + dividerColor: LightColor.lightGrey, + primaryTextTheme: + TextTheme(bodyText1: TextStyle(color: LightColor.titleTextColor))); + + static TextStyle titleStyle = + const TextStyle(color: LightColor.titleTextColor, fontSize: 16); + static TextStyle subTitleStyle = + const TextStyle(color: LightColor.subTitleTextColor, fontSize: 12); + + static TextStyle h1Style = + const TextStyle(fontSize: 24, fontWeight: FontWeight.bold); + static TextStyle h2Style = const TextStyle(fontSize: 22); + static TextStyle h3Style = const TextStyle(fontSize: 20); + static TextStyle h4Style = const TextStyle(fontSize: 18); + static TextStyle h5Style = const TextStyle(fontSize: 16); + static TextStyle h6Style = const TextStyle(fontSize: 14); + + static List shadow = [ + BoxShadow(color: Color(0xfff8f8f8), blurRadius: 10, spreadRadius: 15), + ]; + + static EdgeInsets padding = + const EdgeInsets.symmetric(horizontal: 20, vertical: 10); + static EdgeInsets hPadding = const EdgeInsets.symmetric( + horizontal: 10, + ); + + static double fullWidth(BuildContext context) { + return MediaQuery.of(context).size.width; + } + + static double fullHeight(BuildContext context) { + return MediaQuery.of(context).size.height; + } +} diff --git a/e-commerce/widgets/BottomNavigationBar/bottom_curved_Painter.dart b/e-commerce/widgets/BottomNavigationBar/bottom_curved_Painter.dart new file mode 100644 index 0000000..2858cf1 --- /dev/null +++ b/e-commerce/widgets/BottomNavigationBar/bottom_curved_Painter.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_ecommerce_app/src/widgets/BottomNavigationBar/centered_elasticIn_curve.dart'; + +class BackgroundCurvePainter extends CustomPainter { + static const _radiusTop = 50.0; + static const _radiusBottom = 30.0; + static const _horizontalControlTop = 0.6; + static const _horizontalControlBottom = 0.5; + static const _pointControlTop = 0.35; + static const _pointControlBottom = 0.85; + static const _topY = -60.0; + static const _bottomY = 0.0; + static const _topDistance = 0.0; + static const _bottomDistance = 10.0; + + final double _x; + final double _normalizedY; + final Color _color; + + BackgroundCurvePainter(double x, double normalizedY, Color color) + : _x = x, + _normalizedY = normalizedY, + _color = color; + + @override + void paint(canvas, size) { + // Paint two cubic bezier curves using various linear interpolations based off of the `_normalizedY` value + final norm = LinearPointCurve(0.5, 2.0).transform(_normalizedY) / 5; + + final radius = + Tween(begin: _radiusTop, end: _radiusBottom).transform(norm); + // Point colinear to the top edge of the background pane + final anchorControlOffset = Tween( + begin: radius * _horizontalControlTop, + end: radius * _horizontalControlBottom) + .transform(LinearPointCurve(0.5, 0.75).transform(norm)); + // Point that slides up and down depending on distance for the target x position + final dipControlOffset = Tween( + begin: radius * _pointControlTop, end: radius * _pointControlBottom) + .transform(LinearPointCurve(0.5, 0.8).transform(norm)); + final y = Tween(begin: _topY, end: _bottomY) + .transform(LinearPointCurve(0.2, 0.7).transform(norm)); + final dist = Tween(begin: _topDistance, end: _bottomDistance) + .transform(LinearPointCurve(0.5, 0.0).transform(norm)); + final x0 = _x - dist / 2; + final x1 = _x + dist / 2; + + final path = Path() + ..moveTo(0, 0) + ..lineTo(x0 - radius, 0) + ..cubicTo( + x0 - radius + anchorControlOffset, 0, x0 - dipControlOffset, y, x0, y) + ..lineTo(x1, y) + ..cubicTo(x1 + dipControlOffset, y, x1 + radius - anchorControlOffset, 0, + x1 + radius, 0) + ..lineTo(size.width, 0) + ..lineTo(size.width, size.height) + ..lineTo(0, size.height); + + final paint = Paint()..color = _color; + + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(BackgroundCurvePainter oldPainter) { + return _x != oldPainter._x || + _normalizedY != oldPainter._normalizedY || + _color != oldPainter._color; + } +} diff --git a/e-commerce/widgets/BottomNavigationBar/bottom_navigation_bar.dart b/e-commerce/widgets/BottomNavigationBar/bottom_navigation_bar.dart new file mode 100644 index 0000000..d4e9e94 --- /dev/null +++ b/e-commerce/widgets/BottomNavigationBar/bottom_navigation_bar.dart @@ -0,0 +1,176 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_ecommerce_app/src/themes/light_color.dart'; +import 'package:flutter_ecommerce_app/src/widgets/BottomNavigationBar/bottom_curved_Painter.dart'; + +class CustomBottomNavigationBar extends StatefulWidget { + final Function(int) onIconPresedCallback; + CustomBottomNavigationBar({Key key, this.onIconPresedCallback}) + : super(key: key); + + @override + _CustomBottomNavigationBarState createState() => + _CustomBottomNavigationBarState(); +} + +class _CustomBottomNavigationBarState extends State + with TickerProviderStateMixin { + int _selectedIndex = 0; + + AnimationController _xController; + AnimationController _yController; + @override + void initState() { + _xController = AnimationController( + vsync: this, animationBehavior: AnimationBehavior.preserve); + _yController = AnimationController( + vsync: this, animationBehavior: AnimationBehavior.preserve); + + Listenable.merge([_xController, _yController]).addListener(() { + setState(() {}); + }); + + super.initState(); + } + + @override + void didChangeDependencies() { + _xController.value = + _indexToPosition(_selectedIndex) / MediaQuery.of(context).size.width; + _yController.value = 1.0; + + super.didChangeDependencies(); + } + + double _indexToPosition(int index) { + // Calculate button positions based off of their + // index (works with `MainAxisAlignment.spaceAround`) + const buttonCount = 4.0; + final appWidth = MediaQuery.of(context).size.width; + final buttonsWidth = _getButtonContainerWidth(); + final startX = (appWidth - buttonsWidth) / 2; + return startX + + index.toDouble() * buttonsWidth / buttonCount + + buttonsWidth / (buttonCount * 2.0); + } + + @override + void dispose() { + _xController.dispose(); + _yController.dispose(); + super.dispose(); + } + + Widget _icon(IconData icon, bool isEnable, int index) { + return Expanded( + child: InkWell( + borderRadius: BorderRadius.all(Radius.circular(50)), + onTap: () { + _handlePressed(index); + }, + child: AnimatedContainer( + duration: Duration(milliseconds: 500), + alignment: isEnable ? Alignment.topCenter : Alignment.center, + child: AnimatedContainer( + height: isEnable ? 40 : 20, + duration: Duration(milliseconds: 300), + alignment: Alignment.center, + decoration: BoxDecoration( + color: isEnable ? LightColor.orange : Colors.white, + boxShadow: [ + BoxShadow( + color: isEnable ? Color(0xfffeece2) : Colors.white, + blurRadius: 10, + spreadRadius: 5, + offset: Offset(5, 5), + ), + ], + shape: BoxShape.circle), + child: Opacity( + opacity: isEnable ? _yController.value : 1, + child: Icon(icon, + color: isEnable + ? LightColor.background + : Theme.of(context).iconTheme.color), + )), + ), + ), + ); + } + + Widget _buildBackground() { + final inCurve = ElasticOutCurve(0.38); + return CustomPaint( + painter: BackgroundCurvePainter( + _xController.value * MediaQuery.of(context).size.width, + Tween( + begin: Curves.easeInExpo.transform(_yController.value), + end: inCurve.transform(_yController.value), + ).transform(_yController.velocity.sign * 0.5 + 0.5), + Theme.of(context).backgroundColor), + ); + } + + double _getButtonContainerWidth() { + double width = MediaQuery.of(context).size.width; + if (width > 400.0) { + width = 400.0; + } + return width; + } + + void _handlePressed(int index) { + if (_selectedIndex == index || _xController.isAnimating) return; + widget.onIconPresedCallback(index); + setState(() { + _selectedIndex = index; + }); + + _yController.value = 1.0; + _xController.animateTo( + _indexToPosition(index) / MediaQuery.of(context).size.width, + duration: Duration(milliseconds: 620)); + Future.delayed( + Duration(milliseconds: 500), + () { + _yController.animateTo(1.0, duration: Duration(milliseconds: 1200)); + }, + ); + _yController.animateTo(0.0, duration: Duration(milliseconds: 300)); + } + + @override + Widget build(BuildContext context) { + final appSize = MediaQuery.of(context).size; + final height = 60.0; + return Container( + width: appSize.width, + height: 60, + child: Stack( + children: [ + Positioned( + left: 0, + bottom: 0, + width: appSize.width, + height: height - 10, + child: _buildBackground(), + ), + Positioned( + left: (appSize.width - _getButtonContainerWidth()) / 2, + top: 0, + width: _getButtonContainerWidth(), + height: height, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _icon(Icons.home, _selectedIndex == 0, 0), + _icon(Icons.search, _selectedIndex == 1, 1), + _icon(Icons.card_travel, _selectedIndex == 2, 2), + _icon(Icons.favorite_border, _selectedIndex == 3, 3), + ], + ), + ), + ], + ), + ); + } +} diff --git a/e-commerce/widgets/BottomNavigationBar/centered_elasticIn_curve.dart b/e-commerce/widgets/BottomNavigationBar/centered_elasticIn_curve.dart new file mode 100644 index 0000000..d5e7a5f --- /dev/null +++ b/e-commerce/widgets/BottomNavigationBar/centered_elasticIn_curve.dart @@ -0,0 +1,46 @@ +import 'dart:math' as math; + +import 'package:flutter/material.dart'; + +class CenteredElasticOutCurve extends Curve { + final double period; + + CenteredElasticOutCurve([this.period = 0.4]); + + @override + double transform(double x) { + // Bascially just a slightly modified version of the built in ElasticOutCurve + return math.pow(2.0, -10.0 * x) * math.sin(x * 2.0 * math.pi / period) + + 0.5; + } +} + +class CenteredElasticInCurve extends Curve { + final double period; + + CenteredElasticInCurve([this.period = 0.4]); + + @override + double transform(double x) { + // Bascially just a slightly modified version of the built in ElasticInCurve + return -math.pow(2.0, 10.0 * (x - 1.0)) * + math.sin((x - 1.0) * 2.0 * math.pi / period) + + 0.5; + } +} + +class LinearPointCurve extends Curve { + final double pIn; + final double pOut; + + LinearPointCurve(this.pIn, this.pOut); + + @override + double transform(double x) { + // Just a simple bit of linear interpolation math + final lowerScale = pOut / pIn; + final upperScale = (1.0 - pOut) / (1.0 - pIn); + final upperOffset = 1.0 - upperScale; + return x < pIn ? x * lowerScale : x * upperScale + upperOffset; + } +} diff --git a/e-commerce/widgets/customRoute.dart b/e-commerce/widgets/customRoute.dart new file mode 100644 index 0000000..3794e26 --- /dev/null +++ b/e-commerce/widgets/customRoute.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +class CustomRoute extends MaterialPageRoute { + CustomRoute({WidgetBuilder builder, RouteSettings settings}) + : super(builder: builder, settings: settings); + @override + Widget buildTransitions(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + if (settings.name == "MainPage") { + return child; + } + return FadeTransition( + opacity: animation, + child: child, + ); + } +} diff --git a/e-commerce/widgets/extentions.dart b/e-commerce/widgets/extentions.dart new file mode 100644 index 0000000..c1373cd --- /dev/null +++ b/e-commerce/widgets/extentions.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +extension OnPressed on Widget { + Widget ripple(Function onPressed, + {BorderRadiusGeometry borderRadius = + const BorderRadius.all(Radius.circular(5))}) => + Stack( + children: [ + this, + Positioned( + left: 0, + right: 0, + top: 0, + bottom: 0, + child: TextButton( + style: ButtonStyle( + shape: MaterialStateProperty.all( + RoundedRectangleBorder(borderRadius: borderRadius), + )), + onPressed: () { + if (onPressed != null) { + onPressed(); + } + }, + child: Container()), + ) + ], + ); +} diff --git a/e-commerce/widgets/product_card.dart b/e-commerce/widgets/product_card.dart new file mode 100644 index 0000000..3377421 --- /dev/null +++ b/e-commerce/widgets/product_card.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_ecommerce_app/src/model/product.dart'; +import 'package:flutter_ecommerce_app/src/themes/light_color.dart'; +import 'package:flutter_ecommerce_app/src/widgets/title_text.dart'; +import 'package:flutter_ecommerce_app/src/widgets/extentions.dart'; + +class ProductCard extends StatelessWidget { + final Product product; + final ValueChanged onSelected; + ProductCard({Key key, this.product, this.onSelected}) : super(key: key); + +// @override +// _ProductCardState createState() => _ProductCardState(); +// } + +// class _ProductCardState extends State { +// Product product; +// @override +// void initState() { +// product = widget.product; +// super.initState(); +// } + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: LightColor.background, + borderRadius: BorderRadius.all(Radius.circular(20)), + boxShadow: [ + BoxShadow(color: Color(0xfff8f8f8), blurRadius: 15, spreadRadius: 10), + ], + ), + margin: EdgeInsets.symmetric(vertical: !product.isSelected ? 20 : 0), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Stack( + alignment: Alignment.center, + children: [ + Positioned( + left: 0, + top: 0, + child: IconButton( + icon: Icon( + product.isliked ? Icons.favorite : Icons.favorite_border, + color: + product.isliked ? LightColor.red : LightColor.iconColor, + ), + onPressed: () {}, + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox(height: product.isSelected ? 15 : 0), + Expanded( + child: Stack( + alignment: Alignment.center, + children: [ + CircleAvatar( + radius: 40, + backgroundColor: LightColor.orange.withAlpha(40), + ), + Image.asset(product.image) + ], + ), + ), + // SizedBox(height: 5), + TitleText( + text: product.name, + fontSize: product.isSelected ? 16 : 14, + ), + TitleText( + text: product.category, + fontSize: product.isSelected ? 14 : 12, + color: LightColor.orange, + ), + TitleText( + text: product.price.toString(), + fontSize: product.isSelected ? 18 : 16, + ), + ], + ), + ], + ), + ).ripple(() { + Navigator.of(context).pushNamed('/detail'); + onSelected(product); + }, borderRadius: BorderRadius.all(Radius.circular(20))), + ); + } +} diff --git a/e-commerce/widgets/product_icon.dart b/e-commerce/widgets/product_icon.dart new file mode 100644 index 0000000..39f3fef --- /dev/null +++ b/e-commerce/widgets/product_icon.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_ecommerce_app/src/model/category.dart'; +import 'package:flutter_ecommerce_app/src/themes/light_color.dart'; +import 'package:flutter_ecommerce_app/src/themes/theme.dart'; +import 'package:flutter_ecommerce_app/src/widgets/title_text.dart'; +import 'package:flutter_ecommerce_app/src/widgets/extentions.dart'; + +class ProductIcon extends StatelessWidget { + // final String imagePath; + // final String text; + final ValueChanged onSelected; + final Category model; + ProductIcon({Key key, this.model, this.onSelected}) : super(key: key); + + Widget build(BuildContext context) { + return model.id == null + ? Container(width: 5) + : Container( + margin: EdgeInsets.symmetric(horizontal: 10, vertical: 15), + child: Container( + padding: AppTheme.hPadding, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10)), + color: model.isSelected + ? LightColor.background + : Colors.transparent, + border: Border.all( + color: model.isSelected ? LightColor.orange : LightColor.grey, + width: model.isSelected ? 2 : 1, + ), + boxShadow: [ + BoxShadow( + color: model.isSelected ? Color(0xfffbf2ef) : Colors.white, + blurRadius: 10, + spreadRadius: 5, + offset: Offset(5, 5), + ), + ], + ), + child: Row( + children: [ + model.image != null ? Image.asset(model.image) : SizedBox(), + model.name == null + ? Container() + : Container( + child: TitleText( + text: model.name, + fontWeight: FontWeight.w700, + fontSize: 15, + ), + ) + ], + ), + ).ripple( + () { + onSelected(model); + }, + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + ); + } +} diff --git a/e-commerce/widgets/title_text.dart b/e-commerce/widgets/title_text.dart new file mode 100644 index 0000000..3929e35 --- /dev/null +++ b/e-commerce/widgets/title_text.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_ecommerce_app/src/themes/light_color.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class TitleText extends StatelessWidget { + final String text; + final double fontSize; + final Color color; + final FontWeight fontWeight; + const TitleText( + {Key key, + this.text, + this.fontSize = 18, + this.color = LightColor.titleTextColor, + this.fontWeight = FontWeight.w800}) + : super(key: key); + @override + Widget build(BuildContext context) { + return Text(text, + style: GoogleFonts.mulish( + fontSize: fontSize, fontWeight: fontWeight, color: color)); + } +}