Save current app progress

This commit is contained in:
2026-05-28 22:55:43 +01:00
parent 134789cee1
commit d85e327c07
5 changed files with 481 additions and 414 deletions

28
scratch_db_test.js Normal file
View File

@@ -0,0 +1,28 @@
const supabaseUrl = "https://qyvnryhskgmvgjajqqru.supabase.co";
const supabaseKey = "sb_publishable_fazCCLmO7XjtryY28ePR-A_CS7aU6fF";
async function run() {
try {
const res = await fetch(`${supabaseUrl}/rest/v1/trips`, {
method: "POST",
headers: {
apikey: supabaseKey,
Authorization: `Bearer ${supabaseKey}`,
"Content-Type": "application/json",
Prefer: "return=representation"
},
body: JSON.stringify({
title: "Test Trip",
origin: "Lisbon",
destination: "Porto",
waypoints: []
})
});
const data = await res.json();
console.log("INSERT_RESPONSE:", JSON.stringify(data, null, 2));
} catch (err) {
console.error("Error inserting:", err);
}
}
run();

View File

@@ -6,9 +6,7 @@ interface AuthContextType {
user: User | null; user: User | null;
session: Session | null; session: Session | null;
loading: boolean; loading: boolean;
isDemoMode: boolean;
isSpotifyAuthenticated: boolean; isSpotifyAuthenticated: boolean;
enableDemoMode: () => void;
enableSpotifyMode: () => void; enableSpotifyMode: () => void;
} }
@@ -16,9 +14,7 @@ const AuthContext = createContext<AuthContextType>({
user: null, user: null,
session: null, session: null,
loading: true, loading: true,
isDemoMode: false,
isSpotifyAuthenticated: false, isSpotifyAuthenticated: false,
enableDemoMode: () => {},
enableSpotifyMode: () => {}, enableSpotifyMode: () => {},
}); });
@@ -28,18 +24,9 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
const [user, setUser] = useState<User | null>(null); const [user, setUser] = useState<User | null>(null);
const [session, setSession] = useState<Session | null>(null); const [session, setSession] = useState<Session | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [isDemoMode, setIsDemoMode] = useState(false);
const [isSpotifyAuthenticated, setIsSpotifyAuthenticated] = useState(false); const [isSpotifyAuthenticated, setIsSpotifyAuthenticated] = useState(false);
const enableDemoMode = () => {
setIsDemoMode(true);
setIsSpotifyAuthenticated(false);
setUser({ id: '00000000-0000-4000-8000-000000000002', email: 'demo@roadtripdj.com' } as User);
setLoading(false);
};
const enableSpotifyMode = () => { const enableSpotifyMode = () => {
setIsDemoMode(false);
setIsSpotifyAuthenticated(true); setIsSpotifyAuthenticated(true);
setLoading(false); setLoading(false);
}; };
@@ -49,12 +36,10 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
setSession(session); setSession(session);
setUser(session?.user ?? null); setUser(session?.user ?? null);
if (!session) { if (!session) {
setIsDemoMode(false);
setIsSpotifyAuthenticated(false); setIsSpotifyAuthenticated(false);
} else { } else {
const isSpotify = !!session.user?.user_metadata?.spotify_id; const isSpotify = !!session.user?.user_metadata?.spotify_id;
setIsSpotifyAuthenticated(isSpotify); setIsSpotifyAuthenticated(isSpotify);
setIsDemoMode(false);
} }
setLoading(false); setLoading(false);
}); });
@@ -63,12 +48,10 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
setSession(session); setSession(session);
setUser(session?.user ?? null); setUser(session?.user ?? null);
if (!session) { if (!session) {
setIsDemoMode(false);
setIsSpotifyAuthenticated(false); setIsSpotifyAuthenticated(false);
} else { } else {
const isSpotify = !!session.user?.user_metadata?.spotify_id; const isSpotify = !!session.user?.user_metadata?.spotify_id;
setIsSpotifyAuthenticated(isSpotify); setIsSpotifyAuthenticated(isSpotify);
setIsDemoMode(false);
} }
setLoading(false); setLoading(false);
}); });
@@ -77,7 +60,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
}, []); }, []);
return ( return (
<AuthContext.Provider value={{ user, session, loading, isDemoMode, isSpotifyAuthenticated, enableDemoMode, enableSpotifyMode }}> <AuthContext.Provider value={{ user, session, loading, isSpotifyAuthenticated, enableSpotifyMode }}>
{children} {children}
</AuthContext.Provider> </AuthContext.Provider>
); );

View File

@@ -18,7 +18,7 @@ const discovery: DiscoveryDocument = {
// @ts-ignore // @ts-ignore
export default function LoginScreen({ navigation }) { export default function LoginScreen({ navigation }) {
const { enableDemoMode, enableSpotifyMode } = useAuth(); const { enableSpotifyMode } = useAuth();
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -305,14 +305,6 @@ export default function LoginScreen({ navigation }) {
setLoading(false); setLoading(false);
}; };
const handleResetAuth = async () => {
await supabase.auth.signOut();
await clearSpotifyTokens();
setEmail('');
setPassword('');
Alert.alert('Reset', 'Auth state cleared.');
};
const handleSpotifyLogin = async () => { const handleSpotifyLogin = async () => {
try { try {
console.log("[SpotifyAuthDebug] 1. Exact redirectUri at login press:", redirectUri); console.log("[SpotifyAuthDebug] 1. Exact redirectUri at login press:", redirectUri);
@@ -394,22 +386,6 @@ export default function LoginScreen({ navigation }) {
<Text style={styles.spotifyButtonText}>Entrar com Spotify</Text> <Text style={styles.spotifyButtonText}>Entrar com Spotify</Text>
</TouchableOpacity> </TouchableOpacity>
{__DEV__ && (
<TouchableOpacity
style={[styles.primaryButton, { backgroundColor: '#333', marginBottom: 24 }]}
onPress={() => {
console.log("DEMO_BYPASS_PRESSED");
enableDemoMode();
}}
>
<Text style={styles.primaryButtonText}>Continuar sem Spotify (demo)</Text>
</TouchableOpacity>
)}
<TouchableOpacity style={{ padding: 10, alignItems: 'center', marginBottom: 10 }} onPress={handleResetAuth}>
<Text style={{ color: 'red', fontWeight: '600' }}>Reset Auth</Text>
</TouchableOpacity>
<View style={styles.footerContainer}> <View style={styles.footerContainer}>
<Text style={styles.footerText}>Não tens conta? </Text> <Text style={styles.footerText}>Não tens conta? </Text>
<TouchableOpacity onPress={() => navigation.navigate('Register')}> <TouchableOpacity onPress={() => navigation.navigate('Register')}>
@@ -489,7 +465,7 @@ const styles = StyleSheet.create({
padding: 24, padding: 24,
paddingBottom: Platform.OS === 'ios' ? 40 : 24, paddingBottom: Platform.OS === 'ios' ? 40 : 24,
flexGrow: 1, flexGrow: 1,
justifyContent: 'center', justifyContent: 'flex-start',
shadowColor: '#000', shadowColor: '#000',
shadowOffset: { width: 0, height: -4 }, shadowOffset: { width: 0, height: -4 },
shadowOpacity: 0.1, shadowOpacity: 0.1,

View File

@@ -176,7 +176,15 @@ export default function HomeScreen({ navigation }: Props) {
{loading ? ( {loading ? (
<ActivityIndicator size="large" color={colors.primary} style={{ marginTop: 40 }} /> <ActivityIndicator size="large" color={colors.primary} style={{ marginTop: 40 }} />
) : trips.length > 0 ? ( ) : trips.length > 0 ? (
trips.map(trip => <TripCard key={trip.id} trip={trip} />) trips.map(trip => (
<TouchableOpacity
key={trip.id}
activeOpacity={0.9}
onPress={() => navigation.navigate('TripDetails', { trip })}
>
<TripCard trip={trip} />
</TouchableOpacity>
))
) : ( ) : (
<View style={styles.promptCard}> <View style={styles.promptCard}>
<Text style={styles.promptTitle}>Pronto para a próxima?</Text> <Text style={styles.promptTitle}>Pronto para a próxima?</Text>

View File

@@ -1,361 +1,480 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, ImageBackground, SafeAreaView, FlatList } from 'react-native'; import { View, Text, StyleSheet, ScrollView, TouchableOpacity, ImageBackground, SafeAreaView, Linking, Alert } from 'react-native';
import { ArrowLeft, Share2, MoreVertical, Navigation, Clock, Music, Compass, MapPin, Play } from 'lucide-react-native'; import { ArrowLeft, MapPin, Navigation, Music, Play } from 'lucide-react-native';
import { colors } from '../../utils/colors'; import { colors } from '../../utils/colors';
import TimelineItem from '../../components/TimelineItem'; import TimelineItem from '../../components/TimelineItem';
import TrackItem from '../../components/TrackItem'; import { getDestinationLandmarkImage } from '../../services/destinationImage';
const MOCK_TRACKS = [
{ id: '1', title: 'Born to Run', artist: 'Bruce Springsteen', duration: '4:30' },
{ id: '2', title: 'Hotel California', artist: 'Eagles', duration: '6:30' },
{ id: '3', title: 'Holocene', artist: 'Bon Iver', duration: '5:37' },
{ id: '4', title: 'Ganges', artist: 'The National', duration: '4:12' },
];
// @ts-ignore // @ts-ignore
export default function TripDetailsScreen({ navigation }) { export default function TripDetailsScreen({ navigation, route }) {
const [activeTab, setActiveTab] = useState<'rota' | 'playlist'>('rota'); const { trip } = route.params || {};
const renderRotaTab = () => ( const [imageUrl, setImageUrl] = useState<string | null>(trip?.destination_image_url || null);
<View style={styles.tabContent}> const [landmarkName, setLandmarkName] = useState<string | null>(trip?.destination_landmark_name || null);
{/* Stats Cards */}
<View style={styles.statsContainer}>
<View style={styles.statCard}>
{/* @ts-ignore */}
<Navigation color={colors.primary} size={24} style={{ marginBottom: 8 }} />
<Text style={styles.statValue}>314 km</Text>
<Text style={styles.statLabel}>Distância</Text>
</View>
<View style={styles.dividerVertical} />
<View style={styles.statCard}>
<Clock color={colors.primary} size={24} style={{ marginBottom: 8 }} />
<Text style={styles.statValue}>3h 15m</Text>
<Text style={styles.statLabel}>Tempo</Text>
</View>
</View>
{/* DJ Guide Card */} useEffect(() => {
<View style={styles.djCard}> if (!trip?.destination || imageUrl) return;
<View style={styles.djHeader}>
<View style={styles.djIconContainer}>
<Music color={colors.white} size={16} />
</View>
<Text style={styles.djTitle}>O Teu DJ Guide:</Text>
</View>
<Text style={styles.djText}>
"Na primeira hora da viagem, ouve Rock clássico para acordar. Quando passares pela zona de Leiria, ouve Indie Folk. Ah, e faz uma paragem na área de serviço de Pombal porque o café lá tem ótimas reviews."
</Text>
</View>
{/* Itinerary */} let isMounted = true;
<View style={styles.itinerarySection}> const tripTitle = trip.title || trip.destination || 'Viagem';
<View style={styles.sectionHeader}>
<Compass color={colors.textMain} size={20} style={{ marginRight: 8 }} />
<Text style={styles.sectionTitle}>Itinerário</Text>
</View>
<View style={styles.timelineContainer}> getDestinationLandmarkImage(trip.destination, { tripTitle }).then(result => {
<TimelineItem if (!isMounted) return;
title="Lisbon, Portugal" if (result.imageUrl) {
subtitle="Partida • 0 km" setImageUrl(result.imageUrl);
type="start" }
/> if (result.landmarkName) {
<TimelineItem setLandmarkName(result.landmarkName);
title="Área de Serviço de Pombal" }
subtitle="Paragem sugerida para café. + 145 km" });
type="stop"
/>
<TimelineItem
title="Porto, Portugal"
subtitle="Destino • 314 km"
type="end"
isLast
/>
</View>
</View>
{/* Maps Action */} return () => {
<TouchableOpacity style={styles.mapsButton}> isMounted = false;
<MapPin color={colors.white} size={20} style={{ marginRight: 8 }} /> };
<Text style={styles.mapsButtonText}>Abrir no Google Maps</Text> }, [trip?.destination, imageUrl]);
</TouchableOpacity>
</View>
);
const renderPlaylistTab = () => ( const handleOpenGoogleMaps = () => {
<View style={styles.tabContent}> if (!trip?.origin || !trip?.destination) {
{/* Generated Playlist Card */} Alert.alert('Erro', 'Dados de rota incompletos.');
<View style={styles.playlistGeneratedCard}> return;
<View style={styles.spotifyLogoLarge}> }
{/* Mocking large spotify logo with music icon for now */} const url = `https://www.google.com/maps/dir/?api=1&origin=${encodeURIComponent(trip.origin)}&destination=${encodeURIComponent(trip.destination)}`;
<Music color={colors.spotify} size={40} /> Linking.openURL(url);
</View> };
<Text style={styles.playlistTitle}>Playlist Gerada</Text>
<Text style={styles.playlistSubtitle}>45 músicas 3h 20m de viagem</Text>
<TouchableOpacity style={styles.spotifyActionBtn}> const stops = trip?.stops || trip?.waypoints || [];
<Play fill={colors.white} color={colors.white} size={18} style={{ marginRight: 8 }} /> const hasStops = Array.isArray(stops) && stops.length > 0;
<Text style={styles.spotifyActionText}>Ouvir no Spotify</Text>
</TouchableOpacity>
</View>
{/* Preview List */} const routeVisual = (
<Text style={styles.previewTitle}>Pré-visualização (Músicas Iniciais)</Text> <View style={styles.mockRouteVisual}>
<View style={styles.previewListCard}> <View style={styles.routeDotLarge} />
{MOCK_TRACKS.map((track, index) => ( <View style={styles.routeLineDashed} />
<TrackItem <View style={styles.routePinLarge}>
key={track.id} <MapPin color={colors.white} size={14} />
index={index + 1}
title={track.title}
artist={track.artist}
duration={track.duration}
/>
))}
</View> </View>
</View> </View>
); );
return ( return (
<View style={styles.container}> <SafeAreaView style={styles.safeArea}>
<ScrollView> {/* Header */}
{/* Header Image */} <View style={styles.header}>
<ImageBackground <Text style={styles.title} numberOfLines={1}>
source={{ uri: 'https://images.unsplash.com/photo-1469854523086-cc02fe5d8800?q=80&w=2021&auto=format&fit=crop' }} {trip?.title || trip?.destination || 'Detalhes da Viagem'}
style={styles.headerImage} </Text>
<TouchableOpacity
style={styles.backButton}
onPress={() => navigation.goBack()}
> >
<SafeAreaView style={styles.headerSafeArea}> <ArrowLeft color={colors.textMain} size={20} />
<View style={styles.headerNav}> </TouchableOpacity>
<TouchableOpacity style={styles.navIconButton} onPress={() => navigation.goBack()}> </View>
<ArrowLeft color={colors.white} size={24} />
</TouchableOpacity> <ScrollView contentContainerStyle={styles.scrollContent}>
<View style={styles.navRightActions}> {/* Top visual representation */}
<TouchableOpacity style={styles.navIconButton}> {imageUrl ? (
<Share2 color={colors.white} size={20} /> <ImageBackground source={{ uri: imageUrl }} style={styles.mapArea}>
</TouchableOpacity> <View style={styles.imageOverlay}>
<TouchableOpacity style={[styles.navIconButton, { marginLeft: 12 }]}> {landmarkName && (
<MoreVertical color={colors.white} size={20} /> <View style={styles.landmarkTag}>
</TouchableOpacity> <Text style={styles.landmarkTagText} numberOfLines={1}>
</View> {landmarkName}
</Text>
</View>
)}
{routeVisual}
</View> </View>
</ImageBackground>
) : (
<View style={styles.mapArea}>
{routeVisual}
</View>
)}
<View style={styles.headerTitles}> {/* Content Card overlapping map/image */}
<View style={styles.tag}> <View style={styles.detailsCard}>
<Text style={styles.tagText}>ROADTRIP</Text> {/* Trip Name info */}
</View> <View style={styles.inputGroup}>
<Text style={styles.mainTitle}>Lisbon to Porto Coastline</Text> <Text style={styles.inputLabel}>NOME DA VIAGEM</Text>
<Text style={styles.subTitle}>May 10, 2026</Text> <View style={styles.textInputReadonly}>
<Text style={styles.textInputReadonlyText}>
{trip?.title || trip?.destination || 'Sem título'}
</Text>
</View> </View>
</SafeAreaView>
</ImageBackground>
{/* Content Area overlapping image slightly */}
<View style={styles.contentWrapper}>
{/* Custom Tabs */}
<View style={styles.tabContainer}>
<TouchableOpacity
style={[styles.tabButton, activeTab === 'rota' && styles.tabButtonActive]}
onPress={() => setActiveTab('rota')}
>
<Text style={[styles.tabText, activeTab === 'rota' && styles.tabTextActive]}>
Rota & DJ
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.tabButton, activeTab === 'playlist' && styles.tabButtonActive]}
onPress={() => setActiveTab('playlist')}
>
<Text style={[styles.tabText, activeTab === 'playlist' && styles.tabTextActive]}>
Playlist Spotify
</Text>
</TouchableOpacity>
</View> </View>
{/* Render Tab Content */} {/* Origin & Destination timeline */}
{activeTab === 'rota' ? renderRotaTab() : renderPlaylistTab()} <View style={styles.routeInputContainer}>
<View style={styles.routeTimeline}>
<View style={styles.timelineDot} />
<View style={styles.timelineLine} />
<MapPin color={colors.textSecondary} size={16} style={styles.timelinePin} />
</View>
<View style={styles.routeInputs}>
<View style={styles.inputGroup}>
<Text style={styles.inputLabel}>PARTIDA</Text>
<View style={styles.textInputReadonly}>
<Text style={[styles.textInputReadonlyText, styles.boldText]}>
{trip?.origin}
</Text>
</View>
</View>
<View style={[styles.inputGroup, { marginBottom: 0 }]}>
<Text style={styles.inputLabel}>DESTINO</Text>
<View style={styles.textInputReadonly}>
<Text style={[styles.textInputReadonlyText, styles.boldText]}>
{trip?.destination}
</Text>
</View>
</View>
</View>
</View>
{/* Distance and Duration summary */}
<View style={styles.resultsContainer}>
<View style={styles.resultItem}>
<Text style={styles.resultLabel}>Distância</Text>
<Text style={styles.resultValue}>{trip?.distance || '-'}</Text>
</View>
<View style={styles.resultDivider} />
<View style={styles.resultItem}>
<Text style={styles.resultLabel}>Duração</Text>
<Text style={styles.resultValue}>{trip?.duration || '-'}</Text>
</View>
</View>
{/* Spotify Playlist Segment */}
{trip?.playlist_url ? (
<View style={styles.playlistSection}>
<Text style={styles.sectionLabel}>PLAYLIST DO SPOTIFY</Text>
<View style={styles.spotifyCard}>
<View style={styles.spotifyHeader}>
<View style={styles.spotifyLogoContainer}>
<Music color={colors.spotify} size={24} />
</View>
<View style={styles.spotifyHeaderTextContainer}>
<Text style={styles.spotifyCardTitle} numberOfLines={1}>
{trip?.title || 'Playlist da Viagem'}
</Text>
<Text style={styles.spotifyCardSubtitle} numberOfLines={1}>
{trip?.playlist_url}
</Text>
</View>
</View>
<TouchableOpacity
style={styles.spotifyButton}
onPress={() => Linking.openURL(trip.playlist_url)}
>
<Play fill={colors.white} color={colors.white} size={16} style={{ marginRight: 8 }} />
<Text style={styles.spotifyButtonText}>Ouvir no Spotify</Text>
</TouchableOpacity>
</View>
</View>
) : null}
{/* Route Stops / Waypoints */}
<View style={styles.stopsSection}>
<Text style={styles.sectionLabel}>PARAGENS DA ROTA</Text>
{hasStops ? (
<View style={styles.timelineContainer}>
{stops.map((stop: string, index: number) => (
<TimelineItem
key={index}
title={stop}
subtitle={`Paragem #${index + 1}`}
type="stop"
isLast={index === stops.length - 1}
/>
))}
</View>
) : (
<View style={styles.noStopsCard}>
<Text style={styles.noStopsText}>
Ainda não existem paragens guardadas para esta viagem.
</Text>
</View>
)}
</View>
{/* Bottom Actions */}
<View style={styles.bottomActions}>
<TouchableOpacity
style={styles.mapsButton}
onPress={handleOpenGoogleMaps}
>
<Navigation color={colors.primary} size={20} />
<Text style={styles.mapsButtonText}>Abrir no Google Maps</Text>
</TouchableOpacity>
</View>
</View> </View>
</ScrollView> </ScrollView>
</View> </SafeAreaView>
); );
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { safeArea: {
flex: 1, flex: 1,
backgroundColor: colors.background, backgroundColor: colors.white,
}, },
headerImage: { scrollContent: {
width: '100%', flexGrow: 1,
height: 320,
justifyContent: 'flex-start',
}, },
headerSafeArea: { header: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.3)', // Overlay
justifyContent: 'space-between',
},
headerNav: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 20, paddingHorizontal: 20,
paddingTop: 16, paddingTop: 16,
}, paddingBottom: 16,
navIconButton: { backgroundColor: colors.white,
width: 40, zIndex: 10,
height: 40,
borderRadius: 20,
backgroundColor: 'rgba(255,255,255,0.2)',
justifyContent: 'center',
alignItems: 'center',
},
navRightActions: {
flexDirection: 'row',
},
headerTitles: {
paddingHorizontal: 20,
paddingBottom: 40, // Room for overlap
},
tag: {
backgroundColor: colors.primary,
alignSelf: 'flex-start',
paddingHorizontal: 12,
paddingVertical: 4,
borderRadius: 8,
marginBottom: 8,
},
tagText: {
color: colors.white,
fontSize: 12,
fontWeight: 'bold',
letterSpacing: 1,
},
mainTitle: {
fontSize: 32,
fontWeight: '800',
color: colors.white,
marginBottom: 4,
lineHeight: 38,
},
subTitle: {
fontSize: 16,
color: colors.white,
fontWeight: '500',
},
contentWrapper: {
flex: 1,
backgroundColor: colors.background,
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
marginTop: -24,
minHeight: 500,
},
tabContainer: {
flexDirection: 'row',
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: colors.inputBorder, borderBottomColor: colors.inputBorder,
paddingHorizontal: 20,
marginTop: 10,
}, },
tabButton: { title: {
flex: 1,
paddingVertical: 16,
alignItems: 'center',
borderBottomWidth: 2,
borderBottomColor: 'transparent',
},
tabButtonActive: {
borderBottomColor: colors.primary,
},
tabText: {
fontSize: 15,
fontWeight: 'bold',
color: colors.textSecondary,
},
tabTextActive: {
color: colors.primary,
},
tabContent: {
padding: 20,
},
statsContainer: {
flexDirection: 'row',
backgroundColor: colors.white,
borderRadius: 20,
padding: 20,
marginBottom: 24,
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.05,
shadowRadius: 8,
elevation: 3,
},
statCard: {
flex: 1,
alignItems: 'center',
},
dividerVertical: {
width: 1,
backgroundColor: colors.inputBorder,
marginHorizontal: 10,
},
statValue: {
fontSize: 20, fontSize: 20,
fontWeight: 'bold', fontWeight: 'bold',
color: colors.textMain, color: colors.textMain,
marginBottom: 4, flex: 1,
marginRight: 16,
}, },
statLabel: { backButton: {
fontSize: 13, width: 36,
height: 36,
borderRadius: 18,
backgroundColor: colors.inputBackground,
justifyContent: 'center',
alignItems: 'center',
},
mapArea: {
height: 180,
backgroundColor: '#F0F2F5', // Light map-like gray
justifyContent: 'center',
alignItems: 'center',
},
imageOverlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0,0,0,0.3)',
justifyContent: 'center',
alignItems: 'center',
},
landmarkTag: {
position: 'absolute',
top: 12,
right: 12,
backgroundColor: 'rgba(0, 0, 0, 0.65)',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 12,
maxWidth: '80%',
},
landmarkTagText: {
color: colors.white,
fontSize: 12,
fontWeight: 'bold',
},
mockRouteVisual: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 40,
width: '100%',
},
routeDotLarge: {
width: 24,
height: 24,
borderRadius: 12,
backgroundColor: colors.primary,
borderWidth: 4,
borderColor: colors.white,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
zIndex: 2,
},
routeLineDashed: {
flex: 1,
height: 4,
borderWidth: 2,
borderColor: colors.primary,
borderStyle: 'dashed',
marginHorizontal: -4,
zIndex: 1,
},
routePinLarge: {
width: 28,
height: 28,
borderRadius: 14,
backgroundColor: '#000000',
justifyContent: 'center',
alignItems: 'center',
borderWidth: 2,
borderColor: colors.white,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
zIndex: 2,
},
detailsCard: {
backgroundColor: colors.white,
borderTopLeftRadius: 32,
borderTopRightRadius: 32,
padding: 24,
marginTop: -32, // Overlap map/image
shadowColor: '#000',
shadowOffset: { width: 0, height: -4 },
shadowOpacity: 0.05,
shadowRadius: 12,
elevation: 10,
},
inputGroup: {
marginBottom: 20,
},
inputLabel: {
fontSize: 12,
fontWeight: 'bold',
color: colors.textSecondary, color: colors.textSecondary,
marginBottom: 8,
letterSpacing: 0.5,
},
textInputReadonly: {
backgroundColor: colors.inputBackground,
borderRadius: 16,
paddingHorizontal: 16,
paddingVertical: 16,
},
textInputReadonlyText: {
fontSize: 16,
color: colors.textMain,
fontWeight: '500', fontWeight: '500',
}, },
djCard: { boldText: {
backgroundColor: '#FFF5EB', fontWeight: 'bold',
},
routeInputContainer: {
flexDirection: 'row',
marginTop: 10,
},
routeTimeline: {
alignItems: 'center',
width: 30,
marginTop: 38,
marginRight: 8,
},
timelineDot: {
width: 10,
height: 10,
borderRadius: 5,
backgroundColor: colors.primary,
},
timelineLine: {
width: 1,
height: 60,
backgroundColor: colors.inputBorder,
marginVertical: 4,
},
timelinePin: {
marginTop: 4,
},
routeInputs: {
flex: 1,
},
resultsContainer: {
flexDirection: 'row',
marginTop: 24,
paddingTop: 24,
borderTopWidth: 1,
borderTopColor: colors.inputBorder,
marginBottom: 24,
},
resultItem: {
flex: 1,
alignItems: 'center',
},
resultDivider: {
width: 1,
backgroundColor: colors.inputBorder,
marginHorizontal: 16,
},
resultLabel: {
fontSize: 12,
color: colors.textSecondary,
marginBottom: 4,
fontWeight: 'bold',
},
resultValue: {
fontSize: 18,
color: colors.textMain,
fontWeight: 'bold',
},
sectionLabel: {
fontSize: 12,
fontWeight: 'bold',
color: colors.textSecondary,
marginBottom: 12,
letterSpacing: 0.5,
},
playlistSection: {
marginBottom: 28,
},
spotifyCard: {
backgroundColor: '#E8F5E9', // Light green matches spotify style
borderRadius: 20, borderRadius: 20,
padding: 20, padding: 20,
marginBottom: 30,
borderWidth: 1, borderWidth: 1,
borderColor: '#FFE0C2', borderColor: '#C8E6C9',
}, },
djHeader: { spotifyHeader: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
marginBottom: 16, marginBottom: 16,
}, },
djIconContainer: { spotifyLogoContainer: {
width: 28, width: 44,
height: 28, height: 44,
borderRadius: 14, borderRadius: 22,
backgroundColor: colors.primary, backgroundColor: '#000000',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
marginRight: 10, marginRight: 12,
}, },
djTitle: { spotifyHeaderTextContainer: {
fontSize: 18, flex: 1,
fontWeight: 'bold',
color: '#8C3800',
}, },
djText: { spotifyCardTitle: {
fontSize: 15, fontSize: 16,
lineHeight: 24,
color: '#A34200',
fontWeight: '500',
},
itinerarySection: {
marginBottom: 30,
},
sectionHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 20,
},
sectionTitle: {
fontSize: 20,
fontWeight: 'bold', fontWeight: 'bold',
color: colors.textMain, color: colors.textMain,
marginBottom: 2,
},
spotifyCardSubtitle: {
fontSize: 13,
color: colors.textSecondary,
},
spotifyButton: {
backgroundColor: colors.spotify,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 12,
borderRadius: 12,
shadowColor: colors.spotify,
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.2,
shadowRadius: 4,
elevation: 3,
},
spotifyButtonText: {
color: colors.white,
fontSize: 14,
fontWeight: 'bold',
},
stopsSection: {
marginBottom: 28,
}, },
timelineContainer: { timelineContainer: {
backgroundColor: colors.white, backgroundColor: colors.white,
@@ -366,87 +485,40 @@ const styles = StyleSheet.create({
shadowOpacity: 0.05, shadowOpacity: 0.05,
shadowRadius: 8, shadowRadius: 8,
elevation: 3, elevation: 3,
borderWidth: 1,
borderColor: colors.inputBorder,
},
noStopsCard: {
backgroundColor: '#FFF5EB',
borderRadius: 16,
padding: 16,
borderWidth: 1,
borderColor: '#FFE0C2',
alignItems: 'center',
},
noStopsText: {
color: '#A34200',
fontSize: 14,
fontWeight: '500',
textAlign: 'center',
lineHeight: 20,
},
bottomActions: {
marginTop: 8,
marginBottom: 16,
}, },
mapsButton: { mapsButton: {
backgroundColor: '#111827', // Almost black backgroundColor: colors.inputBackground,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center',
paddingVertical: 18, paddingVertical: 18,
borderRadius: 30, // Highly rounded borderRadius: 16,
marginBottom: 40,
}, },
mapsButtonText: { mapsButtonText: {
color: colors.white, color: colors.primary,
fontSize: 16, fontSize: 16,
fontWeight: 'bold', fontWeight: 'bold',
}, marginLeft: 8,
// Playlist Tab Styles
playlistGeneratedCard: {
backgroundColor: '#E8F5E9', // Light green
borderRadius: 24,
padding: 30,
alignItems: 'center',
marginBottom: 30,
borderWidth: 1,
borderColor: '#C8E6C9',
},
spotifyLogoLarge: {
width: 80,
height: 80,
borderRadius: 40,
backgroundColor: '#000',
justifyContent: 'center',
alignItems: 'center',
marginBottom: 16,
},
playlistTitle: {
fontSize: 24,
fontWeight: 'bold',
color: colors.textMain,
marginBottom: 8,
},
playlistSubtitle: {
fontSize: 15,
color: colors.textSecondary,
marginBottom: 24,
},
spotifyActionBtn: {
backgroundColor: colors.spotify,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 24,
paddingVertical: 14,
borderRadius: 30,
width: '100%',
justifyContent: 'center',
shadowColor: colors.spotify,
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 5,
},
spotifyActionText: {
color: colors.white,
fontSize: 16,
fontWeight: 'bold',
},
previewTitle: {
fontSize: 18,
fontWeight: 'bold',
color: colors.textMain,
marginBottom: 16,
},
previewListCard: {
backgroundColor: colors.white,
borderRadius: 20,
padding: 20,
marginBottom: 40,
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.05,
shadowRadius: 8,
elevation: 3,
}, },
}); });