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',
+ },
});
+