diff --git a/assets/images/icons/add.svg b/assets/images/icons/add.svg new file mode 100644 index 0000000..8fa6e3b --- /dev/null +++ b/assets/images/icons/add.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/icons/cart.svg b/assets/images/icons/cart.svg new file mode 100644 index 0000000..2c4f130 --- /dev/null +++ b/assets/images/icons/cart.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/icons/home.svg b/assets/images/icons/home.svg new file mode 100644 index 0000000..8ee81c9 --- /dev/null +++ b/assets/images/icons/home.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/icons/preview.svg b/assets/images/icons/preview.svg new file mode 100644 index 0000000..9235d82 --- /dev/null +++ b/assets/images/icons/preview.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/icons/reviews.svg b/assets/images/icons/reviews.svg new file mode 100644 index 0000000..acdc168 --- /dev/null +++ b/assets/images/icons/reviews.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/icons/search.svg b/assets/images/icons/search.svg new file mode 100644 index 0000000..b7e87e2 --- /dev/null +++ b/assets/images/icons/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/bindings/dashboard.dart b/lib/bindings/dashboard.dart new file mode 100644 index 0000000..cb5a44a --- /dev/null +++ b/lib/bindings/dashboard.dart @@ -0,0 +1,13 @@ +import 'package:book_store_app/controllers/add_controller.dart'; +import 'package:book_store_app/controllers/dashboard.dart'; +import 'package:book_store_app/controllers/home_controller.dart'; +import 'package:get/get.dart'; + +class DashboardBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => DashboardController()); + Get.lazyPut(() => HomeController()); + Get.lazyPut(() => AddController()); + } +} diff --git a/lib/controllers/add_controller.dart b/lib/controllers/add_controller.dart new file mode 100644 index 0000000..57bb207 --- /dev/null +++ b/lib/controllers/add_controller.dart @@ -0,0 +1,49 @@ +import 'package:book_store_app/data/data_source.dart'; +import 'package:book_store_app/data/models/book.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class AddController extends GetxController { + late final GlobalKey formKey; + late final TextEditingController nameController; + late final TextEditingController authorController; + late final TextEditingController priceController; + late final TextEditingController imageLinkController; + late final TextEditingController descriptionController; + + @override + void onInit() { + super.onInit(); + formKey = GlobalKey(); + nameController = TextEditingController(); + authorController = TextEditingController(); + priceController = TextEditingController(); + imageLinkController = TextEditingController(); + descriptionController = TextEditingController(); + } + + @override + void onClose() { + nameController.dispose(); + authorController.dispose(); + priceController.dispose(); + imageLinkController.dispose(); + descriptionController.dispose(); + } + + addBook() { + if (formKey.currentState!.validate()) { + DataSource.localBooks.add( + Book( + title: nameController.text, + author: authorController.text, + price: double.parse(priceController.text), + cover: imageLinkController.text, + description: descriptionController.text, + ), + ); + Get.closeCurrentSnackbar(); + Get.snackbar('Book has been added successfully', ''); + } + } +} diff --git a/lib/controllers/dashboard.dart b/lib/controllers/dashboard.dart new file mode 100644 index 0000000..f4353d2 --- /dev/null +++ b/lib/controllers/dashboard.dart @@ -0,0 +1,33 @@ +import 'package:book_store_app/views/addition/add_view.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class DashboardController extends GetxController { + var tabIndex = 0.obs; + late PageController pageController; + + @override + void onInit() { + super.onInit(); + pageController = PageController(); + } + + @override + void onClose() { + super.onClose(); + pageController.dispose(); + } + + void changeTabIndex(int index) { + if (index != 2) { + tabIndex.value = index; + pageController.animateToPage( + index, + duration: const Duration(milliseconds: 300), + curve: Curves.linear, + ); + } else { + Get.to(const AddView()); + } + } +} diff --git a/lib/controllers/home_controller.dart b/lib/controllers/home_controller.dart new file mode 100644 index 0000000..4a86642 --- /dev/null +++ b/lib/controllers/home_controller.dart @@ -0,0 +1,18 @@ +import 'package:get/get.dart'; + +class HomeController extends GetxController { + @override + void onInit() { + super.onInit(); + } + + @override + void onReady() { + super.onReady(); + } + + @override + void onClose() { + super.onClose(); + } +} diff --git a/lib/core/theme.dart b/lib/core/theme.dart new file mode 100644 index 0000000..13e09ea --- /dev/null +++ b/lib/core/theme.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +class AppTheme { + const AppTheme._(); + + // Light Theme Colors. + static Color lightBackgroundColor = const Color(0xffFDFDFD); + static Color lightPrimaryColor = const Color(0xff06070D); + static Color lightSecondaryColor = lightPrimaryColor.withOpacity(0.5); + static Color lightTertiaryColor = const Color(0xff9C9EA8); + static Color lightErrorColor = const Color(0xffe63946); + + static final lightTheme = ThemeData( + brightness: Brightness.light, + backgroundColor: lightBackgroundColor, + errorColor: lightErrorColor, + primaryColor: lightPrimaryColor, + textTheme: _textTheme, + colorScheme: _colorScheme, + visualDensity: VisualDensity.adaptivePlatformDensity, + ); + + static final _textTheme = const TextTheme().copyWith(); + + static final _colorScheme = ColorScheme.light( + secondary: lightSecondaryColor, + tertiary: lightTertiaryColor, + ); +} diff --git a/lib/data/data_source.dart b/lib/data/data_source.dart new file mode 100644 index 0000000..8247847 --- /dev/null +++ b/lib/data/data_source.dart @@ -0,0 +1,83 @@ +import 'package:get/get.dart'; + +import 'models/book.dart'; + +class DataSource { + static RxList localBooks = [ + Book( + title: "A Tale of Two Cities", + author: "Charles Dickens", + cover: "https://api.lorem.space/image/book?w=150&h=220&hash=8B7BCDC1", + rate: 5.0, + price: 99.99, + description: + "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of de Finibus Bonorum et Malorum (The Extremes of Good and Evil) by Cicero, written in 45 BC.", + ), + Book( + title: "The Little Prince", + author: "Antoine de Saint-Exupéry", + cover: "https://api.lorem.space/image/book?w=150&h=220&hash=8B7BCDC2", + rate: 4.3, + price: 46.99, + description: + "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of de Finibus Bonorum et Malorum (The Extremes of Good and Evil) by Cicero, written in 45 BC.", + ), + Book( + title: "Harry Potter and the Philosopher's Stone", + author: "J.K.Rowling", + cover: "https://api.lorem.space/image/book?w=150&h=220&hash=8B7BCDC3", + rate: 3.0, + price: 64.99, + description: + "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of de Finibus Bonorum et Malorum (The Extremes of Good and Evil) by Cicero, written in 45 BC.", + ), + Book( + title: "Mockingjay", + author: "Suzanne Collins", + cover: "https://api.lorem.space/image/book?w=150&h=220&hash=8B7BCDC4", + rate: 4.0, + price: 52.99, + description: + "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of de Finibus Bonorum et Malorum (The Extremes of Good and Evil) by Cicero, written in 45 BC.", + ), + Book( + title: "Me Before You", + author: "Jojo Moyes", + cover: "https://api.lorem.space/image/book?w=150&h=220&hash=8B7BCDC5", + rate: 4.9, + price: 30.99, + description: + "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of de Finibus Bonorum et Malorum (The Extremes of Good and Evil) by Cicero, written in 45 BC.", + ), + Book( + title: "Harry Potter and the Philosopher's Stone", + author: "J.K.Rowling", + cover: "https://api.lorem.space/image/book?w=150&h=220&hash=8B7BCDC6", + rate: 3.0, + price: 64.99, + description: + "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of de Finibus Bonorum et Malorum (The Extremes of Good and Evil) by Cicero, written in 45 BC.", + ), + ].obs; + + static RxList cartItems = [ + Book( + title: "Me Before You", + author: "Jojo Moyes", + cover: "https://api.lorem.space/image/book?w=150&h=220&hash=8B7BCDC5", + rate: 4.9, + price: 30.99, + description: + "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of de Finibus Bonorum et Malorum (The Extremes of Good and Evil) by Cicero, written in 45 BC.", + ), + Book( + title: "Harry Potter and the Philosopher's Stone", + author: "J.K.Rowling", + cover: "https://api.lorem.space/image/book?w=150&h=220&hash=8B7BCDC6", + rate: 3.0, + price: 64.99, + description: + "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of de Finibus Bonorum et Malorum (The Extremes of Good and Evil) by Cicero, written in 45 BC.", + ), + ].obs; +} diff --git a/lib/data/models/book.dart b/lib/data/models/book.dart new file mode 100644 index 0000000..73334fc --- /dev/null +++ b/lib/data/models/book.dart @@ -0,0 +1,17 @@ +class Book { + String? title; + String? author; + String? cover; + String? description; + double? price; + double rate; + + Book({ + this.title, + this.author, + this.cover, + this.description, + this.price, + this.rate = 0.0, + }); +} diff --git a/lib/main.dart b/lib/main.dart index bcc58f7..dda936e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,8 @@ +import 'package:book_store_app/core/theme.dart'; +import 'package:book_store_app/routes/app_pages.dart'; +import 'package:book_store_app/routes/app_routes.dart'; import 'package:flutter/material.dart'; +import 'package:get/get.dart'; void main() { runApp(const MyApp()); @@ -9,12 +13,11 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( - home: Scaffold( - body: Center( - child: Text("Book Store App"), - ), - ), + return GetMaterialApp( + debugShowCheckedModeBanner: false, + theme: AppTheme.lightTheme, + initialRoute: AppRoutes.DASHBOARD, + getPages: AppPages.list, ); } } diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart new file mode 100644 index 0000000..62aa0b3 --- /dev/null +++ b/lib/routes/app_pages.dart @@ -0,0 +1,14 @@ +import 'package:book_store_app/bindings/dashboard.dart'; +import 'package:book_store_app/routes/app_routes.dart'; +import 'package:book_store_app/views/dashboard/dashboard_view.dart'; +import 'package:get/get.dart'; + +class AppPages { + static var list = [ + GetPage( + name: AppRoutes.DASHBOARD, + page: () => DashboardView(), + binding: DashboardBinding(), + ), + ]; +} diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart new file mode 100644 index 0000000..86bc040 --- /dev/null +++ b/lib/routes/app_routes.dart @@ -0,0 +1,3 @@ +class AppRoutes { + static const String DASHBOARD = '/'; +} diff --git a/lib/views/addition/add_view.dart b/lib/views/addition/add_view.dart new file mode 100644 index 0000000..d38c89a --- /dev/null +++ b/lib/views/addition/add_view.dart @@ -0,0 +1,16 @@ +import 'widgets/body.dart'; +import 'package:flutter/material.dart'; + +class AddView extends StatelessWidget { + const AddView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return const SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: true, + body: Body(), + ), + ); + } +} diff --git a/lib/views/addition/widgets/body.dart b/lib/views/addition/widgets/body.dart new file mode 100644 index 0000000..1bc1b2f --- /dev/null +++ b/lib/views/addition/widgets/body.dart @@ -0,0 +1,86 @@ +import 'package:book_store_app/controllers/add_controller.dart'; +import 'package:book_store_app/widgets/big_title.dart'; +import 'package:book_store_app/widgets/custom_button.dart'; +import 'package:book_store_app/widgets/header.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../../../widgets/custom_text_form_field.dart'; + +class Body extends GetView { + const Body({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 40, right: 16, left: 16), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Header(), + const SizedBox(height: 53), + const BigTitle(title: 'Add Book'), + Form( + key: controller.formKey, + child: Column( + children: [ + const SizedBox(height: 59), + CustomFormField( + controller: controller.nameController, + hintText: "Book Name", + maxLines: 1, + onSaved: null, + onFieldSubmitted: null, + keyboardInputType: TextInputType.text, + ), + const SizedBox(height: 22), + CustomFormField( + controller: controller.authorController, + hintText: "Author Name", + maxLines: 1, + onSaved: null, + onFieldSubmitted: null, + keyboardInputType: TextInputType.text, + ), + const SizedBox(height: 22), + CustomFormField( + controller: controller.priceController, + hintText: "Price", + maxLines: 1, + onSaved: null, + onFieldSubmitted: null, + keyboardInputType: TextInputType.text, + ), + const SizedBox(height: 22), + CustomFormField( + controller: controller.imageLinkController, + hintText: "Image link", + maxLines: 1, + onSaved: null, + onFieldSubmitted: null, + keyboardInputType: TextInputType.text, + ), + const SizedBox(height: 22), + CustomFormField( + controller: controller.descriptionController, + hintText: "Description", + maxLines: 8, + onSaved: null, + onFieldSubmitted: null, + keyboardInputType: TextInputType.text, + ), + const SizedBox(height: 22), + BigCustomButton( + title: "Add", + onTap: () => controller.addBook(), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/views/cart/cart_view.dart b/lib/views/cart/cart_view.dart new file mode 100644 index 0000000..9baa9ad --- /dev/null +++ b/lib/views/cart/cart_view.dart @@ -0,0 +1,15 @@ +import 'package:book_store_app/views/cart/widgets/body.dart'; +import 'package:flutter/material.dart'; + +class CartView extends StatelessWidget { + const CartView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + body: Body(), + ), + ); + } +} diff --git a/lib/views/cart/widgets/body.dart b/lib/views/cart/widgets/body.dart new file mode 100644 index 0000000..cc34fc3 --- /dev/null +++ b/lib/views/cart/widgets/body.dart @@ -0,0 +1,27 @@ +import 'package:book_store_app/widgets/book_list.dart'; +import 'package:book_store_app/widgets/big_title.dart'; +import 'package:book_store_app/widgets/header.dart'; +import 'package:flutter/material.dart'; + +import '../../../data/data_source.dart'; + +class Body extends StatelessWidget { + const Body({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 40, right: 16, left: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Header(), + SizedBox(height: 30), + BigTitle(title: "Cart"), + SizedBox(height: 15), + BookList(books: DataSource.cartItems), + ], + ), + ); + } +} diff --git a/lib/views/dashboard/dashboard_view.dart b/lib/views/dashboard/dashboard_view.dart new file mode 100644 index 0000000..78821fa --- /dev/null +++ b/lib/views/dashboard/dashboard_view.dart @@ -0,0 +1,15 @@ +import 'package:book_store_app/views/dashboard/widgets/body.dart'; +import 'package:flutter/material.dart'; + +class DashboardView extends StatelessWidget { + const DashboardView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return const SafeArea( + child: Scaffold( + body: Body(), + ), + ); + } +} diff --git a/lib/views/dashboard/widgets/body.dart b/lib/views/dashboard/widgets/body.dart new file mode 100644 index 0000000..76482cc --- /dev/null +++ b/lib/views/dashboard/widgets/body.dart @@ -0,0 +1,30 @@ +import 'package:book_store_app/controllers/dashboard.dart'; +import 'package:book_store_app/views/addition/add_view.dart'; +import 'package:book_store_app/views/dashboard/widgets/bottom_nav.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../../cart/cart_view.dart'; +import '../../home/home_view.dart'; + +class Body extends GetView { + const Body({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + PageView( + controller: controller.pageController, + onPageChanged: controller.changeTabIndex, + children: const [ + HomeView(), + CartView(), + AddView(), + ], + ), + const CustomBottomNavBar(), + ], + ); + } +} diff --git a/lib/views/dashboard/widgets/bottom_nav.dart b/lib/views/dashboard/widgets/bottom_nav.dart new file mode 100644 index 0000000..02f6764 --- /dev/null +++ b/lib/views/dashboard/widgets/bottom_nav.dart @@ -0,0 +1,74 @@ +import 'package:book_store_app/controllers/dashboard.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; + +class CustomBottomNavBar extends GetView { + const CustomBottomNavBar({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.bottomCenter, + child: Container( + margin: const EdgeInsets.only(bottom: 42), + height: 72, + width: 227, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: const Color(0xff07080E).withOpacity(0.05), + spreadRadius: 7, + blurRadius: 32, + offset: const Offset(0, 4), + ) + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(22), + child: Obx( + () => BottomNavigationBar( + unselectedItemColor: const Color(0xff9C9EA8), + currentIndex: controller.tabIndex.value, + showSelectedLabels: false, + showUnselectedLabels: false, + backgroundColor: Colors.white, + elevation: 0, + onTap: controller.changeTabIndex, + items: [ + _bottomNavigationBarItem( + asset: "assets/images/icons/home.svg", + label: "Home", + index: 0, + ), + _bottomNavigationBarItem( + asset: "assets/images/icons/cart.svg", + label: "Cart", + index: 1, + ), + _bottomNavigationBarItem( + asset: "assets/images/icons/add.svg", + label: "Add", + index: 2, + ), + ], + ), + ), + ), + ), + ); + } + + _bottomNavigationBarItem( + {required String asset, required String label, required int index}) { + return BottomNavigationBarItem( + icon: SvgPicture.asset( + asset, + color: controller.tabIndex.value == index + ? const Color(0xff06070D) + : const Color(0xff9C9EA8), + ), + label: label, + ); + } +} diff --git a/lib/views/detail/detail_vew.dart b/lib/views/detail/detail_vew.dart new file mode 100644 index 0000000..5000b7e --- /dev/null +++ b/lib/views/detail/detail_vew.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import '../../data/models/book.dart'; +import 'widgets/body.dart'; + +class DetailView extends StatelessWidget { + const DetailView({Key? key, required this.book}) : super(key: key); + final Book book; + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + body: Body(book: book), + ), + ); + } +} diff --git a/lib/views/detail/widgets/body.dart b/lib/views/detail/widgets/body.dart new file mode 100644 index 0000000..96d0d4c --- /dev/null +++ b/lib/views/detail/widgets/body.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +import '../../../data/models/book.dart'; +import 'book_content.dart'; +import 'footer.dart'; + +class Body extends StatelessWidget { + const Body({Key? key, required this.book}) : super(key: key); + final Book book; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 40, right: 16, left: 16), + child: Stack( + children: [ + BookContent(book: book), + Footer(book: book), + ], + ), + ); + } +} diff --git a/lib/views/detail/widgets/book_content.dart b/lib/views/detail/widgets/book_content.dart new file mode 100644 index 0000000..da8d1d1 --- /dev/null +++ b/lib/views/detail/widgets/book_content.dart @@ -0,0 +1,70 @@ +import 'package:book_store_app/data/models/book.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import '../../../widgets/header.dart'; +import '../../../widgets/rate_stars.dart'; + +class BookContent extends StatelessWidget { + const BookContent({Key? key, required this.book}) : super(key: key); + final Book book; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + const Header(), + ClipRRect( + borderRadius: BorderRadius.circular(5), + child: Image.network( + book.cover!, + fit: BoxFit.contain, + height: 320, + ), + ), + const SizedBox(height: 22), + Text( + book.title!, + style: GoogleFonts.poppins( + fontSize: 24, + fontWeight: FontWeight.w600, + color: Theme.of(context).primaryColor), + ), + const SizedBox(height: 12), + Text( + book.author!, + style: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context).primaryColor.withOpacity(0.5), + ), + ), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const RateStars(), + const SizedBox(width: 10), + Text( + "${book.rate}/5.0", + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + color: const Color(0xff06070D), + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + const SizedBox(height: 12), + Text( + book.description!, + style: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w400, + color: Theme.of(context).primaryColor.withOpacity(0.5)), + ), + ], + ); + } +} diff --git a/lib/views/detail/widgets/footer.dart b/lib/views/detail/widgets/footer.dart new file mode 100644 index 0000000..efb5d81 --- /dev/null +++ b/lib/views/detail/widgets/footer.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; + +import '../../../data/data_source.dart'; +import '../../../data/models/book.dart'; +import '../../../widgets/custom_button.dart'; +import '../../../widgets/small_custom_button.dart'; + +class Footer extends StatelessWidget { + const Footer({Key? key, required this.book}) : super(key: key); + final Book book; + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(bottom: 52), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: const [ + SmallCustomButton( + asset: 'assets/images/icons/preview.svg', title: 'Preview'), + SmallCustomButton( + asset: 'assets/images/icons/reviews.svg', title: 'Reviews'), + ], + ), + const SizedBox(height: 36), + BigCustomButton( + title: "Buy Now for \$${book.price}", + onTap: () { + DataSource.cartItems.add(book); + }, + ), + ], + ), + ); + } +} diff --git a/lib/views/home/home_view.dart b/lib/views/home/home_view.dart new file mode 100644 index 0000000..05b1592 --- /dev/null +++ b/lib/views/home/home_view.dart @@ -0,0 +1,18 @@ +import 'package:book_store_app/controllers/home_controller.dart'; +import 'package:book_store_app/views/home/widgets/body.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class HomeView extends GetView { + const HomeView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + backgroundColor: Theme.of(context).backgroundColor, + body: Body(), + ), + ); + } +} diff --git a/lib/views/home/widgets/body.dart b/lib/views/home/widgets/body.dart new file mode 100644 index 0000000..4e67057 --- /dev/null +++ b/lib/views/home/widgets/body.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +import '../../../data/data_source.dart'; +import '../../../widgets/big_title.dart'; +import '../../../widgets/book_list.dart'; +import '../../../widgets/header.dart'; +import 'search_bar.dart'; + +class Body extends StatelessWidget { + const Body({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 40, right: 16, left: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Header(isHomeHeader: true), + const SizedBox(height: 30), + const SearchBar(), + const SizedBox(height: 30), + const BigTitle(title: 'Book List'), + const SizedBox(height: 15), + Expanded(child: BookList(books: DataSource.localBooks)), + ], + ), + ); + } +} diff --git a/lib/views/home/widgets/search_bar.dart b/lib/views/home/widgets/search_bar.dart new file mode 100644 index 0000000..96ac3a8 --- /dev/null +++ b/lib/views/home/widgets/search_bar.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class SearchBar extends StatelessWidget { + const SearchBar({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: const Color(0xff07080E).withOpacity(0.05), + spreadRadius: 7, + blurRadius: 32, + offset: const Offset(0, 4), + ) + ], + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(15), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Search...", + style: GoogleFonts.poppins( + color: Theme.of(context).colorScheme.secondary, + fontSize: 16, + fontWeight: FontWeight.w400), + ), + SvgPicture.asset("assets/images/icons/search.svg"), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/big_title.dart b/lib/widgets/big_title.dart new file mode 100644 index 0000000..2eb840f --- /dev/null +++ b/lib/widgets/big_title.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class BigTitle extends StatelessWidget { + const BigTitle({Key? key, required this.title}) : super(key: key); + final String title; + + @override + Widget build(BuildContext context) { + return Text( + title, + style: GoogleFonts.poppins( + fontSize: 24, + fontWeight: FontWeight.w600, + color: Theme.of(context).primaryColor), + ); + } +} diff --git a/lib/widgets/book_item.dart b/lib/widgets/book_item.dart new file mode 100644 index 0000000..bc069b2 --- /dev/null +++ b/lib/widgets/book_item.dart @@ -0,0 +1,71 @@ +import 'package:book_store_app/views/detail/detail_vew.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import '../data/models/book.dart'; +import 'rate_stars.dart'; + +class BookItem extends StatelessWidget { + const BookItem({Key? key, required this.book}) : super(key: key); + final Book book; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => Get.to(DetailView( + book: book, + )), + child: Container( + margin: const EdgeInsets.symmetric(vertical: 10), + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(5), + child: Image.network( + book.cover!, + height: 106, + fit: BoxFit.fill, + ), + ), + const SizedBox(width: 20), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + book.title!, + style: GoogleFonts.poppins( + color: Theme.of(context).primaryColor, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 4), + Text( + book.author!, + style: GoogleFonts.poppins( + color: Theme.of(context).colorScheme.secondary, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 8), + Text( + "\$${book.price}", + style: GoogleFonts.poppins( + color: const Color(0xff191919), + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 5), + const RateStars(), + ], + ) + ], + ), + ), + ); + } +} diff --git a/lib/widgets/book_list.dart b/lib/widgets/book_list.dart new file mode 100644 index 0000000..71197b7 --- /dev/null +++ b/lib/widgets/book_list.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +import '../data/models/book.dart'; +import 'book_item.dart'; +import 'fading_list.dart'; + +class BookList extends StatelessWidget { + const BookList({Key? key, required this.books}) : super(key: key); + final List books; + + @override + Widget build(BuildContext context) { + return FadingListView( + listView: ListView.builder( + shrinkWrap: true, + scrollDirection: Axis.vertical, + itemCount: books.length, + physics: const BouncingScrollPhysics(), + itemBuilder: (context, index) => BookItem(book: books[index]), + ), + ); + } +} diff --git a/lib/widgets/custom_button.dart b/lib/widgets/custom_button.dart new file mode 100644 index 0000000..eb525f1 --- /dev/null +++ b/lib/widgets/custom_button.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class BigCustomButton extends StatelessWidget { + const BigCustomButton({Key? key, required this.title, required this.onTap}) + : super(key: key); + final String title; + final void Function() onTap; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + height: 60, + decoration: const BoxDecoration( + boxShadow: [ + BoxShadow( + color: Color(0xff1A07080E), + spreadRadius: 7, + blurRadius: 32, + offset: Offset(0, 4), + ) + ], + ), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + primary: const Color(0xff06070D), + elevation: 0.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16))), + onPressed: onTap, + child: Text( + title, + style: GoogleFonts.poppins( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ), + ); + } +} diff --git a/lib/widgets/custom_text_form_field.dart b/lib/widgets/custom_text_form_field.dart new file mode 100644 index 0000000..ec8c8b8 --- /dev/null +++ b/lib/widgets/custom_text_form_field.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class CustomFormField extends StatelessWidget { + const CustomFormField({ + Key? key, + required this.hintText, + this.maxLines, + this.onSaved, + this.onFieldSubmitted, + this.controller, + this.keyboardInputType, + }) : super(key: key); + + final String hintText; + final int? maxLines; + final void Function(String?)? onSaved; + final void Function(String)? onFieldSubmitted; + final TextEditingController? controller; + final TextInputType? keyboardInputType; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + boxShadow: const [ + BoxShadow( + color: Color(0xff0D07080E), + blurRadius: 32, + offset: Offset(0, 4), + ) + ], + ), + child: Padding( + padding: const EdgeInsets.all(18), + child: TextFormField( + controller: controller, + maxLines: maxLines, + onSaved: onSaved, + keyboardType: keyboardInputType, + onFieldSubmitted: onFieldSubmitted, + cursorColor: Colors.black, + autofocus: true, + decoration: InputDecoration( + isDense: true, + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + hintText: hintText, + focusColor: Colors.red, + hintStyle: GoogleFonts.poppins( + fontSize: 16, + fontWeight: FontWeight.w400, + color: const Color(0xff84889E), + ), + ), + ), + ), + ); + } +} diff --git a/lib/widgets/fading_list.dart b/lib/widgets/fading_list.dart new file mode 100644 index 0000000..734d4bb --- /dev/null +++ b/lib/widgets/fading_list.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +class FadingListView extends StatelessWidget { + const FadingListView({Key? key, required this.listView}) : super(key: key); + final ListView listView; + + @override + Widget build(BuildContext context) { + return Center( + child: ShaderMask( + shaderCallback: (Rect rect) { + return const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.purple, + Colors.transparent, + Colors.transparent, + Colors.purple + ], + stops: [0.0, 0.07, 1.0, 1.0], + ).createShader(rect); + }, + blendMode: BlendMode.dstOut, + child: listView, + ), + ); + } +} diff --git a/lib/widgets/header.dart b/lib/widgets/header.dart new file mode 100644 index 0000000..c1e13e2 --- /dev/null +++ b/lib/widgets/header.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class Header extends StatelessWidget { + const Header({Key? key, this.isHomeHeader = false}) : super(key: key); + final bool isHomeHeader; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + if (isHomeHeader) + SizedBox( + height: 50, + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Image.network( + "https://api.lorem.space/image/face?w=150&h=150"), + ), + ), + if (isHomeHeader) const SizedBox(width: 12), + if (isHomeHeader) + Text( + "Hi, Sadeq!", + style: GoogleFonts.poppins( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Theme.of(context).primaryColor), + ), + if (!isHomeHeader) + IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + onPressed: () => Get.back(), + icon: Icon( + Icons.arrow_back_ios, + color: Theme.of(context).primaryColor, + ), + ), + const Spacer(), + IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + onPressed: () {}, + icon: Icon( + Icons.more_vert, + color: Theme.of(context).primaryColor, + ), + ), + ], + ); + } +} diff --git a/lib/widgets/rate_stars.dart b/lib/widgets/rate_stars.dart new file mode 100644 index 0000000..b1bc937 --- /dev/null +++ b/lib/widgets/rate_stars.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; + +class RateStars extends StatelessWidget { + const RateStars({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Icon( + Icons.star, + color: Color(0xffFFC41F), + size: 20, + ), + Icon( + Icons.star, + color: Color(0xffFFC41F), + size: 20, + ), + Icon( + Icons.star, + color: Color(0xffFFC41F), + size: 20, + ), + Icon( + Icons.star, + color: Color(0xffFFC41F), + size: 20, + ), + Icon( + Icons.star, + color: Color(0xffEDEDEF), + size: 20, + ), + ], + ); + } +} diff --git a/lib/widgets/small_custom_button.dart b/lib/widgets/small_custom_button.dart new file mode 100644 index 0000000..b2008ca --- /dev/null +++ b/lib/widgets/small_custom_button.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class SmallCustomButton extends StatelessWidget { + const SmallCustomButton({Key? key, required this.asset, required this.title}) + : super(key: key); + final String asset; + final String title; + + @override + Widget build(BuildContext context) { + return Container( + height: 40, + decoration: const BoxDecoration( + boxShadow: [ + BoxShadow( + color: Color(0xff0D07080E), + spreadRadius: 7, + blurRadius: 32, + offset: Offset(0, 4), + ) + ], + ), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + elevation: 0.0, + primary: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 32), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + onPressed: () {}, + child: Row( + children: [ + SvgPicture.asset( + asset, + color: Colors.black, + ), + const SizedBox(width: 15), + Text( + title, + style: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context).primaryColor, + ), + ) + ], + ), + ), + ); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..0d56f51 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,8 @@ import FlutterMacOS import Foundation +import path_provider_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 7bc8bdd..7575c05 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -43,6 +43,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.16.0" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" cupertino_icons: dependency: "direct main" description: @@ -57,6 +64,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.4" flutter: dependency: "direct main" description: flutter @@ -69,11 +90,53 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.4" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + font_awesome_flutter: + dependency: "direct main" + description: + name: font_awesome_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "10.1.0" + get: + dependency: "direct main" + description: + name: get + url: "https://pub.dartlang.org" + source: hosted + version: "4.6.5" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.5" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" lints: dependency: transitive description: @@ -109,6 +172,97 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.1" + path_drawing: + dependency: transitive + description: + name: path_drawing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + path_provider: + dependency: transitive + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.20" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.7" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.0" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" sky_engine: dependency: transitive description: flutter @@ -156,6 +310,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.9" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" vector_math: dependency: transitive description: @@ -163,5 +324,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.2" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "2.7.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0+2" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.0" sdks: dart: ">=2.17.6 <3.0.0" + flutter: ">=3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index cd0f457..6587fca 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,11 +29,11 @@ environment: dependencies: flutter: sdk: flutter - - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 + get: ^4.6.5 + google_fonts: ^3.0.1 + font_awesome_flutter: ^10.1.0 + flutter_svg: ^1.1.4 dev_dependencies: flutter_test: @@ -46,6 +46,7 @@ dev_dependencies: # rules and activating additional ones. flutter_lints: ^2.0.0 + # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -58,9 +59,8 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - assets/images/icons/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware