Files
frontend/src/screens/message/components/ChatScreen/ChatInput.tsx

174 lines
6.0 KiB
TypeScript
Raw Normal View History

/**
*
*
*/
import React, { useMemo } from 'react';
import { View, TouchableOpacity, TextInput, ActivityIndicator } from 'react-native';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Text } from '../../../../components/common';
import { colors } from '../../../../theme';
import { chatScreenStyles as baseStyles } from './styles';
import { useBreakpointGTE } from '../../../../hooks/useResponsive';
import { ChatInputProps, GroupMessage, SenderInfo } from './types';
import { extractTextFromSegments } from '../../../../types/dto';
export const ChatInput: React.FC<ChatInputProps & {
currentUser: { id?: string; nickname?: string; avatar?: string | null } | null;
otherUser: { id?: string; nickname?: string; avatar?: string | null } | null;
getSenderInfo: (senderId: string) => SenderInfo;
}> = ({
inputText,
onInputChange,
onSend,
onToggleEmoji,
onToggleMore,
activePanel,
sending,
isMuted,
isGroupChat,
muteAll,
replyingTo,
onCancelReply,
onFocus,
restrictionHint,
disableMore = false,
currentUser,
otherUser,
getSenderInfo,
}) => {
const isDisabled = isGroupChat && isMuted;
// 获取当前用户ID
const currentUserId = currentUser?.id || '';
// 响应式布局
const isWideScreen = useBreakpointGTE('lg');
const styles = baseStyles;
const inputContainerStyle = useMemo(() => ([
styles.inputContainer,
isWideScreen ? { maxWidth: 900, alignSelf: 'center' as const, width: '100%' as const } : null,
]), [isWideScreen, styles.inputContainer]);
// 回复预览组件 - 定义在内部以访问 styles
const ReplyPreview: React.FC<{
replyingTo: GroupMessage;
onCancel: () => void;
}> = ({ replyingTo, onCancel }) => {
const senderInfo = replyingTo.sender_id === currentUserId
? { nickname: '我', avatar: currentUser?.avatar }
: isGroupChat
? getSenderInfo(replyingTo.sender_id)
: { nickname: otherUser?.nickname || '对方', avatar: otherUser?.avatar };
return (
<View style={styles.replyPreviewContainer}>
<View style={styles.replyPreviewContent}>
<MaterialCommunityIcons name="reply" size={16} color={colors.primary.main} />
<View style={styles.replyPreviewText}>
<Text style={styles.replyPreviewName}> {senderInfo.nickname}</Text>
<Text style={styles.replyPreviewMessage} numberOfLines={1}>
{replyingTo.segments?.some(seg => seg.type === 'image') ? '[图片]' : extractTextFromSegments(replyingTo.segments)}
</Text>
</View>
</View>
<TouchableOpacity onPress={onCancel} style={styles.replyPreviewClose}>
<MaterialCommunityIcons name="close" size={18} color="#999" />
</TouchableOpacity>
</View>
);
};
return (
<View style={inputContainerStyle}>
{/* 回复预览 */}
{replyingTo && (
<ReplyPreview
replyingTo={replyingTo}
onCancel={onCancelReply}
/>
)}
{/* 禁言提示 */}
{isGroupChat && isMuted && (
<View style={styles.mutedBanner}>
<MaterialCommunityIcons name="minus-circle" size={16} color="#FF3B30" />
<Text style={styles.mutedBannerText}>
{muteAll ? '全员禁言中,暂无法发送消息' : '你已被禁言,暂无法发送消息'}
</Text>
</View>
)}
{!!restrictionHint && (
<View style={styles.mutedBanner}>
<MaterialCommunityIcons name="information-outline" size={16} color={colors.warning.main} />
<Text style={styles.mutedBannerText}>{restrictionHint}</Text>
</View>
)}
<View style={[styles.inputInner, isDisabled && styles.inputInnerMuted]}>
<TouchableOpacity
style={[styles.iconButton, activePanel === 'emoji' && styles.iconButtonActive]}
onPress={onToggleEmoji}
disabled={isDisabled}
>
<MaterialCommunityIcons
name={activePanel === 'emoji' ? "keyboard" : "emoticon-happy-outline"}
size={26}
color={isDisabled ? '#CCC' : (activePanel === 'emoji' ? colors.primary.main : '#666')}
/>
</TouchableOpacity>
<View style={styles.inputBox}>
<TextInput
style={[styles.input, isDisabled && styles.inputMuted]}
placeholder={isGroupChat ? (isMuted ? (muteAll ? "全员禁言中..." : "你已被禁言...") : "发送消息,@提及成员...") : "发送消息..."}
placeholderTextColor={isDisabled ? '#CCC' : '#999'}
value={inputText}
onChangeText={onInputChange}
multiline
maxLength={500}
returnKeyType="send"
onSubmitEditing={onSend}
blurOnSubmit={false}
editable={!isDisabled}
onFocus={onFocus}
/>
</View>
{inputText.trim() && !sending && !isDisabled ? (
<TouchableOpacity
style={styles.sendButtonActive}
onPress={onSend}
activeOpacity={0.8}
>
<MaterialCommunityIcons name="send" size={20} color="#FFF" />
</TouchableOpacity>
) : sending ? (
<TouchableOpacity
style={[styles.sendButtonActive, styles.sendButtonDisabled]}
disabled
>
<ActivityIndicator size="small" color="#FFF" />
</TouchableOpacity>
) : (
<TouchableOpacity
style={[styles.iconButton, activePanel === 'more' && styles.iconButtonActive]}
onPress={onToggleMore}
disabled={isDisabled || disableMore}
>
<MaterialCommunityIcons
name={activePanel === 'more' ? "close-circle" : "plus-circle-outline"}
size={26}
color={isDisabled || disableMore ? '#CCC' : (activePanel === 'more' ? colors.primary.main : '#666')}
/>
</TouchableOpacity>
)}
</View>
</View>
);
};
export default ChatInput;