diff --git a/Assets/Images/placeholder.jpg b/Assets/Images/placeholder.jpg new file mode 100644 index 0000000..f72d035 Binary files /dev/null and b/Assets/Images/placeholder.jpg differ diff --git a/README.md b/README.md index 037e2ef..959a4c3 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,5 @@ -# Task 3 - Book Store App - -Task resolution process: - -- Fork the repo -- Clone the forked repo to your local machine -- Resolve the task -- Commit your solution -- Push to GitHub -- create a pull request - - - -### Task 3: -Build a book store mobile application. The store contain a list of available books with the ability to add custom books to the store as well as view, order them. - -* Don't use pre-made widgets (packges). -* It's ok to use icons packages. -* Use Getx for state management. - -## UI -### Main Page -The main page should contain a list of the available books. When tapping on a book, the second page (Book Details Page) will be navigated. Use Scrollable view for the list of books. ALso there is a simple search on the top of the page. - - -### Cart Page (Optional) -This page can view the ordered books. - -### Details Page -This have the details of the book which is: -- Book name -- Author -- Desciption -- Image -- Rate (constant) - -### Add Page -It is the page where you can add new books to the store. - -## FLow -1. Build the UI (from figma design). -2. seperate the widgets to small and organized files. -3. Build models that contain the book data (You can build one model or more). -4. Try to seperate the logic (Functions and data) from the UI. - -## Figma Design -https://www.figma.com/file/4BGkiFfTPT7b8K9pyFTBD6/Online-Book-Store-App-(2019)-(Community)?node-id=0%3A1 +![2022-08-25 21_20_14-Android Emulator - Pixel_5_API_33_5554](https://user-images.githubusercontent.com/58166633/203349049-f2809ae9-7707-4609-8427-544b9ca50940.png) +![2022-08-25 21_20_23-Android Emulator - Pixel_5_API_33_5554](https://user-images.githubusercontent.com/58166633/203349053-e3c412f6-2acd-4554-b16b-5a7eb7c33433.png) +![2022-08-25 21_20_41-Android Emulator - Pixel_5_API_33_5554](https://user-images.githubusercontent.com/58166633/203349056-d0173ea0-4cdb-4470-bfe4-629277e5b664.png) +![2022-08-25 21_21_05-Android Emulator - Pixel_5_API_33_5554](https://user-images.githubusercontent.com/58166633/203349060-9d89bec5-fd40-473e-b057-36a8fb99997f.png) +![2022-08-25 21_25_15-Android Emulator - Pixel_5_API_33_5554](https://user-images.githubusercontent.com/58166633/203349064-960764a2-8522-4de9-bcf2-a44d79850c93.png) diff --git a/lib/Add Book/add_book_page.dart b/lib/Add Book/add_book_page.dart new file mode 100644 index 0000000..1fa1479 --- /dev/null +++ b/lib/Add Book/add_book_page.dart @@ -0,0 +1,112 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +// ignore: depend_on_referenced_packages +import 'package:get/get.dart'; + +import 'package:book_app/Models/books_model.dart'; +import 'package:book_app/Reusable_Widgets/star_rating.dart'; +import 'package:book_app/Reusable_Widgets/title.dart'; +import '../Reusable_Widgets/MainButton.dart'; +import '../Reusable_Widgets/customTextField.dart'; + +import './textfiled_vars.dart'; +import 'clear_textfields.dart'; +import 'validating/validating_and_error_msgs.dart'; +import 'validating/validating_textfields.dart'; + +class AddBookPage extends StatelessWidget { + AddBookPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + Rx rate = Rx(0); + + // String imageLink = "https://api.lorem.space/image/book?w=150&h=224"; + addButton() { + try { + validator(); + Books.addToAllBooks( + Book( + title: titleTextfields.text, + author: authorTextfields.text, + imageLink: imageLinkTextfields.text, + price: double.parse(priceTextfields.text), + rate: rate.value.toDouble(), + description: descriptionTextfields.text), + ); + FocusScope.of(context).unfocus(); + clearTextfields(); + if (kDebugMode) { + Book lastBook = Books().allBooks[Books().allBooks.length - 1]; + print( + "title:${lastBook.title}\nauthor:${lastBook.author}\ndescription:${lastBook.description}\nprice:\$${lastBook.price}\nrate:${lastBook.rate}"); + } + } catch (e) {} + } + + return SingleChildScrollView( + child: Container( + height: 800, + width: double.maxFinite, + child: Column( + // mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + const CustomTitle(title: "Add Book"), + const SizedBox(height: 40), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Padding( + padding: const EdgeInsets.only(bottom: 0), + child: Column( + children: [ + CustomTextField( + hintText: "Book's Name", + validator: titleValidator, + errmsg: titleErrMsg, + textEditingController: titleTextfields), + CustomTextField( + validator: authorValidator, + errmsg: authorErrMsg, + hintText: "Author's Name", + textEditingController: authorTextfields), + CustomTextField( + hintText: "Price", + textInputType: TextInputType.number, + validator: priceValidator, + errmsg: priceErrMsg, + maxLines: 1, + textEditingController: priceTextfields), + CustomTextField( + validator: imageLinkValidator, + errmsg: imageLinkErrMsg, + hintText: "Image Link", + maxLines: 1, + textEditingController: imageLinkTextfields), + CustomTextField( + hintText: "Description", + validator: descriptionValidator, + errmsg: descriptionErrMsg, + maxLines: 5, + textEditingController: descriptionTextfields), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: StarRating(rate: rate)), + + // ----------------------- + MainButton( + buttonFunction: addButton, + buttonTitle: "Add", + ), + ], + ), + ), + ), + const Spacer() + ], + ), + ), + ); + } +} diff --git a/lib/Add Book/clear_textfields.dart b/lib/Add Book/clear_textfields.dart new file mode 100644 index 0000000..61e7263 --- /dev/null +++ b/lib/Add Book/clear_textfields.dart @@ -0,0 +1,9 @@ +import 'package:book_app/Add%20Book/textfiled_vars.dart'; + +void clearTextfields(){ + titleTextfields.clear(); + authorTextfields.clear(); + imageLinkTextfields.clear(); + priceTextfields.clear(); + descriptionTextfields.clear(); +} \ No newline at end of file diff --git a/lib/Add Book/textfiled_vars.dart b/lib/Add Book/textfiled_vars.dart new file mode 100644 index 0000000..fd718d3 --- /dev/null +++ b/lib/Add Book/textfiled_vars.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +TextEditingController titleTextfields = TextEditingController(); +TextEditingController authorTextfields = TextEditingController(); +TextEditingController imageLinkTextfields = TextEditingController(); +TextEditingController priceTextfields = TextEditingController(); +TextEditingController descriptionTextfields = TextEditingController(); diff --git a/lib/Add Book/validating/validating_and_error_msgs.dart b/lib/Add Book/validating/validating_and_error_msgs.dart new file mode 100644 index 0000000..a8d31de --- /dev/null +++ b/lib/Add Book/validating/validating_and_error_msgs.dart @@ -0,0 +1,12 @@ +import 'package:get/get.dart'; + +Rx titleValidator = Rx(false); +Rx authorValidator = Rx(false); +Rx priceValidator = Rx(false); +Rx imageLinkValidator = Rx(false); +Rx descriptionValidator = Rx(false); +Rx titleErrMsg = Rx(""); +Rx authorErrMsg = Rx(""); +Rx priceErrMsg = Rx(""); +Rx imageLinkErrMsg = Rx(""); +Rx descriptionErrMsg = Rx(""); diff --git a/lib/Add Book/validating/validating_textfields.dart b/lib/Add Book/validating/validating_textfields.dart new file mode 100644 index 0000000..f921ae2 --- /dev/null +++ b/lib/Add Book/validating/validating_textfields.dart @@ -0,0 +1,43 @@ +import '../textfiled_vars.dart'; +import 'validating_and_error_msgs.dart'; + +void validator() { + if (titleTextfields.text.isEmpty) { + titleValidator.value = true; + titleErrMsg.value = "Book's at least must be 1 character"; + // return null; + } else if (titleTextfields.text.length > 25) { + titleValidator.value = true; + titleErrMsg.value = "Book's must be less than 25"; + } else + titleValidator.value = false; + + if (authorTextfields.text.isEmpty) { + authorValidator.value = true; + authorErrMsg.value = "Author's name at least must be 1 character"; + } else if (authorTextfields.text.length > 25) { + authorValidator.value = true; + authorErrMsg.value = "author must be less than 25"; + } else + authorValidator.value = false; + + if (priceTextfields.text.isEmpty) { + priceValidator.value = true; + priceErrMsg.value = "Price is required field"; + } else if (double.tryParse(priceTextfields.text) == null) { + priceValidator.value = true; + priceErrMsg.value = "Please Enter valid price value"; + } else + priceValidator.value = false; + + if (imageLinkTextfields.text.isEmpty) { + imageLinkValidator.value = true; + imageLinkErrMsg.value = "Image is required field"; + } else + imageLinkValidator.value = false; + + if (titleValidator.value || + authorValidator.value || + priceValidator.value || + imageLinkValidator.value) return null; +} diff --git a/lib/BookPage/book_main.dart b/lib/BookPage/book_main.dart new file mode 100644 index 0000000..41d662e --- /dev/null +++ b/lib/BookPage/book_main.dart @@ -0,0 +1,97 @@ +import 'package:book_app/BookPage/image_container.dart'; +import 'package:flutter/material.dart'; + +import '../Models/books_model.dart'; +import '../Reusable_Widgets/MainButton.dart'; +import '../Reusable_Widgets/subButton.dart'; +import '../Reusable_Widgets/title.dart'; +import 'title_and_rate.dart'; + +class BookPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + dynamic bookInfo = ModalRoute.of(context)?.settings.arguments; + print(bookInfo); + return Scaffold( + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 25), + child: Container( + width: double.maxFinite, + height: double.maxFinite, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Align( + alignment: Alignment.topLeft, + child: IconButton( + onPressed: () => Navigator.pop(context), + icon: Icon(Icons.arrow_back)), + ), + ImageContainer(imageLink: bookInfo['imageLink']), + TitleAndRate( + title: bookInfo['title'], + author: bookInfo['author'], + rate: bookInfo['rate'].toInt()), + Column( + children: [ + Text( + bookInfo['description'], + style: TextStyle(color: Colors.grey), + ), + SizedBox( + height: 10, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: SubButton( + buttonFunction: () => null, + buttonTitle: "Preview", + height: 50, + icon: Icons.bar_chart_rounded, + ), + ), + SizedBox( + width: 10, + ), + Expanded( + child: SubButton( + buttonFunction: () => null, + buttonTitle: "Reviews", + height: 50, + icon: Icons.chat_outlined, + ), + ) + ], + ), + ], + ), + StatefulBuilder( + builder: (BuildContext context, setState) { + return MainButton( + buttonFunction: () { + setState( + () => bookInfo['isCart'] = !bookInfo['isCart']); + + for (var element in Books().allBooks) { + if (element.title == bookInfo['title']) { + element.isCart = bookInfo['isCart']; + } + } + }, + buttonTitle: bookInfo['isCart'] + ? "Remove from Cart" + : "Buy Now For \$${bookInfo['price']}", + ); + }, + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/BookPage/image_container.dart b/lib/BookPage/image_container.dart new file mode 100644 index 0000000..9d78eeb --- /dev/null +++ b/lib/BookPage/image_container.dart @@ -0,0 +1,33 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; + +class ImageContainer extends StatelessWidget { + final String imageLink; + const ImageContainer({ + Key? key, + required this.imageLink, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return CachedNetworkImage( + width: 210, + height: 310, + fit: BoxFit.fill, + imageUrl: imageLink, + placeholder: (context, url) { + return const Center( + child: SizedBox( + width: 30, + height: 30, + child: CircularProgressIndicator(), + ), + ); + }, + errorWidget: (context, url, error) => Image.asset( + "Assets/Images/placeholder.jpg", + fit: BoxFit.fill, + ), + ); + } +} diff --git a/lib/BookPage/title_and_rate.dart b/lib/BookPage/title_and_rate.dart new file mode 100644 index 0000000..a4bf793 --- /dev/null +++ b/lib/BookPage/title_and_rate.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../Reusable_Widgets/star_rating.dart'; +import '../Reusable_Widgets/title.dart'; +class TitleAndRate extends StatelessWidget { + final String title; + final String author; + final int rate; + const TitleAndRate({ + Key? key, + required this.title, + required this.author, + required this.rate, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CustomTitle( + textAlign: TextAlign.center, + title: title, + ), + const SizedBox(height: 5,), + Text( + textAlign:TextAlign.left, + author, + style: TextStyle(color: Colors.grey), + ), + const SizedBox(height: 5,), + SizedBox(width: 180,child: StarRating(rate: Rx(rate),functional: false),) + ], + ); + } +} diff --git a/lib/Books/AppBar/main_AppBar.dart b/lib/Books/AppBar/main_AppBar.dart new file mode 100644 index 0000000..89432e9 --- /dev/null +++ b/lib/Books/AppBar/main_AppBar.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +import '../../Models/books_model.dart'; +import 'profile_pic_name.dart'; +import 'search_button.dart'; +import 'package:get/get.dart'; + +class MainAppBar extends StatefulWidget { + MainAppBar({ + Key? key, + }) : super(key: key); + + @override + State createState() => _MainAppBarState(); +} + +class _MainAppBarState extends State { + + + + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ProfilePicName(), + + SearchButton() + ], + ); + } +} diff --git a/lib/Books/AppBar/profile_pic_name.dart b/lib/Books/AppBar/profile_pic_name.dart new file mode 100644 index 0000000..275ecb6 --- /dev/null +++ b/lib/Books/AppBar/profile_pic_name.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +class ProfilePicName extends StatelessWidget { + const ProfilePicName({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Container( + height: 50, + width: 50, + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(10), + image: const DecorationImage( + image: NetworkImage( + "https://api.lorem.space/image/face?w=150&h=150"))), + ), + const SizedBox( + width: 10, + ), + const Text("Hello User",style: TextStyle(fontSize: 18,fontWeight: FontWeight.bold),) + ], + ); + } +} diff --git a/lib/Books/AppBar/search_button.dart b/lib/Books/AppBar/search_button.dart new file mode 100644 index 0000000..362945f --- /dev/null +++ b/lib/Books/AppBar/search_button.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +class SearchButton extends StatelessWidget { + const SearchButton({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + + IconButton( + padding: EdgeInsets.zero, + onPressed: () => null, + icon: Icon( + Icons.more_vert_rounded, + size: 30, + ), + ), + ], + ); + } +} diff --git a/lib/Books/Display_Books_page.dart b/lib/Books/Display_Books_page.dart new file mode 100644 index 0000000..e402622 --- /dev/null +++ b/lib/Books/Display_Books_page.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import 'package:book_app/Books/AppBar/main_AppBar.dart'; +import 'package:book_app/Models/books_model.dart'; +import 'package:book_app/Models/sent_book_data.dart'; +import 'package:book_app/Reusable%20Functions/book_filter.dart'; +import 'package:book_app/Reusable_Widgets/title.dart'; + +import '../Reusable_Widgets/bookInfoDisplay.dart'; + +class DisplayBooksPage extends StatefulWidget { + final bool appbar; + final String pageTitle; + final Rx> fullBooks; + + const DisplayBooksPage({ + Key? key, + required this.appbar, + required this.pageTitle, + required this.fullBooks, + }) : super(key: key); + + @override + State createState() => _DisplayBooksPageState(); +} + +class _DisplayBooksPageState extends State { + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.appbar) ...[ + MainAppBar(), + const SizedBox( + height: 40, + ), + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + // this the text field section + child: TextField( + onChanged: (value) => + BookFilter(fullBooks: widget.fullBooks).searchBook(value), + decoration: const InputDecoration( + contentPadding: EdgeInsets.all(20), + border: InputBorder.none, + hintStyle: TextStyle(fontSize: 20), + hintText: "Search...", + suffixIcon: Icon( + Icons.search, + size: 35, + ), + ), + ), + ), + ], + const SizedBox(height: 30), + CustomTitle( + title: widget.pageTitle, + ), + const SizedBox(height: 40), + Expanded( + child: Obx( + () => ListView.builder( + physics: const BouncingScrollPhysics(), + itemCount: widget.fullBooks.value.length, + itemBuilder: (context, index) { + if (index == widget.fullBooks.value.length - 1) { + return Column( + children: [ + GestureDetector( + onTap: () { + FocusScope.of(context).unfocus(); + Navigator.pushNamed(context, "/book", + arguments: sentBookData( + fullBooks: widget.fullBooks, index: index) + .sentData); + }, + child: BookInfoDisplay( + bookInfo: sentBookData( + fullBooks: widget.fullBooks, index: index) + .sentData, + ), + ), + const SizedBox( + height: 100, + ), + ], + ); + } + return GestureDetector( + onTap: () { + FocusScope.of(context).unfocus(); + Navigator.pushNamed(context, "/book", + arguments: sentBookData( + fullBooks: widget.fullBooks, index: index) + .sentData); + }, + child: BookInfoDisplay( + bookInfo: sentBookData( + fullBooks: widget.fullBooks, index: index) + .sentData, + )); + }, + ), + ), + ) + ], + ); + } +} diff --git a/lib/Models/books_model.dart b/lib/Models/books_model.dart new file mode 100644 index 0000000..c4495c0 --- /dev/null +++ b/lib/Models/books_model.dart @@ -0,0 +1,79 @@ +class Book { + final String title; + final String author; + final String description; + final String imageLink; + final double price; + final double rate; + bool isSaved; + bool isCart; + Book({ + required this.title, + required this.author, + this.description = "", + required this.imageLink, + required this.price, + required this.rate, + this.isSaved = false, + this.isCart = false, + }); +} + +class Books { + // ignore: prefer_final_fields + static List _allBooks = [ + Book( + title: "Yves Saint Laurent", + author: "Laurence Benaïm", + imageLink: + "https://images-na.ssl-images-amazon.com/images/I/31zSzxEMQ8L._SX340_BO1,204,203,200_.jpg", + price: 4.9, + rate: 5, + ), + Book( + title: "Game of Thrones", + author: "george r r martin", + imageLink: + "https://upload.wikimedia.org/wikipedia/en/d/dc/A_Song_of_Ice_and_Fire_book_collection_box_set_cover.jpg", + price: 14.9, + rate: 3), + Book( + title: "brothers of karamazov", + author: "Fyodor Dostoevsky", + imageLink: "https://kbimages1-a.akamaihd.net/561f9624-ba0a-43dc-a569-dac6327e3804/1200/1200/False/the-brothers-karamazov-233.jpg", + price: 8.0, + rate: 1) + ]; + + List _savedBooks = []; + List _cartBooks = []; + + static void addToAllBooks(Book book) => _allBooks.add(book); + + List get allBooks => _allBooks; + + List get savedBooks { + for (Book element in _allBooks) { + if (element.isSaved == true) { + _savedBooks.add(element); + } + } + return _savedBooks; + } + + List get cartBooks { + for (Book element in _allBooks) { + if (element.isCart == true) { + _cartBooks.addAll([element]); + } + } + + return _cartBooks; + } +} + +// void main(List args) { +// books library = books(); + +// print(library.savedBooks); +// } diff --git a/lib/Models/sent_book_data.dart b/lib/Models/sent_book_data.dart new file mode 100644 index 0000000..c198a77 --- /dev/null +++ b/lib/Models/sent_book_data.dart @@ -0,0 +1,26 @@ +import 'package:get/get.dart'; + +import 'books_model.dart'; +// this data will be sent when navigated to the book page + +class sentBookData { + final Rx> fullBooks; + final int index; + sentBookData({ + required this.fullBooks, + required this.index, + }); + Map _sentData = {}; + get sentData { + return _sentData = { + "title": fullBooks.value[index].title, + "author": fullBooks.value[index].author, + "rate": fullBooks.value[index].rate, + "description": fullBooks.value[index].description, + "price": fullBooks.value[index].price, + "imageLink": fullBooks.value[index].imageLink, + "isCart": fullBooks.value[index].isCart, + "isSaved":fullBooks.value[index].isSaved + }; + } +} diff --git a/lib/Reusable Functions/book_filter.dart b/lib/Reusable Functions/book_filter.dart new file mode 100644 index 0000000..f50f996 --- /dev/null +++ b/lib/Reusable Functions/book_filter.dart @@ -0,0 +1,17 @@ +import 'package:get/get.dart'; + +import '../Models/books_model.dart'; +class BookFilter { + final Rx> fullBooks; + BookFilter({ + required this.fullBooks, + }); + void searchBook(String query) { + final suggestions = Books().allBooks.where((book) { + final bookTitle = book.title.toLowerCase(); + final input = query.toLowerCase(); + return bookTitle.contains(input); + }).toList(); + fullBooks.value = suggestions; + } +} diff --git a/lib/Reusable_Widgets/MainButton.dart b/lib/Reusable_Widgets/MainButton.dart new file mode 100644 index 0000000..cf09f3f --- /dev/null +++ b/lib/Reusable_Widgets/MainButton.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +class MainButton extends StatelessWidget { + + final Function() buttonFunction; + final String buttonTitle; + + const MainButton({ + Key? key, + required this.buttonFunction, + required this.buttonTitle, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.maxFinite, + height: 65, + child: ElevatedButton( + style: ButtonStyle( + foregroundColor: MaterialStateProperty.all(Colors.white), + backgroundColor: + MaterialStateProperty.all( const Color.fromRGBO(6, 7, 13, 1)), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ))), + onPressed: buttonFunction, + child: Text(buttonTitle.toUpperCase(), style: const TextStyle(fontSize: 14)), + ), + ); + } +} diff --git a/lib/Reusable_Widgets/bookInfoDisplay.dart b/lib/Reusable_Widgets/bookInfoDisplay.dart new file mode 100644 index 0000000..66ec220 --- /dev/null +++ b/lib/Reusable_Widgets/bookInfoDisplay.dart @@ -0,0 +1,122 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import 'package:book_app/Reusable_Widgets/star_rating.dart'; + +import '../Models/books_model.dart'; +import 'title.dart'; + +class BookInfoDisplay extends StatelessWidget { + final Map bookInfo; + + const BookInfoDisplay({ + Key? key, + required this.bookInfo, + }) : super(key: key); + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(bottom: 10), + height: 150, + width: double.maxFinite, + decoration: BoxDecoration( + // color: Colors.red, + border: Border.all( + // color: Colors.black, + color: Colors.transparent, + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CachedNetworkImage( + width: 100, + height: 200, + fit: BoxFit.fill, + // imageUrl: bookShelf[index].imageLink, + imageUrl: bookInfo['imageLink'], + placeholder: (context, url) { + return const Center( + child: SizedBox( + width: 30, + height: 30, + child: CircularProgressIndicator(), + ), + ); + }, + errorWidget: (context, url, error) => Image.asset( + "Assets/Images/placeholder.jpg", + fit: BoxFit.fill, + ), + ), + // SizedBox(width: 30), + Expanded( + child: Container( + // width: 150, + // color: Colors.red, + child: Padding( + padding: const EdgeInsets.only(left: 18, top: 8), + child: Column( + // mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomTitle( + title: bookInfo['title'], + size: 18, + ), + const SizedBox(height: 3), + Text( + bookInfo['author'], + style: const TextStyle( + color: const Color.fromARGB(127, 6, 7, 13), + fontSize: 18), + ), + const SizedBox(height: 5), + CustomTitle( + title: "\$${bookInfo['price']}", + size: 20, + ), + const SizedBox(height: 10), + StarRating( + rate: Rx(bookInfo['rate'].toInt()), + functional: false, + ) + ], + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 8), + child: Column( + children: [ + StatefulBuilder( + builder: (BuildContext context, setState) { + return GestureDetector( + onTap: () { + setState( + () { + bookInfo['isSaved'] = !bookInfo['isSaved']; + for (var element in Books().allBooks) { + if (element.title == bookInfo['title']) { + element.isSaved = bookInfo['isSaved']; + } + } + }, + ); + }, + child: bookInfo['isSaved'] + ? const Icon(Icons.bookmark_added_sharp, + color: Colors.green) + : const Icon(Icons.bookmark_add_sharp)); + }, + ), + ], + ), + ) + ], + ), + ); + } +} diff --git a/lib/Reusable_Widgets/customTextField.dart b/lib/Reusable_Widgets/customTextField.dart new file mode 100644 index 0000000..9a1579e --- /dev/null +++ b/lib/Reusable_Widgets/customTextField.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class CustomTextField extends StatelessWidget { + final TextEditingController textEditingController; + final String hintText; + final int maxLines; + final Rx validator; + final Rx errmsg; + final TextInputType textInputType; + const CustomTextField({ + Key? key, + required this.textEditingController, + required this.hintText, + this.maxLines = 1, + required this.validator, + required this.errmsg, + this.textInputType = TextInputType.text, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + ), + child: Obx(() => TextField( + keyboardType: textInputType, + controller: textEditingController, + maxLines: maxLines, + decoration: InputDecoration( + errorText: validator.value ? errmsg.value : null, + hintStyle: const TextStyle(fontSize: 17, color: Colors.grey), + hintText: hintText, + border: InputBorder.none, + contentPadding: const EdgeInsets.all(20), + ), + )), + ), + ); + } +} diff --git a/lib/Reusable_Widgets/star_rating.dart b/lib/Reusable_Widgets/star_rating.dart new file mode 100644 index 0000000..4a1e850 --- /dev/null +++ b/lib/Reusable_Widgets/star_rating.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class StarRating extends StatefulWidget { + Rx rate; + bool functional; + StarRating({ + Key? key, + required this.rate, + this.functional = true, + }) : super(key: key); + + @override + State createState() => _StarRatingState(); +} + +class _StarRatingState extends State { + void rate(int rating) { + widget.rate.value = rating; + print(widget.rate.value); + } + + @override + Widget build(BuildContext context) { + return Obx( + () { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + GestureDetector( + child: Icon( + Icons.star, + color: widget.rate >= 1 ? Colors.orange : Colors.grey, + ), + onTap: () => widget.functional == true?rate(1):null, + ), + GestureDetector( + child: Icon( + Icons.star, + color: widget.rate >= 2 ? Colors.orange : Colors.grey, + ), + onTap: () => widget.functional == true?rate(2):null, + ), + GestureDetector( + child: Icon( + Icons.star, + color: widget.rate >= 3 ? Colors.orange : Colors.grey, + ), + onTap: () => widget.functional == true?rate(3):null, + ), + GestureDetector( + child: Icon( + Icons.star, + color: widget.rate >= 4 ? Colors.orange : Colors.grey, + ), + onTap: () => widget.functional == true?rate(4):null, + ), + GestureDetector( + child: Icon( + Icons.star, + color: widget.rate >= 5 ? Colors.orange : Colors.grey, + ), + onTap: () => widget.functional == true?rate(5):null, + ), + const SizedBox( + width: 10, + ), + Text("${widget.rate.value.toDouble()}/5.0") + ], + ); + }, + ); + } +} diff --git a/lib/Reusable_Widgets/subButton.dart b/lib/Reusable_Widgets/subButton.dart new file mode 100644 index 0000000..6ff29dd --- /dev/null +++ b/lib/Reusable_Widgets/subButton.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; + +class SubButton extends StatelessWidget { + final Function() buttonFunction; + final String buttonTitle; + final double height; + final IconData icon; + const SubButton({ + Key? key, + required this.buttonFunction, + required this.buttonTitle, + required this.height, + required this.icon, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: height, + child: ElevatedButton( + onPressed: buttonFunction, + style: ButtonStyle( + foregroundColor: MaterialStateProperty.all(Colors.white), + backgroundColor: MaterialStateProperty.all(Colors.white), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ))), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Icon( + icon, + color: Colors.black, + ), + Text( + buttonTitle, + style: TextStyle(fontSize: 18, color: Colors.black), + ) + ], + ), + ), + ); + } +} diff --git a/lib/Reusable_Widgets/title.dart b/lib/Reusable_Widgets/title.dart new file mode 100644 index 0000000..4a74659 --- /dev/null +++ b/lib/Reusable_Widgets/title.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class CustomTitle extends StatelessWidget { + final String title; + final double size; + final FontWeight fontWeight; + final TextAlign textAlign; + const CustomTitle({ + Key? key, + required this.title, + this.size = 30.0, + this.fontWeight = FontWeight.bold, + this.textAlign = TextAlign.left, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Text( + textAlign:textAlign, + title, + style: GoogleFonts.notoSans(fontSize: size, fontWeight: fontWeight), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index bcc58f7..2967aae 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,20 +1,23 @@ + import 'package:flutter/material.dart'; -void main() { - runApp(const MyApp()); -} +import 'BookPage/book_main.dart'; +import 'main_view_page.dart'; -class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); +void main() => runApp(MyApp()); +class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( - home: Scaffold( - body: Center( - child: Text("Book Store App"), - ), - ), + + return MaterialApp( + debugShowCheckedModeBanner: false, + title: 'Material App', + initialRoute: "/", + routes: { + "/":(context) => MainView(), + "/book": (context) => BookPage() + }, ); } } diff --git a/lib/main_view_page.dart b/lib/main_view_page.dart new file mode 100644 index 0000000..52bd156 --- /dev/null +++ b/lib/main_view_page.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; + +import 'Add Book/add_book_page.dart'; +import 'Books/Display_Books_page.dart'; +import 'Models/books_model.dart'; + +import 'package:get/get.dart'; + +class MainView extends StatefulWidget { + MainView({Key? key}) : super(key: key); + + @override + State createState() => _MainViewState(); +} + +class _MainViewState extends State { + @override + Widget build(BuildContext context) { + Rx index = Rx(0); + return Scaffold( + backgroundColor: const Color(0xFFFDFDFD), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 20), + child: Stack( + children: [ + Obx(() { + List pages = [ + DisplayBooksPage( + appbar: true, + fullBooks: Rx>(Books().allBooks), + pageTitle: "Book List"), + DisplayBooksPage( + appbar: false, + fullBooks: Rx>(Books().cartBooks), + pageTitle: "Cart"), + DisplayBooksPage( + appbar: false, + fullBooks: Rx>(Books().savedBooks), + pageTitle: "Saved Books"), + AddBookPage() + ]; + return pages[index.value]; + }), + // pages[3], + Padding( + padding: const EdgeInsets.all(8.0), + child: Align( + alignment: Alignment.bottomCenter, + child: ClipRRect( + borderRadius: BorderRadius.circular(22), + child: Obx(() { + return BottomNavigationBar( + currentIndex: index.value, + onTap: (currentIndex) { + index.value = currentIndex; + }, + type: BottomNavigationBarType.fixed, + selectedItemColor: Colors.black, + unselectedItemColor: Colors.grey, + backgroundColor: Colors.white, + showSelectedLabels: false, + showUnselectedLabels: false, + items: [ + BottomNavigationBarItem( + icon: Container( + padding: const EdgeInsets.symmetric(vertical: 10), + child: const Icon( + Icons.home_outlined, + size: 30, + ), + ), + label: "Home", + ), + const BottomNavigationBarItem( + icon: Icon( + Icons.shopping_cart_outlined, + size: 30, + ), + label: "Cart", + ), + const BottomNavigationBarItem( + icon: Icon( + Icons.bookmark_border, + size: 30, + ), + label: "Saved Books", + ), + const BottomNavigationBarItem( + icon: Icon( + Icons.add, + size: 30, + ), + label: "Add Book"), + ], + ); + }), + ), + ), + ) + ], + ), + ), + ), + ); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..f419d26 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,10 @@ import FlutterMacOS import Foundation +import path_provider_macos +import sqflite func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 7bc8bdd..538fb58 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -15,6 +15,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" characters: dependency: transitive description: @@ -43,6 +64,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,11 +85,39 @@ 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 source: sdk version: "0.0.0" + flutter_blurhash: + dependency: transitive + description: + name: flutter_blurhash + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.0" flutter_lints: dependency: "direct dev" description: @@ -74,6 +130,34 @@ packages: description: flutter source: sdk version: "0.0.0" + get: + dependency: "direct dev" + 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: @@ -102,6 +186,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.7.0" + octo_image: + dependency: transitive + description: + name: octo_image + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" path: dependency: transitive description: @@ -109,6 +200,90 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.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" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.1" + 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" + rxdart: + dependency: transitive + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.27.5" sky_engine: dependency: transitive description: flutter @@ -121,6 +296,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.2" + sqflite: + dependency: transitive + description: + name: sqflite + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3+1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1+1" stack_trace: dependency: transitive description: @@ -142,6 +331,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + synchronized: + dependency: transitive + description: + name: synchronized + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0+2" term_glyph: dependency: transitive description: @@ -156,6 +352,20 @@ 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" + uuid: + dependency: transitive + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.6" vector_math: dependency: transitive description: @@ -163,5 +373,20 @@ 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" sdks: dart: ">=2.17.6 <3.0.0" + flutter: ">=3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index cd0f457..443356c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,4 +1,4 @@ -name: book_store_app +name: book_app description: A new Flutter project. # The following line prevents the package from being accidentally published to @@ -34,10 +34,13 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 + google_fonts: ^3.0.1 + cached_network_image: ^3.2.1 dev_dependencies: flutter_test: sdk: flutter + get: ^4.6.5 # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is @@ -58,9 +61,10 @@ 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/ + # - Assets/Images/placeholder.jpg + # - 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