You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
600 lines
16 KiB
600 lines
16 KiB
import React, { useState, useEffect } from "react"; |
|
import customRF from '../utils/customRF'; |
|
|
|
import { |
|
View, |
|
Text, |
|
StyleSheet, |
|
TextInput, |
|
TouchableOpacity, |
|
FlatList, |
|
KeyboardAvoidingView, |
|
Platform, |
|
ImageBackground, |
|
StatusBar, |
|
SafeAreaView, |
|
Modal, |
|
Animated |
|
} from "react-native"; |
|
import { useNavigation } from "@react-navigation/native"; |
|
import { NativeStackNavigationProp } from "@react-navigation/native-stack"; |
|
import { chatService } from "../services/api/chat"; |
|
import useUserStore from "../store/user"; |
|
import AsyncStorage from "@react-native-async-storage/async-storage"; |
|
|
|
import { useTranslation } from 'react-i18next'; |
|
|
|
interface Message { |
|
id?: string; |
|
mimetype: string; |
|
userWs: string; |
|
app_id: string; |
|
country: number; |
|
body: string; |
|
text: string; |
|
type: string; |
|
isMe?: boolean; |
|
timestamp?: Date; |
|
} |
|
|
|
type TabType = "customer" | "product" | "notification"; |
|
type RootStackParamList = { |
|
Login: undefined; |
|
// other screens... |
|
}; |
|
|
|
export const ChatScreen = () => { |
|
const [messages, setMessages] = useState<Message[]>([]); |
|
const [inputText, setInputText] = useState(""); |
|
const [activeTab, setActiveTab] = useState<TabType>("customer"); |
|
const [loginModalVisible, setLoginModalVisible] = useState(false); |
|
const [country, setCountry] = useState<string>(""); // Store the country code |
|
const { t } = useTranslation(); |
|
const { user } = useUserStore(); |
|
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>(); |
|
|
|
// Add animation values |
|
const [fadeAnim] = useState(new Animated.Value(0)); |
|
const [slideAnim] = useState(new Animated.Value(300)); |
|
|
|
// Get country from AsyncStorage |
|
useEffect(() => { |
|
const getCountry = async () => { |
|
try { |
|
const selectedCountry = await AsyncStorage.getItem('@selected_country'); |
|
if (selectedCountry) { |
|
const countryData = JSON.parse(selectedCountry); |
|
setCountry(countryData.name_en || ""); |
|
} |
|
} catch (error) { |
|
console.error('Error getting country data:', error); |
|
} |
|
}; |
|
|
|
getCountry(); |
|
}, []); |
|
|
|
// Check if user is logged in |
|
useEffect(() => { |
|
if (!user.user_id) { |
|
setLoginModalVisible(true); |
|
// Animate modal appearance |
|
Animated.parallel([ |
|
Animated.timing(fadeAnim, { |
|
toValue: 1, |
|
duration: 300, |
|
useNativeDriver: true, |
|
}), |
|
Animated.spring(slideAnim, { |
|
toValue: 0, |
|
tension: 50, |
|
friction: 9, |
|
useNativeDriver: true, |
|
}) |
|
]).start(); |
|
} else { |
|
setLoginModalVisible(false); |
|
} |
|
}, [user.user_id]); |
|
|
|
const handleCloseModal = () => { |
|
// Animate modal exit |
|
Animated.parallel([ |
|
Animated.timing(fadeAnim, { |
|
toValue: 0, |
|
duration: 200, |
|
useNativeDriver: true, |
|
}), |
|
Animated.timing(slideAnim, { |
|
toValue: 300, |
|
duration: 200, |
|
useNativeDriver: true, |
|
}) |
|
]).start(() => { |
|
setLoginModalVisible(false); |
|
}); |
|
}; |
|
|
|
const handleGoToLogin = () => { |
|
handleCloseModal(); |
|
// Navigate to login after modal is closed |
|
setTimeout(() => { |
|
navigation.navigate('Login'); |
|
}, 200); |
|
}; |
|
|
|
const sendMessage = () => { |
|
if (inputText.trim() === "") return; |
|
|
|
const newMessage: Message = { |
|
mimetype: "text/plain", |
|
userWs: "unknown", |
|
app_id: user.user_id ? user.user_id.toString() : "", |
|
country: user.country_code, |
|
body: "", |
|
text: inputText, |
|
type: "text", |
|
isMe: true, |
|
timestamp: new Date(), |
|
id: Date.now().toString(), // Add unique id for keyExtractor |
|
}; |
|
|
|
// Extract only the properties that chatService.sendMessage expects |
|
const chatServiceMessage = { |
|
type: newMessage.type, |
|
mimetype: newMessage.mimetype, |
|
userWs: newMessage.userWs, |
|
app_id: newMessage.app_id, |
|
country: newMessage.country, |
|
body: newMessage.body, |
|
text: newMessage.text |
|
}; |
|
|
|
// Add user message to the chat UI |
|
setMessages([...messages, newMessage]); |
|
setInputText(""); |
|
|
|
// Add simulated response with loading indicator |
|
const simulatedId = `simulated-${Date.now()}`; |
|
const simulatedResponse: Message = { |
|
mimetype: "text/plain", |
|
userWs: "system", |
|
app_id: "system", |
|
country: user.country_code, |
|
body: "", |
|
text: `${t('typingMessage')}...`, |
|
type: "chat", |
|
isMe: false, |
|
timestamp: new Date(), |
|
id: simulatedId, |
|
}; |
|
|
|
// Add simulated message after a short delay to make it feel more natural |
|
setTimeout(() => { |
|
setMessages(prevMessages => [...prevMessages, simulatedResponse]); |
|
}, 800); |
|
|
|
const data = { |
|
newMessage:chatServiceMessage, |
|
} |
|
// Send actual message to API |
|
chatService.sendMessage(data) |
|
.then(response => { |
|
// When real response arrives, replace simulated message |
|
setMessages(prevMessages => { |
|
// Filter out the simulated message and add real response |
|
const filtered = prevMessages.filter(msg => msg.id !== simulatedId); |
|
|
|
// Create the real response message object |
|
const realResponse: Message = { |
|
mimetype: "text/plain", |
|
userWs: "system", |
|
app_id: "system", |
|
country: user.country_code, |
|
body: "", |
|
text: response?.text || t('defaultResponse'), |
|
type: "chat", |
|
isMe: false, |
|
timestamp: new Date(), |
|
id: `real-${Date.now()}`, |
|
}; |
|
|
|
return [...filtered, realResponse]; |
|
}); |
|
}) |
|
.catch(error => { |
|
// In case of error, replace simulated message with error message |
|
console.error('Chat API error:', error); |
|
setMessages(prevMessages => { |
|
const filtered = prevMessages.filter(msg => msg.id !== simulatedId); |
|
|
|
const errorResponse: Message = { |
|
mimetype: "text/plain", |
|
userWs: "system", |
|
app_id: "system", |
|
country: user.country_code, |
|
body: "", |
|
text: t('errorResponse'), |
|
type: "chat", |
|
isMe: false, |
|
timestamp: new Date(), |
|
id: `error-${Date.now()}`, |
|
}; |
|
|
|
return [...filtered, errorResponse]; |
|
}); |
|
}); |
|
}; |
|
|
|
// Generate a unique key for each message |
|
const keyExtractor = (item: Message, index: number): string => { |
|
return item.id || index.toString(); |
|
}; |
|
|
|
const renderMessage = ({ item }: { item: Message }) => ( |
|
<View |
|
style={[ |
|
styles.messageContainer, |
|
item.isMe ? styles.myMessage : styles.theirMessage, |
|
]} |
|
> |
|
<Text style={styles.messageText}>{item.text}</Text> |
|
<Text style={styles.timestamp}> |
|
{item.timestamp?.toLocaleTimeString([], { |
|
hour: "2-digit", |
|
minute: "2-digit", |
|
})} |
|
</Text> |
|
</View> |
|
); |
|
|
|
const renderTabContent = () => { |
|
switch (activeTab) { |
|
case "customer": |
|
return ( |
|
<View style={styles.tabContent}> |
|
<FlatList |
|
data={messages} |
|
renderItem={renderMessage} |
|
keyExtractor={keyExtractor} |
|
contentContainerStyle={styles.messageList} |
|
showsVerticalScrollIndicator={false} |
|
/> |
|
</View> |
|
); |
|
case "product": |
|
return ( |
|
<View style={styles.tabContent}> |
|
<FlatList |
|
data={messages} |
|
renderItem={renderMessage} |
|
keyExtractor={keyExtractor} |
|
contentContainerStyle={styles.messageList} |
|
showsVerticalScrollIndicator={false} |
|
/> |
|
</View> |
|
); |
|
case "notification": |
|
return ( |
|
<View style={styles.tabContent}> |
|
<FlatList |
|
data={messages} |
|
renderItem={renderMessage} |
|
keyExtractor={keyExtractor} |
|
contentContainerStyle={styles.messageList} |
|
showsVerticalScrollIndicator={false} |
|
/> |
|
</View> |
|
); |
|
} |
|
}; |
|
|
|
return ( |
|
<SafeAreaView style={styles.safeArea}> |
|
<StatusBar barStyle="dark-content" backgroundColor="#fff" /> |
|
<View style={styles.safeAreaContent}> |
|
<View style={styles.container}> |
|
<ImageBackground |
|
source={require('../../assets/img/DefaultWallpaper.png')} |
|
style={styles.backgroundImage} |
|
resizeMode="cover" |
|
> |
|
<View style={styles.tabBar}> |
|
<TouchableOpacity |
|
style={[styles.tab, activeTab === "customer" && styles.activeTab]} |
|
onPress={() => setActiveTab("customer")} |
|
> |
|
<Text |
|
style={[ |
|
styles.tabText, |
|
activeTab === "customer" && styles.activeTabText, |
|
]} |
|
> |
|
{t('customerServiceChat')} |
|
</Text> |
|
</TouchableOpacity> |
|
<TouchableOpacity |
|
style={[styles.tab, activeTab === "product" && styles.activeTab]} |
|
onPress={() => setActiveTab("product")} |
|
> |
|
<Text |
|
style={[ |
|
styles.tabText, |
|
activeTab === "product" && styles.activeTabText, |
|
]} |
|
> |
|
{t('productChat')} |
|
</Text> |
|
</TouchableOpacity> |
|
<TouchableOpacity |
|
style={[styles.tab, activeTab === "notification" && styles.activeTab]} |
|
onPress={() => setActiveTab("notification")} |
|
> |
|
<Text |
|
style={[ |
|
styles.tabText, |
|
activeTab === "notification" && styles.activeTabText, |
|
]} |
|
> |
|
{t('notificationChat')} |
|
</Text> |
|
</TouchableOpacity> |
|
</View> |
|
|
|
{renderTabContent()} |
|
|
|
<View style={styles.inputContainer}> |
|
<TextInput |
|
style={styles.input} |
|
value={inputText} |
|
onChangeText={setInputText} |
|
placeholder={t('inputMessage')} |
|
multiline |
|
/> |
|
<TouchableOpacity style={styles.sendButton} onPress={sendMessage}> |
|
<Text style={styles.sendButtonText}>{t('send')}</Text> |
|
</TouchableOpacity> |
|
</View> |
|
</ImageBackground> |
|
|
|
{/* Login Modal */} |
|
<Modal |
|
visible={loginModalVisible} |
|
transparent={true} |
|
animationType="none" |
|
onRequestClose={handleCloseModal} |
|
> |
|
<Animated.View |
|
style={[ |
|
styles.modalContainer, |
|
{ |
|
opacity: fadeAnim, |
|
} |
|
]} |
|
> |
|
<Animated.View |
|
style={[ |
|
styles.modalOverlay, |
|
{ |
|
opacity: fadeAnim, |
|
} |
|
]} |
|
> |
|
<TouchableOpacity |
|
style={styles.modalOverlayTouch} |
|
activeOpacity={1} |
|
onPress={handleCloseModal} |
|
/> |
|
</Animated.View> |
|
<Animated.View |
|
style={[ |
|
styles.modalContent, |
|
{ |
|
transform: [{ translateY: slideAnim }], |
|
}, |
|
]} |
|
> |
|
<TouchableOpacity |
|
style={styles.closeButton} |
|
onPress={handleCloseModal} |
|
> |
|
<Text style={styles.closeButtonText}>✕</Text> |
|
</TouchableOpacity> |
|
<View style={styles.modalHeader}> |
|
<View style={styles.modalIndicator} /> |
|
</View> |
|
<Text style={styles.modalTitle}>{t('loginRequired')}</Text> |
|
<Text style={styles.modalText}> |
|
{t('pleaseLoginToChat')} |
|
</Text> |
|
<TouchableOpacity |
|
style={styles.modalButton} |
|
onPress={handleGoToLogin} |
|
> |
|
<Text style={styles.modalButtonText}>{t('loginNow')}</Text> |
|
</TouchableOpacity> |
|
</Animated.View> |
|
</Animated.View> |
|
</Modal> |
|
</View> |
|
</View> |
|
</SafeAreaView> |
|
); |
|
}; |
|
|
|
const styles = StyleSheet.create({ |
|
safeArea: { |
|
flex: 1, |
|
backgroundColor: '#fff', |
|
}, |
|
safeAreaContent: { |
|
flex: 1, |
|
paddingTop: Platform.OS === 'android' ? 0 : 0, |
|
}, |
|
container: { |
|
flex: 1, |
|
backgroundColor: '#fff', |
|
}, |
|
backgroundImage: { |
|
flex: 1, |
|
width: '100%', |
|
height: '100%', |
|
}, |
|
tabBar: { |
|
flexDirection: "row", |
|
backgroundColor: "#007a6c", |
|
borderBottomWidth: 1, |
|
borderBottomColor: "#eef0f1", |
|
shadowColor: "#000", |
|
shadowOffset: { |
|
width: 0, |
|
height: 2, |
|
}, |
|
shadowOpacity: 0.25, |
|
shadowRadius: 3.84, |
|
elevation: 5, |
|
}, |
|
tab: { |
|
flex: 1, |
|
paddingVertical: 15, |
|
alignItems: "center", |
|
}, |
|
activeTab: { |
|
borderBottomWidth: 2, |
|
borderBottomColor: "#eef0f1", |
|
}, |
|
tabText: { |
|
fontSize: customRF(14), |
|
color: "#fff", |
|
}, |
|
activeTabText: { |
|
color: "#fff", |
|
fontWeight: "600", |
|
}, |
|
tabContent: { |
|
flex: 1, |
|
}, |
|
messageList: { |
|
padding: 10, |
|
}, |
|
messageContainer: { |
|
maxWidth: "80%", |
|
padding: 10, |
|
borderRadius: 10, |
|
marginVertical: 5, |
|
}, |
|
myMessage: { |
|
alignSelf: "flex-end", |
|
backgroundColor: "#dcf8c6", |
|
}, |
|
theirMessage: { |
|
alignSelf: "flex-start", |
|
backgroundColor: "white", |
|
}, |
|
messageText: { |
|
fontSize: 16, |
|
}, |
|
timestamp: { |
|
fontSize: 12, |
|
color: "#666", |
|
alignSelf: "flex-end", |
|
marginTop: 5, |
|
}, |
|
inputContainer: { |
|
flexDirection: "row", |
|
padding: 10, |
|
backgroundColor: "white", |
|
borderTopWidth: 1, |
|
borderTopColor: "#ddd", |
|
}, |
|
input: { |
|
flex: 1, |
|
backgroundColor: "#f0f0f0", |
|
borderRadius: 20, |
|
paddingHorizontal: 15, |
|
paddingVertical: 8, |
|
marginRight: 10, |
|
maxHeight: 100, |
|
}, |
|
sendButton: { |
|
backgroundColor: "#128C7E", |
|
borderRadius: 20, |
|
paddingHorizontal: 20, |
|
justifyContent: "center", |
|
}, |
|
sendButtonText: { |
|
color: "white", |
|
fontSize: 16, |
|
}, |
|
// Modal styles |
|
modalContainer: { |
|
flex: 1, |
|
justifyContent: 'flex-end', |
|
}, |
|
modalOverlay: { |
|
...StyleSheet.absoluteFillObject, |
|
backgroundColor: 'rgba(0, 0, 0, 0.5)', |
|
}, |
|
modalOverlayTouch: { |
|
flex: 1, |
|
}, |
|
modalContent: { |
|
backgroundColor: '#fff', |
|
borderTopLeftRadius: 24, |
|
borderTopRightRadius: 24, |
|
paddingHorizontal: 20, |
|
paddingBottom: Platform.OS === 'ios' ? 40 : 20, |
|
position: 'relative', |
|
}, |
|
closeButton: { |
|
position: 'absolute', |
|
top: 20, |
|
right: 20, |
|
width: 24, |
|
height: 24, |
|
justifyContent: 'center', |
|
alignItems: 'center', |
|
zIndex: 1, |
|
}, |
|
closeButtonText: { |
|
fontSize: 20, |
|
color: '#999', |
|
}, |
|
modalHeader: { |
|
alignItems: 'center', |
|
paddingTop: 12, |
|
paddingBottom: 20, |
|
}, |
|
modalIndicator: { |
|
width: 40, |
|
height: 4, |
|
backgroundColor: '#E0E0E0', |
|
borderRadius: 2, |
|
}, |
|
modalTitle: { |
|
fontSize: 20, |
|
fontWeight: '600', |
|
color: '#000', |
|
textAlign: 'center', |
|
marginBottom: 12, |
|
}, |
|
modalText: { |
|
fontSize: 16, |
|
color: '#666', |
|
textAlign: 'center', |
|
marginBottom: 24, |
|
lineHeight: 22, |
|
}, |
|
modalButton: { |
|
backgroundColor: '#007a6c', |
|
height: 50, |
|
borderRadius: 25, |
|
justifyContent: 'center', |
|
alignItems: 'center', |
|
}, |
|
modalButtonText: { |
|
color: '#fff', |
|
fontSize: 16, |
|
fontWeight: '600', |
|
}, |
|
});
|
|
|