Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Swipeable misalignment after resizing on web #3341

Merged
merged 19 commits into from
Jan 22, 2025

Conversation

latekvo
Copy link
Contributor

@latekvo latekvo commented Jan 16, 2025

Description

This PR allows Swipeable to be dynamically resized.

Fixes #3333

Test plan

  • open any swipeable example on web
  • resize window
  • see how the ReanimatedSwipeable still works, while the legacy Swipeable is misaligned

@latekvo latekvo changed the title Simplify Swipeable logic. Fix Swipeable misalignment after resizing on web Jan 16, 2025
Copy link
Contributor

@blazejkustra blazejkustra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works well on web 👍

@latekvo latekvo marked this pull request as ready for review January 16, 2025 14:01
@latekvo latekvo requested a review from m-bert January 16, 2025 14:03
@latekvo
Copy link
Contributor Author

latekvo commented Jan 16, 2025

This PR breaks the following repro: Fixed.

Collapsed repro
import React from 'react';
import { View, Text } from 'react-native';
import Swipeable from 'react-native-gesture-handler/ReanimatedSwipeable';

export default function Example() {
  return (
    <Swipeable
      leftThreshold={50}
      rightThreshold={50}
      renderLeftActions={() => {
        return (
          <View style={{ height: 80, width: 80, backgroundColor: 'yellow' }}>
            <Text>Left</Text>
          </View>
        );
      }}
      renderRightActions={() => {
        return (
          <View style={{ height: 80, width: 80, backgroundColor: 'magenta' }}>
            <Text>Right</Text>
          </View>
        );
      }}>
      <View
        style={{
          height: 80,
          backgroundColor: 'blue',
        }}
      />
    </Swipeable>
  );
}

@latekvo latekvo marked this pull request as draft January 16, 2025 15:25
@latekvo latekvo marked this pull request as ready for review January 17, 2025 13:52
Copy link
Contributor

@m-bert m-bert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Starting from this commit, you can no longer open swipeable.

Could you check that @blazejkustra?

Nagranie.z.ekranu.2025-01-20.o.11.46.50.mov

@blazejkustra
Copy link
Contributor

I can confirm that it does not work now:

Screen.Recording.2025-01-20.at.12.07.43.mov

@latekvo
Copy link
Contributor Author

latekvo commented Jan 20, 2025

Fixed the issues reported above in 14f14fa.

I take that back. 14f14fa initially seemed to fix both the left and right panels. After a complete rebuild, they started behaving incorrectly again.

@latekvo
Copy link
Contributor Author

latekvo commented Jan 22, 2025

All previous issues should now be fixed since d35e1b3.
Tested on the following repro:

Collapsed repro

make sure to copy AppleStyleSwipeableRow.tsx and GmailStyleSwipeableRow.tsx from example/src/new_api/swipeable to example/src/empty for this repro to work.

import React, { useRef } from 'react';
import { View, Text, StyleSheet, I18nManager } from 'react-native';
import Swipeable, {
  SwipeableMethods,
} from 'react-native-gesture-handler/ReanimatedSwipeable';
import Animated, {
  SharedValue,
  useAnimatedStyle,
} from 'react-native-reanimated';

import AppleStyleSwipeableRow from './AppleStyleSwipeableRow';
import GmailStyleSwipeableRow from './GmailStyleSwipeableRow';
import { Pressable, RectButton } from 'react-native-gesture-handler';

//  To toggle LTR/RTL change to `true`
I18nManager.allowRTL(false);

function LeftAction(prog: SharedValue<number>, drag: SharedValue<number>) {
  const styleAnimation = useAnimatedStyle(() => {
    console.log('[R] showLeftProgress:', prog.value);
    console.log('[R] appliedTranslation:', drag.value);

    return {
      transform: [{ translateX: drag.value - 50 }],
    };
  });

  return (
    <Animated.View style={styleAnimation}>
      <Text style={styles.leftAction}>Text</Text>
    </Animated.View>
  );
}

function RightAction(prog: SharedValue<number>, drag: SharedValue<number>) {
  const styleAnimation = useAnimatedStyle(() => {
    console.log('[R] showRightProgress:', prog.value);
    console.log('[R] appliedTranslation:', drag.value);

    return {
      transform: [{ translateX: drag.value + 50 }],
    };
  });

  return (
    <Animated.View style={styleAnimation}>
      <Text style={styles.rightAction}>Text</Text>
    </Animated.View>
  );
}

export default function Example() {
  const swipeableRef = useRef<SwipeableMethods>(null);

  return (
    <View>
      <View style={styles.controlPanelWrapper}>
        <Text>Programatical controls</Text>
        <View style={styles.controlPanel}>
          <Pressable
            style={styles.control}
            onPress={() => {
              swipeableRef.current!.openLeft();
            }}>
            <Text>open left</Text>
          </Pressable>
          <Pressable
            style={styles.control}
            onPress={() => {
              swipeableRef.current!.close();
            }}>
            <Text>close</Text>
          </Pressable>
          <Pressable
            style={styles.control}
            onPress={() => {
              swipeableRef.current!.reset();
            }}>
            <Text>reset</Text>
          </Pressable>
          <Pressable
            style={styles.control}
            onPress={() => {
              swipeableRef.current!.openRight();
            }}>
            <Text>open right</Text>
          </Pressable>
        </View>
      </View>
      <Swipeable
        ref={swipeableRef}
        containerStyle={styles.swipeable}
        friction={2}
        leftThreshold={80}
        enableTrackpadTwoFingerGesture
        rightThreshold={40}
        renderLeftActions={LeftAction}
        renderRightActions={RightAction}>
        <Text>[Reanimated] Swipe me!</Text>
      </Swipeable>
      <Swipeable
        leftThreshold={50}
        rightThreshold={50}
        renderLeftActions={() => {
          return (
            <View style={{ height: 80, width: 80, backgroundColor: 'yellow' }}>
              <Text>Left</Text>
            </View>
          );
        }}
        renderRightActions={() => {
          return (
            <View style={{ height: 80, width: 80, backgroundColor: 'magenta' }}>
              <Text>Right</Text>
            </View>
          );
        }}>
        <View
          style={{
            height: 80,
            backgroundColor: 'blue',
          }}
        />
      </Swipeable>
      <AppleStyleSwipeableRow>
        <RectButton
          style={styles.rectButton}
          onPress={() => console.log('test-1')}>
          <Text style={styles.fromText}>Test</Text>
        </RectButton>
      </AppleStyleSwipeableRow>
      <GmailStyleSwipeableRow>
        <RectButton
          style={styles.rectButton}
          onPress={() => console.log('test-2')}>
          <Text style={styles.fromText}>Test </Text>
        </RectButton>
      </GmailStyleSwipeableRow>
    </View>
  );
}

const styles = StyleSheet.create({
  leftAction: { width: 50, height: 50, backgroundColor: 'crimson' },
  rightAction: { width: 50, height: 50, backgroundColor: 'purple' },
  rectButton: {
    height: 80,
    paddingVertical: 10,
    paddingHorizontal: 20,
    justifyContent: 'space-between',
    flexDirection: 'column',
    backgroundColor: 'white',
  },
  separator: {
    backgroundColor: 'rgb(200, 199, 204)',
    height: StyleSheet.hairlineWidth,
  },
  fromText: {
    fontWeight: 'bold',
    backgroundColor: 'transparent',
  },
  messageText: {
    color: '#999',
    backgroundColor: 'transparent',
  },
  dateText: {
    backgroundColor: 'transparent',
    position: 'absolute',
    right: 20,
    top: 10,
    color: '#999',
    fontWeight: 'bold',
  },
  swipeable: {
    height: 50,
    backgroundColor: 'papayawhip',
    alignItems: 'center',
  },
  controlPanelWrapper: {
    backgroundColor: 'papayawhip',
    alignItems: 'center',
  },
  controlPanel: {
    backgroundColor: 'papayawhip',
    alignItems: 'center',
    flexDirection: 'row',
  },
  control: {
    flex: 1,
    height: 40,
    borderWidth: StyleSheet.hairlineWidth,
    alignItems: 'center',
    justifyContent: 'center',
  },
});

Unfortunately I had to remove the removeClippedSubviews optimisation performed in #3340 as I couldn't find a way around the conflict between measure() function and transform: [translateX] style.
The pageX is the only useful output of the measure() function, as width cannot be relied upon, due to a possibility to make it always equal to rowWidth as a result of inclusion of flex: 1 inside renderRightActions element.

Copy link
Contributor

@m-bert m-bert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks ok, please wait till @blazejkustra confirms 😅

@latekvo
Copy link
Contributor Author

latekvo commented Jan 22, 2025

Swipeables that don't span 100% of the page width should be fixed since a6536cc.

Test code:

make sure to copy AppleStyleSwipeableRow.tsx and GmailStyleSwipeableRow.tsx from example/src/new_api/swipeable to example/src/empty for this repro to work.

import React, { useRef } from 'react';
import { View, Text, StyleSheet, I18nManager } from 'react-native';
import Swipeable, {
  SwipeableMethods,
} from 'react-native-gesture-handler/ReanimatedSwipeable';
import Animated, {
  SharedValue,
  useAnimatedStyle,
} from 'react-native-reanimated';

import AppleStyleSwipeableRow from './AppleStyleSwipeableRow';
import GmailStyleSwipeableRow from './GmailStyleSwipeableRow';
import { Pressable, RectButton } from 'react-native-gesture-handler';

//  To toggle LTR/RTL change to `true`
I18nManager.allowRTL(false);

function LeftAction(prog: SharedValue<number>, drag: SharedValue<number>) {
  const styleAnimation = useAnimatedStyle(() => {
    console.log('[R] showLeftProgress:', prog.value);
    console.log('[R] appliedTranslation:', drag.value);

    return {
      transform: [{ translateX: drag.value - 50 }],
    };
  });

  return (
    <Animated.View style={styleAnimation}>
      <Text style={styles.leftAction}>Text</Text>
    </Animated.View>
  );
}

function RightAction(prog: SharedValue<number>, drag: SharedValue<number>) {
  const styleAnimation = useAnimatedStyle(() => {
    console.log('[R] showRightProgress:', prog.value);
    console.log('[R] appliedTranslation:', drag.value);

    return {
      transform: [{ translateX: drag.value + 50 }],
    };
  });

  return (
    <Animated.View style={styleAnimation}>
      <Text style={styles.rightAction}>Text</Text>
    </Animated.View>
  );
}

export default function Example() {
  const swipeableRef = useRef<SwipeableMethods>(null);

  return (
    <View style={{ backgroundColor: 'pink' }}>
      <View style={styles.controlPanelWrapper}>
        <Text>Programatical controls</Text>
        <View style={styles.controlPanel}>
          <Pressable
            style={styles.control}
            onPress={() => {
              swipeableRef.current!.openLeft();
            }}>
            <Text>open left</Text>
          </Pressable>
          <Pressable
            style={styles.control}
            onPress={() => {
              swipeableRef.current!.close();
            }}>
            <Text>close</Text>
          </Pressable>
          <Pressable
            style={styles.control}
            onPress={() => {
              swipeableRef.current!.reset();
            }}>
            <Text>reset</Text>
          </Pressable>
          <Pressable
            style={styles.control}
            onPress={() => {
              swipeableRef.current!.openRight();
            }}>
            <Text>open right</Text>
          </Pressable>
        </View>
      </View>
      <Swipeable
        ref={swipeableRef}
        containerStyle={styles.swipeable}
        friction={2}
        leftThreshold={80}
        enableTrackpadTwoFingerGesture
        rightThreshold={40}
        renderLeftActions={LeftAction}
        renderRightActions={RightAction}>
        <Text>[Reanimated] Swipe me!</Text>
      </Swipeable>
      <Swipeable
        leftThreshold={50}
        rightThreshold={50}
        renderLeftActions={() => {
          return (
            <View style={{ height: 80, width: 80, backgroundColor: 'yellow' }}>
              <Text>Left</Text>
            </View>
          );
        }}
        renderRightActions={() => {
          return (
            <View style={{ height: 80, width: 80, backgroundColor: 'magenta' }}>
              <Text>Right</Text>
            </View>
          );
        }}>
        <View
          style={{
            height: 80,
            backgroundColor: 'blue',
          }}
        />
      </Swipeable>
      <View style={{ marginLeft: 60 }}>
        <AppleStyleSwipeableRow>
          <RectButton
            style={styles.rectButton}
            onPress={() => console.log('test-1-center')}>
            <Text style={styles.fromText}>Test Center 1</Text>
          </RectButton>
        </AppleStyleSwipeableRow>
        <GmailStyleSwipeableRow>
          <RectButton
            style={styles.rectButton}
            onPress={() => console.log('test-2-center')}>
            <Text style={styles.fromText}>Test Center 2</Text>
          </RectButton>
        </GmailStyleSwipeableRow>
      </View>
      <View style={{ marginRight: 60 }}>
        <AppleStyleSwipeableRow>
          <RectButton
            style={styles.rectButton}
            onPress={() => console.log('test-1-center')}>
            <Text style={styles.fromText}>Test Center 1</Text>
          </RectButton>
        </AppleStyleSwipeableRow>
        <GmailStyleSwipeableRow>
          <RectButton
            style={styles.rectButton}
            onPress={() => console.log('test-2-center')}>
            <Text style={styles.fromText}>Test Center 2</Text>
          </RectButton>
        </GmailStyleSwipeableRow>
      </View>
      <View style={{ marginLeft: 40, marginRight: 40 }}>
        <AppleStyleSwipeableRow>
          <RectButton
            style={styles.rectButton}
            onPress={() => console.log('test-1-center')}>
            <Text style={styles.fromText}>Test Center 1</Text>
          </RectButton>
        </AppleStyleSwipeableRow>
        <GmailStyleSwipeableRow>
          <RectButton
            style={styles.rectButton}
            onPress={() => console.log('test-2-center')}>
            <Text style={styles.fromText}>Test Center 2</Text>
          </RectButton>
        </GmailStyleSwipeableRow>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  leftAction: { width: 50, height: 50, backgroundColor: 'crimson' },
  rightAction: { width: 50, height: 50, backgroundColor: 'purple' },
  rectButton: {
    height: 80,
    paddingVertical: 10,
    paddingHorizontal: 20,
    justifyContent: 'space-between',
    flexDirection: 'column',
    backgroundColor: 'white',
  },
  separator: {
    backgroundColor: 'rgb(200, 199, 204)',
    height: StyleSheet.hairlineWidth,
  },
  fromText: {
    fontWeight: 'bold',
    backgroundColor: 'transparent',
  },
  messageText: {
    color: '#999',
    backgroundColor: 'transparent',
  },
  dateText: {
    backgroundColor: 'transparent',
    position: 'absolute',
    right: 20,
    top: 10,
    color: '#999',
    fontWeight: 'bold',
  },
  swipeable: {
    height: 50,
    backgroundColor: 'papayawhip',
    alignItems: 'center',
  },
  controlPanelWrapper: {
    backgroundColor: 'papayawhip',
    alignItems: 'center',
  },
  controlPanel: {
    backgroundColor: 'papayawhip',
    alignItems: 'center',
    flexDirection: 'row',
  },
  control: {
    flex: 1,
    height: 40,
    borderWidth: StyleSheet.hairlineWidth,
    alignItems: 'center',
    justifyContent: 'center',
  },
});

@blazejkustra
Copy link
Contributor

It's working fine now, thanks @latekvo!

@latekvo latekvo merged commit 44f6315 into main Jan 22, 2025
1 check passed
@latekvo latekvo deleted the @latekvo/simplify-swipeable-logic branch January 22, 2025 16:48
latekvo added a commit that referenced this pull request Jan 23, 2025
## Description

This PR allows Swipeable to be dynamically resized.

Fixes #3333

## Test plan

- open any swipeable example on web
- resize window
- see how the `ReanimatedSwipeable` still works, while the legacy
`Swipeable` is misaligned
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ReanimatedSwipeable doesn't display right actions properly on window resize
3 participants