cofounderella/lib/pages/liked_users_page.dart

352 lines
12 KiB
Dart

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import '../components/user_tile_likes.dart';
import '../constants.dart';
import '../enumerations.dart';
import '../models/swipe.dart';
import '../models/user_profile.dart';
import '../services/auth/auth_service.dart';
import '../services/user_service.dart';
import '../utils/helper_dialogs.dart';
import 'chat_page.dart';
import 'user_profile_page.dart';
class LikedUsersPage extends StatefulWidget {
const LikedUsersPage({super.key});
@override
LikedUsersPageState createState() => LikedUsersPageState();
}
class LikedUsersPageState extends State<LikedUsersPage> {
final String currentUserId = AuthService().getCurrentUser()!.uid;
final Map<String, bool> _cachedMatches = {};
List<MapEntry<Swipe, DocumentSnapshot>> _likedUsersWithSwipes = [];
bool _isLoading = true;
String? _fetchError;
UserProfile? _currentUser;
ViewOrder _orderPreference = ViewOrder.swipedFirst;
MenuSort _sortPreference = MenuSort.nameAsc;
@override
void initState() {
super.initState();
_loadCurrentUserProfile();
_fetchLikedUsersWithSwipes();
}
Future<void> _loadCurrentUserProfile() async {
try {
_currentUser = await UserService.getUserProfileById(currentUserId);
} catch (e) {
debugPrint("Error loading current user profile: $e");
}
}
Future<void> _fetchLikedUsersWithSwipes() async {
try {
QuerySnapshot likedUsersSnapshot = await FirebaseFirestore.instance
.collection(Constants.dbCollectionUsers)
.doc(currentUserId)
.collection(Constants.dbCollectionSwipes)
.where(Constants.dbFieldSwipesLike, isEqualTo: true)
.get();
List<MapEntry<Swipe, DocumentSnapshot>> likedUsersWithSwipes = [];
for (var doc in likedUsersSnapshot.docs) {
Swipe swipe = Swipe.fromDocument(doc);
DocumentSnapshot userDoc = await FirebaseFirestore.instance
.collection(Constants.dbCollectionUsers)
.doc(swipe.swipedId)
.get();
// Initialize _cachedMatches to keep database accesses to a minimum
bool hasMatch =
await UserService.hasMatch(currentUserId, swipe.swipedId);
_cachedMatches[swipe.swipedId] = hasMatch;
likedUsersWithSwipes.add(MapEntry(swipe, userDoc));
}
setState(() {
_likedUsersWithSwipes = likedUsersWithSwipes;
_isLoading = false;
});
} catch (e) {
setState(() {
_fetchError = e.toString();
_isLoading = false;
});
}
}
Future<void> _unlikeUser(String userId) async {
try {
await FirebaseFirestore.instance
.collection(Constants.dbCollectionUsers)
.doc(currentUserId)
.collection(Constants.dbCollectionSwipes)
.doc(userId)
.delete();
// Refresh the UI
setState(() {
_likedUsersWithSwipes
.removeWhere((entry) => entry.key.swipedId == userId);
});
} catch (e) {
if (mounted) {
showMsg(context, 'Error undoing Like', e.toString());
}
}
}
List<MapEntry<Swipe, DocumentSnapshot>> _getSortedLikedUsersWithSwipes() {
List<MapEntry<Swipe, DocumentSnapshot>> likedOnlyUsers = [];
List<MapEntry<Swipe, DocumentSnapshot>> matchedUsers = [];
for (var entry in _likedUsersWithSwipes) {
bool hasMatch = _cachedMatches.containsKey(entry.key.swipedId)
? _cachedMatches[entry.key.swipedId]!
: false;
if (hasMatch) {
matchedUsers.add(entry);
} else {
likedOnlyUsers.add(entry);
}
}
if (_sortPreference == MenuSort.timestampAsc) {
likedOnlyUsers.sort((a, b) => a.key.timestamp.compareTo(b.key.timestamp));
matchedUsers.sort((a, b) => a.key.timestamp.compareTo(b.key.timestamp));
} else if (_sortPreference == MenuSort.timestampDesc) {
likedOnlyUsers.sort((a, b) => b.key.timestamp.compareTo(a.key.timestamp));
matchedUsers.sort((a, b) => b.key.timestamp.compareTo(a.key.timestamp));
} else if (_sortPreference == MenuSort.nameAsc) {
likedOnlyUsers.sort((a, b) => _compareNames(a, b));
matchedUsers.sort((a, b) => _compareNames(a, b));
} else if (_sortPreference == MenuSort.nameDesc) {
likedOnlyUsers.sort((a, b) => _compareNames(b, a));
matchedUsers.sort((a, b) => _compareNames(b, a));
}
if (_orderPreference == ViewOrder.matchedFirst) {
return [...matchedUsers, ...likedOnlyUsers];
} else if (_orderPreference == ViewOrder.swipedOnly) {
return [...likedOnlyUsers];
} else if (_orderPreference == ViewOrder.matchedOnly) {
return [...matchedUsers];
} else {
// default: swipesFirst
return [...likedOnlyUsers, ...matchedUsers];
}
}
int _compareNames(MapEntry<Swipe, DocumentSnapshot> a,
MapEntry<Swipe, DocumentSnapshot> b) {
final dataA = (a.value.data() as Map<String, dynamic>);
final dataB = (b.value.data() as Map<String, dynamic>);
final nameA =
dataA.isNotEmpty && dataA.containsKey(Constants.dbFieldUsersName)
? a.value[Constants.dbFieldUsersName]
: '';
final nameB =
dataB.isNotEmpty && dataB.containsKey(Constants.dbFieldUsersName)
? b.value[Constants.dbFieldUsersName]
: '';
return nameA.compareTo(nameB);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('My favored profiles'),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
DropdownButton<ViewOrder>(
value: _orderPreference,
items: const [
DropdownMenuItem(
value: ViewOrder.swipedFirst,
child: Text('Swipes First'),
),
DropdownMenuItem(
value: ViewOrder.matchedFirst,
child: Text('Matches First'),
),
DropdownMenuItem(
value: ViewOrder.swipedOnly,
child: Text('Swipes Only'),
),
DropdownMenuItem(
value: ViewOrder.matchedOnly,
child: Text('Matches Only'),
),
],
onChanged: (value) {
// update UI on change only
if (_orderPreference != value) {
setState(() {
_orderPreference = value!;
});
}
},
),
Align(
alignment: Alignment.centerRight,
child: PopupMenuButton<MenuSort>(
icon: const Icon(Icons.sort),
onSelected: (MenuSort item) {
// update UI on change only
if (_sortPreference != item) {
setState(() {
_sortPreference = item;
});
}
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<MenuSort>>[
PopupMenuItem<MenuSort>(
value: MenuSort.nameAsc,
child: ListTile(
leading: _sortPreference == MenuSort.nameAsc
? const Icon(Icons.check)
: const Icon(null),
title: const Text('Name Ascending'),
),
),
PopupMenuItem<MenuSort>(
value: MenuSort.nameDesc,
child: ListTile(
leading: _sortPreference == MenuSort.nameDesc
? const Icon(Icons.check)
: const Icon(null),
title: const Text('Name Descending'),
),
),
PopupMenuItem<MenuSort>(
value: MenuSort.timestampAsc,
child: ListTile(
leading: _sortPreference == MenuSort.timestampAsc
? const Icon(Icons.check)
: const Icon(null),
title: const Text('Oldest first'),
),
),
PopupMenuItem<MenuSort>(
value: MenuSort.timestampDesc,
child: ListTile(
leading: _sortPreference == MenuSort.timestampDesc
? const Icon(Icons.check)
: const Icon(null),
title: const Text('Newest first'),
),
),
],
),
),
],
),
),
_isLoading
? const Center(child: CircularProgressIndicator())
: _fetchError != null
? Center(child: Text('Error: $_fetchError'))
: _likedUsersWithSwipes.isEmpty
? const Center(child: Text('No favored profiles yet.'))
: buildLikedUserList(),
],
),
);
}
Widget buildLikedUserList() {
var sortedSwipeUsers = _getSortedLikedUsersWithSwipes();
return Expanded(
child: ListView.builder(
itemCount: sortedSwipeUsers.length,
itemBuilder: (BuildContext context, int index) {
var entry = sortedSwipeUsers[index];
var user = entry.value;
bool hasMatch = _cachedMatches.containsKey(user.id)
? _cachedMatches[user.id]!
: false;
return UserTileLikes(
user: user,
hasMatch: hasMatch,
currentUser: _currentUser,
onUnlike: () async {
Map<String, dynamic> userMap =
user.data() as Map<String, dynamic>;
bool hasName = userMap.containsKey(Constants.dbFieldUsersName);
bool? confirm = await showConfirmationDialog(
context,
'Confirm Removal',
'Are you sure you want to remove ${(hasName ? user[Constants.dbFieldUsersName] : 'Name: n/a')} from your list?',
);
if (confirm == true) {
bool hasMatch =
await UserService.hasMatch(currentUserId, user.id);
if (!hasMatch) {
await _unlikeUser(user.id);
} else {
// abort, update _cachedMatches and UI
_cachedMatches[user.id] = hasMatch;
setState(() {});
}
}
},
onShowChat: () {
Map<String, dynamic> userMap =
user.data() as Map<String, dynamic>;
String? profilePic =
userMap.containsKey(Constants.dbFieldUsersProfilePic)
? user[Constants.dbFieldUsersProfilePic]
: null;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatPage(
receiverEmail: user[Constants.dbFieldUsersEmail],
receiverID: user[Constants.dbFieldUsersID],
chatTitle: user[Constants.dbFieldUsersName],
profileImageUrl: profilePic,
),
),
);
},
onViewInfo: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => UserProfilePage(
userId: user.id,
),
),
);
},
);
},
),
);
}
}