我对这个屏幕有疑问。元素溢出,我无法显示侧边栏来滚动内容。奇怪的是,在所有其他屏幕中,我使用我的组件
<AppContainer>
和 <SafeScreen>
,并且一切正常。我没有包含每个单独组件的代码,因为滚动在任何幻灯片上都不起作用。
我实际上只在网页版上进行测试。
import React, { useState, useEffect, useRef } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Alert, Platform, TextInput, ScrollView, Dimensions, KeyboardAvoidingView } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import AppContainer from '../AppContainer';
import SafeScreen from '../SafeScreen';
import AsyncStorage from '@react-native-async-storage/async-storage';
import RicercaScuolaComponent from '../RicercaScuolaComponent';
import RicercaPartecipanti from '../RicercaPartecipanti';
import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated';
import loadGoogleMapsAPI from '../webMapComponent';
import PianoViaggiSummary from './PianoViaggiSummary';
import { getAuth } from 'firebase/auth';
import { getFirestore, collection, doc, setDoc } from 'firebase/firestore';
let MapView, Marker, Geocoder, AutocompleteService, PlacesService;;
const NuovoPianoViaggiScreen = ({ }) => {
const [currentSlide, setCurrentSlide] = useState(0);
const [partecipanti, setPartecipanti] = useState([]);
const [scuolaSelezionata, setScuolaSelezionata] = useState(null);
const [googleMapsLoaded, setGoogleMapsLoaded] = useState(false);
const [meetingPlace, setMeetingPlace] = useState(null);
const [searchQuery, setSearchQuery] = useState('');
const [meetingAddress, setMeetingAddress] = useState('');
//const [scuolaDestinazione, setScuolaDestinazione] = useState(null);
const [lastSavedSlide, setLastSavedSlide] = useState(-1);
const [meetingName, setMeetingName] = useState('');
const [suggestions, setSuggestions] = useState([]);
const handleScuolaSelezionata = (scuola) => {
console.log('Scuola selezionata:', scuola);
setScuolaSelezionata(scuola);
// Aggiorna il pianoViaggi direttamente
setPianoViaggi(prev => ({
...prev,
scuola: scuola
}));
};
const [pianoViaggi, setPianoViaggi] = useState({
luogo: null,
scuola: null,
partecipanti: []
});
const [region, setRegion] = useState({
latitude: 41.9028,
longitude: 12.4964,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
});
const mapRef = useRef(null);
const opacity = useSharedValue(1);
useEffect(() => {
{/*...*/} }
);
// custom alert
const customAlert = (title, message, buttons) => {
if (Platform.OS === 'web') {
if (window.confirm(`${title}\n\n${message}`)) {
buttons.find(b => b.style === 'default').onPress();
} else {
buttons.find(b => b.style === 'cancel').onPress();
}
} else {
Alert.alert(title, message, buttons);
}
};
const salvaProgressoLocale = async () => {
{/*...*/}
};
const caricaProgressoLocale = async () => {
{/*...*/}
}
};
const salvaSuFirestore = async () => {
{/*...*/}
};
const handleMapPress = (event) => {
{/*...*/}
};
const handleSearch = () => {
{/*...*/}
};
const handleSearchInputChange = (text) => {
{/*...*/}
};
const renderSuggestion = ({ item }) => (
{/*...*/}
);
const renderMap = () => {
if (!googleMapsLoaded || !MapView || !AutocompleteService || !PlacesService) {
return <Text>Caricamento della mappa...</Text>;
}
return (
<View style={styles.mapContainer}>
<View style={styles.searchContainer}>
<TextInput
style={styles.searchInput}
value={searchQuery}
onChangeText={handleSearchInputChange}
placeholder="Cerca un luogo o selezionalo sulla mappa"
/>
<TouchableOpacity style={styles.searchButton} onPress={handleSearch}>
<Ionicons name="search" size={24} color="#fff" />
</TouchableOpacity>
</View>
<MapView
style={styles.map}
region={region}
onRegionChangeComplete={setRegion}
onPress={handleMapPress}
>
{meetingPlace && (
<Marker
coordinate={meetingPlace}
title="Punto di Ritrovo"
description="Luogo di incontro selezionato"
/>
)}
</MapView>
{/* Input per il nome personalizzato del luogo */}
<TextInput
style={styles.locationNameInput}
value={meetingName}
onChangeText={setMeetingName} // Cambia il nome personalizzato
placeholder="Assegna un nome personalizzato al luogo"
/>
</View>
);
};
const fadeOutIn = (nextSlide) => {
opacity.value = withTiming(0, { duration: 300 }, () => {
setCurrentSlide(nextSlide);
opacity.value = withTiming(1, { duration: 300 });
});
};
const passaAllaSlideSuccessiva = async () => {
console.log(`Inizio passaAllaSlideSuccessiva. Slide corrente: ${currentSlide}`);
let canProceed = true;
if (currentSlide === 0) {
console.log('Slide 0: Verifica dati del luogo');
console.log(`meetingPlace: ${JSON.stringify(meetingPlace)}`);
console.log(`meetingAddress: ${meetingAddress}`);
console.log(`meetingName: ${meetingName}`);
if (!meetingPlace || !meetingAddress || !meetingName) {
console.log('Errore: Dati del luogo mancanti');
customAlert("Errore", "Per favore, seleziona un punto di ritrovo e inserisci un nome per il luogo prima di procedere.");
canProceed = false;
} else {
console.log('Aggiornamento pianoViaggi con i dati del luogo');
setPianoViaggi(prev => ({
{/*...*/}
}
}));
}
} else if (currentSlide === 1) {
console.log('Slide 1: Verifica selezione scuola');
console.log(`scuolaSelezionata: ${JSON.stringify(scuolaSelezionata)}`);
if (!scuolaSelezionata) {
console.log('Errore: Scuola non selezionata');
customAlert("Errore", "Per favore, seleziona una scuola dalla lista prima di procedere.");
canProceed = false;
} else {
console.log('Aggiornamento pianoViaggi con la scuola selezionata');
// Aggiorna anche il pianoViaggi con la destinazione
setPianoViaggi(prev => ({
...prev,
scuola: scuolaSelezionata // Aggiungi qui
}));
}
}
if (canProceed) {
{/*...*/}
};
const passaAllaSlidePrecedente = () => {
fadeOutIn(currentSlide - 1);
};
const animatedStyle = useAnimatedStyle(() => {
return {
opacity: opacity.value,
};
});
const renderSlide = () => {
const slideContent = (() => {
switch (currentSlide) {
case 0:
return (
<>
<Text style={styles.title}>Seleziona il Punto di Ritrovo</Text>
{renderMap()}
</>
);
case 1:
return (
<>
<Text style={styles.title}>Scuola di Destinazione</Text>
<RicercaScuolaComponent
onScuolaSelezionata={setScuolaSelezionata}
scuolaSelezionata={scuolaSelezionata}
/>
</>
);
case 2:
return (
<>
<Text style={styles.title}>Aggiungi Partecipanti</Text>
<RicercaPartecipanti
scuolaSelezionata={scuolaSelezionata}
onPartecipanteSelezionato={(partecipante) =>
setPartecipanti([...partecipanti, partecipante])}
/>
</>
);
case 3:
return (
<>
<Text style={styles.title}>Riepilogo del piano viaggi</Text>
<PianoViaggiSummary
pianoViaggi={pianoViaggi}
partecipanti={partecipanti}
/>
</>
);
default:
return null;
}
})();
return (
<AppContainer>
<SafeScreen style={styles.container}>
<Animated.View style={[styles.slideContainer, animatedStyle]}>
{slideContent}
</Animated.View>
</SafeScreen>
</AppContainer>
);
};
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.container}
>
{renderSlide()}
<View style={styles.navigationContainer}>
{currentSlide > 0 && (
<TouchableOpacity
style={styles.navButton}
onPress={passaAllaSlidePrecedente}
>
<Ionicons name="arrow-back" size={24} color="#fff" />
<Text style={styles.navButtonText}>Indietro</Text>
</TouchableOpacity>
)}
{currentSlide < 3 && (
<TouchableOpacity
style={styles.navButton}
onPress={passaAllaSlideSuccessiva}
>
<Text style={styles.navButtonText}>Avanti</Text>
<Ionicons name="arrow-forward" size={24} color="#fff" />
</TouchableOpacity>
)}
</View>
</KeyboardAvoidingView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
justifyContent: 'center',
backgroundColor: '#f4f4f9',
},
contentContainer: {
flexGrow: 1,
padding:20,
paddingBottom:100,
},
title: {
fontSize: 28,
fontWeight: 'bold',
color: '#333',
marginBottom: 20,
textAlign: 'center',
},
subtitle: {
fontSize: 22,
fontWeight: 'bold',
marginTop: 20,
marginBottom: 10,
color: '#333',
},
input: {
borderWidth: 1,
borderColor: '#ddd',
padding: 15,
borderRadius: 10,
marginBottom: 20,
backgroundColor: '#fff',
fontSize: 16,
},
suggestionList: {
flex: 1,
marginBottom: 20,
},
suggestionItem: {
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#ddd',
backgroundColor: '#f9f9f9',
borderRadius: 8,
marginBottom: 5,
},
suggestionText: {
color: '#333',
fontSize: 16,
fontWeight: 'bold',
},
addManuallyButton: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
marginTop: 10,
marginBottom: 20,
padding: 10,
backgroundColor: '#f0e6ff',
borderRadius: 8,
},
subtitle: {
fontSize: 16,
color: '#666',
marginBottom: 20,
textAlign: 'center',
},
addManuallyText: {
color: '#6200EE',
marginLeft: 10,
fontSize: 16,
},
participantList: {
flex: 1,
},
participantItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#ddd',
backgroundColor: '#f9f9f9',
borderRadius: 8,
marginBottom: 5,
},
participantText: {
color: '#333',
fontSize: 16,
},
scrollView: {
flexGrow: 1,
},
navButton: {
flexDirection: 'row',
alignItems: 'center',
},
navButtonText: {
color: '#fff',
marginHorizontal: 8,
},
searchContainer: {
flexDirection: 'row',
marginBottom: 10,
},
searchInput: {
flex: 1,
borderWidth: 1,
borderColor: '#ddd',
padding: 10,
borderRadius: 5,
marginRight: 10,
backgroundColor: '#fff',
},
searchButton: {
backgroundColor: '#6200EE',
padding: 10,
borderRadius: 5,
justifyContent: 'center',
alignItems: 'center',
},
scrollContent: {
flexGrow: 1,
paddingBottom: 80, // Aggiungi spazio per la navigation bar
},
navigationContainer: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
backgroundColor: '#000',
},
navigation: {
flexDirection: 'row',
justifyContent: 'space-between',
padding: 16,
},
mapContainer: {
height: 400, // Altezza fissa per la mappa
marginVertical: 20,
},
map: {
flex: 1,
},
locationNameInput: {
borderWidth: 1,
borderColor: '#ddd',
padding: 10,
borderRadius: 5,
marginTop: 10,
backgroundColor: '#fff',
},
safeArea: {
flex: 1,
backgroundColor: '#f4f4f9',
},
slideContainer: {
flex:1,
},
title: {
fontSize: 28,
fontWeight: 'bold',
color: '#333',
marginBottom: 20,
textAlign: 'center',
},
mapContainer: {
height: 400,
marginVertical: 20,
width: '100%',
},
map: {
flex: 1,
},
searchContainer: {
flexDirection: 'row',
marginBottom: 10,
width: '100%',
},
searchInput: {
flex: 1,
borderWidth: 1,
borderColor: '#ddd',
padding: 10,
borderRadius: 5,
marginRight: 10,
backgroundColor: '#fff',
},
searchButton: {
backgroundColor: '#6200EE',
padding: 10,
borderRadius: 5,
justifyContent: 'center',
alignItems: 'center',
},
locationNameInput: {
borderWidth: 1,
borderColor: '#ddd',
padding: 10,
borderRadius: 5,
marginTop: 10,
backgroundColor: '#fff',
width: '100%',
},
navigationContainer: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
backgroundColor: '#6200EE',
padding: 16,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
navButton: {
flexDirection: 'row',
alignItems: 'center',
padding: 10,
},
navButtonText: {
color: '#fff',
marginHorizontal: 8,
fontSize: 16,
},
});
export default NewScreen;
import React from 'react';
import { View, StyleSheet, useWindowDimensions, ScrollView } from 'react-native';
const AppContainer = ({ children }) => {
const { width } = useWindowDimensions();
const isDesktop = width > 768;
return (
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollViewContent}
keyboardShouldPersistTaps="handled"
>
<View style={[styles.container, isDesktop && styles.desktopContainer]}>
<View style={[styles.content, isDesktop && styles.desktopContent]}>
{children}
</View>
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
scrollView: {
flex: 1,
padding: 5,
backgroundColor: '#f5f5f5',
},
scrollViewContent: {
flexGrow: 1,
},
container: {
flex: 1,
alignItems: 'center',
},
desktopContainer: {
paddingHorizontal: 16,
},
content: {
flex: 1,
width: '100%',
maxWidth: 768,
backgroundColor: '#f5f5f5',
},
desktopContent: {
elevation: 5,
},
});
export default AppContainer;
import React from 'react';
import { SafeAreaView, StyleSheet, Platform, View, ScrollView, StatusBar } from 'react-native';
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
import { useNavigationState } from '@react-navigation/native';
const SafeScreen = ({ children, style, scrollable = false }) => {
const navigationState = useNavigationState(state => state);
let tabBarHeight = 0;
let isInBottomTab = false;
if (navigationState) {
const route = navigationState.routes[navigationState.index];
isInBottomTab = route.state && route.state.type === 'tab';
}
if (isInBottomTab) {
try {
tabBarHeight = useBottomTabBarHeight();
} catch (error) {
console.warn('Failed to get bottom tab bar height:', error);
}
}
const content = (
<View
style={[
styles.content,
{ paddingBottom: Platform.OS === 'android' && isInBottomTab ? tabBarHeight : 0 }
]}
>
{children}
</View>
);
return (
<SafeAreaView style={[styles.container, style]}>
<StatusBar barStyle="dark-content" backgroundColor="white" />
{content}
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0,
},
content: {
flex: 1,
},
scrollViewContent: {
flexGrow: 1,
},
});
export default SafeScreen;
不要使用本机派生组件作为包装器,这会导致 UI 和手势行为中出现错误。 在您的情况下,您需要用另一个
ScrollView
包裹您的 View
并对其应用 flex: 1
样式。