Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,27 @@ import 'package:provider/provider.dart';
import 'screens/home_screen.dart';
import 'services/google_books_api.dart';

// Entry point of the Flutter application
void main() {
runApp(const MyApp());
}

// Root widget of the application
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return MultiProvider(
// Providing dependencies to the widget tree using MultiProvider
providers: [
// Registering GoogleBooksApi as a ChangeNotifier for state management
ChangeNotifierProvider(create: (_) => GoogleBooksApi()),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'EBook reader Application',

theme: ThemeData(
primarySwatch: Colors.blue,
),
// Sets the HomeScreen as the initial screen
home: const HomeScreen(),
),
);
Expand Down
182 changes: 182 additions & 0 deletions lib/screens/book_details.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';

class BookDetail extends StatelessWidget {
final Map book;

const BookDetail({Key? key, required this.book}) : super(key: key);
// Function to open URLs in the browser
Future<void> _launchURL(String url) async {
try {
// Encode the URL to handle special characters
final Uri uri = Uri.parse(url);
if (!await launchUrl(uri)) {
throw Exception('Could not launch $uri');
}
} catch (e) {
// Show a snackbar with the error message
debugPrint('Error launching URL: $e');
// You might want to show this error to the user through a SnackBar
// or other UI element
}
}

@override
Widget build(BuildContext context) {
final volumeInfo = book['volumeInfo'];

// Extract URLs
final thumbnail =
volumeInfo['imageLinks'] != null
? volumeInfo['imageLinks']['thumbnail']
: null;

final previewLink = volumeInfo['previewLink'];
final downloadLink =
book['accessInfo'] != null
? book['accessInfo']['pdf'] != null &&
book['accessInfo']['pdf']['isAvailable']
? book['accessInfo']['pdf']['acsTokenLink']
: null
: null;

final buyLink = volumeInfo['infoLink'];

return Scaffold(
appBar: AppBar(title: Text(volumeInfo['title'] ?? 'No Title')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Display the image with a placeholder and error handling
thumbnail != null
? Center(
child: Image.network(
thumbnail,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) {
return child;
}
return const Center(child: CircularProgressIndicator());
},
errorBuilder: (context, error, stackTrace) {
return const Icon(Icons.error, size: 100);
},
),
)
: const Icon(Icons.book, size: 100),
const SizedBox(height: 16),
Text(
volumeInfo['title'] ?? 'No Title',
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text(
'Author(s): ' +
(volumeInfo['authors'] != null
? volumeInfo['authors'].join(', ')
: 'Unknown'),
),
const SizedBox(height: 8),
Text(volumeInfo['description'] ?? 'No Description'),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Preview Button
if (previewLink != null)
ElevatedButton(
onPressed: () {
_launchURL(previewLink);
},
child: const Text(
'Preview',
style: TextStyle(
fontSize: 10,
letterSpacing: 1.0,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green[700],
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
padding: EdgeInsets.only(
right: 20,
left: 25,
top: 15,
bottom: 15,
),
),
),
const SizedBox(width: 10),
// Download Button
if (downloadLink != null)
ElevatedButton(
onPressed: () {
_launchURL(downloadLink);
},
child: const Text(
'Download',
style: TextStyle(
fontSize: 10,
letterSpacing: 1.0,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green[700],
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
padding: EdgeInsets.only(
right: 20,
left: 25,
top: 15,
bottom: 15,
),
),
)
else
ElevatedButton(
onPressed: () {
_launchURL(buyLink);
},
child: const Text(
'More Info',
style: TextStyle(
fontSize: 10,
letterSpacing: 1.0,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green[700],
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
padding: EdgeInsets.only(
right: 20,
left: 25,
top: 15,
bottom: 15,
),
),
),
],
),
],
),
),
);
}
}
106 changes: 70 additions & 36 deletions lib/screens/home_screen.dart
Original file line number Diff line number Diff line change
@@ -1,73 +1,107 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../services/google_books_api.dart';
import 'book_detail.dart'; // Import added
import './book_details.dart';

// HomeScreen is the main screen of the application where users can search for books
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
// Access the GoogleBooksApi provider
final bookProvider = Provider.of<GoogleBooksApi>(context);
// Controller for the search input field
TextEditingController searchController = TextEditingController();

return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text('EBook reader Application', style: TextStyle(fontSize: 25)),
backgroundColor: Colors.green[700],
toolbarHeight: 70,
elevation: 5,
shadowColor: Colors.green[700],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20))
title: const Text(
'EBook reader Application',
style: TextStyle(fontSize: 25),
),
backgroundColor: Colors.green[700],
foregroundColor: Colors.white,
toolbarHeight: 70,
elevation: 5,
shadowColor: Colors.green[700],
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
),
body: Column(
children: [
const SizedBox(width: 10),
const SizedBox(height: 10), // Adds spacing at the top
Padding(
padding: const EdgeInsets.all(10.0),
child: TextField(
// Binds the controller to the TextField
controller: searchController,
// Triggers search on submission
onSubmitted:
(value) => bookProvider.search(searchController.text),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Search for books',
labelStyle: TextStyle(
color: Color.fromARGB(255, 3, 131, 29),// Set the color you want here
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(
color: Color.fromARGB(255, 3, 131, 29),
),
borderRadius: BorderRadius.circular(10.0),
),
// Placeholder text
labelText: 'Search for books',
labelStyle: TextStyle(color: Color.fromARGB(255, 3, 131, 29)),
suffixIcon: IconButton(
icon: const Icon(Icons.search, size: 30, color: Color.fromARGB(255, 3, 131, 29),),
icon: const Icon(
Icons.search,
size: 30,
color: Color.fromARGB(255, 3, 131, 29),
),
onPressed: () {
// Triggers search on button press
bookProvider.search(searchController.text);
},
),
),
),
),
Expanded(
child: bookProvider.isLoading
? const Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: bookProvider.books.length,
itemBuilder: (context, index) {
final book = bookProvider.books[index];
return ListTile(
title: Text(book['volumeInfo']['title'] ?? 'No Title'),
subtitle: Text(book['volumeInfo']['authors'] != null
? book['volumeInfo']['authors'].join(', ')
: 'Unknown Author'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
BookDetail(book: book),
),
);
},
);
},
),
// Checks if data is still loading
child:
bookProvider.isLoading
? const Center(
child: CircularProgressIndicator(
color: Color.fromARGB(255, 3, 131, 29),
),
)
: ListView.builder(
// Number of books to display
itemCount: bookProvider.books.length,
itemBuilder: (context, index) {
// Current book data
final book = bookProvider.books[index];
return ListTile(
// Book title
title: Text(
book['volumeInfo']['title'] ?? 'No Title',
),
// Authors list
subtitle: Text(
book['volumeInfo']['authors'] != null
? book['volumeInfo']['authors'].join(', ')
: 'Unknown Author', // Fallback if no authors are available
),
onTap: () {
// Navigate to the BookDetail screen when a book is tapped
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BookDetail(book: book),
),
);
},
);
},
),
),
],
),
Expand Down
Loading