I want to make a bottom sheet, but I dont know why my overlay not working with a opacity background.
App.tsx
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import BottomSheet from './components/BottomSheet';
export default function App() {
return (
<GestureHandlerRootView style={s.container}>
<Text>Hello</Text>
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center', opacity: 0.8, backgroundColor: 'rgba(0,0,0,0.9)', zIndex: 10}}>
<BottomSheet />
</View>
</GestureHandlerRootView>
)
}
const s = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'red',
alignItems: 'center',
justifyContent: 'center'
}
});
BottomSheet.tsx
import { Dimensions, StyleSheet, Text, View } from 'react-native'
import React, { useEffect } from 'react'
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, { useAnimatedStyle, useSharedValue, withSpring, withTiming } from 'react-native-reanimated';
const { height } = Dimensions.get('screen');
const START_HEIGHT = height / 3;
const BottomSheet = () => {
const translateY = useSharedValue(0);
const context = useSharedValue(0);
const panGesture = Gesture.Pan()
.onStart(() => {
context.value = translateY.value;
})
.onUpdate((e) => {
translateY.value = e.translationY + context.value;
translateY.value = Math.max(translateY.value, -height / 3);
})
.onEnd(() => {
if(translateY.value >= -height / 4) {
translateY.value = withTiming(0, { duration: 100 });
} else {
translateY.value = withSpring(-height / 3, { damping: 50 })
}
});
useEffect(() => {
translateY.value = -height / 3
}, []);
const rBottomSheet = useAnimatedStyle(() => {
return {
transform: [{ translateY: translateY.value }]
}
});
return (
<GestureDetector gesture={panGesture}>
<Animated.View style={[s.bottomSheet, rBottomSheet]}>
<View style={s.line} />
</Animated.View>
</GestureDetector>
)
}
export default BottomSheet
const s = StyleSheet.create({
bottomSheet: {
backgroundColor: '#fff',
height: height,
width: '100%',
position: 'absolute',
top: height,
borderRadius: 25
},
line: {
height: 4,
width: 70,
marginVertical: 15,
backgroundColor: 'grey',
borderRadius: 6,
alignSelf: 'center'
}
})
Image:
What I am doing wrong ? Can anyone help me please
..sa..................................................................................................................................................................................
Related
For some reason, only on android, the keyboard compatible view with stream chat doesn't calculate the proper height correctly until I start to type. I have tried many different configurations and can't figure out why it is only on android and why it readjusts as soon as I start to type.
Here is the code for the chat.
import "react-native-gesture-handler";
import {
StyleSheet,
Text,
View,
Pressable,
KeyboardAvoidingView,
Platform,
Dimensions,
StatusBar,
Keyboard,
} from "react-native";
import { useHeaderHeight } from "#react-navigation/elements";
import React, { useState, useContext, useEffect } from "react";
import Header from "../components/Header";
import { StreamChat } from "stream-chat";
import {
SafeAreaView,
useSafeAreaInsets,
SafeAreaProvider,
} from "react-native-safe-area-context";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { ChatContext } from "../../contexts/ChatContext";
import {
Channel,
Chat,
MessageInput,
MessageList,
OverlayProvider,
ChannelList,
Thread,
} from "stream-chat-expo";
import { Ionicons } from "#expo/vector-icons";
import { getAuth } from "firebase/auth";
import { doc, getDoc } from "firebase/firestore";
import { db } from "../../firebase";
import { UserContext } from "../../contexts/UserContext";
import ChatProfileView from "../components/ChatProfileView";
const client = StreamChat.getInstance(api-key);
const auth = getAuth();
const ChatScreen = () => {
const { channel, setChannel } = useContext(UserContext);
const [chatContactVisible, setChatContactVisible] = useState(false);
const [chatContactToView, setChatContactToView] = useState({});
const [thread, setThread] = useState(null);
const [userName, setUserName] = useState(null);
const [chatName, setChatName] = useState(null);
const [chatId, setChatId] = useState(null);
const [keyboardHeight, setKeyboardHeight] = useState(0);
const { bottom } = useSafeAreaInsets();
const headerHeight = useHeaderHeight();
useEffect(() => {
if (!client.user) {
getStreamUserToken().then((userToken) => {
client.connectUser(
{
id: auth.currentUser.uid,
},
userToken.data
);
console.log("loggeduserin- login");
});
}
}, []);
const clearChannel = () => {
setChannel();
setChatName();
setChatContactToView();
};
const clearThread = () => {
setThread(null);
};
useEffect(() => {
console.log(channel);
}, [channel, setChannel]);
const theme = {
messageList: {
container: {
backgroundColor: "white",
},
},
channelPreview: {
container: { backgroundColor: "white" },
},
};
useEffect(() => {
async function getUserNames() {
const userRef = doc(db, "users", auth.currentUser.uid);
const docSnap = await getDoc(userRef);
if (docSnap.exists()) {
const info = docSnap.data();
setUserName(info.firstName);
}
}
getUserNames();
}, []);
const getUserData = async () => {
const userRef = doc(db, "users", chatId);
const docSnap = await getDoc(userRef);
if (docSnap.exists()) {
const info = docSnap.data();
setChatContactToView(info);
console.log(info);
}
};
useEffect(() => {
if (channel) {
if (userName === channel.data.memberOne) {
setChatName(channel.data.memberTwo);
setChatId(channel.data.memberTwoId);
console.log("---------- id set");
console.log(channel.data.memberTwoId);
} else {
setChatName(channel.data.memberOne);
setChatId(channel.data.memberOneId);
console.log("---------- id set now");
console.log(channel.data.memberTwoId);
}
}
}, [channel, setChannel]);
const CustomPreviewTitle = ({ channel }) => {
if (userName === channel.data.memberOne) {
return <Text>{channel.data.memberTwo}</Text>;
} else {
return <Text>{channel.data.memberOne}</Text>;
}
};
const openChatContact = () => {
getUserData()
.then(setChatContactVisible(true), console.log("opened"))
.catch((err) => {
alert("User no longer exists :(");
});
};
const filters = { members: { $in: [auth.currentUser.uid] } };
return (
<ChatContext.Provider
value={{
chatContactVisible,
setChatContactVisible,
chatContactToView,
setChatContactToView,
}}
>
<GestureHandlerRootView style={styles.container}>
<SafeAreaView style={{ flex: 1 }}>
<Chat client={client} style={theme}>
<Header screenName="Chat" />
{channel ? (
<Channel
channel={channel}
thread={thread}
threadList={!!thread}
keyboardBehavior={"padding"}
keyboardVerticalOffset={0}
>
<View
style={{
justifyContent: "space-between",
flexDirection: "row",
alignItems: "center",
height: 40,
}}
>
<View
style={{
marginLeft: 10,
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
}}
>
<Pressable onPress={clearChannel}>
<Ionicons
name="arrow-back-sharp"
size={30}
color="black"
/>
</Pressable>
<Pressable onPress={openChatContact}>
<Text
style={{
color: "black",
fontSize: 20,
marginLeft: 10,
}}
>
{chatName}
</Text>
</Pressable>
</View>
{thread ? (
<Pressable
style={{
marginRight: 15,
}}
onPress={clearThread}
>
<Text style={{ color: "#3b55d9" }}>Close thread</Text>
</Pressable>
) : (
<></>
)}
</View>
{thread ? (
<Thread />
) : (
<>
<MessageList onThreadSelect={setThread} />
<MessageInput />
</>
)}
</Channel>
) : (
<View style={styles.scroll}>
<ChannelList
onSelect={setChannel}
filters={filters}
PreviewTitle={CustomPreviewTitle}
style={theme}
/>
</View>
)}
</Chat>
{chatContactVisible ? <ChatProfileView /> : <></>}
</SafeAreaView>
</GestureHandlerRootView>
</ChatContext.Provider>
);
};
export default ChatScreen;
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: "column",
backgroundColor: "white",
},
scroll: {
flex: 9,
},
});
`
Here is the code for the header component.
`import { StyleSheet, Text, View, Pressable } from "react-native";
import React, { useState, useEffect, useContext } from "react";
import { auth } from "../../firebase";
import { useNavigation } from "#react-navigation/core";
import { Ionicons } from "#expo/vector-icons";
import { StreamChat } from "stream-chat";
import { UserContext } from "../../contexts/UserContext";
import { getDocs, query, where, collection } from "firebase/firestore";
import { getAuth } from "firebase/auth";
import { db } from "../../firebase";
import { LikesContext } from "../../contexts/LikesContext";
import ProfileView from "./Likes";
const client = StreamChat.getInstance(api-key);
const Header = (props) => {
const [likesVisible, setLikesVisible] = useState(false);
const { tempLikesArray, setTempLikesArray } = useContext(UserContext);
const navigation = useNavigation();
const handleSignOut = () => {
auth
.signOut()
.then(async () => {
navigation.replace("Login");
await client.disconnectUser();
console.log("User disconnected");
})
.catch((error) => alert(error.mesage));
};
const openLikes = () => {
setLikesVisible(true);
};
return (
<LikesContext.Provider
value={{
likesVisible,
setLikesVisible,
}}
>
<View style={styles.header}>
<Text style={styles.logoText}>{props.screenName}</Text>
<View style={{ flexDirection: "row" }}>
<Pressable
style={{ justifyContent: "center", marginRight: 15 }}
onPress={openLikes}
>
<View
style={[
tempLikesArray.length > 0
? styles.likeBubbleVisible
: styles.likeBubbleInvisible,
]}
></View>
<Ionicons name="ios-person-add-outline" size={24} color="black" />
</Pressable>
<Pressable
style={{ justifyContent: "center", marginRight: 15 }}
onPress={handleSignOut}
>
<Ionicons name="ios-log-out-outline" size={24} color="black" />
</Pressable>
</View>
{likesVisible ? <ProfileView /> : <></>}
</View>
</LikesContext.Provider>
);
};
export default Header;
const styles = StyleSheet.create({
header: {
flex: 1,
flexDirection: "row",
flexWrap: "wrap",
backgroundColor: "white",
width: "100%",
justifyContent: "space-between",
marginTop: 10,
},
logoText: {
alignSelf: "center",
fontSize: 30,
color: "#3b55d9",
marginLeft: 20,
fontFamily: "Inter_900Black",
},
logoutButton: {
alignSelf: "center",
},
image: {
width: 20,
height: 20,
alignSelf: "center",
marginRight: 20,
},
likeBubbleVisible: {
width: 5,
height: 5,
borderRadius: 10,
backgroundColor: "blue",
position: "absolute",
display: "flex",
top: 2,
left: 0,
},
likeBubbleInvisible: {
width: 5,
height: 5,
backgroundColor: "blue",
position: "absolute",
display: "none",
top: 2,
left: 0,
},
});
`
I have a button when pressing that, want to increase height of view in an animated way. And then after pressing that button again, want to reverse the animation to get the initial height of view.
I have done animating increasing the height of view but when I give the new state to my component, reverse animation is not happening. How to solve this problem?
This is my screen:
import React, { useEffect, useRef, useState } from "react";
import {
Animated,
Easing,
Pressable,
StyleSheet,
Text,
View,
} from "react-native";
import AnimatedTile from "../components/AnimatedTile";
function Test(props) {
const [start, setStart] = useState(false);
return (
<View style={styles.container}>
<View style={styles.animationContainer}>
<Pressable
android_ripple={{ color: "red" }}
onPress={() => setStart(!start)}
>
<Text>animate</Text>
</Pressable>
<AnimatedTile startAnimation={start} /> // ---->My animated component
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
animationContainer: {
flexDirection: "row",
width: "60%",
height: "20%",
justifyContent: "space-between",
alignItems: "center",
},
});
export default Test;
And this is my animated component:
import React, { useEffect, useRef, useState } from "react";
import {
Animated,
Easing,
Pressable,
StyleSheet,
Text,
View,
} from "react-native";
function AnimatedTile({ startAnimation }) {
const height = new Animated.Value(0);
const isExpanded = useRef(false);
const maxHieght = height.interpolate({
inputRange: [0, 1],
outputRange: isExpanded.current ? [180, 10] : [10, 180],
});
const start = () => {
if (isExpanded.current) {
Animated.timing(height, {
toValue: 0,
duration: 500,
easing: Easing.linear,
useNativeDriver: false,
}).start();
} else {
Animated.timing(height, {
toValue: 1,
duration: 500,
easing: Easing.linear,
useNativeDriver: false,
}).start();
}
isExpanded.current = !isExpanded.current;
};
useEffect(() => {
start();
}, [startAnimation]);
return (
<Animated.View
style={{
width: 5,
height: 120,
maxHeight: maxHieght,
backgroundColor: "dodgerblue",
borderRadius: 5,
}}
/>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
animationContainer: {
flexDirection: "row",
width: "60%",
height: "20%",
justifyContent: "space-between",
alignItems: "center",
},
});
export default AnimatedTile;
How I make my box to center like justify content center but with position absolute ?
top: '50%' is too close to the bottom its not centered.
Modal.tsx
import React, { useEffect } from 'react';
import { StyleSheet, View, Pressable, StyleProp, ViewStyle } from 'react-native';
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
import Animated, { runOnJS, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
interface IModalCenter {
children: React.ReactNode;
onPress: () => void;
style?: StyleProp<ViewStyle>;
}
const ModalCenter = ({ children, onPress, style }: IModalCenter) => {
const x = useSharedValue(0);
useEffect(() => {
x.value = 1;
}, []);
const aView = useAnimatedStyle(() => {
const scale = withSpring(x.value);
return {
transform: [{ scale }]
}
});
return (
<GestureHandlerRootView style={s.container}>
<Animated.View style={{flexGrow: 1, backgroundColor: 'rgba(0,0,0,0.4)',}} />
<Animated.View style={[style, aView]}>
{ children }
</Animated.View>
</GestureHandlerRootView>
)
};
const s = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
flex: 1,
zIndex: 600,
margin: 'auto'
}
})
export default ModalCenter;
app.tsx
<ModalCenter style={{backgroundColor: '#fff', position: 'absolute', margin: 'auto', alignSelf: 'center', height: 200, width: 300, borderRadius: 8, elevation: 4}} onPress={handleToggleModalMessage}>
<Text>Hello</Text>
</ModalCenter>
how can i make it center ? ........................................................................................................................................................................................
You need to add 'justify-content' property to the container:
container: {
...StyleSheet.absoluteFillObject,
flex: 1,
zIndex: 600,
margin: 'auto',
justify-content: 'center' <---- add this
}
Today I want to make an animation to be determined by the user. In my example is when the user moves the square close and hit the red rectangle for example to write something in console. But i don't know how to calculate distance to the rectangle and I can't determine when the square touched the rectangle? So my code sa far -
import React from "react";
import { StyleSheet, View } from "react-native";
import {
GestureHandlerRootView,
PanGestureHandler,
PanGestureHandlerGestureEvent,
} from "react-native-gesture-handler";
import Animated, {
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withSpring,
withTiming,
} from "react-native-reanimated";
const SIZE = 100.0;
const CIRCLE_RADIUS = SIZE * 2;
type ContextType = {
translateX: number;
translateY: number;
size: number;
};
export default function App() {
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);
const size = useSharedValue(100);
const panGestureEvent = useAnimatedGestureHandler<
PanGestureHandlerGestureEvent,
ContextType
>({
onStart: (event, context) => {
context.translateX = translateX.value;
context.translateY = translateY.value;
size.value = 122;
},
onActive: (event, context) => {
translateX.value = event.translationX + context.translateX;
translateY.value = event.translationY + context.translateY;
},
onEnd: () => {
const distance = Math.sqrt(translateX.value ** 2 + translateY.value ** 2);
size.value = 100;
if (distance < CIRCLE_RADIUS + SIZE / 2) {
translateX.value = withSpring(30);
translateY.value = withSpring(50);
}
},
});
const rStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateX: translateX.value,
},
{
translateY: translateY.value,
},
],
width: withTiming(size.value, { duration: 160 }),
height: withTiming(size.value, { duration: 160 }),
};
});
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<View style={styles.container}>
<View style={styles.circle}>
<PanGestureHandler onGestureEvent={panGestureEvent}>
<Animated.View
style={[
{
width: size.value,
height: size.value,
backgroundColor: "rgba(0, 0, 256, 0.5)",
borderRadius: 20,
},
rStyle,
]}
/>
</PanGestureHandler>
</View>
<View
style={{ width: 30, height: 50, backgroundColor: "red", top: 80 }}
></View>
</View>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
circle: {
width: CIRCLE_RADIUS * 2,
height: CIRCLE_RADIUS * 2,
alignItems: "center",
justifyContent: "center",
borderRadius: CIRCLE_RADIUS,
borderWidth: 5,
borderColor: "rgba(0, 0, 256, 0.5)",
},
});
I'm facing a problem with centring the text after the animation finishes as you can see in the video here https://www.youtube.com/watch?v=hhBGUp9_GAY&feature=youtu.be. I want to get both titles perfectly centered horizontally on all devices no matter the screen width. I'm using the Animated API. Any suggestions?
Here is my approach
import React, { useEffect } from "react";
import { View, StyleSheet, Animated, Text, Dimensions, AsyncStorage } from "react-native";
export default function Welcome({ navigation }) {
const width = Dimensions.get('screen').width
let position1 = new Animated.ValueXY(0, 0);
let position2 = new Animated.ValueXY(0, 0);
useEffect(() => {
Animated.timing(position1, {
toValue: { x: width / 4.5, y: 0 },
duration: 900
}).start();
Animated.timing(position2, {
toValue: { x: -width / 3, y: 0 },
duration: 900
}).start();
}, []);
_retrieveData = async () => {
try {
const token = await AsyncStorage.getItem('tokehhn');
if (token !== null) {
// We have data!!
setTimeout(() => navigation.navigate('Home'), 2000)
} else {
setTimeout(() => navigation.navigate('Auth'), 2000)
}
} catch (error) {
// Error retrieving data
}
};
useEffect(() => {
_retrieveData()
}, [])
return (
<View style={styles.container}>
<Animated.View style={position1.getLayout()}>
{/* <View style={styles.ball} /> */}
<Text style={{ position: 'relative', fontWeight: 'bold', fontSize: 24, color: '#5790f9' }}>Welcome to Glue</Text>
</Animated.View>
<Animated.View style={position2.getLayout()}>
{/* <View style={styles.ball} /> */}
<Text style={{ position: 'relative', right: -220, fontWeight: 'bold', fontSize: 21, color: '#5790f9' }}>Where everything happens</Text>
</Animated.View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center'
}
});
Thats how you do it:
let {width} = Dimensions.get('window')
export default function App() {
let animation = new Animated.Value(-width);
let translateX = animation.interpolate({inputRange:[-width,0],outputRange:[2*width,0]});
React.useEffect(()=>{
Animated.timing(animation,{toValue:0}).start();
},[])//eslint-ignore-line
return (
<View style={styles.container}>
<Animated.Text style={[styles.text,{transform:[{translateX:animation}]}]}>LOL</Animated.Text>
<Animated.Text style={[styles.text,{transform:[{translateX}]}]}>Longer LOLLLL</Animated.Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
text:{
textAlign:'center'
}
});
I have created snack as well
Make it a simple and clean interpolation.
The code looks always clean, and readable if we use Animated.Value in range of 0 - 1.
Full code:
import React, {useEffect} from 'react';
import {View, StyleSheet, Animated} from 'react-native';
const App = () => {
const animate = new Animated.Value(0);
const inputRange = [0, 1];
const translate1 = animate.interpolate({inputRange, outputRange: [-100, 0]});
const translate2 = animate.interpolate({inputRange, outputRange: [100, 0]});
useEffect(() => {
Animated.timing(animate, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}).start();
}, []);
return (
<View style={styles.container}>
<Animated.Text
style={[styles.text, {transform: [{translateX: translate1}]}]}>
First Text
</Animated.Text>
<Animated.Text
style={[styles.text, {transform: [{translateX: translate2}]}]}>
Second Text
</Animated.Text>
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
text: {
fontSize: 25,
},
});
Using that animated value, implement any other animations if needed.
For example, If you need to scale the text while moving:
const scale = animate.interpolate({inputRange, outputRange: [1, 1.5]});