From 3be7fa4dc1adc6b312e6af9a8736a1282f5cb6da Mon Sep 17 00:00:00 2001 From: Rafael <1024481@stud.hs-mannheim.de> Date: Sun, 9 Jun 2024 00:04:13 +0200 Subject: [PATCH] Appearance of the swipe card revised --- lib/enumerations.dart | 26 ++++- lib/models/location.dart | 14 ++- lib/pages/home_page.dart | 105 +++++++++--------- lib/pages/registration_complete_page.dart | 2 +- lib/pages/user_matching_page.dart | 125 +++++++++++++++++----- lib/services/auth/auth_gate.dart | 2 +- 6 files changed, 187 insertions(+), 87 deletions(-) diff --git a/lib/enumerations.dart b/lib/enumerations.dart index 747e6be..738b6ce 100644 --- a/lib/enumerations.dart +++ b/lib/enumerations.dart @@ -88,18 +88,36 @@ enum AvailabilityOption { lessThan10Hours, tenTo20Hours, twentyTo40Hours, - fullTime; + fullTime, + flexible; String get displayName { switch (this) { case AvailabilityOption.lessThan10Hours: return 'Less than 10 hours'; case AvailabilityOption.tenTo20Hours: - return 'Part time (10 - 20 hours)'; + return 'Part-time (10 - 20 hours)'; case AvailabilityOption.twentyTo40Hours: - return 'Part time (20 - 40 hours)'; + return 'Part-time (20 - 40 hours)'; case AvailabilityOption.fullTime: - return 'Full time (40 hours or more)'; + return 'Full-time (40 hours or more)'; + case AvailabilityOption.flexible: + return 'Flexible, full-time or part-time'; + } + } + + String get commitmentText { + switch (this) { + case AvailabilityOption.lessThan10Hours: + return 'secondary occupation with less than 10 hours per week'; + case AvailabilityOption.tenTo20Hours: + return 'part-time with 10 to 20 hours per week'; + case AvailabilityOption.twentyTo40Hours: + return 'part-time with 20 to 40 hours per week'; + case AvailabilityOption.fullTime: + return 'full-time'; + case AvailabilityOption.flexible: + return 'full-time or part-time'; } } diff --git a/lib/models/location.dart b/lib/models/location.dart index d3f5639..0ced8a1 100644 --- a/lib/models/location.dart +++ b/lib/models/location.dart @@ -8,8 +8,10 @@ class MyLocation { /// DE: Bundesland final String? administrativeArea; + /// City final String locality; + /// DE: Stadtteil final String? subLocality; final String? postalCode; @@ -67,10 +69,18 @@ class MyLocation { } } - /// Returns: locality, country + /// Returns the location formatted as 'locality, country' + /// if both values are present or an empty string if both values are empty. @override String toString() { - return '$locality, $country'; + if (locality.isNotEmpty && country.isNotEmpty) { + return '$locality, $country'; + } else if (country.isNotEmpty) { + return country; + } else if (locality.isNotEmpty) { + return locality; + } + return ''; } @override diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 760bca8..6c50cd9 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -1,12 +1,9 @@ import 'package:flutter/material.dart'; import '../components/my_drawer.dart'; -import '../services/auth/auth_service.dart'; class HomePage extends StatelessWidget { - HomePage({super.key}); - - final AuthService _authService = AuthService(); + const HomePage({super.key}); @override Widget build(BuildContext context) { @@ -19,58 +16,58 @@ class HomePage extends StatelessWidget { drawer: const MyDrawer(), // body: _buildUserList(), body: Center( - child: AspectRatio( - aspectRatio: 1, - child: LayoutBuilder( - builder: (context, constraints) { - // Calculation of the tile size based on the spacing - double size = constraints.biggest.shortestSide / 2 - 16; - return GridView.count( - crossAxisCount: 2, - crossAxisSpacing: 16.0, - mainAxisSpacing: 16.0, - padding: const EdgeInsets.all(16.0), - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - children: [ - _buildTile( - context, - 'Discover', - Icons.explore, - Colors.lightBlue.shade300, - '/discover', - size, - ), - _buildTile( - context, - 'View Profiles', - Icons.supervised_user_circle, - Colors.teal.shade300, - '/favorites', - size, - ), - _buildTile( - context, - 'Chat', - Icons.chat, - Colors.indigo.shade300, - '/chats', - size, - ), - _buildTile( - context, - 'My Profile', - Icons.edit_note, - Colors.blueGrey.shade300, - '/profile', - size, - ), - ], - ); - }, - ), + child: AspectRatio( + aspectRatio: 1, + child: LayoutBuilder( + builder: (context, constraints) { + // Calculation of the tile size based on the spacing + double size = constraints.biggest.shortestSide / 2 - 16; + return GridView.count( + crossAxisCount: 2, + crossAxisSpacing: 16.0, + mainAxisSpacing: 16.0, + padding: const EdgeInsets.all(16.0), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + children: [ + _buildTile( + context, + 'Discover', + Icons.explore, + Colors.lightBlue.shade300, + '/discover', + size, + ), + _buildTile( + context, + 'View Profiles', + Icons.supervised_user_circle, + Colors.teal.shade300, + '/favorites', + size, + ), + _buildTile( + context, + 'Chat', + Icons.chat, + Colors.indigo.shade300, + '/chats', + size, + ), + _buildTile( + context, + 'My Profile', + Icons.edit_note, + Colors.blueGrey.shade300, + '/profile', + size, + ), + ], + ); + }, ), ), + ), ); } diff --git a/lib/pages/registration_complete_page.dart b/lib/pages/registration_complete_page.dart index d062eca..1c6569b 100644 --- a/lib/pages/registration_complete_page.dart +++ b/lib/pages/registration_complete_page.dart @@ -45,7 +45,7 @@ class RegistrationCompletePage extends StatelessWidget { onPressed: () { Navigator.push( context, - MaterialPageRoute(builder: (context) => HomePage()), + MaterialPageRoute(builder: (context) => const HomePage()), ); }, child: const Text('S T A R T'), diff --git a/lib/pages/user_matching_page.dart b/lib/pages/user_matching_page.dart index 79d2474..aa271b4 100644 --- a/lib/pages/user_matching_page.dart +++ b/lib/pages/user_matching_page.dart @@ -1,6 +1,8 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:swipable_stack/swipable_stack.dart'; import '../constants.dart'; @@ -69,7 +71,7 @@ class UserMatchingPageState extends State { .get(); final Set likedUserIds = swipesSnapshot.docs - .where((doc) => doc['liked'] == true) + .where((doc) => doc[Constants.dbFieldSwipesLike] == true) .map((doc) => doc.id) .toSet(); @@ -77,8 +79,10 @@ class UserMatchingPageState extends State { DateTime.now().subtract(const Duration(hours: 24)); final Set dislikedUserIds = swipesSnapshot.docs .where((doc) => - doc['liked'] == false && - (doc['timestamp'] as Timestamp).toDate().isAfter(thresholdDate)) + doc[Constants.dbFieldSwipesLike] == false && + (doc[Constants.dbFieldSwipesTimestamp] as Timestamp) + .toDate() + .isAfter(thresholdDate)) .map((doc) => doc.id) .toSet(); @@ -178,9 +182,9 @@ class UserMatchingPageState extends State { .doc(swipedUserId) // UserID instead of autogenerated ID .set( { - 'swipedId': swipedUserId, - 'liked': direction == SwipeDirection.right, - 'timestamp': FieldValue.serverTimestamp(), + Constants.dbFieldSwipesSwipedId: swipedUserId, + Constants.dbFieldSwipesLike: direction == SwipeDirection.right, + Constants.dbFieldSwipesTimestamp: FieldValue.serverTimestamp(), }, ); } @@ -197,8 +201,8 @@ class UserMatchingPageState extends State { .collection(Constants.dbCollectionUsers) .doc(swipedUserId) .collection(Constants.dbCollectionSwipes) - .where('swipedId', isEqualTo: currentUserId) - .where('liked', isEqualTo: true) + .where(Constants.dbFieldSwipesSwipedId, isEqualTo: currentUserId) + .where(Constants.dbFieldSwipesLike, isEqualTo: true) .get(); if (matchSnapshot.docs.isNotEmpty) { @@ -384,32 +388,105 @@ class UserMatchingPageState extends State { } Widget _buildUserCard(UserProfile userProfile) { + String? profileImageUrl = userProfile.profilePictureUrl; + + int age = calcAge(userProfile.born); + String ageInfo = age > 0 ? ' ($age)' : ''; + + // Sort the languages according to the given order below + List sortedLanguages = List.from(userProfile.languages); + sortedLanguages.sort((a, b) { + if (a.code == 'de') return -1; // German first + if (b.code == 'de') return 1; // German first + if (a.code == 'en') return -1; // English second + if (b.code == 'en') return 1; // English second + return a.name.compareTo(b.name); // All others by name ascending + }); + + String shortDist = + shortestDistanceBetweenUsers(currentUserProfile!, userProfile) + .toStringAsFixed(0); + return Card( child: Padding( padding: const EdgeInsets.all(16.0), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(userProfile.name, style: const TextStyle(fontSize: 24)), - Text(userProfile.email, style: const TextStyle(fontSize: 24)), + if (kDebugMode) + Center( + child: Text( + userProfile.email, + style: const TextStyle(fontSize: 12), + ), + ), + + Center( + child: CircleAvatar( + radius: 50, + backgroundImage: + ((profileImageUrl != null && profileImageUrl.isNotEmpty)) + ? NetworkImage(profileImageUrl) + : null, + child: (profileImageUrl == null || profileImageUrl.isEmpty) + ? const Icon(Icons.person_pin, size: 50) + : null, + ), + ), + const SizedBox(height: 8), + Center( + child: Text('${userProfile.name}$ageInfo', + style: const TextStyle(fontSize: 24)), + ), const SizedBox(height: 8), Text( - 'Has skills and experience in: ${userProfile.skills.map((x) => x.displayName).join(', ')}'), + 'Would like to team up with someone who has experience in ' + '${userProfile.skillsSought.map((x) => x.displayName).join(', ')}.', + ), Text( - 'Seeks someone with skills in: ${userProfile.skillsSought.map((x) => x.displayName).join(', ')}'), - Text('Availability: ${userProfile.availability.displayName}'), - Text('Risk type: ${userProfile.risk.displayName}'), + 'He/She brings skills and experience in ' + '${userProfile.skills.map((x) => x.displayName).join(', ')}', + ), Text( - 'Speaks: ${userProfile.languages.map((lang) => lang.name).join(', ')}'), + 'and is willing to commit in ' + '${userProfile.availability.commitmentText}.', + ), Text( - 'Lives in: ${userProfile.locations[Constants.dbDocMainLocation]?.locality ?? 'N/A'}'), - Text( - 'Coordinates: ${userProfile.locations[Constants.dbDocMainLocation]?.latitude} ${userProfile.locations[Constants.dbDocMainLocation]?.longitude}'), - Text( - 'Second home: ${userProfile.locations[Constants.dbDocSecondLocation]?.locality ?? 'N/A'}'), - Text( - 'Shortest distance: ${shortestDistanceBetweenUsers(currentUserProfile!, userProfile).toStringAsFixed(0)} km'), + 'Lives in ${userProfile.locations[Constants.dbDocMainLocation]?.toString() ?? 'N/A'}' + ' and ${userProfile.locations[Constants.dbDocSecondLocation]?.toString() ?? 'N/A'}' + ' which is only/about $shortDist km away from you.', + ), + const SizedBox(height: 8), + const Row( + children: [ + Text('Spoken languages '), + Expanded(child: Divider()), + ], + ), + const SizedBox(height: 4), + Wrap( + children: sortedLanguages.map( + (language) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + language.iconFile, + height: 12.0, + ), + const SizedBox(width: 4.0), + Text(language.name), + // Space between each language icon pair + const SizedBox(width: 8.0), + ], + ); + }, + ).toList(), + ), + + //Text('Risk type: ${userProfile.risk.displayName}'), ], ), ), @@ -554,9 +631,7 @@ class CardOverlay extends StatelessWidget { child: Icon( direction == SwipeDirection.right ? Icons.thumb_up - : (direction == SwipeDirection.left - ? Icons.thumb_down - : Icons.skip_next), + : (direction == SwipeDirection.left ? Icons.thumb_down : null), size: 100, color: direction == SwipeDirection.right ? Colors.green diff --git a/lib/services/auth/auth_gate.dart b/lib/services/auth/auth_gate.dart index 412a137..dad3685 100644 --- a/lib/services/auth/auth_gate.dart +++ b/lib/services/auth/auth_gate.dart @@ -34,7 +34,7 @@ class AuthGate extends StatelessWidget { if (snapshot.connectionState == ConnectionState.waiting) { return const CircularProgressIndicator(); } else if (snapshot.hasData && snapshot.data == true) { - return HomePage(); + return const HomePage(); } else { // also in case of (snapshot.hasError) return const UserDataPage(