Created
June 22, 2024 08:39
-
-
Save Jalson1982/451a5b8624866c33cca23df4a9259f4b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, {useEffect, useRef, useState} from 'react'; | |
import {useSafeAreaInsets} from 'react-native-safe-area-context'; | |
import { | |
Canvas, | |
RoundedRect, | |
RadialGradient, | |
BackdropBlur, | |
Fill, | |
vec, | |
Rect, | |
} from '@shopify/react-native-skia'; | |
import Animated, { | |
Easing, | |
runOnJS, | |
useAnimatedStyle, | |
useSharedValue, | |
withTiming, | |
} from 'react-native-reanimated'; | |
import {Dimensions, StyleSheet, View} from 'react-native'; | |
import { | |
Directions, | |
Gesture, | |
GestureDetector, | |
} from 'react-native-gesture-handler';; | |
const duration = 300; | |
const _size = Dimensions.get('window').width * 0.8; | |
const layout = { | |
borderRadius: 16, | |
width: _size, | |
height: 193, | |
spacing: 12, | |
cardsGap: 22, | |
}; | |
const colors = { | |
primary: '#6667AB', | |
light: '#fff', | |
dark: '#111', | |
}; | |
const data = [ | |
{ | |
id: '0fa08fa5-6d45-4d56-86e0-8fd56632dfd4', | |
role: 'Subcontractor', | |
type: 'Excavator', | |
from: '10:52 AM', | |
to: '9:40 PM', | |
duration: 2, | |
distance: 19.7, | |
}, | |
{ | |
id: 'f8f798bb-c7bd-4080-9bc6-aa4cabfab482', | |
role: 'Surveyor', | |
type: 'Crawler', | |
from: '11:45 AM', | |
to: '4:08 PM', | |
duration: 0, | |
distance: 55.2, | |
}, | |
{ | |
id: '068de8c4-1b87-4051-9885-ee28a48cd274', | |
role: 'Surveyor', | |
type: 'Scraper', | |
from: '9:04 AM', | |
to: '9:17 PM', | |
duration: 5, | |
distance: 1.9, | |
}, | |
]; | |
export const locationImage = | |
'https://miro.medium.com/v2/resize:fit:1200/1*ybR6fbfwo6XTmWvTjXSOAA.png'; | |
const cards = [ | |
{id: 1, text: 'Card 1'}, | |
{id: 2, text: 'Card 2'}, | |
{id: 3, text: 'Card 3'}, | |
]; | |
export const Home = () => { | |
const {top} = useSafeAreaInsets(); | |
const animation = useSharedValue(0); | |
const [currentIndex, setCurrentIndex] = useState(0); | |
function mapIndex(index: number, totalCards: number) { | |
const scaleFactor = 0.2; // Change this value to adjust the scaling effect | |
return 1 + scaleFactor * (totalCards - 1 - index); | |
} | |
const animatedStyles = cards.map((_, index) => | |
useAnimatedStyle(() => { | |
const isCurrent = currentIndex === index; | |
return { | |
transform: [ | |
{ | |
translateY: withTiming(isCurrent ? 100 : index * -50, { | |
duration: 500, | |
easing: Easing.inOut(Easing.ease), | |
}), | |
}, | |
{ | |
scale: withTiming(1 + 0.2 * (3 - index), { | |
duration: 500, | |
easing: Easing.inOut(Easing.ease), | |
}), | |
}, | |
], | |
opacity: withTiming(isCurrent ? 1 : 0.5, { | |
duration: 500, | |
easing: Easing.inOut(Easing.ease), | |
}), | |
zIndex: isCurrent ? 1 : 0, | |
}; | |
}), | |
); | |
const [activeIndex, setActiveIndex] = useState(0); | |
const floatActiveIndex = useSharedValue(0); | |
const [isMenuVisible, setIsMenuVisible] = useState(false); | |
const [data1, setData1] = useState(data); | |
const [boom, setBoom] = useState(0); | |
const offsetX = useSharedValue(0); | |
function perfomSwipe(direction) { | |
if (direction === 'right' && boom >= data1.length - 1) { | |
return; | |
} | |
if (direction === 'left' && boom <= 0) { | |
return; | |
} | |
if (direction === 'right') { | |
setBoom(boom + 1); | |
} else { | |
setBoom(boom - 1); | |
} | |
setData1(oldData => { | |
const newData = [...oldData]; | |
const lastElement = newData.pop(); | |
newData.unshift(lastElement); | |
return newData; | |
}); | |
} | |
const panGesture = Gesture.Pan() | |
.onChange(event => { | |
offsetX.value = event.translationX; | |
}) | |
.onFinalize(event => { | |
if ( | |
Math.abs(event.translationX) > 120 || | |
Math.abs(event.translationY) > 120 | |
) { | |
//runOnJS(perfomSwipe)(); | |
} | |
offsetX.value = withTiming(0); | |
}); | |
const flingUp = Gesture.Fling() | |
.direction(Directions.UP) | |
.onStart(() => { | |
if (floatActiveIndex.value <= 0) { | |
floatActiveIndex.value = 0; | |
return; | |
} | |
floatActiveIndex.value = withTiming(floatActiveIndex.value - 1, { | |
duration, | |
}); | |
}); | |
const flingDown = Gesture.Fling() | |
.direction(Directions.RIGHT) | |
.onStart(() => { | |
if (floatActiveIndex.value === data.length) { | |
return; | |
} | |
floatActiveIndex.value = withTiming(floatActiveIndex.value + 1, { | |
duration, | |
}); | |
}); | |
const flingRight = Gesture.Fling() | |
.direction(Directions.RIGHT) | |
.onStart(() => { | |
if (floatActiveIndex.value === data.length) { | |
return; | |
} | |
floatActiveIndex.value = withTiming(floatActiveIndex.value + 1, { | |
duration, | |
}); | |
}); | |
const flingLeft = Gesture.Fling() | |
.direction(Directions.LEFT) | |
.onStart(() => { | |
if (floatActiveIndex.value <= 0) { | |
floatActiveIndex.value = 0; | |
return; | |
} | |
floatActiveIndex.value = withTiming(floatActiveIndex.value - 1, { | |
duration, | |
}); | |
}); | |
return ( | |
<Box | |
flex={1} | |
backgroundColor="primary950" | |
style={{ | |
paddingTop: top + 10, | |
}}> | |
<Box | |
style={{ | |
alignItems: 'center', | |
flex: 1, | |
justifyContent: 'center', | |
marginBottom: layout.cardsGap * 2, | |
}} | |
pointerEvents="box-none"> | |
{data1.map((c, index) => { | |
return ( | |
<Card | |
info={c} | |
key={c.id} | |
id={c.id} | |
index={index} | |
totalLength={data.length - 1} | |
activeIndex={floatActiveIndex} | |
perfomSwipe={perfomSwipe} | |
offsetX={offsetX} | |
isFirst={index === 0} | |
boom={boom} | |
/> | |
); | |
})} | |
</Box> | |
</Box> | |
); | |
}; | |
function Card({ | |
info, | |
index, | |
totalLength, | |
activeIndex, | |
perfomSwipe, | |
isFirst, | |
id, | |
boom, | |
}) { | |
const offsetX = useSharedValue(0); | |
const indexRef = useSharedValue(index); | |
console.log('index', index, id); | |
const createPanGesture = cardIndex => { | |
return Gesture.Pan() | |
.onChange(event => { | |
console.log('event111boom', boom); | |
if (boom === 0 && event.translationX < 0) { | |
console.log('muuu'); | |
return; | |
} | |
if (boom >= totalLength && event.translationX > 0) { | |
console.log('muuu'); | |
return; | |
} | |
console.log('event111222', event.translationX, id); | |
offsetX.value = event.translationX; | |
}) | |
.onFinalize(event => { | |
if (Math.abs(event.translationX) > 120) { | |
if (cardIndex === 0 && event.translationX < 0) return; | |
console.log('event111', cardIndex, isFirst, event.translationX); | |
runOnJS(perfomSwipe)(event.translationX > 0 ? 'right' : 'left'); | |
} | |
offsetX.value = withTiming(0); | |
}); | |
}; | |
const panGesture = createPanGesture(index); | |
const stylez = useAnimatedStyle(() => { | |
return { | |
position: 'absolute', | |
opacity: withTiming( | |
Math.max( | |
1 - ((totalLength - index - 1) / 10) * 1 * (totalLength - index - 1), | |
0.8, | |
), | |
), | |
transform: [ | |
{translateX: offsetX.value}, | |
{ | |
scaleX: withTiming( | |
Math.max( | |
1 - | |
(totalLength - index - 1) / 10 + | |
0 * (totalLength - index - 1), | |
0.8, | |
), | |
), | |
}, | |
{translateY: withTiming(-Math.min((totalLength - index - 1) * 15, 10))}, | |
], | |
zIndex: 0, | |
}; | |
}); | |
return ( | |
<GestureDetector | |
// We use Exclusive to prevent using different gestures at the same time | |
// There's only one gesture that can be performed. This is by design. | |
// If you would like to support multiple gestures, you can use `Simultaneous` | |
gesture={panGesture}> | |
<Animated.View | |
style={[ | |
{ | |
borderRadius: layout.borderRadius, | |
width: layout.width, | |
height: layout.height, | |
}, | |
stylez, | |
]}> | |
<Canvas | |
style={{ | |
position: 'absolute', | |
width: layout.width, | |
height: layout.height, | |
borderRadius: layout.borderRadius, | |
overflow: 'hidden', | |
}}> | |
<Rect | |
x={0} | |
y={0} | |
width={Dimensions.get('window').width * 0.8} | |
height={193}> | |
<RadialGradient | |
c={vec((Dimensions.get('window').width * 0.8) / 2, 193)} // Center at 50% 100% | |
r={193} // Radius to cover the canvas height | |
colors={[ | |
'rgba(190, 87, 209, 0.60)', // Color at the center | |
'rgba(91, 44, 198, 1.0)', // Color at the edge | |
]} | |
/> | |
</Rect> | |
<BackdropBlur | |
blur={4} | |
clip={{ | |
x: 0, | |
y: 0, | |
width: Dimensions.get('window').width * 0.8, | |
height: 193, | |
}}> | |
<Fill color="rgba(0, 0, 0, 0.)" /> | |
</BackdropBlur> | |
</Canvas> | |
<> | |
<Text style={styles.title}>{info.type}</Text> | |
<View style={styles.row}> | |
<Text style={styles.subtitle}> | |
{info.from} - {info.to} | |
</Text> | |
</View> | |
<View style={styles.row}> | |
<Text style={styles.subtitle}>{info.distance} km</Text> | |
</View> | |
<View style={styles.row}> | |
<Text style={styles.subtitle}>{info.role}</Text> | |
</View> | |
</> | |
</Animated.View> | |
</GestureDetector> | |
); | |
} | |
const styles = StyleSheet.create({ | |
container: { | |
flex: 1, | |
paddingTop: 30, | |
backgroundColor: colors.primary, | |
padding: layout.spacing, | |
}, | |
card: { | |
borderRadius: layout.borderRadius, | |
width: layout.width, | |
height: layout.height, | |
padding: layout.spacing, | |
backgroundColor: colors.light, | |
}, | |
title: { | |
fontSize: 32, | |
fontWeight: '600', | |
textAlign: 'center', | |
color: 'white', | |
}, | |
subtitle: {color: 'white', textAlign: 'center'}, | |
cardContent: {textAlign: 'center'}, | |
locationImage: { | |
flex: 1, | |
borderRadius: layout.borderRadius - layout.spacing / 2, | |
}, | |
row: { | |
flexDirection: 'row', | |
columnGap: layout.spacing / 2, | |
alignItems: 'center', | |
justifyContent: 'center', | |
}, | |
icon: {}, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment