Swiping update (2nd draft)
parent
95b1df39da
commit
9c25f2402d
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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] ?? []),
|
||||||
|
|
|
@ -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
|
||||||
|
.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(),
|
'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(
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue