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 '../services/auth/auth_service.dart'; import '../utils/helper.dart'; import 'user_profile_page.dart'; class LikedUsersPage extends StatefulWidget { const LikedUsersPage({super.key}); @override LikedUsersPageState createState() => LikedUsersPageState(); } class LikedUsersPageState extends State { final String currentUserId = AuthService().getCurrentUser()!.uid; final Map _cachedMatches = {}; List> _likedUsersWithSwipes = []; bool _isLoading = true; String? _fetchError; ViewOrder _orderPreference = ViewOrder.swipedFirst; MenuSort _sortPreference = MenuSort.nameAsc; @override void initState() { super.initState(); _fetchLikedUsersWithSwipes(); } Future _fetchLikedUsersWithSwipes() async { try { QuerySnapshot likedUsersSnapshot = await FirebaseFirestore.instance .collection(Constants.dbCollectionUsers) .doc(currentUserId) .collection(Constants.dbCollectionSwipes) .where(Constants.dbFieldSwipesLike, isEqualTo: true) .get(); List> 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 _hasMatch(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 _hasMatch(String userId) async { DocumentSnapshot matchDoc = await FirebaseFirestore.instance .collection(Constants.dbCollectionUsers) .doc(currentUserId) .collection(Constants.dbCollectionMatches) .doc(userId) .get(); return matchDoc.exists; } Future _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 during unlike', e.toString()); } } } void _showMatchMessage() { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Match Exists'), content: const Text( 'You cannot unlike a user with whom you have a match.', ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Close'), ), ], ); }, ); } Future _showConfirmationDialog(String userId, String userName) async { bool? confirm = await showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Confirm Unlike'), content: Text('Are you sure you want to unlike $userName?'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('Cancel'), ), TextButton( onPressed: () => Navigator.of(context).pop(true), child: const Text('Confirm'), ), ], ); }, ); return confirm; } List> _getSortedLikedUsersWithSwipes() { List> likedOnlyUsers = []; List> 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 a, MapEntry b) { final dataA = (a.value.data() as Map); final dataB = (b.value.data() as Map); 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( 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( icon: const Icon(Icons.sort), onSelected: (MenuSort item) { // update UI on change only if (_sortPreference != item) { setState(() { _sortPreference = item; }); } }, itemBuilder: (BuildContext context) => >[ PopupMenuItem( value: MenuSort.nameAsc, child: ListTile( leading: _sortPreference == MenuSort.nameAsc ? const Icon(Icons.check) : const Icon(null), title: const Text('Name Ascending'), ), ), PopupMenuItem( value: MenuSort.nameDesc, child: ListTile( leading: _sortPreference == MenuSort.nameDesc ? const Icon(Icons.check) : const Icon(null), title: const Text('Name Descending'), ), ), PopupMenuItem( value: MenuSort.timestampAsc, child: ListTile( leading: _sortPreference == MenuSort.timestampAsc ? const Icon(Icons.check) : const Icon(null), title: const Text('Oldest first'), ), ), PopupMenuItem( 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 liked users found.')) : 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, onUnlike: () async { Map userMap = user.data() as Map; bool hasName = userMap.containsKey(Constants.dbFieldUsersName); bool? confirm = await _showConfirmationDialog( user.id, (hasName ? user[Constants.dbFieldUsersName] : 'Name: n/a'), ); if (confirm == true) { bool hasMatch = await _hasMatch(user.id); if (!hasMatch) { await _unlikeUser(user.id); } else { // abort, update _cachedMatches and UI _cachedMatches[user.id] = hasMatch; setState(() {}); } } }, //_unlikeUser(user.id), onShowMatchMessage: _showMatchMessage, onViewInfo: () { Navigator.push( context, MaterialPageRoute( builder: (BuildContext context) => UserProfilePage( userId: user.id, ), ), ); }, ); }, ), ); } }