connect-x-reactnative-livechat-sdk 0.2.0 → 1.1.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.
@@ -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 = ({ color, size }: { color: string; size: number }) => (
44
- <Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
45
- <Path
46
- d="M22 2L15 22L11 13L2 9L22 2Z"
47
- stroke={color}
48
- strokeWidth="2"
49
- strokeLinecap="round"
50
- strokeLinejoin="round"
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 [styleSettings, setStyleSettings] = useState<any>(null);
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: "Siwakorn'", //'ปุ้นซู่ซ่า', //sdf0001111111111 //78e839c38397b8cac97437d17b1c91b568a5adb4
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
- // initializeUserId();
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 settings = await apiGet(
181
+ const resSettings = await apiGet(
126
182
  `liveChat/${orgId}/${channelId}/getSettingConnector`,
127
183
  token
128
184
  );
129
- setStyleSettings(settings);
130
- setStyles(getDynamicStyles(settings.styles));
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'); // YYYY-MM-DD format for reliable sorting
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
- }, 400);
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 sendMessage = (messageBody: Omit<Message, 'sender' | 'timestamp'>) => {
338
- if (!socketRef.current) return;
408
+ const handleSendText = () => {
409
+ if (inputText.trim() === '' || isSending) return;
410
+ setIsSending(true);
411
+
339
412
  const fullMessage: Message = {
340
- ...messageBody,
413
+ type: 'text',
414
+ text: inputText.trim(),
341
415
  sender: 'user',
342
416
  timestamp: Date.now(),
343
- type: messageBody.type || 'text',
344
417
  };
345
- socketRef.current.emit(
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
- if (fullMessage.type === 'text') {
355
- setInputText('');
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
- 'File-Picker Error',
378
- 'An error occurred during file selection.'
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
- sendMessage({
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' && styleSettings?.styles?.styleLogoUrl && (
569
+ {item.sender === 'agent' && settings?.styles?.styleLogoUrl && (
440
570
  <Image
441
- source={{ uri: styleSettings.styles.styleLogoUrl }}
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
- <Text
463
- style={
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
- {item.text}
470
- </Text>
601
+ linkStyle={styles.linkTextStyle}
602
+ />
471
603
  )}
472
604
  {item.type === 'image' && (
473
- <Image
474
- source={{ uri: item.url }}
475
- style={styles.imageMessage}
476
- resizeMode="cover"
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
- <View style={styles.header}>
515
- <View style={styles.headerProfile}>
516
- {styleSettings?.styles?.styleLogoUrl && (
517
- <Image
518
- source={{ uri: styleSettings.styles.styleLogoUrl }}
519
- style={styles.headerAvatar}
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
- <Text style={styles.headerTitle}>
523
- {styleSettings?.styles.name || 'Live Chat'}
524
- </Text>
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
- <TouchableOpacity onPress={onClose}>
527
- <Text style={styles.closeButtonText}>✕</Text>
528
- </TouchableOpacity>
529
- </View>
530
- <KeyboardAvoidingView style={styles.container}>
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={styleSettings?.styles?.styleHeadColor}
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
- <TouchableOpacity
564
- style={[styles.actionButton, styles.uploadButton]}
565
- onPress={handleConfirmUpload}
566
- >
567
- <Text style={styles.uploadButtonText}>Upload a file</Text>
568
- </TouchableOpacity>
569
- <TouchableOpacity
570
- style={[styles.actionButton, styles.cancelButton]}
571
- onPress={handleCancelUpload}
572
- >
573
- <Text style={styles.cancelButtonText}>Cancel</Text>
574
- </TouchableOpacity>
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={styles.sendButton}
774
+ style={[
775
+ styles.sendButton,
776
+ isSending && styles.disabledButton,
777
+ ]}
778
+ disabled={isSending}
605
779
  >
606
- <SendIcon
607
- size={22}
608
- color={styleSettings?.styles?.styleTextColor || '#FFFFFF'}
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: 10 },
844
+ messageList: { flex: 1, paddingHorizontal: 8 },
662
845
  messageRow: {
663
846
  flexDirection: 'row',
664
- alignItems: 'flex-end',
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: '80%',
671
- alignItems: 'flex-start',
853
+ maxWidth: screenWidth * 0.6,
854
+ flexWrap: 'wrap',
672
855
  flexDirection: 'row',
673
- // marginBottom: 10,
856
+ alignItems: 'flex-end',
674
857
  },
675
858
  messageAgentContainer: {
676
- maxWidth: '80%',
677
- alignItems: 'flex-start',
678
- flexDirection: 'row-reverse',
679
- marginVertical: 0,
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
- marginTop: 5,
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: 42,
739
- backgroundColor: '#F0F0F0',
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: 24,
805
- height: 24,
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: '#8A8A8A',
1005
+ backgroundColor: '#525E62',
812
1006
  },
813
1007
  plusIconBarHorizontal: {
814
- width: 20,
1008
+ width: 18,
815
1009
  height: 2,
816
1010
  borderRadius: 1,
817
1011
  },
818
1012
  plusIconBarVertical: {
819
1013
  width: 2,
820
- height: 20,
1014
+ height: 18,
821
1015
  borderRadius: 1,
822
1016
  },
823
1017
  imageMessage: {
824
1018
  width: 200,
825
1019
  height: 200,
826
- borderRadius: 10,
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
  };