Save current app progress
This commit is contained in:
28
scratch_db_test.js
Normal file
28
scratch_db_test.js
Normal 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();
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
|
||||||
<View style={styles.navRightActions}>
|
|
||||||
<TouchableOpacity style={styles.navIconButton}>
|
|
||||||
<Share2 color={colors.white} size={20} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity style={[styles.navIconButton, { marginLeft: 12 }]}>
|
|
||||||
<MoreVertical color={colors.white} size={20} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.headerTitles}>
|
<ScrollView contentContainerStyle={styles.scrollContent}>
|
||||||
<View style={styles.tag}>
|
{/* Top visual representation */}
|
||||||
<Text style={styles.tagText}>ROADTRIP</Text>
|
{imageUrl ? (
|
||||||
</View>
|
<ImageBackground source={{ uri: imageUrl }} style={styles.mapArea}>
|
||||||
<Text style={styles.mainTitle}>Lisbon to Porto Coastline</Text>
|
<View style={styles.imageOverlay}>
|
||||||
<Text style={styles.subTitle}>May 10, 2026</Text>
|
{landmarkName && (
|
||||||
|
<View style={styles.landmarkTag}>
|
||||||
|
<Text style={styles.landmarkTagText} numberOfLines={1}>
|
||||||
|
{landmarkName}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{routeVisual}
|
||||||
</View>
|
</View>
|
||||||
</SafeAreaView>
|
</ImageBackground>
|
||||||
</ImageBackground>
|
) : (
|
||||||
|
<View style={styles.mapArea}>
|
||||||
|
{routeVisual}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Content Area overlapping image slightly */}
|
{/* Content Card overlapping map/image */}
|
||||||
<View style={styles.contentWrapper}>
|
<View style={styles.detailsCard}>
|
||||||
|
{/* Trip Name info */}
|
||||||
{/* Custom Tabs */}
|
<View style={styles.inputGroup}>
|
||||||
<View style={styles.tabContainer}>
|
<Text style={styles.inputLabel}>NOME DA VIAGEM</Text>
|
||||||
<TouchableOpacity
|
<View style={styles.textInputReadonly}>
|
||||||
style={[styles.tabButton, activeTab === 'rota' && styles.tabButtonActive]}
|
<Text style={styles.textInputReadonlyText}>
|
||||||
onPress={() => setActiveTab('rota')}
|
{trip?.title || trip?.destination || 'Sem título'}
|
||||||
>
|
|
||||||
<Text style={[styles.tabText, activeTab === 'rota' && styles.tabTextActive]}>
|
|
||||||
Rota & DJ
|
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</View>
|
||||||
|
|
||||||
<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,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user