Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
P
PaperChase
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
h703249754
PaperChase
Commits
c43cc7ec
Commit
c43cc7ec
authored
Mar 28, 2025
by
Adam Bruck
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
search
parent
e953b7df
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
119 additions
and
52 deletions
+119
-52
.flutter-plugins-dependencies
.flutter-plugins-dependencies
+1
-1
cache.dill.track.dill
build/cache.dill.track.dill
+0
-0
book_detail_page.dart
lib/book_detail_page.dart
+32
-21
home.dart
lib/home.dart
+1
-0
main.dart
lib/main.dart
+56
-21
mybooks.dart
lib/mybooks.dart
+1
-1
post.dart
lib/post.dart
+28
-8
No files found.
.flutter-plugins-dependencies
View file @
c43cc7ec
This diff is collapsed.
Click to expand it.
build/cache.dill.track.dill
View file @
c43cc7ec
No preview for this file type
lib/book_detail_page.dart
View file @
c43cc7ec
...
@@ -2,35 +2,46 @@ import 'package:flutter/material.dart';
...
@@ -2,35 +2,46 @@ import 'package:flutter/material.dart';
import
'package:cloud_firestore/cloud_firestore.dart'
;
import
'package:cloud_firestore/cloud_firestore.dart'
;
class
BookDetailsPage
extends
StatelessWidget
{
class
BookDetailsPage
extends
StatelessWidget
{
final
QueryDocumentSnapshot
book
;
final
Map
<
String
,
dynamic
>
book
;
BookDetailsPage
({
required
this
.
book
});
BookDetailsPage
({
required
this
.
book
});
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
final
title
=
book
[
'title'
]
??
'No title available'
;
final
author
=
book
[
'author'
]
??
'No author available'
;
final
isbn
=
book
[
'isbn'
]
??
'No ISBN available'
;
final
price
=
book
[
'price'
]
is
String
?
double
.
tryParse
(
book
[
'price'
])
??
0.0
:
book
[
'price'
]
??
0.0
;
final
condition
=
book
[
'condition'
]
??
'Condition not available'
;
final
description
=
book
[
'description'
]
??
'No description available'
;
final
imageUrl
=
book
[
'imageUrl'
]
??
'https://via.placeholder.com/200'
;
// Fallback URL
return
Scaffold
(
return
Scaffold
(
appBar:
AppBar
(
title:
Text
(
book
[
'title'
])),
appBar:
AppBar
(
title:
Text
(
title
)),
body:
Padding
(
body:
SingleChildScrollView
(
// Makes content scrollable
padding:
const
EdgeInsets
.
all
(
16.0
),
child:
Padding
(
padding:
EdgeInsets
.
all
(
16.0
),
child:
Column
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
children:
[
Center
(
Center
(
child:
book
[
'imageUrl'
]
!=
null
child:
imageUrl
.
isNotEmpty
?
Image
.
network
(
book
[
'imageUrl'
]
,
height:
200
,
fit:
BoxFit
.
cover
)
?
Image
.
network
(
imageUrl
,
height:
200
,
fit:
BoxFit
.
cover
)
:
Icon
(
Icons
.
book
,
size:
100
),
:
Icon
(
Icons
.
book
,
size:
100
),
),
),
SizedBox
(
height:
20
),
SizedBox
(
height:
20
),
Text
(
"Title:
${book['title']}
"
,
style:
TextStyle
(
fontSize:
22
,
fontWeight:
FontWeight
.
bold
)),
Text
(
"Title:
$title
"
,
style:
TextStyle
(
fontSize:
22
,
fontWeight:
FontWeight
.
bold
)),
Text
(
"Author:
${book['author']}
"
,
style:
TextStyle
(
fontSize:
18
)),
Text
(
"Author:
$author
"
,
style:
TextStyle
(
fontSize:
18
)),
Text
(
"ISBN:
${book['isbn']}
"
,
style:
TextStyle
(
fontSize:
16
)),
Text
(
"ISBN:
$isbn
"
,
style:
TextStyle
(
fontSize:
16
)),
Text
(
"Price:
\$
${book['price']}
"
,
style:
TextStyle
(
fontSize:
16
,
color:
Colors
.
green
)),
Text
(
"Price:
\$
${price.toStringAsFixed(2)}
"
,
style:
TextStyle
(
fontSize:
16
,
color:
Colors
.
green
)),
Text
(
"Condition:
$condition
"
,
style:
TextStyle
(
fontSize:
16
)),
SizedBox
(
height:
10
),
SizedBox
(
height:
10
),
Text
(
"Description:"
,
style:
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
bold
)),
Text
(
"Description:"
,
style:
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
bold
)),
Text
(
book
[
'description'
]
??
'No description available'
,
style:
TextStyle
(
fontSize:
16
)),
Text
(
description
,
style:
TextStyle
(
fontSize:
16
)),
],
],
),
),
),
),
),
);
);
}
}
}
}
lib/home.dart
View file @
c43cc7ec
...
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
...
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import
'post.dart'
;
import
'post.dart'
;
import
'inbox.dart'
;
import
'inbox.dart'
;
class
HomeScreen
extends
StatefulWidget
{
class
HomeScreen
extends
StatefulWidget
{
const
HomeScreen
({
super
.
key
});
const
HomeScreen
({
super
.
key
});
...
...
lib/main.dart
View file @
c43cc7ec
import
'package:cloud_firestore/cloud_firestore.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:firebase_auth/firebase_auth.dart'
;
import
'package:firebase_auth/firebase_auth.dart'
;
import
'package:firebase_core/firebase_core.dart'
;
import
'package:firebase_core/firebase_core.dart'
;
import
'package:paperchase_app/book_detail_page.dart'
;
import
'package:paperchase_app/mybooks.dart'
;
import
'package:paperchase_app/mybooks.dart'
;
import
'firebase_options.dart'
;
import
'firebase_options.dart'
;
import
'package:http/http.dart'
as
http
;
import
'package:http/http.dart'
as
http
;
...
@@ -101,6 +103,7 @@ class _HomePageState extends State<HomePage> {
...
@@ -101,6 +103,7 @@ class _HomePageState extends State<HomePage> {
void
initState
()
{
void
initState
()
{
super
.
initState
();
super
.
initState
();
_checkUserLoginStatus
();
_checkUserLoginStatus
();
_loadRecentBooks
();
}
}
void
_checkUserLoginStatus
()
{
void
_checkUserLoginStatus
()
{
...
@@ -122,22 +125,53 @@ class _HomePageState extends State<HomePage> {
...
@@ -122,22 +125,53 @@ class _HomePageState extends State<HomePage> {
Future
<
void
>
_searchBooks
()
async
{
Future
<
void
>
_searchBooks
()
async
{
final
query
=
_searchController
.
text
;
final
query
=
_searchController
.
text
;
if
(
query
.
isEmpty
)
return
;
if
(
query
.
isEmpty
)
{
_loadRecentBooks
();
// If search is empty, load recent books
return
;
}
try
{
final
QuerySnapshot
snapshot
=
await
FirebaseFirestore
.
instance
.
collection
(
'books'
)
.
orderBy
(
'timestamp'
,
descending:
true
)
// Sort by the most recent posts
.
get
();
// Now filter books locally based on the title
final
filteredBooks
=
snapshot
.
docs
.
where
((
doc
)
{
final
title
=
doc
[
'title'
].
toString
().
toLowerCase
();
return
title
.
contains
(
query
.
toLowerCase
());
// Case-insensitive search
}).
toList
();
setState
(()
{
_books
=
filteredBooks
.
map
((
doc
)
=>
doc
.
data
()).
toList
();
});
}
catch
(
e
)
{
print
(
"Error searching books:
$e
"
);
}
}
final
url
=
Uri
.
parse
(
'https://www.googleapis.com/books/v1/volumes?q=
${Uri.encodeComponent(query)}
'
);
Future
<
void
>
_loadRecentBooks
()
async
{
try
{
try
{
final
response
=
await
http
.
get
(
url
);
final
QuerySnapshot
snapshot
=
await
FirebaseFirestore
.
instance
final
data
=
json
.
decode
(
response
.
body
);
.
collection
(
'books'
)
.
orderBy
(
'timestamp'
,
descending:
true
)
// Sort by the most recent posts
.
limit
(
10
)
// Optionally limit to the latest 10 books
.
get
();
setState
(()
{
setState
(()
{
_books
=
data
[
'items'
]
??
[]
;
_books
=
snapshot
.
docs
.
map
((
doc
)
=>
doc
.
data
()).
toList
()
;
});
});
}
catch
(
e
rror
)
{
}
catch
(
e
)
{
print
(
"Error fetching
books:
$error
"
);
print
(
"Error fetching
recent books:
$e
"
);
}
}
}
}
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
return
Scaffold
(
...
@@ -198,23 +232,24 @@ class _HomePageState extends State<HomePage> {
...
@@ -198,23 +232,24 @@ class _HomePageState extends State<HomePage> {
child:
ListView
.
builder
(
child:
ListView
.
builder
(
itemCount:
_books
.
length
,
itemCount:
_books
.
length
,
itemBuilder:
(
context
,
index
)
{
itemBuilder:
(
context
,
index
)
{
final
book
=
_books
[
index
][
'volumeInfo'
];
final
book
=
_books
[
index
];
final
title
=
book
[
'title'
]
??
"Unknown Title"
;
final
title
=
book
[
'title'
]
??
"Unknown Title"
;
final
authors
=
book
[
'authors'
]?.
join
(
", "
)
??
"Unknown Author"
;
final
author
=
book
[
'author'
]
??
"No author available"
;
final
thumbnail
=
book
[
'imageLinks'
]?[
'thumbnail'
]
??
"https://via.placeholder.com/50"
;
final
thumbnail
=
book
[
'imageUrl'
]
??
"https://via.placeholder.com/50"
;
final
link
=
book
[
'infoLink'
]
??
"#"
;
return
ListTile
(
return
ListTile
(
leading:
Image
.
network
(
thumbnail
,
width:
50
,
height:
50
,
fit:
BoxFit
.
cover
),
leading:
Image
.
network
(
thumbnail
,
width:
50
,
height:
50
,
fit:
BoxFit
.
cover
),
title:
Text
(
title
),
title:
Text
(
title
),
subtitle:
Text
(
authors
),
subtitle:
Text
(
author
),
onTap:
()
async
{
onTap:
()
{
final
Uri
url
=
Uri
.
parse
(
link
);
if
(
await
canLaunchUrl
(
url
))
{
Navigator
.
push
(
await
launchUrl
(
url
);
context
,
}
else
{
MaterialPageRoute
(
print
(
"Could not open
$url
"
);
builder:
(
context
)
=>
BookDetailsPage
(
book:
book
),
// Pass book data
}
),
);
},
},
);
);
},
},
...
...
lib/mybooks.dart
View file @
c43cc7ec
...
@@ -28,7 +28,7 @@ class MyBooksPage extends StatelessWidget {
...
@@ -28,7 +28,7 @@ class MyBooksPage extends StatelessWidget {
return
ListView
.
builder
(
return
ListView
.
builder
(
itemCount:
books
.
length
,
itemCount:
books
.
length
,
itemBuilder:
(
context
,
index
)
{
itemBuilder:
(
context
,
index
)
{
var
book
=
books
[
index
]
;
var
book
=
books
[
index
]
.
data
()
as
Map
<
String
,
dynamic
>;
// Use data() to get the map
return
ListTile
(
return
ListTile
(
leading:
book
[
'imageUrl'
]
!=
null
leading:
book
[
'imageUrl'
]
!=
null
...
...
lib/post.dart
View file @
c43cc7ec
...
@@ -19,6 +19,7 @@ class _PostBookPageState extends State<PostBookPage> {
...
@@ -19,6 +19,7 @@ class _PostBookPageState extends State<PostBookPage> {
final
TextEditingController
authorController
=
TextEditingController
();
final
TextEditingController
authorController
=
TextEditingController
();
final
TextEditingController
descriptionController
=
TextEditingController
();
final
TextEditingController
descriptionController
=
TextEditingController
();
File
?
_imageFile
;
File
?
_imageFile
;
String
_selectedCondition
=
"Like New"
;
// Function to pick an image from camera or gallery
// Function to pick an image from camera or gallery
Future
<
void
>
_pickImage
(
ImageSource
source
)
async
{
Future
<
void
>
_pickImage
(
ImageSource
source
)
async
{
...
@@ -52,19 +53,22 @@ Future<String?> fetchBookDescription(String isbn) async {
...
@@ -52,19 +53,22 @@ Future<String?> fetchBookDescription(String isbn) async {
Future
<
String
?>
uploadImageToImgur
(
File
imageFile
)
async
{
Future
<
String
?>
uploadImageToImgur
(
File
imageFile
)
async
{
try
{
try
{
final
uri
=
Uri
.
parse
(
'https://api.imgur.com/3/upload'
);
var
request
=
http
.
MultipartRequest
(
final
request
=
http
.
MultipartRequest
(
'POST'
,
uri
)
'POST'
,
Uri
.
parse
(
'https://api.imgur.com/3/upload'
)
..
headers
[
'Authorization'
]
=
'00caf989adf38fa'
);
..
files
.
add
(
await
http
.
MultipartFile
.
fromPath
(
'image'
,
imageFile
.
path
));
request
.
headers
[
'Authorization'
]
=
'Client-ID 00caf989adf38fa'
;
final
response
=
await
request
.
send
();
var
pic
=
await
http
.
MultipartFile
.
fromPath
(
'image'
,
imageFile
.
path
);
request
.
files
.
add
(
pic
);
var
response
=
await
request
.
send
();
if
(
response
.
statusCode
==
200
)
{
if
(
response
.
statusCode
==
200
)
{
final
responseData
=
await
response
.
stream
.
bytesToString
();
final
responseData
=
await
response
.
stream
.
bytesToString
();
final
jsonData
=
json
.
decode
(
responseData
);
final
jsonData
=
json
.
decode
(
responseData
);
return
jsonData
[
'data'
][
'link'
];
//
The i
mage URL from Imgur
return
jsonData
[
'data'
][
'link'
];
//
I
mage URL from Imgur
}
else
{
}
else
{
print
(
'Failed to upload image
to Imgur
'
);
print
(
'Failed to upload image
:
${response.reasonPhrase}
'
);
return
null
;
return
null
;
}
}
}
catch
(
e
)
{
}
catch
(
e
)
{
...
@@ -91,8 +95,10 @@ Future<String?> uploadImageToImgur(File imageFile) async {
...
@@ -91,8 +95,10 @@ Future<String?> uploadImageToImgur(File imageFile) async {
'isbn'
:
isbnController
.
text
,
'isbn'
:
isbnController
.
text
,
'price'
:
priceController
.
text
,
'price'
:
priceController
.
text
,
'description'
:
descriptionController
.
text
,
'description'
:
descriptionController
.
text
,
'condition'
:
_selectedCondition
,
'userId'
:
user
.
uid
,
// 🔹 Save logged-in user's ID
'userId'
:
user
.
uid
,
// 🔹 Save logged-in user's ID
'imageUrl'
:
imageUrl
??
""
,
// Optional image
'imageUrl'
:
imageUrl
??
""
,
// Optional image
'timestamp'
:
FieldValue
.
serverTimestamp
(),
});
});
return
true
;
return
true
;
}
catch
(
e
)
{
}
catch
(
e
)
{
...
@@ -167,7 +173,21 @@ Future<String?> uploadImageToImgur(File imageFile) async {
...
@@ -167,7 +173,21 @@ Future<String?> uploadImageToImgur(File imageFile) async {
decoration:
InputDecoration
(
labelText:
'Author'
),
decoration:
InputDecoration
(
labelText:
'Author'
),
),
),
SizedBox
(
height:
20
),
SizedBox
(
height:
20
),
DropdownButtonFormField
<
String
>(
value:
_selectedCondition
,
items:
[
'Like New'
,
'Good'
,
'Fair'
,
'Poor'
]
.
map
((
condition
)
=>
DropdownMenuItem
(
value:
condition
,
child:
Text
(
condition
),
))
.
toList
(),
onChanged:
(
value
)
{
setState
(()
{
_selectedCondition
=
value
!;
});
},
decoration:
InputDecoration
(
labelText:
'Condition'
),
),
// Image Picker Buttons
// Image Picker Buttons
Row
(
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
mainAxisAlignment:
MainAxisAlignment
.
center
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment