@ -7,7 +7,6 @@ import {
TextInput ,
TextInput ,
TouchableOpacity ,
TouchableOpacity ,
FlatList ,
FlatList ,
KeyboardAvoidingView ,
Platform ,
Platform ,
ImageBackground ,
ImageBackground ,
StatusBar ,
StatusBar ,
@ -20,7 +19,8 @@ import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { chatService } from "../services/api/chat" ;
import { chatService } from "../services/api/chat" ;
import useUserStore from "../store/user" ;
import useUserStore from "../store/user" ;
import AsyncStorage from "@react-native-async-storage/async-storage" ;
import AsyncStorage from "@react-native-async-storage/async-storage" ;
import { useTranslation } from 'react-i18next' ;
import { t } from "../i18n" ;
interface Message {
interface Message {
id? : string ;
id? : string ;
mimetype : string ;
mimetype : string ;
@ -33,31 +33,28 @@ interface Message {
isMe? : boolean ;
isMe? : boolean ;
timestamp? : Date ;
timestamp? : Date ;
}
}
type TabType = "customer" | "product" | "notification" ;
type TabType = "customer" | "product" | "notification" ;
type RootStackParamList = {
type RootStackParamList = {
Login : undefined ;
Login : undefined ;
// other screens...
// other screens...
} ;
} ;
export const ChatScreen = ( ) = > {
export const ChatScreen = ( ) = > {
const [ messages , setMessages ] = useState < Message [ ] > ( [ ] ) ;
const [ messages , setMessages ] = useState < Message [ ] > ( [ ] ) ;
const [ inputText , setInputText ] = useState ( "" ) ;
const [ inputText , setInputText ] = useState ( "" ) ;
const [ activeTab , setActiveTab ] = useState < TabType > ( "customer" ) ;
const [ activeTab , setActiveTab ] = useState < TabType > ( "customer" ) ;
const [ loginModalVisible , setLoginModalVisible ] = useState ( false ) ;
const [ country , setCountry ] = useState < string > ( "" ) ; // Store the country code
const [ country , setCountry ] = useState < string > ( "" ) ; // Store the country code
const { t } = useTranslation ( ) ;
const { user } = useUserStore ( ) ;
const { user } = useUserStore ( ) ;
const navigation = useNavigation < NativeStackNavigationProp < RootStackParamList > > ( ) ;
const navigation = useNavigation < NativeStackNavigationProp < RootStackParamList > > ( ) ;
// Add FlatList ref for auto-scrolling
// Add FlatList ref for auto-scrolling
const flatListRef = useRef < FlatList > ( null ) ;
const flatListRef = useRef < FlatList > ( null ) ;
// Add animation values
const [ fadeAnim ] = useState ( new Animated . Value ( 0 ) ) ;
const [ slideAnim ] = useState ( new Animated . Value ( 300 ) ) ;
// Auto-scroll to bottom when messages change
// Auto-scroll to bottom when messages change
useEffect ( ( ) = > {
useEffect ( ( ) = > {
if ( messages . length > 0 ) {
if ( messages . length > 0 && user . user_id ) {
setTimeout ( ( ) = > {
setTimeout ( ( ) = > {
flatListRef . current ? . scrollToEnd ( { animated : true } ) ;
flatListRef . current ? . scrollToEnd ( { animated : true } ) ;
} , 100 ) ;
} , 100 ) ;
@ -78,56 +75,25 @@ export const ChatScreen = () => {
}
}
} ;
} ;
getCountry ( ) ;
// Only get country if user is logged in
} , [ ] ) ;
if ( user . user_id ) {
// Check if user is logged in
getCountry ( ) ;
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 ] ) ;
} , [ user . user_id ] ) ;
const handleCloseModal = ( ) = > {
// Animate modal exit
// 添加导航到登录页面的函数
Animated . parallel ( [
const goToLogin = ( ) = > {
Animated . timing ( fadeAnim , {
navigation . navigate ( "Login" ) ;
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 = ( ) = > {
const sendMessage = ( ) = > {
// 如果用户未登录,直接返回
if ( ! user . user_id ) {
return ;
}
if ( inputText . trim ( ) === "" ) return ;
if ( inputText . trim ( ) === "" ) return ;
const newMessage : Message = {
const newMessage : Message = {
mimetype : "text/plain" ,
mimetype : "text/plain" ,
userWs : "unknown" ,
userWs : "unknown" ,
@ -140,6 +106,7 @@ export const ChatScreen = () => {
timestamp : new Date ( ) ,
timestamp : new Date ( ) ,
id : Date.now ( ) . toString ( ) , // Add unique id for keyExtractor
id : Date.now ( ) . toString ( ) , // Add unique id for keyExtractor
} ;
} ;
// Extract only the properties that chatService.sendMessage expects
// Extract only the properties that chatService.sendMessage expects
const chatServiceMessage = {
const chatServiceMessage = {
type : newMessage . type ,
type : newMessage . type ,
@ -150,6 +117,7 @@ export const ChatScreen = () => {
body : newMessage.body ,
body : newMessage.body ,
text : newMessage.text
text : newMessage.text
} ;
} ;
// Add user message to the chat UI
// Add user message to the chat UI
setMessages ( [ . . . messages , newMessage ] ) ;
setMessages ( [ . . . messages , newMessage ] ) ;
setInputText ( "" ) ;
setInputText ( "" ) ;
@ -162,7 +130,7 @@ export const ChatScreen = () => {
app_id : "system" ,
app_id : "system" ,
country : user.country_code ,
country : user.country_code ,
body : "" ,
body : "" ,
text : ` ${ t ( 'typingM essage' ) } ... ` ,
text : ` ${ t ( 'chat.typing_m essage' ) } ... ` ,
type : "chat" ,
type : "chat" ,
isMe : false ,
isMe : false ,
timestamp : new Date ( ) ,
timestamp : new Date ( ) ,
@ -177,6 +145,7 @@ export const ChatScreen = () => {
const data = {
const data = {
newMessage :chatServiceMessage ,
newMessage :chatServiceMessage ,
}
}
// Send actual message to API
// Send actual message to API
chatService . sendMessage ( data )
chatService . sendMessage ( data )
. then ( response = > {
. then ( response = > {
@ -192,7 +161,7 @@ export const ChatScreen = () => {
app_id : "system" ,
app_id : "system" ,
country : user.country_code ,
country : user.country_code ,
body : "" ,
body : "" ,
text : response?.reply || t ( 'defaultR esponse' ) ,
text : response?.reply || t ( 'chat.default_r esponse' ) ,
type : "chat" ,
type : "chat" ,
isMe : false ,
isMe : false ,
timestamp : new Date ( ) ,
timestamp : new Date ( ) ,
@ -214,7 +183,7 @@ export const ChatScreen = () => {
app_id : "system" ,
app_id : "system" ,
country : user.country_code ,
country : user.country_code ,
body : "" ,
body : "" ,
text : t ( 'errorR esponse' ) ,
text : t ( 'chat.error_r esponse' ) ,
type : "chat" ,
type : "chat" ,
isMe : false ,
isMe : false ,
timestamp : new Date ( ) ,
timestamp : new Date ( ) ,
@ -225,10 +194,12 @@ export const ChatScreen = () => {
} ) ;
} ) ;
} ) ;
} ) ;
} ;
} ;
// Generate a unique key for each message
// Generate a unique key for each message
const keyExtractor = ( item : Message , index : number ) : string = > {
const keyExtractor = ( item : Message , index : number ) : string = > {
return item . id || index . toString ( ) ;
return item . id || index . toString ( ) ;
} ;
} ;
const renderMessage = ( { item } : { item : Message } ) = > (
const renderMessage = ( { item } : { item : Message } ) = > (
< View
< View
style = { [
style = { [
@ -245,6 +216,7 @@ export const ChatScreen = () => {
< / Text >
< / Text >
< / View >
< / View >
) ;
) ;
const renderTabContent = ( ) = > {
const renderTabContent = ( ) = > {
switch ( activeTab ) {
switch ( activeTab ) {
case "customer" :
case "customer" :
@ -257,6 +229,7 @@ export const ChatScreen = () => {
keyExtractor = { keyExtractor }
keyExtractor = { keyExtractor }
contentContainerStyle = { styles . messageList }
contentContainerStyle = { styles . messageList }
showsVerticalScrollIndicator = { false }
showsVerticalScrollIndicator = { false }
scrollEnabled = { ! ! user . user_id }
/ >
/ >
< / View >
< / View >
) ;
) ;
@ -270,6 +243,7 @@ export const ChatScreen = () => {
keyExtractor = { keyExtractor }
keyExtractor = { keyExtractor }
contentContainerStyle = { styles . messageList }
contentContainerStyle = { styles . messageList }
showsVerticalScrollIndicator = { false }
showsVerticalScrollIndicator = { false }
scrollEnabled = { ! ! user . user_id }
/ >
/ >
< / View >
< / View >
) ;
) ;
@ -283,11 +257,13 @@ export const ChatScreen = () => {
keyExtractor = { keyExtractor }
keyExtractor = { keyExtractor }
contentContainerStyle = { styles . messageList }
contentContainerStyle = { styles . messageList }
showsVerticalScrollIndicator = { false }
showsVerticalScrollIndicator = { false }
scrollEnabled = { ! ! user . user_id }
/ >
/ >
< / View >
< / View >
) ;
) ;
}
}
} ;
} ;
return (
return (
< SafeAreaView style = { styles . safeArea } >
< SafeAreaView style = { styles . safeArea } >
< StatusBar barStyle = "dark-content" backgroundColor = "#fff" / >
< StatusBar barStyle = "dark-content" backgroundColor = "#fff" / >
@ -301,7 +277,8 @@ export const ChatScreen = () => {
< View style = { styles . tabBar } >
< View style = { styles . tabBar } >
< TouchableOpacity
< TouchableOpacity
style = { [ styles . tab , activeTab === "customer" && styles . activeTab ] }
style = { [ styles . tab , activeTab === "customer" && styles . activeTab ] }
onPress = { ( ) = > setActiveTab ( "customer" ) }
onPress = { ( ) = > user . user_id && setActiveTab ( "customer" ) }
disabled = { ! user . user_id }
>
>
< Text
< Text
style = { [
style = { [
@ -309,12 +286,13 @@ export const ChatScreen = () => {
activeTab === "customer" && styles . activeTabText ,
activeTab === "customer" && styles . activeTabText ,
] }
] }
>
>
{ t ( 'customerServiceChat ' ) }
{ t ( 'chat.customer_service ' ) }
< / Text >
< / Text >
< / TouchableOpacity >
< / TouchableOpacity >
< TouchableOpacity
< TouchableOpacity
style = { [ styles . tab , activeTab === "product" && styles . activeTab ] }
style = { [ styles . tab , activeTab === "product" && styles . activeTab ] }
onPress = { ( ) = > setActiveTab ( "product" ) }
onPress = { ( ) = > user . user_id && setActiveTab ( "product" ) }
disabled = { ! user . user_id }
>
>
< Text
< Text
style = { [
style = { [
@ -322,12 +300,13 @@ export const ChatScreen = () => {
activeTab === "product" && styles . activeTabText ,
activeTab === "product" && styles . activeTabText ,
] }
] }
>
>
{ t ( 'productCha t' ) }
{ t ( 'chat.product_suppor t' ) }
< / Text >
< / Text >
< / TouchableOpacity >
< / TouchableOpacity >
< TouchableOpacity
< TouchableOpacity
style = { [ styles . tab , activeTab === "notification" && styles . activeTab ] }
style = { [ styles . tab , activeTab === "notification" && styles . activeTab ] }
onPress = { ( ) = > setActiveTab ( "notification" ) }
onPress = { ( ) = > user . user_id && setActiveTab ( "notification" ) }
disabled = { ! user . user_id }
>
>
< Text
< Text
style = { [
style = { [
@ -335,89 +314,62 @@ export const ChatScreen = () => {
activeTab === "notification" && styles . activeTabText ,
activeTab === "notification" && styles . activeTabText ,
] }
] }
>
>
{ t ( 'notificationChat ' ) }
{ t ( 'chat.notifications ' ) }
< / Text >
< / Text >
< / TouchableOpacity >
< / TouchableOpacity >
< / View >
< / View >
{ renderTabContent ( ) }
{ renderTabContent ( ) }
< View style = { styles . inputContainer } >
< View style = { styles . inputContainer } >
< TextInput
< TextInput
style = { styles . input }
style = { [ styles . input , ! user . user_id && styles . disabledInput ] }
value = { inputText }
value = { inputText }
onChangeText = { setInputText }
onChangeText = { user . user_id ? setInputText : undefined }
placeholder = { t ( 'inputM essage' ) }
placeholder = { t ( 'chat.input_m essage' ) }
multiline
multiline
editable = { ! ! user . user_id }
/ >
/ >
< TouchableOpacity style = { styles . sendButton } onPress = { sendMessage } >
< TouchableOpacity
< Text style = { styles . sendButtonText } > { t ( 'send' ) } < / Text >
style = { [ styles . sendButton , ! user . user_id && styles . disabledButton ] }
onPress = { user . user_id ? sendMessage : undefined }
disabled = { ! user . user_id }
>
< Text style = { styles . sendButtonText } > { t ( 'chat.send' ) } < / Text >
< / TouchableOpacity >
< / TouchableOpacity >
< / View >
< / View >
< / ImageBackground >
< / ImageBackground >
{ /* Login Modal */ }
{ /* 未登录遮罩 */ }
< Modal
{ ! user . user_id && (
visible = { loginModalVisible }
< View style = { styles . loginOverlay } >
transparent = { true }
< View style = { styles . blurContainer } >
animationType = "none"
< View style = { styles . loginPromptContainer } >
onRequestClose = { handleCloseModal }
< View style = { styles . loginIcon } >
>
< Text style = { styles . loginIconText } > 💬 < / Text >
< Animated.View
< / View >
style = { [
< Text style = { styles . loginPromptTitle } >
styles . modalContainer ,
{ t ( "chat.login_required_title" , "请先登录" ) }
{
< / Text >
opacity : fadeAnim ,
< Text style = { styles . loginPromptSubtitle } >
}
{ t ( "chat.login_required_subtitle" , "登录后即可使用聊天功能" ) }
] }
< / Text >
>
< TouchableOpacity
< Animated.View
style = { styles . loginButton }
style = { [
onPress = { goToLogin }
styles . modalOverlay ,
>
{
< Text style = { styles . loginButtonText } >
opacity : fadeAnim ,
{ t ( "chat.login_now" , "立即登录" ) }
}
< / Text >
] }
< / TouchableOpacity >
>
< 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 >
< / View >
< Text style = { styles . modalTitle } > { t ( 'loginRequired' ) } < / Text >
< / View >
< Text style = { styles . modalText } >
< / View >
{ t ( 'pleaseLoginToChat' ) }
) }
< / Text >
< TouchableOpacity
style = { styles . modalButton }
onPress = { handleGoToLogin }
>
< Text style = { styles . modalButtonText } > { t ( 'loginNow' ) } < / Text >
< / TouchableOpacity >
< / Animated.View >
< / Animated.View >
< / Modal >
< / View >
< / View >
< / View >
< / View >
< / SafeAreaView >
< / SafeAreaView >
) ;
) ;
} ;
} ;
const styles = StyleSheet . create ( {
const styles = StyleSheet . create ( {
safeArea : {
safeArea : {
flex : 1 ,
flex : 1 ,
@ -522,75 +474,85 @@ const styles = StyleSheet.create({
color : "white" ,
color : "white" ,
fontSize : 16 ,
fontSize : 16 ,
} ,
} ,
// Modal styles
loginOverlay : {
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' ,
position : 'absolute' ,
top : 20 ,
top : 0 ,
right : 20 ,
left : 0 ,
width : 24 ,
right : 0 ,
height : 24 ,
bottom : 0 ,
backgroundColor : 'rgba(255, 255, 255, 0.9)' ,
backdropFilter : 'blur(10px)' , // iOS 毛玻璃效果
justifyContent : 'center' ,
justifyContent : 'center' ,
alignItems : 'center' ,
alignItems : 'center' ,
zIndex : 1 ,
zIndex : 1000 ,
} ,
closeButtonText : {
fontSize : 20 ,
color : '#999' ,
} ,
} ,
modalHeader : {
blurContainer : {
width : '100%' ,
height : '100%' ,
justifyContent : 'center' ,
alignItems : 'center' ,
alignItems : 'center' ,
paddingTop : 12 ,
backgroundColor : Platform.OS === 'android' ? 'rgba(255, 255, 255, 0.95)' : 'transparent' ,
paddingBottom : 20 ,
} ,
} ,
modalIndicator : {
loginPromptContainer : {
width : 40 ,
backgroundColor : 'white' ,
height : 4 ,
borderRadius : 20 ,
backgroundColor : '#E0E0E0' ,
padding : 40 ,
borderRadius : 2 ,
alignItems : 'center' ,
shadowColor : '#000' ,
shadowOffset : {
width : 0 ,
height : 2 ,
} ,
shadowOpacity : 0.25 ,
shadowRadius : 3.84 ,
elevation : 5 ,
maxWidth : '80%' ,
} ,
} ,
modalTitle : {
loginIcon : {
fontSize : 20 ,
width : 80 ,
fontWeight : '600' ,
height : 80 ,
color : '#000' ,
marginBottom : 20 ,
justifyContent : 'center' ,
alignItems : 'center' ,
backgroundColor : 'rgba(0, 122, 108, 0.1)' ,
borderRadius : 40 ,
} ,
loginIconText : {
fontSize : 40 ,
fontWeight : 'bold' ,
color : '#007a6c' ,
} ,
loginPromptTitle : {
fontSize : 24 ,
fontWeight : '700' ,
color : '#333' ,
marginBottom : 10 ,
textAlign : 'center' ,
textAlign : 'center' ,
marginBottom : 12 ,
} ,
} ,
modalText : {
loginPromptSubtitle : {
fontSize : 16 ,
fontSize : 16 ,
color : '#666' ,
color : '#666' ,
marginBottom : 30 ,
textAlign : 'center' ,
textAlign : 'center' ,
marginBottom : 24 ,
lineHeight : 22 ,
lineHeight : 22 ,
} ,
} ,
modalButton : {
login Button : {
backgroundColor : '#007a6c' ,
backgroundColor : '#007a6c' ,
height : 50 ,
paddingHorizontal : 40 ,
paddingVertical : 15 ,
borderRadius : 25 ,
borderRadius : 25 ,
justifyContent : 'center' ,
minWidth : 160 ,
alignItems : 'center' ,
} ,
} ,
modalButtonText : {
loginButtonText : {
color : '#fff' ,
color : 'white' ,
fontSize : 16 ,
fontSize : 18 ,
fontWeight : '600' ,
fontWeight : '700' ,
textAlign : 'center' ,
} ,
disabledInput : {
opacity : 0.6 ,
} ,
disabledButton : {
opacity : 0.6 ,
} ,
} ,
} ) ;
} ) ;