TheWidlarzGroup / rn-emoji-keyboard

Super performant, lightweight, fully customizable emoji picker 🚀
https://thewidlarzgroup.github.io/rn-emoji-keyboard/
MIT License
323 stars 55 forks source link

Emoji selection need double tap when we search an emoji #155

Open suman379 opened 9 months ago

suman379 commented 9 months ago

Describe the bug Need to double tap on emoji to select the emoji when you search.

To Reproduce Steps to reproduce the behavior:

  1. Search an emoji
  2. Click on emoji you want.
  3. On first tap it will close the keyboard first.
  4. Then you need to tap on emoji again

Expected behavior It should select the emoji on first tap even keyboard is selected.

jan-kozinski commented 9 months ago

Hey, I've been trying to reproduce without success. Can you specify if it was android / ios? Which version and what device? I tested on Android emulator Pixel 7 Pro and iOS simulator with iPhone 14 Pro. It would help if you attached a recording please

Okelm commented 2 months ago

The first tap on the apple closed the keyboard while it should have actually chosen the apple and close the modal https://github.com/TheWidlarzGroup/rn-emoji-keyboard/assets/14316935/9a90e16d-0555-474b-9989-c8da5cc19b27

efstathiosntonas commented 2 months ago

this must be one of the oldest bugs in react native where keyboardShouldPersistTaps="handled" does not work inside a Modal and this package relies on it.

efstathiosntonas commented 2 months ago

Only solution that works fine is this:

SingleEmoji.tsx:

import React, { memo } from "react";
import { TouchableOpacity, View, Text } from "react-native";

import { Gesture, GestureDetector } from "react-native-gesture-handler";
import Animated, {
  measure,
  runOnJS,
  runOnUI,
  useAnimatedRef
} from "react-native-reanimated";
import { createStyleSheet, useStyles } from "react-native-unistyles";

import type { StyleProp, ViewStyle } from "react-native";
import type { EmojiSizes, JsonEmoji } from "../types";

const AnimatedTouchable = Animated.createAnimatedComponent(TouchableOpacity);

type Props = {
  emojiSize: number;
  index: number;
  isSelected?: boolean;
  item: JsonEmoji;
  onLongPress: (emoji: JsonEmoji, emojiIndex: number, emojiSizes: EmojiSizes) => void;
  onPress: (emoji: JsonEmoji) => void;
  selectedEmojiStyle?: StyleProp<ViewStyle>;
};

export const SingleEmoji = memo(
  (p: Props) => {
    const animatedRef = useAnimatedRef();
    const { styles } = useStyles(stylesheet);
    const handlePress = () => p.onPress(p.item);

    const handleLongPress = () => {
      runOnUI(() => {
        const layout = measure(animatedRef);
        if (layout === null) {
          return;
        }

        runOnJS(p.onLongPress)(p.item, p.index, {
          width: layout.width,
          height: layout.height
        });
      })();
    };

    const longGesture = Gesture.LongPress().onStart(() => {
      runOnJS(handleLongPress)();
    });

    const gesture = Gesture.Tap().onEnd(() => {
      runOnJS(handlePress)();
    });

    return (
 <GestureHandlerRootView style={styles.flex}> <=== even though the whole app might be wrapped with this, Android will fail to recognize taps for some f reason if you don't wrap it
      <GestureDetector gesture={gesture}>
        <GestureDetector gesture={longGesture}>
          <AnimatedTouchable
            accessibilityRole="button"
            activeOpacity={0.8}
            // @ts-ignore
            ref={animatedRef}
            style={styles.button}
          >
            <View style={[styles.emojiWrapper, p.selectedEmojiStyle]}>
              <Text style={[styles.emoji, { fontSize: p.emojiSize }]}>
                {p.item.emoji}
              </Text>
            </View>
          </AnimatedTouchable>
        </GestureDetector>
      </GestureDetector>
 <GestureHandlerRootView>
    );
  },
  (prevProps, nextProps) => prevProps.isSelected === nextProps.isSelected
);

SingleEmoji.displayName = "SingleEmoji";

const stylesheet = createStyleSheet((theme) => ({
  button: {
    alignItems: "center",
    flex: 1,
    justifyContent: "center",
    padding: theme.spacing.mvs4
  },
  emojiWrapper: {
    padding: theme.spacing.mvs4
  },
  emoji: { color: "#000" },
  flex: {
   flex: 1
  }
}));

SingleSkinTone.tsx:

import React, { Component } from "react";
import { Pressable, StyleSheet, View, Text} from "react-native";

import { Gesture, GestureDetector } from "react-native-gesture-handler";
import { runOnJS } from "react-native-reanimated";

import type { JsonEmoji } from "../types";

export class SingleSkinTone extends Component<{
  emojiSize: number;
  item: JsonEmoji;
  onLongPress: () => void;
  onPress: () => void;
}> {
  shouldComponentUpdate() {
    return false;
  }

  render() {
    const { item, emojiSize, onPress } = this.props;

    const gesture = Gesture.Tap().onEnd(() => {
      runOnJS(onPress)();
    });

    return (
 <GestureHandlerRootView style={styles.flex}>
      <GestureDetector gesture={gesture}>
        <Pressable
          accessibilityRole="button"
          style={({ pressed }) => [styles.container, { opacity: pressed ? 0.7 : 1 }]}
        >
          <View style={styles.iconContainer}>
            <Text style={[styles.emoji, { fontSize: emojiSize }]}>{item.emoji}</Text>
          </View>
        </Pressable>
      </GestureDetector>
</GestureHandlerRootView>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    padding: 0
  },
  emoji: {
    color: "#000"
  },
 flex: {
  flex:1
 },
  iconContainer: {
    alignItems: "center",
    justifyContent: "center"
  }
});