Swiping update (2nd draft)

master
Rafael 2024-05-25 13:56:45 +02:00
parent 95b1df39da
commit 9c25f2402d
6 changed files with 238 additions and 26 deletions

View File

@ -8,6 +8,8 @@ class Constants {
static const String dbCollectionUsers = 'Users'; static const String dbCollectionUsers = 'Users';
static const String dbCollectionLanguages = 'languages'; static const String dbCollectionLanguages = 'languages';
static const String dbCollectionLocations = 'locations'; static const String dbCollectionLocations = 'locations';
static const String dbCollectionMatches = 'matches';
static const String dbCollectionSwipes = 'swipes';
static const String dbCollectionChatRooms = 'chat_rooms'; static const String dbCollectionChatRooms = 'chat_rooms';
static const String dbCollectionMessages = 'messages'; static const String dbCollectionMessages = 'messages';

View File

@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
class MatchedScreen extends StatelessWidget {
final String user1Name;
final String user2Name;
final String user1ImageUrl;
final String user2ImageUrl;
final VoidCallback onMessageButtonPressed;
final VoidCallback onContinueButtonPressed;
const MatchedScreen({super.key,
required this.user1Name,
required this.user2Name,
required this.user1ImageUrl,
required this.user2ImageUrl,
required this.onMessageButtonPressed,
required this.onContinueButtonPressed,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('It\'s a Match!')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'You and $user2Name have liked each other!',
style: const TextStyle(fontSize: 24),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircleAvatar(
backgroundImage: NetworkImage(user1ImageUrl),
radius: 50,
),
const SizedBox(width: 24),
CircleAvatar(
backgroundImage: NetworkImage(user2ImageUrl),
radius: 50,
),
],
),
const SizedBox(height: 24),
Text(
'$user1Name and $user2Name',
style: const TextStyle(fontSize: 20),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: onMessageButtonPressed,
child: const Text('Send a Message'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: onContinueButtonPressed,
child: const Text('Continue Swiping'),
),
],
),
),
);
}
}

View File

@ -8,6 +8,15 @@ bool equalContent(List<dynamic> list1, List<dynamic> list2) {
return const DeepCollectionEquality.unordered().equals(list1, list2); return const DeepCollectionEquality.unordered().equals(list1, list2);
} }
///
/// Creates a composite ID from the passed [ids].
/// In the format id(1)_id(n)
///
String getCompoundId(List<String> ids){
ids.sort(); // sort to ensure the result is the same for any order of ids
return ids.join('_');
}
/// ///
/// Convert decimal coordinate to degrees minutes seconds (DMS). /// Convert decimal coordinate to degrees minutes seconds (DMS).
/// ///
@ -30,7 +39,7 @@ String convertDecimalToDMS(double decimalValue) {
} }
/// ///
/// Get the displayName of our own Enumerations. /// Get the [displayName] of our own Enumerations.
/// ///
String getDisplayText(dynamic option) { String getDisplayText(dynamic option) {
// Check if the option is an enum and has a displayName property // Check if the option is an enum and has a displayName property

View File

@ -36,11 +36,11 @@ class UserProfile {
return UserProfile( return UserProfile(
id: doc.id, id: doc.id,
uid: data[Constants.dbFieldUsersID] ?? '',
email: data[Constants.dbFieldUsersEmail] ?? '', email: data[Constants.dbFieldUsersEmail] ?? '',
name: data[Constants.dbFieldUsersName] ?? '', name: data[Constants.dbFieldUsersName] ?? '',
firstName: data[Constants.dbFieldUsersFirstName] ?? '', firstName: data[Constants.dbFieldUsersFirstName] ?? '',
lastName: data[Constants.dbFieldUsersLastName] ?? '', lastName: data[Constants.dbFieldUsersLastName] ?? '',
uid: data[Constants.dbFieldUsersID] ?? '',
skills: List<String>.from(data[Constants.dbFieldUsersSkills] ?? []), skills: List<String>.from(data[Constants.dbFieldUsersSkills] ?? []),
skillsSought: skillsSought:
List<String>.from(data[Constants.dbFieldUsersSkillsSought] ?? []), List<String>.from(data[Constants.dbFieldUsersSkillsSought] ?? []),

View File

@ -4,9 +4,12 @@ import 'package:flutter/material.dart';
import 'package:swipable_stack/swipable_stack.dart'; import 'package:swipable_stack/swipable_stack.dart';
import '../constants.dart'; import '../constants.dart';
import '../forms/matched_screen.dart';
import '../models/language.dart'; import '../models/language.dart';
import '../models/location.dart'; import '../models/location.dart';
import '../models/user_profile.dart'; import '../models/user_profile.dart';
import '../services/auth/auth_service.dart';
import 'chat_page.dart';
class UserProfilePage extends StatefulWidget { class UserProfilePage extends StatefulWidget {
const UserProfilePage({super.key}); const UserProfilePage({super.key});
@ -19,6 +22,10 @@ class UserProfilePageState extends State<UserProfilePage> {
List<UserProfile> userProfiles = []; List<UserProfile> userProfiles = [];
late final SwipableStackController _controller; late final SwipableStackController _controller;
// get instance of firestore and auth
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final AuthService _authService = AuthService();
void _listenController() => setState(() {}); void _listenController() => setState(() {});
@override @override
@ -109,13 +116,11 @@ class UserProfilePageState extends State<UserProfilePage> {
} }
void _swipeLeft() { void _swipeLeft() {
_saveSwipeAction(userProfiles[_controller.currentIndex].id, 'dislike');
_controller.next( _controller.next(
swipeDirection: SwipeDirection.left, duration: Durations.extralong4); swipeDirection: SwipeDirection.left, duration: Durations.extralong4);
} }
void _swipeRight() { void _swipeRight() {
_saveSwipeAction(userProfiles[_controller.currentIndex].id, 'like');
_controller.next( _controller.next(
swipeDirection: SwipeDirection.right, duration: Durations.extralong4); swipeDirection: SwipeDirection.right, duration: Durations.extralong4);
} }
@ -125,12 +130,100 @@ class UserProfilePageState extends State<UserProfilePage> {
swipeDirection: SwipeDirection.up, duration: Durations.extralong2); swipeDirection: SwipeDirection.up, duration: Durations.extralong2);
} }
void _saveSwipeAction(String userId, String action) { /// Save swipe status to database
/* FirebaseFirestore.instance.collection('swipes').add({ Future<void> _saveSwipeAction(
'userId': userId, String swipedUserId, SwipeDirection direction) async {
'action': action, await _firestore
'timestamp': FieldValue.serverTimestamp(), .collection(Constants.dbCollectionUsers)
});*/ .doc(_authService.getCurrentUser()!.uid)
.collection(Constants.dbCollectionSwipes)
.doc(swipedUserId) // UserID instead of autogenerated ID
.set(
{
'swipedId': swipedUserId,
'liked': direction == SwipeDirection.right,
'timestamp': FieldValue.serverTimestamp(),
},
);
}
UserProfile getUserProfile(String userId) {
return userProfiles.firstWhere((x) => x.uid == userId);
}
/// Check whether the swiped user has also swiped current user to the right
Future<void> _checkForMatch(swipedUserId) async {
String currentUserId = _authService.getCurrentUser()!.uid;
final QuerySnapshot matchSnapshot = await _firestore
.collection(Constants.dbCollectionUsers)
.doc(swipedUserId)
.collection(Constants.dbCollectionSwipes)
.where('swipedId', isEqualTo: currentUserId)
.where('liked', isEqualTo: true)
.get();
if (matchSnapshot.docs.isNotEmpty) {
// save match for both users
final matchesCurrentUser = _firestore
.collection(Constants.dbCollectionUsers)
.doc(currentUserId)
.collection(Constants.dbCollectionMatches);
final matchesSwipedUser = _firestore
.collection(Constants.dbCollectionUsers)
.doc(swipedUserId)
.collection(Constants.dbCollectionMatches);
await matchesCurrentUser.add({
'otherUserId': swipedUserId,
'timestamp': FieldValue.serverTimestamp(),
});
await matchesSwipedUser.add({
'otherUserId': currentUserId,
'timestamp': FieldValue.serverTimestamp(),
});
//
// TODO Notification and further logic, e.g. initialization of the chat
//
print(
'We have a match between ${getUserProfile(currentUserId).name} and ${getUserProfile(swipedUserId).name}');
showMatchedScreen(currentUserId, swipedUserId);
}
}
showMatchedScreen(String currentUserId, String swipedUserId) {
UserProfile currentUser = getUserProfile(currentUserId);
UserProfile swipedUser = getUserProfile(swipedUserId);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MatchedScreen(
user1Name: swipedUser.name,
user2Name: currentUser.name,
user1ImageUrl: '', // swipedUser.profilePicture,
user2ImageUrl: '', // currentUser.profilePicture,
onMessageButtonPressed: () {
Navigator.pop(context); // Close the MatchedScreen
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatPage(
receiverEmail: swipedUser.email,
receiverID: swipedUser.uid,
),
),
);
},
onContinueButtonPressed: () {
Navigator.pop(context); // Close the MatchedScreen
},
),
),
);
} }
@override @override
@ -159,21 +252,63 @@ class UserProfilePageState extends State<UserProfilePage> {
}, },
controller: _controller, controller: _controller,
stackClipBehaviour: Clip.none, stackClipBehaviour: Clip.none,
swipeAnchor: SwipeAnchor.bottom,
itemCount: userProfiles.length+1, // +1 for rerun option
onSwipeCompleted: (index, direction) { onSwipeCompleted: (index, direction) {
if (index >= userProfiles.length) { //
setState(() { // Swipe logic goes here
_controller.currentIndex = 0; // again from the start //
}); String swipedUserId =
userProfiles[_controller.currentIndex].id;
if (direction == SwipeDirection.right) {
_saveSwipeAction(swipedUserId, SwipeDirection.right);
_checkForMatch(swipedUserId);
} else if (direction == SwipeDirection.left) {
_saveSwipeAction(swipedUserId, SwipeDirection.left);
} }
}, },
horizontalSwipeThreshold: 0.8, horizontalSwipeThreshold: 0.8,
verticalSwipeThreshold: 0.8, verticalSwipeThreshold: 0.8,
/*overlayBuilder: (context, properties) {
final opacity = min(properties.swipeProgress, 1.0);
final isRight = properties.direction == SwipeDirection.right;
return Opacity(
opacity: isRight ? opacity : 0,
child: CardLabel.right(),
);
},*/
builder: (context, properties) { builder: (context, properties) {
if (properties.index == userProfiles.length) {
return Center(
child: Column( // Show end message and restart button
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('That\'s all.\nDo you want to do another run?',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
setState(() {
_controller.currentIndex = 0; // Restart swiping from the beginning
});
},
child: const Text('Rerun'),
),
],
),
);
}
final userProfile = final userProfile =
userProfiles[properties.index % userProfiles.length]; userProfiles[properties.index % userProfiles.length];
return Container( return Container(
alignment: Alignment.center, alignment: Alignment.center,
color: Colors.tealAccent, decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25.0),
color: Colors.blue,
),
child: Stack( child: Stack(
children: [ children: [
Card( Card(

View File

@ -1,8 +1,10 @@
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:cofounderella/constants.dart';
import 'package:cofounderella/models/message.dart';
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import '../../constants.dart';
import '../../helper.dart';
import '../../models/message.dart';
class ChatService { class ChatService {
// get instance of firestore and auth // get instance of firestore and auth
final FirebaseFirestore _firestore = FirebaseFirestore.instance; final FirebaseFirestore _firestore = FirebaseFirestore.instance;
@ -40,10 +42,8 @@ class ChatService {
timestamp: timestamp, timestamp: timestamp,
); );
// construct chat room ID for the two users (sorted to ensure uniqueness) // construct chat room ID for the two users
List<String> ids = [currentUserID, receiverID]; String chatRoomID = getCompoundId([currentUserID, receiverID]);
ids.sort(); // sort to ensure the chatroomID is the same for any 2 users
String chatRoomID = ids.join('_');
// add new message to database // add new message to database
await _firestore await _firestore
@ -55,11 +55,7 @@ class ChatService {
// get messages // get messages
Stream<QuerySnapshot> getMessages(String userID, otherUserID) { Stream<QuerySnapshot> getMessages(String userID, otherUserID) {
// TODO create chat room ID -- same code snippet as above String chatRoomID = getCompoundId([userID, otherUserID]);
// construct chat room ID for the two users (sorted to ensure uniqueness)
List<String> ids = [userID, otherUserID];
ids.sort(); // sort to ensure the chatroomID is the same for any 2 users
String chatRoomID = ids.join('_');
return _firestore return _firestore
.collection(Constants.dbCollectionChatRooms) .collection(Constants.dbCollectionChatRooms)