Save current app progress
This commit is contained in:
@@ -1,15 +1,155 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet, TouchableOpacity, Image, ScrollView } from 'react-native';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { View, Text, StyleSheet, TouchableOpacity, ScrollView, Modal } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { Settings, Music, Map as MapIcon, Heart, LogOut } from 'lucide-react-native';
|
||||
import { Settings, Music, Map as MapIcon, Heart, LogOut, X } from 'lucide-react-native';
|
||||
import { colors } from '../../utils/colors';
|
||||
import { supabase } from '../../services/supabase';
|
||||
import { useAuth } from '../../contexts/AuthContext';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
const GENRES = [
|
||||
'Pop', 'Rock', 'Hip-Hop', 'Rap', 'Indie', 'Eletrónica',
|
||||
'Fado', 'Funk', 'Jazz', 'Classical', 'Reggaeton', 'Outro'
|
||||
];
|
||||
|
||||
function parseDistanceToKm(distanceStr: string | null | undefined): number {
|
||||
if (!distanceStr) return 0;
|
||||
|
||||
// Clean all spaces (including non-breaking spaces)
|
||||
const cleanStr = distanceStr.replace(/\u00A0/g, '').replace(/\s+/g, '').toLowerCase();
|
||||
|
||||
// Extract number part
|
||||
const match = cleanStr.match(/[\d.,]+/);
|
||||
if (!match) return 0;
|
||||
|
||||
const numStr = match[0];
|
||||
let val = 0;
|
||||
|
||||
// Parse numeric value based on localized decimal and thousands separators
|
||||
if (numStr.includes('.') && numStr.includes(',')) {
|
||||
if (numStr.indexOf('.') < numStr.indexOf(',')) {
|
||||
// e.g. "1.234,5" -> dot thousands, comma decimal
|
||||
val = parseFloat(numStr.replace(/\./g, '').replace(/,/g, '.'));
|
||||
} else {
|
||||
// e.g. "1,234.5" -> comma thousands, dot decimal
|
||||
val = parseFloat(numStr.replace(/,/g, ''));
|
||||
}
|
||||
} else if (numStr.includes(',')) {
|
||||
const parts = numStr.split(',');
|
||||
if (parts[parts.length - 1].length === 3) {
|
||||
// Thousands separator: e.g. "1,234" -> 1234
|
||||
val = parseFloat(numStr.replace(/,/g, ''));
|
||||
} else {
|
||||
// Decimal separator: e.g. "12,5" -> 12.5
|
||||
val = parseFloat(numStr.replace(/,/g, '.'));
|
||||
}
|
||||
} else if (numStr.includes('.')) {
|
||||
const parts = numStr.split('.');
|
||||
if (parts[parts.length - 1].length === 3) {
|
||||
// Thousands separator: e.g. "1.234" -> 1234
|
||||
val = parseFloat(numStr.replace(/\./g, ''));
|
||||
} else {
|
||||
// Decimal separator: e.g. "1.2" -> 1.2
|
||||
val = parseFloat(numStr);
|
||||
}
|
||||
} else {
|
||||
val = parseFloat(numStr);
|
||||
}
|
||||
|
||||
if (isNaN(val)) return 0;
|
||||
|
||||
// Apply unit conversion if necessary
|
||||
if (cleanStr.endsWith('mi') || cleanStr.endsWith('miles') || cleanStr.endsWith('milhas')) {
|
||||
return val * 1.60934;
|
||||
} else if (cleanStr.endsWith('m') && !cleanStr.endsWith('km')) {
|
||||
return val / 1000;
|
||||
} else if (cleanStr.endsWith('ft') || cleanStr.endsWith('feet') || cleanStr.endsWith('pés')) {
|
||||
return val / 3280.84;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
export default function ProfileScreen({ navigation }) {
|
||||
const { user } = useAuth();
|
||||
|
||||
const [tripsCount, setTripsCount] = useState(0);
|
||||
const [totalDistance, setTotalDistance] = useState(0);
|
||||
const [favoriteGenre, setFavoriteGenre] = useState('Ainda não definido');
|
||||
const [selectedGenre, setSelectedGenre] = useState('Pop');
|
||||
const [isGenreModalVisible, setIsGenreModalVisible] = useState(false);
|
||||
|
||||
const loadProfileData = useCallback(async () => {
|
||||
try {
|
||||
// 1. Fetch favorite genre from AsyncStorage
|
||||
const storageKey = user ? `@favorite_genre_${user.id}` : '@favorite_genre_guest';
|
||||
const storedGenre = await AsyncStorage.getItem(storageKey);
|
||||
const activeGenre = storedGenre || 'Ainda não definido';
|
||||
setFavoriteGenre(activeGenre);
|
||||
if (storedGenre) {
|
||||
setSelectedGenre(storedGenre);
|
||||
}
|
||||
|
||||
// 2. Fetch user's trips from Supabase
|
||||
let query = supabase.from('trips').select('distance');
|
||||
if (user) {
|
||||
query = query.eq('user_id', user.id);
|
||||
} else {
|
||||
query = query.is('user_id', null);
|
||||
}
|
||||
|
||||
const { data, error } = await query;
|
||||
if (error) {
|
||||
console.error('Error fetching trips for profile stats:', error);
|
||||
} else {
|
||||
const count = data ? data.length : 0;
|
||||
let sumKm = 0;
|
||||
if (data) {
|
||||
data.forEach(trip => {
|
||||
if (trip.distance) {
|
||||
sumKm += parseDistanceToKm(trip.distance);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const roundedDistance = Math.round(sumKm);
|
||||
setTripsCount(count);
|
||||
setTotalDistance(roundedDistance);
|
||||
|
||||
// Print the safe logs exactly as requested:
|
||||
console.log('PROFILE_TRIPS_COUNT', count);
|
||||
console.log('PROFILE_TOTAL_DISTANCE_KM', roundedDistance);
|
||||
console.log('PROFILE_FAVORITE_GENRE', activeGenre);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error loading profile statistics:', err);
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
loadProfileData();
|
||||
}, [loadProfileData])
|
||||
);
|
||||
|
||||
const handleSaveGenre = async () => {
|
||||
try {
|
||||
const storageKey = user ? `@favorite_genre_${user.id}` : '@favorite_genre_guest';
|
||||
await AsyncStorage.setItem(storageKey, selectedGenre);
|
||||
setFavoriteGenre(selectedGenre);
|
||||
setIsGenreModalVisible(false);
|
||||
|
||||
// Print the updated safe logs:
|
||||
console.log('PROFILE_TRIPS_COUNT', tripsCount);
|
||||
console.log('PROFILE_TOTAL_DISTANCE_KM', totalDistance);
|
||||
console.log('PROFILE_FAVORITE_GENRE', selectedGenre);
|
||||
} catch (err) {
|
||||
console.error('Error saving favorite genre:', err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = async () => {
|
||||
const { error } = await supabase.auth.signOut();
|
||||
if (error) {
|
||||
@@ -52,16 +192,8 @@ export default function ProfileScreen({ navigation }) {
|
||||
{/* Stats Row */}
|
||||
<View style={styles.statsRow}>
|
||||
<View style={styles.statCol}>
|
||||
<Text style={styles.statNumber}>0</Text>
|
||||
<Text style={styles.statLabel}>VIAGENS</Text>
|
||||
</View>
|
||||
<View style={styles.statCol}>
|
||||
<Text style={styles.statNumber}>0</Text>
|
||||
<Text style={styles.statLabel}>SEGUIDORES</Text>
|
||||
</View>
|
||||
<View style={styles.statCol}>
|
||||
<Text style={styles.statNumber}>0</Text>
|
||||
<Text style={styles.statLabel}>A SEGUIR</Text>
|
||||
<Text style={styles.statNumber}>{tripsCount}</Text>
|
||||
<Text style={styles.statLabel}>VIAGENS REALIZADAS</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -80,20 +212,28 @@ export default function ProfileScreen({ navigation }) {
|
||||
</View>
|
||||
<View style={styles.prefTextContainer}>
|
||||
<Text style={styles.prefTitle}>Distância Total</Text>
|
||||
<Text style={styles.prefSubtitle}>0 km conduzidos</Text>
|
||||
<Text style={styles.prefSubtitle}>{totalDistance} km conduzidos</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Preference Item 2 */}
|
||||
<View style={[styles.prefItem, { marginBottom: 0, marginTop: 24 }]}>
|
||||
<TouchableOpacity
|
||||
style={[styles.prefItem, { marginBottom: 0, marginTop: 24 }]}
|
||||
onPress={() => {
|
||||
if (favoriteGenre !== 'Ainda não definido') {
|
||||
setSelectedGenre(favoriteGenre);
|
||||
}
|
||||
setIsGenreModalVisible(true);
|
||||
}}
|
||||
>
|
||||
<View style={[styles.prefIconContainer, { backgroundColor: '#E8F5E9' }]}>
|
||||
<Heart color={colors.spotify} size={20} />
|
||||
</View>
|
||||
<View style={styles.prefTextContainer}>
|
||||
<Text style={styles.prefTitle}>Género Favorito</Text>
|
||||
<Text style={styles.prefSubtitle}>Ainda não definido</Text>
|
||||
<Text style={styles.prefSubtitle}>{favoriteGenre}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
</View>
|
||||
</View>
|
||||
@@ -105,6 +245,64 @@ export default function ProfileScreen({ navigation }) {
|
||||
</TouchableOpacity>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
{/* Genre Picker Modal */}
|
||||
<Modal
|
||||
visible={isGenreModalVisible}
|
||||
animationType="slide"
|
||||
transparent={true}
|
||||
onRequestClose={() => setIsGenreModalVisible(false)}
|
||||
>
|
||||
<View style={styles.modalOverlay}>
|
||||
<View style={styles.modalContent}>
|
||||
<View style={styles.modalHeader}>
|
||||
<Text style={styles.modalTitle}>Género Musical Favorito</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.closeModalButton}
|
||||
onPress={() => setIsGenreModalVisible(false)}
|
||||
>
|
||||
<X color={colors.textMain} size={20} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<Text style={styles.modalSubtitle}>
|
||||
Escolhe o teu género musical favorito para personalizar as tuas bandas sonoras de viagem:
|
||||
</Text>
|
||||
|
||||
<View style={styles.genresContainer}>
|
||||
{GENRES.map((genre) => {
|
||||
const isSelected = selectedGenre === genre;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={genre}
|
||||
style={[
|
||||
styles.genreTag,
|
||||
isSelected && styles.genreTagSelected
|
||||
]}
|
||||
onPress={() => setSelectedGenre(genre)}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreTagText,
|
||||
isSelected && styles.genreTagTextSelected
|
||||
]}
|
||||
>
|
||||
{genre}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.saveGenreButton}
|
||||
onPress={handleSaveGenre}
|
||||
>
|
||||
<Text style={styles.saveGenreButtonText}>Guardar Preferência</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
@@ -192,22 +390,21 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
statsRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 10,
|
||||
marginBottom: 24,
|
||||
},
|
||||
statCol: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
},
|
||||
statNumber: {
|
||||
fontSize: 22,
|
||||
fontSize: 26,
|
||||
fontWeight: '800',
|
||||
color: colors.textMain,
|
||||
marginBottom: 4,
|
||||
},
|
||||
statLabel: {
|
||||
fontSize: 11,
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold',
|
||||
color: colors.textSecondary,
|
||||
letterSpacing: 1,
|
||||
@@ -279,4 +476,87 @@ const styles = StyleSheet.create({
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
modalOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
modalContent: {
|
||||
backgroundColor: colors.white,
|
||||
borderTopLeftRadius: 32,
|
||||
borderTopRightRadius: 32,
|
||||
padding: 24,
|
||||
paddingBottom: 40,
|
||||
maxHeight: '80%',
|
||||
},
|
||||
modalHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: colors.textMain,
|
||||
},
|
||||
closeModalButton: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 18,
|
||||
backgroundColor: colors.inputBackground,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
modalSubtitle: {
|
||||
fontSize: 14,
|
||||
color: colors.textSecondary,
|
||||
marginBottom: 20,
|
||||
lineHeight: 20,
|
||||
},
|
||||
genresContainer: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
marginBottom: 30,
|
||||
},
|
||||
genreTag: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 20,
|
||||
backgroundColor: colors.inputBackground,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.inputBorder,
|
||||
marginRight: 10,
|
||||
marginBottom: 10,
|
||||
},
|
||||
genreTagSelected: {
|
||||
backgroundColor: colors.primary,
|
||||
borderColor: colors.primary,
|
||||
},
|
||||
genreTagText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: colors.textMain,
|
||||
},
|
||||
genreTagTextSelected: {
|
||||
color: colors.white,
|
||||
},
|
||||
saveGenreButton: {
|
||||
backgroundColor: colors.primary,
|
||||
paddingVertical: 16,
|
||||
borderRadius: 16,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
shadowColor: colors.primary,
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.2,
|
||||
shadowRadius: 8,
|
||||
elevation: 4,
|
||||
},
|
||||
saveGenreButtonText: {
|
||||
color: colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user