RN 表情符号键盘在 React Native 中出现问题它在键盘上不起作用,避免像输入字段隐藏在下面

问题描述 投票:0回答:1

我正在开发聊天应用程序。我想像 WhatsApp 一样添加表情符号键盘。我的设计有问题。如果我打开表情符号键盘,我的输入字段会隐藏在此处,但如果打开系统键盘,它可以正常工作,这是我的代码。

我需要像系统键盘和 WhatsApp GUI 一样显示此键盘。请给我任何建议,因为我是反应原生的新手。

import React, { useEffect, useState,useRef  } from 'react';
import { View, Text, StyleSheet, ImageBackground, TextInput, 
    FlatList, 
    TouchableOpacity,
     Modal, 
     Alert,
     Platform ,
     ScrollView,
     Animated,Easing,
     TouchableWithoutFeedback,
     KeyboardAvoidingView,Keyboard ,Dimensions } from 'react-native';
import moment from 'moment';
import { useNavigation } from '@react-navigation/native';
import  {EmojiKeyboard } from 'rn-emoji-keyboard';

Sound.setCategory('Playback');
const { height: screenHeight, width: screenWidth } = Dimensions.get('window');


const SingleChat: React.FC<NativeStackScreenProps<NavigationProps, 'SingleChat'>> = ({ route }) => {
    const userData = useSelector((state: RootState) => state.user.userData);
 
    const { receiverData } = route.params;
    const [isConnected, setIsConnected] = useState<boolean>(false);
    
    const [msg, setMsg] = useState<string>('');
    const [disabled, setDisabled] = useState<boolean>(false);
    const [allChat, setAllChat] = useState<ChatMessage[]>([]);
    const [voiceRecordVisible, setVoiceRecordVisible] = useState<boolean>(true);
    const [modalVisible, setModalVisible] = useState<boolean>(false);
    const [newMessageId, setNewMessageId] = useState<string | null>(null);
    const isFocused = useIsFocused();
    const [ReceipentID, setReceipentID] = useState<string>('');

    const [isRecording, setIsRecording] = useState(false);
    const [recordingPath, setRecordingPath] = useState('');

    const [AttachmentMenuvisible, setAttachmentMenuvisible] = useState(false);
  
    const [menuPosition, setMenuPosition] = useState(0); 


    const [keyboardOffset, setKeyboardOffset] = useState(0);
    const [keyboardHeight, setkeyboardHeight] = useState(0);
  
    const [RecentDownloadedMessageID,SetDownloadedMessageID]=useState<string>('');
    const [showEmoji, setShowEmoji] = useState(false); 
    const [isOpen, setIsOpen] = React.useState<boolean>(false)
    const footerPosition = useRef(new Animated.Value(0)).current;
    const [selectedAttachment, setSelectedAttachment] = useState<{
        uri: string;
        type: string;
        name?: string;
      } | null>(null); 
const navigation =useNavigation();
    const dispatch = useDispatch();
       
    
  

    const slideAnim = useRef(new Animated.Value(300)).current; // Initial position below the screen

    const openMenu = () => {
        console.log('HERE i AM shOWING MENU  ')
        setAttachmentMenuvisible(true);
        Animated.timing(slideAnim, {
            toValue: 0,
            duration: 300,
            easing: Easing.ease,
            useNativeDriver: true,
        }).start();
    };

    const closeMenu = () => {
        Animated.timing(slideAnim, {
            toValue: 200, // Slide back down out of view
            duration: 300,
            easing: Easing.ease,
            useNativeDriver: true,
        }).start(() => {
            setAttachmentMenuvisible(false);
        });
       
    };

    const loadMessagesLocally = (roomId: string) => {
        console.log(roomId);
        try
        {
        getMessages(roomId, (messages: ChatMessage[]) => {
            const sortedMessages = messages.sort((a, b) => new Date(b.sendTime).getTime() - new Date(a.sendTime).getTime());
            setAllChat(sortedMessages);
            
        });
    }catch(error)
    {
        console.error('An Error Occured While Fetching local Messages',error);
    }
    };

    
       

     
  useEffect(() => {
 
    const handleConnectivityChange = (state: NetInfoState) => {
      setIsConnected(state.isConnected ?? false);
     
    };

   
    const unsubscribe = NetInfo.addEventListener(handleConnectivityChange);

   
    NetInfo.fetch().then((state) => {
      handleConnectivityChange(state);
    });

  
    return () => {
      unsubscribe();
    };
  }, []);

  useEffect(() => {
    const keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', (event) => {
        const keyboardHeight = event.endCoordinates.height;
        console.log('here is the main heigh',keyboardHeight)

      setkeyboardHeight(keyboardHeight)
        
        setKeyboardOffset(keyboardHeight*0.1);
        console.log('here is the main Scree height',keyboardOffset)
    });

    const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => {
      setKeyboardOffset(0);
    });

    return () => {
      keyboardDidShowListener.remove();
      keyboardDidHideListener.remove();
    };
  }, []);






    useEffect(() => {

        console.log('Single Chat Use Effect Getting invoked...1')
        const loadAndCompareMessages = async () => {
            try {
                
                const localMessages = await new Promise<ChatMessage[]>((resolve, reject) => {
                    getMessages(receiverData.roomId, (messages: ChatMessage[]) => {
                        resolve(messages);
                    });
                });
    
              
                
                const LocalsortedMessages = localMessages.sort((a, b) => {
                   
                    const dateA = new Date(a.sendTime).getTime();
                    const dateB = new Date(b.sendTime).getTime();
                  
                  
                    if (isNaN(dateA) || isNaN(dateB)) {
                      console.warn('Invalid date format detected:', a.sendTime, b.sendTime);
                      return 0; 
                    }
                  
                 
                    return dateB - dateA ;
                  });
                  setAllChat(LocalsortedMessages);
             
                if (isConnected) {
                    const messagesRef = ref(db, `/messages/${receiverData.roomId}`);
                    const snapshot = await get(messagesRef);
                    const firebaseMessages: ChatMessage[] = [];
                    snapshot.forEach((childSnapshot) => {
                        const message = childSnapshot.val() as ChatMessage;
                        firebaseMessages.push(message);
                    });
    
                   
                    const sortedFirebaseMessages = firebaseMessages.sort((a, b) => new Date(a.sendTime).getTime() - new Date(b.sendTime).getTime());
                    const localMessageIds = new Set(localMessages.map(msg => msg.localmsgid));
                    const validFirebaseMessages = sortedFirebaseMessages.filter(msg => msg.status !== 'uploading');

                    const newMessages = validFirebaseMessages.filter(msg => !localMessageIds.has(msg.localmsgid));
    
                    if (newMessages.length > 0) {
                        await saveMessages(newMessages);
                    }
    
                   
                    const allMessages = [...localMessages, ...newMessages].sort((a, b) => new Date(b.sendTime).getTime() - new Date(a.sendTime).getTime());

                 

                    setAllChat(allMessages);
                }
            } catch (error) {
                console.error('An error occurred while loading and comparing messages:', error);
            }
        };
    
        loadAndCompareMessages();
    
        const messagesRef = ref(db, `/messages/${receiverData.roomId}`);
        const unsubscribe = onValue(messagesRef, loadAndCompareMessages);
    
        return () => unsubscribe();
    }, [isConnected, userData.uid, receiverData.roomId,RecentDownloadedMessageID]);
    

    const [recordingAnimation] = useState(new Animated.Value(0));

            const startAnimation = () => {
                Animated.loop(
                    Animated.sequence([
            Animated.timing(recordingAnimation, {
                toValue: 1,
                duration: 500,
                easing: Easing.inOut(Easing.ease),
                useNativeDriver: true,
            }),
            Animated.timing(recordingAnimation, {
                toValue: 0,
                duration: 500,
                easing: Easing.inOut(Easing.ease),
                useNativeDriver: true,
            }),
        ])
    ).start();
};
const addEmoji = (emoji: EmojiType) => {
    setMsg((prev) => prev + emoji.emoji);
   
    if (!showEmoji) {
      Keyboard.dismiss();
    }
  };
const stopAnimation = () => {
    recordingAnimation.stopAnimation();
    recordingAnimation.setValue(0);
};

const startRecording = async () => {
    try {
        const FileNameRandNumber = uuid.v4();
        const path = Platform.select({
            ios: `file://${RNFS.DocumentDirectoryPath}/${FileNameRandNumber}.m4a`,
            android: `${RNFS.ExternalDirectoryPath}/${FileNameRandNumber}.mp3`,
        });

        const microphonePermission = Platform.select({
            ios: PERMISSIONS.IOS.MICROPHONE,
            android: PERMISSIONS.ANDROID.RECORD_AUDIO,
        });

      

        if (!microphonePermission) {
            Alert.alert('Error', 'Required permissions are not available for this platform.');
            return;
        }

       
        const micGranted = await request(microphonePermission);

      
        if (micGranted === RESULTS.GRANTED) {
            if (isRecording) {
                await stopRecording();
            }

            const directoryPath = `${RNFS.CachesDirectoryPath}`; // Adjust if necessary
            const cleanFileName = `${FileNameRandNumber}`;
            const recordingPath = `${directoryPath}/${cleanFileName}`;

            const directoryExists = await RNFS.exists(directoryPath);
            if (!directoryExists) {
                await RNFS.mkdir(directoryPath);
            }

          
            setIsRecording(true);
            startAnimation();
            const uri =  await audioRecorderPlayer.startRecorder(path);
            setRecordingPath(uri);
        } else {
            Alert.alert('Permission Denied', 'Please allow microphone and storage access.');
        }
    } catch (error) {
        console.error('Error starting recording:', {
            message: error.message,
            stack: error.stack,
            name: error.name,
        });
        Alert.alert('Recording Error', 'An error occurred while starting the recording.');
    }
};


  const stopRecording = async () => {
    try {
      if (!isRecording) {
        console.warn('Recording is not active. Cannot stop.');
        return;
      }
  
      console.log('Stopping recording...');
      const result = await audioRecorderPlayer.stopRecorder();
      audioRecorderPlayer.removeRecordBackListener();
      setIsRecording(false);
      stopAnimation();
  
      if (result) {
        console.log('Recording stopped successfully:', recordingPath);
        await uploadAudioToFirebase(recordingPath); // Use the existing recording path
      } else {
        console.warn('Recording result is null or undefined.');
      }
    } catch (error) {
      console.error('Error stopping recording:', error);
      Alert.alert('Recording Error', 'An error occurred while stopping the recording.');
      setIsRecording(false);
    }
  };
  const toggleEmojiKeyboard = () => {
    setShowEmoji((prev) => !prev);
    if (showEmoji) {
      // Hide emoji picker and move footer down
      Animated.timing(footerPosition, {
        toValue: 0,
        duration: 300,
        useNativeDriver: false
      }).start();
    } else {
      
        Keyboard.dismiss();
      // Show emoji picker and move footer up
      Animated.timing(footerPosition, {
        toValue: screenHeight * 0.4, // Move up by 40% of the screen height (emoji picker height)
        duration: 300,
        useNativeDriver: false
      }).start();
    }
  };
 

 
    const msgValid = (txt: string) => txt && txt.trim().length > 0;

    const handleSendMsg = () => {
        if (!msgValid(msg)) {
            SimpleToast.show('Enter something....', 9000);
            return;
        }
        setDisabled(true);
        sendMessage(msg, receiverData, userData, setMsg, setDisabled,'','text','','',0);
    };

    const pickImage = () => {
        launchImageLibrary({ mediaType: 'photo', quality: 1 }, async (response) => {
            if (response.assets && response.assets.length > 0) {
                const asset = response.assets[0];
                if (asset.uri) {
                 
                    const blob = await fetch(asset.uri).then(r => r.blob());
                    const fileName = asset.fileName || `${uuid.v4()}.jpg`; 
                    const fileType = asset.type || 'image/jpeg';
                    const fileSize = blob.size; 
                    
                    navigation.navigate('AttachmentPreview', { attachmentUri: asset.uri, attachmentType: 'image',recipientName:receiverData.name,receiverData:receiverData,userData:userData,responcePicker:asset });

                     
                    try {
                      
                     
                        closeMenu();
                    } catch (error) {
                        console.error('Error uploading image:', error);
                        closeMenu();
                        setDisabled(false);
                    }
                }
            }
        });
    };
  
    const AcknowledgeDownloadFile=async (messageID:string,FileDownloaded:boolean)=>{

        if(FileDownloaded)
        {
            SetDownloadedMessageID(messageID);
        }


    }
    const dismissEmojiPicker = () => {
        if (isOpen) {
          setIsOpen(false);
          setShowEmoji(false);
        }
      };

    const pickDocument = async () => {
        try {
           
            const res = await DocumentPicker.pick({
                type: [
                    'application/pdf', 
                    'application/msword', 
                    'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
                    'video/mp4',
                    'video/x-msvideo', 
                    'video/quicktime' 
                ],
            });
            if (res && res.length > 0) {
                const { uri, name, type, size } = res[0]; 
               
                const cleanFileName = name??"".replace(/[^a-zA-Z0-9_.]/g, '_');

             
                const sentFolderPath = `${RNFS.ExternalDirectoryPath}/ChipChap Sent`;
    
                // Create the directory if it doesn't exist
                const folderExists = await RNFS.exists(sentFolderPath);
                if (!folderExists) {
                    await RNFS.mkdir(sentFolderPath);
                }
    
               
                const targetPath = `${sentFolderPath}/${cleanFileName}`;
    
                await RNFS.copyFile(uri, targetPath);
    
                const localFileUri = 'file://' + targetPath;
                const attachmentType = type?.includes('pdf') ? 'pdf' : 
                             type?.includes('video') ? 'video' : 
                             'document';
                navigation.navigate('AttachmentPreview', { attachmentUri: localFileUri, attachmentType: attachmentType,recipientName:receiverData.name,receiverData:receiverData,userData:userData,responcePicker:res });

                // if (type?.includes('video')) {
                //     sendMessage('', receiverData, userData, setMsg, setDisabled, 'video', 'video', type ?? '', name ?? '', size??0,uri,localFileUri);
                // } else {
                //     sendMessage('', receiverData, userData, setMsg, setDisabled, 'document', 'document', type ?? '', name ?? '', size??0,uri,localFileUri);
                // }
                closeMenu();
            }
        } catch (err) {
            if (DocumentPicker.isCancel(err)) {
                closeMenu();
            } else {
                console.error('Document Picker Error:', err);
            }
        }
    };

    

    return (
        <TouchableWithoutFeedback onPress={toggleEmojiKeyboard}>
        <View
      style={styles.container}
    >
        
            <ChatHeader data={receiverData} />
         
            <ImageBackground
                source={require('../assets/ChatsList.jpg')}
                style={{ flex: 1 }}
            >
               
                <FlatList
                    style={{ flex: 1 }}
                    data={allChat}
                    showsVerticalScrollIndicator={false}
                    keyExtractor={(item) => item.localmsgid ?? Math.random().toString()}
                    inverted
                    renderItem={({ item }) => (
                        <MsgComponent
                            sender={item.messageFrom === userData.uid}
                            item={{
                                message: item.message,
                                sendTime: item.sendTime,
                                seen: item.IsSeen,
                                msgType: item.msgType,
                                id: item.id,
                                localURL: item.localURL??"",
                                localmsgid: item.localmsgid,
                                documentType: item.documentType,
                                documentName: item.documentName,
                                documentSize: item.documentSize,
                                SenderName:receiverData.name,
                                caption:item.caption
                              }}
                            isNew={item.id === newMessageId}
                            AcknowledgeDownloadFile={AcknowledgeDownloadFile}
                        />
                    )}
                />
               
            </ImageBackground>
            <KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : undefined}>
            
            <View style={styles.footer}>
            
            <Ionicons
          name="attach"
          size={32}
          color={colors.white}
          onPress={openMenu}
        />

        {AttachmentMenuvisible && (
          <Animated.View style={[styles.menuContainer, { transform: [{ translateX: slideAnim }] }]}>
            <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.menuScroll}>
            
              <TouchableOpacity style={styles.menuItem} onPress={() => console.log('Contacts')}>
                <Ionicons name="person-outline" size={24} color={colors.black} />
                <Text>Contacts</Text>
              </TouchableOpacity>
              <TouchableOpacity style={styles.menuItem} onPress={() =>pickImage()}>
                <Ionicons name="images-outline" size={24} color={colors.black} />
                <Text>Gallery</Text>
              </TouchableOpacity>
              <TouchableOpacity style={styles.menuItem} onPress={() => pickDocument()}>
                <Ionicons name="attach-outline" size={24} color={colors.black} />
                <Text>Attachments</Text>
              </TouchableOpacity>
              <TouchableOpacity style={styles.menuItem} onPress={() => console.log('Location')}>
                <Ionicons name="location-outline" size={24} color={colors.black} />
                <Text>Location</Text>
              </TouchableOpacity>
            </ScrollView>
          </Animated.View>
        )}
                    
                   
                
                <View style={styles.textInputWrapper}>
                <ScrollView>
                <TextInput
                    style={styles.input}
                    placeholder="type a message"
                    placeholderTextColor={colors.black}
                    multiline={true}
                    value={msg}
                    onChangeText={setMsg}
                    onFocus={() => {
                        setVoiceRecordVisible(false);
                        if (showEmoji) {
                            setShowEmoji(false); 
                            Animated.timing(footerPosition, {
                                toValue: 0,
                                duration: 300,
                                useNativeDriver: false
                              }).start();
                          }
                    }}
                    onBlur={() => {
                        setVoiceRecordVisible(true);
                    }}
                />
                 </ScrollView>
                 
             
                 </View>
                 <TouchableOpacity style={styles.iconContainer} onPress={toggleEmojiKeyboard}>
          <Ionicons name={showEmoji ? "keypad-outline" : "happy-outline"} size={32} color={colors.white} /> 
        </TouchableOpacity>
                {msg.trim() !== '' ? (
                    <TouchableOpacity disabled={disabled} onPress={handleSendMsg} style={styles.iconContainer}>
                        <Ionicons
                            name="paper-plane-sharp"
                            size={32}
                            color={colors.white}
                        />
                    </TouchableOpacity>
                ) :  voiceRecordVisible && !isRecording && (
                    <TouchableOpacity style={styles.iconContainer} onPress={startRecording}>
                      <Ionicons
                        name="mic"
                        size={32}
                        color={colors.white}
                      />
                    </TouchableOpacity>
                  )}
                  
                  {isRecording && (
                    <TouchableOpacity style={styles.iconContainer} onPress={stopRecording}>
                      <Ionicons
                        name="stop"
                        size={32}
                        color={colors.white}
                      />
                    </TouchableOpacity>
                  )}
                  {isRecording && (
                    <Animated.View
                        style={[
                            styles.recordingIndicator,
                            {
                                opacity: recordingAnimation,
                                transform: [
                                    {
                                        scale: recordingAnimation.interpolate({
                                            inputRange: [0, 1],
                                            outputRange: [1, 1.2],
                                        }),
                                    },
                                ],
                            },
                        ]}
                    >
                        <Text style={styles.recordingText}>Recording...</Text>
                    </Animated.View>
                )}
                 {showEmoji && (
     <View style={styles.emojiContainer}>
   <EmojiKeyboard onEmojiSelected={addEmoji} />

   </View>
)}
               
            </View>
           

            </KeyboardAvoidingView>
             
 

            <Modal
                animationType="slide"
                transparent={true}
                visible={modalVisible}
                onRequestClose={() => {
                    setModalVisible(!modalVisible);
                }}
            >
                <View style={styles.modalContainer}>
                    <TouchableOpacity onPress={() => { setModalVisible(false); pickImage(); }} style={styles.modalButton}>
                        <Ionicons name="image" size={24} color={colors.black} />
                        <Text style={styles.modalText}>Gallery</Text>
                    </TouchableOpacity>
                    <TouchableOpacity onPress={() => { setModalVisible(false); pickDocument(); }} style={styles.modalButton}>
                        <Ionicons name="document-text" size={24} color={colors.black} />
                        <Text style={styles.modalText}>Document</Text>
                    </TouchableOpacity>
                    <TouchableOpacity onPress={() => setModalVisible(false)} style={styles.modalButton}>
                        <Ionicons name="close" size={24} color={colors.black} />
                        <Text style={styles.modalText}>Cancel</Text>
                    </TouchableOpacity>
                </View>
            </Modal>

          
            </View>
            </TouchableWithoutFeedback>
    );
};



const styles = StyleSheet.create({
    container: {
        flex: 1,
    },
    bgImage: {
        flex: 1,
        resizeMode: 'cover',
    },
    inputContainer: {
        flexDirection: 'row',
        alignItems: 'center',
        padding: 10,
    },
    textInput: {
        flex: 1,
        backgroundColor: '#fff',
        borderRadius: 20,
        paddingHorizontal: 10,
        paddingVertical: 5,
    },
    iconContainer: {
        padding: 10,
    },
    modalContainer: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: 'rgba(0,0,0,0.5)',
    },
    modalButton: {
        backgroundColor: colors.white,
        padding: 20,
        borderRadius: 10,
        marginVertical: 5,
        flexDirection: 'row',
        alignItems: 'center',
        width: '80%',
        justifyContent: 'space-between',
    },
    modalText: {
        fontSize: 18,
        color: colors.black,
    },
    footer: {
        backgroundColor: colors.theme,
        elevation: 5,
        flexDirection: 'row',
        alignItems: 'center',
        justifyContent: 'space-between',
        paddingHorizontal: 10,
        paddingVertical: 5,
    },
    input: {
        flex: 1,
        backgroundColor: colors.white,
        borderRadius: 20,
        paddingHorizontal: 10,
        marginHorizontal: 10,
        color: colors.black,
    },
    textInputWrapper: {
        flex: 1,
        maxHeight: 120, 
      },
      menuContainer: {
        position: 'absolute',
        bottom: 60,
        backgroundColor: 'white',
        borderRadius: 8,
        shadowColor: '#000',
        shadowOffset: { width: 0, height: 2 },
        shadowOpacity: 0.25,
        shadowRadius: 3.84,
        elevation: 5,
      },
      menuScroll: {
        flexDirection: 'row',
        padding: 10,
      },
      menuItem: {
        alignItems: 'center',
        marginHorizontal: 10,
      },
      recordingIndicator: {
        position: 'absolute',
        left: '50%',
        top: '50%',
        transform: [{ translateX: -50 }, { translateY: -50 }],
        backgroundColor: 'rgba(255,0,0,0.7)',
        padding: 10,
        borderRadius: 50,
        justifyContent: 'center',
        alignItems: 'center',
    },
    recordingText: {
        color: colors.white,
        fontWeight: 'bold',
    },
    emojiContainer: {
        position: 'absolute',
        bottom: 0,
        left: 0,
        right: 0,
        height: screenHeight * 0.4, 
        backgroundColor: 'white',
    },
    iconButton: {
        padding: 10,
      },
    });

export default SingleChat;

我尝试使用动画视图,但这并没有给我带来 Whatsapp GUI 的感觉,我希望自动将我的文本输入字段滑到表情符号键盘上方。 请让我知道如何避免这种情况,因为我希望我的表情符号出现在键盘输入的正下方。

react-native chat whatsapp
1个回答
0
投票

尝试将代码包装到 KeyboardAviodingView

<KeyboardAvoidingView
      behavior={Platform.OS === 'ios' ? 'height' : 'padding'}
      keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : -keyboardHeight}
      style={{
        flex: 1,
      }}>{...your code}
</KeyboardAvoidingView>
© www.soinside.com 2019 - 2024. All rights reserved.