@developer_tribe/react-native-comnyx 0.13.13 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/generated/RCTAppDependencyProvider.h +25 -0
- package/android/generated/RCTAppDependencyProvider.mm +55 -0
- package/android/generated/RCTModulesConformingToProtocolsProvider.h +18 -0
- package/android/generated/RCTModulesConformingToProtocolsProvider.mm +33 -0
- package/android/generated/RCTThirdPartyComponentsProvider.h +16 -0
- package/android/generated/RCTThirdPartyComponentsProvider.mm +23 -0
- package/android/generated/ReactAppDependencyProvider.podspec +34 -0
- package/android/generated/jni/CMakeLists.txt +36 -0
- package/android/generated/jni/RNComnyxSpec-generated.cpp +22 -0
- package/android/generated/jni/RNComnyxSpec.h +24 -0
- package/android/generated/jni/react/renderer/components/RNComnyxSpec/RNComnyxSpecJSI-generated.cpp +17 -0
- package/android/generated/jni/react/renderer/components/RNComnyxSpec/RNComnyxSpecJSI.h +19 -0
- package/android/src/main/AndroidManifest.xml +15 -0
- package/android/src/main/AndroidManifestNew.xml +4 -0
- package/android/src/main/java/com/comnyx/ComnyxMediaPickerModule.kt +347 -0
- package/android/src/main/java/com/comnyx/ComnyxPackage.kt +1 -1
- package/android/src/main/java/com/comnyx/VideoPlayerActivity.kt +91 -0
- package/ios/ComnyxMediaPicker.m +29 -0
- package/ios/ComnyxMediaPicker.swift +436 -0
- package/lib/commonjs/NativeComnyxMediaPicker.js +83 -0
- package/lib/commonjs/NativeComnyxMediaPicker.js.map +1 -0
- package/lib/commonjs/api/index.js +19 -0
- package/lib/commonjs/api/index.js.map +1 -1
- package/lib/commonjs/api/media.js +76 -0
- package/lib/commonjs/api/media.js.map +1 -0
- package/lib/commonjs/assets/attachment-01.png +0 -0
- package/lib/commonjs/assets/gallery.png +0 -0
- package/lib/commonjs/assets/video-play.png +0 -0
- package/lib/commonjs/assets/x-circle.png +0 -0
- package/lib/commonjs/components/ChatList.js +48 -22
- package/lib/commonjs/components/ChatList.js.map +1 -1
- package/lib/commonjs/components/LinkifyText.js +5 -1
- package/lib/commonjs/components/LinkifyText.js.map +1 -1
- package/lib/commonjs/components/MediaMessageItem.js +333 -0
- package/lib/commonjs/components/MediaMessageItem.js.map +1 -0
- package/lib/commonjs/components/MediaPickerButton.js +244 -0
- package/lib/commonjs/components/MediaPickerButton.js.map +1 -0
- package/lib/commonjs/components/MediaViewerModal.js +164 -0
- package/lib/commonjs/components/MediaViewerModal.js.map +1 -0
- package/lib/commonjs/components/MessageInput.js +344 -73
- package/lib/commonjs/components/MessageInput.js.map +1 -1
- package/lib/commonjs/components/MessageItem.js +17 -8
- package/lib/commonjs/components/MessageItem.js.map +1 -1
- package/lib/commonjs/constants/translations.js +174 -29
- package/lib/commonjs/constants/translations.js.map +1 -1
- package/lib/commonjs/data/fake/media.js +105 -0
- package/lib/commonjs/data/fake/media.js.map +1 -0
- package/lib/commonjs/types/MediaTypes.js +2 -0
- package/lib/commonjs/types/MediaTypes.js.map +1 -0
- package/lib/commonjs/version.js +1 -1
- package/lib/commonjs/version.js.map +1 -1
- package/lib/module/NativeComnyxMediaPicker.js +73 -0
- package/lib/module/NativeComnyxMediaPicker.js.map +1 -0
- package/lib/module/api/index.js +1 -0
- package/lib/module/api/index.js.map +1 -1
- package/lib/module/api/media.js +70 -0
- package/lib/module/api/media.js.map +1 -0
- package/lib/module/assets/attachment-01.png +0 -0
- package/lib/module/assets/gallery.png +0 -0
- package/lib/module/assets/video-play.png +0 -0
- package/lib/module/assets/x-circle.png +0 -0
- package/lib/module/components/ChatList.js +48 -22
- package/lib/module/components/ChatList.js.map +1 -1
- package/lib/module/components/LinkifyText.js +5 -1
- package/lib/module/components/LinkifyText.js.map +1 -1
- package/lib/module/components/MediaMessageItem.js +330 -0
- package/lib/module/components/MediaMessageItem.js.map +1 -0
- package/lib/module/components/MediaPickerButton.js +240 -0
- package/lib/module/components/MediaPickerButton.js.map +1 -0
- package/lib/module/components/MediaViewerModal.js +160 -0
- package/lib/module/components/MediaViewerModal.js.map +1 -0
- package/lib/module/components/MessageInput.js +347 -75
- package/lib/module/components/MessageInput.js.map +1 -1
- package/lib/module/components/MessageItem.js +17 -8
- package/lib/module/components/MessageItem.js.map +1 -1
- package/lib/module/constants/translations.js +174 -29
- package/lib/module/constants/translations.js.map +1 -1
- package/lib/module/data/fake/media.js +99 -0
- package/lib/module/data/fake/media.js.map +1 -0
- package/lib/module/types/MediaTypes.js +2 -0
- package/lib/module/types/MediaTypes.js.map +1 -0
- package/lib/module/version.js +1 -1
- package/lib/module/version.js.map +1 -1
- package/lib/typescript/src/NativeComnyxMediaPicker.d.ts +9 -0
- package/lib/typescript/src/NativeComnyxMediaPicker.d.ts.map +1 -0
- package/lib/typescript/src/api/index.d.ts +1 -0
- package/lib/typescript/src/api/index.d.ts.map +1 -1
- package/lib/typescript/src/api/media.d.ts +7 -0
- package/lib/typescript/src/api/media.d.ts.map +1 -0
- package/lib/typescript/src/components/ChatList.d.ts.map +1 -1
- package/lib/typescript/src/components/LinkifyText.d.ts.map +1 -1
- package/lib/typescript/src/components/MediaMessageItem.d.ts +6 -0
- package/lib/typescript/src/components/MediaMessageItem.d.ts.map +1 -0
- package/lib/typescript/src/components/MediaPickerButton.d.ts +5 -0
- package/lib/typescript/src/components/MediaPickerButton.d.ts.map +1 -0
- package/lib/typescript/src/components/MediaViewerModal.d.ts +8 -0
- package/lib/typescript/src/components/MediaViewerModal.d.ts.map +1 -0
- package/lib/typescript/src/components/MessageInput.d.ts.map +1 -1
- package/lib/typescript/src/components/MessageItem.d.ts.map +1 -1
- package/lib/typescript/src/constants/translations.d.ts.map +1 -1
- package/lib/typescript/src/data/fake/media.d.ts +6 -0
- package/lib/typescript/src/data/fake/media.d.ts.map +1 -0
- package/lib/typescript/src/types/Conversation.d.ts +19 -0
- package/lib/typescript/src/types/Conversation.d.ts.map +1 -1
- package/lib/typescript/src/types/LocalizationKeys.d.ts +5 -0
- package/lib/typescript/src/types/LocalizationKeys.d.ts.map +1 -1
- package/lib/typescript/src/types/MediaTypes.d.ts +26 -0
- package/lib/typescript/src/types/MediaTypes.d.ts.map +1 -0
- package/lib/typescript/src/version.d.ts +1 -1
- package/lib/typescript/src/version.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeComnyxMediaPicker.ts +83 -0
- package/src/api/index.ts +1 -0
- package/src/api/media.ts +116 -0
- package/src/assets/attachment-01.png +0 -0
- package/src/assets/gallery.png +0 -0
- package/src/assets/video-play.png +0 -0
- package/src/assets/x-circle.png +0 -0
- package/src/components/ChatList.tsx +81 -24
- package/src/components/CustomerForm.tsx +1 -1
- package/src/components/LinkifyText.tsx +3 -2
- package/src/components/MediaMessageItem.tsx +390 -0
- package/src/components/MediaPickerButton.tsx +269 -0
- package/src/components/MediaViewerModal.tsx +168 -0
- package/src/components/MessageInput.tsx +396 -84
- package/src/components/MessageItem.tsx +19 -5
- package/src/constants/translations.ts +145 -0
- package/src/data/fake/media.ts +110 -0
- package/src/types/Conversation.ts +20 -0
- package/src/types/LocalizationKeys.ts +5 -0
- package/src/types/MediaTypes.ts +27 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { NativeModules } from 'react-native';
|
|
2
|
+
import type { MediaAsset } from './types/MediaTypes';
|
|
3
|
+
|
|
4
|
+
const { ComnyxMediaPicker } = NativeModules;
|
|
5
|
+
|
|
6
|
+
function mapResults(results: any[]): MediaAsset[] {
|
|
7
|
+
return results.map((result: any) => {
|
|
8
|
+
const isImage =
|
|
9
|
+
result.type === 'image' ||
|
|
10
|
+
(result.mimeType && result.mimeType.startsWith('image'));
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
uri: result.uri,
|
|
14
|
+
type: isImage ? ('image' as const) : ('video' as const),
|
|
15
|
+
fileName: result.fileName,
|
|
16
|
+
fileSize: result.fileSize,
|
|
17
|
+
mimeType: result.mimeType,
|
|
18
|
+
width: isImage ? result.width : undefined,
|
|
19
|
+
height: isImage ? result.height : undefined,
|
|
20
|
+
duration: isImage ? undefined : result.duration,
|
|
21
|
+
thumbnailUri: isImage ? undefined : result.thumbnailUri,
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function pickMedia(): Promise<MediaAsset[]> {
|
|
27
|
+
if (!ComnyxMediaPicker) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
const results = await ComnyxMediaPicker.pickMedia();
|
|
31
|
+
if (!results || !Array.isArray(results) || results.length === 0) return [];
|
|
32
|
+
return mapResults(results);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function pickImage(): Promise<MediaAsset[]> {
|
|
36
|
+
if (!ComnyxMediaPicker) {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
const results = await ComnyxMediaPicker.pickImage();
|
|
40
|
+
if (!results || !Array.isArray(results) || results.length === 0) return [];
|
|
41
|
+
return mapResults(results);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function pickVideo(): Promise<MediaAsset[]> {
|
|
45
|
+
if (!ComnyxMediaPicker) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
const results = await ComnyxMediaPicker.pickVideo();
|
|
49
|
+
if (!results || !Array.isArray(results) || results.length === 0) return [];
|
|
50
|
+
return mapResults(results);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function openVideo(uri: string): Promise<void> {
|
|
54
|
+
if (!ComnyxMediaPicker) {
|
|
55
|
+
console.warn('[Comnyx] ComnyxMediaPicker native module is not available');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
await ComnyxMediaPicker.openVideo(uri);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function generateThumbnail(
|
|
62
|
+
videoUrl: string
|
|
63
|
+
): Promise<string | null> {
|
|
64
|
+
if (!ComnyxMediaPicker) {
|
|
65
|
+
console.warn('[Comnyx] ComnyxMediaPicker native module is not available');
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return ComnyxMediaPicker.generateThumbnail(videoUrl);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function deleteTempFile(uri: string): Promise<void> {
|
|
72
|
+
if (!ComnyxMediaPicker) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
return ComnyxMediaPicker.deleteTempFile(uri);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function cleanupTempFiles(): Promise<void> {
|
|
79
|
+
if (!ComnyxMediaPicker) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
return ComnyxMediaPicker.cleanupTempFiles();
|
|
83
|
+
}
|
package/src/api/index.ts
CHANGED
package/src/api/media.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import type { UploadUrlResponse } from '../types/MediaTypes';
|
|
3
|
+
import type { MessageResponse } from '../types/MessageResponse';
|
|
4
|
+
import type { ApiOptions } from '../types/ApiOptions';
|
|
5
|
+
import { axiosInstance } from './api';
|
|
6
|
+
import {
|
|
7
|
+
getFakeUploadUrlResponse,
|
|
8
|
+
fakeUploadToS3,
|
|
9
|
+
getFakeMediaMessageResponse,
|
|
10
|
+
} from '../data/fake/media';
|
|
11
|
+
|
|
12
|
+
export function getUploadUrl(
|
|
13
|
+
fileName: string,
|
|
14
|
+
contentType: string,
|
|
15
|
+
options: ApiOptions
|
|
16
|
+
): Promise<UploadUrlResponse> {
|
|
17
|
+
if (options.fake) {
|
|
18
|
+
return getFakeUploadUrlResponse(fileName);
|
|
19
|
+
}
|
|
20
|
+
return axiosInstance
|
|
21
|
+
.post<UploadUrlResponse>('/api/media/presign', {
|
|
22
|
+
fileName,
|
|
23
|
+
fileType: contentType,
|
|
24
|
+
})
|
|
25
|
+
.then((res) => res.data);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function uploadFileToS3(
|
|
29
|
+
uploadUrl: string,
|
|
30
|
+
fileUri: string,
|
|
31
|
+
contentType: string,
|
|
32
|
+
onProgress?: (percentage: number) => void,
|
|
33
|
+
options?: ApiOptions
|
|
34
|
+
): Promise<void> {
|
|
35
|
+
if (options?.fake) {
|
|
36
|
+
return fakeUploadToS3(onProgress);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const xhr = new XMLHttpRequest();
|
|
41
|
+
|
|
42
|
+
xhr.upload.addEventListener('progress', (event) => {
|
|
43
|
+
if (event.lengthComputable && onProgress) {
|
|
44
|
+
const percentage = Math.min(
|
|
45
|
+
100,
|
|
46
|
+
Math.round((event.loaded / event.total) * 100)
|
|
47
|
+
);
|
|
48
|
+
onProgress(percentage);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
xhr.addEventListener('load', () => {
|
|
53
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
54
|
+
resolve();
|
|
55
|
+
} else {
|
|
56
|
+
console.error('[Comnyx] Upload failed:', xhr.status, xhr.responseText);
|
|
57
|
+
reject(new Error(`Upload failed with status ${xhr.status}`));
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
xhr.addEventListener('error', () => {
|
|
62
|
+
console.error(
|
|
63
|
+
'[Comnyx] Upload XHR error, status:',
|
|
64
|
+
xhr.status,
|
|
65
|
+
'response:',
|
|
66
|
+
xhr.responseText
|
|
67
|
+
);
|
|
68
|
+
reject(new Error('Upload failed'));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
xhr.addEventListener('abort', () => {
|
|
72
|
+
reject(new Error('Upload aborted'));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
xhr.open('PUT', uploadUrl);
|
|
76
|
+
xhr.setRequestHeader('Content-Type', contentType);
|
|
77
|
+
|
|
78
|
+
// React Native supports sending file URIs directly via XHR
|
|
79
|
+
const file: any = {
|
|
80
|
+
uri: Platform.OS === 'android' ? fileUri : fileUri.replace('file://', ''),
|
|
81
|
+
type: contentType,
|
|
82
|
+
name: fileUri.split('/').pop() || 'file',
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
xhr.send(file);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function sendMediaMessage(
|
|
90
|
+
externalId: string,
|
|
91
|
+
fileUrls: string[],
|
|
92
|
+
mediaType: 'image' | 'video',
|
|
93
|
+
content: string,
|
|
94
|
+
options: ApiOptions
|
|
95
|
+
): Promise<MessageResponse> {
|
|
96
|
+
if (options.fake) {
|
|
97
|
+
return getFakeMediaMessageResponse(fileUrls, mediaType);
|
|
98
|
+
}
|
|
99
|
+
return axiosInstance
|
|
100
|
+
.post<MessageResponse>(
|
|
101
|
+
'/api/customers/message',
|
|
102
|
+
{
|
|
103
|
+
externalId,
|
|
104
|
+
content,
|
|
105
|
+
files: fileUrls,
|
|
106
|
+
media_type: mediaType,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
headers: {
|
|
110
|
+
'Accept': 'application/json',
|
|
111
|
+
'Content-Type': 'application/json',
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
.then((res) => res.data);
|
|
116
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -15,7 +15,10 @@ import {
|
|
|
15
15
|
} from 'react-native';
|
|
16
16
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
17
17
|
import { getCustomerConversation, sendCustomerMessage } from '../api';
|
|
18
|
-
import type {
|
|
18
|
+
import type {
|
|
19
|
+
AppConversationMessage,
|
|
20
|
+
ConversationMessage,
|
|
21
|
+
} from '../types/Conversation';
|
|
19
22
|
import { MessageItem } from './MessageItem';
|
|
20
23
|
import { MessageInput } from './MessageInput';
|
|
21
24
|
import { useThemeColors } from '../hooks/useThemeColors';
|
|
@@ -32,6 +35,66 @@ import { useAppStore } from '../store/store';
|
|
|
32
35
|
const headphonesIcon = require('../assets/headphones-01.png');
|
|
33
36
|
const closeIcon = require('../assets/x-close.png');
|
|
34
37
|
|
|
38
|
+
const VIDEO_EXTENSIONS = [
|
|
39
|
+
'.mp4',
|
|
40
|
+
'.mov',
|
|
41
|
+
'.m4v',
|
|
42
|
+
'.avi',
|
|
43
|
+
'.webm',
|
|
44
|
+
'.mkv',
|
|
45
|
+
'.3gp',
|
|
46
|
+
];
|
|
47
|
+
const IMAGE_EXTENSIONS = [
|
|
48
|
+
'.jpg',
|
|
49
|
+
'.jpeg',
|
|
50
|
+
'.png',
|
|
51
|
+
'.gif',
|
|
52
|
+
'.heic',
|
|
53
|
+
'.heif',
|
|
54
|
+
'.webp',
|
|
55
|
+
'.bmp',
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
function getMediaTypeFromFile(
|
|
59
|
+
file: { mime_type?: string; url?: string } | null
|
|
60
|
+
): 'image' | 'video' | undefined {
|
|
61
|
+
if (!file) return undefined;
|
|
62
|
+
// 1. Try mime_type first
|
|
63
|
+
if (file.mime_type) {
|
|
64
|
+
if (file.mime_type.startsWith('image')) return 'image';
|
|
65
|
+
if (file.mime_type.startsWith('video')) return 'video';
|
|
66
|
+
}
|
|
67
|
+
// 2. Fallback: check URL for known extensions
|
|
68
|
+
if (file.url) {
|
|
69
|
+
// Strip query params from URL for extension check
|
|
70
|
+
const urlPath = file.url.split('?')[0] || '';
|
|
71
|
+
const lower = urlPath.toLowerCase();
|
|
72
|
+
if (VIDEO_EXTENSIONS.some((ext) => lower.endsWith(ext))) return 'video';
|
|
73
|
+
if (IMAGE_EXTENSIONS.some((ext) => lower.endsWith(ext))) return 'image';
|
|
74
|
+
}
|
|
75
|
+
// 3. Default to image if file exists but can't determine type
|
|
76
|
+
return file.url ? 'image' : undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function processRawMessage(msg: ConversationMessage): AppConversationMessage {
|
|
80
|
+
const file = msg.files && msg.files.length > 0 ? msg.files[0] : null;
|
|
81
|
+
const mediaFiles =
|
|
82
|
+
msg.files && msg.files.length > 0
|
|
83
|
+
? msg.files.map((f) => ({
|
|
84
|
+
url: f.url,
|
|
85
|
+
type: getMediaTypeFromFile(f) || ('image' as const),
|
|
86
|
+
}))
|
|
87
|
+
: undefined;
|
|
88
|
+
return {
|
|
89
|
+
...msg,
|
|
90
|
+
created_at: new Date(msg.created_at),
|
|
91
|
+
approved: true,
|
|
92
|
+
media_url: file?.url || undefined,
|
|
93
|
+
media_type: getMediaTypeFromFile(file),
|
|
94
|
+
media_files: mediaFiles,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
35
98
|
function LoadingItem() {
|
|
36
99
|
const themeColors = useThemeColors();
|
|
37
100
|
return (
|
|
@@ -141,7 +204,15 @@ export function ChatList({
|
|
|
141
204
|
const sections = useMemo(() => {
|
|
142
205
|
if (!data || data.length === 0) return [];
|
|
143
206
|
|
|
144
|
-
const
|
|
207
|
+
const seenIds = new Set<number>();
|
|
208
|
+
const dedupedData = data.filter((msg) => {
|
|
209
|
+
if (msg.id == null) return true;
|
|
210
|
+
if (seenIds.has(msg.id)) return false;
|
|
211
|
+
seenIds.add(msg.id);
|
|
212
|
+
return true;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const validData = dedupedData.map((message) => {
|
|
145
216
|
if (!message.created_at || !(message.created_at instanceof Date)) {
|
|
146
217
|
return {
|
|
147
218
|
...message,
|
|
@@ -340,11 +411,8 @@ export function ChatList({
|
|
|
340
411
|
(msg) => !existingIds.has(msg.id)
|
|
341
412
|
);
|
|
342
413
|
|
|
343
|
-
const processedMessages =
|
|
344
|
-
|
|
345
|
-
created_at: new Date(u.created_at),
|
|
346
|
-
approved: true,
|
|
347
|
-
}));
|
|
414
|
+
const processedMessages =
|
|
415
|
+
uniqueNewMessages.map(processRawMessage);
|
|
348
416
|
|
|
349
417
|
if (processedMessages.length === 0) {
|
|
350
418
|
nextPageStatus.current = 'empty';
|
|
@@ -355,11 +423,7 @@ export function ChatList({
|
|
|
355
423
|
}
|
|
356
424
|
} else if (newMessages.length > 0) {
|
|
357
425
|
// Only new messages, no previous data
|
|
358
|
-
const processedMessages = newMessages.map(
|
|
359
|
-
...u,
|
|
360
|
-
created_at: new Date(u.created_at),
|
|
361
|
-
approved: true,
|
|
362
|
-
}));
|
|
426
|
+
const processedMessages = newMessages.map(processRawMessage);
|
|
363
427
|
nextPageStatus.current = undefined;
|
|
364
428
|
return processedMessages;
|
|
365
429
|
} else {
|
|
@@ -464,11 +528,8 @@ export function ChatList({
|
|
|
464
528
|
const uniqueNewMessages = newMessages.filter(
|
|
465
529
|
(msg) => !existingIds.has(msg.id)
|
|
466
530
|
);
|
|
467
|
-
const processedMessages =
|
|
468
|
-
|
|
469
|
-
created_at: new Date(u.created_at),
|
|
470
|
-
approved: true,
|
|
471
|
-
}));
|
|
531
|
+
const processedMessages =
|
|
532
|
+
uniqueNewMessages.map(processRawMessage);
|
|
472
533
|
useAppStore.setState({
|
|
473
534
|
firstMessage: processedMessages[0] || prevData[0],
|
|
474
535
|
});
|
|
@@ -478,11 +539,7 @@ export function ChatList({
|
|
|
478
539
|
return [...prevData, ...processedMessages];
|
|
479
540
|
} else {
|
|
480
541
|
// Handle the case where there's no previous data
|
|
481
|
-
const processedMessages = newMessages.map(
|
|
482
|
-
...u,
|
|
483
|
-
created_at: new Date(u.created_at),
|
|
484
|
-
approved: true,
|
|
485
|
-
}));
|
|
542
|
+
const processedMessages = newMessages.map(processRawMessage);
|
|
486
543
|
useAppStore.setState({
|
|
487
544
|
firstMessage: processedMessages[0],
|
|
488
545
|
});
|
|
@@ -638,8 +695,8 @@ export function ChatList({
|
|
|
638
695
|
) : null
|
|
639
696
|
}
|
|
640
697
|
ListFooterComponent={loading ? <LoadingItem /> : null}
|
|
641
|
-
keyExtractor={(item: AppConversationMessage) =>
|
|
642
|
-
item.id
|
|
698
|
+
keyExtractor={(item: AppConversationMessage, index: number) =>
|
|
699
|
+
item.id?.toString() ?? item.local_id ?? `fake-${index}`
|
|
643
700
|
}
|
|
644
701
|
removeClippedSubviews={false}
|
|
645
702
|
maxToRenderPerBatch={20}
|
|
@@ -15,7 +15,8 @@ interface Props {
|
|
|
15
15
|
patterns: Pattern[];
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function parseText(text: string, patterns: Pattern[]) {
|
|
18
|
+
function parseText(text: string | null | undefined, patterns: Pattern[]) {
|
|
19
|
+
if (!text) return [{ type: 'text' as const, value: '' }];
|
|
19
20
|
type Segment =
|
|
20
21
|
| { type: 'text'; value: string }
|
|
21
22
|
| { type: 'match'; pattern: Pattern; value: string; matchedText: string };
|
|
@@ -74,7 +75,7 @@ export function LinkifyText({
|
|
|
74
75
|
containerStyle,
|
|
75
76
|
...props
|
|
76
77
|
}: Props) {
|
|
77
|
-
const parts = parseText(children, patterns);
|
|
78
|
+
const parts = parseText(children || '', patterns);
|
|
78
79
|
|
|
79
80
|
return (
|
|
80
81
|
<AppText style={containerStyle} {...props}>
|