Commit 7e291a8b authored by Aryan Patel's avatar Aryan Patel

Aryan-4/27/25

parent 9cee33a8
...@@ -499,7 +499,7 @@ ...@@ -499,7 +499,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.IOSPhoneTesting.paperchaseApp; PRODUCT_BUNDLE_IDENTIFIER = com.IOSPhoneTesting2.paperchaseApp;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
...@@ -682,7 +682,7 @@ ...@@ -682,7 +682,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.IOSPhoneTesting.paperchaseApp; PRODUCT_BUNDLE_IDENTIFIER = com.IOSPhoneTesting2.paperchaseApp;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
...@@ -705,7 +705,7 @@ ...@@ -705,7 +705,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.IOSPhoneTesting.paperchaseApp; PRODUCT_BUNDLE_IDENTIFIER = com.IOSPhoneTesting2.paperchaseApp;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
......
...@@ -2,19 +2,24 @@ import 'package:flutter/material.dart'; ...@@ -2,19 +2,24 @@ import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:paperchase_app/chat_page.dart';
import 'colors.dart'; import 'colors.dart';
import 'NavBar.dart';
class BookDetailsPage extends StatelessWidget { class BookDetailsPage extends StatelessWidget {
final Map<String, dynamic> book; final Map<String, dynamic> book;
final String bookId;
const BookDetailsPage({super.key, required this.book}); const BookDetailsPage({super.key, required this.book, required this.bookId});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool isDarkMode = Theme.of(context).brightness == Brightness.dark; final bool isDarkMode = Theme.of(context).brightness == Brightness.dark;
final currentUser = FirebaseAuth.instance.currentUser; final currentUser = FirebaseAuth.instance.currentUser;
final isMyBook = currentUser?.uid == book['userId']; final isMyBook = currentUser?.uid == book['userId'];
final title = book['title'] ?? 'No title available'; final title = book['title'] ?? 'No title available';
final author = book['author'] ?? 'No author available'; final author = book['author'] ?? 'No author available';
...@@ -28,6 +33,7 @@ class BookDetailsPage extends StatelessWidget { ...@@ -28,6 +33,7 @@ class BookDetailsPage extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
automaticallyImplyLeading: true,
iconTheme: IconThemeData( iconTheme: IconThemeData(
color: isDarkMode ? kDarkBackground : kLightBackground, color: isDarkMode ? kDarkBackground : kLightBackground,
), ),
...@@ -42,7 +48,7 @@ class BookDetailsPage extends StatelessWidget { ...@@ -42,7 +48,7 @@ class BookDetailsPage extends StatelessWidget {
), ),
), ),
), ),
drawer: const NavBar(),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
...@@ -68,12 +74,31 @@ class BookDetailsPage extends StatelessWidget { ...@@ -68,12 +74,31 @@ class BookDetailsPage extends StatelessWidget {
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
Text(description, style: const TextStyle(fontSize: 16)), Text(description, style: const TextStyle(fontSize: 16)),
const SizedBox(height: 24), const SizedBox(height: 24),
if (!isMyBook && currentUser != null) if (isMyBook && currentUser != null)
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => _confirmAndDeleteBook(context, bookId),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text(
'Delete Book',
style: TextStyle(fontSize: 18, color: Colors.white),
),
),
)
else if (!isMyBook && currentUser != null)
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: () => onPressed: () =>
_contactSeller(context, book['userId'], title), _contactSeller(context, book, bookId),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: kPrimaryColor, backgroundColor: kPrimaryColor,
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
...@@ -86,7 +111,9 @@ class BookDetailsPage extends StatelessWidget { ...@@ -86,7 +111,9 @@ class BookDetailsPage extends StatelessWidget {
style: TextStyle(fontSize: 18, color: Colors.white), style: TextStyle(fontSize: 18, color: Colors.white),
), ),
), ),
) )
else if (currentUser == null) else if (currentUser == null)
Center( Center(
child: TextButton( child: TextButton(
...@@ -127,79 +154,112 @@ class BookDetailsPage extends StatelessWidget { ...@@ -127,79 +154,112 @@ class BookDetailsPage extends StatelessWidget {
); );
} }
Future<void> _contactSeller(
BuildContext context, String sellerId, String bookTitle) async {
final currentUser = FirebaseAuth.instance.currentUser;
if (currentUser == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Please log in to contact the seller')),
);
return;
}
try { Future<void> _contactSeller(BuildContext context, Map<String, dynamic> book, String bookId) async {
final users = [currentUser.uid, sellerId]..sort(); final currentUser = FirebaseAuth.instance.currentUser;
final chatRoomId = users.join('_'); if (currentUser == null) {
ScaffoldMessenger.of(context).showSnackBar(
final existingChat = await FirebaseFirestore.instance const SnackBar(content: Text('Please log in to contact the seller')),
.collection('chats') );
.doc(chatRoomId) return;
.get(); }
final chatData = { final sellerId = book['userId']; // 📌 This is the user who posted the book
'users': users, final isBuyer = currentUser.uid != sellerId;
'lastMessage': 'Hi! Is this book still available?', final rolePrefix = isBuyer ? 'buyer' : 'seller';
'lastMessageTime': FieldValue.serverTimestamp(), final users = [currentUser.uid, sellerId]..sort();
'bookTitle': bookTitle, final chatRoomId = "${rolePrefix}_${bookId}_${users.join('_')}";
'createdAt': FieldValue.serverTimestamp(),
'participants': {
currentUser.uid: true,
sellerId: true,
},
};
if (existingChat.exists) {
await FirebaseFirestore.instance
.collection('chats')
.doc(chatRoomId)
.update({
'lastMessage': chatData['lastMessage'],
'lastMessageTime': chatData['lastMessageTime'],
});
} else {
await FirebaseFirestore.instance
.collection('chats')
.doc(chatRoomId)
.set(chatData);
}
await FirebaseFirestore.instance try {
.collection('chats') final sellerDoc = await FirebaseFirestore.instance
.doc(chatRoomId) .collection('users')
.collection('messages') .doc(sellerId)
.add({ .get();
'senderId': currentUser.uid,
'message': 'Hi! Is this book still available?', final sellerName = sellerDoc.exists
'timestamp': FieldValue.serverTimestamp(), ? "${sellerDoc['first_name']} ${sellerDoc['last_name']}"
: "Unknown Seller";
final chatRef = FirebaseFirestore.instance.collection('chats').doc(chatRoomId);
final chatData = {
'users': users,
'bookId': bookId,
'bookTitle': book['title'],
'lastMessage': 'Hi! Is this book still available?',
'lastMessageTime': FieldValue.serverTimestamp(),
'createdAt': FieldValue.serverTimestamp(),
'participants': {
currentUser.uid: true,
sellerId: true,
},
'sellerId': sellerId,
'buyerId': isBuyer ? currentUser.uid : null, // null if seller is messaging
};
final existingChat = await chatRef.get();
if (existingChat.exists) {
await chatRef.update({
'lastMessage': chatData['lastMessage'],
'lastMessageTime': chatData['lastMessageTime'],
}); });
} else {
await chatRef.set(chatData);
}
await chatRef.collection('messages').add({
'senderId': currentUser.uid,
'message': 'Hi! Is this book still available?',
'timestamp': FieldValue.serverTimestamp(),
'read': false,
});
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StrictChatPage(
chatId: chatRoomId,
otherUserName: sellerName,
currentUserId: currentUser.uid,
sellerId: sellerId,
),
),
);
} catch (e) {
debugPrint('Error starting chat: $e');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Failed to contact seller. Please try again.')),
);
}
}
void _confirmAndDeleteBook(BuildContext context, String bookId) async {
final shouldDelete = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Confirm Deletion'),
content: const Text('Are you sure you want to delete this book?'),
actions: [
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Cancel')),
TextButton(onPressed: () => Navigator.pop(context, true), child: const Text('Delete')),
],
),
);
if (shouldDelete == true) {
await FirebaseFirestore.instance.collection('books').doc(bookId).delete();
if (context.mounted) { if (context.mounted) {
Navigator.pushReplacementNamed(context, '/inbox'); Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Chat started with the seller')),
);
}
} catch (e) {
debugPrint("Error contacting seller: $e");
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(content: Text('Book removed successfully')),
content: Text('Failed to contact seller. Please try again.')),
); );
} }
} }
} }
String _formatPrice(dynamic price) { String _formatPrice(dynamic price) {
if (price == null) return '0.00'; if (price == null) return '0.00';
if (price is num) return price.toStringAsFixed(2); if (price is num) return price.toStringAsFixed(2);
......
...@@ -2,27 +2,24 @@ import 'package:flutter/foundation.dart'; ...@@ -2,27 +2,24 @@ import 'package:flutter/foundation.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:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:paperchase_app/book_detail_page.dart';
import 'colors.dart'; import 'colors.dart';
class StrictChatPage extends StatefulWidget { class StrictChatPage extends StatefulWidget {
final String chatId; final String chatId;
//final String bookId;
final String otherUserName; final String otherUserName;
final List<String> predefinedMessages; final String currentUserId;
final String sellerId;
const StrictChatPage({ const StrictChatPage({
Key? key, Key? key,
required this.chatId, required this.chatId,
required this.otherUserName, required this.otherUserName,
this.predefinedMessages = const [ required this.currentUserId,
"Is this still available?", required this.sellerId,
"When can we meet?", }) : super(key: key);
"I'll take it",
"Thanks!",
"Hello",
"Can you hold it for me?",
"What's your lowest price?",
],
}) : super(key: key);
@override @override
_StrictChatPageState createState() => _StrictChatPageState(); _StrictChatPageState createState() => _StrictChatPageState();
...@@ -32,6 +29,32 @@ class _StrictChatPageState extends State<StrictChatPage> { ...@@ -32,6 +29,32 @@ class _StrictChatPageState extends State<StrictChatPage> {
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
final TextEditingController _messageController = TextEditingController(); final TextEditingController _messageController = TextEditingController();
String? _bookTitle; String? _bookTitle;
String? _bookId;
List<String> get predefinedMessages {
final currentUser = FirebaseAuth.instance.currentUser;
final email = currentUser?.email ?? "your email";
if (widget.currentUserId == widget.sellerId) {
return [
"Yes, it's still available.",
"Thanks!",
"How about we meet this weekend?",
"That a deal!",
"Yes, I will hold it",
"Contact me at $email"
];
} else {
return [
"Is this still available?",
"When can we meet?",
"I'll take it",
"Thanks!",
"Can you hold it for me?",
"Contact me at $email",
];
}
}
@override @override
void initState() { void initState() {
...@@ -52,10 +75,11 @@ class _StrictChatPageState extends State<StrictChatPage> { ...@@ -52,10 +75,11 @@ class _StrictChatPageState extends State<StrictChatPage> {
.collection('chats') .collection('chats')
.doc(widget.chatId) .doc(widget.chatId)
.get(); .get();
if (doc.exists) { if (doc.exists) {
setState(() { setState(() {
_bookTitle = doc.data()?['bookTitle'] as String?; _bookTitle = doc.data()?['bookTitle'] as String?;
_bookId = doc.data()?['bookId'] as String?;
}); });
} }
} catch (e) { } catch (e) {
...@@ -111,7 +135,8 @@ class _StrictChatPageState extends State<StrictChatPage> { ...@@ -111,7 +135,8 @@ class _StrictChatPageState extends State<StrictChatPage> {
final backgroundColor2 = isDarkMode ? kLightBackground : kDarkBackground; final backgroundColor2 = isDarkMode ? kLightBackground : kDarkBackground;
final textColor = isDarkMode ? kDarkText : kLightText; final textColor = isDarkMode ? kDarkText : kLightText;
final textColor2 = isDarkMode ? kLightText : kDarkText; final textColor2 = isDarkMode ? kLightText : kDarkText;
final messageBackgroundOther = isDarkMode ? Colors.grey[800] : Colors.grey[200]; final messageBackgroundOther = isDarkMode ? Colors.grey[800] : Colors
.grey[200];
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
...@@ -176,7 +201,8 @@ class _StrictChatPageState extends State<StrictChatPage> { ...@@ -176,7 +201,8 @@ class _StrictChatPageState extends State<StrictChatPage> {
final text = data['message'] as String? ?? ''; final text = data['message'] as String? ?? '';
final senderId = data['senderId'] as String? ?? ''; final senderId = data['senderId'] as String? ?? '';
final currentUser = FirebaseAuth.instance.currentUser; final currentUser = FirebaseAuth.instance.currentUser;
final isMe = currentUser != null && senderId == currentUser.uid; final isMe = currentUser != null &&
senderId == currentUser.uid;
return Container( return Container(
margin: const EdgeInsets.symmetric(vertical: 4), margin: const EdgeInsets.symmetric(vertical: 4),
...@@ -187,7 +213,10 @@ class _StrictChatPageState extends State<StrictChatPage> { ...@@ -187,7 +213,10 @@ class _StrictChatPageState extends State<StrictChatPage> {
children: [ children: [
Container( Container(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.75, maxWidth: MediaQuery
.of(context)
.size
.width * 0.75,
), ),
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
...@@ -216,48 +245,78 @@ class _StrictChatPageState extends State<StrictChatPage> { ...@@ -216,48 +245,78 @@ class _StrictChatPageState extends State<StrictChatPage> {
), ),
), ),
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
margin: const EdgeInsets.only(top: 2), margin: const EdgeInsets.only(top: 2),
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
color: backgroundColor2, color: backgroundColor2,
border: Border( border: Border(
top: BorderSide( top: BorderSide(
color: backgroundColor.withOpacity(0.2), color: backgroundColor.withOpacity(0.2),
), ),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Quick replies:',
style: TextStyle(
color: textColor2.withOpacity(0.7),
fontSize: 14,
),
),
// Only show the Confirmed button if the current user is the seller
if (widget.currentUserId == widget.sellerId)
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => _confirmAndCompletePurchase(context),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.lightGreenAccent,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
), ),
), ),
child: Column( child: const Text(
crossAxisAlignment: CrossAxisAlignment.start, 'Confirmed!',
children: [ style: TextStyle(fontSize: 18, color: kLightText),
Text(
'Quick replies:',
style: TextStyle(
color: textColor2.withOpacity(0.7),
fontSize: 14,
),
),
const SizedBox(height: 12),
Wrap(
spacing: 12,
runSpacing: 12,
children: widget.predefinedMessages.map((msg) {
return ActionChip(
label: Text(msg),
onPressed: () => _sendMessage(msg),
backgroundColor: backgroundColor,
labelStyle: TextStyle(
color: textColor,
),
);
}).toList(),
),
const SizedBox(height: 12),
],
), ),
), ),
),
if (widget.currentUserId == widget.sellerId)
const SizedBox(height: 12),
Wrap(
spacing: 12,
runSpacing: 12,
children: predefinedMessages.map((msg) {
return ActionChip(
label: Text(msg),
onPressed: () => _sendMessage(msg),
backgroundColor: backgroundColor,
labelStyle: TextStyle(color: textColor,),
);
}).toList(),
),
const SizedBox(height: 12),
],
),
),
], ],
), ),
); );
} }
void _confirmAndCompletePurchase(BuildContext context) async {
await FirebaseFirestore.instance.collection('books').doc(_bookId).delete();
//await FirebaseFirestore.instance.collection('chats').doc(widget.chatId).collection('messages').doc().delete();
await FirebaseFirestore.instance.collection('chats').doc(widget.chatId).delete();
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Transaction completed!')),
);
}
} }
\ No newline at end of file
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/foundation.dart' show kDebugMode; import 'package:firebase_auth/firebase_auth.dart';
import 'colors.dart'; import 'package:paperchase_app/NavBar.dart';
import 'NavBar.dart'; import 'package:paperchase_app/chat_page.dart';
import 'chat_page.dart'; import 'package:paperchase_app/colors.dart';
enum BookFilter {
all,
sold,
bought,
}
class InboxPage extends StatefulWidget { class InboxPage extends StatefulWidget {
const InboxPage({super.key}); const InboxPage({super.key});
...@@ -20,33 +14,10 @@ class InboxPage extends StatefulWidget { ...@@ -20,33 +14,10 @@ class InboxPage extends StatefulWidget {
} }
class _InboxPageState extends State<InboxPage> { class _InboxPageState extends State<InboxPage> {
BookFilter _currentFilter = BookFilter.all;
String _getFilterName(BookFilter filter) {
switch (filter) {
case BookFilter.all:
return 'All Books';
case BookFilter.sold:
return 'Sold Books';
case BookFilter.bought:
return 'Bought Books';
}
}
Query<Map<String, dynamic>> _getFilteredQuery(String userId) { Query<Map<String, dynamic>> _getFilteredQuery(String userId) {
final baseQuery = FirebaseFirestore.instance.collection('chats'); return FirebaseFirestore.instance
switch (_currentFilter) { .collection('chats')
case BookFilter.all: .where('users', arrayContains: userId);
return baseQuery.where('users', arrayContains: userId);
case BookFilter.sold:
return baseQuery
.where('users', arrayContains: userId)
.where('sellerId', isEqualTo: userId);
case BookFilter.bought:
return baseQuery
.where('users', arrayContains: userId)
.where('buyerId', isEqualTo: userId);
}
} }
@override @override
...@@ -61,9 +32,7 @@ class _InboxPageState extends State<InboxPage> { ...@@ -61,9 +32,7 @@ class _InboxPageState extends State<InboxPage> {
if (currentUser == null) { if (currentUser == null) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
iconTheme: IconThemeData( iconTheme: IconThemeData(color: textColor),
color: isDarkMode ? kDarkBackground : kLightBackground,
),
title: const Text( title: const Text(
"Inbox", "Inbox",
style: TextStyle( style: TextStyle(
...@@ -74,7 +43,7 @@ class _InboxPageState extends State<InboxPage> { ...@@ -74,7 +43,7 @@ class _InboxPageState extends State<InboxPage> {
color: kPrimaryColor, color: kPrimaryColor,
), ),
), ),
foregroundColor: isDarkMode ? kLightBackground : kDarkBackground, backgroundColor: scaffoldColor,
), ),
drawer: const NavBar(), drawer: const NavBar(),
body: Container( body: Container(
...@@ -102,75 +71,21 @@ class _InboxPageState extends State<InboxPage> { ...@@ -102,75 +71,21 @@ class _InboxPageState extends State<InboxPage> {
), ),
), ),
), ),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: _buildBottomNavigationBar(isDarkMode, textColor2),
backgroundColor: isDarkMode ? kLightBackground : kDarkBackground,
selectedItemColor: kPrimaryColor,
unselectedItemColor: isDarkMode ? kDarkBackground : kLightBackground,
currentIndex: 1,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: "Home"),
BottomNavigationBarItem(icon: Icon(Icons.add), label: "Post"),
BottomNavigationBarItem(icon: Icon(Icons.mail), label: "Inbox"),
],
onTap: (index) {
if (index == 0) {
Navigator.pushNamedAndRemoveUntil(context, '/', (route) => false);
} else if (index == 1) {
Navigator.pushNamed(context, '/post');
} else if (index == 2) {
Navigator.pushNamed(context, '/inbox');
}
},
),
); );
} }
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Row( title: const Text(
children: [ 'Inbox',
const Text( style: TextStyle(
'Inbox', fontFamily: 'Impact',
style: TextStyle( fontSize: 24,
fontFamily: 'Impact', fontStyle: FontStyle.italic,
fontSize: 24, fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic, color: kPrimaryColor,
fontWeight: FontWeight.bold, ),
color: kPrimaryColor,
),
),
const SizedBox(width: 16),
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
borderRadius: BorderRadius.circular(20),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<BookFilter>(
value: _currentFilter,
icon: Icon(Icons.arrow_drop_down, color: textColor),
style: TextStyle(color: textColor, fontSize: 14),
dropdownColor: isDarkMode ? Colors.grey[800] : Colors.grey[200],
items: BookFilter.values.map((filter) {
return DropdownMenuItem<BookFilter>(
value: filter,
child: Text(_getFilterName(filter)),
);
}).toList(),
onChanged: (BookFilter? newValue) {
if (newValue != null) {
setState(() {
_currentFilter = newValue;
});
}
},
),
),
),
),
],
), ),
backgroundColor: scaffoldColor, backgroundColor: scaffoldColor,
iconTheme: IconThemeData(color: textColor2), iconTheme: IconThemeData(color: textColor2),
...@@ -183,26 +98,7 @@ class _InboxPageState extends State<InboxPage> { ...@@ -183,26 +98,7 @@ class _InboxPageState extends State<InboxPage> {
.orderBy('lastMessageTime', descending: true) .orderBy('lastMessageTime', descending: true)
.snapshots(), .snapshots(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (kDebugMode) {
print('Current user ID in Inbox: ${currentUser.uid}');
print('Current filter: ${_getFilterName(_currentFilter)}');
print('Stream connection state: ${snapshot.connectionState}');
if (snapshot.hasError) {
print('Stream error: ${snapshot.error}');
print('Error stack trace: ${snapshot.stackTrace}');
}
}
if (snapshot.hasError) { if (snapshot.hasError) {
final error = snapshot.error.toString();
if (error.contains('failed-precondition') || error.contains('requires an index')) {
return StreamBuilder<QuerySnapshot>(
stream: _getFilteredQuery(currentUser.uid).snapshots(),
builder: (context, simpleSnapshot) {
return _buildChatList(simpleSnapshot, currentUser, isDarkMode, textColor);
},
);
}
return Center( return Center(
child: Text( child: Text(
'Error loading conversations: ${snapshot.error}', 'Error loading conversations: ${snapshot.error}',
...@@ -210,39 +106,22 @@ class _InboxPageState extends State<InboxPage> { ...@@ -210,39 +106,22 @@ class _InboxPageState extends State<InboxPage> {
), ),
); );
} }
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
return _buildChatList(snapshot, currentUser, isDarkMode, textColor); return _buildChatList(snapshot, currentUser, isDarkMode, textColor);
}, },
), ),
), ),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: _buildBottomNavigationBar(isDarkMode, textColor2),
backgroundColor: scaffoldColor,
selectedItemColor: kPrimaryColor,
unselectedItemColor: textColor2,
currentIndex: 2,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.add), label: 'Post'),
BottomNavigationBarItem(icon: Icon(Icons.mail), label: 'Inbox'),
],
onTap: (index) {
if (index == 0) {
Navigator.pushReplacementNamed(context, '/');
} else if (index == 1) {
Navigator.pushReplacementNamed(context, '/post');
}
},
),
); );
} }
Widget _buildChatList(AsyncSnapshot<QuerySnapshot> snapshot, User currentUser, bool isDarkMode, Color textColor) { Widget _buildChatList(AsyncSnapshot<QuerySnapshot> snapshot, User currentUser, bool isDarkMode, Color textColor) {
final chats = snapshot.data?.docs ?? []; final chats = snapshot.data?.docs ?? [];
if (chats.isEmpty) { if (chats.isEmpty) {
return Center( return Center(
child: Column( child: Column(
...@@ -250,22 +129,9 @@ class _InboxPageState extends State<InboxPage> { ...@@ -250,22 +129,9 @@ class _InboxPageState extends State<InboxPage> {
children: [ children: [
Icon(Icons.chat_bubble_outline, size: 64, color: textColor.withOpacity(0.5)), Icon(Icons.chat_bubble_outline, size: 64, color: textColor.withOpacity(0.5)),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( const Text('No conversations yet'),
_currentFilter == BookFilter.all
? 'No conversations yet'
: _currentFilter == BookFilter.sold
? 'No sold books conversations'
: 'No bought books conversations',
style: TextStyle(fontSize: 18, color: textColor.withOpacity(0.7)),
),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( const Text('Browse books and contact sellers to start chatting'),
_currentFilter == BookFilter.all
? 'Browse books and contact sellers to start chatting'
: 'No messages found for this filter',
style: TextStyle(fontSize: 14, color: textColor.withOpacity(0.5)),
textAlign: TextAlign.center,
),
], ],
), ),
); );
...@@ -286,29 +152,16 @@ class _InboxPageState extends State<InboxPage> { ...@@ -286,29 +152,16 @@ class _InboxPageState extends State<InboxPage> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final chat = sortedChats[index]; final chat = sortedChats[index];
final data = chat.data() as Map<String, dynamic>; final data = chat.data() as Map<String, dynamic>;
final chatId = chat.id;
if (kDebugMode) { // We'll determine real seller in FutureBuilder
print('Chat data: $data'); final bookId = data['bookId'] as String? ?? '';
}
final lastMessage = data['lastMessage'] as String?; final lastMessage = data['lastMessage'] as String?;
final lastMessageTime = (data['lastMessageTime'] as Timestamp?)?.toDate(); final lastMessageTime = (data['lastMessageTime'] as Timestamp?)?.toDate();
final bookTitle = data['bookTitle'] as String?; final bookTitle = data['bookTitle'] as String?;
final usersList = (data['users'] as List?)?.cast<String>() ?? []; final usersList = (data['users'] as List?)?.cast<String>() ?? [];
String otherUserId; String otherUserId = usersList.firstWhere((id) => id != currentUser.uid, orElse: () => 'unknown');
try {
otherUserId = usersList.firstWhere(
(id) => id != currentUser.uid,
orElse: () => 'unknown',
);
} catch (e) {
if (kDebugMode) {
print('Error finding other user: $e');
}
otherUserId = 'unknown';
}
return FutureBuilder<DocumentSnapshot>( return FutureBuilder<DocumentSnapshot>(
future: FirebaseFirestore.instance.collection('users').doc(otherUserId).get(), future: FirebaseFirestore.instance.collection('users').doc(otherUserId).get(),
builder: (context, userSnapshot) { builder: (context, userSnapshot) {
...@@ -318,79 +171,77 @@ class _InboxPageState extends State<InboxPage> { ...@@ -318,79 +171,77 @@ class _InboxPageState extends State<InboxPage> {
userName = '${userData['first_name'] ?? ''} ${userData['last_name'] ?? ''}'.trim(); userName = '${userData['first_name'] ?? ''} ${userData['last_name'] ?? ''}'.trim();
if (userName.isEmpty) userName = 'Unknown User'; if (userName.isEmpty) userName = 'Unknown User';
} }
return Card( return FutureBuilder<QuerySnapshot>(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), future: FirebaseFirestore.instance
color: isDarkMode ? Colors.grey[900] : Colors.white, .collection('chats')
child: ListTile( .doc(chatId)
onTap: () { .collection('messages')
Navigator.push( .orderBy('timestamp', descending: false)
context, .limit(1)
MaterialPageRoute( .get(),
builder: (context) => StrictChatPage( builder: (context, messagesSnapshot) {
chatId: chat.id, String sellerId = '';
otherUserName: userName, String buyerId = '';
predefinedMessages: const [
"Is this still available?", // Check who sent first message to determine buyer
"When can we meet?", if (messagesSnapshot.hasData && messagesSnapshot.data!.docs.isNotEmpty) {
"I'll take it", final firstMessage = messagesSnapshot.data!.docs.first;
"Thanks!", final firstMessageData = firstMessage.data() as Map<String, dynamic>;
"Hello", buyerId = firstMessageData['senderId'] as String? ?? '';
"Can you hold it for me?",
"What's your lowest price?", // If buyer is first message sender, seller is the other user
], sellerId = usersList.firstWhere((id) => id != buyerId, orElse: () => '');
), } else {
), // If no messages yet, use any seller ID from data if available
sellerId = data['sellerId'] as String? ?? '';
// If seller ID still not available, default to book owner from books collection
if (sellerId.isEmpty && bookId.isNotEmpty) {
// This will be handled later in the next FutureBuilder
}
}
// Return placeholder while loading book data if necessary
if (sellerId.isEmpty && bookId.isNotEmpty) {
return FutureBuilder<DocumentSnapshot>(
future: FirebaseFirestore.instance.collection('books').doc(bookId).get(),
builder: (context, bookSnapshot) {
if (bookSnapshot.hasData && bookSnapshot.data!.exists) {
final bookData = bookSnapshot.data!.data() as Map<String, dynamic>? ?? {};
sellerId = bookData['userId'] as String? ?? '';
}
// Now build the actual chat list item
return _buildChatListItem(
context,
chatId,
userName,
currentUser.uid,
sellerId,
bookTitle,
lastMessage,
lastMessageTime,
isDarkMode,
textColor,
);
},
); );
}, }
leading: CircleAvatar(
backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[200], return _buildChatListItem(
child: Text( context,
userName[0].toUpperCase(), chatId,
style: TextStyle(
color: textColor,
fontWeight: FontWeight.bold,
),
),
),
title: Text(
userName, userName,
style: TextStyle( currentUser.uid,
color: textColor, sellerId,
fontWeight: FontWeight.bold, bookTitle,
), lastMessage,
), lastMessageTime,
subtitle: Column( isDarkMode,
crossAxisAlignment: CrossAxisAlignment.start, textColor,
children: [ );
if (bookTitle != null) },
Text(
'Re: $bookTitle',
style: TextStyle(
color: textColor.withOpacity(0.7),
fontSize: 12,
),
),
Text(
lastMessage ?? 'No messages yet',
style: TextStyle(
color: textColor.withOpacity(0.7),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
trailing: lastMessageTime != null
? Text(
_formatTimestamp(lastMessageTime),
style: TextStyle(
color: textColor.withOpacity(0.5),
fontSize: 12,
),
)
: null,
),
); );
}, },
); );
...@@ -398,6 +249,107 @@ class _InboxPageState extends State<InboxPage> { ...@@ -398,6 +249,107 @@ class _InboxPageState extends State<InboxPage> {
); );
} }
Widget _buildChatListItem(
BuildContext context,
String chatId,
String userName,
String currentUserId,
String sellerId,
String? bookTitle,
String? lastMessage,
DateTime? lastMessageTime,
bool isDarkMode,
Color textColor,
) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
color: isDarkMode ? Colors.grey[900] : Colors.white,
child: ListTile(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StrictChatPage(
chatId: chatId,
otherUserName: userName,
currentUserId: currentUserId,
sellerId: sellerId,
),
),
);
},
leading: CircleAvatar(
backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[200],
child: Text(
userName.isNotEmpty ? userName[0].toUpperCase() : '?',
style: TextStyle(
color: textColor,
fontWeight: FontWeight.bold,
),
),
),
title: Text(
userName,
style: TextStyle(
color: textColor,
fontWeight: FontWeight.bold,
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (bookTitle != null)
Text(
'Re: $bookTitle',
style: TextStyle(
color: textColor.withOpacity(0.7),
fontSize: 12,
),
),
Text(
lastMessage ?? 'No messages yet',
style: TextStyle(
color: textColor.withOpacity(0.7),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
trailing: lastMessageTime != null
? Text(
_formatTimestamp(lastMessageTime),
style: TextStyle(
color: textColor.withOpacity(0.5),
fontSize: 12,
),
)
: null,
),
);
}
BottomNavigationBar _buildBottomNavigationBar(bool isDarkMode, Color textColor2) {
return BottomNavigationBar(
backgroundColor: isDarkMode ? kLightBackground : kDarkBackground,
selectedItemColor: kPrimaryColor,
unselectedItemColor: textColor2,
currentIndex: 2,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.add), label: 'Post'),
BottomNavigationBarItem(icon: Icon(Icons.mail), label: 'Inbox'),
],
onTap: (index) {
if (index == 0) {
Navigator.pushReplacementNamed(context, '/');
} else if (index == 1) {
Navigator.pushReplacementNamed(context, '/post');
}
},
);
}
String _formatTimestamp(DateTime timestamp) { String _formatTimestamp(DateTime timestamp) {
final now = DateTime.now(); final now = DateTime.now();
final difference = now.difference(timestamp); final difference = now.difference(timestamp);
......
...@@ -3,9 +3,6 @@ import 'package:flutter/material.dart'; ...@@ -3,9 +3,6 @@ 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 'firebase_options.dart'; import 'firebase_options.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:url_launcher/url_launcher.dart';
import 'login.dart'; import 'login.dart';
import 'signup.dart'; import 'signup.dart';
import 'profile.dart'; import 'profile.dart';
...@@ -14,7 +11,6 @@ import 'inbox.dart'; ...@@ -14,7 +11,6 @@ import 'inbox.dart';
import 'package:firebase_app_check/firebase_app_check.dart'; import 'package:firebase_app_check/firebase_app_check.dart';
import 'colors.dart'; import 'colors.dart';
import 'utils.dart'; import 'utils.dart';
import 'home.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'NavBar.dart'; import 'NavBar.dart';
import 'book_detail_page.dart'; import 'book_detail_page.dart';
...@@ -185,7 +181,8 @@ class _HomePageState extends State<HomePage> { ...@@ -185,7 +181,8 @@ class _HomePageState extends State<HomePage> {
}).toList(); }).toList();
setState(() { setState(() {
_books = filteredBooks.map((doc) => doc.data()).toList(); _books = filteredBooks;
}); });
} catch (e) { } catch (e) {
print("Error searching books: $e"); print("Error searching books: $e");
...@@ -202,7 +199,8 @@ class _HomePageState extends State<HomePage> { ...@@ -202,7 +199,8 @@ class _HomePageState extends State<HomePage> {
.get(); .get();
setState(() { setState(() {
_books = snapshot.docs.map((doc) => doc.data()).toList(); _books = snapshot.docs;
}); });
} catch (e) { } catch (e) {
print("Error fetching recent books: $e"); print("Error fetching recent books: $e");
...@@ -357,21 +355,29 @@ String _filterBy = 'Latest Posted'; // Default filter option ...@@ -357,21 +355,29 @@ String _filterBy = 'Latest Posted'; // Default filter option
itemCount: _books.length, itemCount: _books.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final book = _books[index]; final book = _books[index];
final bookId = book.id;
final data = book.data() as Map<String, dynamic>;
final title = book['title'] ?? "Unknown Title"; final title = book.data()['title'] ?? "Unknown Title";
final author = book['author'] ?? "No author available"; final author = book.data()['author'] ?? "No author available";
final thumbnail = book['imageUrl'] ?? "https://via.placeholder.com/50"; final thumbnail = book.data()['imageUrl'] ?? "https://via.placeholder.com/50";
final price = book.data()['price'];
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(author), subtitle: Text('$author - \$$price - ${book.data()['condition'] ?? 'Condition not available'}'),
onTap: () { onTap: () {
if (_isLoggedIn) { if (_isLoggedIn) {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => BookDetailsPage(book: book), // Pass book data builder: (context) => BookDetailsPage(book: book.data() as Map<String, dynamic>, bookId: bookId), // Pass book data
), ),
); );
} else { } else {
......
...@@ -7,6 +7,7 @@ import 'package:image_picker/image_picker.dart'; ...@@ -7,6 +7,7 @@ import 'package:image_picker/image_picker.dart';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart'; import 'package:firebase_storage/firebase_storage.dart';
import 'colors.dart'; import 'colors.dart';
import 'NavBar.dart';
class PostBookPage extends StatefulWidget { class PostBookPage extends StatefulWidget {
@override @override
...@@ -137,6 +138,7 @@ Future<String?> uploadImageToImgur(File imageFile) async { ...@@ -137,6 +138,7 @@ Future<String?> uploadImageToImgur(File imageFile) async {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool isDarkMode = Theme.of(context).brightness == Brightness.dark; final bool isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold( return Scaffold(
drawer: NavBar(),
appBar: AppBar( appBar: AppBar(
iconTheme: IconThemeData( iconTheme: IconThemeData(
color: isDarkMode ? kDarkBackground : kLightBackground, color: isDarkMode ? kDarkBackground : kLightBackground,
......
...@@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; ...@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:paperchase_app/book_detail_page.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'dart:io'; import 'dart:io';
import 'colors.dart'; import 'colors.dart';
...@@ -155,7 +156,9 @@ class ProfilePage extends StatelessWidget { ...@@ -155,7 +156,9 @@ class ProfilePage extends StatelessWidget {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
final books = booksSnapshot.data?.docs ?? []; final books = booksSnapshot.data!.docs;
if (books.isEmpty) { if (books.isEmpty) {
return Text( return Text(
...@@ -182,6 +185,7 @@ class ProfilePage extends StatelessWidget { ...@@ -182,6 +185,7 @@ class ProfilePage extends StatelessWidget {
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemCount: books.length, itemCount: books.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final doc = books[index];
final book = books[index].data() as Map<String, dynamic>; final book = books[index].data() as Map<String, dynamic>;
return Card( return Card(
color: isDarkMode ? Colors.grey[900] : Colors.white, color: isDarkMode ? Colors.grey[900] : Colors.white,
...@@ -204,11 +208,14 @@ class ProfilePage extends StatelessWidget { ...@@ -204,11 +208,14 @@ class ProfilePage extends StatelessWidget {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
onTap: () { onTap: () {
Navigator.pushNamed( Navigator.push(
context, context,
'/book_details', MaterialPageRoute(
arguments: books[index].id, builder: (context) => BookDetailsPage(book: book, bookId: doc.id ),
),
); );
}, },
), ),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment