Skip to content

Commit

Permalink
feat: Improve swipeable task animation
Browse files Browse the repository at this point in the history
  • Loading branch information
olerichter00 committed Oct 14, 2024
1 parent 44906cf commit 17ea2b2
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 24 deletions.
60 changes: 42 additions & 18 deletions src/app/Components/Swipeable/Swipeable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import ReanimatedSwipeable, {
SwipeableMethods,
SwipeableRef,
} from "react-native-gesture-handler/ReanimatedSwipeable"
import { runOnJS, SharedValue, useAnimatedStyle, useSharedValue } from "react-native-reanimated"
import Animated, {
runOnJS,
SharedValue,
useAnimatedStyle,
useSharedValue,
} from "react-native-reanimated"

const FRICTION = 1.2
const SWIPE_TO_INTERACT_THRESHOLD = 80
Expand All @@ -17,6 +22,11 @@ export interface SwipeableProps {
actionBackground?: Color
swipeableProps?: SwipeableProps
enabled?: boolean
/**
* The width of the action component. We need to set it to make the swipeable animation.\
* Make sure to consider font scaling when setting this value.
*/
actionComponentWidth: number
}

export const Swipeable = forwardRef((props: SwipeableProps, swipeableRef: SwipeableRef) => {
Expand All @@ -25,6 +35,7 @@ export const Swipeable = forwardRef((props: SwipeableProps, swipeableRef: Swipea
actionOnPress,
actionOnSwipe,
actionComponent,
actionComponentWidth,
actionBackground = "red100",
enabled = true,
swipeableProps,
Expand All @@ -40,39 +51,52 @@ export const Swipeable = forwardRef((props: SwipeableProps, swipeableRef: Swipea
}

const RightActions = (
_progress: SharedValue<number>,
progress: SharedValue<number>,
dragX: SharedValue<number>,
_swipable: SwipeableMethods
): React.ReactNode => {
useAnimatedStyle(() => {
const animatedStyles = useAnimatedStyle(() => {
"worklet"

const style = {
width: progress.value >= 1 ? -dragX.value - 10 : undefined,
}

// Don't do anything if the action is disabled, if the user has already swiped, or if the width is not yet set (on first render)
if (!actionOnSwipe || hasSwiped.current || !width.value) return {}
if (!actionOnSwipe || hasSwiped.current || !width.value) return style

if (width.value + dragX.value * FRICTION <= SWIPE_TO_INTERACT_THRESHOLD) {
// TODO: Add haptic feedback

runOnJS(handleSwipeToInteract)()

return {}
}

return {}
return style
})

return (
<Touchable haptic onPress={actionOnPress}>
<Flex
flex={1}
ml={1}
p={2}
bg={actionBackground}
justifyContent="center"
border="1px solid"
borderColor={actionBackground}
borderRadius={5}
>
{actionComponent}
<Flex flex={1} ml={1} flexDirection="column" width={actionComponentWidth}>
<Animated.View
style={[
animatedStyles,
{
// Setting the position to absolute to not change the actual width of the action component.
position: "absolute",
top: 0,
right: 0,
height: "100%",
backgroundColor: actionBackground,
borderColor: actionBackground,
borderRadius: 5,
minWidth: actionComponentWidth,
justifyContent: "center",
padding: 10,
},
]}
>
<Flex m="auto">{actionComponent}</Flex>
</Animated.View>
</Flex>
</Touchable>
)
Expand Down
21 changes: 15 additions & 6 deletions src/app/Components/Tasks/Task.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,25 @@ import { Task_task$key } from "__generated__/Task_task.graphql"
import { Swipeable } from "app/Components/Swipeable/Swipeable"
import { navigate } from "app/system/navigation/navigate"
import { useDismissTask } from "app/utils/mutations/useDismissTask"
import { PixelRatio } from "react-native"
import { graphql, useFragment } from "react-relay"

const TASK_IMAGE_SIZE = 60

export const Task: React.FC<{
disableSwipeable?: boolean
onClearTask: () => void
onPress?: () => void
task: Task_task$key
}> = ({ disableSwipeable, onClearTask, ...restProps }) => {
}> = ({ disableSwipeable, onClearTask, onPress, ...restProps }) => {
const { submitMutation: dismissTask } = useDismissTask()
const fontScale = PixelRatio.getFontScale()

const task = useFragment(taskFragment, restProps.task)

const handlePressTask = () => {
if (onPress) return onPress()

// TODO: Add tracking

// TODO: Resolve the task instead of dismissing it.
Expand All @@ -28,17 +33,21 @@ export const Task: React.FC<{

const handleClearTask = async () => {
// TODO: Add tracking

dismissTask({ variables: { taskID: task.internalID } })
onClearTask()
}

return (
<Swipeable
actionComponent={<Text color="white100">Clear</Text>}
actionComponent={
<Text variant="xs" color="white100">
Clear
</Text>
}
actionComponentWidth={80 * fontScale}
actionOnPress={handleClearTask}
actionOnSwipe={handleClearTask}
actionBackground="red100"
actionBackground="#BB392D"
enabled={!disableSwipeable}
>
<Flex backgroundColor="white100" borderRadius={5}>
Expand All @@ -48,7 +57,7 @@ export const Task: React.FC<{
ml={2}
flexDirection="row"
border="1px solid"
borderColor="black60"
borderColor="black100"
borderRadius={5}
zIndex={3}
backgroundColor="black100"
Expand All @@ -57,7 +66,7 @@ export const Task: React.FC<{
<Image src={task.imageUrl} width={TASK_IMAGE_SIZE} height={TASK_IMAGE_SIZE} />
</Flex>

<Flex flex={1}>
<Flex flex={1} pr={1}>
<Text color="white100" variant="xs" fontWeight="bold">
{task.title}
</Text>
Expand Down

0 comments on commit 17ea2b2

Please sign in to comment.