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

Aryan-4/27/25

parent 9cee33a8
......@@ -499,7 +499,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.IOSPhoneTesting.paperchaseApp;
PRODUCT_BUNDLE_IDENTIFIER = com.IOSPhoneTesting2.paperchaseApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
......@@ -682,7 +682,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.IOSPhoneTesting.paperchaseApp;
PRODUCT_BUNDLE_IDENTIFIER = com.IOSPhoneTesting2.paperchaseApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
......@@ -705,7 +705,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.IOSPhoneTesting.paperchaseApp;
PRODUCT_BUNDLE_IDENTIFIER = com.IOSPhoneTesting2.paperchaseApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
......
......@@ -2,19 +2,24 @@ import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart';
import 'package:paperchase_app/chat_page.dart';
import 'colors.dart';
import 'NavBar.dart';
class BookDetailsPage extends StatelessWidget {
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
Widget build(BuildContext context) {
final bool isDarkMode = Theme.of(context).brightness == Brightness.dark;
final currentUser = FirebaseAuth.instance.currentUser;
final isMyBook = currentUser?.uid == book['userId'];
final title = book['title'] ?? 'No title available';
final author = book['author'] ?? 'No author available';
......@@ -28,6 +33,7 @@ class BookDetailsPage extends StatelessWidget {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
iconTheme: IconThemeData(
color: isDarkMode ? kDarkBackground : kLightBackground,
),
......@@ -42,7 +48,7 @@ class BookDetailsPage extends StatelessWidget {
),
),
),
drawer: const NavBar(),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
......@@ -68,12 +74,31 @@ class BookDetailsPage extends StatelessWidget {
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
Text(description, style: const TextStyle(fontSize: 16)),
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(
width: double.infinity,
child: ElevatedButton(
onPressed: () =>
_contactSeller(context, book['userId'], title),
_contactSeller(context, book, bookId),
style: ElevatedButton.styleFrom(
backgroundColor: kPrimaryColor,
padding: const EdgeInsets.symmetric(vertical: 16),
......@@ -86,7 +111,9 @@ class BookDetailsPage extends StatelessWidget {
style: TextStyle(fontSize: 18, color: Colors.white),
),
),
)
else if (currentUser == null)
Center(
child: TextButton(
......@@ -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 {
final users = [currentUser.uid, sellerId]..sort();
final chatRoomId = users.join('_');
final existingChat = await FirebaseFirestore.instance
.collection('chats')
.doc(chatRoomId)
.get();
final chatData = {
'users': users,
'lastMessage': 'Hi! Is this book still available?',
'lastMessageTime': FieldValue.serverTimestamp(),
'bookTitle': bookTitle,
'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);
}
Future<void> _contactSeller(BuildContext context, Map<String, dynamic> book, String bookId) 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;
}
final sellerId = book['userId']; // 📌 This is the user who posted the book
final isBuyer = currentUser.uid != sellerId;
final rolePrefix = isBuyer ? 'buyer' : 'seller';
final users = [currentUser.uid, sellerId]..sort();
final chatRoomId = "${rolePrefix}_${bookId}_${users.join('_')}";
await FirebaseFirestore.instance
.collection('chats')
.doc(chatRoomId)
.collection('messages')
.add({
'senderId': currentUser.uid,
'message': 'Hi! Is this book still available?',
'timestamp': FieldValue.serverTimestamp(),
try {
final sellerDoc = await FirebaseFirestore.instance
.collection('users')
.doc(sellerId)
.get();
final sellerName = sellerDoc.exists
? "${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) {
Navigator.pushReplacementNamed(context, '/inbox');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Chat started with the seller')),
);
}
} catch (e) {
debugPrint("Error contacting seller: $e");
if (context.mounted) {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Failed to contact seller. Please try again.')),
const SnackBar(content: Text('Book removed successfully')),
);
}
}
}
String _formatPrice(dynamic price) {
if (price == null) return '0.00';
if (price is num) return price.toStringAsFixed(2);
......
......@@ -2,27 +2,24 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:paperchase_app/book_detail_page.dart';
import 'colors.dart';
class StrictChatPage extends StatefulWidget {
final String chatId;
//final String bookId;
final String otherUserName;
final List<String> predefinedMessages;
final String currentUserId;
final String sellerId;
const StrictChatPage({
Key? key,
required this.chatId,
required this.otherUserName,
this.predefinedMessages = const [
"Is this still available?",
"When can we meet?",
"I'll take it",
"Thanks!",
"Hello",
"Can you hold it for me?",
"What's your lowest price?",
],
}) : super(key: key);
required this.currentUserId,
required this.sellerId,
}) : super(key: key);
@override
_StrictChatPageState createState() => _StrictChatPageState();
......@@ -32,6 +29,32 @@ class _StrictChatPageState extends State<StrictChatPage> {
final ScrollController _scrollController = ScrollController();
final TextEditingController _messageController = TextEditingController();
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
void initState() {
......@@ -52,10 +75,11 @@ class _StrictChatPageState extends State<StrictChatPage> {
.collection('chats')
.doc(widget.chatId)
.get();
if (doc.exists) {
setState(() {
_bookTitle = doc.data()?['bookTitle'] as String?;
_bookId = doc.data()?['bookId'] as String?;
});
}
} catch (e) {
......@@ -111,7 +135,8 @@ class _StrictChatPageState extends State<StrictChatPage> {
final backgroundColor2 = isDarkMode ? kLightBackground : kDarkBackground;
final textColor = isDarkMode ? kDarkText : kLightText;
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(
appBar: AppBar(
......@@ -176,7 +201,8 @@ class _StrictChatPageState extends State<StrictChatPage> {
final text = data['message'] as String? ?? '';
final senderId = data['senderId'] as String? ?? '';
final currentUser = FirebaseAuth.instance.currentUser;
final isMe = currentUser != null && senderId == currentUser.uid;
final isMe = currentUser != null &&
senderId == currentUser.uid;
return Container(
margin: const EdgeInsets.symmetric(vertical: 4),
......@@ -187,7 +213,10 @@ class _StrictChatPageState extends State<StrictChatPage> {
children: [
Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.75,
maxWidth: MediaQuery
.of(context)
.size
.width * 0.75,
),
padding: const EdgeInsets.symmetric(
horizontal: 16,
......@@ -216,48 +245,78 @@ class _StrictChatPageState extends State<StrictChatPage> {
),
),
Container(
padding: const EdgeInsets.all(16),
margin: const EdgeInsets.only(top: 2),
width: double.infinity,
decoration: BoxDecoration(
color: backgroundColor2,
border: Border(
top: BorderSide(
color: backgroundColor.withOpacity(0.2),
),
padding: const EdgeInsets.all(16),
margin: const EdgeInsets.only(top: 2),
width: double.infinity,
decoration: BoxDecoration(
color: backgroundColor2,
border: Border(
top: BorderSide(
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(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
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),
],
child: const Text(
'Confirmed!',
style: TextStyle(fontSize: 18, color: kLightText),
),
),
),
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:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'colors.dart';
import 'NavBar.dart';
import 'chat_page.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:paperchase_app/NavBar.dart';
import 'package:paperchase_app/chat_page.dart';
import 'package:paperchase_app/colors.dart';
enum BookFilter {
all,
sold,
bought,
}
class InboxPage extends StatefulWidget {
const InboxPage({super.key});
......@@ -20,33 +14,10 @@ class InboxPage extends StatefulWidget {
}
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) {
final baseQuery = FirebaseFirestore.instance.collection('chats');
switch (_currentFilter) {
case BookFilter.all:
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);
}
return FirebaseFirestore.instance
.collection('chats')
.where('users', arrayContains: userId);
}
@override
......@@ -61,9 +32,7 @@ class _InboxPageState extends State<InboxPage> {
if (currentUser == null) {
return Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(
color: isDarkMode ? kDarkBackground : kLightBackground,
),
iconTheme: IconThemeData(color: textColor),
title: const Text(
"Inbox",
style: TextStyle(
......@@ -74,7 +43,7 @@ class _InboxPageState extends State<InboxPage> {
color: kPrimaryColor,
),
),
foregroundColor: isDarkMode ? kLightBackground : kDarkBackground,
backgroundColor: scaffoldColor,
),
drawer: const NavBar(),
body: Container(
......@@ -102,75 +71,21 @@ class _InboxPageState extends State<InboxPage> {
),
),
),
bottomNavigationBar: BottomNavigationBar(
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');
}
},
),
bottomNavigationBar: _buildBottomNavigationBar(isDarkMode, textColor2),
);
}
return Scaffold(
appBar: AppBar(
title: Row(
children: [
const Text(
'Inbox',
style: TextStyle(
fontFamily: 'Impact',
fontSize: 24,
fontStyle: FontStyle.italic,
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;
});
}
},
),
),
),
),
],
title: const Text(
'Inbox',
style: TextStyle(
fontFamily: 'Impact',
fontSize: 24,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.bold,
color: kPrimaryColor,
),
),
backgroundColor: scaffoldColor,
iconTheme: IconThemeData(color: textColor2),
......@@ -183,26 +98,7 @@ class _InboxPageState extends State<InboxPage> {
.orderBy('lastMessageTime', descending: true)
.snapshots(),
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) {
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(
child: Text(
'Error loading conversations: ${snapshot.error}',
......@@ -210,39 +106,22 @@ class _InboxPageState extends State<InboxPage> {
),
);
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
return _buildChatList(snapshot, currentUser, isDarkMode, textColor);
},
),
),
bottomNavigationBar: BottomNavigationBar(
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');
}
},
),
bottomNavigationBar: _buildBottomNavigationBar(isDarkMode, textColor2),
);
}
Widget _buildChatList(AsyncSnapshot<QuerySnapshot> snapshot, User currentUser, bool isDarkMode, Color textColor) {
final chats = snapshot.data?.docs ?? [];
if (chats.isEmpty) {
return Center(
child: Column(
......@@ -250,22 +129,9 @@ class _InboxPageState extends State<InboxPage> {
children: [
Icon(Icons.chat_bubble_outline, size: 64, color: textColor.withOpacity(0.5)),
const SizedBox(height: 16),
Text(
_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 Text('No conversations yet'),
const SizedBox(height: 8),
Text(
_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,
),
const Text('Browse books and contact sellers to start chatting'),
],
),
);
......@@ -286,29 +152,16 @@ class _InboxPageState extends State<InboxPage> {
itemBuilder: (context, index) {
final chat = sortedChats[index];
final data = chat.data() as Map<String, dynamic>;
if (kDebugMode) {
print('Chat data: $data');
}
final chatId = chat.id;
// We'll determine real seller in FutureBuilder
final bookId = data['bookId'] as String? ?? '';
final lastMessage = data['lastMessage'] as String?;
final lastMessageTime = (data['lastMessageTime'] as Timestamp?)?.toDate();
final bookTitle = data['bookTitle'] as String?;
final usersList = (data['users'] as List?)?.cast<String>() ?? [];
String otherUserId;
try {
otherUserId = usersList.firstWhere(
(id) => id != currentUser.uid,
orElse: () => 'unknown',
);
} catch (e) {
if (kDebugMode) {
print('Error finding other user: $e');
}
otherUserId = 'unknown';
}
String otherUserId = usersList.firstWhere((id) => id != currentUser.uid, orElse: () => 'unknown');
return FutureBuilder<DocumentSnapshot>(
future: FirebaseFirestore.instance.collection('users').doc(otherUserId).get(),
builder: (context, userSnapshot) {
......@@ -318,79 +171,77 @@ class _InboxPageState extends State<InboxPage> {
userName = '${userData['first_name'] ?? ''} ${userData['last_name'] ?? ''}'.trim();
if (userName.isEmpty) userName = 'Unknown User';
}
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: chat.id,
otherUserName: userName,
predefinedMessages: const [
"Is this still available?",
"When can we meet?",
"I'll take it",
"Thanks!",
"Hello",
"Can you hold it for me?",
"What's your lowest price?",
],
),
),
return FutureBuilder<QuerySnapshot>(
future: FirebaseFirestore.instance
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timestamp', descending: false)
.limit(1)
.get(),
builder: (context, messagesSnapshot) {
String sellerId = '';
String buyerId = '';
// Check who sent first message to determine buyer
if (messagesSnapshot.hasData && messagesSnapshot.data!.docs.isNotEmpty) {
final firstMessage = messagesSnapshot.data!.docs.first;
final firstMessageData = firstMessage.data() as Map<String, dynamic>;
buyerId = firstMessageData['senderId'] as String? ?? '';
// 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],
child: Text(
userName[0].toUpperCase(),
style: TextStyle(
color: textColor,
fontWeight: FontWeight.bold,
),
),
),
title: Text(
}
return _buildChatListItem(
context,
chatId,
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,
),
currentUser.uid,
sellerId,
bookTitle,
lastMessage,
lastMessageTime,
isDarkMode,
textColor,
);
},
);
},
);
......@@ -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) {
final now = DateTime.now();
final difference = now.difference(timestamp);
......
......@@ -3,9 +3,6 @@ import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.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 'signup.dart';
import 'profile.dart';
......@@ -14,7 +11,6 @@ import 'inbox.dart';
import 'package:firebase_app_check/firebase_app_check.dart';
import 'colors.dart';
import 'utils.dart';
import 'home.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'NavBar.dart';
import 'book_detail_page.dart';
......@@ -185,7 +181,8 @@ class _HomePageState extends State<HomePage> {
}).toList();
setState(() {
_books = filteredBooks.map((doc) => doc.data()).toList();
_books = filteredBooks;
});
} catch (e) {
print("Error searching books: $e");
......@@ -202,7 +199,8 @@ class _HomePageState extends State<HomePage> {
.get();
setState(() {
_books = snapshot.docs.map((doc) => doc.data()).toList();
_books = snapshot.docs;
});
} catch (e) {
print("Error fetching recent books: $e");
......@@ -357,21 +355,29 @@ String _filterBy = 'Latest Posted'; // Default filter option
itemCount: _books.length,
itemBuilder: (context, index) {
final book = _books[index];
final bookId = book.id;
final data = book.data() as Map<String, dynamic>;
final title = book['title'] ?? "Unknown Title";
final author = book['author'] ?? "No author available";
final thumbnail = book['imageUrl'] ?? "https://via.placeholder.com/50";
final title = book.data()['title'] ?? "Unknown Title";
final author = book.data()['author'] ?? "No author available";
final thumbnail = book.data()['imageUrl'] ?? "https://via.placeholder.com/50";
final price = book.data()['price'];
return ListTile(
leading: Image.network(thumbnail, width: 50, height: 50, fit: BoxFit.cover),
title: Text(title),
subtitle: Text(author),
subtitle: Text('$author - \$$price - ${book.data()['condition'] ?? 'Condition not available'}'),
onTap: () {
if (_isLoggedIn) {
Navigator.push(
context,
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 {
......
......@@ -7,6 +7,7 @@ import 'package:image_picker/image_picker.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'colors.dart';
import 'NavBar.dart';
class PostBookPage extends StatefulWidget {
@override
......@@ -137,6 +138,7 @@ Future<String?> uploadImageToImgur(File imageFile) async {
Widget build(BuildContext context) {
final bool isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
drawer: NavBar(),
appBar: AppBar(
iconTheme: IconThemeData(
color: isDarkMode ? kDarkBackground : kLightBackground,
......
......@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:image_picker/image_picker.dart';
import 'package:paperchase_app/book_detail_page.dart';
import 'package:permission_handler/permission_handler.dart';
import 'dart:io';
import 'colors.dart';
......@@ -155,7 +156,9 @@ class ProfilePage extends StatelessWidget {
return const Center(child: CircularProgressIndicator());
}
final books = booksSnapshot.data?.docs ?? [];
final books = booksSnapshot.data!.docs;
if (books.isEmpty) {
return Text(
......@@ -182,6 +185,7 @@ class ProfilePage extends StatelessWidget {
physics: const NeverScrollableScrollPhysics(),
itemCount: books.length,
itemBuilder: (context, index) {
final doc = books[index];
final book = books[index].data() as Map<String, dynamic>;
return Card(
color: isDarkMode ? Colors.grey[900] : Colors.white,
......@@ -204,11 +208,14 @@ class ProfilePage extends StatelessWidget {
fontWeight: FontWeight.bold,
),
),
onTap: () {
Navigator.pushNamed(
Navigator.push(
context,
'/book_details',
arguments: books[index].id,
MaterialPageRoute(
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