From 134789cee1430e3894b545cf6100a5be6051c31b Mon Sep 17 00:00:00 2001 From: 240424 <240424@epvc.pt> Date: Thu, 28 May 2026 15:38:56 +0100 Subject: [PATCH] Save current app progress --- src/screens/main/ProfileScreen.tsx | 322 +++++++++++++++++++++++++++-- 1 file changed, 301 insertions(+), 21 deletions(-) diff --git a/src/screens/main/ProfileScreen.tsx b/src/screens/main/ProfileScreen.tsx index 46918d5..a86d2cb 100644 --- a/src/screens/main/ProfileScreen.tsx +++ b/src/screens/main/ProfileScreen.tsx @@ -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 */} - 0 - VIAGENS - - - 0 - SEGUIDORES - - - 0 - A SEGUIR + {tripsCount} + VIAGENS REALIZADAS @@ -80,20 +212,28 @@ export default function ProfileScreen({ navigation }) { Distância Total - 0 km conduzidos + {totalDistance} km conduzidos {/* Preference Item 2 */} - + { + if (favoriteGenre !== 'Ainda não definido') { + setSelectedGenre(favoriteGenre); + } + setIsGenreModalVisible(true); + }} + > Género Favorito - Ainda não definido + {favoriteGenre} - + @@ -105,6 +245,64 @@ export default function ProfileScreen({ navigation }) { + + {/* Genre Picker Modal */} + setIsGenreModalVisible(false)} + > + + + + Género Musical Favorito + setIsGenreModalVisible(false)} + > + + + + + + Escolhe o teu género musical favorito para personalizar as tuas bandas sonoras de viagem: + + + + {GENRES.map((genre) => { + const isSelected = selectedGenre === genre; + return ( + setSelectedGenre(genre)} + > + + {genre} + + + ); + })} + + + + Guardar Preferência + + + + ); } @@ -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', + }, }); +