Initial frontend repository commit.
Include app source and update .gitignore to exclude local release artifacts and signing files. Made-with: Cursor
This commit is contained in:
173
src/screens/message/components/ChatScreen/ChatInput.tsx
Normal file
173
src/screens/message/components/ChatScreen/ChatInput.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* 聊天输入框组件
|
||||
* 支持响应式布局(宽屏下居中显示)
|
||||
*/
|
||||
|
||||
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;
|
||||
Reference in New Issue
Block a user