connect-x-reactnative-livechat-sdk 0.2.0 → 1.2.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/README.md +117 -15
- package/android/build.gradle +6 -6
- package/android/src/main/AndroidManifest.xml +1 -0
- package/lib/module/ChatScreen.js +276 -118
- package/lib/module/ChatScreen.js.map +1 -1
- package/lib/module/api.js +2 -1
- package/lib/module/api.js.map +1 -1
- package/lib/module/assets/send.js +40 -0
- package/lib/module/assets/send.js.map +1 -0
- package/lib/module/index.js +3 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/ChatScreen.d.ts +4 -1
- package/lib/typescript/src/ChatScreen.d.ts.map +1 -1
- package/lib/typescript/src/api.d.ts +1 -1
- package/lib/typescript/src/api.d.ts.map +1 -1
- package/lib/typescript/src/assets/send.d.ts +7 -0
- package/lib/typescript/src/assets/send.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/ChatScreen.tsx +327 -116
- package/src/api.ts +2 -2
- package/src/assets/send.tsx +28 -0
- package/src/index.tsx +9 -1
package/src/ChatScreen.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/ChatScreen.tsx
|
|
2
|
-
import { useState, useEffect, useRef, useMemo } from 'react';
|
|
2
|
+
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
|
3
3
|
import {
|
|
4
4
|
View,
|
|
5
5
|
Text,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
Alert,
|
|
16
16
|
Linking,
|
|
17
17
|
Dimensions,
|
|
18
|
+
Modal,
|
|
18
19
|
} from 'react-native';
|
|
19
20
|
import io from 'socket.io-client';
|
|
20
21
|
import type { Socket } from 'socket.io-client';
|
|
@@ -25,8 +26,9 @@ import {
|
|
|
25
26
|
} from '@react-native-documents/picker';
|
|
26
27
|
import { ConnectXMobileSdk } from 'connect-x-react-native-sdk';
|
|
27
28
|
import { apiGet, uploadFile, BASE_URL_LIVE_CHAT, apiPost } from './api';
|
|
28
|
-
import Svg, { Path } from 'react-native-svg';
|
|
29
|
+
import Svg, { ClipPath, Defs, G, Path, Rect } from 'react-native-svg';
|
|
29
30
|
import hexToRgba from 'hex-to-rgba';
|
|
31
|
+
import ImageViewer from 'react-native-image-zoom-viewer';
|
|
30
32
|
|
|
31
33
|
interface Message {
|
|
32
34
|
id?: string;
|
|
@@ -40,29 +42,47 @@ interface Message {
|
|
|
40
42
|
[key: string]: any; // for other properties
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
const SendIcon = ({
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
const SendIcon = ({
|
|
46
|
+
size,
|
|
47
|
+
backgroundColor,
|
|
48
|
+
iconColor,
|
|
49
|
+
}: {
|
|
50
|
+
size: number;
|
|
51
|
+
backgroundColor: string;
|
|
52
|
+
iconColor: string;
|
|
53
|
+
}) => (
|
|
54
|
+
<Svg width={size} height={size} viewBox="0 0 32 32" fill="none">
|
|
55
|
+
<Rect width={32} height={32} rx={16} fill={backgroundColor} />
|
|
56
|
+
<Defs>
|
|
57
|
+
<ClipPath id="clip0_4087_14448">
|
|
58
|
+
<Rect width={20} height={20} fill="white" transform="translate(6 6)" />
|
|
59
|
+
</ClipPath>
|
|
60
|
+
</Defs>
|
|
61
|
+
<G clipPath="url(#clip0_4087_14448)">
|
|
62
|
+
<Path
|
|
63
|
+
fillRule="evenodd"
|
|
64
|
+
clipRule="evenodd"
|
|
65
|
+
d="M20.9918 11.0084L10.4985 14.8026L13.9943 16.826L17.0768 13.7426C17.2332 13.5864 17.4452 13.4986 17.6663 13.4987C17.8874 13.4988 18.0993 13.5867 18.2556 13.743C18.4118 13.8994 18.4996 14.1114 18.4995 14.3325C18.4994 14.5536 18.4115 14.7655 18.2552 14.9218L15.1718 18.0051L17.1968 21.5001L20.9918 11.0084ZM21.261 9.13845C22.2568 8.77762 23.2218 9.74262 22.861 10.7384L18.4593 22.9093C18.0977 23.9076 16.7343 24.0293 16.2018 23.1101L13.521 18.4784L8.88934 15.7976C7.97018 15.2651 8.09184 13.9018 9.09018 13.5401L21.261 9.13845Z"
|
|
66
|
+
fill={iconColor}
|
|
67
|
+
/>
|
|
68
|
+
</G>
|
|
52
69
|
</Svg>
|
|
53
70
|
);
|
|
54
|
-
|
|
55
71
|
// Component หลักของหน้าแชท
|
|
56
72
|
export const ChatScreen = ({
|
|
57
73
|
onClose,
|
|
58
74
|
token,
|
|
59
75
|
orgId,
|
|
60
76
|
channelId,
|
|
77
|
+
externalValue,
|
|
78
|
+
customHeader,
|
|
61
79
|
}: {
|
|
62
80
|
onClose: () => void;
|
|
63
81
|
token: string;
|
|
64
82
|
orgId: string;
|
|
65
83
|
channelId: string;
|
|
84
|
+
externalValue: string;
|
|
85
|
+
customHeader?: React.ReactNode;
|
|
66
86
|
}) => {
|
|
67
87
|
const [messages, setMessages] = useState<Message[]>([]);
|
|
68
88
|
const [inputText, setInputText] = useState('');
|
|
@@ -71,12 +91,17 @@ export const ChatScreen = ({
|
|
|
71
91
|
const [userId, setUserId] = useState<string | null>(null);
|
|
72
92
|
const [selectedFile, setSelectedFile] =
|
|
73
93
|
useState<DocumentPickerResponse | null>(null);
|
|
74
|
-
const [
|
|
94
|
+
const [settings, setSettings] = useState<any>(null);
|
|
75
95
|
const [styles, setStyles] = useState<any>({});
|
|
76
96
|
const sectionListRef = useRef<SectionList<Message>>(null);
|
|
77
97
|
const [chatPhase, setChatPhase] = useState<'pre-chat' | 'chatting'>(
|
|
78
98
|
'chatting'
|
|
79
99
|
);
|
|
100
|
+
const [isSending, setIsSending] = useState(false);
|
|
101
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
102
|
+
const [isImageViewerVisible, setImageViewerVisible] = useState(false);
|
|
103
|
+
const [viewerImages, setViewerImages] = useState<{ url: string }[]>([]);
|
|
104
|
+
const [viewerIndex, setViewerIndex] = useState(0);
|
|
80
105
|
|
|
81
106
|
useEffect(() => {
|
|
82
107
|
const findCustomer = async (unknownId: String) => {
|
|
@@ -84,7 +109,7 @@ export const ChatScreen = ({
|
|
|
84
109
|
const res = await apiPost(
|
|
85
110
|
`liveChat/${orgId}/${channelId}/setLiveChatConnector`,
|
|
86
111
|
{
|
|
87
|
-
externalValue:
|
|
112
|
+
externalValue: externalValue, //'ปุ้นซู่ซ่า', //sdf0001111111111 //78e839c38397b8cac97437d17b1c91b568a5adb4
|
|
88
113
|
userId: unknownId,
|
|
89
114
|
},
|
|
90
115
|
token
|
|
@@ -96,12 +121,16 @@ export const ChatScreen = ({
|
|
|
96
121
|
// initializeUserId();
|
|
97
122
|
}
|
|
98
123
|
} catch (error) {
|
|
99
|
-
|
|
124
|
+
onClose();
|
|
125
|
+
Alert.alert('Message', 'Customer not found. Please try again later.');
|
|
100
126
|
console.log('Error finding customer:', error);
|
|
101
127
|
}
|
|
102
128
|
};
|
|
103
129
|
|
|
104
130
|
const initializeUserId = async () => {
|
|
131
|
+
if (!(await checkAllowed())) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
105
134
|
try {
|
|
106
135
|
const res = await ConnectXMobileSdk.getUnknownId();
|
|
107
136
|
console.log('Generated userId:', res);
|
|
@@ -111,6 +140,33 @@ export const ChatScreen = ({
|
|
|
111
140
|
console.log('Error generating userId:', error);
|
|
112
141
|
}
|
|
113
142
|
};
|
|
143
|
+
|
|
144
|
+
const checkAllowed = async () => {
|
|
145
|
+
try {
|
|
146
|
+
const res = await apiGet(`liveChat/${orgId}/checkAllowApp`, token);
|
|
147
|
+
console.log('App permissions:', res);
|
|
148
|
+
if (res) {
|
|
149
|
+
console.log('Permission granted for app access');
|
|
150
|
+
return true;
|
|
151
|
+
} else {
|
|
152
|
+
onClose();
|
|
153
|
+
Alert.alert(
|
|
154
|
+
'Permission Denied',
|
|
155
|
+
`You do not have permission to access this chat`
|
|
156
|
+
);
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
onClose();
|
|
161
|
+
console.log('Error checking permissions:', error);
|
|
162
|
+
Alert.alert(
|
|
163
|
+
'Permission Denied',
|
|
164
|
+
`You do not have permission to access this chat.`
|
|
165
|
+
);
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
114
170
|
if (!userId) {
|
|
115
171
|
initializeUserId();
|
|
116
172
|
}
|
|
@@ -122,12 +178,13 @@ export const ChatScreen = ({
|
|
|
122
178
|
try {
|
|
123
179
|
console.log('Checking domain/app permissions...');
|
|
124
180
|
|
|
125
|
-
const
|
|
181
|
+
const resSettings = await apiGet(
|
|
126
182
|
`liveChat/${orgId}/${channelId}/getSettingConnector`,
|
|
127
183
|
token
|
|
128
184
|
);
|
|
129
|
-
|
|
130
|
-
|
|
185
|
+
console.log('Settings received:', resSettings);
|
|
186
|
+
setSettings(resSettings);
|
|
187
|
+
setStyles(getDynamicStyles(resSettings.styles));
|
|
131
188
|
|
|
132
189
|
const history = await apiGet(
|
|
133
190
|
`liveChat/${orgId}/${channelId}/getChatHistory/${userId}`,
|
|
@@ -174,7 +231,7 @@ export const ChatScreen = ({
|
|
|
174
231
|
|
|
175
232
|
const groupedMessages = messages.reduce(
|
|
176
233
|
(acc, message) => {
|
|
177
|
-
const dateKey = new Date(message.timestamp).toLocaleDateString('en-CA'); //
|
|
234
|
+
const dateKey = new Date(message.timestamp).toLocaleDateString('en-CA'); // yyyy-MM-dd format for reliable sorting
|
|
178
235
|
if (!acc[dateKey]) {
|
|
179
236
|
acc[dateKey] = [];
|
|
180
237
|
}
|
|
@@ -308,13 +365,27 @@ export const ChatScreen = ({
|
|
|
308
365
|
animated: true,
|
|
309
366
|
});
|
|
310
367
|
}
|
|
311
|
-
},
|
|
368
|
+
}, 1000);
|
|
312
369
|
}
|
|
313
370
|
} catch (error) {
|
|
314
371
|
console.error('Error scrolling to last section:', error);
|
|
315
372
|
}
|
|
316
373
|
}, [messageSections]);
|
|
317
374
|
|
|
375
|
+
const handleContentSizeChange = () => {
|
|
376
|
+
if (sectionListRef.current && messageSections.length > 0) {
|
|
377
|
+
const lastSectionIndex = messageSections.length - 1;
|
|
378
|
+
sectionListRef.current.scrollToLocation({
|
|
379
|
+
sectionIndex: lastSectionIndex,
|
|
380
|
+
itemIndex: messageSections[lastSectionIndex]?.data
|
|
381
|
+
? messageSections[lastSectionIndex].data.length - 1
|
|
382
|
+
: 0,
|
|
383
|
+
viewPosition: 0,
|
|
384
|
+
animated: true,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
318
389
|
const handleScrollToIndexFailed = (info: {
|
|
319
390
|
index: number;
|
|
320
391
|
highestMeasuredFrameIndex: number;
|
|
@@ -334,15 +405,18 @@ export const ChatScreen = ({
|
|
|
334
405
|
}
|
|
335
406
|
};
|
|
336
407
|
|
|
337
|
-
const
|
|
338
|
-
if (
|
|
408
|
+
const handleSendText = () => {
|
|
409
|
+
if (inputText.trim() === '' || isSending) return;
|
|
410
|
+
setIsSending(true);
|
|
411
|
+
|
|
339
412
|
const fullMessage: Message = {
|
|
340
|
-
|
|
413
|
+
type: 'text',
|
|
414
|
+
text: inputText.trim(),
|
|
341
415
|
sender: 'user',
|
|
342
416
|
timestamp: Date.now(),
|
|
343
|
-
type: messageBody.type || 'text',
|
|
344
417
|
};
|
|
345
|
-
|
|
418
|
+
|
|
419
|
+
socketRef.current?.emit(
|
|
346
420
|
'sendMessage',
|
|
347
421
|
{
|
|
348
422
|
organizeId: orgId,
|
|
@@ -351,19 +425,12 @@ export const ChatScreen = ({
|
|
|
351
425
|
message: fullMessage,
|
|
352
426
|
},
|
|
353
427
|
() => {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
428
|
+
setInputText('');
|
|
429
|
+
setIsSending(false);
|
|
357
430
|
}
|
|
358
431
|
);
|
|
359
432
|
};
|
|
360
433
|
|
|
361
|
-
const handleSendText = () => {
|
|
362
|
-
if (inputText.trim()) {
|
|
363
|
-
sendMessage({ type: 'text', text: inputText.trim() });
|
|
364
|
-
}
|
|
365
|
-
};
|
|
366
|
-
|
|
367
434
|
const handleSelectFile = async () => {
|
|
368
435
|
try {
|
|
369
436
|
const result = await pick({
|
|
@@ -373,33 +440,96 @@ export const ChatScreen = ({
|
|
|
373
440
|
setSelectedFile(result[0]);
|
|
374
441
|
} catch (err) {
|
|
375
442
|
if ((err as any)?.code !== 'DOCUMENT_PICKER_CANCELED') {
|
|
376
|
-
Alert.alert(
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
);
|
|
443
|
+
// Alert.alert(
|
|
444
|
+
// 'File-Picker Error',
|
|
445
|
+
// 'An error occurred during file selection.'
|
|
446
|
+
// );
|
|
380
447
|
}
|
|
381
448
|
}
|
|
382
449
|
};
|
|
383
450
|
|
|
451
|
+
const MessageWithLinks = ({
|
|
452
|
+
text,
|
|
453
|
+
textStyle,
|
|
454
|
+
linkStyle,
|
|
455
|
+
}: {
|
|
456
|
+
text: string;
|
|
457
|
+
textStyle: object;
|
|
458
|
+
linkStyle: object;
|
|
459
|
+
}) => {
|
|
460
|
+
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
|
461
|
+
const parts = text.split(urlRegex);
|
|
462
|
+
|
|
463
|
+
return (
|
|
464
|
+
<Text style={textStyle}>
|
|
465
|
+
{parts.map((part, index) => {
|
|
466
|
+
if (part.match(urlRegex)) {
|
|
467
|
+
return (
|
|
468
|
+
<Text
|
|
469
|
+
key={index}
|
|
470
|
+
style={linkStyle}
|
|
471
|
+
onPress={() => Linking.openURL(part)}
|
|
472
|
+
>
|
|
473
|
+
{part}
|
|
474
|
+
</Text>
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
return <Text key={index}>{part}</Text>;
|
|
478
|
+
})}
|
|
479
|
+
</Text>
|
|
480
|
+
);
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
const openImageViewer = (tappedImageUrl: string) => {
|
|
484
|
+
const allImages = messages
|
|
485
|
+
.filter((msg) => msg.type === 'image' && msg.url)
|
|
486
|
+
.map((msg) => ({ url: msg.url! }));
|
|
487
|
+
|
|
488
|
+
const tappedIndex = allImages.findIndex(
|
|
489
|
+
(img) => img.url === tappedImageUrl
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
if (tappedIndex !== -1) {
|
|
493
|
+
setViewerImages(allImages);
|
|
494
|
+
setViewerIndex(tappedIndex);
|
|
495
|
+
setImageViewerVisible(true);
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
|
|
384
499
|
const handleCancelUpload = () => {
|
|
500
|
+
if (isUploading) return;
|
|
385
501
|
setSelectedFile(null);
|
|
386
502
|
};
|
|
387
503
|
|
|
388
504
|
const handleConfirmUpload = async () => {
|
|
389
|
-
if (!selectedFile) return;
|
|
505
|
+
if (!selectedFile || isUploading) return;
|
|
506
|
+
setIsUploading(true);
|
|
390
507
|
try {
|
|
391
508
|
const fileUrl = await uploadFile(selectedFile, orgId, channelId);
|
|
392
509
|
const type = selectedFile.type?.startsWith('image/') ? 'image' : 'file';
|
|
393
|
-
|
|
510
|
+
const messageBody: Omit<Message, 'sender' | 'timestamp'> = {
|
|
394
511
|
type,
|
|
395
512
|
fileName: selectedFile.name || 'file',
|
|
396
513
|
url: fileUrl,
|
|
514
|
+
};
|
|
515
|
+
const fullMessage: Message = {
|
|
516
|
+
...messageBody,
|
|
517
|
+
sender: 'user',
|
|
518
|
+
timestamp: Date.now(),
|
|
519
|
+
type,
|
|
520
|
+
};
|
|
521
|
+
socketRef.current?.emit('sendMessage', {
|
|
522
|
+
organizeId: orgId,
|
|
523
|
+
channelId: channelId,
|
|
524
|
+
userId: userId,
|
|
525
|
+
message: fullMessage,
|
|
397
526
|
});
|
|
398
527
|
} catch (error) {
|
|
399
528
|
Alert.alert('Upload Error', 'Failed to upload the file.');
|
|
400
529
|
console.error('Upload failed', error);
|
|
401
530
|
} finally {
|
|
402
531
|
setSelectedFile(null);
|
|
532
|
+
setIsUploading(false);
|
|
403
533
|
}
|
|
404
534
|
};
|
|
405
535
|
|
|
@@ -436,12 +566,17 @@ export const ChatScreen = ({
|
|
|
436
566
|
item.sender === 'user' ? styles.userRow : styles.agentRow,
|
|
437
567
|
]}
|
|
438
568
|
>
|
|
439
|
-
{item.sender === 'agent' &&
|
|
569
|
+
{item.sender === 'agent' && settings?.styles?.styleLogoUrl && (
|
|
440
570
|
<Image
|
|
441
|
-
source={{ uri:
|
|
571
|
+
source={{ uri: settings.styles.styleLogoUrl }}
|
|
442
572
|
style={styles.avatar}
|
|
443
573
|
/>
|
|
444
574
|
)}
|
|
575
|
+
{item.sender === 'user' && (
|
|
576
|
+
<Text style={styles.timestampText}>
|
|
577
|
+
{formatTimestamp(item.timestamp)}
|
|
578
|
+
</Text>
|
|
579
|
+
)}
|
|
445
580
|
<View
|
|
446
581
|
style={
|
|
447
582
|
item.sender === 'agent'
|
|
@@ -449,9 +584,6 @@ export const ChatScreen = ({
|
|
|
449
584
|
: styles.messageContainer
|
|
450
585
|
}
|
|
451
586
|
>
|
|
452
|
-
<Text style={styles.timestampText}>
|
|
453
|
-
{formatTimestamp(item.timestamp)}
|
|
454
|
-
</Text>
|
|
455
587
|
<View
|
|
456
588
|
style={[
|
|
457
589
|
styles.messageBubble,
|
|
@@ -459,22 +591,24 @@ export const ChatScreen = ({
|
|
|
459
591
|
]}
|
|
460
592
|
>
|
|
461
593
|
{item.type === 'text' && (
|
|
462
|
-
<
|
|
463
|
-
|
|
594
|
+
<MessageWithLinks
|
|
595
|
+
text={item.text || ''}
|
|
596
|
+
textStyle={
|
|
464
597
|
item.sender === 'user'
|
|
465
598
|
? styles.customerChatText
|
|
466
599
|
: styles.agentChatText
|
|
467
600
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
</Text>
|
|
601
|
+
linkStyle={styles.linkTextStyle}
|
|
602
|
+
/>
|
|
471
603
|
)}
|
|
472
604
|
{item.type === 'image' && (
|
|
473
|
-
<
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
605
|
+
<TouchableOpacity onPress={() => openImageViewer(item.url!)}>
|
|
606
|
+
<Image
|
|
607
|
+
source={{ uri: item.url }}
|
|
608
|
+
style={styles.imageMessage}
|
|
609
|
+
resizeMode="cover"
|
|
610
|
+
/>
|
|
611
|
+
</TouchableOpacity>
|
|
478
612
|
)}
|
|
479
613
|
{item.type === 'file' && (
|
|
480
614
|
<TouchableOpacity onPress={handleFilePress}>
|
|
@@ -491,6 +625,11 @@ export const ChatScreen = ({
|
|
|
491
625
|
</TouchableOpacity>
|
|
492
626
|
)}
|
|
493
627
|
</View>
|
|
628
|
+
{item.sender === 'agent' && (
|
|
629
|
+
<Text style={styles.timestampText}>
|
|
630
|
+
{formatTimestamp(item.timestamp)}
|
|
631
|
+
</Text>
|
|
632
|
+
)}
|
|
494
633
|
</View>
|
|
495
634
|
</View>
|
|
496
635
|
);
|
|
@@ -504,36 +643,54 @@ export const ChatScreen = ({
|
|
|
504
643
|
);
|
|
505
644
|
}
|
|
506
645
|
|
|
507
|
-
// const agentProfile = {
|
|
508
|
-
// name: styleSettings?.name,
|
|
509
|
-
// avatarUrl: styleSettings?.styleLogoUrl,
|
|
510
|
-
// };
|
|
511
|
-
|
|
512
646
|
return (
|
|
513
647
|
<SafeAreaView style={styles.safeArea}>
|
|
514
|
-
<
|
|
515
|
-
<
|
|
516
|
-
{
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
648
|
+
<Modal visible={isImageViewerVisible} transparent={true}>
|
|
649
|
+
<ImageViewer
|
|
650
|
+
imageUrls={viewerImages}
|
|
651
|
+
index={viewerIndex}
|
|
652
|
+
onCancel={() => setImageViewerVisible(false)}
|
|
653
|
+
enableSwipeDown
|
|
654
|
+
renderHeader={() => (
|
|
655
|
+
<TouchableOpacity
|
|
656
|
+
style={styles.imageViewerCloseButton}
|
|
657
|
+
onPress={() => setImageViewerVisible(false)}
|
|
658
|
+
>
|
|
659
|
+
<Text style={styles.imageViewerCloseButtonText}>✕</Text>
|
|
660
|
+
</TouchableOpacity>
|
|
521
661
|
)}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
662
|
+
/>
|
|
663
|
+
</Modal>
|
|
664
|
+
{customHeader !== undefined ? (
|
|
665
|
+
customHeader
|
|
666
|
+
) : (
|
|
667
|
+
<View style={styles.header}>
|
|
668
|
+
<View style={styles.headerProfile}>
|
|
669
|
+
{settings?.styles?.styleLogoUrl && (
|
|
670
|
+
<Image
|
|
671
|
+
source={{ uri: settings.styles.styleLogoUrl }}
|
|
672
|
+
style={styles.headerAvatar}
|
|
673
|
+
/>
|
|
674
|
+
)}
|
|
675
|
+
<Text style={styles.headerTitle}>
|
|
676
|
+
{settings?.styles.name || 'Live Chat'}
|
|
677
|
+
</Text>
|
|
678
|
+
</View>
|
|
679
|
+
<TouchableOpacity onPress={onClose}>
|
|
680
|
+
<Text style={styles.closeButtonText}>✕</Text>
|
|
681
|
+
</TouchableOpacity>
|
|
525
682
|
</View>
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
683
|
+
)}
|
|
684
|
+
<KeyboardAvoidingView
|
|
685
|
+
style={styles.container}
|
|
686
|
+
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
|
687
|
+
>
|
|
531
688
|
{chatPhase === 'pre-chat' ? (
|
|
532
689
|
<PreChatForm
|
|
533
690
|
onSubmit={() => setChatPhase('chatting')}
|
|
534
691
|
// agentProfile={agentProfile}
|
|
535
692
|
// styles={styles}
|
|
536
|
-
headerColor={
|
|
693
|
+
headerColor={settings?.styles?.styleHeadColor}
|
|
537
694
|
/>
|
|
538
695
|
) : (
|
|
539
696
|
<>
|
|
@@ -549,6 +706,7 @@ export const ChatScreen = ({
|
|
|
549
706
|
keyExtractor={(item) => item.timestamp.toString()}
|
|
550
707
|
style={styles.messageList}
|
|
551
708
|
onScrollToIndexFailed={handleScrollToIndexFailed}
|
|
709
|
+
onContentSizeChange={handleContentSizeChange}
|
|
552
710
|
/>
|
|
553
711
|
<View style={styles.inputContainer}>
|
|
554
712
|
{selectedFile ? (
|
|
@@ -560,18 +718,30 @@ export const ChatScreen = ({
|
|
|
560
718
|
</Text>
|
|
561
719
|
</View>
|
|
562
720
|
<View style={styles.buttonRow}>
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
721
|
+
{isUploading ? (
|
|
722
|
+
<ActivityIndicator
|
|
723
|
+
color={styles.uploadButton.backgroundColor}
|
|
724
|
+
/>
|
|
725
|
+
) : (
|
|
726
|
+
<>
|
|
727
|
+
<TouchableOpacity
|
|
728
|
+
style={[styles.actionButton, styles.uploadButton]}
|
|
729
|
+
onPress={handleConfirmUpload}
|
|
730
|
+
disabled={isUploading}
|
|
731
|
+
>
|
|
732
|
+
<Text style={styles.uploadButtonText}>
|
|
733
|
+
Upload a file
|
|
734
|
+
</Text>
|
|
735
|
+
</TouchableOpacity>
|
|
736
|
+
<TouchableOpacity
|
|
737
|
+
style={[styles.actionButton, styles.cancelButton]}
|
|
738
|
+
onPress={handleCancelUpload}
|
|
739
|
+
disabled={isUploading}
|
|
740
|
+
>
|
|
741
|
+
<Text style={styles.cancelButtonText}>Cancel</Text>
|
|
742
|
+
</TouchableOpacity>
|
|
743
|
+
</>
|
|
744
|
+
)}
|
|
575
745
|
</View>
|
|
576
746
|
</View>
|
|
577
747
|
) : (
|
|
@@ -601,12 +771,25 @@ export const ChatScreen = ({
|
|
|
601
771
|
/>
|
|
602
772
|
<TouchableOpacity
|
|
603
773
|
onPress={handleSendText}
|
|
604
|
-
style={
|
|
774
|
+
style={[
|
|
775
|
+
styles.sendButton,
|
|
776
|
+
isSending && styles.disabledButton,
|
|
777
|
+
]}
|
|
778
|
+
disabled={isSending}
|
|
605
779
|
>
|
|
606
|
-
|
|
607
|
-
size=
|
|
608
|
-
|
|
609
|
-
|
|
780
|
+
{isSending ? (
|
|
781
|
+
<ActivityIndicator size="small" color="#FFFFFF" />
|
|
782
|
+
) : (
|
|
783
|
+
<SendIcon
|
|
784
|
+
size={40}
|
|
785
|
+
backgroundColor={
|
|
786
|
+
settings?.styles?.styleHeadColor || '#3761DF'
|
|
787
|
+
}
|
|
788
|
+
iconColor={
|
|
789
|
+
settings?.styles?.styleTextColor || '#FFFFFF'
|
|
790
|
+
}
|
|
791
|
+
/>
|
|
792
|
+
)}
|
|
610
793
|
</TouchableOpacity>
|
|
611
794
|
</View>
|
|
612
795
|
)}
|
|
@@ -658,32 +841,36 @@ const getDynamicStyles = (settingStyle: any = {}) => {
|
|
|
658
841
|
color: settingStyle?.styleTextColor || '#FFFFFF',
|
|
659
842
|
fontWeight: 'bold',
|
|
660
843
|
},
|
|
661
|
-
messageList: { flex: 1, paddingHorizontal:
|
|
844
|
+
messageList: { flex: 1, paddingHorizontal: 8 },
|
|
662
845
|
messageRow: {
|
|
663
846
|
flexDirection: 'row',
|
|
664
|
-
alignItems: 'flex-
|
|
847
|
+
alignItems: 'flex-start',
|
|
665
848
|
marginVertical: 5,
|
|
666
849
|
},
|
|
667
850
|
agentRow: { justifyContent: 'flex-start' },
|
|
668
851
|
userRow: { justifyContent: 'flex-end' },
|
|
669
852
|
messageContainer: {
|
|
670
|
-
maxWidth:
|
|
671
|
-
|
|
853
|
+
maxWidth: screenWidth * 0.6,
|
|
854
|
+
flexWrap: 'wrap',
|
|
672
855
|
flexDirection: 'row',
|
|
673
|
-
|
|
856
|
+
alignItems: 'flex-end',
|
|
674
857
|
},
|
|
675
858
|
messageAgentContainer: {
|
|
676
|
-
maxWidth:
|
|
677
|
-
alignItems: 'flex-
|
|
678
|
-
flexDirection: 'row
|
|
679
|
-
|
|
859
|
+
maxWidth: screenWidth * 0.6,
|
|
860
|
+
alignItems: 'flex-end',
|
|
861
|
+
flexDirection: 'row',
|
|
862
|
+
},
|
|
863
|
+
avatar: {
|
|
864
|
+
width: 36,
|
|
865
|
+
height: 36,
|
|
866
|
+
borderRadius: 20,
|
|
867
|
+
marginRight: 6,
|
|
868
|
+
alignSelf: 'flex-end',
|
|
680
869
|
},
|
|
681
|
-
avatar: { width: 36, height: 36, borderRadius: 20, marginRight: 8 },
|
|
682
870
|
messageBubble: {
|
|
683
871
|
paddingHorizontal: 8,
|
|
684
872
|
paddingVertical: 8,
|
|
685
873
|
borderRadius: 12,
|
|
686
|
-
// width: '80%',
|
|
687
874
|
},
|
|
688
875
|
agentBubble: {
|
|
689
876
|
backgroundColor: settingStyle.styleAgentColor || '#E0E0E0',
|
|
@@ -698,16 +885,14 @@ const getDynamicStyles = (settingStyle: any = {}) => {
|
|
|
698
885
|
color: settingStyle.styleAgentTextColor || '#000000',
|
|
699
886
|
},
|
|
700
887
|
fileLink: {
|
|
888
|
+
maxWidth: screenWidth * 0.6,
|
|
701
889
|
textDecorationLine: 'underline',
|
|
702
|
-
elevation: 4,
|
|
703
|
-
width: screenWidth * 0.6,
|
|
704
890
|
},
|
|
705
891
|
timestampText: {
|
|
706
892
|
fontSize: 12,
|
|
707
893
|
color: '#888',
|
|
708
|
-
|
|
894
|
+
marginHorizontal: 4,
|
|
709
895
|
alignSelf: 'flex-end',
|
|
710
|
-
marginHorizontal: 8,
|
|
711
896
|
},
|
|
712
897
|
datePartition: {
|
|
713
898
|
alignSelf: 'center',
|
|
@@ -735,12 +920,14 @@ const getDynamicStyles = (settingStyle: any = {}) => {
|
|
|
735
920
|
},
|
|
736
921
|
input: {
|
|
737
922
|
flex: 1,
|
|
738
|
-
height:
|
|
739
|
-
backgroundColor: '#
|
|
923
|
+
height: 43,
|
|
924
|
+
backgroundColor: '#FFFFFF',
|
|
740
925
|
borderRadius: 22.5,
|
|
741
926
|
paddingHorizontal: 15,
|
|
742
927
|
fontSize: 16,
|
|
743
928
|
marginHorizontal: 10,
|
|
929
|
+
borderColor: '#EAECEC',
|
|
930
|
+
borderWidth: 1,
|
|
744
931
|
},
|
|
745
932
|
attachmentButton: { padding: 5 },
|
|
746
933
|
sendButton: {
|
|
@@ -753,6 +940,13 @@ const getDynamicStyles = (settingStyle: any = {}) => {
|
|
|
753
940
|
justifyContent: 'center',
|
|
754
941
|
alignItems: 'center',
|
|
755
942
|
},
|
|
943
|
+
disabledButton: {
|
|
944
|
+
opacity: 0.5,
|
|
945
|
+
justifyContent: 'center',
|
|
946
|
+
alignItems: 'center',
|
|
947
|
+
paddingRight: 0,
|
|
948
|
+
paddingTop: 0,
|
|
949
|
+
},
|
|
756
950
|
uploadPreviewContainer: {
|
|
757
951
|
alignItems: 'center',
|
|
758
952
|
},
|
|
@@ -801,29 +995,29 @@ const getDynamicStyles = (settingStyle: any = {}) => {
|
|
|
801
995
|
fontSize: 16,
|
|
802
996
|
},
|
|
803
997
|
plusIconContainer: {
|
|
804
|
-
width:
|
|
805
|
-
height:
|
|
998
|
+
width: 10,
|
|
999
|
+
height: 18,
|
|
806
1000
|
justifyContent: 'center',
|
|
807
1001
|
alignItems: 'center',
|
|
808
1002
|
},
|
|
809
1003
|
plusIconBar: {
|
|
810
1004
|
position: 'absolute',
|
|
811
|
-
backgroundColor: '#
|
|
1005
|
+
backgroundColor: '#525E62',
|
|
812
1006
|
},
|
|
813
1007
|
plusIconBarHorizontal: {
|
|
814
|
-
width:
|
|
1008
|
+
width: 18,
|
|
815
1009
|
height: 2,
|
|
816
1010
|
borderRadius: 1,
|
|
817
1011
|
},
|
|
818
1012
|
plusIconBarVertical: {
|
|
819
1013
|
width: 2,
|
|
820
|
-
height:
|
|
1014
|
+
height: 18,
|
|
821
1015
|
borderRadius: 1,
|
|
822
1016
|
},
|
|
823
1017
|
imageMessage: {
|
|
824
1018
|
width: 200,
|
|
825
1019
|
height: 200,
|
|
826
|
-
borderRadius:
|
|
1020
|
+
borderRadius: 12,
|
|
827
1021
|
},
|
|
828
1022
|
formContainer: {
|
|
829
1023
|
// flex: 1,
|
|
@@ -910,5 +1104,22 @@ const getDynamicStyles = (settingStyle: any = {}) => {
|
|
|
910
1104
|
fontSize: 12,
|
|
911
1105
|
fontWeight: 'bold',
|
|
912
1106
|
},
|
|
1107
|
+
imageViewerCloseButton: {
|
|
1108
|
+
position: 'absolute',
|
|
1109
|
+
top: 20,
|
|
1110
|
+
right: 20,
|
|
1111
|
+
zIndex: 20,
|
|
1112
|
+
padding: 10,
|
|
1113
|
+
},
|
|
1114
|
+
imageViewerCloseButtonText: {
|
|
1115
|
+
color: 'white',
|
|
1116
|
+
fontSize: 20,
|
|
1117
|
+
zIndex: 20,
|
|
1118
|
+
fontWeight: 'bold',
|
|
1119
|
+
},
|
|
1120
|
+
linkTextStyle: {
|
|
1121
|
+
color: '#0066CC',
|
|
1122
|
+
textDecorationLine: 'underline',
|
|
1123
|
+
},
|
|
913
1124
|
});
|
|
914
1125
|
};
|