connect-x-reactnative-livechat-sdk 1.5.0-legacy → 1.6.0-legacy
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 +45 -6
- package/lib/module/ChatScreen.js +197 -78
- package/lib/module/ChatScreen.js.map +1 -1
- package/lib/module/api.js +36 -1
- package/lib/module/api.js.map +1 -1
- package/lib/typescript/src/ChatScreen.d.ts +2 -2
- package/lib/typescript/src/ChatScreen.d.ts.map +1 -1
- package/lib/typescript/src/api.d.ts +12 -0
- package/lib/typescript/src/api.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/ChatScreen.tsx +213 -76
- package/src/api.ts +42 -1
- package/src/index.tsx +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,8 @@ An SDK for easily integrating the Connect-X Live Chat into your React Native app
|
|
|
9
9
|
* Displays chat history with date partitions.
|
|
10
10
|
* Customizable styles and colors from from [Connect-X](https://app.connect-x.tech/) by navigating to: Organization Settings → Connector → Live Chat → Style.
|
|
11
11
|
* Supports custom header components.
|
|
12
|
-
* Image preview with zoom capabilities.
|
|
12
|
+
* Image preview with zoom capabilities and download (Save to Files on iOS, save-as on Android).
|
|
13
|
+
* Supports DNG, HEIC and JFIF images.
|
|
13
14
|
|
|
14
15
|
## 🔧 Installation
|
|
15
16
|
|
|
@@ -33,7 +34,7 @@ yarn connect-x-reactnative-livechat-sdk@1.4.7-legacy
|
|
|
33
34
|
You need to install the following peer dependencies manually:
|
|
34
35
|
|
|
35
36
|
```bash
|
|
36
|
-
npm install @react-native-documents/picker react-native-svg@13.4.0 react-native-image-zoom-viewer react-native-device-info @react-native-community/netinfo
|
|
37
|
+
npm install @react-native-documents/picker react-native-image-picker react-native-svg@13.4.0 react-native-image-zoom-viewer react-native-device-info react-native-blob-util @react-native-community/netinfo
|
|
37
38
|
```
|
|
38
39
|
|
|
39
40
|
If you have a problem with @react-native-documents/picker,
|
|
@@ -59,11 +60,30 @@ https://github.com/react-native-documents/document-picker/issues/777#issuecommen
|
|
|
59
60
|
This library requires some peer dependencies. You need to install these libraries in your project as well:
|
|
60
61
|
|
|
61
62
|
```bash
|
|
62
|
-
npm install socket.io-client @react-native-documents/picker react-native-svg react-native-image-zoom-viewer react-native-device-info @react-native-community/netinfo
|
|
63
|
+
npm install socket.io-client @react-native-documents/picker react-native-image-picker react-native-svg react-native-image-zoom-viewer react-native-device-info react-native-blob-util @react-native-community/netinfo
|
|
63
64
|
|
|
64
65
|
#or
|
|
65
66
|
|
|
66
|
-
yarn add socket.io-client @react-native-documents/picker react-native-svg react-native-image-zoom-viewer react-native-device-info @react-native-community/netinfo
|
|
67
|
+
yarn add socket.io-client @react-native-documents/picker react-native-image-picker react-native-svg react-native-image-zoom-viewer react-native-device-info react-native-blob-util @react-native-community/netinfo
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Camera & Photo Library Permissions
|
|
71
|
+
|
|
72
|
+
The chat input has **File**, **Camera** and **Gallery** buttons (via `react-native-image-picker`). Add these to your app:
|
|
73
|
+
|
|
74
|
+
**iOS** (`Info.plist`):
|
|
75
|
+
|
|
76
|
+
```xml
|
|
77
|
+
<key>NSCameraUsageDescription</key>
|
|
78
|
+
<string>Take a photo to send in live chat</string>
|
|
79
|
+
<key>NSPhotoLibraryUsageDescription</key>
|
|
80
|
+
<string>Choose a photo to send in live chat</string>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Android** (`AndroidManifest.xml`):
|
|
84
|
+
|
|
85
|
+
```xml
|
|
86
|
+
<uses-permission android:name="android.permission.CAMERA" />
|
|
67
87
|
```
|
|
68
88
|
|
|
69
89
|
## 🚀 Usage
|
|
@@ -89,8 +109,15 @@ const AppContent = () => {
|
|
|
89
109
|
token: 'YOUR_AUTH_TOKEN',
|
|
90
110
|
orgId: 'YOUR_ORGANIZATION_ID',
|
|
91
111
|
channelId: 'YOUR_CHANNEL_ID',
|
|
92
|
-
|
|
93
|
-
|
|
112
|
+
customer: {
|
|
113
|
+
cx_Name: 'CUSTOMER_NAME',
|
|
114
|
+
cx_firstName: 'CUSTOMER_FIRST_NAME',
|
|
115
|
+
cx_lastName: 'CUSTOMER_LAST_NAME',
|
|
116
|
+
// You can add any customer field here
|
|
117
|
+
// Note: The customer object must contain at least one field configured as an Upsert Key in Connect-X.
|
|
118
|
+
},
|
|
119
|
+
customHeader: <YourCustomHeaderComponent />, // Optional
|
|
120
|
+
});
|
|
94
121
|
}}
|
|
95
122
|
/>
|
|
96
123
|
</View>
|
|
@@ -167,6 +194,18 @@ A method to open the chat window. It accepts a `config` object with the followin
|
|
|
167
194
|
|
|
168
195
|
---
|
|
169
196
|
|
|
197
|
+
## 📝 Changelog
|
|
198
|
+
|
|
199
|
+
### 1.6.0-legacy
|
|
200
|
+
|
|
201
|
+
* **Attach buttons:** the chat input has separate **File** (+ icon), **Camera** and **Gallery** buttons.
|
|
202
|
+
* **DNG / HEIC / JFIF:** these images can be sent, viewed and downloaded. Images the device can't decode fall back to a download link.
|
|
203
|
+
* **Correct file types:** DNG and HEIC picked from the iOS gallery keep their real extension instead of becoming `.jpg`.
|
|
204
|
+
* **Image preview:** the close button sits below the notch, and a new download button saves the image through the system Save to Files / save-as sheet.
|
|
205
|
+
* **New dependency:** `react-native-blob-util`. Install it and run `pod install` after upgrading.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
170
209
|
## 📄 License
|
|
171
210
|
|
|
172
211
|
[](https://www.apache.org/licenses/LICENSE-2.0)
|
package/lib/module/ChatScreen.js
CHANGED
|
@@ -2,16 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
// src/ChatScreen.tsx
|
|
4
4
|
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
|
5
|
-
import { View, Text, TextInput, TouchableOpacity, SectionList, StyleSheet, SafeAreaView, KeyboardAvoidingView, Platform, Image, ActivityIndicator, Alert, Linking, Dimensions, Modal } from 'react-native';
|
|
5
|
+
import { View, Text, TextInput, TouchableOpacity, SectionList, StyleSheet, SafeAreaView, KeyboardAvoidingView, Platform, StatusBar, Image, ActivityIndicator, Alert, Linking, Dimensions, Modal } from 'react-native';
|
|
6
6
|
import io from 'socket.io-client';
|
|
7
|
-
import { pick, types } from '@react-native-documents/picker';
|
|
7
|
+
import { errorCodes, pick, saveDocuments, types } from '@react-native-documents/picker';
|
|
8
|
+
import ReactNativeBlobUtil from 'react-native-blob-util';
|
|
9
|
+
import { launchCamera, launchImageLibrary } from 'react-native-image-picker';
|
|
8
10
|
import { ConnectXMobileSdk } from 'connect-x-react-native-sdk';
|
|
9
|
-
import { apiGet, uploadFile, BASE_URL_LIVE_CHAT, apiPost } from "./api.js";
|
|
11
|
+
import { apiGet, uploadFile, BASE_URL_LIVE_CHAT, apiPost, buildConnectorBody, fileMimeType, sniffImageExt } from "./api.js";
|
|
10
12
|
import Svg, { ClipPath, Defs, G, Path, Rect } from 'react-native-svg';
|
|
11
13
|
import hexToRgba from 'hex-to-rgba';
|
|
12
14
|
import ImageViewer from 'react-native-image-zoom-viewer';
|
|
13
15
|
import { mergeIncomingMessage, mergeHistory, messageSig } from "./mergeMessages.js";
|
|
14
16
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
17
|
+
// ponytail: ไอคอน SVG ในไฟล์ แทน vector-icons เพราะไม่ต้องให้แอปที่ใช้ SDK link font เพิ่ม
|
|
18
|
+
const ATTACH_ICONS = {
|
|
19
|
+
file: 'M12 5v14M5 12h14',
|
|
20
|
+
camera: 'M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2zM12 17a4 4 0 1 0 0-8 4 4 0 0 0 0 8z',
|
|
21
|
+
gallery: 'M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zM8.5 10a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zM21 15l-5-5L5 21',
|
|
22
|
+
download: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// ระยะจากขอบบนของ preview ให้พ้น status bar / notch
|
|
26
|
+
// ponytail: ค่าคงที่ ไม่ใช้ safe-area lib; iOS 60 พอสำหรับ Dynamic Island
|
|
27
|
+
const VIEWER_TOP = Platform.OS === 'ios' ? 60 : (StatusBar.currentHeight ?? 0) + 16;
|
|
28
|
+
const AttachIcon = ({
|
|
29
|
+
name,
|
|
30
|
+
color = '#525E62'
|
|
31
|
+
}) => /*#__PURE__*/_jsx(Svg, {
|
|
32
|
+
width: 22,
|
|
33
|
+
height: 22,
|
|
34
|
+
viewBox: "0 0 24 24",
|
|
35
|
+
fill: "none",
|
|
36
|
+
children: /*#__PURE__*/_jsx(Path, {
|
|
37
|
+
d: ATTACH_ICONS[name],
|
|
38
|
+
stroke: color,
|
|
39
|
+
strokeWidth: 2,
|
|
40
|
+
strokeLinecap: "round",
|
|
41
|
+
strokeLinejoin: "round"
|
|
42
|
+
})
|
|
43
|
+
});
|
|
15
44
|
const SendIcon = ({
|
|
16
45
|
size,
|
|
17
46
|
backgroundColor,
|
|
@@ -52,6 +81,7 @@ export const ChatScreen = ({
|
|
|
52
81
|
token,
|
|
53
82
|
orgId,
|
|
54
83
|
channelId,
|
|
84
|
+
externalValue,
|
|
55
85
|
customHeader,
|
|
56
86
|
customer
|
|
57
87
|
}) => {
|
|
@@ -63,6 +93,8 @@ export const ChatScreen = ({
|
|
|
63
93
|
const socketRef = useRef(null);
|
|
64
94
|
const hasConnectedRef = useRef(false);
|
|
65
95
|
const [userId, setUserId] = useState(null);
|
|
96
|
+
// ponytail: url ที่ <Image> decode ไม่ผ่าน (dng/heic บน Android ฯลฯ) → ตกไปเป็นลิงก์ดาวน์โหลด
|
|
97
|
+
const [undecodableUrls, setUndecodableUrls] = useState({});
|
|
66
98
|
const [selectedFile, setSelectedFile] = useState(null);
|
|
67
99
|
const [settings, setSettings] = useState(null);
|
|
68
100
|
const [styles, setStyles] = useState({});
|
|
@@ -72,14 +104,15 @@ export const ChatScreen = ({
|
|
|
72
104
|
const [viewerImages, setViewerImages] = useState([]);
|
|
73
105
|
const [viewerIndex, setViewerIndex] = useState(0);
|
|
74
106
|
useEffect(() => {
|
|
107
|
+
// ponytail: ยังรับ unknownId ไว้เฉย ๆ ไม่ได้ส่งเข้า body แล้ว แต่ getUnknownId() ฝั่ง native ยังต้องเรียก
|
|
75
108
|
const findCustomer = async _unknownId => {
|
|
76
109
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}, token);
|
|
110
|
+
const body = buildConnectorBody({
|
|
111
|
+
externalValue,
|
|
112
|
+
customer
|
|
113
|
+
});
|
|
114
|
+
console.log('setLiveChatConnector body:', body);
|
|
115
|
+
const res = await apiPost(`liveChat/${orgId}/${channelId}/setLiveChatConnector`, body, token);
|
|
83
116
|
console.log('Found customer:', res);
|
|
84
117
|
if (res) {
|
|
85
118
|
setUserId(res?.userId);
|
|
@@ -337,10 +370,19 @@ export const ChatScreen = ({
|
|
|
337
370
|
const handleSelectFile = async () => {
|
|
338
371
|
try {
|
|
339
372
|
const result = await pick({
|
|
340
|
-
|
|
373
|
+
// ponytail: allFiles เพราะ iOS ไม่รู้จักนามสกุลแปลก ๆ (.jfif) เลย grey out
|
|
374
|
+
// ถ้าปล่อยเป็น public.image; ชนิดไฟล์คัดตอนส่งด้วย mime อยู่แล้ว
|
|
375
|
+
type: [types.allFiles],
|
|
341
376
|
allowMultiSelection: false
|
|
342
377
|
});
|
|
343
|
-
|
|
378
|
+
const file = result[0];
|
|
379
|
+
if (file) {
|
|
380
|
+
setSelectedFile({
|
|
381
|
+
uri: file.uri,
|
|
382
|
+
name: file.name || 'file',
|
|
383
|
+
type: file.type
|
|
384
|
+
});
|
|
385
|
+
}
|
|
344
386
|
} catch (err) {
|
|
345
387
|
if (err?.code !== 'DOCUMENT_PICKER_CANCELED') {
|
|
346
388
|
// Alert.alert(
|
|
@@ -350,6 +392,51 @@ export const ChatScreen = ({
|
|
|
350
392
|
}
|
|
351
393
|
}
|
|
352
394
|
};
|
|
395
|
+
|
|
396
|
+
// อ่านแค่ 12 byte แรกของไฟล์ ไว้เช็คชนิดจริง
|
|
397
|
+
const readFileHead = async uri => {
|
|
398
|
+
try {
|
|
399
|
+
const head = `${ReactNativeBlobUtil.fs.dirs.CacheDir}/head_${Date.now()}`;
|
|
400
|
+
await ReactNativeBlobUtil.fs.slice(uri.replace('file://', ''), head, 0, 12);
|
|
401
|
+
const bytes = await ReactNativeBlobUtil.fs.readFile(head, 'ascii');
|
|
402
|
+
ReactNativeBlobUtil.fs.unlink(head).catch(() => {});
|
|
403
|
+
return bytes;
|
|
404
|
+
} catch {
|
|
405
|
+
return [];
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
const handleImagePicked = async res => {
|
|
409
|
+
if (res.didCancel) return;
|
|
410
|
+
const asset = res.assets?.[0];
|
|
411
|
+
if (res.errorCode || !asset?.uri) {
|
|
412
|
+
if (res.errorCode === 'permission') {
|
|
413
|
+
Alert.alert('Permission required', 'Please allow access in Settings.');
|
|
414
|
+
}
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
let name = asset.fileName || `image_${Date.now()}.jpg`;
|
|
418
|
+
let type = asset.type || 'image/jpeg';
|
|
419
|
+
const realExt = sniffImageExt(await readFileHead(asset.uri));
|
|
420
|
+
if (realExt) {
|
|
421
|
+
name = `${name.replace(/\.[^.]+$/, '')}.${realExt}`;
|
|
422
|
+
type = fileMimeType({
|
|
423
|
+
name
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
setSelectedFile({
|
|
427
|
+
uri: asset.uri,
|
|
428
|
+
name,
|
|
429
|
+
type
|
|
430
|
+
});
|
|
431
|
+
};
|
|
432
|
+
const handleOpenCamera = () => launchCamera({
|
|
433
|
+
mediaType: 'photo',
|
|
434
|
+
saveToPhotos: false
|
|
435
|
+
}).then(handleImagePicked);
|
|
436
|
+
const handleOpenGallery = () => launchImageLibrary({
|
|
437
|
+
mediaType: 'photo',
|
|
438
|
+
selectionLimit: 1
|
|
439
|
+
}).then(handleImagePicked);
|
|
353
440
|
const MessageWithLinks = ({
|
|
354
441
|
text,
|
|
355
442
|
textStyle,
|
|
@@ -373,8 +460,9 @@ export const ChatScreen = ({
|
|
|
373
460
|
})
|
|
374
461
|
});
|
|
375
462
|
};
|
|
463
|
+
const isDisplayableImage = item => item.type === 'image' && !!item.url && !undecodableUrls[item.url];
|
|
376
464
|
const openImageViewer = tappedImageUrl => {
|
|
377
|
-
const allImages = messages.filter(
|
|
465
|
+
const allImages = messages.filter(isDisplayableImage).map(msg => ({
|
|
378
466
|
url: msg.url
|
|
379
467
|
}));
|
|
380
468
|
const tappedIndex = allImages.findIndex(img => img.url === tappedImageUrl);
|
|
@@ -384,6 +472,30 @@ export const ChatScreen = ({
|
|
|
384
472
|
setImageViewerVisible(true);
|
|
385
473
|
}
|
|
386
474
|
};
|
|
475
|
+
|
|
476
|
+
// โหลดรูปลง cache ก่อน แล้วเปิดหน้า "Save to Files" (iOS) / "บันทึกเป็น" (Android)
|
|
477
|
+
// เพราะ saveDocuments รับแค่ไฟล์ในเครื่อง
|
|
478
|
+
const handleDownloadImage = async url => {
|
|
479
|
+
try {
|
|
480
|
+
// ใช้ชื่อไฟล์ตอนส่ง (เช่น .dng / .jfif) ก่อน ชื่อใน url อาจถูก storage เปลี่ยนนามสกุล
|
|
481
|
+
const name = messages.find(m => m.url === url)?.fileName || decodeURIComponent(url.split('?')[0].split('/').pop() || 'image');
|
|
482
|
+
const res = await ReactNativeBlobUtil.config({
|
|
483
|
+
path: `${ReactNativeBlobUtil.fs.dirs.CacheDir}/${name}`
|
|
484
|
+
}).fetch('GET', url);
|
|
485
|
+
await saveDocuments({
|
|
486
|
+
sourceUris: [`file://${res.path()}`],
|
|
487
|
+
fileName: name,
|
|
488
|
+
mimeType: fileMimeType({
|
|
489
|
+
name
|
|
490
|
+
})
|
|
491
|
+
});
|
|
492
|
+
} catch (err) {
|
|
493
|
+
if (err?.code !== errorCodes.OPERATION_CANCELED) {
|
|
494
|
+
Alert.alert('Download Error', 'Failed to save the image.');
|
|
495
|
+
console.error('Download failed', err);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
};
|
|
387
499
|
const handleCancelUpload = () => {
|
|
388
500
|
if (isUploading) return;
|
|
389
501
|
setSelectedFile(null);
|
|
@@ -393,7 +505,7 @@ export const ChatScreen = ({
|
|
|
393
505
|
setIsUploading(true);
|
|
394
506
|
try {
|
|
395
507
|
const fileUrl = await uploadFile(selectedFile, orgId, channelId);
|
|
396
|
-
const type = selectedFile.
|
|
508
|
+
const type = fileMimeType(selectedFile).startsWith('image/') ? 'image' : 'file';
|
|
397
509
|
const messageBody = {
|
|
398
510
|
type,
|
|
399
511
|
fileName: selectedFile.name || 'file',
|
|
@@ -461,16 +573,20 @@ export const ChatScreen = ({
|
|
|
461
573
|
text: item.text || '',
|
|
462
574
|
textStyle: item.sender === 'user' ? styles.customerChatText : styles.agentChatText,
|
|
463
575
|
linkStyle: styles.linkTextStyle
|
|
464
|
-
}), item
|
|
576
|
+
}), isDisplayableImage(item) && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
465
577
|
onPress: () => openImageViewer(item.url),
|
|
466
578
|
children: /*#__PURE__*/_jsx(Image, {
|
|
467
579
|
source: {
|
|
468
580
|
uri: item.url
|
|
469
581
|
},
|
|
470
582
|
style: styles.imageMessage,
|
|
471
|
-
resizeMode: "cover"
|
|
583
|
+
resizeMode: "cover",
|
|
584
|
+
onError: () => setUndecodableUrls(prev => ({
|
|
585
|
+
...prev,
|
|
586
|
+
[item.url]: true
|
|
587
|
+
}))
|
|
472
588
|
})
|
|
473
|
-
}), item.type === 'file' && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
589
|
+
}), (item.type === 'file' || item.type === 'image' && !isDisplayableImage(item)) && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
474
590
|
onPress: handleFilePress,
|
|
475
591
|
children: /*#__PURE__*/_jsx(Text, {
|
|
476
592
|
style: [styles.fileLink, item.sender === 'user' ? styles.customerChatText : styles.agentChatText],
|
|
@@ -510,7 +626,9 @@ export const ChatScreen = ({
|
|
|
510
626
|
renderIndicator: (currentIndex, allSize) => /*#__PURE__*/_jsx(View, {
|
|
511
627
|
style: {
|
|
512
628
|
position: 'absolute',
|
|
513
|
-
top:
|
|
629
|
+
top: VIEWER_TOP,
|
|
630
|
+
height: 42,
|
|
631
|
+
justifyContent: 'center',
|
|
514
632
|
width: '100%',
|
|
515
633
|
alignItems: 'center',
|
|
516
634
|
zIndex: 1
|
|
@@ -524,30 +642,41 @@ export const ChatScreen = ({
|
|
|
524
642
|
children: [currentIndex, " / ", allSize]
|
|
525
643
|
})
|
|
526
644
|
}),
|
|
527
|
-
renderHeader:
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
645
|
+
renderHeader: currentIndex => /*#__PURE__*/_jsxs(View, {
|
|
646
|
+
pointerEvents: "box-none",
|
|
647
|
+
style: [styles.imageViewerHeader, {
|
|
648
|
+
top: VIEWER_TOP
|
|
649
|
+
}],
|
|
650
|
+
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
651
|
+
style: styles.imageViewerCloseButton,
|
|
652
|
+
hitSlop: {
|
|
653
|
+
top: 20,
|
|
654
|
+
bottom: 20,
|
|
655
|
+
left: 20,
|
|
656
|
+
right: 20
|
|
657
|
+
},
|
|
658
|
+
onPress: () => {
|
|
659
|
+
const url = viewerImages[currentIndex ?? 0]?.url;
|
|
660
|
+
if (url) handleDownloadImage(url);
|
|
661
|
+
},
|
|
662
|
+
children: /*#__PURE__*/_jsx(AttachIcon, {
|
|
663
|
+
name: "download",
|
|
664
|
+
color: "white"
|
|
665
|
+
})
|
|
666
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
667
|
+
style: styles.imageViewerCloseButton,
|
|
668
|
+
hitSlop: {
|
|
669
|
+
top: 20,
|
|
670
|
+
bottom: 20,
|
|
671
|
+
left: 20,
|
|
672
|
+
right: 20
|
|
673
|
+
},
|
|
674
|
+
onPress: () => setImageViewerVisible(false),
|
|
675
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
676
|
+
style: styles.imageViewerCloseButtonText,
|
|
677
|
+
children: "\u2715"
|
|
678
|
+
})
|
|
679
|
+
})]
|
|
551
680
|
})
|
|
552
681
|
})
|
|
553
682
|
}), customHeader !== undefined ? customHeader : /*#__PURE__*/_jsxs(View, {
|
|
@@ -648,13 +777,23 @@ export const ChatScreen = ({
|
|
|
648
777
|
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
649
778
|
onPress: handleSelectFile,
|
|
650
779
|
style: styles.attachmentButton,
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
780
|
+
accessibilityLabel: "Attach file",
|
|
781
|
+
children: /*#__PURE__*/_jsx(AttachIcon, {
|
|
782
|
+
name: "file"
|
|
783
|
+
})
|
|
784
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
785
|
+
onPress: handleOpenCamera,
|
|
786
|
+
style: styles.attachmentButton,
|
|
787
|
+
accessibilityLabel: "Take photo",
|
|
788
|
+
children: /*#__PURE__*/_jsx(AttachIcon, {
|
|
789
|
+
name: "camera"
|
|
790
|
+
})
|
|
791
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
792
|
+
onPress: handleOpenGallery,
|
|
793
|
+
style: styles.attachmentButton,
|
|
794
|
+
accessibilityLabel: "Choose from gallery",
|
|
795
|
+
children: /*#__PURE__*/_jsx(AttachIcon, {
|
|
796
|
+
name: "gallery"
|
|
658
797
|
})
|
|
659
798
|
}), /*#__PURE__*/_jsx(TextInput, {
|
|
660
799
|
style: styles.input,
|
|
@@ -846,13 +985,8 @@ const getDynamicStyles = (settingStyle = {}) => {
|
|
|
846
985
|
attachmentButton: {
|
|
847
986
|
padding: 5
|
|
848
987
|
},
|
|
988
|
+
// ponytail: วงกลม+สีพื้นอยู่ใน SendIcon (SVG) แล้ว ตรงนี้เลยไม่ต้องมีอีกวง
|
|
849
989
|
sendButton: {
|
|
850
|
-
paddingRight: 3,
|
|
851
|
-
paddingTop: 2,
|
|
852
|
-
width: 42,
|
|
853
|
-
height: 42,
|
|
854
|
-
borderRadius: 24,
|
|
855
|
-
backgroundColor: settingStyle?.styleHeadColor || '#3761DF',
|
|
856
990
|
justifyContent: 'center',
|
|
857
991
|
alignItems: 'center'
|
|
858
992
|
},
|
|
@@ -910,26 +1044,6 @@ const getDynamicStyles = (settingStyle = {}) => {
|
|
|
910
1044
|
fontWeight: 'bold',
|
|
911
1045
|
fontSize: 16
|
|
912
1046
|
},
|
|
913
|
-
plusIconContainer: {
|
|
914
|
-
width: 10,
|
|
915
|
-
height: 18,
|
|
916
|
-
justifyContent: 'center',
|
|
917
|
-
alignItems: 'center'
|
|
918
|
-
},
|
|
919
|
-
plusIconBar: {
|
|
920
|
-
position: 'absolute',
|
|
921
|
-
backgroundColor: '#525E62'
|
|
922
|
-
},
|
|
923
|
-
plusIconBarHorizontal: {
|
|
924
|
-
width: 18,
|
|
925
|
-
height: 2,
|
|
926
|
-
borderRadius: 1
|
|
927
|
-
},
|
|
928
|
-
plusIconBarVertical: {
|
|
929
|
-
width: 2,
|
|
930
|
-
height: 18,
|
|
931
|
-
borderRadius: 1
|
|
932
|
-
},
|
|
933
1047
|
imageMessage: {
|
|
934
1048
|
width: 200,
|
|
935
1049
|
height: 200,
|
|
@@ -1017,11 +1131,16 @@ const getDynamicStyles = (settingStyle = {}) => {
|
|
|
1017
1131
|
fontSize: 12,
|
|
1018
1132
|
fontWeight: 'bold'
|
|
1019
1133
|
},
|
|
1020
|
-
|
|
1134
|
+
imageViewerHeader: {
|
|
1021
1135
|
position: 'absolute',
|
|
1022
|
-
|
|
1023
|
-
right:
|
|
1024
|
-
zIndex:
|
|
1136
|
+
left: 10,
|
|
1137
|
+
right: 10,
|
|
1138
|
+
zIndex: 9999,
|
|
1139
|
+
flexDirection: 'row',
|
|
1140
|
+
justifyContent: 'space-between',
|
|
1141
|
+
alignItems: 'center'
|
|
1142
|
+
},
|
|
1143
|
+
imageViewerCloseButton: {
|
|
1025
1144
|
padding: 10
|
|
1026
1145
|
},
|
|
1027
1146
|
imageViewerCloseButtonText: {
|