feat(ChatScreen): enhance message input and attachment handling
Some checks failed
Frontend CI / build-and-push-web (push) Successful in 8m3s
Frontend CI / ota-android (push) Successful in 11m1s
Frontend CI / build-android-apk (push) Has been cancelled

- Updated ChatInput to manage pending attachments, allowing users to upload multiple images before sending.
- Introduced functionality to remove pending attachments and display their thumbnails in the input area.
- Enhanced ChatScreen to reflect changes in attachment handling, including dynamic messaging during uploads.
- Refactored message segment rendering to support inline text and grouped images, improving visual organization.
- Added constants for maximum pending images to enforce limits on user uploads.
This commit is contained in:
lafay
2026-03-25 03:44:16 +08:00
parent 583ac64dfd
commit dc8f9061ab
7 changed files with 338 additions and 95 deletions

View File

@@ -4,7 +4,7 @@
*/
import React, { useMemo } from 'react';
import { View, TouchableOpacity, TextInput, ActivityIndicator } from 'react-native';
import { View, TouchableOpacity, TextInput, ActivityIndicator, ScrollView, StyleSheet, Image as RNImage } from 'react-native';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Text } from '../../../../components/common';
import { colors } from '../../../../theme';
@@ -24,7 +24,7 @@ export const ChatInput: React.FC<ChatInputProps & {
onToggleEmoji,
onToggleMore,
activePanel,
sending,
isComposerBusy,
isMuted,
isGroupChat,
muteAll,
@@ -33,6 +33,8 @@ export const ChatInput: React.FC<ChatInputProps & {
onFocus,
restrictionHint,
disableMore = false,
pendingAttachments = [],
onRemovePendingAttachment,
currentUser,
otherUser,
getSenderInfo,
@@ -51,6 +53,11 @@ export const ChatInput: React.FC<ChatInputProps & {
isWideScreen ? { maxWidth: 900, alignSelf: 'center' as const, width: '100%' as const } : null,
]), [isWideScreen, styles.inputContainer]);
const canSend =
(!!inputText.trim() || pendingAttachments.length > 0) &&
!isComposerBusy &&
!isDisabled;
// 回复预览组件 - 定义在内部以访问 styles
const ReplyPreview: React.FC<{
replyingTo: GroupMessage;
@@ -69,7 +76,16 @@ export const ChatInput: React.FC<ChatInputProps & {
<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)}
{(() => {
const segs = replyingTo.segments;
const imgCount = segs?.filter(seg => seg.type === 'image').length ?? 0;
const raw = extractTextFromSegments(segs);
if (imgCount > 1) {
const textPart = raw.replace(/\[图片\]/g, '').replace(/\s+/g, ' ').trim();
return textPart ? `${textPart} [${imgCount}张图片]` : `[${imgCount}张图片]`;
}
return raw;
})()}
</Text>
</View>
</View>
@@ -106,6 +122,30 @@ export const ChatInput: React.FC<ChatInputProps & {
<Text style={styles.mutedBannerText}>{restrictionHint}</Text>
</View>
)}
{pendingAttachments.length > 0 && (
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={attachmentStyles.stripScroll}
contentContainerStyle={attachmentStyles.stripContent}
>
{pendingAttachments.map(item => (
<View key={item.id} style={attachmentStyles.thumbWrap}>
<RNImage source={{ uri: item.uri }} style={attachmentStyles.thumb} />
{!isDisabled && onRemovePendingAttachment ? (
<TouchableOpacity
style={attachmentStyles.removeBtn}
onPress={() => onRemovePendingAttachment(item.id)}
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
>
<MaterialCommunityIcons name="close-circle" size={22} color="rgba(0,0,0,0.55)" />
</TouchableOpacity>
) : null}
</View>
))}
</ScrollView>
)}
<View style={[styles.inputInner, isDisabled && styles.inputInnerMuted]}>
<TouchableOpacity
@@ -140,7 +180,7 @@ export const ChatInput: React.FC<ChatInputProps & {
/>
</View>
{inputText.trim() && !sending && !isDisabled ? (
{canSend ? (
<TouchableOpacity
style={styles.sendButtonActive}
onPress={onSend}
@@ -148,7 +188,7 @@ export const ChatInput: React.FC<ChatInputProps & {
>
<MaterialCommunityIcons name="send" size={20} color="#FFF" />
</TouchableOpacity>
) : sending ? (
) : isComposerBusy && !isDisabled ? (
<TouchableOpacity
style={[styles.sendButtonActive, styles.sendButtonDisabled]}
disabled
@@ -173,4 +213,34 @@ export const ChatInput: React.FC<ChatInputProps & {
);
};
const attachmentStyles = StyleSheet.create({
stripScroll: {
maxHeight: 88,
marginBottom: 6,
paddingHorizontal: 4,
},
stripContent: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 4,
},
thumbWrap: {
position: 'relative',
marginRight: 8,
},
thumb: {
width: 72,
height: 72,
borderRadius: 10,
backgroundColor: '#E8EAED',
},
removeBtn: {
position: 'absolute',
top: -6,
right: -6,
backgroundColor: '#FFF',
borderRadius: 12,
},
});
export default ChatInput;