diff options
author | George Rusu <george@tagg.id> | 2021-05-19 16:55:04 -0700 |
---|---|---|
committer | George Rusu <george@tagg.id> | 2021-05-19 16:55:04 -0700 |
commit | 73cc07e1adce4ecea0f15bd651f8db2e510c9644 (patch) | |
tree | e72fbee2eb577affbe0ad7f91de5d6c9c4067be3 /src | |
parent | 5ddbf16658a6e3f7bb53e78b821e62190b804cf1 (diff) |
Add delte from list, need to add rediret to profile
Diffstat (limited to 'src')
-rw-r--r-- | src/components/common/Draggable.tsx | 352 | ||||
-rw-r--r-- | src/components/taggs/TaggDraggable.tsx | 14 | ||||
-rw-r--r-- | src/screens/profile/CaptionScreen.tsx | 2 |
3 files changed, 365 insertions, 3 deletions
diff --git a/src/components/common/Draggable.tsx b/src/components/common/Draggable.tsx new file mode 100644 index 00000000..347d2121 --- /dev/null +++ b/src/components/common/Draggable.tsx @@ -0,0 +1,352 @@ +/** + * * https://github.com/tongyy/react-native-draggable + * + */ + +import React from 'react'; +import { + View, + Text, + Image, + PanResponder, + Animated, + Dimensions, + TouchableOpacity, + StyleSheet, + GestureResponderEvent, + PanResponderGestureState, + StyleProp, +} from 'react-native'; +// import PropTypes from 'prop-types'; +import {ViewStyle} from 'react-native'; + +function clamp(number: number, min: number, max: number) { + return Math.max(min, Math.min(number, max)); +} + +interface IProps { + /**** props that should probably be removed in favor of "children" */ + renderText?: string; + isCircle?: boolean; + renderSize?: number; + imageSource?: number; + renderColor?: string; + /**** */ + children?: React.ReactNode; + shouldReverse?: boolean; + disabled?: boolean; + debug?: boolean; + animatedViewProps?: object; + touchableOpacityProps?: object; + onDrag?: ( + e: GestureResponderEvent, + gestureState: PanResponderGestureState, + ) => void; + onShortPressRelease?: (event: GestureResponderEvent) => void; + onDragRelease?: ( + e: GestureResponderEvent, + gestureState: PanResponderGestureState, + ) => void; + onLongPress?: (event: GestureResponderEvent) => void; + onPressIn?: (event: GestureResponderEvent) => void; + onPressOut?: (event: GestureResponderEvent) => void; + onRelease?: (event: GestureResponderEvent, wasDragging: boolean) => void; + onReverse?: () => {x: number; y: number}; + x?: number; + y?: number; + // z/elevation should be removed because it doesn't sync up visually and haptically + z?: number; + minX?: number; + minY?: number; + maxX?: number; + maxY?: number; +} + +export default function Draggable(props: IProps) { + const { + renderText, + isCircle, + renderSize, + imageSource, + renderColor, + children, + shouldReverse, + disabled, + debug, + animatedViewProps, + touchableOpacityProps, + onDrag, + onShortPressRelease, + onDragRelease, + onLongPress, + onPressIn, + onPressOut, + onRelease, + x, + y, + z, + minX, + minY, + maxX, + maxY, + } = props; + + // The Animated object housing our xy value so that we can spring back + const pan = React.useRef(new Animated.ValueXY()); + // Always set to xy value of pan, would like to remove + const offsetFromStart = React.useRef({x: 0, y: 0}); + // Width/Height of Draggable (renderSize is arbitrary if children are passed in) + const childSize = React.useRef({x: renderSize, y: renderSize}); + // Top/Left/Right/Bottom location on screen from start of most recent touch + const startBounds = React.useRef({top: 0, bottom: 0, left: 0, right: 0}); + // Whether we're currently dragging or not + const isDragging = React.useRef(false); + + const getBounds = React.useCallback(() => { + const left = x + offsetFromStart.current.x; + const top = y + offsetFromStart.current.y; + return { + left, + top, + right: left + childSize.current.x, + bottom: top + childSize.current.y, + }; + }, [x, y]); + + const shouldStartDrag = React.useCallback( + (gs) => { + return !disabled && (Math.abs(gs.dx) > 2 || Math.abs(gs.dy) > 2); + }, + [disabled], + ); + + const reversePosition = React.useCallback(() => { + Animated.spring(pan.current, { + toValue: {x: 0, y: 0}, + useNativeDriver: false, + }).start(); + }, [pan]); + + const onPanResponderRelease = React.useCallback( + (e: GestureResponderEvent, gestureState: PanResponderGestureState) => { + isDragging.current = false; + if (onDragRelease) { + onDragRelease(e, gestureState); + onRelease(e, true); + } + if (!shouldReverse) { + pan.current.flattenOffset(); + } else { + reversePosition(); + } + }, + [onDragRelease, shouldReverse, onRelease, reversePosition], + ); + + const onPanResponderGrant = React.useCallback( + (e: GestureResponderEvent, gestureState: PanResponderGestureState) => { + startBounds.current = getBounds(); + isDragging.current = true; + if (!shouldReverse) { + pan.current.setOffset(offsetFromStart.current); + pan.current.setValue({x: 0, y: 0}); + } + }, + [getBounds, shouldReverse], + ); + + const handleOnDrag = React.useCallback( + (e: GestureResponderEvent, gestureState: PanResponderGestureState) => { + const {dx, dy} = gestureState; + const {top, right, left, bottom} = startBounds.current; + const far = 999999999; + const changeX = clamp( + dx, + Number.isFinite(minX) ? minX - left : -far, + Number.isFinite(maxX) ? maxX - right : far, + ); + const changeY = clamp( + dy, + Number.isFinite(minY) ? minY - top : -far, + Number.isFinite(maxY) ? maxY - bottom : far, + ); + pan.current.setValue({x: changeX, y: changeY}); + onDrag(e, gestureState); + }, + [maxX, maxY, minX, minY, onDrag], + ); + + const panResponder = React.useMemo(() => { + return PanResponder.create({ + onMoveShouldSetPanResponder: (_, gestureState) => + shouldStartDrag(gestureState), + onMoveShouldSetPanResponderCapture: (_, gestureState) => + shouldStartDrag(gestureState), + onPanResponderGrant, + onPanResponderMove: Animated.event([], { + // Typed incorrectly https://reactnative.dev/docs/panresponder + listener: handleOnDrag, + useNativeDriver: false, + }), + onPanResponderRelease, + }); + }, [ + handleOnDrag, + onPanResponderGrant, + onPanResponderRelease, + shouldStartDrag, + ]); + + // TODO Figure out a way to destroy and remove offsetFromStart entirely + React.useEffect(() => { + const curPan = pan.current; // Using an instance to avoid losing the pointer before the cleanup + if (!shouldReverse) { + curPan.addListener((c) => (offsetFromStart.current = c)); + } + return () => { + // Typed incorrectly + curPan.removeAllListeners(); + }; + }, [shouldReverse]); + + const positionCss: StyleProp<ViewStyle> = React.useMemo(() => { + const Window = Dimensions.get('window'); + return { + position: 'absolute', + top: 0, + left: 0, + width: Window.width, + height: Window.height, + }; + }, []); + + const dragItemCss = React.useMemo(() => { + const style: StyleProp<ViewStyle> = { + top: y, + left: x, + elevation: z, + zIndex: z, + }; + if (renderColor) { + style.backgroundColor = renderColor; + } + if (isCircle) { + style.borderRadius = renderSize; + } + + if (children) { + return { + ...style, + alignSelf: 'baseline', + }; + } + return { + ...style, + justifyContent: 'center', + width: renderSize, + height: renderSize, + }; + }, [children, isCircle, renderColor, renderSize, x, y, z]); + + const touchableContent = React.useMemo(() => { + if (children) { + return children; + } else if (imageSource) { + return ( + <Image + style={{width: renderSize, height: renderSize}} + source={imageSource} + /> + ); + } else { + return <Text style={styles.text}>{renderText}</Text>; + } + }, [children, imageSource, renderSize, renderText]); + + const handleOnLayout = React.useCallback((event) => { + const {height, width} = event.nativeEvent.layout; + childSize.current = {x: width, y: height}; + }, []); + + const handlePressOut = React.useCallback( + (event: GestureResponderEvent) => { + onPressOut(event); + if (!isDragging.current) { + onRelease(event, false); + } + }, + [onPressOut, onRelease], + ); + + const getDebugView = React.useCallback(() => { + const {width, height} = Dimensions.get('window'); + const far = 9999; + const constrained = minX || maxX || minY || maxY; + if (!constrained) { + return null; + } // could show other debug info here + const left = minX || -far; + const right = maxX ? width - maxX : -far; + const top = minY || -far; + const bottom = maxY ? height - maxY : -far; + return ( + <View + pointerEvents="box-none" + style={{left, right, top, bottom, ...styles.debugView}} + /> + ); + }, [maxX, maxY, minX, minY]); + + return ( + <View pointerEvents="box-none" style={positionCss}> + {debug && getDebugView()} + <Animated.View + pointerEvents="box-none" + {...animatedViewProps} + {...panResponder.panHandlers} + style={pan.current.getLayout()}> + <TouchableOpacity + {...touchableOpacityProps} + onLayout={handleOnLayout} + style={dragItemCss} + disabled={true} + onPress={onShortPressRelease} + onLongPress={onLongPress} + onPressIn={onPressIn} + onPressOut={handlePressOut}> + {touchableContent} + </TouchableOpacity> + </Animated.View> + </View> + ); +} + +/***** Default props and types */ + +Draggable.defaultProps = { + renderText: '+', + renderSize: 36, + shouldReverse: false, + disabled: false, + debug: false, + onDrag: () => {}, + onShortPressRelease: () => {}, + onDragRelease: () => {}, + onLongPress: () => {}, + onPressIn: () => {}, + onPressOut: () => {}, + onRelease: () => {}, + x: 0, + y: 0, + z: 1, +}; + +const styles = StyleSheet.create({ + text: {color: '#fff', textAlign: 'center'}, + debugView: { + backgroundColor: '#ff000044', + position: 'absolute', + borderColor: '#fced0ecc', + borderWidth: 4, + }, +}); diff --git a/src/components/taggs/TaggDraggable.tsx b/src/components/taggs/TaggDraggable.tsx index 0d57c590..ac494a6b 100644 --- a/src/components/taggs/TaggDraggable.tsx +++ b/src/components/taggs/TaggDraggable.tsx @@ -35,6 +35,15 @@ const TaggDraggable: React.FC<TaggDraggableProps> = ( const dispatch = useDispatch(); const navigation = useNavigation(); const state: RootState = useStore().getState(); + + const gotoTaggedProfile = (userID: string) => { + //Since the logged In User is navigating to own profile, useXId is not required + navigation.navigate('Profile', { + ScreenType, + userXId: userID, + }); + }; + const { draggable, minX, @@ -69,7 +78,8 @@ const TaggDraggable: React.FC<TaggDraggableProps> = ( style={styles.container} onLayout={(event) => console.log(event.nativeEvent.layout)}> <TouchableOpacity - disabled={true} + onPressIn={() => gotoTaggedProfile(taggedUser.userId)} + disabled={false} style={[styles.button]} ref={draggableRef}> <TaggAvatar @@ -79,7 +89,7 @@ const TaggDraggable: React.FC<TaggDraggableProps> = ( userXId={undefined} /> <Text style={styles.buttonTitle}>@{taggedUser.username}</Text> - <TouchableOpacity disabled={false} onPressIn={deleteFromList()}> + <TouchableOpacity disabled={false} onPressIn={() => deleteFromList()}> <Image style={styles.imageX} source={uriX} /> </TouchableOpacity> </TouchableOpacity> diff --git a/src/screens/profile/CaptionScreen.tsx b/src/screens/profile/CaptionScreen.tsx index bd715a53..37003207 100644 --- a/src/screens/profile/CaptionScreen.tsx +++ b/src/screens/profile/CaptionScreen.tsx @@ -35,7 +35,7 @@ import { import {mentionPartTypes} from '../../utils/comments'; import TaggDraggable from '../../components/taggs/TaggDraggable'; -import Draggable from 'react-native-draggable'; +import Draggable from '../../components/common/Draggable'; /** * Upload Screen to allow users to upload posts to Tagg |