I am unable to pass the photo data from the takePicture function in CameraScreen.tsx to the AddNewPostButton.tsx component to insert the photo into the firebase database. My idea of using useContext is not working and I don't know how to solve it.
I tried to send photos from the CameraScreen screen to the AddNewPostButton component using the useContext mechanism, but I couldn't do it and I don't know how to do it.
CameraScreen.tsx
"Cannot assign argument of type 'string | undefined' to parameter of type 'string | null'.
Type 'undefined' cannot be assigned to type 'string | null'."
import React, { useContext, useState, createContext } from 'react';
import { Pressable, Center, Modal, Button, Stack, Input, TextArea, Icon, Text, View } from 'native-base';
import { MaterialCommunityIcons } from '#expo/vector-icons';
import { addDoc, Timestamp, collection } from 'firebase/firestore';
import { db } from '../firebaseConfig';
import { AuthContext } from '../utils/AuthStateListener';
import type { StackNavigationProp } from '#react-navigation/stack';
import { useNavigation } from '#react-navigation/native';
import { RootStackParamList } from '../stacks/RootStack';
type photoButton = StackNavigationProp<RootStackParamList>;
export const ImageContext = createContext<{ image: string | null, setImage: (newImage: string | null) => void }>({
image: null,
setImage: () => {},
});
function AddNewPostButton() {
const { currentUser, userProfile } = useContext(AuthContext);
//const [image, setImage] = useState<string | null>(null);
const navigation: photoButton = useNavigation();
const { image, setImage } = useContext(ImageContext);
/* const setImage = (newImage: string | null) => {
ImageContext.setImage(newImage);
} */
const [modalVisible, setModalVisible] = React.useState(false);
const [newPost, setNewPost] = React.useState({
title: '',
description: '',
location: '',
photo: ''
});
if (image) console.log(image);
const handleChange = (name: string, value: string) => {
setNewPost((prev) => ({ ...prev, [name]: value }));
};
const addNewPost = async () => {
await addDoc(collection(db, 'publicPosts'), {
title: newPost.title,
description: newPost.description,
location: newPost.location,
date: Timestamp.fromDate(new Date()),
photo: image ? image : '',
likes: 0,
authorId: currentUser?.uid,
authorName: userProfile?.name,
});
setNewPost({
title: '',
description: '',
location: '',
photo: ''
});
setModalVisible(false);
setImage('');
};
return (
<>
<Modal isOpen={modalVisible} onClose={setModalVisible}>
<Modal.Content>
<Modal.CloseButton />
<Modal.Header>Dodaj ogłoszenie</Modal.Header>
<Modal.Body>
<Stack space={1} w="75%" maxW="300px" mx="auto">
<Input
variant="outline"
placeholder="Tytuł nowego ogłoszenia"
onChangeText={(value) => handleChange('title', value)}
/>
<Input
variant="outline"
placeholder="Lokalizacja"
onChangeText={(value) => handleChange('location', value)}
/>
<TextArea
variant="outline"
placeholder="Opis ogłoszenia"
onChangeText={(value) => handleChange('description', value)}
/>
</Stack>
<View>
<Button
onPress={() => {
navigation.navigate({
name: 'Camera',
key: 'CameraScreen',
});
//navigation.navigate({name:'Camera'})
setModalVisible(false);
}}>
<Text>Dodaj zdjęcie</Text>
</Button>
</View>
</Modal.Body>
<Modal.Footer>
<Button.Group space={2}>
<Button
variant="ghost"
colorScheme="blueGray"
onPress={() => {
setModalVisible(false);
}}>
Anuluj
</Button>
<Button
onPress={() => {
addNewPost();
}}>
Zapisz
</Button>
</Button.Group>
</Modal.Footer>
</Modal.Content>
</Modal>
<Pressable
opacity={modalVisible === true ? 1 : 0.5}
py="3"
flex={1}
onPress={() => {
setModalVisible(true);
}}>
<Center>
<Icon mb="1" as={<MaterialCommunityIcons name="plus-box" />} color="white" size="sm" />
<Text color="white" fontSize="12">
Dodaj
</Text>
</Center>
</Pressable>
</>
);
}
export default AddNewPostButton;
import { Camera, CameraType } from 'expo-camera';
import { useState, useRef, useContext } from 'react';
import { StyleSheet, Text, TouchableOpacity, View, Dimensions, Alert } from 'react-native';
import { Ionicons } from '#expo/vector-icons';
import { ImageContext } from '../components/AddNewPostButton';
const screenWidth = Dimensions.get('window').width;
function CameraScreen() {
const [type, setType] = useState(CameraType.back);
const [permission, requestPermission] = Camera.useCameraPermissions();
const { image, setImage } = useContext(ImageContext);
let camera = useRef<Camera>(null);
if (!permission) {
return <View />;
}
if (!permission.granted) {
return (
<Text>Brak dostępu do kamery</Text>
);
}
const takePicture = async () => {
if (camera.current) {
try {
const options = { quality: 0.5, base64: true, orientation: 'portrait' };
const data = await camera.current.takePictureAsync(options);
setImage(data.base64);
} catch (error) {
console.error(error);
Alert.alert('Wystąpił błąd podczas dodawania zdjęcia');
}
}
}
return (
<View style={styles.container}>
<Camera ref={camera} style={styles.camera} type={type}>
<View style={styles.cameraControls}>
<TouchableOpacity
style={styles.flipButton}
onPress={() => {
setType(
type === CameraType.back
? CameraType.front
: CameraType.back
);
}}>
<Ionicons name="ios-camera-reverse-outline" size={64} color="#fff" />
</TouchableOpacity>
<TouchableOpacity
style={styles.captureButton}
onPress={() => takePicture()}>
<Ionicons name="ios-camera" size={64} color="#fff" />
</TouchableOpacity>
</View>
</Camera>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
alignItems: 'center',
justifyContent: 'center',
},
camera: {
width: screenWidth,
height: screenWidth + 200,
},
cameraControls: {
width: '100%',
height: '100%',
alignItems: 'center',
justifyContent: 'space-evenly',
flexDirection: 'row',
paddingHorizontal: 16,
paddingVertical: 16,
},
flipButton: {
alignSelf: 'flex-end',
},
captureButton: {
alignSelf: 'flex-end',
},
text: {
fontSize: 14,
},
});
export default CameraScreen;
import React, { useContext } from 'react';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import { Timestamp } from 'firebase/firestore';
import ToggleDarkMode from '../components/DarkModeToggle';
import Header from '../components/Header';
import HomeScreen from '../screens/HomeScreen';
import PostDetailsScreen from '../screens/PostDetailsScreen';
import Signup from '../screens/SignupScreen';
import SigninScreen from '../screens/SigninScreen';
import { AuthContext } from '../utils/AuthStateListener';
import ResetPassword from '../screens/ResetPassword';
import HeaderNavigateBackButton from '../components/HeaderNavigateBackButton';
import UserDetailsScreen from '../screens/UserDetailsScreen';
import CameraScreen from '../screens/CameraScreen';
export type RootStackParamList = {
Home: undefined;
Signup: undefined;
Signin: undefined;
ResetPassword: undefined;
UserDetails: undefined;
Camera: {
param: string;
};
Details: {
post: {
title: string;
description: string;
date: Timestamp;
location: string;
likes: number;
};
};
};
const Stack = createNativeStackNavigator<RootStackParamList>();
// Functions to prevent eslint error of declaring component inside parent component
function ToggleDarkModeComponent() {
return <ToggleDarkMode />;
}
function HeaderComponent() {
return <Header />;
}
function NavigateBackButtonComponent() {
return <HeaderNavigateBackButton />;
}
function RootStack() {
const { currentUser } = useContext(AuthContext);
return (
<Stack.Navigator
initialRouteName="Home"
screenOptions={{
headerStyle: { backgroundColor: '#86efac' },
headerRight: ToggleDarkModeComponent,
}}>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
headerTitle: HeaderComponent,
}}
/>
{!currentUser ? (
<>
<Stack.Screen
name="Signin"
component={SigninScreen}
options={{
headerLeft: NavigateBackButtonComponent,
}}
/>
<Stack.Screen
name="Signup"
component={Signup}
options={{
headerLeft: NavigateBackButtonComponent,
}}
/>
<Stack.Screen
name="ResetPassword"
component={ResetPassword}
options={{
headerLeft: NavigateBackButtonComponent,
}}
/>
</>
) : (
<>
<Stack.Screen
name="Details"
component={PostDetailsScreen}
options={{
headerLeft: NavigateBackButtonComponent,
}}
/>
<Stack.Screen
name="UserDetails"
component={UserDetailsScreen}
options={{
headerLeft: NavigateBackButtonComponent,
}}
/>
<Stack.Screen
name="Camera"
component={CameraScreen}
options={{
headerLeft: NavigateBackButtonComponent,
}}
/>
</>
)}
</Stack.Navigator>
);
}
export default RootStack;
Related
Well, I made a presentation onboarding screen, as soon as the user opens the app, the screen is shown, but I want this to be saved using AsyncStorage, if I close and open the app, the onboarding screen is not shown, but the screen of login.
I did all the code but nothing happens and the screen is displayed every time I close and open the app, I don't know what I'm doing wrong, code below.
App.js
import React, { useEffect, useState } from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import {AuthProvider} from './src/providers/Auth';
import AsyncStorage from '#react-native-async-storage/async-storage';
import { OnBoarding } from './src/screen/OnBoarding';
import { SignIn } from './src/screen/SignIn';
import { HomeScreen } from './src/screen/HomeScreen';
import { ActivityIndicator } from 'react-native';
const Stack = createNativeStackNavigator();
const Loading = () => {
return (
<View>
<ActivityIndicator size="large" />
</View>
);
}
export function App(){
const [loading, setLoading] = useState(true);
const [viewedOnboarding, setViewedOnboarding] = useState(false);
useEffect(() => {
checkOnBoarding();
}, [])
const checkOnBoarding = async () => {
try{
const value = await AsyncStorage.getItem('#viewedOnboarding');
if(value !== null){
setViewedOnboarding(true);
}
console.log(value);
}catch(err) {
console.log('Error #checkOnboarding: ', err);
}finally {
setLoading(false)
}
}
return (
<AuthProvider>
<NavigationContainer>
<Stack.Navigator
initialRouteName={loading ? <Loading /> : viewedOnboarding ? <HomeScreen /> : <OnBoarding />}
screenOptions={
{headerShown: false}
}
>
<Stack.Screen
name="SignIn"
component={SignIn}
/>
</Stack.Navigator>
</NavigationContainer>
</AuthProvider>
);
}
Onboarding.js
import React, { useEffect, useState } from 'react';
import {Text, View, StyleSheet, Image, TouchableOpacity, Button} from 'react-native';
import AppIntroSlider from 'react-native-app-intro-slider';
import Icon from 'react-native-vector-icons/FontAwesome';
import AsyncStorage from '#react-native-async-storage/async-storage';
const slides = [
{
key: 1,
title: 'Only Books Can Help You',
text: 'Books can help you to increase your knowledge and become more successfully.',
image: require('../../assets/imagem1.png'),
},
{
key: 2,
title: 'Learn Smartly',
text: 'It’s 2022 and it’s time to learn every quickly and smartly. All books are storage in cloud and you can access all of them from your laptop or PC.',
image: require('../../assets/imagem2.png'),
},
{
key: 3,
title: 'Learn Smartly',
text: 'It’s 2022 and it’s time to learn every quickly and smartly. All books are storage in cloud and you can access all of them from your laptop or PC.',
image: require('../../assets/imagem2.png'),
},
];
export function OnBoarding(){
function renderItem({item}){
return (
<View style={styles.container}>
<Image style={styles.image} source={item.image} />
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.content}>{item.text}</Text>
</View>
);
}
function _renderPrevButton() {
return (
<View style={styles.buttonCircle}>
<Icon
name="angle-left"
color="#000"
size={40}
/>
</View>
);
};
function _renderNextButton() {
return (
<View style={styles.buttonCircle}>
<Icon
name="angle-right"
color="#000"
size={40}
/>
</View>
);
};
const onPressFinish = async () => {
try{
await AsyncStorage.setItem('#viewedOnboarding', 'true')
navigation.navigate('SignIn');
}catch(err) {
console.log("Error #setitem ", err);
}
};
const renderDoneButton = () => {
return (
<TouchableOpacity onPress={onPressFinish}>
<Text style={{color: "#000"}}>Done</Text>
</TouchableOpacity>
);
};
return (
<AppIntroSlider
data={slides}
renderItem={renderItem}
keyExtractor={item => item.key}
renderPrevButton={_renderPrevButton}
renderNextButton={_renderNextButton}
renderDoneButton={renderDoneButton}
showPrevButton
showDoneButton
dotStyle={{backgroundColor: '#9D9D9D'}}
activeDotStyle={{backgroundColor: '#DE7773'}}
/>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: "#fff"
},
title: {
color: "#292B38",
fontSize: 24,
fontWeight: 'bold',
marginTop: 50
},
content: {
color: "#4D506C",
textAlign: 'center',
padding: 25,
lineHeight: 18
},
image: {
width: 300,
height: 300,
},
button: {
color: "#000",
backgroundColor: "transparent"
}
})
Probably because of the async nature of AsyncStore when you open your app first you init viewedOnboarding with false, then start reading storage and then you check is onboarding done or not. And of course because storage is async first time you check in Navigation condition you always get
I have modified your checkOnBoarding function.
Here is the code:
const checkOnBoarding = () => {
try{
AsyncStorage.getItem('#viewedOnboarding').then(value => {
if(value !== null){
setViewedOnboarding(true);
}
console.log(value);
})
}catch(err) {
console.log('Error #checkOnboarding: ', err);
}finally {
setLoading(false)
}
}
I'm not sure what happened, but I had to clean/rebuild gradle from a version issue with react-native-screens, and when I finally got everything back and working, the screens don't show on Android, but they work exactly as they did before with the issue. I'm not even sure how or where to look. I know it's the Drawer Navigator because if I pass a view instead it will show. Keep in mind - before I had the gradle issue, EVERYTHING worked - which is why I'm so stumped.
Here is my code:
App.tsx
import 'react-native-gesture-handler';
import React, { useEffect, useRef, useState } from 'react';
import { NavigationContainer, DefaultTheme, DarkTheme, LinkingOptions, NavigatorScreenParams, useNavigationContainerRef } from '#react-navigation/native';
import { createNativeStackNavigator, NativeStackHeaderProps } from '#react-navigation/native-stack';
import { Platform, StatusBar, View } from 'react-native';
import { enableScreens } from 'react-native-screens';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import Orientation from 'react-native-orientation-locker';
import OneSignal from 'react-native-onesignal';
import analytics from '#react-native-firebase/analytics';
import {
StyleSheet
} from 'react-native';
import MenuButton from './src/components/layout/MenuButton';
// Import pages and components
import { colors } from './src/styles/colorPalette';
import MainHeader from './src/components/headers/MainHeader';
import DrawerNavigator from './src/navigators/DrawerNavigator';
import ThemeContextProvider, { useTheme } from './src/contexts/ThemeContext';
import AuthContextProvider from './src/contexts/AuthContext'
import { useAuth } from './src/contexts/AuthContext';
import LoginRegister from './src/components/LoginRegister';
import EmailPhoneCollector from './src/components/EmailPhoneCollector';
import Search from './src/components/Search';
import SearchHeader from './src/components/headers/SearchHeader'
import DetailView from './src/components/DetailView';
import FullScreenLoader from './src/components/FullScreenLoading';
// Import theme and styling files
import { testAppDefaultTheme, testAppDefaultDarkTheme } from './src/styles/theme';
import Home from './src/pages/home';
// Globals (fonts, etc)
const sequel100Wide = Platform.OS == 'ios' ? 'Sequel100Wide-65' : 'Sequel100Wide-85'
type DrawerParamsList = {
Home: undefined
Videos: undefined
News: undefined
Podcasts: undefined
}
type RootStackParamList = {
Drawer: NavigatorScreenParams<DrawerParamsList>
DetailView: undefined
Search: undefined
config?: undefined
}
const Stack = createNativeStackNavigator()
enableScreens()
export default function App(props: any) {
// To override login functionality
const bypassLogin = false
const clearOnRestart = false
const CustomApp = () => {
const { clearID, logout, user, emailSMSId, timeExceeded } = useAuth()
const { darkMode, showNavBar } = useTheme()
const navigationRef = useNavigationContainerRef()
const routeNameRef = useRef<string | undefined>('')
// Where we display pre-prompt on login and iOS platform
const checkIosSoftPrompt = async () => {
const deviceState = await OneSignal.getDeviceState()
if (!deviceState?.isSubscribed) {
OneSignal.addTrigger('ios_prompt', 'true')
}
}
const linking: LinkingOptions<RootStackParamList> = {
prefixes: ['testApp://', 'https://testApp.com', 'https://*.testApp.com'],
config: {
screens: {
Drawer: {
initialRouteName: 'Home',
screens: {
Videos: 'tv',
News: 'news',
Podcasts: 'podcasts',
Home: ''
}
},
DetailView: {
path: ':category?/:type/:slug',
},
Search: {
path: 'search',
}
}
},
}
const HeaderComponent = (props: any) => <MainHeader {...props} />
useEffect(() => {
if (bypassLogin && user) logout()
/* if ((bypassLogin || (user || emailSMSId)) && Platform.OS == 'ios') {
checkIosSoftPrompt()
} */
// More aggressive targeting for Pre-Prompt message
if (Platform.OS === 'ios' && (emailSMSId)) checkIosSoftPrompt();
}, [bypassLogin, user, emailSMSId])
useEffect(() => {
if (clearOnRestart) clearID()
}, [])
const checkRoutes = async () => {
const previousRouteName = routeNameRef.current;
const currentRouteName = navigationRef.current?.getCurrentRoute()?.name ?? '';
if (previousRouteName !== currentRouteName) {
await analytics().logScreenView({ screen_name: currentRouteName });
}
// Save the current route name for later comparison
routeNameRef.current = currentRouteName;
}
const setRefs = () => {
routeNameRef.current = navigationRef.current?.getCurrentRoute()?.name;
}
return(
<SafeAreaProvider>
<StatusBar translucent barStyle={darkMode ? 'light-content' : 'dark-content'} backgroundColor="transparent" />
<NavigationContainer
ref={navigationRef}
onReady={ setRefs }
onStateChange={ checkRoutes }
theme={darkMode ? testAppDefaultDarkTheme : testAppDefaultTheme}
linking={linking}
fallback={<FullScreenLoader message='Loading...'/>}
>
{ bypassLogin || !!user || emailSMSId || (timeExceeded != null && !timeExceeded ) ?
<Stack.Navigator
screenOptions={{ header: HeaderComponent, contentStyle:{ backgroundColor: darkMode ? colors.background.darkHeaderBg : colors.background.lightSubdued } }}
>
<Stack.Screen name="Drawer" component={DrawerNavigator} options={{ headerShown: false }} />
{ /* Common screens outside flow of Drawer Navigator */}
<Stack.Screen name="Search" component={Search}
options={{
header: SearchHeader,
headerShown: false,
animation: 'fade_from_bottom',
headerTitle: '',
headerStyle: {
backgroundColor: darkMode ? colors.primary.blue7 : '#fff',
},
contentStyle: {
backgroundColor: darkMode ? colors.primary.blue7 : '#fff',
}
}}
/>
<Stack.Screen name="DetailView" component={DetailView}
options={{
presentation: 'containedModal',
headerShown: false,
contentStyle: {
backgroundColor: darkMode
? colors.primary.blue7
: '#fff',
},
orientation:
Platform.OS === 'ios' ? 'portrait_up' : undefined,
headerBackButtonMenuEnabled: false,
headerBackVisible: false,
headerTitle: ''
}}
/>
</Stack.Navigator>
:
<EmailPhoneCollector />
}
</NavigationContainer>
</SafeAreaProvider>
)
}
// Main app useEffect hook
useEffect(() => {
// Lock orientation with Orientation (rather than React Navigation)
Orientation.lockToPortrait()
// OneSignal initializer
OneSignal.setLogLevel(6, 0);
OneSignal.setAppId("99a9aeab-f82e-49e8-bc40-8f471c7bf1f9");
/* //Prompt for push on iOS
OneSignal.promptForPushNotificationsWithUserResponse((response: any) => { // Replace this for pre-prompt flow
console.log("Prompt response:", response);
}); */
//Method for handling notifications received while app in foreground
OneSignal.setNotificationWillShowInForegroundHandler(notificationReceivedEvent => {
console.log("OneSignal: notification will show in foreground:", notificationReceivedEvent);
let notification = notificationReceivedEvent.getNotification();
console.log("notification: ", notification);
const data = notification.additionalData
console.log("additionalData: ", data);
// Complete with null means don't show a notification.
notificationReceivedEvent.complete(notification);
});
//Method for handling notifications opened
OneSignal.setNotificationOpenedHandler((notification) => {
// console.log("OneSignal: notification opened:", notification);
});
}, [])
return (
<AuthContextProvider>
<ThemeContextProvider>
<CustomApp />
</ThemeContextProvider>
</AuthContextProvider>
);
}
const styles = StyleSheet.create({
drawerIcon: {
width: 30,
height: 30,
}
})
DrawerNavigator:
import React, { useEffect, useState } from 'react';
import { Linking, View, TouchableOpacity, Platform } from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import { SafeAreaView } from 'react-native-safe-area-context';
import FontAwesome from 'react-native-vector-icons/FontAwesome';
import { Divider, Switch } from 'react-native-elements';
import { DrawerContentScrollView, DrawerItem, DrawerItemList } from '#react-navigation/drawer';
import { colors } from '../../styles/colorPalette';
import { useTheme } from '../../contexts/ThemeContext';
import HomeLogo from './HomeLogo';
import { Button } from 'react-native-elements/dist/buttons/Button';
import Text from '../TextComponents'
// Contexts
import { useAuth } from '../../contexts/AuthContext'
import VersionCheck from 'react-native-version-check';
// Globals (fonts, etc)
const sequel100Wide6585 = Platform.OS == 'ios' ? 'Sequel100Wide-65' : 'Sequel100Wide-85'
export default function DrawerWrapper(props: any) {
const darkMode = useTheme().darkMode;
const { navigation, route, state } = props;
const { appInfo, user, logout } = useAuth();
const goToStore = async () => {
if (Platform.OS == 'ios') {
VersionCheck.getAppStoreUrl({ appID: "1609502829" })
.then(res => {
if (res) {
Linking.canOpenURL(res)
.then(supported => {
console.log('the link: ', res)
supported && Linking.openURL(res)
},
(err) => console.log('Error opening link: ', err)
)
}
})
}
else if (Platform.OS == 'android') {
VersionCheck.getPlayStoreUrl({ packageName: "com.testAppmobile.testApp" })
.then(res => {
if (res) {
Linking.canOpenURL(res)
.then(supported => {
console.log('the link: ', res)
supported && Linking.openURL(res)
},
(err) => console.log('Error opening link: ', err)
)
}
})
}
}
return (
<SafeAreaView style={{flex: 1}} edges={[]}>
<View style={{ flexDirection: "row", padding: 20, alignItems: 'center', alignContent: 'center', marginTop: Platform.OS == 'android' ? 20 : 0 }}>
<TouchableOpacity onPress={() => navigation.closeDrawer()}>
<Icon name="md-close" size={40} color={darkMode ? colors.primary.blue2 : 'black'}/>
</TouchableOpacity>
<HomeLogo />
<TouchableOpacity style={{flexGrow: 1, display: 'none'}}>
<FontAwesome style={{textAlign: 'right'}} name="cog" size={40} color={darkMode ? colors.primary.blue2 : 'black'} />
</TouchableOpacity>
</View>
{ user && <View style={{flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-end', paddingHorizontal: 20, paddingBottom: 10}}>
<Text style={{marginRight: 20}}>{user?.username ?? user?.name ?? user?.email}</Text>
<Button title="Logout" titleStyle={{fontSize: 12, textTransform: 'uppercase'}} style={{backgroundColor: 'red'}} onPress={() => logout()}/>
</View>}
<Divider />
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} labelStyle={{fontFamily: sequel100Wide6585}}/>
<DrawerItem
onPress={() => Linking.openURL('https://testApp.com')}
activeBackgroundColor={darkMode ? colors.primary.blue5 : colors.primary.blue1} {...props}
label="testApp.com"
labelStyle={{fontFamily: sequel100Wide6585,
fontWeight: 'normal',
fontSize: 13,
paddingVertical: 15,
color: darkMode ? colors.primary.blue2 : '#000'}} icon={({focused, size, color}) => <FontAwesome size={30} style={{color: darkMode ? '#98D4FF' : "#000", marginLeft: -5}} name="external-link-square"/>}
/>
</DrawerContentScrollView>
<View style={{flexDirection: 'row', alignItems: 'center'}}>
{ !!appInfo?.appVersion && <Text variant="h5" style={{ color: darkMode ? '#fff' : '#000', paddingLeft: 10 }}>{`v${appInfo.appVersion}`}</Text> }
{ !!appInfo?.needsUpdate && <Button onPress={goToStore} buttonStyle={{borderRadius: 10, backgroundColor: 'green', marginLeft: 10}} title={<Text variant="h5" style={{color: '#fff'}}>Update Available</Text>} />}
</View>
</SafeAreaView>
)
}
My Home Stack:
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { ActivityIndicator, FlatList, Platform, RefreshControl, RefreshControlProps, StyleSheet, TouchableOpacity, View } from 'react-native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import { useHeaderHeight } from '#react-navigation/elements';
import PostCard from '../../components/PostCard';
import FullScreenLoader from '../../components/FullScreenLoading';
import { colors } from '../../styles/colorPalette';
import { useTheme } from '../../contexts/ThemeContext';
import LiveVideoCard from '../../components/LiveVideoCard';
import HorizontalCarousel from '../../components/HorizontalCarousel';
import { KeyboardAwareFlatList, KeyboardAwareFlatListProps } from 'react-native-keyboard-aware-scroll-view';
import FooterStack from '../../components/layout/FooterStack';
import PersonDetails from '../../components/PersonDetails';
import Text from '../../components/TextComponents';
import NoResults from '../../components/NoResults';
import Animated, { enableLayoutAnimations, FadeIn, Layout } from 'react-native-reanimated';
const Home = (props: any) => {
const { navigation } = props;
const darkMode = useTheme().darkMode;
const headerHeight = useHeaderHeight();
const Stack = createNativeStackNavigator();
const styles = StyleSheet.create({
button: {
backgroundColor: 'transparent',
borderRadius: 50,
flex: 1,
width: 120,
height: 40,
marginHorizontal: 10,
},
container: {
flexGrow: 1,
flex: 1,
},
image: {
height: 300,
resizeMode: 'contain',
},
logo: {
height: 100,
width: '100%',
resizeMode: 'contain',
},
posts: {
borderStyle: 'solid',
borderWidth: 5,
borderColor: 'red',
},
subtitle: {
fontSize: 12,
marginTop: 3,
color: '#777777',
flex: 1,
},
title: {
fontSize: 24,
fontWeight: 'bold',
flex: 1,
},
videoContainer: {
position: 'relative',
height: 300,
marginVertical: 25,
flex: 1,
},
watchMore: {
flexDirection: 'row',
justifyContent: 'space-between',
padding: 20,
borderTopWidth: 1,
borderBottomWidth: 1,
borderStyle: 'solid',
borderColor: 'rgba(0,0,0,0.25)',
},
});
const HomeHeader = React.memo(() => {
const darkMode = useTheme().darkMode
return (
<>
<LiveVideoCard />
<View>
<View style={[styles.watchMore, { backgroundColor: darkMode ? colors.background.darkBg : '#fff' }]}>
<Text style={{color: darkMode ? '#fff' : '#000', fontSize: 15, fontWeight: 'bold'}}>
Watch
</Text>
<TouchableOpacity onPress={() => navigation.navigate('Videos')}>
<Text style={{color: darkMode ? colors.primary.blue2 : colors.primary.blueMain, fontWeight: 'bold', fontSize: 14}}>
Watch More
</Text>
</TouchableOpacity>
</View>
<HorizontalCarousel />
</View>
</>
)
}, (prev, next) => {
return true
})
const FooterItem = React.memo(() => {
const darkMode = useTheme().darkMode
return(
<View style={{paddingBottom: 100}}>
<Text variant="h6" style={{alignSelf: 'center', color: darkMode ? '#fff' : '#000'}}>Loading more posts...</Text>
<ActivityIndicator size={'large'} color={darkMode ? '#fff' : '#777'} style={{ paddingVertical: 20 }} />
</View>
)}, () => true)
const renderItem = useCallback((props: any) => {
return (
<PostCard data={props.item.node} categoryOverlay="Home" index={props.index} navigation={navigation} />
)
}, [])
const CustomRefreshControl = (props: RefreshControlProps) => {
const darkMode = useTheme().darkMode
return <RefreshControl
progressViewOffset={headerHeight}
refreshing={props.refreshing}
onRefresh={props.onRefresh}
tintColor={darkMode ? '#fff' : '#000'}
titleColor={darkMode ? '#fff' : '#000'}
/>
}
const HomeComponent = useCallback(() => {
const [data, setData] = useState([]);
const flatlistRef = useRef<FlatList>(null)
const onEndReachedCalledDuringMomentum = useRef(false)
const [loading, setLoading] = useState(true);
const [pageNum, setPageNum] = useState(0);
const [refreshing, setRefreshing] = useState(false);
const controller = new AbortController()
const fetchData = async (newPageNum?: number) => {
if (newPageNum !== 0 && newPageNum == pageNum) return console.log("They're the same")
const timer = setTimeout(() => {
controller?.abort()
}, 10000)
try {
// Here is where we try to fetch data
const videos = await fetch(
`https://home.testApp.com/m/shows/latest?page=${newPageNum ?? pageNum}`,
{
method: 'GET',
mode: 'cors',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': 'testApp/2.0'
},
signal: controller.signal
}
);
const json = await videos
.json()
.catch((e: any) =>
console.log('this is an error in the json output: ', e),
);
clearTimeout(timer)
setData(data.concat(json.nodes));
if (!!newPageNum) setPageNum(newPageNum)
} catch (e) {
// Manage the errors here
console.log('Error returning from testApp videos: ', e);
} finally {
if (newPageNum === 0 || !!loading) setLoading(false)
if (!!refreshing) setRefreshing(false)
controller?.abort()
}
};
const keyExtractor = (item: any, idx: number) => {
return `${item.node.nid}-${idx}`
};
const handleLoadMore = async (props: any) => {
if (onEndReachedCalledDuringMomentum.current === true) return console.log('momentum blocked')
fetchData(pageNum + 1)
onEndReachedCalledDuringMomentum.current = true
}
const handleRefresh = async () => {
setRefreshing(true);
setLoading(true);
setData([]);
fetchData(0);
setRefreshing(false)
}
const _onMomentumScrollBegin = () => {
onEndReachedCalledDuringMomentum.current = false
}
useEffect(() => {
// This is where we fetch the data from the website
if (loading) fetchData();
// Pass ref to navigation object for scrollTo logic
navigation.setOptions({
homeScrollRef: flatlistRef
})
return () => controller?.abort()
}, []);
if (!!loading) return <FullScreenLoader message={'Loading Home posts'} />
return (
<FlatList
ref={flatlistRef}
contentContainerStyle={{marginTop: headerHeight ?? undefined }}
style={{flex: 1 }}
scrollIndicatorInsets={{ right: 1 }}
showsVerticalScrollIndicator={false}
initialNumToRender={5}
maxToRenderPerBatch={15}
windowSize={15}
data={data}
keyExtractor={keyExtractor}
ListEmptyComponent={<NoResults />}
ListHeaderComponent={HomeHeader}
ListFooterComponent={FooterItem}
renderItem={renderItem}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.5}
onMomentumScrollBegin={_onMomentumScrollBegin}
scrollEventThrottle={16}
refreshControl={<CustomRefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
/>}
/>
)
}, [])
return (
<Stack.Navigator screenOptions={{ orientation: Platform.OS === 'ios' ? 'portrait_up' : 'portrait_up', headerShown: false, contentStyle: { backgroundColor: darkMode ? colors.background.darkHeaderBg : colors.background.lightSubdued }}}>
<Stack.Screen name="HomeView" options={{title: 'Home' }} component={HomeComponent} />
<Stack.Screen name="FooterLinks" component={FooterStack} />
</Stack.Navigator>
);
}
export default Home
try disable animation on android
import { enableLayoutAnimations } from "react-native-reanimated";
if (Platform.OS === 'android') {
enableLayoutAnimations(false);
}
I wrote the below code to make multiple side drawer but unluckily I am not able.
Here open the left side drawer easily but the right-side drawer is not open. for Analyze purpose header.js file when I used here onPress={() => navigation.openDrawer()} then open left side drawer and I also used in second drawer element onPress={() => navigation.openDrawer()} also open left ride drawer but I want to open right side drawer.
What should I do?
app.js file:
import * as React from 'react';
import {View, Button,StyleSheet, Alert, Text, ActivityIndicator, Image} from 'react-native';
import {NavigationContainer} from '#react-navigation/native';
import {createStackNavigator} from '#react-navigation/stack';
import {createDrawerNavigator} from '#react-navigation/drawer';
import DrawerContent from './screens/common/DrawerContent';
import SearchDrawer from './screens/common/SearchDrawer';
import Registration from './screens/authentication/Registration';
import Login from './screens/authentication/Login';
import ForgotPassword from './screens/authentication/ForgotPassword';
import ChangePassword from './screens/authentication/ChangePassword';
import Dashboard from './screens/user/Dashboard';
import Notifications from './screens/user/Notifications';
import ProfileStep from './screens/user';
import Shipment from './screens/user/shipment';
// import SplashScreen from 'react-native-splash-screen';
// import {LogBox} from 'react-native';
import {Provider} from 'react-redux';
import AsyncStorage from '#react-native-community/async-storage';
import {store, getAsyncStorage} from './redux/store';
import messaging from '#react-native-firebase/messaging';
const Stack = createStackNavigator();
const DrawerR = createDrawerNavigator();
const DrawerL = createDrawerNavigator();
function User(props) {
const [userInfo, setUserInfo] = React.useState('');
React.useEffect(() => {
getUserInfo();
}, []);
const getUserInfo = async () => {
let userInfo = await AsyncStorage.getItem('userInfo');
if (userInfo) {
store.dispatch(getAsyncStorage());
userInfo = JSON.parse(userInfo);
setUserInfo(userInfo);
}
};
return (
<DrawerL.Navigator
drawerContent={(props) => (
<DrawerContent {...props} size="22" data={userInfo} />
)}
// drawerPosition="right"
// drawerContent={(props) => (
// <SearchDrawer {...props} size="22" />
// )}
>
{props.route.params !== undefined && props.route.params.completed ? (
<>
<DrawerL.Screen name="Dashboard" component={Dashboard} />
<DrawerL.Screen name="ProfileStep" component={ProfileStep} />
</>
) : (
<>
<DrawerL.Screen name="ProfileStep" component={ProfileStep} />
<DrawerL.Screen name="Dashboard" component={Dashboard} />
</>
)}
<DrawerL.Screen name="Shipment" component={Shipment} />
</DrawerL.Navigator>
);
}
function User1(props){
const [userInfo1, setUserInfo1] = React.useState('')
React.useEffect(() => {
getUserInfo1();
}, []);
const getUserInfo1 = async () => {
let userInfo1 = await AsyncStorage.getItem('userInfo');
if (userInfo1) {
store.dispatch(getAsyncStorage());
userInfo1 = JSON.parse(userInfo1);
setUserInfo1(userInfo1);
}
};
return (
<DrawerR.Navigator
drawerPosition="right"
drawerContent={(props) => (
<SearchDrawer {...props} size="22" data={userInfo1}/>
)}
>
</DrawerR.Navigator>
);
}
function App() {
React.useEffect(() => {
const unsubscribe = messaging().onMessage(async (remoteMessage) => {
Alert.alert('A new FCM message arrived!', JSON.stringify(remoteMessage));
});
return unsubscribe;
}, []);
return (
<Provider store={store}>
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerShown: false,
}}>
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Registration" component={Registration} />
<Stack.Screen name="ForgotPassword" component={ForgotPassword} />
<Stack.Screen name="ChangePassword" component={ChangePassword} />
<Stack.Screen name="User" component={User} />
<Stack.Screen name="User1" component={User1} />
<Stack.Screen name="Notifications" component={Notifications} />
</Stack.Navigator>
</NavigationContainer>
</Provider>
);
}
export default App;
DrawerContent.js
import React,{useEffect} from 'react';
import {View, StyleSheet, Alert, Text, ActivityIndicator, Image} from 'react-native';
import {
Avatar,
Title,
Caption,
Drawer,
TouchableRipple,
Divider,
Switch,
} from 'react-native-paper';
import {DrawerContentScrollView, DrawerItem} from '#react-navigation/drawer';
import AsyncStorage from '#react-native-community/async-storage';
import {connect} from 'react-redux';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import {setTheme} from './../../redux/actions/config';
import {getThemeColors} from '../../global/themes';
import {API_URL} from './../../global/config';
import { Linking } from 'react-native';
// import {bindActionCreators} from 'redux';
var pkg = require('./../../package.json');
function DrawerContent(props) {
const [loading, setLoading] = React.useState(false);
const [profileStatus, setProfileStatus] = React.useState({});
const [whitelabel, setIfWhiteLabel] = React.useState('')
// const [isDarkMode, setIsDarkMode] = React.useState(false);
let userInfo = props.data;
const logout = () => {
Alert.alert(
'Are you sure?',
'You want to Logout',
[
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Yes',
onPress: () => {
setLoading(true);
console.log(`${API_URL}disableDevice`);
fetch(`${API_URL}disableDevice`, {
method: 'post',
body: JSON.stringify({user_id: userInfo.id}),
})
.then((res) => res.json())
.then(async (res) => {
if (!res.status) {
setLoading(false);
alert('something went wrong!!');
} else {
try {
await AsyncStorage.removeItem('userInfo');
await AsyncStorage.removeItem('profileStatus');
props.navigation.reset({
index: 0,
routes: [{name: 'Login'}],
});
} catch (exception) {
setLoading(false);
return false;
}
}
})
.catch((e) => {
setLoading(false);
console.log(e);
});
},
},
],
{cancelable: false},
);
};
const onThemeChange = async () => {
AsyncStorage.setItem(
'themeMode',
props.theme === 'default' ? 'dark' : 'default',
);
props.onSelectTheme(props.theme === 'default' ? 'dark' : 'default');
fetch(`${API_URL}toggleDarkMode`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
user_id: userInfo.id,
dark_mode: props.theme === 'default' ? 1 : 0,
}),
})
.then((res) => res.json())
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
};
const {colors} = props;
React.useEffect(() => {
var profileStatus = props.profileStatus;
if (profileStatus) {
profileStatus = JSON.parse(profileStatus);
setProfileStatus(profileStatus);
}
checkIfWhiteLabel()
}, [props.profileStatus]);
useEffect(() => {
checkIfWhiteLabel()
}, [])
const checkIfWhiteLabel = async () =>{
let iftru = await AsyncStorage.getItem('whiteLabel');
if(iftru == null){
setIfWhiteLabel('false')
}else{
setIfWhiteLabel(iftru)
}
}
return (
<>
<View style={{flex: 1, backgroundColor: colors.drawerBackground}}>
<DrawerContentScrollView {...props}>
<View style={styles.drawerContent}>
<View style={styles.tinyLogoDiv}>
<Image
style={styles.tinyLogo}
source={require('../../assets/logo.png')}
/>
</View>
<TouchableRipple
style={styles.userInfoSection}
onPress={() =>
props.navigation.navigate('ProfileStep', {status: 'TRUE'})
}
rippleColor="rgba(0, 0, 0, .32)">
<View style={{flexDirection: 'row'}}>
<Avatar.Image
source={require('../../assets/avater.png')}
size={50}
/>
<View style={{marginLeft: 15, flexDirection: 'column'}}>
<Title style={styles.title(colors.textColor)}>
{profileStatus.username ?? userInfo.name}
</Title>
<Caption style={styles.caption(colors.textColor)}>
{profileStatus.email ?? userInfo.email}
</Caption>
</View>
<View
style={{
justifyContent: 'center',
marginLeft: 'auto',
paddingRight: 7,
}}>
<Icon
name="account-edit"
size={25}
color={colors.textColor}
/>
</View>
</View>
</TouchableRipple>
<Divider />
<Drawer.Section style={styles.drawerSection}>
<DrawerItem
icon={() => (
<Icon name="home" size={30} color={colors.textColor} />
)}
labelStyle={{color: colors.textColor}}
label="Home"
onPress={() => {
if (profileStatus.type || profileStatus.type === undefined) {
alert('Complete Your profile first');
} else {
props.navigation.navigate('Dashboard');
}
}}
/>
<DrawerItem
icon={() => (
<Icon
name="text-box-plus"
size={30}
color={colors.textColor}
/>
)}
label="Parcel"
labelStyle={{color: colors.textColor}}
onPress={() => {
if (profileStatus.type || profileStatus.type === undefined) {
alert('Complete Your profile first');
} else {
props.navigation.navigate('Shipment');
}
}}
/>
</Drawer.Section>
</View>
</DrawerContentScrollView>
<Drawer.Section style={styles.bottomDrawerSection}>
<DrawerItem
style={{paddingBottom: 0}}
icon={() =>
loading ? (
<ActivityIndicator size={30} color={colors.primaryColor} />
) : (
<Icon name="exit-to-app" size={30} color={colors.textColor} />
)
}
label={'Logout'}
labelStyle={{color: colors.textColor}}
onPress={() => logout()}
/>
</Drawer.Section>
<View
style={{
marginBottom: 5,
alignItems: 'center',
}}>
<Text style={{opacity: 0.8, color: colors.textColor}}>
V - {pkg.version}
</Text>
</View>
</View>
</>
);
}
const mapStateToProps = (state) => {
let imagePlaceholder = JSON.parse(state.brand);
if (imagePlaceholder) {
imagePlaceholder = imagePlaceholder.default.avatar;
} else {
imagePlaceholder = '';
}
var theme = getThemeColors(state.theme);
return {
theme: state.theme,
colors: theme,
profileStatus: state.profileStatus,
imagePlaceholder: imagePlaceholder,
};
// count: state.count,
};
export default connect(mapStateToProps, {
onSelectTheme: setTheme,
})(DrawerContent);
//
const styles = StyleSheet.create({
drawerContent: {
flex: 1,
},
userInfoSection: {
paddingLeft: 20,
paddingVertical: 10,
},
title: (color) => ({
fontSize: 16,
marginTop: 3,
fontWeight: 'bold',
color: color,
}),
caption: (color) => ({
fontSize: 12,
lineHeight: 14,
opacity: 0.8,
color: color,
marginLeft:-15,
}),
row: {
marginTop: 20,
flexDirection: 'row',
alignItems: 'center',
},
section: {
flexDirection: 'row',
alignItems: 'center',
marginRight: 15,
},
paragraph: {
fontWeight: 'bold',
marginRight: 3,
},
drawerSection: {
marginTop: 15,
},
bottomDrawerSection: {
// marginBottom: 15,
borderTopColor: '#ccc',
borderTopWidth: 0.2,
borderBottomColor: '#ccc',
borderBottomWidth: 0.2,
},
preference: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: 12,
paddingHorizontal: 16,
},
loading: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
opacity: 0.6,
justifyContent: 'center',
alignItems: 'center',
},
loadingText: {
fontSize: 20,
paddingTop: 20,
fontWeight: 'bold',
},
tinyLogoDiv:{
// width:100,
},
tinyLogo: {
width: 150,
height: 50,
// alignItems:'center',
marginLeft:70
},
});
SearchDrawer.js
import React from 'react';
import { View, StyleSheet, Text, Button,TextInput } from 'react-native';
import { DrawerContentScrollView} from '#react-navigation/drawer';
import { setTheme } from './../../redux/actions/config';
import { getThemeColors } from '../../global/themes';
import { connect } from 'react-redux';
function SearchDrawer(props) {
return (
<>
<View style={{ flex: 1, backgroundColor: '#ffffff' }}>
<DrawerContentScrollView {...props}>
<View>
<Text>This is input Field</Text>
<TextInput
label='Search here'
style={styles.input}
placeholder="Search By Order Id"
/>
<Button
onPress={() => alert('This is a button!')}
title="Info"
color="#fff"
/>
</View>
</DrawerContentScrollView>
</View>
</>
);
}
const mapStateToProps = (state) => {
let imagePlaceholder = JSON.parse(state.brand);
if (imagePlaceholder) {
imagePlaceholder = imagePlaceholder.default.avatar;
} else {
imagePlaceholder = '';
}
var theme = getThemeColors(state.theme);
return {
theme: state.theme,
colors: theme,
profileStatus: state.profileStatus,
imagePlaceholder: imagePlaceholder,
};
};
const styles = StyleSheet.create({
drawerSection: {
marginTop: 15,
},
})
export default connect(mapStateToProps, {
onSelectTheme: setTheme,
})(SearchDrawer);
Header.js
import React from 'react';
import {
// StyleSheet,
// TouchableOpacity,
// TouchableWithoutFeedback,
Text,
View, TouchableOpacity,
AppState, TextInput, StyleSheet
} from 'react-native';
import { DrawerItem } from '#react-navigation/drawer';
import { DrawerActions } from '#react-navigation/native';
import { useNavigation } from '#react-navigation/native';
import { Appbar, Badge } from 'react-native-paper';
// import {colors} from '../../global/themes';
import { FontAwesomeIcon } from '#fortawesome/react-native-fontawesome'
import { faCoffee } from '#fortawesome/free-solid-svg-icons'
import Icon from 'react-native-vector-icons/FontAwesome';
import { connect } from 'react-redux';
import { setTheme } from './../../redux/actions/config';
// import {bindActionCreators} from 'redux';
import { getThemeColors } from '../../global/themes';
// import {useState} from 'react';
import { API_URL } from '../../global/config';
import AsyncStorage from '#react-native-community/async-storage';
import Menu, { MenuItem } from 'react-native-material-menu';
import messaging from '#react-native-firebase/messaging';
import notifee, { EventType } from '#notifee/react-native';
const ContentTitle = ({ title, color }) => (
<Appbar.Content
title={
// <View style={{zIndex: 99999999}}>
<Text style={{ fontSize: 20, color: color, zIndex: 99999999 }}>
{' '}
{title}{' '}
</Text>
// </View>
}
style={{ marginLeft: -10 }}
/>
);
function Header(props) {
const navigation = useNavigation();
console.log('Pera Navigation', navigation)
const { colors } = props;
const [notificationCount, setNotificationCount] = React.useState(0);
const [showMenu, setShowMenu] = React.useState(false);
const [appState, setAppState] = React.useState(AppState.currentState);
const [actions, setActions] = React.useState([]);
const menu = React.useRef();
const getUserId = async () => {
let userInfo = await AsyncStorage.getItem('userInfo');
if (userInfo) {
userInfo = JSON.parse(userInfo);
return userInfo.id;
}
};
function getNotificationCount() {
getUserId().then((id) => {
console.log(`${API_URL}newNotification`);
fetch(`${API_URL}newNotification`, {
method: 'post',
body: JSON.stringify({ user_id: id }),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
})
.then((res) => res.json())
.then((res) => {
if (res.status) {
setNotificationCount(res.notification_count);
}
})
.catch((e) => {
console.log(e);
});
});
}
const _handleAppStateChange = (nextAppState) => {
getNotificationCount();
setAppState(nextAppState);
};
React.useEffect(() => {
AppState.addEventListener('change', (e) => _handleAppStateChange(e));
getNotificationCount();
notifee.createChannel({
id: 'custom-sound',
name: 'System Sound',
sound: 'notification__.mp3',
});
notifee.onForegroundEvent(({ type, detail }) => {
switch (type) {
case EventType.DISMISSED:
console.log('User dismissed notification', detail.notification);
break;
case EventType.PRESS:
let count = notificationCount;
setNotificationCount(0);
// alert('0 done');
navigation.navigate('Notifications', {
count: count,
screen: ' ',
});
break;
}
});
messaging().onMessage(async (remoteMessage) => {
// console.log('ok oko k');
await notifee.displayNotification({
title: remoteMessage.notification.title,
body: remoteMessage.notification.body,
android: {
channelId: 'custom-sound',
},
});
getNotificationCount();
});
messaging().onNotificationOpenedApp((remoteMessage) => {
let count = notificationCount;
setNotificationCount(0);
navigation.navigate('Notifications', {
count: count,
screen: 'header',
});
});
}, []);
React.useEffect(() => {
if (showMenu) {
menu.current.show();
}
}, [showMenu]);
return (
<Appbar.Header style={{ backgroundColor: colors.headerColor }}>
{props.back ? (
<Appbar.BackAction
onPress={() => (props.goBack ? props.goBack() : navigation.goBack())}
/>
) : (
<Appbar.Action
icon="menu"
onPress={() => navigation.openDrawer()}
size={28}
color={colors.textColor}
style={{ paddingLeft: 3 }}
/>
)}
<TouchableOpacity style={styles.searchSection}>
<TextInput
style={styles.input}
placeholder="Search By Order Id"
onPress={() =>navigation.openDrawer()}
/>
<Icon style={styles.searchIcon} name="search" size={20} color="#000" />
</TouchableOpacity>
{/* <TouchableOpacity onPress={() => navigation.openDrawer()}>
<Icon style={{marginLeft: 10}} name='menu'/>
</TouchableOpacity> */}
{/* <Button title='open the drawer' onPress={this.props.navigation.dispatch(DrawerActions.openDrawer('drawerOpenLeft'))}></Button>
<Button title='open the drawer right' onPress={this.props.navigation.dispatch(DrawerActions.openDrawer('drawerOpenRight'))}></Button> */}
<ContentTitle title={props.title} color={colors.textColor} />
{!props.hideNotification && (
<View>
{notificationCount > 0 && (
<Badge
visible={true}
size={16}
style={{ position: 'absolute', top: 10, right: 7, zIndex: 9999 }}>
<Text style={{ fontSize: 12 }}>{notificationCount}</Text>
</Badge>
)}
<Appbar.Action
icon="bell"
onPress={() => {
let count = notificationCount;
setNotificationCount(0);
navigation.navigate('Notifications', {
count: count,
screen: 'header',
});
}}
size={28}
color={colors.textColor}
style={{ paddingLeft: 3 }}
/>
</View>
)}
{props.selected && (
<>
<Appbar.Action
icon="delete"
onPress={() => props.onDeletePress()}
size={28}
color={colors.textColor}
style={{ left: 10 }}
/>
<Menu
ref={menu}
style={{ backgroundColor: colors.secondaryColor }}
onHidden={() => {
setShowMenu(false);
}}
button={
<Appbar.Action
icon="dots-vertical"
onPress={() => {
props.onActionPress().then((res) => {
console.log(res);
// if (res.status === 'TRUE') {
setShowMenu(res.status);
setActions(res.actions);
// } else {
// setShowMenu(false);
// }
});
}}
size={28}
color={colors.textColor}
/>
}>
{actions.length > 0 &&
actions.map((item, i) => {
return (
<>
{item.statusList !== undefined && (
<>
<MenuItem style={{ height: 40 }} disabled>
<Text style={{ color: colors.textLight }}>
{item.text}
</Text>
</MenuItem>
{item.statusList.map((list) => {
return (
<MenuItem
onPress={() => {
props.onMenuPress(item.type, list.VALUE);
menu.current.hide();
}}
key={i}>
<Text style={{ color: colors.textColor }}>
{' '}
{list.LABEL}
</Text>
</MenuItem>
);
})}
</>
)}
</>
);
})}
</Menu>
</>
)}
</Appbar.Header>
);
}
const mapStateToProps = (state) => {
var theme = getThemeColors(state.theme);
return { colors: theme };
};
export default connect(mapStateToProps, {
onSelectTheme: setTheme,
})(Header);
const styles = StyleSheet.create({
searchSection: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
searchIcon: {
padding: 10,
},
input: {
flex: 1,
height: 40,
padding: 10,
paddingLeft: 0,
backgroundColor: '#fff',
color: '#424242',
},
})
I don't know if I understood well but I need a little help.
I am learning react-native by reading some blogs and trying to replicate the concepts on a test application that uses Drawer (Home and About screens), Stack (Home -> Settings) and Tab (Home and About Screens) navigations. So far, so good.
I am using 'react-native-localize', 'i18n-js' and 'lodash.memoize' in order to make the app multi language. During its startup, the application successfully determines the current locale from the device settings and display the correct contents based on the locale.
However, I want to be able to change it dynamically by pressing the toggle language button on Settings screen. I don't know how to change the locale and make the whole application (drawer menu option names, bottom tab names and all screens text contents) re-render and display text from the new locale. Could someone help me, please?
App.js
import * as i18n from './src/utils/i18n';
const App = () => {
const [locale, setLocale] = useState(i18n.DEFAULT_LANGUAGE);
const localizationContext = useMemo(
() => ({
t: (scope, options) => i18n.t(scope, {locale, ...options}),
locale,
setLocale
}),
[locale],
);
const handleLocalizationChange = useCallback(
(newLocale) => {
const newSetLocale = i18n.setI18nConfig(newLocale);
setLocale(newSetLocale);
},
[locale],
);
useEffect(() => {
handleLocalizationChange();
RNLocalize.addEventListener('change', handleLocalizationChange);
return () => {
RNLocalize.removeEventListener('change', handleLocalizationChange);
};
}, []);
return (
<LocalizationContext.Provider value={localizationContext}>
<NavigationContainer>
<DrawerNavigator localizationChange={handleLocalizationChange}/>
</NavigationContainer>
</LocalizationContext.Provider>
);
};
export default App;
i18n.js
import {I18nManager} from 'react-native';
import * as RNLocalize from 'react-native-localize';
import i18n from 'i18n-js';
import memoize from 'lodash.memoize';
export const DEFAULT_LANGUAGE = 'en';
export const translationGetters = {
'en': () => require('../locales/en.json'),
'es': () => require('../locales/es.json'),
};
export const translate = memoize(
(key, config) => i18n.t(key, config),
(key, config) => (config ? key + JSON.stringify(config) : key),
);
export const t = translate;
export const setI18nConfig = (codeLang = null) => {
// Fallback if no available language fits
const fallback = {languageTag: DEFAULT_LANGUAGE, isRTL: false};
const lang = codeLang ? {languageTag: codeLang, isRTL: false} : null;
const {languageTag, isRTL} = lang
? lang
: RNLocalize.findBestAvailableLanguage(Object.keys(translationGetters)) ||
fallback;
// Enables fallbacks
i18n.fallbacks = true;
// Clear translation cache
translate.cache.clear();
// Update layout direction
I18nManager.forceRTL(isRTL);
// Set i18n-js config
i18n.translations = {[languageTag]: translationGetters[languageTag]()};
i18n.locale = languageTag;
return languageTag;
};
DrawerNavigation.js
import React, { useContext } from "react";
import { AboutStackNavigator } from "./StackNavigator";
import TabNavigator from "./TabNavigator";
import { createDrawerNavigator } from "#react-navigation/drawer";
import LocalizationContext from '../providers/LocalizationContext';
const Drawer = createDrawerNavigator();
const DrawerNavigator = () => {
const {t} = useContext(LocalizationContext);
return (
<Drawer.Navigator
drawerContentOptions={{
activeTintColor: 'gray',
inactiveTintColor: 'gray',
}}
>
<Drawer.Screen
name="Home"
component={ TabNavigator }
options={{
title: t('title_home')
}}
/>
<Drawer.Screen
name="About"
component={ AboutStackNavigator }
options={{
title: t('title_about')
}}
/>
</Drawer.Navigator>
);
};
export default DrawerNavigator;
StackNavigator.js
import React, { useContext } from "react";
import { createStackNavigator } from "#react-navigation/stack";
import Home from "../screens/Home";
import About from "../screens/About";
import Settings from "../screens/Settings";
import LocalizationContext from '../providers/LocalizationContext';
const Stack = createStackNavigator();
const MainStackNavigator = () => {
const {t} = useContext(LocalizationContext);
return (
<Stack.Navigator screenOptions={screenOptionStyle}>
<Stack.Screen name="Home" component={ Home } options={{ headerShown:false }} />
<Stack.Screen name="About" component={ About } options={{ title: t('title_about') }}/>
<Stack.Screen name="Settings" component={ Settings } options={{ title: t('title_settings') }}/>
</Stack.Navigator>
);
};
const AboutStackNavigator = () => {
const {t} = useContext(LocalizationContext);
return (
<Stack.Navigator screenOptions={screenOptionStyle}>
<Stack.Screen name="About" component={ About } options={{ title: t('title_about') }} />
</Stack.Navigator>
);
};
const SettingsStackNavigator = () => {
const {t} = useContext(LocalizationContext);
return (
<Stack.Navigator screenOptions={screenOptionStyle}>
<Stack.Screen name="Settings" component={ Settings } options={{ title: t('title_settings') }} />
</Stack.Navigator>
);
};
const screenOptionStyle = {
headerStyle: {
backgroundColor: 'white',
},
headerTintColor: "gray",
headerBackTitle: "Back",
};
export {
MainStackNavigator,
AboutStackNavigator,
SettingsStackNavigator
};
TabNavigator.js
import React, { useContext } from 'react';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { MainStackNavigator, AboutStackNavigator } from './StackNavigator';
import LocalizationContext from '../providers/LocalizationContext';
const Tab = createBottomTabNavigator();
const BottomTabNavigator = () => {
const {t} = useContext(LocalizationContext);
return (
<Tab.Navigator
tabBarOptions={{
activeTintColor: 'black',
inactiveTintColor: 'gray',
activeBackgroundColor: 'white',
inactiveBackgroundColor: 'white',
labelStyle: {
fontSize: 12,
},
}} >
<Tab.Screen name="Home" component={ MainStackNavigator } options={{ title: t('title_home') }} />
<Tab.Screen name="About" component={ AboutStackNavigator } options={{ title: t('title_about'), headerShown:false }} />
</Tab.Navigator>
);
}
export default BottomTabNavigator;
Home.js
import React, { useContext } from "react";
import { View, SafeAreaView, StyleSheet, Text, StatusBar } from "react-native";
import { Header } from 'react-native-elements';
import LocalizationContext from '../providers/LocalizationContext';
const Home = ({ navigation }) => {
const {t} = useContext(LocalizationContext);
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" backgroundColor='white' />
<Header
statusBarProps={{ barStyle: 'dark-content', backgroundColor: 'gray' }}
placement="center"
leftComponent={{ icon: 'menu', color: 'white', onPress: () => navigation.openDrawer() }}
centerComponent={{ text: 'My App', style: styles.centerComponent }}
rightComponent={{ icon: 'settings', color: 'white', onPress: () => navigation.navigate("Settings") }}
containerStyle={{ backgroundColor: 'gray', justifyContent: 'space-around' }}
/>
<View style={styles.center}>
<Text>{t('text_home')}</Text>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
center: {
flex: 1,
backgroundColor: 'white',
justifyContent: "center",
alignItems: "center",
textAlign: "center",
},
container: {
flex: 1,
backgroundColor: 'white',
},
centerComponent: {
color: 'white',
fontWeight: 'bold',
fontSize: 20
},
});
export default Home;
Settings.js
import React, { useContext, useState } from "react";
import { View, StyleSheet, Text, StatusBar, Button } from "react-native";
import LocalizationContext from '../providers/LocalizationContext';
const Settings = () => {
const {t} = useContext(LocalizationContext);
const [locale, setLocale] = useState('en');
const toggleLanguage = (locale) => {
setLocale(locale);
console.log(locale);
};
return (
<View style={styles.center}>
<StatusBar barStyle="dark-content" backgroundColor='white' />
<View style={styles.center}>
<Text>{t('text_setings')}</Text>
<Button onPress={() => toggleLanguage(locale == "es" ? "en" : "es")} title={locale == "es" ? "EN" : "ES"} />
</View>
</View>
);
};
const styles = StyleSheet.create({
center: {
flex: 1,
backgroundColor: 'white',
justifyContent: "center",
alignItems: "center",
textAlign: "center",
},
});
export default Settings;
To allow changes in language display, you may use the following method (assuming that you want to display English , French and German) :
make a globals.js with the following (add more items if you need):
module.exports = {
eunit:["Eldery Unit", "Unité des personnes âgées", "Senioreneinheit"],
skeysearch:["Search by Name", "Recherche par nom", "Suche mit Name"],
sstatus: ["Status", "Statut", "Status"]
};
include the following line at the top of your js pages:
GLOBAL = require('./globals');
In the places where you want to display different language, use the following:
GLOBAL.skeysearch[this.state.lang]
change the lang state according to your needs. (say by clicking a button),
So ,
this.state.lang=0 for English
this.state.lang=1 for French
this.state.lang=2 for German
I need to render a modal when the user press in the middle button. I'm using react-native-raw-bottom-sheet library to provide a Modal to my application.
I tried to pass a prop isFocused={props.navigation.isFocused} inside the <Tab.Screen> but the problem is when I pass the props to based if is or not focused in the the is rendered two times instead one.
I also tried to trigger the modal direct by the but without success.
My problematic is, when the user press the i need render the new that will contain the hole logic of the content in the modal and also will render the modal.
My tab.routes.js
import React from 'react';
import {createBottomTabNavigator} from '#react-navigation/bottom-tabs';
import TabButton from '../components/Tab/Button';
import Icon from 'react-native-vector-icons/MaterialIcons';
import Home from '../containers/Home';
import Adjustment from '../containers/Adjustment';
import Graphic from '../containers/Graphic';
import Help from '../containers/Help';
import NewTransaction from '../containers/NewTransaction';
const icons = {
Home: {
name: 'home',
},
Graphic: {
name: 'pie-chart',
},
NewTransaction: {
name: 'notifications-none',
},
Help: {
name: 'help-outline',
},
Adjustment: {
name: 'settings',
},
};
const Tab = createBottomTabNavigator();
const TabRoutes = () => (
<Tab.Navigator
initialRouteName="HomeScreen"
screenOptions={({route, navigation}) => ({
tabBarIcon: ({color, size, focused}) => {
if (route.name === 'NewTransaction') {
return <TabButton focused={focused} onPress={() => navigation.navigate('NewTransaction')} />;
}
const {name} = icons[route.name];
return <Icon name={name} size={size} color={color} />;
},
})}
tabBarOptions={{
keyboardHidesTabBar: true,
activeTintColor: '#f8b006',
inactiveTintColor: '#1C3041',
style: {
height: 60,
},
iconStyle: {
marginTop: 5,
},
labelStyle: {
fontSize: 12,
marginBottom: 10,
},
}}>
<Tab.Screen
options={{
title: 'Home',
}}
name="Home"
component={Home}
/>
<Tab.Screen
options={{
title: 'Gráfico',
}}
name="Graphic"
component={Graphic}
/>
<Tab.Screen
options={{
title: '',
}}
name="NewTransaction"
component={NewTransaction}
/>
<Tab.Screen
options={{
title: 'Ajuda',
}}
name="Help"
component={Help}
/>
<Tab.Screen
options={{
title: 'Ajustes',
}}
name="Adjustment">
{(props) => (
<Adjustment isVisible={props.navigation.isFocused()} onPress={() => props.navigation.navigate('Home')} />
)}
</Tab.Screen>
</Tab.Navigator>
);
export default TabRoutes;
Tab/Button/index.js
import React from 'react';
import {TouchableWithoutFeedback} from 'react-native-gesture-handler';
import Icon from 'react-native-vector-icons/MaterialIcons';
import Button from './styles';
const TabButton = ({onPress, focused}) => {
return (
<TouchableWithoutFeedback onPress={onPress}>
<Button focused={focused}>
<Icon name="add" size={35} color={'white'} />
</Button>
</TouchableWithoutFeedback>
);
};
export default TabButton;
And the component that will display the modal content
import React from 'react';
import {Text, View} from 'react-native';
const NewTransaction = ({isVisible}) => {
return (
<View>
<Text>Welcome to NewTransactions </Text>
</View>
);
};
export default NewTransaction;
This is my home tab it looks like to you
I custom tab bar with code but the add button is not a screen it is just a button and popup options to select
import {hideModalCreate, showModalCreate} from '#features/loading/actions';
import CreateYCTV from '#features/main/CreateYCTV';
import HomeScreen from '#features/main/Home';
import Manager from '#features/main/Manager';
import Notification from '#features/main/Notification';
import {createBottomTabNavigator} from '#react-navigation/bottom-tabs';
import images from '#res/icons';
import * as React from 'react';
import {Image, Pressable, View} from 'react-native';
import {Text, useTheme} from 'react-native-paper';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import {connect} from 'react-redux';
import ProductStack from './ProductStack';
const Tab = createBottomTabNavigator();
function MyTabBar({
state,
descriptors,
navigation,
showModalCreate,
hideModalCreate,
isShowModalCreate,
}) {
const {colors} = useTheme();
const insets = useSafeAreaInsets();
const focusedOptions = descriptors[state.routes[state.index].key].options;
if (focusedOptions.tabBarVisible === false) {
return null;
}
return (
<View
style={{
flexDirection: 'row',
backgroundColor: '#FFFFFF',
paddingBottom: Math.max(insets.bottom, 0),
}}>
{state.routes.map((route, index) => {
const {options} = descriptors[route.key];
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
const isFocused = state.index === index;
const getSourceImage = (isFocused) => {
switch (route.name) {
case 'home':
return isFocused ? images.tab_home1 : images.tab_home;
case 'loans':
return isFocused ? images.tab_searching1 : images.tab_searching;
case 'notification':
return isFocused ? images.notifications1 : images.notifications;
case 'manager':
return isFocused
? images.tab_paper_folder1
: images.tab_paper_folder;
default:
return images.tab_add;
}
};
const onPress = () => {
if (route.name === 'create') {
if (isShowModalCreate) {
hideModalCreate();
return;
}
showModalCreate();
return;
}
hideModalCreate();
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!isFocused && !event.defaultPrevented) {
navigation.navigate(route.name);
}
};
const onLongPress = () => {
navigation.emit({
type: 'tabLongPress',
target: route.key,
});
};
return (
<Pressable
accessibilityRole="button"
accessibilityStates={isFocused ? ['selected'] : []}
accessibilityLabel={options.tabBarAccessibilityLabel}
testID={options.tabBarTestID}
onPress={onPress}
onLongPress={onLongPress}
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 8,
backgroundColor: 'white',
}}>
<Image source={getSourceImage(isFocused)} />
{route.name != 'create' ? (
<Text
style={{
color: isFocused ? colors.primary : colors.placeholder,
fontSize: 10,
marginTop: 4,
}}>
{label}
</Text>
) : null}
</Pressable>
);
})}
</View>
);
}
const Tabbar = ({showModalCreate, hideModalCreate, isShowModalCreate}) => {
return (
<Tab.Navigator
tabBar={(props) => (
<MyTabBar
isShowModalCreate={isShowModalCreate}
showModalCreate={showModalCreate}
{...props}
hideModalCreate={hideModalCreate}
/>
)}>
<Tab.Screen
name="home"
component={HomeScreen}
options={{
title: 'Trang chủ',
}}
/>
<Tab.Screen
name="loans"
component={ProductStack}
options={{
title: 'Sản phẩm',
}}
/>
<Tab.Screen name="create" component={CreateYCTV} />
<Tab.Screen
name="notification"
component={Notification}
options={{
title: 'Thông báo',
tabBarBadge: '13',
}}
/>
<Tab.Screen
name="manager"
component={Manager}
options={{
title: 'Quản lý',
}}
/>
</Tab.Navigator>
);
};
const mapStateToProps = (state, ownProps) => {
return {
isShowModalCreate: state.loading.isShowModalCreate,
};
};
const mapDispatch = {
showModalCreate: showModalCreate,
hideModalCreate: hideModalCreate,
};
export default connect(mapStateToProps, mapDispatch)(Tabbar);