|
@@ -24,6 +24,7 @@ import { RadioButton } from 'react-native-paper';
|
|
|
import { storage, StoreType } from 'src/storage';
|
|
|
import moment from 'moment';
|
|
|
import {
|
|
|
+ usePostGetListOfCountriesForLocationDataQuery,
|
|
|
usePostGetSettingsQuery,
|
|
|
usePostSetSettingsMutation,
|
|
|
usePostUpdateLocationMutation
|
|
@@ -38,6 +39,11 @@ import {
|
|
|
startBackgroundLocationUpdates,
|
|
|
stopBackgroundLocationUpdates
|
|
|
} from 'src/utils/backgroundLocation';
|
|
|
+import MultiSelectorCountries from 'src/components/MultiSelectorCountries';
|
|
|
+import ChevronIcon from 'assets/icons/travels-screens/down-arrow.svg';
|
|
|
+import { getFontSize } from 'src/utils';
|
|
|
+import InfoIcon from 'assets/icons/info-solid.svg';
|
|
|
+import Tooltip from 'react-native-walkthrough-tooltip';
|
|
|
|
|
|
const FilterModal = ({
|
|
|
isFilterVisible,
|
|
@@ -49,12 +55,15 @@ const FilterModal = ({
|
|
|
userId,
|
|
|
setRegionsFilter,
|
|
|
setSeriesFilter,
|
|
|
+ setNomadsFilter,
|
|
|
isPublicView,
|
|
|
isLogged = true,
|
|
|
showNomads,
|
|
|
setShowNomads,
|
|
|
usersOnMapCount,
|
|
|
- isConnected = true
|
|
|
+ friendsOnTheMapCount,
|
|
|
+ isConnected = true,
|
|
|
+ isPremium
|
|
|
}: {
|
|
|
isFilterVisible: string | null;
|
|
|
setIsFilterVisible: (filterType: string | null) => void;
|
|
@@ -65,18 +74,25 @@ const FilterModal = ({
|
|
|
userId: number;
|
|
|
setRegionsFilter: (data: { visitedLabel: 'in' | 'by'; year: number }) => void;
|
|
|
setSeriesFilter?: (filter: any) => void;
|
|
|
+ setNomadsFilter?: (filter: any) => void;
|
|
|
isPublicView: boolean;
|
|
|
isLogged: boolean;
|
|
|
showNomads?: boolean;
|
|
|
setShowNomads?: (showNomads: boolean) => void;
|
|
|
usersOnMapCount?: number | null;
|
|
|
+ friendsOnTheMapCount?: number | null;
|
|
|
isConnected?: boolean;
|
|
|
+ isPremium?: boolean;
|
|
|
}) => {
|
|
|
const token = storage.get('token', StoreType.STRING) as string;
|
|
|
const { data: locationSettings } = usePostGetSettingsQuery(
|
|
|
token,
|
|
|
isLogged && !isPublicView && isConnected
|
|
|
);
|
|
|
+ const { data: countriesData } = usePostGetListOfCountriesForLocationDataQuery(
|
|
|
+ token,
|
|
|
+ isLogged && !isPublicView && isConnected
|
|
|
+ );
|
|
|
const { mutateAsync: setSettings } = usePostSetSettingsMutation();
|
|
|
const { mutateAsync: updateLocation } = usePostUpdateLocationMutation();
|
|
|
const [selectedYear, setSelectedYear] = useState<{ label: string; value: number } | null>(null);
|
|
@@ -96,6 +112,9 @@ const FilterModal = ({
|
|
|
const [selectedSeries, setSelectedSeries] = useState<number[]>([]);
|
|
|
const [seriesVisible, setSeriesVisible] = useState(true);
|
|
|
const [selectedSeriesFilter, setSelectedSeriesFilter] = useState(-1);
|
|
|
+ const [selectedCountries, setSelectedCountries] = useState<any[]>([]);
|
|
|
+ const [friendsVisible, setFriendsVisible] = useState(false);
|
|
|
+ const [trustedVisible, setTrustedVisible] = useState(false);
|
|
|
const savedFilterSettings = !isPublicView
|
|
|
? (storage.get('filterSettings', StoreType.STRING) as string)
|
|
|
: null;
|
|
@@ -105,6 +124,12 @@ const FilterModal = ({
|
|
|
const [openSettingsVisible, setOpenSettingsVisible] = useState<boolean>(false);
|
|
|
const [openSettingsBackgroundVisible, setOpenSettingsBackgroundVisible] =
|
|
|
useState<boolean>(false);
|
|
|
+ const [countrySelectorVisible, setCountrySelectorVisible] = useState(false);
|
|
|
+ const [countriesForSelector, setCountriesForSelector] = useState<
|
|
|
+ { country: number; name: string; count: number; flag: string | null }[]
|
|
|
+ >([]);
|
|
|
+ const [premiumModalVisible, setPremiumModalVisible] = useState(false);
|
|
|
+ const [toolTipVisible, setToolTipVisible] = useState<boolean>(false);
|
|
|
|
|
|
useEffect(() => {
|
|
|
const syncSettings = async () => {
|
|
@@ -119,6 +144,12 @@ const FilterModal = ({
|
|
|
syncSettings();
|
|
|
}, [locationSettings]);
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
+ if (countriesData && countriesData.countries) {
|
|
|
+ setCountriesForSelector(countriesData.countries.filter((country: any) => country.count > 0));
|
|
|
+ }
|
|
|
+ }, [countriesData]);
|
|
|
+
|
|
|
useEffect(() => {
|
|
|
const loadFilterSettings = () => {
|
|
|
try {
|
|
@@ -130,6 +161,9 @@ const FilterModal = ({
|
|
|
setSelectedSeries(filterSettings.seriesFilter.groups);
|
|
|
setSeriesVisible(filterSettings.seriesFilter.visible);
|
|
|
setSelectedSeriesFilter(filterSettings.seriesFilter.status);
|
|
|
+ setSelectedCountries(filterSettings.nomadsFilter.countries || []);
|
|
|
+ setFriendsVisible(filterSettings.nomadsFilter.friends === 1);
|
|
|
+ setTrustedVisible(filterSettings.nomadsFilter.trusted === 1);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('Failed to load filter settings', error);
|
|
@@ -141,6 +175,17 @@ const FilterModal = ({
|
|
|
}
|
|
|
}, [savedFilterSettings, isFilterVisible]);
|
|
|
|
|
|
+ const handleCountrySelect = (
|
|
|
+ selectedItems: { country: number; name: string; count: number; flag: string | null }[]
|
|
|
+ ) => {
|
|
|
+ if (selectedItems.length > 0) {
|
|
|
+ setShowNomads && setShowNomads(false);
|
|
|
+ storage.set('showNomads', false);
|
|
|
+ }
|
|
|
+ setSelectedCountries(selectedItems);
|
|
|
+ setCountrySelectorVisible(false);
|
|
|
+ };
|
|
|
+
|
|
|
const saveFilterSettings = async () => {
|
|
|
if (isLogged && !isPublicView) {
|
|
|
try {
|
|
@@ -161,7 +206,13 @@ const FilterModal = ({
|
|
|
groups: selectedSeries,
|
|
|
applied: true,
|
|
|
status: selectedSeriesFilter
|
|
|
- }
|
|
|
+ },
|
|
|
+ nomadsFilter: {
|
|
|
+ friends: friendsVisible ? 1 : 0,
|
|
|
+ trusted: trustedVisible ? 1 : 0,
|
|
|
+ countries: selectedCountries
|
|
|
+ },
|
|
|
+ showNomads: showNomads
|
|
|
};
|
|
|
storage.set('filterSettings', JSON.stringify(filterSettings));
|
|
|
} catch (error) {
|
|
@@ -207,6 +258,18 @@ const FilterModal = ({
|
|
|
}
|
|
|
}, [selectedSeries, seriesVisible, selectedSeriesFilter]);
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
+ if (isFilterVisible && isFilterVisible === 'nomads') {
|
|
|
+ setNomadsFilter &&
|
|
|
+ setNomadsFilter({
|
|
|
+ friends: friendsVisible ? 1 : 0,
|
|
|
+ trusted: trustedVisible ? 1 : 0,
|
|
|
+ countries: selectedCountries
|
|
|
+ });
|
|
|
+ saveFilterSettings();
|
|
|
+ }
|
|
|
+ }, [friendsVisible, trustedVisible, selectedCountries]);
|
|
|
+
|
|
|
useEffect(() => {
|
|
|
if (isFilterVisible && isFilterVisible === 'regions') {
|
|
|
try {
|
|
@@ -430,9 +493,32 @@ const FilterModal = ({
|
|
|
setSettings({ token, sharing: 0 });
|
|
|
setShowNomads && setShowNomads(false);
|
|
|
storage.set('showNomads', false);
|
|
|
+ setTrustedVisible(false);
|
|
|
+ setFriendsVisible(false);
|
|
|
+ setSelectedCountries([]);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ const toggleTrustedSwitch = async () => {
|
|
|
+ if (!isPremium) {
|
|
|
+ setPremiumModalVisible(true);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ setShowNomads && setShowNomads(false);
|
|
|
+ storage.set('showNomads', false);
|
|
|
+ setTrustedVisible(!trustedVisible);
|
|
|
+ };
|
|
|
+
|
|
|
+ const toggleFriendsSwitch = async () => {
|
|
|
+ if (!isPremium) {
|
|
|
+ setPremiumModalVisible(true);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ setShowNomads && setShowNomads(false);
|
|
|
+ storage.set('showNomads', false);
|
|
|
+ setFriendsVisible(!friendsVisible);
|
|
|
+ };
|
|
|
+
|
|
|
const requestBackgroundPermissionSafe = async () => {
|
|
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
|
return await Location.requestBackgroundPermissionsAsync();
|
|
@@ -510,6 +596,11 @@ const FilterModal = ({
|
|
|
};
|
|
|
|
|
|
const toggleNomadsSwitch = () => {
|
|
|
+ if (!showNomads) {
|
|
|
+ setSelectedCountries([]);
|
|
|
+ setTrustedVisible(false);
|
|
|
+ setFriendsVisible(false);
|
|
|
+ }
|
|
|
setShowNomads && setShowNomads(!showNomads);
|
|
|
storage.set('showNomads', !showNomads);
|
|
|
};
|
|
@@ -519,7 +610,92 @@ const FilterModal = ({
|
|
|
<View style={[styles.sceneContainer, { flex: 0 }]}>
|
|
|
<View style={styles.textContainer}>
|
|
|
<Text style={styles.text}>The location is shared with 1 km radius precision.</Text>
|
|
|
+ <Tooltip
|
|
|
+ isVisible={toolTipVisible}
|
|
|
+ content={
|
|
|
+ <View style={{ gap: 6 }}>
|
|
|
+ <Text style={[styles.text, styles.boldText]}>
|
|
|
+ At NomadMania, we respect your privacy.
|
|
|
+ </Text>
|
|
|
+ <Text style={[styles.text]}>
|
|
|
+ If you choose to share your location, it will be used to show your current
|
|
|
+ location to other users who also share theirs.
|
|
|
+ </Text>
|
|
|
+ <Text style={[styles.text]}>
|
|
|
+ You can choose how you want to share your location:
|
|
|
+ </Text>
|
|
|
+ <View style={[styles.bulletItem]}>
|
|
|
+ <Text style={styles.bulletIcon}>{'\u2022'}</Text>
|
|
|
+ <Text style={[styles.text, { flex: 1 }]}>
|
|
|
+ <Text style={styles.boldText}>Only when the app is open</Text> – Your location
|
|
|
+ updates only when you open the app. This uses less battery but may be less
|
|
|
+ accurate.
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ <View style={styles.bulletItem}>
|
|
|
+ <Text style={styles.bulletIcon}>{'\u2022'}</Text>
|
|
|
+ <Text style={[styles.text, { flex: 1 }]}>
|
|
|
+ <Text style={styles.boldText}>In the background</Text> – Your location stays up
|
|
|
+ to date even when the app is closed. Other users see your latest location.
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ <Text style={[styles.text]}>
|
|
|
+ You’re always in control, and you can change these settings anytime in the app{' '}
|
|
|
+ <Text
|
|
|
+ style={{ color: Colors.ORANGE }}
|
|
|
+ onPress={() =>
|
|
|
+ Platform.OS === 'ios'
|
|
|
+ ? Linking.openURL('app-settings:')
|
|
|
+ : Linking.openSettings()
|
|
|
+ }
|
|
|
+ >
|
|
|
+ Settings
|
|
|
+ </Text>{' '}
|
|
|
+ section.
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ }
|
|
|
+ contentStyle={{ backgroundColor: Colors.WHITE }}
|
|
|
+ placement="top"
|
|
|
+ onClose={() => setToolTipVisible(false)}
|
|
|
+ backgroundColor="transparent"
|
|
|
+ allowChildInteraction={false}
|
|
|
+ >
|
|
|
+ <TouchableOpacity
|
|
|
+ style={{ paddingHorizontal: 5 }}
|
|
|
+ onPress={() => setToolTipVisible(true)}
|
|
|
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
|
+ >
|
|
|
+ <InfoIcon fill={Colors.DARK_BLUE} width={20} height={20} />
|
|
|
+ </TouchableOpacity>
|
|
|
+ </Tooltip>
|
|
|
</View>
|
|
|
+
|
|
|
+ <TouchableOpacity
|
|
|
+ style={[
|
|
|
+ styles.alignStyle,
|
|
|
+ styles.buttonWrapper,
|
|
|
+ {
|
|
|
+ justifyContent: 'space-between'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ onPress={toggleSettingsSwitch}
|
|
|
+ >
|
|
|
+ <View style={styles.alignStyle}>
|
|
|
+ <SharingIcon fill={Colors.DARK_BLUE} width={20} height={20} />
|
|
|
+ <Text style={styles.buttonLabel}>Share my location to see others</Text>
|
|
|
+ </View>
|
|
|
+ <View>
|
|
|
+ <Switch
|
|
|
+ trackColor={{ false: Colors.LIGHT_GRAY, true: Colors.DARK_BLUE }}
|
|
|
+ thumbColor={Colors.WHITE}
|
|
|
+ onValueChange={toggleSettingsSwitch}
|
|
|
+ value={isSharing}
|
|
|
+ style={{ transform: 'scale(0.8)' }}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+ </TouchableOpacity>
|
|
|
+
|
|
|
<TouchableOpacity
|
|
|
style={[
|
|
|
styles.alignStyle,
|
|
@@ -538,7 +714,7 @@ const FilterModal = ({
|
|
|
height={20}
|
|
|
/>
|
|
|
<Text style={[styles.buttonLabel, !isSharing ? { color: Colors.LIGHT_GRAY } : {}]}>
|
|
|
- Show Nomads - {usersOnMapCount ? formatNumber(usersOnMapCount) : null} online
|
|
|
+ Show all Nomads - {usersOnMapCount ? formatNumber(usersOnMapCount) : null} online
|
|
|
</Text>
|
|
|
</View>
|
|
|
<View>
|
|
@@ -553,7 +729,22 @@ const FilterModal = ({
|
|
|
</View>
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
- <TouchableOpacity
|
|
|
+ <View style={{ flexDirection: 'row', alignItems: 'center', marginVertical: 12 }}>
|
|
|
+ <View style={styles.divider} />
|
|
|
+ <Text
|
|
|
+ style={{
|
|
|
+ color: Colors.DARK_BLUE,
|
|
|
+ fontSize: getFontSize(12),
|
|
|
+ fontWeight: '500',
|
|
|
+ paddingHorizontal: 4
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ Premium Features
|
|
|
+ </Text>
|
|
|
+ <View style={styles.divider} />
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* <TouchableOpacity
|
|
|
style={[
|
|
|
styles.alignStyle,
|
|
|
styles.buttonWrapper,
|
|
@@ -561,58 +752,131 @@ const FilterModal = ({
|
|
|
justifyContent: 'space-between'
|
|
|
}
|
|
|
]}
|
|
|
- onPress={toggleSettingsSwitch}
|
|
|
+ onPress={toggleTrustedSwitch}
|
|
|
+ disabled={!isSharing}
|
|
|
>
|
|
|
<View style={styles.alignStyle}>
|
|
|
- <SharingIcon fill={Colors.DARK_BLUE} width={20} height={20} />
|
|
|
- <Text style={styles.buttonLabel}>Share location to see others</Text>
|
|
|
+ <UsersIcon
|
|
|
+ fill={isSharing ? Colors.DARK_BLUE : Colors.LIGHT_GRAY}
|
|
|
+ width={20}
|
|
|
+ height={20}
|
|
|
+ />
|
|
|
+ <Text style={[styles.buttonLabel, !isSharing ? { color: Colors.LIGHT_GRAY } : {}]}>
|
|
|
+ Show trusted Nomads
|
|
|
+ </Text>
|
|
|
</View>
|
|
|
<View>
|
|
|
<Switch
|
|
|
trackColor={{ false: Colors.LIGHT_GRAY, true: Colors.DARK_BLUE }}
|
|
|
thumbColor={Colors.WHITE}
|
|
|
- onValueChange={toggleSettingsSwitch}
|
|
|
- value={isSharing}
|
|
|
+ onValueChange={toggleTrustedSwitch}
|
|
|
+ value={trustedVisible}
|
|
|
style={{ transform: 'scale(0.8)' }}
|
|
|
+ disabled={!isSharing}
|
|
|
/>
|
|
|
</View>
|
|
|
- </TouchableOpacity>
|
|
|
- <View style={{ marginVertical: 12, gap: 6 }}>
|
|
|
- <Text style={[styles.text, styles.boldText]}>
|
|
|
- At NomadMania, we respect your privacy.
|
|
|
- </Text>
|
|
|
- <Text style={[styles.text]}>
|
|
|
- If you choose to share your location, it will be used to show your current location to
|
|
|
- other users who also share theirs.
|
|
|
- </Text>
|
|
|
- <Text style={[styles.text]}>You can choose how you want to share your location:</Text>
|
|
|
- <View style={[styles.bulletItem]}>
|
|
|
- <Text style={styles.bulletIcon}>{'\u2022'}</Text>
|
|
|
- <Text style={[styles.text, { flex: 1 }]}>
|
|
|
- <Text style={styles.boldText}>Only when the app is open</Text> – Your location updates
|
|
|
- only when you open the app. This uses less battery but may be less accurate.
|
|
|
+ </TouchableOpacity> */}
|
|
|
+
|
|
|
+ <TouchableOpacity
|
|
|
+ style={[
|
|
|
+ styles.alignStyle,
|
|
|
+ styles.buttonWrapper,
|
|
|
+ {
|
|
|
+ justifyContent: 'space-between'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ onPress={toggleFriendsSwitch}
|
|
|
+ disabled={!isSharing}
|
|
|
+ >
|
|
|
+ <View style={styles.alignStyle}>
|
|
|
+ <UsersIcon
|
|
|
+ fill={isSharing ? Colors.DARK_BLUE : Colors.LIGHT_GRAY}
|
|
|
+ width={20}
|
|
|
+ height={20}
|
|
|
+ />
|
|
|
+ <Text style={[styles.buttonLabel, !isSharing ? { color: Colors.LIGHT_GRAY } : {}]}>
|
|
|
+ Show my friends - {friendsOnTheMapCount ? formatNumber(friendsOnTheMapCount) : null}{' '}
|
|
|
+ online
|
|
|
</Text>
|
|
|
</View>
|
|
|
- <View style={styles.bulletItem}>
|
|
|
- <Text style={styles.bulletIcon}>{'\u2022'}</Text>
|
|
|
- <Text style={[styles.text, { flex: 1 }]}>
|
|
|
- <Text style={styles.boldText}>In the background</Text> – Your location stays up to
|
|
|
- date even when the app is closed. Other users see your latest location.
|
|
|
- </Text>
|
|
|
+ <View>
|
|
|
+ <Switch
|
|
|
+ trackColor={{ false: Colors.LIGHT_GRAY, true: Colors.DARK_BLUE }}
|
|
|
+ thumbColor={Colors.WHITE}
|
|
|
+ onValueChange={toggleFriendsSwitch}
|
|
|
+ value={friendsVisible}
|
|
|
+ style={{ transform: 'scale(0.8)' }}
|
|
|
+ disabled={!isSharing}
|
|
|
+ />
|
|
|
</View>
|
|
|
- <Text style={[styles.text]}>
|
|
|
- You’re always in control, and you can change these settings anytime in the app{' '}
|
|
|
- <Text
|
|
|
- style={{ color: Colors.ORANGE }}
|
|
|
- onPress={() =>
|
|
|
- Platform.OS === 'ios' ? Linking.openURL('app-settings:') : Linking.openSettings()
|
|
|
- }
|
|
|
- >
|
|
|
- Settings
|
|
|
- </Text>{' '}
|
|
|
- section.
|
|
|
- </Text>
|
|
|
- </View>
|
|
|
+ </TouchableOpacity>
|
|
|
+
|
|
|
+ <TouchableOpacity
|
|
|
+ style={styles.megaSelector}
|
|
|
+ onPress={() => {
|
|
|
+ if (!isPremium) {
|
|
|
+ setPremiumModalVisible(true);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ setCountrySelectorVisible(true);
|
|
|
+ }}
|
|
|
+ disabled={!isSharing}
|
|
|
+ >
|
|
|
+ <Text style={styles.megaButtonText}>Show Nomads from</Text>
|
|
|
+ <ChevronIcon width={18} height={18} />
|
|
|
+ </TouchableOpacity>
|
|
|
+
|
|
|
+ {selectedCountries.length > 0 && (
|
|
|
+ <View
|
|
|
+ style={{
|
|
|
+ flexDirection: 'row',
|
|
|
+ alignItems: 'center',
|
|
|
+ flexWrap: 'wrap',
|
|
|
+ marginLeft: 12,
|
|
|
+ gap: 6,
|
|
|
+ marginVertical: 8
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {selectedCountries.map((item) => (
|
|
|
+ <View
|
|
|
+ key={item.country}
|
|
|
+ style={{
|
|
|
+ marginLeft: -12
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Image
|
|
|
+ source={{ uri: API_HOST + item.flag }}
|
|
|
+ style={{
|
|
|
+ width: 28,
|
|
|
+ height: 28,
|
|
|
+ borderRadius: 14,
|
|
|
+ borderWidth: 0.5,
|
|
|
+ borderColor: Colors.BORDER_LIGHT
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+ ))}
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <MultiSelectorCountries
|
|
|
+ isVisible={countrySelectorVisible}
|
|
|
+ onClose={() => setCountrySelectorVisible(false)}
|
|
|
+ onSelect={handleCountrySelect}
|
|
|
+ data={countriesForSelector ?? []}
|
|
|
+ initialSelectedIds={selectedCountries.map((item) => parseInt(item.country))}
|
|
|
+ />
|
|
|
+
|
|
|
+ <WarningModal
|
|
|
+ type={'success'}
|
|
|
+ isVisible={premiumModalVisible}
|
|
|
+ message={
|
|
|
+ 'This feature is available to Premium users. Premium account settings can be managed on our website.'
|
|
|
+ }
|
|
|
+ action={() => {}}
|
|
|
+ onClose={() => setPremiumModalVisible(false)}
|
|
|
+ title={'Premium Feature'}
|
|
|
+ />
|
|
|
|
|
|
<WarningModal
|
|
|
type={'success'}
|