|
|
|
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.toString(),
|
|
|
|
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?.reply || 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',
|
|
|
|
},
|
|
|
|
});
|