enx-uikit-react-native
Version:
It is a react native component for Enablex users.
365 lines (345 loc) • 12.9 kB
JavaScript
import React, { PureComponent,createRef } from "react";
import { View,
Text,
FlatList,
TextInput,
TouchableOpacity,
Dimensions,
Keyboard,
KeyboardAvoidingView,
Platform,
TouchableWithoutFeedback,
Image,
SafeAreaView,
StatusBar} from "react-native";
import { format } from 'date-fns';
import { LinkPreview } from '@flyerhq/react-native-link-preview'
import { styles } from "../style/EnxChatScreenStyle";
import { EnxSetting } from "..";
class EnxChatScreen extends PureComponent {
constructor(props) {
super(props);
this.state={
selfClientId: this.props.selfClientId,//this.props.selfClientId,
participantClientId:this.props.participantClientId,
chatType:this.props.chatType,
newMessage: '',
keyboardOffset: 0,
screenWidth: Dimensions.get('window').width, // Current screen width
screenHeight: Dimensions.get('window').height,
shouldScrollToBottom: true, // Flag to indicate if we should scroll
}
this.flatListRef = createRef();
//this.isKeyboardVisible = false; // Track if the keyboard is visible
}
componentDidMount() {
// Add keyboard listeners to adjust the view when the keyboard is opened or closed
this.keyboardDidShowListener = Keyboard.addListener("keyboardDidShow", this._keyboardDidShow);
this.keyboardDidHideListener = Keyboard.addListener("keyboardDidHide", this._keyboardDidHide);
// Listen to orientation changes
this.dimensionsListener = Dimensions.addEventListener("change", this.handleOrientationChange);
//Add listener for notification
EnxSetting.addEventListener('ChatPageBack', this.handleBackEvent);
// Set initial state for proper keyboard handling
this.setState({
keyboardOffset: 0,
shouldScrollToBottom: true
});
}
componentWillUnmount() {
// Remove listeners when the component is unmounted
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
this.dimensionsListener.remove();
EnxSetting.removeEventListener('ChatPageBack', this.handleBackEvent);
}
//Handle notification lisner from EnxSetting class
handleBackEvent = (data) => {
this.props.onBack();
};
// Handle orientation change
handleOrientationChange = ({ window: { width, height } }) => {
this.setState({
screenWidth: width,
screenHeight: height,
});
};
// Scroll to the bottom of the list when the keyboard is opened
_keyboardDidShow = (event) => {
const keyboardHeight = event.endCoordinates.height;
console.log("🎹 Keyboard shown, height:", keyboardHeight);
this.setState({ keyboardOffset: keyboardHeight }, () => {
// Delay scroll to ensure layout has adjusted
setTimeout(() => {
if (this.state.shouldScrollToBottom) {
this.scrollToBottom();
}
}, 100);
});
};
// Reset the offset when the keyboard is closed
// _keyboardDidHide = () => {
// this.setState({ keyboardOffset: 0 }, () => {
// this.isKeyboardVisible = false; // Reset keyboard visible flag
// });
// };
_keyboardDidHide = () => {
console.log("🎹 Keyboard hidden");
this.setState({ keyboardOffset: 0 }, () => {
// Ensure the view adjusts back properly
setTimeout(() => {
if (this.flatListRef.current) {
// Maintain scroll position when keyboard hides
this.flatListRef.current.scrollToEnd({ animated: false });
}
}, 50);
});
};
// Scroll the FlatList to the most recent message (bottom)
scrollToBottom = () => {
if (this.flatListRef.current) {
this.flatListRef.current.scrollToEnd({ animated: true });
}
};
// Get dynamic container style that accounts for keyboard
getContainerStyle = () => {
const { keyboardOffset, screenHeight } = this.state;
const statusBarHeight = Platform.OS === 'android' ? StatusBar.currentHeight || 0 : 0;
return {
flex: 1,
backgroundColor: '#fff',
paddingTop: statusBarHeight,
// Adjust height when keyboard is visible
height: keyboardOffset > 0 ? screenHeight - keyboardOffset : screenHeight,
};
};
// Get input container style that stays above keyboard
getInputContainerStyle = () => {
const { keyboardOffset } = this.state;
return {
...styles.inputContainer,
// Ensure input stays above keyboard
marginBottom: Platform.OS === 'ios' ? 0 : 0,
paddingBottom: Platform.OS === 'ios' ? 0 : 10,
};
};
shareFile = () => {
var option = {
type: this.state.chatType,
clientId: this.state.participantClientId,
}
this.props.shareFile(option)
}
//Send message
sendMessage = () => {
if(this.state.newMessage != "") {
var option = {
msg:this.state.newMessage,
type:this.state.chatType,
clientId: this.state.participantClientId,
}
this.props.sendMessageInGroup(option)
this.setState({
newMessage:''
},()=>{
this.scrollToBottom(); // Scroll to bottom if keyboard is visible
})
}
}
onScrollBeginDrag = () => {
// User has started scrolling manually, so disable auto-scroll
console.log('keybard getting drag');
this.setState({ shouldScrollToBottom: false });
};
onScrollEndDrag = () => {
console.log('keybard drag end');
// User has finished scrolling
this.setState({ shouldScrollToBottom: true });
};
render() {
const { screenWidth, screenHeight, newMessage, keyboardOffset } = this.state;
return (
<View style={{ flex: 1, backgroundColor: '#fff' }}>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={{ flex: 1 }}>
{/* Messages List Container */}
<View style={{
flex: 1,
paddingHorizontal: 10,
marginBottom: keyboardOffset > 0 ? keyboardOffset + 80 : 80 // Reserve space for input + keyboard
}}>
{this.props.data.length > 0 ? (
<FlatList
ref={this.flatListRef}
data={this.props.data}
renderItem={this.renderItem}
keyExtractor={(item, index) => {
// Create unique key from item id, timestamp, or index
if (item.id) return String(item.id);
if (item.timestamp) return `msg-${item.timestamp}-${index}`;
if (item.sender && item.message) return `msg-${item.sender}-${index}-${item.message.length}`;
return `msg-${index}`;
}}
onTouchStart={this.onScrollBeginDrag}
onTouchEnd={this.onScrollEndDrag}
contentContainerStyle={{
paddingBottom: 20,
flexGrow: 1,
}}
showsVerticalScrollIndicator={false}
style={{ flex: 1 }}
/>
) : (
<View style={styles.placeholder}>
<Text>No messages available.</Text>
</View>
)}
</View>
{/* Input Container - Absolutely positioned at bottom */}
<View style={{
position: 'absolute',
bottom: keyboardOffset, // Move up by keyboard height
left: 0,
right: 0,
backgroundColor: '#f5f5f5',
borderTopWidth: 1,
borderTopColor: '#e0e0e0',
paddingHorizontal: 10,
paddingVertical: 8,
flexDirection: 'row',
alignItems: 'center',
minHeight: 60,
elevation: 5, // Android shadow
shadowColor: '#000', // iOS shadow
shadowOffset: { width: 0, height: -2 },
shadowOpacity: 0.1,
shadowRadius: 4,
}}>
<TextInput
style={{
flex: 1,
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 20,
paddingHorizontal: 15,
paddingVertical: 10,
maxHeight: 100,
backgroundColor: '#fff',
fontSize: 16,
}}
placeholder="Type your message..."
value={newMessage}
onChangeText={(text) => this.setState({ newMessage: text })}
onFocus={() => {
console.log("📝 Text input focused, keyboard offset:", keyboardOffset);
this.setState({ shouldScrollToBottom: true });
setTimeout(() => {
this.scrollToBottom();
}, 150);
}}
multiline
textAlignVertical="top"
returnKeyType="send"
onSubmitEditing={this.sendMessage}
/>
<TouchableOpacity
style={{ padding: 8, marginLeft: 5 }}
onPress={this.shareFile}
>
<Image
source={require("../image_asset/file_chooser.png")}
style={{ width: 27, height: 27,tintColor:'#e60073' }}
/>
</TouchableOpacity>
<TouchableOpacity
style={[styles.sendButton, { padding: 8, marginLeft: 5 }]}
onPress={this.sendMessage}
>
<Image
source={require("../image_asset/send.png")}
style={styles.itemImage}
/>
</TouchableOpacity>
</View>
</View>
</TouchableWithoutFeedback>
</View>
);
}
renderItem = ({ item,index }) => {
console.log("Item43",item);
// Function to render text with link preview
const renderTextWithLinkPreview = (text) => {
const urlRegex = /(https?:\/\/[^\s]+)/g;
const parts = text.split(urlRegex);
return parts.map((part, i) => {
if (part.match(urlRegex)) {
return (
<LinkPreview
key={`link-${index}-${i}`}
text={part}
renderText={(previewText) => <Text style={styles.linkText} key={`linktext-${index}-${i}`}>{previewText}</Text>}
containerStyle={styles.linkPreviewContainer}
textContainerStyle={styles.linkPreviewTextContainer}
/>
);
}
return <Text style={styles.normalText} key={`text-${index}-${i}`}>{part}</Text>;
});
};
const isSender = item.isReceived;
const firstLetter = item.sender.charAt(0).toUpperCase();
return (
<View
style={[
styles.messageContainer,
isSender ? styles.senderContainer : styles.receiverContainer,
]}
>
{/* First Row: First letter, Name, Date */}
<View
style={[
styles.firstRow,
isSender ? styles.senderFirstRow : styles.receiverFirstRow,
]}
>
<View
style={[
styles.firstLetterContainer,
isSender ? styles.senderFirstLetter : styles.receiverFirstLetter,
]}
>
<Text style={styles.firstLetterText}>{firstLetter}</Text>
</View>
{isSender ? <View style={styles.nameDateContainer}>
<Text style={styles.dateText}>{format(new Date(item.timestamp), 'hh:mm:ss a')}</Text>
<Text style={styles.nameText}>{item.senderName}</Text>
</View> :
<View style={styles.nameDateContainer}>
<Text style={styles.nameText}>{item.senderName}</Text>
<Text style={styles.dateText}>{format(new Date(item.timestamp), 'hh:mm:ss a')}</Text>
</View> }
</View>
{/* Second Row: Message */}
<View style={[styles.secondRow, isSender ? styles.senderSecondRow : null]}>
<View
style={[
styles.messageBubble,
isSender ? styles.senderBubble : styles.receiverBubble,
]}
>
<Text style={styles.messageText}>{renderTextWithLinkPreview(item.message)}</Text>
{item.jsondata!=null && item.jsondata!=''?
<TouchableOpacity onPress={() => { this.props.downloadFile(index)}}
>
<Text style={{color: 'blue'}}>[Download]</Text>
</TouchableOpacity>
:null
}
</View>
</View>
</View>
);
};
}
export default EnxChatScreen;