From 77ffb5ffcfc01ff37a0372d1a5ff717469553214 Mon Sep 17 00:00:00 2001 From: Rafael <1024481@stud.hs-mannheim.de> Date: Fri, 16 Aug 2024 13:55:11 +0200 Subject: [PATCH] Added Settings Option to show only profiles from the same country. --- lib/constants.dart | 1 + lib/main.dart | 6 ++ lib/pages/settings_page.dart | 76 +++++++++++---- lib/pages/user_matching_page.dart | 54 +++++++--- lib/settings/settings_options.dart | 13 +++ lib/settings/settings_provider.dart | 30 ++++++ lib/utils/helper_locations.dart | 146 ++++++++++++++++++++++++++++ 7 files changed, 294 insertions(+), 32 deletions(-) create mode 100644 lib/settings/settings_options.dart create mode 100644 lib/settings/settings_provider.dart create mode 100644 lib/utils/helper_locations.dart diff --git a/lib/constants.dart b/lib/constants.dart index 14b8b79..5eaa0b5 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -69,4 +69,5 @@ class Constants { static const String pathLanguagesJson = 'lib/assets/languages.json'; static const String prefKeyThemeDarkMode = 'theme_dark_mode'; + static const String prefKeySameCountry = 'match_same_country'; } diff --git a/lib/main.dart b/lib/main.dart index 878e93e..a3e523c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,6 +12,7 @@ import 'pages/liked_users_page.dart'; import 'pages/user_matching_page.dart'; import 'pages/user_profile_page.dart'; import 'services/swipe_stream_service.dart'; +import 'settings/settings_provider.dart'; void main() async { // Firebase stuff @@ -23,6 +24,7 @@ void main() async { // Init SharedPreferences final prefs = await SharedPreferences.getInstance(); final isDarkMode = prefs.getBool(Constants.prefKeyThemeDarkMode) ?? false; + final onlySameCountry = prefs.getBool(Constants.prefKeySameCountry) ?? false; final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); @@ -70,6 +72,10 @@ void main() async { create: (context) => ThemeProvider(initialIsDarkMode: isDarkMode)), Provider.value( value: flutterLocalNotificationsPlugin), + ChangeNotifierProvider( + create: (context) => + SettingsProvider(showSameCountry: onlySameCountry), + ), ], child: const MyApp(), ), diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart index 22eb5c8..96ffd2e 100644 --- a/lib/pages/settings_page.dart +++ b/lib/pages/settings_page.dart @@ -3,6 +3,8 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import '../settings/settings_provider.dart'; + class SettingsPage extends StatelessWidget { const SettingsPage({super.key}); @@ -13,27 +15,61 @@ class SettingsPage extends StatelessWidget { appBar: AppBar( title: const Text("Settings"), ), - body: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.secondaryContainer, - borderRadius: BorderRadius.circular(12), - ), - margin: const EdgeInsets.all(25), - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + body: SingleChildScrollView( + child: Column( children: [ - // dark mode switch - const Text("Dark Mode"), - Consumer( - builder: (context, themeProvider, child) { - return CupertinoSwitch( - value: themeProvider.isDarkMode, - onChanged: (value) { - themeProvider.toggleTheme(); - }, - ); - }, + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.secondaryContainer, + borderRadius: BorderRadius.circular(12), + ), + margin: const EdgeInsets.all(16), + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // dark mode switch + const Text("Dark Mode"), + Consumer( + builder: (context, themeProvider, child) { + return CupertinoSwitch( + value: themeProvider.isDarkMode, + onChanged: (value) { + themeProvider.toggleTheme(); + }, + ); + }, + ), + ], + ), + ), + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.secondaryContainer, + borderRadius: BorderRadius.circular(12), + ), + margin: const EdgeInsets.all(16), + padding: const EdgeInsets.all(16), + child: Consumer( + builder: (context, settingsProvider, child) { + return Padding( + padding: const EdgeInsets.all(0.0), + child: Column( + children: settingsProvider.settingsOptions.map((option) { + return SwitchListTile( + title: Text(option.title), + subtitle: (option.subtitle.isNotEmpty + ? Text(option.subtitle) + : null), + value: option.getValue(), + onChanged: option.onChanged, + contentPadding: EdgeInsets.zero, + ); + }).toList(), + ), + ); + }, + ), ), ], ), diff --git a/lib/pages/user_matching_page.dart b/lib/pages/user_matching_page.dart index 453bc8d..6ef6446 100644 --- a/lib/pages/user_matching_page.dart +++ b/lib/pages/user_matching_page.dart @@ -2,6 +2,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:percent_indicator/circular_percent_indicator.dart'; +import 'package:provider/provider.dart'; import 'package:swipable_stack/swipable_stack.dart'; import '../components/card_overlay.dart'; @@ -14,7 +15,9 @@ import '../models/user_profile.dart'; import '../services/auth/auth_service.dart'; import '../services/swipe_stream_service.dart'; import '../services/user_service.dart'; +import '../settings/settings_provider.dart'; import '../utils/helper.dart'; +import '../utils/helper_locations.dart'; import '../utils/list_utils.dart'; import '../utils/math.dart'; import 'chat_page.dart'; @@ -37,6 +40,7 @@ class UserMatchingPageState extends State { List potentialUserProfiles = []; late final SwipableStackController _controller; + late final bool _showSameCountry; // get instance of firestore and auth final FirebaseFirestore _firestore = FirebaseFirestore.instance; @@ -48,6 +52,8 @@ class UserMatchingPageState extends State { void initState() { super.initState(); _controller = SwipableStackController()..addListener(_listenController); + _showSameCountry = + Provider.of(context, listen: false).showSameCountry; _fetchUserProfiles(); } @@ -91,6 +97,17 @@ class UserMatchingPageState extends State { .map((doc) => doc.id) .toSet(); + // Fetch the current user's location + QuerySnapshot locationSnapshot = await _firestore + .collection(Constants.dbCollectionUsers) + .doc(currentUserId) + .collection(Constants.dbCollectionLocations) + .get(); + List userLoc = []; + for (var doc in locationSnapshot.docs) { + userLoc.add(getCountryIsoCode(doc[Constants.dbFieldLocationCountry])); + } + for (var userDoc in usersSnapshot.docs) { UserProfile userProfile = await UserService.getUserProfileFromDocument(userDoc); @@ -99,12 +116,20 @@ class UserMatchingPageState extends State { allUsers.add(userProfile); // Exclude (1) the current user's profile, (2) the already liked profiles // (3) users that were disliked less than 24 hours ago - // and (4) not active profile + // and (4) not active profiles if (userDoc.id != currentUserId && !likedUserIds.contains(userDoc.id) && !dislikedUserIds.contains(userDoc.id) && (userProfile.active ?? false)) { showProfiles.add(userProfile); + // (5) apply additional settings + if (_showSameCountry && + userProfile.locations.values.firstWhereOrNull((x) => + x != null && + userLoc.contains(getCountryIsoCode(x.country))) == + null) { + showProfiles.remove(userProfile); + } } } // end for @@ -284,7 +309,7 @@ class UserMatchingPageState extends State { } return Scaffold( - appBar: AppBar(title: const Text('Find your Match')), + appBar: AppBar(title: const Text('Swipe and Find your Match')), body: SafeArea( top: false, child: Stack( @@ -534,9 +559,9 @@ class UserMatchingPageState extends State { left: 0, right: 0, child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.center, children: [ - FloatingActionButton( + /*FloatingActionButton( heroTag: 'myFABUndo', tooltip: 'Undo last action', shape: const CircleBorder(), @@ -544,12 +569,13 @@ class UserMatchingPageState extends State { _controller.rewind(duration: Durations.extralong4); }, child: const Icon(Icons.undo), - ), + ),*/ SizedBox( width: 72, height: 72, child: FloatingActionButton( heroTag: 'myFABSwipeLeft', + tooltip: 'Does not fit', shape: const CircleBorder(), onPressed: _swipeLeft, child: Stack( @@ -569,11 +595,22 @@ class UserMatchingPageState extends State { ), ), ), + const SizedBox(width: 12), + FloatingActionButton.extended( + //icon: const Icon(Icons.skip_next), + label: const Text('Skip'), + tooltip: 'Skip profile', + heroTag: 'myFABSkip', + shape: const CircleBorder(), + onPressed: _skip, + ), + const SizedBox(width: 12), SizedBox( width: 72, height: 72, child: FloatingActionButton( heroTag: 'myFABSwipeRight', + tooltip: "Interesting", shape: const CircleBorder(), onPressed: _swipeRight, child: Stack( @@ -593,13 +630,6 @@ class UserMatchingPageState extends State { ), ), ), - FloatingActionButton( - tooltip: 'Skip profile', - heroTag: 'myFABSkip', - shape: const CircleBorder(), - onPressed: _skip, - child: const Icon(Icons.skip_next), - ), ], ), ); diff --git a/lib/settings/settings_options.dart b/lib/settings/settings_options.dart new file mode 100644 index 0000000..1e6cafc --- /dev/null +++ b/lib/settings/settings_options.dart @@ -0,0 +1,13 @@ +class SettingsOption { + final String title; + final String subtitle; + final bool Function() getValue; + final void Function(bool) onChanged; + + SettingsOption({ + required this.title, + this.subtitle = '', + required this.getValue, + required this.onChanged, + }); +} diff --git a/lib/settings/settings_provider.dart b/lib/settings/settings_provider.dart new file mode 100644 index 0000000..719da77 --- /dev/null +++ b/lib/settings/settings_provider.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../constants.dart'; +import 'settings_options.dart'; + +class SettingsProvider extends ChangeNotifier { + bool showSameCountry; + + SettingsProvider({ + this.showSameCountry = false, // default value + }); + + void toggleShowSameCountries() async { + showSameCountry = !showSameCountry; + notifyListeners(); + + SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setBool(Constants.prefKeySameCountry, showSameCountry); + } + + List get settingsOptions => [ + SettingsOption( + title: "Match within my country", + subtitle: 'Show profiles from your country only', + getValue: () => showSameCountry, + onChanged: (value) => toggleShowSameCountries(), + ), + ]; +} diff --git a/lib/utils/helper_locations.dart b/lib/utils/helper_locations.dart new file mode 100644 index 0000000..e978865 --- /dev/null +++ b/lib/utils/helper_locations.dart @@ -0,0 +1,146 @@ +Map countryToIsoCode = { + // Europe + 'Germany': 'DE', + 'Deutschland': 'DE', + 'Österreich': 'AT', + 'Austria': 'AT', + 'Belgien': 'BE', + 'Belgium': 'BE', + 'Belgique': 'BE', + 'België': 'BE', + 'Bulgarien': 'BG', + 'Bulgaria': 'BG', + 'България': 'BG', + 'Kroatien': 'HR', + 'Croatia': 'HR', + 'Hrvatska': 'HR', + 'Zypern': 'CY', + 'Cyprus': 'CY', + 'Κύπρος': 'CY', + 'Tschechien': 'CZ', + 'Czech Republic': 'CZ', + 'Česká republika': 'CZ', + 'Dänemark': 'DK', + 'Denmark': 'DK', + 'Danmark': 'DK', + 'Estland': 'EE', + 'Estonia': 'EE', + 'Eesti': 'EE', + 'Finnland': 'FI', + 'Finland': 'FI', + 'Suomi': 'FI', + 'Frankreich': 'FR', + 'France': 'FR', + 'Griechenland': 'GR', + 'Greece': 'GR', + 'Ελλάδα': 'GR', + 'Ungarn': 'HU', + 'Hungary': 'HU', + 'Magyarország': 'HU', + 'Island': 'IS', + 'Iceland': 'IS', + 'Ísland': 'IS', + 'Irland': 'IE', + 'Ireland': 'IE', + 'Éire': 'IE', + 'Italien': 'IT', + 'Italy': 'IT', + 'Italia': 'IT', + 'Lettland': 'LV', + 'Latvia': 'LV', + 'Latvija': 'LV', + 'Litauen': 'LT', + 'Lithuania': 'LT', + 'Lietuva': 'LT', + 'Luxemburg': 'LU', + 'Luxembourg': 'LU', + 'Malta': 'MT', + 'Netherlands': 'NL', + 'Niederlande': 'NL', + 'Nederland': 'NL', + 'Polen': 'PL', + 'Poland': 'PL', + 'Polska': 'PL', + 'Portugal': 'PT', + 'Rumänien': 'RO', + 'Romania': 'RO', + 'România': 'RO', + 'Slowakei': 'SK', + 'Slovakia': 'SK', + 'Slovensko': 'SK', + 'Slowenien': 'SI', + 'Slovenia': 'SI', + 'Slovenija': 'SI', + 'Spanien': 'ES', + 'Spain': 'ES', + 'España': 'ES', + 'Schweden': 'SE', + 'Sweden': 'SE', + 'Sverige': 'SE', + 'Schweiz': 'CH', + 'Switzerland': 'CH', + 'Suisse': 'CH', + 'Svizzera': 'CH', + 'Беларусь': 'BY', + 'Belarus': 'BY', + 'Weißrussland': 'BY', + 'Bosnien und Herzegowina': 'BA', + 'Bosnia and Herzegovina': 'BA', + 'Bosna i Hercegovina': 'BA', + 'Nordmazedonien': 'MK', + 'North Macedonia': 'MK', + 'Северна Македонија': 'MK', + 'Serbien': 'RS', + 'Serbia': 'RS', + 'Србија': 'RS', + 'Montenegro': 'ME', + 'Albanien': 'AL', + 'Albania': 'AL', + 'Shqipëri': 'AL', + 'Türkei': 'TR', + 'Turkey': 'TR', + 'Türkiye': 'TR', + 'Russland': 'RU', + 'Russia': 'RU', + 'Россия': 'RU', + 'Ukraine': 'UA', + 'Україна': 'UA', + + // Asia + 'China': 'CN', + '中国': 'CN', + 'Zhōngguó': 'CN', + 'Japan': 'JP', + '日本': 'JP', + 'Nihon': 'JP', + 'Südkorea': 'KR', + 'South Korea': 'KR', + '대한민국': 'KR', + 'Daehanminguk': 'KR', + 'Indien': 'IN', + 'India': 'IN', + 'भारत': 'IN', + 'Bhārat': 'IN', + + // North America + 'Kanada': 'CA', + 'Canada': 'CA', + 'Mexiko': 'MX', + 'Mexico': 'MX', + 'Estados Unidos Mexicanos': 'MX', + 'Vereinigte Staaten': 'US', + 'United States': 'US', + 'USA': 'US', +}; + +String getCountryIsoCode(String country) { + return countryToIsoCode[country] ?? country; +} + +bool compareCountries(String country1, String country2) { + if (getCountryIsoCode(country1) == getCountryIsoCode(country2)) { + return true; + } else { + return false; + } +}