This is an EBook flutter application where you can show all types of books in one place to readers. This will show Book list, Book details like Book Name, Author name, Price, Free or Paid. If the Free book it will load the PDF inside application then readers can read the book.
This Application developed with Flutter as Frontend and PHP as Backend.
Flutter app contains below screens
PHP : This project contains below files as created with Core PHP
App Architecture
This project we build with Bloc pattern with cubit to maintain the state of the application.
Step 1: Create Flutter application with your favorite IDE, here we used Android studio.
Step 2: App communicates with backend, so let's creat a class to add your api end points
This class under core/apis folder
class ApiConstants{ ApiConstants._(); static const String base = "BASE_URL"; static const String book_list = "bookbycat"; static const String book_details = "bookdetails"; static const String book_category = "categories"; static const String book_search = "booksearch"; } |
Step 3: To communicate with server we need to add network libraries to our application, in this we used
Lets add required dependencies to pubspec.yaml file
dependencies: flutter: sdk: flutter google_fonts: ^2.1.0 dio: ^4.0.0 flutter_bloc: ^7.1.0 url_launcher: ^6.0.10 flutter_html: ^2.0.0 connectivity_plus: ^1.1.0 flutter_svg: ^0.22.0 |
Step 3: Let's create ClientRequest class to manage network call
This class under core/apis folder
import 'package:aone/core/apis/constants.dart'; import 'package:dio/dio.dart'; import 'fake_data.dart'; class ClientRequest{ late final Dio dio; ClientRequest(this.dio); get(String path,String? token, Map?query,bool fake) async{ if(fake) { return getFakeData(path); } dynamic response; response=await dio.get(ApiConstants.base+path+".php", queryParameters: query, options: Options( method: 'GET', headers: token != null ? {'Authorization': token} : null, )); if (response?.statusCode == 200) { return response.data; } else if (response?.statusCode == 401) { throw Exception("UnAuthorized"); } else { throw Exception(response.statusMessage); } } } |
In the above class we were created a fake data call if we doesn't want to get data from server.
fake.dart file should be like below. This class under core/apis folder
import 'package:aone/core/apis/constants.dart'; dynamic getFakeData(String path){ switch(path) { case ApiConstants.book_list: return getBookList(); case ApiConstants.book_category: return getCategory(); case ApiConstants.book_search: return getBookList(); } } getBookList(){ return {"result": [ { "id":"1", "title":"BookTitle1", "author":"Author Name", "is_paid":1, "price":200, "img":"", "thumb_nail":"", "details":"Book Details", "source":"", "download_path":"", "category_id":1, "catebory_name":"category name" }, { "id":"2", "title":"BookTitle2", "author":"Author Name", "is_paid":1, "price":200, "img":"", "thumb_nail":"", "details":"Book Details", "source":"", "download_path":"", "category_id":1, "catebory_name":"category name" }, { "id":"3", "title":"BookTitle3", "author":"Author Name", "is_paid":0, "price":200, "img":"", "thumb_nail":"", "details":"Book Details", "source":"", "download_path":"", "category_id":2, "catebory_name":"category name" }, { "id":"3", "title":"BookTitle3", "author":"Author Name", "is_paid":0, "price":200, "img":"", "thumb_nail":"", "details":"Book Details", "source":"", "download_path":"", "category_id":2, "catebory_name":"category name" }, { "id":"3", "title":"BookTitle3", "author":"Author Name", "is_paid":0, "price":200, "img":"", "thumb_nail":"", "details":"Book Details", "source":"", "download_path":"", "category_id":2, "catebory_name":"category name" } ]}; } getCategory(){ return {"category":[ {"id":"1","name":"IT Books"}, {"id":"2","name":"Fun"}, {"id":"3","name":"CBSE"}, {"id":"4","name":"Quotation"}, {"id":"5","name":"Quotation"}, ]}; } |
Step 4: Now lets create route_page,settings page app_theme and custom_appbar pages. These files under core folder
route_page.dart
import 'package:aone/presentation/journey/screens/book_details.dart'; import 'package:aone/presentation/journey/screens/book_list.dart'; import 'package:aone/presentation/journey/screens/error_page.dart'; import 'package:aone/presentation/journey/screens/filter_books.dart'; import 'package:aone/presentation/journey/screens/homepage.dart'; import 'package:flutter/material.dart'; final GlobalKey rootNavKey = GlobalKey(); class RouteGenerator { static RoutegenerateRoute(RouteSettings settings){ final dynamic args = settings.arguments; switch(settings.name) { case AppRoutes.landing: return MaterialPageRoute(builder: (_)=>HomePage()); case AppRoutes.booklist: return MaterialPageRoute(builder: (_)=>BookList(args)); case AppRoutes.bookdetails: return MaterialPageRoute(builder: (_)=>BookDetails(args)); case AppRoutes.filter: return MaterialPageRoute(builder: (_)=>FilterBook()); case AppRoutes.error: return MaterialPageRoute(builder: (_)=>ErrorPage(message: "No Network")); default: return _errorRoute(args); } } static _errorRoute(dynamic args) { return MaterialPageRoute( builder: (_) => ErrorPage(message: args), ); } } class AppRoutes { static const String landing = '/'; static const String error = '/error'; static const String login = '/login'; static const String signup = '/signup'; static const String booklist = '/booklist'; static const String bookdetails = '/bookdetails'; static const String filter = '/filter'; } |
settings page
class Strings{ const Strings(); static final String app_name="AOne"; static final String search_books="Search Books..."; static final String enter_book_name="Enter a book name"; static final String see_all='More'; } |
app_themes.dart
import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; class AppColor { const AppColor._(); //darkColor static const Color darkUnselectedColor = Colors.white60; static const Color darkPrimary = Color(0xFF32CD32); static const Color darkAccent = Color(0xFF32CD32); static const Color darkBackgroundColor = Color(0xFF202020); static const Color darkCanvasColor = Colors.black; static const Color darkCardColor = Colors.black; static const Color darkBorderColor = Colors.white60; static const Color darkPrimaryTextColor = Colors.white; static const Color darkSecondaryTextColor = Colors.white70; //lightColor static const Color lightUnselectedColor = Colors.black54; static const Color lightPrimary = Color(0xFF32CD32); static const Color lightAccent = Color(0xFF32CD32); static const Color lightBackgroundColor = Color(0xFFF5F5F5); static const Color lightCanvasColor = Colors.white; static const Color lightCardColor = Colors.white; static const Color lightBorderColor = Colors.black54; static const Color lightPrimaryTextColor = Colors.black; static const Color lightSecondaryTextColor = Colors.black87; //button colors static const Color buttonColor = Color(0xFF32CD32); static const Color buttonTextColor = Colors.white; static const Color tagTextColor = Colors.black54; } class AppTheme { const AppTheme._(); static ThemeData init(BuildContext context, Themes theme) { return ThemeData( unselectedWidgetColor: theme == Themes.dark ? AppColor.darkUnselectedColor : AppColor.lightUnselectedColor, primaryColor: theme == Themes.dark ? AppColor.darkPrimary : AppColor.lightPrimary, accentColor: theme == Themes.dark ? AppColor.darkAccent : AppColor.lightAccent, scaffoldBackgroundColor: theme == Themes.dark ? AppColor.darkBackgroundColor : AppColor.lightBackgroundColor, brightness: theme == Themes.dark ? Brightness.dark : Brightness.light, cardTheme: CardTheme( color: theme == Themes.dark ? AppColor.darkCardColor : AppColor.lightCardColor, ), dividerColor: theme == Themes.dark ? AppColor.darkBorderColor : AppColor.lightBorderColor, buttonColor: AppColor.buttonColor, visualDensity: VisualDensity.adaptivePlatformDensity, secondaryHeaderColor: theme == Themes.dark ? Colors.white : Colors.black, textTheme: theme == Themes.dark ? AppTheme.darkTextTheme() : AppTheme.lightTextTheme(), appBarTheme: AppBarTheme( elevation: 0, iconTheme: IconThemeData( color: theme == Themes.dark ? Colors.white : Colors.black, ), backgroundColor: theme == Themes.dark ? AppColor.darkCanvasColor : AppColor.lightCanvasColor, textTheme: theme == Themes.dark ? AppTheme.darkTextTheme() : AppTheme.lightTextTheme(), ), canvasColor: theme == Themes.dark ? AppColor.darkCanvasColor : AppColor.lightCanvasColor, inputDecorationTheme: InputDecorationTheme( border: InputBorder.none, hintStyle: theme == Themes.dark ? Theme.of(context).textTheme.darkHintStyle : Theme.of(context).textTheme.lightHintStyle, ), ); } static TextTheme get _poppinsTextTheme => GoogleFonts.latoTextTheme(); static TextStyle? get _headline6 => _poppinsTextTheme.headline6?.copyWith( fontSize: 20.0, ); static TextStyle? get _headline5 => _poppinsTextTheme.headline5?.copyWith( fontSize: 24, ); static TextStyle? get _subtitle1 => _poppinsTextTheme.subtitle1?.copyWith( fontSize: 16, ); static TextStyle? get _button => _poppinsTextTheme.button?.copyWith( fontSize: 14, color: AppColor.buttonTextColor, ); static TextStyle? get _bodyText2 => _poppinsTextTheme.bodyText2?.copyWith( fontSize: 14, wordSpacing: 0.25, letterSpacing: 0.25, height: 1.5, ); static TextStyle? get _caption => _poppinsTextTheme.caption?.copyWith( fontSize: 14, wordSpacing: 0.25, letterSpacing: 0.25, height: 1.5, ); static TextTheme darkTextTheme() => TextTheme( headline6: _headline6?.copyWith(color: AppColor.darkPrimaryTextColor), headline5: _headline5?.copyWith(color: AppColor.darkPrimaryTextColor), subtitle1: _subtitle1?.copyWith(color: AppColor.darkPrimaryTextColor), bodyText2: _bodyText2?.copyWith(color: AppColor.darkPrimaryTextColor), button: _button, caption: _caption?.copyWith(color: AppColor.darkPrimaryTextColor), ); static TextTheme lightTextTheme() => TextTheme( headline6: _headline6?.copyWith(color: AppColor.lightPrimaryTextColor), headline5: _headline5?.copyWith(color: AppColor.lightPrimaryTextColor), subtitle1: _subtitle1?.copyWith(color: AppColor.lightPrimaryTextColor), bodyText2: _bodyText2?.copyWith(color: AppColor.lightPrimaryTextColor), button: _button, caption: _caption?.copyWith(color: AppColor.lightPrimaryTextColor), ); } extension ThemeTextExtension on TextTheme { TextStyle? get darkHintStyle => bodyText2?.copyWith(color: AppColor.darkSecondaryTextColor); TextStyle? get lightHintStyle => subtitle2?.copyWith(color: AppColor.lightSecondaryTextColor); TextStyle? get tabSelectedTextStyle => caption?.copyWith(fontSize: 12); TextStyle? get tabUnSelectedTextStyle => caption?.copyWith(fontSize: 10); TextStyle? get cardTextStyle => caption?.copyWith( fontSize: 10, fontWeight: FontWeight.w700, color: Colors.black, ); TextStyle? get tagTextStyle => caption?.copyWith( fontSize: 10, ); TextStyle? get ratingTextStyle => caption?.copyWith( fontSize: 10, ); } enum Themes { dark, light } |
custom-appbar
import 'package:aone/core/app_strings.dart'; import 'package:aone/presentation/journey/screens/search.dart'; import 'package:flutter/material.dart'; class CustomAppBar extends StatelessWidget{ String type; CustomAppBar(this.type); @override Widget build(BuildContext context) { return GestureDetector( onTap: () =>showSearch( context: context, delegate: ApSearchDelegate(type), ), child: AppBar( title: TextField( enabled: false, decoration: InputDecoration(hintText: Strings.search_books),textAlign: TextAlign.center,), actions:[ Icon(Icons.search), ], backgroundColor: Theme.of(context).appBarTheme.backgroundColor, ), ); } } |
Step 5: book,category and filer_model, these are the model classes used in application. We will create these classes under data/entities folder
book.dart
class Book{ late String id; late String title; late String author; late String img; late int is_paid; late int price; late String thumb_nail; late String details; late String source; late String download_path; late int category_id; late String catebory_name; Book(this.id,this.title,this.author,this.img,this.is_paid,this.price,this.thumb_nail,this.details,this.source,this.download_path,this.category_id,this.catebory_name); factory Book.fromJSON(Mapjson) { return Book( json['id'], json['title'], json['author'], json.containsKey("img")?json['img']:"", json.containsKey("is_paid")?(int.parse(json['is_paid'])):0, json.containsKey("price")?(int.parse(json['price'] )):0, json['thumb_nail'], json.containsKey("details")?json['details']:"", json.containsKey("source")?json['source']:"", json.containsKey("download_path")?json['download_path']:"", int.parse(json['category_id'] ), json.containsKey("catebory_name")?json['catebory_name']:"" ); } } class BookListResult{ List booklist; BookListResult(this.booklist); factory BookListResult.fromJSON(Map json){ var list=List.empty(growable: true); if(json['booklist']!=null) { json["booklist"].forEach((value) { list.add(Book.fromJSON(value)); }); } return BookListResult(list); } } |
category.dart
class Category{ Category(this.id,this.name,this.selected); String id; String name; bool selected=false; factory Category.fromJSON(Mapjson) { return Category(json['id'],json['name'],false); } } class CategoryList{ List catlist; CategoryList(this.catlist); factory CategoryList.fromJSON(Mapjson) { var datalist=List.empty(growable: true); json['categorylist'].forEach((v){ datalist.add(Category.fromJSON(v)); }); return CategoryList(datalist); } } |
filter.dart
class BookF{ String filter_id; String filter_title; bool isSelected; BookF(this.filter_id,this.filter_title,this.isSelected); } |
Step 6:
We are done with Network and Model classes let's come to presentation layer. This present layer contains blocs and journeys classes.
Let's create bloc classes. Every bloc class contains two classes one is for bloc and other is for state. In this application we have below bloc classes book,category and connectivity blocs, these classes contains state classes respectively
book cubit
import 'package:aone/core/apis/client_request.dart'; import 'package:aone/core/apis/constants.dart'; import 'package:aone/core/apis/fake_data.dart'; import 'package:aone/data/entities/book.dart'; import 'package:aone/data/entities/category.dart'; import 'package:bloc/bloc.dart'; import 'package:meta/meta.dart'; import 'dart:convert'; part 'book_state.dart'; class BookCubit extends Cubit { ClientRequest request; BookCubit(this.request) : super(BookInitial()); int page=0; Future<void>getBookList(String id, int start) async { if(state is BookLoading) return; final current_state=state; var oldBooks=[]; if(current_state is BookLoaded) { oldBooks=current_state.booklist; } emit(BookLoading(oldBooks,isFirst: (page==1))); Maphm=Map(); hm['id']=id; hm['start']=page*6; final responseString= await request.get(ApiConstants.book_list, "", hm, false); page++; print(responseString); var newLIst=BookListResult.fromJSON(json.decode(responseString)).booklist; var oldList=(state as BookLoading).oldlist; oldList.addAll(newLIst); emit( BookLoaded(oldList)) ; } Future<void>getBookListf(String id) async { Maphm=Map(); hm['id']=id; final responseString= await request.get(ApiConstants.book_list, "", hm, false); print(responseString); emit( BookLoaded(BookListResult.fromJSON(json.decode(responseString)).booklist)) ; } Future<void>getBookDetail(String id) async { Maphm=Map(); hm['id']=id; final responseString= await request.get(ApiConstants.book_details, "", hm, false); print(responseString); emit( BookLoaded(BookListResult.fromJSON(json.decode(responseString)).booklist)) ; } Future<void>searchBook(String search,var filter_ids,var isFirstPage) async { if(state is BookLoading) return; final current_state=state; var oldBooks=[]; if(current_state is BookLoaded) { if(isFirstPage) current_state.booklist.clear(); oldBooks=current_state.booklist; } if(isFirstPage) page=0; emit(BookLoading(oldBooks,isFirst: isFirstPage)); Maphm=Map(); hm['search']=search; if(filter_ids!=null) hm['filter_ids']=filter_ids; hm['start']=page*6; final responseString= await request.get(ApiConstants.book_search, "", hm, false); page++; print(responseString); var newLIst=BookListResult.fromJSON(json.decode(responseString)).booklist; var oldList=(state as BookLoading).oldlist; oldList.addAll(newLIst); emit( BookLoaded(oldList)) ; print("search on search $search"); /* final responseString= await request.get(ApiConstants.book_search, "", hm, false); print(responseString); emit( BookLoaded(BookListResult.fromJSON(json.decode(responseString)).booklist)) ;*/ } } |
book_state
part of 'book_cubit.dart'; @immutable abstract class BookState { const BookState(); } class BookInitial extends BookState {} class BookLoaded extends BookState { final Listbooklist; const BookLoaded(this.booklist); } class BookLoading extends BookState { final Listoldlist; final bool isFirst; const BookLoading(this.oldlist,{this.isFirst=false}); } |
category_cubit
import 'dart:convert'; import 'package:aone/core/apis/client_request.dart'; import 'package:aone/core/apis/constants.dart'; import 'package:aone/data/entities/category.dart'; import 'package:bloc/bloc.dart'; import 'package:meta/meta.dart'; part 'category_state.dart'; class CategoryCubit extends Cubit { ClientRequest request; CategoryCubit(this.request) : super(CategoryInitial()); Future<void>getCategoryList() async { final responseString= await request.get(ApiConstants.book_category, "", null, false); print(responseString); emit( CategoryLoaded(CategoryList.fromJSON(json.decode(responseString)).catlist)) ; } } |
category_state
part of 'category_cubit.dart'; @immutable abstract class CategoryState { const CategoryState(); } class CategoryInitial extends CategoryState {} class CategoryLoaded extends CategoryState { final Listcatlist; const CategoryLoaded(this.catlist); } |
Connectivity_cubit
import 'package:bloc/bloc.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; part 'connectivity_state.dart'; class ConnectivityCubit extends Cubit { Connectivity _connectivity; ConnectivityCubit(this._connectivity) : super(ConnectivityInitial()); Future<void>getConnectivityStatus() async { late ConnectivityResult result; try { result = await _connectivity.checkConnectivity(); } on PlatformException catch (e) { print(e.toString()); return; } emit( ConnectivityChanged(result)) ; } } |
Connectivity_state
part of 'connectivity_cubit.dart'; @immutable abstract class ConnectivityState {} class ConnectivityInitial extends ConnectivityState {} class ConnectivityChanged extends ConnectivityState{ ConnectivityResult result; ConnectivityChanged(this.result); } |
Step 7: Let's create journey classes
Journeys contains two folder
Widgets: book_widget,book_widget_v,header_text,loading
Screens :book_details,book_list,error_page,filer_books,homepage,network_error,search screens
book_details.dart
import 'package:aone/core/apis/client_request.dart'; import 'package:aone/data/entities/book.dart'; import 'package:aone/presentation/blocs/book_cubit.dart'; import 'package:aone/presentation/journey/widgets/header_text.dart'; import 'package:aone/presentation/journey/widgets/loading.dart'; import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:url_launcher/url_launcher.dart'; class BookDetails extends StatelessWidget{ BookCubit _bookCubit=BookCubit(ClientRequest(Dio())); Book book; BookDetails(this.book); @override Widget build(BuildContext context) { Size size=MediaQuery.of(context).size; return Scaffold( appBar: AppBar(title: Text("Book Details"),), body: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Stack( alignment: Alignment.topCenter, children: [ Container( width: double.infinity, height: 250, decoration: BoxDecoration( gradient: RadialGradient( colors: [Colors.white,Theme.of(context).primaryColorLight/* const Color(0xFF02BB9F)*/] )/*LinearGradient( colors: [Colors.lightGreen,Colors.lightGreenAccent, Colors.black87,Colors.black54])*/ /*image: DecorationImage( fit: BoxFit.fill, image: AssetImage("assets/images/aone.JPG"), )*/, borderRadius: BorderRadiusDirectional.only(bottomStart: Radius.circular(10),bottomEnd: Radius.circular(10))), child: Padding(padding: EdgeInsets.symmetric(horizontal: 24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 12,), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded(child: Text(book.title,style: Theme.of(context).textTheme.headline5,)), Image.network(book.img, height: 150,), ], ), SizedBox(height: 10,), Expanded(child: Text(book.author)), Expanded(child: Text((book.is_paid==1)?"${book.price}":"Free")), ], ),), ), Positioned( bottom: 10, right: 10, child: FloatingActionButton( elevation: 20, backgroundColor: Theme.of(context).scaffoldBackgroundColor, onPressed: (){ _launchInWebViewWithJavaScript((book.is_paid==1)?book.source:book.download_path); },child: Text((book.is_paid==1)?"Buy ":"Read"),)) ], ), BlocProvider.value(value: _bookCubit..getBookDetail(book.id), child: BlocBuilder(builder: (_,state){ if(state is BookInitial) { return Loading(); }else if(state is BookLoaded) { print("state.booklist.length ${state.booklist.length}"); if(state.booklist.length>0) { return Padding( padding: const EdgeInsets.all(8.0), child: Card( color: Theme.of(context).cardColor, child: Padding( padding: const EdgeInsets.all(8.0), child: Html( shrinkWrap: true, data: state.booklist[0].details, onLinkTap: (url, ctx, map, el) { map.keys.forEach((element) { print("OnLink Tap element $element"); }); }, style: { }), )), ); } } return SizedBox.shrink(); })), ], ), ), ); } Future<void> _launchInWebViewWithJavaScript(String url) async { if (await canLaunch(url)) { await launch( url, forceSafariVC: true, forceWebView: true, enableJavaScript: true, ); } else { throw 'Could not launch $url'; } } } |
book_list.dart
import 'dart:async'; import 'package:aone/core/apis/client_request.dart'; import 'package:aone/core/custom_appbar.dart'; import 'package:aone/presentation/blocs/book_cubit.dart'; import 'package:aone/presentation/journey/widgets/book_widget.dart'; import 'package:aone/presentation/journey/widgets/book_widget_v.dart'; import 'package:aone/presentation/journey/widgets/loading.dart'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class BookList extends StatelessWidget{ ScrollController _controller=ScrollController(); GlobalKey_globalKey=GlobalKey(); String type; int start=0; bool isEnd=false; BookList(this.type); var booklist=[]; BookCubit _bookCubit=BookCubit(ClientRequest(Dio())); handleController(context) { _controller.addListener(() { if(_controller.position.atEdge) { if(_controller.position.pixels!=0) { print("Page at End");if(!isEnd) _bookCubit.getBookList(type,start); } } }); } @override Widget build(BuildContext context) { handleController(context); return Scaffold( key: _globalKey, appBar: AppBar(title: Text("ItBooks"),), body: Padding( padding: const EdgeInsets.all(8.0), child: BlocProvider.value(value: _bookCubit..getBookList(type,start), child: BlocBuilder(builder: (_,state){ print(state); var isLoading=false; if(state is BookLoading&& state.isFirst) { return Loading(); } if(state is BookLoading) { isLoading=true; booklist=state.oldlist; }else if(state is BookLoaded) { booklist=state.booklist; } if(booklist.length>0&&(booklist.length % 6)!=0) { isEnd=true; } return ListView.builder( controller: _controller, shrinkWrap: true, scrollDirection: Axis.vertical, itemCount: (booklist.length>0)?booklist.length+((isLoading)?1:0):0, itemBuilder: (_,index){ if(index<=booklist.length) return Container( width: MediaQuery.of(context).size.width, child: BookWidgetV( 140,booklist[index]) ); else { Timer(Duration(milliseconds: 30),(){ _controller.jumpTo(_controller.position.maxScrollExtent); }); return Loading(); } }); return SizedBox.shrink(); } ) ), ) ); } } |
error_page.dart
import 'package:flutter/material.dart'; class ErrorPage extends StatelessWidget{ String message; ErrorPage({required this.message}); @override Widget build(BuildContext context) { return Scaffold(body: Center(child: Text(message),),); } } |
filter_dart
import 'dart:convert'; import 'package:aone/data/entities/filter_model.dart'; import 'package:aone/presentation/journey/widgets/header_text.dart'; import 'package:flutter/material.dart'; class FilterBook extends StatefulWidget{ @override _FilterBookState createState() => _FilterBookState(); } class _FilterBookState extends State { Listselected=List.empty(growable: true); bool isPaid=false; bool isFree=false; bool isLatest=false; bool isOld=false; bool isFilter=false; List filter_three = [ BookF( "1","IT Books", false), BookF( "2","Horror", false), BookF( "3","Sports", false), BookF( "12","Horror", false), BookF( "13","Academic", false), ]; @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar( leading: MaterialButton( onPressed: (){ Navigator.pop(context,""); },child: Icon(Icons.arrow_back_ios,color:Colors.white)), backgroundColor: Colors.purple, actions: [ (isFilter||selected.length>0)?MaterialButton(onPressed: (){ setState(() { filter_three.forEach((element) { element.isSelected=false; }); selected.clear(); isFilter=false; isPaid=false; isFree=false; isLatest=false; isOld=false; }); },child: Icon(Icons.clear,color:Colors.white),):SizedBox.shrink(), (isFilter||selected.length>0)?MaterialButton(onPressed: (){ Mapfilters=Map(); filters['isPaid']=(isPaid)?1:0; filters['isFree']=(isFree)?1:0; filters['Latest']=(isLatest)?1:0 ; filters['Old']=(isOld)?1:0 ; filters['cat']=(selected); Navigator.pop(context, jsonEncode(filters)); },child: Text("Apply",style: TextStyle(color:Colors.white),)):SizedBox.shrink() ],), body: Padding( padding: const EdgeInsets.all(8.0), child: Card( child : Padding( padding: const EdgeInsets.all(8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MediumText("Sort By",Colors.red), SizedBox(height: 10,), Row(children: [ Checkbox( value: isPaid, onChanged: (value){ setState(() { isPaid=value!; isFilter=true; }); }), MediumText("Paid",Colors.red), ]),Row(children: [ Checkbox( value: isFree, onChanged: (value){ setState(() { isFree=value!; isFilter=true; }); }), MediumText("Free",Colors.red), ]), SizedBox(height: 20,), Divider(height: 2,color: Colors.grey,), MediumText("Sort By",Colors.deepPurple), SizedBox(height: 10,), Row(children: [ Checkbox( value: isLatest, onChanged: (value){ setState(() { isLatest=value!; }); }), MediumText("Latest",Colors.deepPurple), ]),Row(children: [ Checkbox( value: isOld, onChanged: (value){ setState(() { isOld=value!; }); }), MediumText("Old",Colors.deepPurple), ]), SizedBox(height: 20,), Divider(height: 2,color: Colors.grey,), MediumText("Filter By",Colors.blue), SizedBox(height: 10,), Wrap( spacing: 8, direction: Axis.horizontal, children: techChips(filter_three,Colors.blue), ), ], ), ), ), ), ); } List techChips (List _chipsList,color) { List chips = []; for (int i=0; i< _chipsList.length; i++) { Widget item = Padding( padding: const EdgeInsets.only(left:10, right: 5), child: FilterChip( selectedColor: color, label: Text(_chipsList[i].filter_title), labelStyle: TextStyle(color: Colors.white), backgroundColor: color, selected: _chipsList[i].isSelected, checkmarkColor: Colors.white, onSelected: (bool value) { if(value) { selected.add(_chipsList[i].filter_id); }else { selected.remove(_chipsList[i].filter_id); } setState(() { _chipsList[i].isSelected = value; }); }, ), ); chips.add(item); } return chips; } } |
homepage.dart
import 'dart:async'; import 'dart:convert'; import 'package:aone/core/apis/client_request.dart'; import 'package:aone/core/app_route.dart'; import 'package:aone/core/app_strings.dart'; import 'package:aone/core/custom_appbar.dart'; import 'package:aone/data/entities/category.dart'; import 'package:aone/presentation/blocs/book_cubit.dart'; import 'package:aone/presentation/blocs/category_cubit.dart'; import 'package:aone/presentation/blocs/connectivity_cubit.dart'; import 'package:aone/presentation/journey/screens/error_page.dart'; import 'package:aone/presentation/journey/screens/widgets/it_books.dart'; import 'package:aone/presentation/journey/widgets/loading.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'network_error.dart'; class HomePage extends StatelessWidget{ CategoryCubit _catCubit=CategoryCubit(ClientRequest(Dio())); ConnectivityResult _connectionStatus = ConnectivityResult.none; final Connectivity _connectivity = Connectivity(); ConnectivityCubit _connectivityCubit=ConnectivityCubit(Connectivity()); late StreamSubscription _connectivitySubscription; /* Future initConnectivity() async { late ConnectivityResult result; try { result = await _connectivity.checkConnectivity(); } on PlatformException catch (e) { print(e.toString()); return; } return _updateConnectionStatus(result); }*/ Future<void> _updateConnectionStatus(ConnectivityResult result) async { _connectivityCubit.emit(ConnectivityChanged(result)); } @override Widget build(BuildContext context) { // initConnectivity(); _connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus); return SafeArea( child: Scaffold( appBar: AppBar(actions: [ MaterialButton(onPressed: ()async { var result= await Navigator.pushNamed(context, AppRoutes.filter,); print(result); /* List _chipsList =result as List; print("String to Json: ${jsonEncode(_chipsList)}"); _chipsList.forEach((element) { print(element); });*/ },child: Icon(Icons.filter_tilt_shift_sharp),) ],), body: MultiBlocProvider( providers: [ BlocProvider.value(value: _catCubit..getCategoryList(), ), BlocProvider.value(value: _connectivityCubit..getConnectivityStatus(),) ], child:BlocBuilder(builder: (_,state) { if(state is ConnectivityChanged) { print(" state.result ${state.result}"); if(state.result==ConnectivityResult.none) { // return NetworkError(); } } return BlocBuilder(builder: (_,state){ return SingleChildScrollView( child: Column( children: [ Container( height: 150, decoration:BoxDecoration( image: DecorationImage( fit: BoxFit.fill, image: AssetImage("assets/images/aone.JPG"), ), ), ), Container(child: CustomAppBar(""), decoration: BoxDecoration(border: Border(bottom: BorderSide(width: 1))),), (state is BookInitial)?Loading():(state is CategoryLoaded)?Column(children: loadList(state),):SizedBox.shrink(), ], ), ); },); }) ) ), ); } List loadList(state) { var list=List.empty(growable: true); state.catlist.forEach((element) { list.add(ItBookBox(element));}); return list; } } |
network_error.dart
import 'package:aone/presentation/journey/widgets/header_text.dart'; import 'package:flutter/material.dart'; class NetworkError extends StatelessWidget{ @override Widget build(BuildContext context) { return Scaffold( body: Container( decoration: BoxDecoration( image: DecorationImage( fit: BoxFit.fill, image: AssetImage("assets/images/aone.JPG"), ), ), child: Center( child: Card( elevation: 40, shadowColor: Colors.green, margin: EdgeInsets.symmetric(horizontal: 20), child: Container( height: 250, decoration: BoxDecoration( gradient: RadialGradient( colors: [Colors.white,Colors.red/* const Color(0xFF02BB9F)*/] ), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ _getCloseButton(context), Padding( padding: EdgeInsets.fromLTRB( 20,10, 20, 0), child: Column( children: [ Icon(Icons.warning_amber_sharp,size: 64,color: Colors.red,), SizedBox( height: 15, ), Text( "Network Error",style: TextStyle(fontSize: 28,color: Colors.deepPurple), ), SizedBox( height: 10, ), MediumText( "Please check your connectivity", Colors.white ), SizedBox( height: 20, ), ], ), ) ], ), ), ), ), ) ); } _getCloseButton(context) { return Padding( padding: const EdgeInsets.fromLTRB(0, 10, 10, 0), child: GestureDetector( onTap: () { }, child: Container( alignment: FractionalOffset.topRight, child: GestureDetector(child: Icon(Icons.clear,color: Colors.red,), onTap: (){ },), ), ), ); } } |
search.dart
import 'dart:async'; import 'package:aone/core/apis/client_request.dart'; import 'package:aone/core/app_route.dart'; import 'package:aone/core/app_strings.dart'; import 'package:aone/presentation/journey/widgets/book_widget_v.dart'; import 'package:aone/presentation/journey/widgets/loading.dart'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:aone/presentation/blocs/book_cubit.dart'; import 'package:aone/presentation/journey/widgets/book_widget.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class ApSearchDelegate extends SearchDelegate{ BookCubit _bookCubit=BookCubit(ClientRequest(Dio())); var result; String type; int start=0; bool isAddListner=false; bool isEnd=false; bool isFirst=true; var booklist=[]; ScrollController _controller=ScrollController(); ApSearchDelegate( this.type) : super(searchFieldLabel: Strings.enter_book_name); handleController() { _controller.addListener(() { if(_controller.position.atEdge) { if(_controller.position.pixels!=0) { print("Page at End"); if(!isEnd) _bookCubit.searchBook(type,result,isFirst); } } }); } @override ThemeData appBarTheme(BuildContext context) => Theme.of(context); @override List buildActions(BuildContext context) { return [ if (query.isNotEmpty) IconButton( icon: Icon( Icons.clear, color: Theme.of(context).dividerColor, ), onPressed: () { isFirst=true; booklist.clear(); query = ''; } ), MaterialButton(onPressed: ()async { result= await Navigator.pushNamed(context, AppRoutes.filter,); isFirst=true; isEnd=false; booklist.clear(); _bookCubit.searchBook(type,result,isFirst); print(result); },child: Icon(Icons.filter_tilt_shift_sharp),) ]; } @override Widget buildLeading(BuildContext context) { return GestureDetector( onTap: () => close(context, null), child: Icon( Icons.arrow_back_ios, color: Theme.of(context).dividerColor, ), ); } @override Widget buildResults(BuildContext context) { FocusScope.of(context).unfocus(); _bookCubit.searchBook(type,result,isFirst); return SizedBox.shrink(); } @override Widget buildSuggestions(BuildContext context) { start=0; isFirst=true; //FocusScope.of(context).unfocus(); if(!isAddListner) { handleController(); isAddListner=true; } return Padding( padding: const EdgeInsets.all(8.0), child: BlocProvider.value(value: _bookCubit..searchBook(query,result,isFirst), child: BlocBuilder(builder: (_,state){ print(state); var isLoading=false; if(state is BookLoading&& state.isFirst) { return Loading(); } if(state is BookLoading) { isLoading=true; booklist=state.oldlist; }else if(state is BookLoaded) { booklist=state.booklist; } if(booklist.length>0&&(booklist.length % 6)!=0) { isEnd=true; } isFirst=!(booklist.length>0); print(state); if(state is BookInitial) { return Loading(); }else if(state is BookLoaded) { return ListView.builder( controller: _controller, shrinkWrap: true, scrollDirection: Axis.vertical, itemCount:(booklist.length>0)?booklist.length+((isLoading)?1:0):0, itemBuilder: (_,index){ if(index<=booklist.length) return Container( width: MediaQuery.of(context).size.width, child: BookWidgetV( 140,state.booklist[index]) ); else { Timer(Duration(milliseconds: 100),(){ //_controller.jumpTo(_controller.position.maxScrollExtent); }); return Loading(); } });; } return SizedBox.shrink(); } ) ), ); } } |
it_books.dart
import 'package:aone/core/apis/client_request.dart'; import 'package:aone/core/app_route.dart'; import 'package:aone/core/app_strings.dart'; import 'package:aone/data/entities/category.dart'; import 'package:aone/presentation/blocs/book_cubit.dart'; import 'package:aone/presentation/journey/widgets/book_widget.dart'; import 'package:aone/presentation/journey/widgets/header_text.dart'; import 'package:aone/presentation/journey/widgets/loading.dart'; import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class ItBookBox extends StatelessWidget{ Category category; BookCubit _bookCubit=BookCubit(ClientRequest(Dio())); ItBookBox(this.category); @override Widget build(BuildContext context) { return BlocProvider.value(value: _bookCubit..getBookListf(category.id), child: BlocBuilder(builder: (_,state){ if(state is BookInitial) { return Loading(); }else if(state is BookLoaded) { print("state.booklist.length ${state.booklist.length}"); if(state.booklist.length>0) { return Container( margin: EdgeInsets.symmetric(vertical: 8,horizontal: 12), height: 250, child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ HeaderText(category.name), TextButton( onPressed:(){ print("On Presses"); Navigator.pushNamed(context, AppRoutes.booklist, arguments: category.id,); /*rootNavKey.currentState?.pushNamed( AppRoutes.booklist, arguments: 1, );*/ }, child:Text( Strings.see_all, style: Theme.of(context).textTheme.caption,) ) ], ), Divider(height: 1,), Expanded(child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: state.booklist.length, itemBuilder: (_,index){ return Container( width: 140, child: BookWidget( 140, 100,state.booklist[index]) ); })) ], ), ); } } return SizedBox.shrink(); },),) ; } } |
widget classes
book_widget.dart
import 'package:aone/core/app_route.dart'; import 'package:aone/data/entities/book.dart'; import 'package:aone/data/entities/category.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; class BookWidget extends StatelessWidget{ double height=100; double width=140; Book book; BookWidget(this.width,this.height, this. book); @override Widget build(BuildContext context) { return Card( color: Theme.of(context).cardColor, elevation: 5, child: Column( children: [ (book.thumb_nail.endsWith("svg"))?SvgPicture.network( book.thumb_nail, width: width, height: height, placeholderBuilder: (BuildContext context) => Container( padding: const EdgeInsets.all(30.0), child: const CircularProgressIndicator()), ): Container( width: width, height: height, decoration: BoxDecoration( image: DecorationImage( fit: BoxFit.fill, image: NetworkImage(book.thumb_nail), ), ), ), // FittedBox(child: Image.asset("assets/images/aone.JPG",width: 140,height: 80,),fit: BoxFit.fill,), Expanded(child: Stack(children: [ Padding( padding: const EdgeInsets.only(left:8.0), child: Text(book.title,maxLines: 2,), ), Align(alignment: Alignment.bottomCenter, child: MaterialButton( height: 28, shape: RoundedRectangleBorder(side: BorderSide(width: 1,color:Colors.black ),borderRadius: BorderRadius.all(Radius.circular(8))), onPressed: (){Navigator.pushNamed(context, AppRoutes.bookdetails, arguments: book,);},child: Text("View"),)/*Container( margin: EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration(color: Theme.of(context).scaffoldBackgroundColor, shape:BoxShape.rectangle,borderRadius: BorderRadius.all(Radius.circular(8)),border:Border(bottom: BorderSide(width: 1),top: BorderSide(width: 1),left: BorderSide(width: 1),right: BorderSide(width: 1))), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16,vertical: 1), child: InkWell(child: Text("View"),onTap: (){ Navigator.pushNamed(context, AppRoutes.bookdetails, arguments: book,); },), ))*/) ],)) ], ), ); } } |
book_widget_v.dart
import 'package:aone/core/app_route.dart'; import 'package:aone/data/entities/book.dart'; import 'package:aone/data/entities/category.dart'; import 'package:aone/presentation/journey/widgets/header_text.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; class BookWidgetV extends StatelessWidget{ double height=100; Book book; BookWidgetV(this.height, this. book); @override Widget build(BuildContext context) { return Card( color: Theme.of(context).cardColor, elevation: 5, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ (book.thumb_nail.endsWith("svg"))?SvgPicture.network( book.thumb_nail, width: 120, height: height, placeholderBuilder: (BuildContext context) => Container( padding: const EdgeInsets.all(30.0), child: const CircularProgressIndicator()), ): Container( width: 120, height: height, decoration: BoxDecoration( image: DecorationImage( fit: BoxFit.fill, image: NetworkImage(book.thumb_nail), ), ), ), // FittedBox(child: Image.asset("assets/images/aone.JPG",width: 140,height: 80,),fit: BoxFit.fill,), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.only(left:8.0,right:8.0), child: MediumText(book.title,Colors.black), ), SizedBox(height: 10,), Padding( padding: const EdgeInsets.only(left:8.0,right:8.0), child: Text(" ${book.author}",style: TextStyle(color: Colors.brown),), ), Padding( padding: const EdgeInsets.only(left:12.0,right:8.0), child: MaterialButton( height: 28, shape: RoundedRectangleBorder(side: BorderSide(width: 1,color:Colors.black ),borderRadius: BorderRadius.all(Radius.circular(8))), onPressed: (){Navigator.pushNamed(context, AppRoutes.bookdetails, arguments: book,);},child: Text("View"),), ) ], ), ), ], ), ); } } |
header_text.dart
import 'package:flutter/material.dart'; class HeaderText extends StatelessWidget{ String title; HeaderText(this.title); @override Widget build(BuildContext context) { return Text(title,style: TextStyle(fontSize: 20),); }} class MediumText extends StatelessWidget{ String title; Color color; MediumText(this.title, this.color); @override Widget build(BuildContext context) { return Text(title,style: TextStyle(fontSize: 17,color:this.color),); }} |
loading.dart
import 'package:flutter/material.dart'; class Loading extends StatelessWidget{ @override Widget build(BuildContext context) { return Container( height: 100, child: Center(child: CircularProgressIndicator( color: Colors.lightGreenAccent,),), ); } } |
Step 8:
Now finally add our main.dart
import 'dart:async'; import 'package:aone/core/app_themes.dart'; import 'package:aone/presentation/journey/screens/error_page.dart'; import 'package:aone/presentation/journey/screens/homepage.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'core/app_route.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( key: rootNavKey, title: 'Aone', theme: AppTheme.init(context, Themes.light), initialRoute: AppRoutes.landing, onGenerateRoute: RouteGenerator.generateRoute, ); } } |
Now let's run the application
Article Contributed By :
|
|
|
|
1663 Views |