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

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',
},
});