import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:swipable_stack/swipable_stack.dart'; import '../constants.dart'; import '../models/language.dart'; import '../models/location.dart'; import '../models/user_profile.dart'; class UserProfilePage extends StatefulWidget { const UserProfilePage({super.key}); @override UserProfilePageState createState() => UserProfilePageState(); } class UserProfilePageState extends State { List userProfiles = []; late final SwipableStackController _controller; void _listenController() => setState(() {}); @override void initState() { super.initState(); _controller = SwipableStackController()..addListener(_listenController); _fetchUserProfiles(); } @override void dispose() { _controller ..removeListener(_listenController) ..dispose(); super.dispose(); } Future _fetchUserProfiles() async { final querySnapshot = await FirebaseFirestore.instance .collection(Constants.dbCollectionUsers) .get(); final users = await Future.wait(querySnapshot.docs.map((doc) async { final languagesSnapshot = await doc.reference.collection(Constants.dbCollectionLanguages).get(); final locationsSnapshot = await doc.reference.collection(Constants.dbCollectionLocations).get(); final languages = languagesSnapshot.docs.map((doc) { final data = doc.data(); return Language( code: data['code'], name: data['name'], nativeName: data['nativeName'], iconFile: data['iconFile'], ); }).toList(); final mainDoc = locationsSnapshot.docs.firstWhereOrNull( (doc) => doc.id == Constants.dbDocMainLocation, ); final secondaryDoc = locationsSnapshot.docs.firstWhereOrNull( (doc) => doc.id == Constants.dbDocSecondLocation, ); final locations = { Constants.dbDocMainLocation: mainDoc != null ? _createLocationFromDoc(mainDoc.data()) : null, Constants.dbDocSecondLocation: secondaryDoc != null ? _createLocationFromDoc(secondaryDoc.data()) : null, }; final data = doc.data(); return UserProfile( id: doc.id, uid: data[Constants.dbFieldUsersID] ?? '', email: data[Constants.dbFieldUsersEmail] ?? '', name: data[Constants.dbFieldUsersName] ?? '', firstName: data[Constants.dbFieldUsersFirstName] ?? '', lastName: data[Constants.dbFieldUsersLastName] ?? '', skills: List.from(data[Constants.dbFieldUsersSkills] ?? []), skillsSought: List.from(data[Constants.dbFieldUsersSkillsSought] ?? []), risk: data[Constants.dbFieldUsersRiskTolerance] ?? '', languages: languages, locations: locations, ); }).toList()); setState(() { userProfiles = users; }); } MyLocation? _createLocationFromDoc(Map? data) { if (data == null || data.isEmpty) return null; return MyLocation( street: data[Constants.dbFieldLocationStreet], country: data[Constants.dbFieldLocationCountry], administrativeArea: data[Constants.dbFieldLocationArea], locality: data[Constants.dbFieldLocationLocality], subLocality: data[Constants.dbFieldLocationSubLocality], postalCode: data[Constants.dbFieldLocationPostalCode], latitude: data[Constants.dbFieldLocationLatitude], longitude: data[Constants.dbFieldLocationLongitude], ); } void _swipeLeft() { _saveSwipeAction(userProfiles[_controller.currentIndex].id, 'dislike'); _controller.next( swipeDirection: SwipeDirection.left, duration: Durations.extralong4); } void _swipeRight() { _saveSwipeAction(userProfiles[_controller.currentIndex].id, 'like'); _controller.next( swipeDirection: SwipeDirection.right, duration: Durations.extralong4); } void _skip() { _controller.next( swipeDirection: SwipeDirection.up, duration: Durations.extralong2); } void _saveSwipeAction(String userId, String action) { /* FirebaseFirestore.instance.collection('swipes').add({ 'userId': userId, 'action': action, 'timestamp': FieldValue.serverTimestamp(), });*/ } @override Widget build(BuildContext context) { if (userProfiles.isEmpty) { return Scaffold( appBar: AppBar(title: const Text('User Profiles')), body: const Center(child: CircularProgressIndicator()), ); } return Scaffold( appBar: AppBar(title: const Text('User Profiles')), body: SafeArea( top: false, child: Stack( children: [ Positioned.fill( child: Padding( padding: const EdgeInsets.all(8), child: SwipableStack( detectableSwipeDirections: const { SwipeDirection.right, SwipeDirection.left, SwipeDirection.up, }, controller: _controller, stackClipBehaviour: Clip.none, onSwipeCompleted: (index, direction) { if (index >= userProfiles.length) { setState(() { _controller.currentIndex = 0; // again from the start }); } }, horizontalSwipeThreshold: 0.8, verticalSwipeThreshold: 0.8, builder: (context, properties) { final userProfile = userProfiles[properties.index % userProfiles.length]; return Container( alignment: Alignment.center, color: Colors.tealAccent, child: Stack( children: [ Card( child: Padding( padding: const EdgeInsets.all(16.0), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(userProfile.name, style: const TextStyle(fontSize: 24)), Text(userProfile.email, style: const TextStyle(fontSize: 24)), const SizedBox(height: 8), Text( 'Has skills and experience in: ${userProfile.skills.join(', ')}'), Text( 'Seeks someone with skills in: ${userProfile.skillsSought.join(', ')}'), Text('Risk type: ${userProfile.risk}'), Text( 'Speaks: ${userProfile.languages.map((lang) => lang.name).join(', ')}'), Text( 'Lives in: ${userProfile.locations['main']?.locality ?? 'N/A'}'), Text( 'Second home: ${userProfile.locations['secondary']?.locality ?? 'N/A'}'), ], ), ), ), ), if (properties.stackIndex == 0 && properties.direction != null) CardOverlay( swipeProgress: properties.swipeProgress, direction: properties.direction!, ), ], ), ); }, ), ), ), Positioned( bottom: 16, left: 0, right: 0, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ FloatingActionButton( tooltip: 'Undo last action', shape: const CircleBorder(), onPressed: () { _controller.rewind(duration: Durations.extralong4); }, child: const Icon(Icons.undo), ), SizedBox( width: 72, height: 72, child: FloatingActionButton( shape: const CircleBorder(), onPressed: _swipeLeft, child: const Icon(Icons.cancel, color: Colors.red, size: 64), ), ), SizedBox( width: 72, height: 72, child: FloatingActionButton( shape: const CircleBorder(), onPressed: _swipeRight, child: const Icon(Icons.check_circle, color: Colors.green, size: 64), ), ), FloatingActionButton( tooltip: 'Skip profile', shape: const CircleBorder(), onPressed: _skip, child: const Icon(Icons.skip_next), ), ], ), ), ], ), ), ); } } class CardOverlay extends StatelessWidget { final double swipeProgress; final SwipeDirection direction; const CardOverlay({ super.key, required this.swipeProgress, required this.direction, }); @override Widget build(BuildContext context) { return Positioned.fill( bottom: 300, child: Opacity( opacity: swipeProgress.abs().clamp(0.0, 1.0), child: Align( alignment: direction == SwipeDirection.right ? Alignment.centerLeft : (direction == SwipeDirection.left ? Alignment.centerRight : Alignment.center), child: Icon( direction == SwipeDirection.right ? Icons.check_circle : (direction == SwipeDirection.left ? Icons.cancel : Icons.skip_next), size: 100, color: direction == SwipeDirection.right ? Colors.green : (direction == SwipeDirection.left ? Colors.red : Colors.blue), ), ), ), ); } }