@regantis-sdk/react-native-chat 0.1.1 โ 0.1.3
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 +34 -2
- package/android/build.gradle +1 -0
- package/android/src/main/AndroidManifest.xml +13 -1
- package/android/src/main/java/technology/regantis/chat/RegantisChatNativeModule.java +109 -56
- package/android/src/main/res/xml/regantis_chat_file_paths.xml +4 -0
- package/index.d.ts +5 -1
- package/ios/RegantisChatNative.h +1 -1
- package/ios/RegantisChatNative.m +81 -31
- package/package.json +1 -1
- package/src/RegantisChat.js +682 -205
- package/src/client.js +3 -3
- package/src/defaults.js +1 -1
- package/src/native.js +5 -5
package/src/RegantisChat.js
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
const React = require('react');
|
|
4
4
|
const {
|
|
5
5
|
ActivityIndicator,
|
|
6
|
+
Animated,
|
|
6
7
|
AppState,
|
|
7
8
|
FlatList,
|
|
8
9
|
Image,
|
|
9
10
|
KeyboardAvoidingView,
|
|
10
11
|
Linking,
|
|
11
12
|
Modal,
|
|
13
|
+
PanResponder,
|
|
12
14
|
Platform,
|
|
13
15
|
Pressable,
|
|
14
16
|
ScrollView,
|
|
@@ -19,7 +21,171 @@ const {
|
|
|
19
21
|
} = require('react-native');
|
|
20
22
|
const { RegantisChatClient } = require('./client');
|
|
21
23
|
|
|
22
|
-
const EMOJIS = ['๐', '๐', '๐', '๐', '๐', '๐ค', '๐', '๐ข', '๐', 'โค๏ธ', '๐', '๐', '๐', '๐'];
|
|
24
|
+
const EMOJIS = ['๐', '๐', '๐', '๐', '๐', '๐', '๐ค', '๐', '๐ข', '๐ญ', '๐', 'โค๏ธ', '๐', '๐', '๐', '๐'];
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
const LOCAL_TEXTS = {
|
|
28
|
+
en: { attach_camera: 'Take photo', lightbox_close: 'Close', lightbox_reset: 'Reset zoom', back: 'Back' },
|
|
29
|
+
ro: { attach_camera: 'Fฤ o fotografie', lightbox_close: 'รnchide', lightbox_reset: 'Reseteazฤ zoom', back: 'รnapoi' },
|
|
30
|
+
de: { attach_camera: 'Foto aufnehmen', lightbox_close: 'Schlieรen', lightbox_reset: 'Zoom zurรผcksetzen', back: 'Zurรผck' },
|
|
31
|
+
fr: { attach_camera: 'Prendre une photo', lightbox_close: 'Fermer', lightbox_reset: 'Rรฉinitialiser le zoom', back: 'Retour' },
|
|
32
|
+
es: { attach_camera: 'Tomar una foto', lightbox_close: 'Cerrar', lightbox_reset: 'Restablecer zoom', back: 'Atrรกs' },
|
|
33
|
+
it: { attach_camera: 'Scatta una foto', lightbox_close: 'Chiudi', lightbox_reset: 'Reimposta zoom', back: 'Indietro' },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function localText(language, key) {
|
|
37
|
+
const code = String(language || 'en').trim().toLowerCase().split(/[-_]/)[0];
|
|
38
|
+
return (LOCAL_TEXTS[code] && LOCAL_TEXTS[code][key]) || LOCAL_TEXTS.en[key] || '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function touchDistance(touches) {
|
|
42
|
+
if (!touches || touches.length < 2) return 0;
|
|
43
|
+
const dx = Number(touches[0].pageX || 0) - Number(touches[1].pageX || 0);
|
|
44
|
+
const dy = Number(touches[0].pageY || 0) - Number(touches[1].pageY || 0);
|
|
45
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function ImageLightbox({ visible, uri, onClose, closeLabel, resetLabel }) {
|
|
49
|
+
const scale = React.useRef(new Animated.Value(1)).current;
|
|
50
|
+
const translateX = React.useRef(new Animated.Value(0)).current;
|
|
51
|
+
const translateY = React.useRef(new Animated.Value(0)).current;
|
|
52
|
+
const current = React.useRef({ scale: 1, x: 0, y: 0 });
|
|
53
|
+
const gesture = React.useRef({ mode: '', distance: 0, scale: 1, x: 0, y: 0, startX: 0, startY: 0 });
|
|
54
|
+
|
|
55
|
+
const resetZoom = React.useCallback((animated = true) => {
|
|
56
|
+
current.current = { scale: 1, x: 0, y: 0 };
|
|
57
|
+
gesture.current.mode = '';
|
|
58
|
+
const values = [
|
|
59
|
+
Animated.spring(scale, { toValue: 1, useNativeDriver: true, friction: 8, tension: 70 }),
|
|
60
|
+
Animated.spring(translateX, { toValue: 0, useNativeDriver: true, friction: 8, tension: 70 }),
|
|
61
|
+
Animated.spring(translateY, { toValue: 0, useNativeDriver: true, friction: 8, tension: 70 }),
|
|
62
|
+
];
|
|
63
|
+
if (animated) Animated.parallel(values).start();
|
|
64
|
+
else {
|
|
65
|
+
scale.setValue(1);
|
|
66
|
+
translateX.setValue(0);
|
|
67
|
+
translateY.setValue(0);
|
|
68
|
+
}
|
|
69
|
+
}, [scale, translateX, translateY]);
|
|
70
|
+
|
|
71
|
+
React.useEffect(() => {
|
|
72
|
+
if (visible) resetZoom(false);
|
|
73
|
+
}, [visible, uri, resetZoom]);
|
|
74
|
+
|
|
75
|
+
const panResponder = React.useMemo(() => PanResponder.create({
|
|
76
|
+
onStartShouldSetPanResponder: () => true,
|
|
77
|
+
onMoveShouldSetPanResponder: () => true,
|
|
78
|
+
onPanResponderGrant: event => {
|
|
79
|
+
const touches = event.nativeEvent.touches || [];
|
|
80
|
+
if (touches.length >= 2) {
|
|
81
|
+
gesture.current = {
|
|
82
|
+
mode: 'pinch',
|
|
83
|
+
distance: Math.max(1, touchDistance(touches)),
|
|
84
|
+
scale: current.current.scale,
|
|
85
|
+
x: current.current.x,
|
|
86
|
+
y: current.current.y,
|
|
87
|
+
startX: 0,
|
|
88
|
+
startY: 0,
|
|
89
|
+
};
|
|
90
|
+
} else if (touches.length === 1) {
|
|
91
|
+
gesture.current = {
|
|
92
|
+
mode: 'pan',
|
|
93
|
+
distance: 0,
|
|
94
|
+
scale: current.current.scale,
|
|
95
|
+
x: current.current.x,
|
|
96
|
+
y: current.current.y,
|
|
97
|
+
startX: Number(touches[0].pageX || 0),
|
|
98
|
+
startY: Number(touches[0].pageY || 0),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
onPanResponderMove: event => {
|
|
103
|
+
const touches = event.nativeEvent.touches || [];
|
|
104
|
+
if (touches.length >= 2) {
|
|
105
|
+
const distance = Math.max(1, touchDistance(touches));
|
|
106
|
+
if (gesture.current.mode !== 'pinch') {
|
|
107
|
+
gesture.current.mode = 'pinch';
|
|
108
|
+
gesture.current.distance = distance;
|
|
109
|
+
gesture.current.scale = current.current.scale;
|
|
110
|
+
}
|
|
111
|
+
const nextScale = Math.max(1, Math.min(5, gesture.current.scale * (distance / Math.max(1, gesture.current.distance))));
|
|
112
|
+
current.current.scale = nextScale;
|
|
113
|
+
scale.setValue(nextScale);
|
|
114
|
+
if (nextScale <= 1.01) {
|
|
115
|
+
current.current.x = 0;
|
|
116
|
+
current.current.y = 0;
|
|
117
|
+
translateX.setValue(0);
|
|
118
|
+
translateY.setValue(0);
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (touches.length === 1 && current.current.scale > 1.01) {
|
|
124
|
+
const x = Number(touches[0].pageX || 0);
|
|
125
|
+
const y = Number(touches[0].pageY || 0);
|
|
126
|
+
if (gesture.current.mode !== 'pan') {
|
|
127
|
+
gesture.current.mode = 'pan';
|
|
128
|
+
gesture.current.x = current.current.x;
|
|
129
|
+
gesture.current.y = current.current.y;
|
|
130
|
+
gesture.current.startX = x;
|
|
131
|
+
gesture.current.startY = y;
|
|
132
|
+
}
|
|
133
|
+
const nextX = gesture.current.x + (x - gesture.current.startX);
|
|
134
|
+
const nextY = gesture.current.y + (y - gesture.current.startY);
|
|
135
|
+
current.current.x = nextX;
|
|
136
|
+
current.current.y = nextY;
|
|
137
|
+
translateX.setValue(nextX);
|
|
138
|
+
translateY.setValue(nextY);
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
onPanResponderRelease: () => {
|
|
142
|
+
gesture.current.mode = '';
|
|
143
|
+
if (current.current.scale <= 1.05) resetZoom(true);
|
|
144
|
+
},
|
|
145
|
+
onPanResponderTerminate: () => {
|
|
146
|
+
gesture.current.mode = '';
|
|
147
|
+
if (current.current.scale <= 1.05) resetZoom(true);
|
|
148
|
+
},
|
|
149
|
+
}), [resetZoom, scale, translateX, translateY]);
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<Modal visible={visible} transparent animationType="fade" onRequestClose={onClose} statusBarTranslucent>
|
|
153
|
+
<View style={lightboxStyles.root}>
|
|
154
|
+
<View style={lightboxStyles.toolbar}>
|
|
155
|
+
<Pressable accessibilityRole="button" accessibilityLabel={closeLabel} style={lightboxStyles.toolbarButton} onPress={onClose}>
|
|
156
|
+
<Text style={lightboxStyles.closeIcon}>โน</Text>
|
|
157
|
+
</Pressable>
|
|
158
|
+
<Pressable accessibilityRole="button" accessibilityLabel={resetLabel} style={lightboxStyles.resetButton} onPress={() => resetZoom(true)}>
|
|
159
|
+
<Text style={lightboxStyles.resetText}>1:1</Text>
|
|
160
|
+
</Pressable>
|
|
161
|
+
</View>
|
|
162
|
+
<View style={lightboxStyles.stage} {...panResponder.panHandlers}>
|
|
163
|
+
{uri ? (
|
|
164
|
+
<Animated.Image
|
|
165
|
+
source={{ uri }}
|
|
166
|
+
resizeMode="contain"
|
|
167
|
+
style={[
|
|
168
|
+
lightboxStyles.image,
|
|
169
|
+
{ transform: [{ translateX }, { translateY }, { scale }] },
|
|
170
|
+
]}
|
|
171
|
+
/>
|
|
172
|
+
) : null}
|
|
173
|
+
</View>
|
|
174
|
+
</View>
|
|
175
|
+
</Modal>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const lightboxStyles = StyleSheet.create({
|
|
180
|
+
root: { flex: 1, backgroundColor: 'rgba(0,0,0,0.96)' },
|
|
181
|
+
toolbar: { position: 'absolute', zIndex: 20, top: Platform.OS === 'ios' ? 48 : 18, left: 14, right: 14, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
|
|
182
|
+
toolbarButton: { width: 44, height: 44, borderRadius: 22, backgroundColor: 'rgba(255,255,255,0.14)', alignItems: 'center', justifyContent: 'center' },
|
|
183
|
+
closeIcon: { color: '#FFFFFF', fontSize: 39, lineHeight: 40, marginTop: -6 },
|
|
184
|
+
resetButton: { minWidth: 50, height: 40, paddingHorizontal: 12, borderRadius: 20, backgroundColor: 'rgba(255,255,255,0.14)', alignItems: 'center', justifyContent: 'center' },
|
|
185
|
+
resetText: { color: '#FFFFFF', fontSize: 13, fontWeight: '700' },
|
|
186
|
+
stage: { flex: 1, overflow: 'hidden', alignItems: 'center', justifyContent: 'center' },
|
|
187
|
+
image: { width: '100%', height: '100%' },
|
|
188
|
+
});
|
|
23
189
|
|
|
24
190
|
function colorWithOpacity(hex, opacity) {
|
|
25
191
|
const value = String(hex || '#F5F7FB').replace('#', '');
|
|
@@ -35,18 +201,24 @@ function makePalette(settings) {
|
|
|
35
201
|
const dark = settings.theme_mode === 'dark';
|
|
36
202
|
const primary = settings.color_mode === 'advanced' ? settings.primary_color : settings.theme_color;
|
|
37
203
|
return {
|
|
204
|
+
dark,
|
|
38
205
|
primary,
|
|
39
206
|
background: colorWithOpacity(settings.chat_background, settings.chat_background_opacity),
|
|
40
|
-
surface: dark ? '#
|
|
41
|
-
surfaceAlt: dark ? '#
|
|
42
|
-
|
|
207
|
+
surface: dark ? '#1D2127' : '#FFFFFF',
|
|
208
|
+
surfaceAlt: dark ? '#252A31' : '#F5F7FB',
|
|
209
|
+
header: dark ? '#181B20' : '#F8F9FC',
|
|
210
|
+
border: dark ? 'rgba(255,255,255,0.10)' : 'rgba(20,32,54,0.10)',
|
|
43
211
|
text: dark ? '#F7F8FA' : '#17181B',
|
|
44
212
|
muted: settings.system_text || (dark ? '#A7ADB7' : '#6F7480'),
|
|
45
213
|
customerBubble: settings.color_mode === 'advanced' ? settings.customer_bubble : primary,
|
|
46
214
|
customerText: settings.color_mode === 'advanced' ? settings.customer_text : '#FFFFFF',
|
|
47
215
|
agentBubble: settings.color_mode === 'advanced' ? settings.agent_bubble : (dark ? '#22252A' : '#FFFFFF'),
|
|
48
216
|
agentText: settings.color_mode === 'advanced' ? settings.agent_text : (dark ? '#F7F8FA' : '#17181B'),
|
|
49
|
-
danger: '#
|
|
217
|
+
danger: '#DF2917',
|
|
218
|
+
heroBase: dark ? '#151B21' : '#EEF7FC',
|
|
219
|
+
heroBlobA: dark ? 'rgba(73,83,152,0.25)' : 'rgba(118,137,255,0.30)',
|
|
220
|
+
heroBlobB: dark ? 'rgba(34,126,168,0.22)' : 'rgba(96,222,215,0.28)',
|
|
221
|
+
heroBlobC: dark ? 'rgba(255,255,255,0.035)' : 'rgba(255,255,255,0.52)',
|
|
50
222
|
};
|
|
51
223
|
}
|
|
52
224
|
|
|
@@ -57,6 +229,11 @@ function formatTime(value) {
|
|
|
57
229
|
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
58
230
|
}
|
|
59
231
|
|
|
232
|
+
function isSupportMessage(message) {
|
|
233
|
+
const sender = String(message && message.sender_type || '').toLowerCase();
|
|
234
|
+
return sender === 'agent' || sender === 'admin' || sender === 'chatbot';
|
|
235
|
+
}
|
|
236
|
+
|
|
60
237
|
function RegantisChat(props) {
|
|
61
238
|
const {
|
|
62
239
|
apiKey,
|
|
@@ -68,6 +245,10 @@ function RegantisChat(props) {
|
|
|
68
245
|
texts,
|
|
69
246
|
screen = 'chat',
|
|
70
247
|
style,
|
|
248
|
+
initialView = 'home',
|
|
249
|
+
onClose,
|
|
250
|
+
onBack,
|
|
251
|
+
onViewChange,
|
|
71
252
|
onMessage,
|
|
72
253
|
onUnreadChange,
|
|
73
254
|
onError,
|
|
@@ -88,11 +269,13 @@ function RegantisChat(props) {
|
|
|
88
269
|
}), [apiKey, serverUrl, language, translationLanguage, customer, theme, texts, screen, onMessage, onUnreadChange, onError]);
|
|
89
270
|
|
|
90
271
|
const [state, setState] = React.useState(client.getState());
|
|
272
|
+
const [activeView, setActiveView] = React.useState(initialView === 'chat' ? 'chat' : 'home');
|
|
91
273
|
const [draft, setDraft] = React.useState('');
|
|
92
274
|
const [contactName, setContactName] = React.useState(customer && customer.name || '');
|
|
93
275
|
const [contactEmail, setContactEmail] = React.useState(customer && customer.email || '');
|
|
94
276
|
const [contactError, setContactError] = React.useState('');
|
|
95
277
|
const [optionsOpen, setOptionsOpen] = React.useState(false);
|
|
278
|
+
const [languageOpen, setLanguageOpen] = React.useState(false);
|
|
96
279
|
const [attachOpen, setAttachOpen] = React.useState(false);
|
|
97
280
|
const [emojiOpen, setEmojiOpen] = React.useState(false);
|
|
98
281
|
const [closeOpen, setCloseOpen] = React.useState(false);
|
|
@@ -103,6 +286,7 @@ function RegantisChat(props) {
|
|
|
103
286
|
const [ratingComment, setRatingComment] = React.useState('');
|
|
104
287
|
const [ratingStatus, setRatingStatus] = React.useState('');
|
|
105
288
|
const [busyAction, setBusyAction] = React.useState('');
|
|
289
|
+
const [lightboxUri, setLightboxUri] = React.useState('');
|
|
106
290
|
const listRef = React.useRef(null);
|
|
107
291
|
|
|
108
292
|
React.useEffect(() => {
|
|
@@ -124,19 +308,20 @@ function RegantisChat(props) {
|
|
|
124
308
|
}, [state.conversation, contactName, contactEmail, transcriptEmail]);
|
|
125
309
|
|
|
126
310
|
React.useEffect(() => {
|
|
127
|
-
if (state.messages.length) {
|
|
311
|
+
if (activeView === 'chat' && state.messages.length) {
|
|
128
312
|
const timer = setTimeout(() => listRef.current && listRef.current.scrollToEnd({ animated: true }), 50);
|
|
129
313
|
return () => clearTimeout(timer);
|
|
130
314
|
}
|
|
131
|
-
}, [state.messages.length]);
|
|
315
|
+
}, [activeView, state.messages.length]);
|
|
132
316
|
|
|
133
|
-
const t = key => state.texts[key] || key;
|
|
317
|
+
const t = key => state.texts[key] || localText(language, key) || key;
|
|
134
318
|
const palette = makePalette(state.settings);
|
|
135
319
|
const styles = React.useMemo(() => buildStyles(palette), [
|
|
136
320
|
palette.primary,
|
|
137
321
|
palette.background,
|
|
138
322
|
palette.surface,
|
|
139
323
|
palette.surfaceAlt,
|
|
324
|
+
palette.header,
|
|
140
325
|
palette.border,
|
|
141
326
|
palette.text,
|
|
142
327
|
palette.muted,
|
|
@@ -144,8 +329,29 @@ function RegantisChat(props) {
|
|
|
144
329
|
palette.customerText,
|
|
145
330
|
palette.agentBubble,
|
|
146
331
|
palette.agentText,
|
|
332
|
+
palette.heroBase,
|
|
147
333
|
]);
|
|
148
334
|
|
|
335
|
+
const c = state.conversation || {};
|
|
336
|
+
const active = Number(c.status || 0) === 1;
|
|
337
|
+
const contactConfirmed = Number(c.contact_confirmed || 0) === 1;
|
|
338
|
+
const canUpload = !!(state.upload && state.upload.enabled);
|
|
339
|
+
const hasExisting = state.messages.length > 0 || contactConfirmed;
|
|
340
|
+
const lastSupport = [...state.messages].reverse().find(isSupportMessage);
|
|
341
|
+
const homePreview = lastSupport
|
|
342
|
+
? String(lastSupport.message || '').trim() || (lastSupport.attachment ? `[${String(lastSupport.message_type || 'file')}]` : t('empty'))
|
|
343
|
+
: t('empty');
|
|
344
|
+
|
|
345
|
+
function showView(next) {
|
|
346
|
+
const view = next === 'chat' ? 'chat' : 'home';
|
|
347
|
+
setActiveView(view);
|
|
348
|
+
setOptionsOpen(false);
|
|
349
|
+
setLanguageOpen(false);
|
|
350
|
+
setAttachOpen(false);
|
|
351
|
+
setEmojiOpen(false);
|
|
352
|
+
if (typeof onViewChange === 'function') onViewChange(view);
|
|
353
|
+
}
|
|
354
|
+
|
|
149
355
|
async function submitContact() {
|
|
150
356
|
setContactError('');
|
|
151
357
|
if (!contactName.trim() || !/^\S+@\S+\.\S+$/.test(contactEmail.trim())) {
|
|
@@ -189,6 +395,7 @@ function RegantisChat(props) {
|
|
|
189
395
|
setBusyAction('language');
|
|
190
396
|
try {
|
|
191
397
|
await client.setTranslationLanguage(code);
|
|
398
|
+
setLanguageOpen(false);
|
|
192
399
|
setOptionsOpen(false);
|
|
193
400
|
} finally {
|
|
194
401
|
setBusyAction('');
|
|
@@ -254,6 +461,30 @@ function RegantisChat(props) {
|
|
|
254
461
|
}
|
|
255
462
|
}
|
|
256
463
|
|
|
464
|
+
function renderLogo(size, marginRight) {
|
|
465
|
+
if (!state.settings.show_logo) return null;
|
|
466
|
+
if (state.settings.logo_url) {
|
|
467
|
+
return <Image source={{ uri: state.settings.logo_url }} style={[styles.logoImage, { width: size, height: size, borderRadius: size / 2, marginRight: marginRight || 0 }]} resizeMode="cover" />;
|
|
468
|
+
}
|
|
469
|
+
return (
|
|
470
|
+
<View style={[styles.logoFallback, { width: size, height: size, borderRadius: size / 2, marginRight: marginRight || 0 }]}>
|
|
471
|
+
<Text style={[styles.logoFallbackText, { fontSize: Math.max(12, Math.round(size * 0.38)) }]}>R</Text>
|
|
472
|
+
</View>
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function renderAgentVisual() {
|
|
477
|
+
const showLogo = !!state.settings.show_logo;
|
|
478
|
+
const showAgent = !!state.settings.show_agent_photo;
|
|
479
|
+
return (
|
|
480
|
+
<View style={styles.agentVisual}>
|
|
481
|
+
{showLogo ? renderLogo(28, 0) : null}
|
|
482
|
+
{showAgent ? <View style={[styles.agentAvatar, showLogo && styles.agentAvatarOverlap]}><Text style={styles.agentAvatarText}>R</Text></View> : null}
|
|
483
|
+
{!showLogo && !showAgent ? <View style={styles.agentFallback}><Text style={styles.agentFallbackText}>โ</Text></View> : null}
|
|
484
|
+
</View>
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
|
|
257
488
|
function renderMessage({ item }) {
|
|
258
489
|
const sender = String(item.sender_type || '').toLowerCase();
|
|
259
490
|
const system = sender === 'system' || String(item.message_type || '').startsWith('routing_') || String(item.message_type || '').startsWith('rating_');
|
|
@@ -267,66 +498,168 @@ function RegantisChat(props) {
|
|
|
267
498
|
const textStyle = visitor ? styles.customerText : styles.agentText;
|
|
268
499
|
const attachment = item.attachment || null;
|
|
269
500
|
const isImage = attachment && String(attachment.mime || '').startsWith('image/');
|
|
501
|
+
const senderName = sender === 'chatbot' ? 'Chatbot' : (item.sender_name || state.settings.operator_name || t('support_name'));
|
|
270
502
|
|
|
271
503
|
return (
|
|
272
504
|
<View style={[styles.messageRow, visitor ? styles.messageRowRight : styles.messageRowLeft]}>
|
|
273
|
-
{!visitor && state.settings.show_agent_photo ? <View style={styles.avatar}><Text style={styles.avatarText}>{String(item.sender_name || state.settings.operator_name || 'S').slice(0, 1).toUpperCase()}</Text></View> : null}
|
|
274
505
|
<View style={[styles.bubble, bubbleStyle]}>
|
|
275
|
-
{!visitor && item.sender_name ? <Text style={[styles.senderName, textStyle]}>{item.sender_name}</Text> : null}
|
|
276
506
|
{isImage ? (
|
|
277
|
-
<Pressable onPress={() =>
|
|
507
|
+
<Pressable onPress={() => setLightboxUri(attachment.url)}>
|
|
278
508
|
<Image source={{ uri: attachment.url }} style={styles.attachmentImage} resizeMode="cover" />
|
|
279
509
|
</Pressable>
|
|
280
510
|
) : attachment ? (
|
|
281
511
|
<Pressable style={styles.fileAttachment} onPress={() => Linking.openURL(attachment.url)}>
|
|
282
|
-
<Text style={textStyle}>๐ {attachment.name || t('attach_file')}</Text>
|
|
512
|
+
<Text style={[styles.fileAttachmentText, textStyle]}>๐ {attachment.name || t('attach_file')}</Text>
|
|
283
513
|
</Pressable>
|
|
284
514
|
) : null}
|
|
285
515
|
{item.message ? <Text style={[styles.messageText, textStyle]}>{item.message}</Text> : null}
|
|
286
|
-
<
|
|
516
|
+
<View style={styles.messageMeta}>
|
|
517
|
+
{!visitor ? <Text numberOfLines={1} style={[styles.messageSender, textStyle]}>{senderName}</Text> : <View />}
|
|
518
|
+
<Text style={[styles.timeText, textStyle]}>{formatTime(item.date_added)}</Text>
|
|
519
|
+
</View>
|
|
287
520
|
</View>
|
|
288
521
|
</View>
|
|
289
522
|
);
|
|
290
523
|
}
|
|
291
524
|
|
|
292
|
-
|
|
293
|
-
|
|
525
|
+
function renderPoweredBy() {
|
|
526
|
+
if (state.settings.white_label) return null;
|
|
527
|
+
return <View style={styles.poweredBy}><Text style={styles.poweredByText}>{t('powered_by')} <Text style={styles.poweredByBrand}>Regantis</Text></Text></View>;
|
|
294
528
|
}
|
|
295
529
|
|
|
296
|
-
|
|
530
|
+
function renderHome() {
|
|
297
531
|
return (
|
|
298
|
-
<View style={
|
|
299
|
-
<
|
|
300
|
-
|
|
532
|
+
<View style={styles.screen}>
|
|
533
|
+
<View style={styles.homeHero}>
|
|
534
|
+
<View pointerEvents="none" style={styles.heroDecorationA} />
|
|
535
|
+
<View pointerEvents="none" style={styles.heroDecorationB} />
|
|
536
|
+
<View pointerEvents="none" style={styles.heroDecorationC} />
|
|
537
|
+
|
|
538
|
+
<View style={styles.homeTop}>
|
|
539
|
+
<View style={styles.homeTopLeft}>
|
|
540
|
+
{typeof onBack === 'function' || typeof onClose === 'function' ? (
|
|
541
|
+
<Pressable accessibilityRole="button" accessibilityLabel={t('back')} style={styles.iconButton} onPress={typeof onBack === 'function' ? onBack : onClose}>
|
|
542
|
+
<Text style={styles.homeBackIcon}>โน</Text>
|
|
543
|
+
</Pressable>
|
|
544
|
+
) : null}
|
|
545
|
+
<View>{renderLogo(42, 0)}</View>
|
|
546
|
+
</View>
|
|
547
|
+
{typeof onClose === 'function' ? (
|
|
548
|
+
<Pressable accessibilityRole="button" accessibilityLabel={t('minimize')} style={styles.iconButton} onPress={onClose}>
|
|
549
|
+
<Text style={styles.minimizeIcon}>โ</Text>
|
|
550
|
+
</Pressable>
|
|
551
|
+
) : <View style={styles.homeTopPlaceholder} />}
|
|
552
|
+
</View>
|
|
553
|
+
|
|
554
|
+
<View style={styles.homeTitle}>
|
|
555
|
+
<Text style={styles.homeTitleLine}>{t('welcome_title')}</Text>
|
|
556
|
+
<Text style={styles.homeTitleLine}>{t('welcome_text')}</Text>
|
|
557
|
+
</View>
|
|
558
|
+
|
|
559
|
+
<Pressable style={styles.homeCard} onPress={() => showView('chat')}>
|
|
560
|
+
<View style={styles.homeCardTop}>
|
|
561
|
+
{state.settings.show_agent_photo ? <View style={styles.homeAvatar}><Text style={styles.homeAvatarText}>R</Text></View> : null}
|
|
562
|
+
<View style={styles.homeCardCopy}>
|
|
563
|
+
<Text numberOfLines={1} style={styles.homeCardAgent}>{state.settings.operator_name || t('support_name')}</Text>
|
|
564
|
+
<Text numberOfLines={1} style={styles.homeCardPreview}>{homePreview}</Text>
|
|
565
|
+
</View>
|
|
566
|
+
</View>
|
|
567
|
+
<View style={styles.homeCardAction}>
|
|
568
|
+
<Text style={styles.homeCardActionText}>{hasExisting ? t('back_to_chat') : t('lets_chat')}</Text>
|
|
569
|
+
</View>
|
|
570
|
+
</Pressable>
|
|
571
|
+
</View>
|
|
572
|
+
|
|
573
|
+
<View style={styles.homeSpacer} />
|
|
574
|
+
|
|
575
|
+
<View style={styles.bottomNav}>
|
|
576
|
+
<Pressable style={[styles.bottomNavButton, styles.bottomNavButtonActive]} onPress={() => showView('home')}>
|
|
577
|
+
<Text style={[styles.bottomNavIcon, styles.bottomNavIconActive]}>โ</Text>
|
|
578
|
+
<Text style={[styles.bottomNavText, styles.bottomNavTextActive]}>{t('home')}</Text>
|
|
579
|
+
</Pressable>
|
|
580
|
+
<Pressable style={styles.bottomNavButton} onPress={() => showView('chat')}>
|
|
581
|
+
<Text style={styles.bottomNavIcon}>โ</Text>
|
|
582
|
+
<Text style={styles.bottomNavText}>{t('chat')}</Text>
|
|
583
|
+
</Pressable>
|
|
584
|
+
</View>
|
|
585
|
+
{renderPoweredBy()}
|
|
301
586
|
</View>
|
|
302
587
|
);
|
|
303
588
|
}
|
|
304
589
|
|
|
305
|
-
|
|
590
|
+
function renderChatHeader() {
|
|
591
|
+
return (
|
|
592
|
+
<View style={styles.chatHeader}>
|
|
593
|
+
<View style={styles.chatHeaderLeft}>
|
|
594
|
+
<Pressable accessibilityRole="button" accessibilityLabel={t('back')} style={styles.headerIconButton} onPress={() => showView('home')}>
|
|
595
|
+
<Text style={styles.backIcon}>โน</Text>
|
|
596
|
+
</Pressable>
|
|
597
|
+
<Pressable accessibilityRole="button" accessibilityLabel={t('options')} style={styles.headerIconButton} onPress={() => setOptionsOpen(value => !value)}>
|
|
598
|
+
<Text style={styles.menuIcon}>โขโขโข</Text>
|
|
599
|
+
</Pressable>
|
|
600
|
+
</View>
|
|
306
601
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
602
|
+
<Pressable
|
|
603
|
+
accessibilityRole="button"
|
|
604
|
+
accessibilityLabel={state.settings.operator_name || t('support_name')}
|
|
605
|
+
style={styles.agentTrigger}
|
|
606
|
+
onPress={() => {
|
|
607
|
+
if (state.settings.rating_enabled && contactConfirmed) setRatingOpen(true);
|
|
608
|
+
}}
|
|
609
|
+
>
|
|
610
|
+
{renderAgentVisual()}
|
|
611
|
+
</Pressable>
|
|
311
612
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
<
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
<Pressable style={styles.iconButton} onPress={() => setOptionsOpen(true)}><Text style={styles.iconText}>โฏ</Text></Pressable>
|
|
324
|
-
{active && state.conversation.conversation_id ? <Pressable style={styles.iconButton} onPress={() => setCloseOpen(true)}><Text style={styles.iconText}>ร</Text></Pressable> : null}
|
|
613
|
+
<View style={styles.chatHeaderRight}>
|
|
614
|
+
{typeof onClose === 'function' ? (
|
|
615
|
+
<Pressable accessibilityRole="button" accessibilityLabel={t('minimize')} style={styles.headerIconButton} onPress={onClose}>
|
|
616
|
+
<Text style={styles.minimizeIcon}>โ</Text>
|
|
617
|
+
</Pressable>
|
|
618
|
+
) : null}
|
|
619
|
+
{active && Number(c.conversation_id || 0) > 0 ? (
|
|
620
|
+
<Pressable accessibilityRole="button" accessibilityLabel={t('close')} style={styles.headerIconButton} onPress={() => setCloseOpen(true)}>
|
|
621
|
+
<Text style={styles.closeIcon}>ร</Text>
|
|
622
|
+
</Pressable>
|
|
623
|
+
) : null}
|
|
325
624
|
</View>
|
|
625
|
+
|
|
626
|
+
{optionsOpen ? (
|
|
627
|
+
<>
|
|
628
|
+
<Pressable style={styles.inlineDismiss} onPress={() => setOptionsOpen(false)} />
|
|
629
|
+
<View style={styles.optionsPopover}>
|
|
630
|
+
{state.settings.transcript_enabled && contactConfirmed ? (
|
|
631
|
+
<Pressable style={styles.popoverRow} onPress={() => { setOptionsOpen(false); setTranscriptOpen(true); }}>
|
|
632
|
+
<Text style={styles.popoverIcon}>โ</Text>
|
|
633
|
+
<Text style={styles.popoverText}>{t('send_transcript')}</Text>
|
|
634
|
+
</Pressable>
|
|
635
|
+
) : null}
|
|
636
|
+
{state.translationCustomerEnabled ? (
|
|
637
|
+
<Pressable style={styles.popoverRow} onPress={() => { setOptionsOpen(false); setLanguageOpen(true); }}>
|
|
638
|
+
<Text style={styles.popoverIcon}>โ</Text>
|
|
639
|
+
<Text style={styles.popoverText}>{t('language')}</Text>
|
|
640
|
+
<Text style={styles.popoverChevron}>โบ</Text>
|
|
641
|
+
</Pressable>
|
|
642
|
+
) : null}
|
|
643
|
+
{state.settings.sound_enabled !== undefined ? (
|
|
644
|
+
<Pressable style={styles.popoverRow} onPress={() => client.setSoundEnabled(!state.settings.sound_enabled)}>
|
|
645
|
+
<Text style={styles.popoverIcon}>โ</Text>
|
|
646
|
+
<Text style={styles.popoverText}>{t('sounds')}</Text>
|
|
647
|
+
<View style={[styles.switchTrack, state.settings.sound_enabled && styles.switchTrackActive]}>
|
|
648
|
+
<View style={[styles.switchThumb, state.settings.sound_enabled && styles.switchThumbActive]} />
|
|
649
|
+
</View>
|
|
650
|
+
</Pressable>
|
|
651
|
+
) : null}
|
|
652
|
+
</View>
|
|
653
|
+
</>
|
|
654
|
+
) : null}
|
|
326
655
|
</View>
|
|
656
|
+
);
|
|
657
|
+
}
|
|
327
658
|
|
|
328
|
-
|
|
329
|
-
|
|
659
|
+
function renderContactStage() {
|
|
660
|
+
return (
|
|
661
|
+
<ScrollView style={styles.contactStageScroll} contentContainerStyle={styles.contactStage} keyboardShouldPersistTaps="handled">
|
|
662
|
+
<View style={styles.contactCard}>
|
|
330
663
|
<Text style={styles.contactTitle}>{t('start_chat_title')}</Text>
|
|
331
664
|
<Text style={styles.label}>{t('name')}</Text>
|
|
332
665
|
<TextInput style={styles.input} value={contactName} onChangeText={setContactName} maxLength={190} placeholder={t('name')} placeholderTextColor={palette.muted} autoCapitalize="words" />
|
|
@@ -336,203 +669,347 @@ function RegantisChat(props) {
|
|
|
336
669
|
<Pressable disabled={busyAction === 'contact'} style={[styles.primaryButton, busyAction === 'contact' && styles.disabled]} onPress={submitContact}>
|
|
337
670
|
{busyAction === 'contact' ? <ActivityIndicator color="#FFFFFF" /> : <Text style={styles.primaryButtonText}>{t('start_chat')}</Text>}
|
|
338
671
|
</Pressable>
|
|
339
|
-
</
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
ref={listRef}
|
|
344
|
-
data={state.messages}
|
|
345
|
-
keyExtractor={item => String(item.chat_message_id)}
|
|
346
|
-
renderItem={renderMessage}
|
|
347
|
-
style={styles.messageList}
|
|
348
|
-
contentContainerStyle={styles.messageListContent}
|
|
349
|
-
onScrollBeginDrag={() => state.hasMore && client.loadOlder().catch(() => {})}
|
|
350
|
-
ListEmptyComponent={<View style={styles.emptyState}><Text style={styles.emptyText}>{t('empty')}</Text></View>}
|
|
351
|
-
/>
|
|
672
|
+
</View>
|
|
673
|
+
</ScrollView>
|
|
674
|
+
);
|
|
675
|
+
}
|
|
352
676
|
|
|
353
|
-
|
|
677
|
+
function renderComposer() {
|
|
678
|
+
if (!active) {
|
|
679
|
+
return (
|
|
680
|
+
<View style={styles.closedStage}>
|
|
681
|
+
<Text style={styles.closedText}>{c.closed_by === 'visitor' ? t('chat_closed') : t('chat_closed_agent')}</Text>
|
|
682
|
+
<Pressable disabled={busyAction === 'reopen'} style={styles.primaryButton} onPress={reopenChat}>
|
|
683
|
+
<Text style={styles.primaryButtonText}>{t('restart_chat')}</Text>
|
|
684
|
+
</Pressable>
|
|
685
|
+
</View>
|
|
686
|
+
);
|
|
687
|
+
}
|
|
354
688
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
689
|
+
return (
|
|
690
|
+
<View style={styles.composerArea}>
|
|
691
|
+
{String(c.routing_mode || '') === 'chatbot' ? <Text style={styles.aiCompliance}>โ {t('ai_compliance')}</Text> : null}
|
|
692
|
+
|
|
693
|
+
{attachOpen ? (
|
|
694
|
+
<View style={styles.attachmentMenu}>
|
|
695
|
+
{Platform.OS === 'android' || Platform.OS === 'ios' ? (
|
|
696
|
+
<Pressable style={styles.attachmentAction} onPress={() => pick('camera')}><Text style={styles.attachmentActionIcon}>โ</Text><Text style={styles.attachmentActionText}>{t('attach_camera')}</Text></Pressable>
|
|
697
|
+
) : null}
|
|
698
|
+
<Pressable style={styles.attachmentAction} onPress={() => pick('image')}><Text style={styles.attachmentActionIcon}>โฃ</Text><Text style={styles.attachmentActionText}>{t('attach_image')}</Text></Pressable>
|
|
699
|
+
<Pressable style={styles.attachmentAction} onPress={() => pick('file')}><Text style={styles.attachmentActionIcon}>โฅ</Text><Text style={styles.attachmentActionText}>{t('attach_file')}</Text></Pressable>
|
|
700
|
+
</View>
|
|
701
|
+
) : null}
|
|
702
|
+
|
|
703
|
+
{emojiOpen ? (
|
|
704
|
+
<View style={styles.emojiPicker}>
|
|
705
|
+
{EMOJIS.map(emoji => (
|
|
706
|
+
<Pressable key={emoji} style={styles.emojiButton} onPress={() => { setDraft(value => value + emoji); setEmojiOpen(false); }}>
|
|
707
|
+
<Text style={styles.emojiText}>{emoji}</Text>
|
|
708
|
+
</Pressable>
|
|
709
|
+
))}
|
|
710
|
+
</View>
|
|
711
|
+
) : null}
|
|
712
|
+
|
|
713
|
+
<View style={styles.composer}>
|
|
714
|
+
<Pressable disabled={!canUpload || state.uploading} style={[styles.composerButton, (!canUpload || state.uploading) && styles.disabled]} onPress={() => { setEmojiOpen(false); setAttachOpen(value => !value); }}>
|
|
715
|
+
<Text style={styles.composerPlus}>๏ผ</Text>
|
|
716
|
+
</Pressable>
|
|
717
|
+
<Pressable style={styles.composerButton} onPress={() => { setAttachOpen(false); setEmojiOpen(value => !value); }}>
|
|
718
|
+
<Text style={styles.composerEmoji}>โบ</Text>
|
|
719
|
+
</Pressable>
|
|
720
|
+
<TextInput
|
|
721
|
+
style={styles.composerInput}
|
|
722
|
+
value={draft}
|
|
723
|
+
onChangeText={value => { setDraft(value); client.sendTyping(value); }}
|
|
724
|
+
placeholder={t('placeholder')}
|
|
725
|
+
placeholderTextColor={palette.muted}
|
|
726
|
+
multiline
|
|
727
|
+
maxLength={5000}
|
|
728
|
+
/>
|
|
729
|
+
<Pressable disabled={!draft.trim() || state.sending} style={[styles.sendButton, (!draft.trim() || state.sending) && styles.disabled]} onPress={submitMessage}>
|
|
730
|
+
{state.sending ? <ActivityIndicator size="small" color="#FFFFFF" /> : <Text style={styles.sendIcon}>โค</Text>}
|
|
731
|
+
</Pressable>
|
|
732
|
+
</View>
|
|
733
|
+
</View>
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function renderChat() {
|
|
738
|
+
return (
|
|
739
|
+
<View style={styles.screen}>
|
|
740
|
+
{renderChatHeader()}
|
|
741
|
+
<View style={styles.chatContent}>
|
|
742
|
+
{!contactConfirmed ? renderContactStage() : (
|
|
743
|
+
<>
|
|
744
|
+
<FlatList
|
|
745
|
+
ref={listRef}
|
|
746
|
+
data={state.messages}
|
|
747
|
+
keyExtractor={item => String(item.chat_message_id)}
|
|
748
|
+
renderItem={renderMessage}
|
|
749
|
+
style={styles.messageList}
|
|
750
|
+
contentContainerStyle={styles.messageListContent}
|
|
751
|
+
onScrollBeginDrag={() => state.hasMore && client.loadOlder().catch(() => {})}
|
|
752
|
+
ListHeaderComponent={state.hasMore ? <View style={styles.historyLoader}><ActivityIndicator size="small" color={palette.primary} /></View> : null}
|
|
753
|
+
ListEmptyComponent={<View style={styles.emptyState}><Text style={styles.emptyText}>{t('empty')}</Text></View>}
|
|
754
|
+
/>
|
|
755
|
+
{state.remoteTyping ? <View style={styles.typingRow}><Text style={styles.typingText}>{state.remoteTyping === 'chatbot' ? t('chatbot_typing') : t('typing')}</Text></View> : null}
|
|
756
|
+
{renderComposer()}
|
|
757
|
+
</>
|
|
382
758
|
)}
|
|
383
|
-
|
|
384
|
-
|
|
759
|
+
{renderPoweredBy()}
|
|
760
|
+
</View>
|
|
761
|
+
</View>
|
|
762
|
+
);
|
|
763
|
+
}
|
|
385
764
|
|
|
386
|
-
|
|
765
|
+
if (state.loading) {
|
|
766
|
+
return <View style={[styles.root, styles.center, style]}><ActivityIndicator size="large" color={palette.primary} /><Text style={styles.loadingText}>{t('loading')}</Text></View>;
|
|
767
|
+
}
|
|
387
768
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
</Modal>
|
|
769
|
+
if (state.error) {
|
|
770
|
+
return (
|
|
771
|
+
<View style={[styles.root, styles.center, style]}>
|
|
772
|
+
<Text style={styles.errorText}>{t('error')}</Text>
|
|
773
|
+
<Pressable style={styles.primaryButton} onPress={() => client.start().catch(() => {})}><Text style={styles.primaryButtonText}>{t('restart_chat')}</Text></Pressable>
|
|
774
|
+
</View>
|
|
775
|
+
);
|
|
776
|
+
}
|
|
397
777
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
</
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
{state.settings.transcript_enabled && contactConfirmed ? <Pressable style={styles.optionRow} onPress={() => { setOptionsOpen(false); setTranscriptOpen(true); }}><Text style={styles.optionText}>{t('send_transcript')}</Text><Text style={styles.optionValue}>โบ</Text></Pressable> : null}
|
|
417
|
-
{state.settings.rating_enabled && contactConfirmed ? <Pressable style={styles.optionRow} onPress={() => { setOptionsOpen(false); setRatingOpen(true); }}><Text style={styles.optionText}>{t('rating_agent_label')}</Text><Text style={styles.optionValue}>โบ</Text></Pressable> : null}
|
|
778
|
+
if (state.banned) return <View style={[styles.root, styles.center, style]}><Text style={styles.errorText}>{t('error')}</Text></View>;
|
|
779
|
+
|
|
780
|
+
return (
|
|
781
|
+
<KeyboardAvoidingView style={[styles.root, style]} behavior={Platform.OS === 'ios' ? 'padding' : undefined} keyboardVerticalOffset={props.keyboardVerticalOffset || 0}>
|
|
782
|
+
{activeView === 'home' ? renderHome() : renderChat()}
|
|
783
|
+
|
|
784
|
+
<Modal transparent visible={languageOpen} animationType="fade" onRequestClose={() => setLanguageOpen(false)}>
|
|
785
|
+
<Pressable style={styles.modalBackdrop} onPress={() => setLanguageOpen(false)}>
|
|
786
|
+
<View style={styles.sheet} onStartShouldSetResponder={() => true}>
|
|
787
|
+
<Text style={styles.sheetTitle}>{t('language')}</Text>
|
|
788
|
+
<ScrollView style={styles.languageList}>
|
|
789
|
+
<Pressable style={styles.languageRow} onPress={() => selectLanguage('')}><Text style={styles.optionText}>{t('language_auto')}</Text><Text style={styles.optionValue}>{!c.translation_language_code ? 'โ' : ''}</Text></Pressable>
|
|
790
|
+
{state.languages.map(item => {
|
|
791
|
+
const code = String(item.code || item.language_code || '').toLowerCase();
|
|
792
|
+
const name = item.name || item.language_name || code.toUpperCase();
|
|
793
|
+
return <Pressable key={code} style={styles.languageRow} onPress={() => selectLanguage(code)}><Text style={styles.optionText}>{name}</Text><Text style={styles.optionValue}>{String(c.translation_language_code || '').toLowerCase() === code ? 'โ' : code.toUpperCase()}</Text></Pressable>;
|
|
794
|
+
})}
|
|
795
|
+
</ScrollView>
|
|
418
796
|
</View>
|
|
419
797
|
</Pressable>
|
|
420
798
|
</Modal>
|
|
421
799
|
|
|
422
800
|
<Modal transparent visible={closeOpen} animationType="fade" onRequestClose={() => setCloseOpen(false)}>
|
|
423
|
-
<View style={styles.
|
|
424
|
-
<
|
|
425
|
-
|
|
426
|
-
|
|
801
|
+
<View style={styles.modalBackdropTop}>
|
|
802
|
+
<View style={styles.modalCard}>
|
|
803
|
+
<View style={styles.modalIcon}><Text style={styles.modalIconText}>โช</Text></View>
|
|
804
|
+
<Text style={styles.modalText}>{t('close_chat_question')}</Text>
|
|
805
|
+
<View style={styles.modalActions}>
|
|
806
|
+
<Pressable style={styles.secondaryButton} onPress={() => setCloseOpen(false)}><Text style={styles.secondaryButtonText}>{t('cancel')}</Text></Pressable>
|
|
807
|
+
<Pressable style={styles.dangerButton} onPress={closeChat}><Text style={styles.primaryButtonText}>{t('close_chat_confirm')}</Text></Pressable>
|
|
808
|
+
</View>
|
|
809
|
+
</View>
|
|
810
|
+
</View>
|
|
427
811
|
</Modal>
|
|
428
812
|
|
|
429
813
|
<Modal transparent visible={transcriptOpen} animationType="fade" onRequestClose={() => setTranscriptOpen(false)}>
|
|
430
|
-
<View style={styles.
|
|
431
|
-
<
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
814
|
+
<View style={styles.modalBackdropTop}>
|
|
815
|
+
<View style={styles.modalCard}>
|
|
816
|
+
<View style={styles.modalIcon}><Text style={styles.modalIconText}>โ</Text></View>
|
|
817
|
+
<Text style={styles.modalText}>{t('send_transcript_title')}</Text>
|
|
818
|
+
<TextInput style={styles.input} value={transcriptEmail} onChangeText={setTranscriptEmail} keyboardType="email-address" autoCapitalize="none" placeholder={t('email')} placeholderTextColor={palette.muted} />
|
|
819
|
+
{transcriptStatus ? <Text style={styles.modalStatus}>{transcriptStatus}</Text> : null}
|
|
820
|
+
<Pressable style={styles.primaryButton} onPress={sendTranscript}><Text style={styles.primaryButtonText}>{t('send_transcript')}</Text></Pressable>
|
|
821
|
+
</View>
|
|
822
|
+
</View>
|
|
436
823
|
</Modal>
|
|
437
824
|
|
|
438
825
|
<Modal transparent visible={ratingOpen} animationType="fade" onRequestClose={() => setRatingOpen(false)}>
|
|
439
|
-
<View style={styles.
|
|
440
|
-
<
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
826
|
+
<View style={styles.modalBackdropTop}>
|
|
827
|
+
<View style={styles.modalCard}>
|
|
828
|
+
<Text style={styles.modalTitle}>{state.settings.operator_name || t('support_name')}</Text>
|
|
829
|
+
<Text style={styles.ratingLabel}>{t('rating_agent_label')}</Text>
|
|
830
|
+
<View style={styles.ratingButtons}>
|
|
831
|
+
<Pressable style={styles.ratingButton} onPress={() => rate(1)}><Text style={styles.ratingIcon}>๐</Text></Pressable>
|
|
832
|
+
<Pressable style={styles.ratingButton} onPress={() => rate(0)}><Text style={styles.ratingIcon}>๐</Text></Pressable>
|
|
833
|
+
</View>
|
|
834
|
+
{ratingStatus ? <Text style={styles.modalStatus}>{ratingStatus}</Text> : null}
|
|
835
|
+
<TextInput style={[styles.input, styles.commentInput]} value={ratingComment} onChangeText={setRatingComment} multiline maxLength={2000} placeholder={t('rating_comment_placeholder')} placeholderTextColor={palette.muted} />
|
|
836
|
+
<View style={styles.modalActions}>
|
|
837
|
+
<Pressable style={styles.secondaryButton} onPress={() => setRatingOpen(false)}><Text style={styles.secondaryButtonText}>{t('cancel')}</Text></Pressable>
|
|
838
|
+
<Pressable style={styles.primaryButtonCompact} onPress={sendRatingComment}><Text style={styles.primaryButtonText}>{t('rating_comment_send')}</Text></Pressable>
|
|
839
|
+
</View>
|
|
840
|
+
</View>
|
|
841
|
+
</View>
|
|
446
842
|
</Modal>
|
|
843
|
+
|
|
844
|
+
<ImageLightbox
|
|
845
|
+
visible={!!lightboxUri}
|
|
846
|
+
uri={lightboxUri}
|
|
847
|
+
onClose={() => setLightboxUri('')}
|
|
848
|
+
closeLabel={t('lightbox_close')}
|
|
849
|
+
resetLabel={t('lightbox_reset')}
|
|
850
|
+
/>
|
|
447
851
|
</KeyboardAvoidingView>
|
|
448
852
|
);
|
|
449
853
|
}
|
|
450
854
|
|
|
451
855
|
function buildStyles(p) {
|
|
856
|
+
const shadow = {
|
|
857
|
+
shadowColor: '#172236',
|
|
858
|
+
shadowOffset: { width: 0, height: 8 },
|
|
859
|
+
shadowOpacity: p.dark ? 0.24 : 0.10,
|
|
860
|
+
shadowRadius: 18,
|
|
861
|
+
elevation: 5,
|
|
862
|
+
};
|
|
863
|
+
|
|
452
864
|
return StyleSheet.create({
|
|
453
|
-
root: { flex: 1, backgroundColor: p.background },
|
|
865
|
+
root: { flex: 1, minWidth: 0, minHeight: 0, overflow: 'hidden', backgroundColor: p.background },
|
|
866
|
+
screen: { flex: 1, minWidth: 0, minHeight: 0, backgroundColor: p.background },
|
|
454
867
|
center: { alignItems: 'center', justifyContent: 'center', padding: 24, gap: 14 },
|
|
455
|
-
loadingText: { color: p.muted, fontSize:
|
|
456
|
-
errorText: { color: p.text, fontSize:
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
868
|
+
loadingText: { color: p.muted, fontSize: 13 },
|
|
869
|
+
errorText: { color: p.text, fontSize: 14, textAlign: 'center', marginBottom: 12 },
|
|
870
|
+
|
|
871
|
+
homeHero: { position: 'relative', overflow: 'hidden', paddingHorizontal: 18, paddingTop: 24, paddingBottom: 28, backgroundColor: p.heroBase },
|
|
872
|
+
heroDecorationA: { position: 'absolute', width: 240, height: 240, borderRadius: 120, top: -138, left: -74, backgroundColor: p.heroBlobA },
|
|
873
|
+
heroDecorationB: { position: 'absolute', width: 230, height: 230, borderRadius: 115, top: 80, right: -92, backgroundColor: p.heroBlobB },
|
|
874
|
+
heroDecorationC: { position: 'absolute', width: 210, height: 210, borderRadius: 105, right: -82, bottom: -120, backgroundColor: p.heroBlobC },
|
|
875
|
+
homeTop: { minHeight: 42, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', zIndex: 2 },
|
|
876
|
+
homeTopLeft: { flexDirection: 'row', alignItems: 'center', gap: 9 },
|
|
877
|
+
homeBackIcon: { color: p.text, fontSize: 37, lineHeight: 38, marginTop: -5 },
|
|
878
|
+
homeTopPlaceholder: { width: 32, height: 32 },
|
|
879
|
+
logoImage: { backgroundColor: p.surface },
|
|
880
|
+
logoFallback: { backgroundColor: p.primary, alignItems: 'center', justifyContent: 'center', shadowColor: p.primary, shadowOffset: { width: 0, height: 6 }, shadowOpacity: 0.22, shadowRadius: 10, elevation: 4 },
|
|
881
|
+
logoFallbackText: { color: '#FFFFFF', fontWeight: '800' },
|
|
882
|
+
iconButton: { width: 38, height: 38, borderRadius: 19, backgroundColor: p.dark ? 'rgba(255,255,255,0.08)' : 'rgba(255,255,255,0.82)', alignItems: 'center', justifyContent: 'center', zIndex: 3 },
|
|
883
|
+
minimizeIcon: { color: p.text, fontSize: 25, lineHeight: 27, fontWeight: '400', marginTop: -3 },
|
|
884
|
+
homeTitle: { marginTop: 24, zIndex: 2 },
|
|
885
|
+
homeTitleLine: { color: p.text, fontSize: 34, lineHeight: 36, letterSpacing: -1.1, fontWeight: '800' },
|
|
886
|
+
homeCard: { marginTop: 22, padding: 14, borderWidth: 1, borderColor: p.border, borderRadius: 18, backgroundColor: p.dark ? p.surface : 'rgba(255,255,255,0.96)', zIndex: 2, ...shadow },
|
|
887
|
+
homeCardTop: { minHeight: 38, flexDirection: 'row', alignItems: 'center' },
|
|
888
|
+
homeAvatar: { width: 38, height: 38, borderRadius: 19, marginRight: 10, backgroundColor: p.primary, alignItems: 'center', justifyContent: 'center' },
|
|
889
|
+
homeAvatarText: { color: '#FFFFFF', fontSize: 14, fontWeight: '700' },
|
|
890
|
+
homeCardCopy: { flex: 1, minWidth: 0 },
|
|
891
|
+
homeCardAgent: { color: p.muted, fontSize: 11, lineHeight: 15, fontWeight: '700' },
|
|
892
|
+
homeCardPreview: { marginTop: 3, color: p.text, fontSize: 13, lineHeight: 18 },
|
|
893
|
+
homeCardAction: { minHeight: 41, marginTop: 12, paddingHorizontal: 12, borderRadius: 11, backgroundColor: p.primary, alignItems: 'center', justifyContent: 'center' },
|
|
894
|
+
homeCardActionText: { color: '#FFFFFF', fontSize: 13, lineHeight: 18, fontWeight: '700', textAlign: 'center' },
|
|
895
|
+
homeSpacer: { flex: 1, minHeight: 20, backgroundColor: p.background },
|
|
896
|
+
bottomNav: { minHeight: 64, flexDirection: 'row', gap: 6, marginHorizontal: 16, marginBottom: 10, padding: 7, borderWidth: 1, borderColor: p.border, borderRadius: 18, backgroundColor: p.surface, ...shadow },
|
|
897
|
+
bottomNavButton: { flex: 1, minHeight: 48, borderRadius: 12, alignItems: 'center', justifyContent: 'center' },
|
|
898
|
+
bottomNavButtonActive: { backgroundColor: p.dark ? 'rgba(255,255,255,0.04)' : '#FAFBFC' },
|
|
899
|
+
bottomNavIcon: { color: p.muted, fontSize: 22, lineHeight: 23 },
|
|
900
|
+
bottomNavIconActive: { color: p.text },
|
|
901
|
+
bottomNavText: { marginTop: 2, color: p.muted, fontSize: 10, lineHeight: 12 },
|
|
902
|
+
bottomNavTextActive: { color: p.text },
|
|
903
|
+
|
|
904
|
+
chatHeader: { position: 'relative', zIndex: 20, minHeight: 64, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 12, borderBottomWidth: 1, borderBottomColor: p.border, backgroundColor: p.header, overflow: 'visible' },
|
|
905
|
+
chatHeaderLeft: { position: 'absolute', zIndex: 30, left: 12, top: 16, flexDirection: 'row', alignItems: 'center', gap: 5 },
|
|
906
|
+
chatHeaderRight: { position: 'absolute', zIndex: 30, right: 12, top: 16, flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-end', gap: 5 },
|
|
907
|
+
headerIconButton: { width: 32, height: 32, borderRadius: 16, backgroundColor: p.dark ? 'rgba(255,255,255,0.08)' : '#FFFFFF', alignItems: 'center', justifyContent: 'center' },
|
|
908
|
+
backIcon: { color: p.text, fontSize: 31, lineHeight: 31, marginTop: -4 },
|
|
909
|
+
menuIcon: { color: p.text, fontSize: 17, lineHeight: 18, letterSpacing: -1.5, fontWeight: '700', marginTop: -4 },
|
|
910
|
+
closeIcon: { color: p.text, fontSize: 23, lineHeight: 24, marginTop: -2 },
|
|
911
|
+
agentTrigger: { position: 'absolute', zIndex: 18, top: 0, left: '50%', width: 218, height: 48, marginLeft: -109, borderWidth: 1, borderTopWidth: 0, borderColor: p.border, borderBottomLeftRadius: 24, borderBottomRightRadius: 24, backgroundColor: p.surface, alignItems: 'center', justifyContent: 'center', shadowColor: '#172236', shadowOffset: { width: 0, height: 5 }, shadowOpacity: p.dark ? 0.18 : 0.07, shadowRadius: 8, elevation: 2 },
|
|
912
|
+
agentVisual: { height: 30, flexDirection: 'row', alignItems: 'center', justifyContent: 'center' },
|
|
913
|
+
agentAvatar: { width: 28, height: 28, borderRadius: 14, backgroundColor: p.primary, alignItems: 'center', justifyContent: 'center' },
|
|
914
|
+
agentAvatarOverlap: { marginLeft: -8, borderWidth: 2, borderColor: p.surface },
|
|
915
|
+
agentAvatarText: { color: '#FFFFFF', fontSize: 11, fontWeight: '700' },
|
|
916
|
+
agentFallback: { width: 28, height: 28, borderRadius: 14, backgroundColor: p.surfaceAlt, alignItems: 'center', justifyContent: 'center' },
|
|
917
|
+
agentFallbackText: { color: p.muted, fontSize: 12 },
|
|
918
|
+
inlineDismiss: { position: 'absolute', zIndex: 23, top: 0, left: -12, right: -12, bottom: -1000 },
|
|
919
|
+
optionsPopover: { position: 'absolute', zIndex: 40, top: 52, left: 46, width: 230, padding: 7, borderWidth: 1, borderColor: p.border, borderRadius: 14, backgroundColor: p.surface, ...shadow },
|
|
920
|
+
popoverRow: { minHeight: 43, flexDirection: 'row', alignItems: 'center', paddingHorizontal: 9, borderRadius: 9 },
|
|
921
|
+
popoverIcon: { width: 26, color: p.text, fontSize: 16 },
|
|
922
|
+
popoverText: { flex: 1, color: p.text, fontSize: 13 },
|
|
923
|
+
popoverChevron: { color: p.muted, fontSize: 20 },
|
|
924
|
+
switchTrack: { width: 34, height: 20, borderRadius: 10, padding: 2, backgroundColor: p.dark ? '#3D434B' : '#D7DBE1' },
|
|
925
|
+
switchTrackActive: { backgroundColor: '#24935B' },
|
|
926
|
+
switchThumb: { width: 16, height: 16, borderRadius: 8, backgroundColor: '#FFFFFF' },
|
|
927
|
+
switchThumbActive: { transform: [{ translateX: 14 }] },
|
|
928
|
+
|
|
929
|
+
chatContent: { flex: 1, minHeight: 0, backgroundColor: p.background },
|
|
930
|
+
contactStageScroll: { flex: 1 },
|
|
931
|
+
contactStage: { paddingHorizontal: 16, paddingTop: 18, paddingBottom: 24 },
|
|
932
|
+
contactCard: { padding: 18, borderWidth: 1, borderColor: p.border, borderRadius: 18, backgroundColor: p.surface, ...shadow },
|
|
933
|
+
contactTitle: { color: p.text, fontSize: 14, lineHeight: 20, marginBottom: 13 },
|
|
934
|
+
label: { color: p.text, fontSize: 12, lineHeight: 16, marginBottom: 5, marginTop: 9 },
|
|
935
|
+
input: { minHeight: 42, borderWidth: 1, borderColor: p.dark ? '#555B64' : '#B7BDC7', borderRadius: 8, backgroundColor: p.surface, color: p.text, paddingHorizontal: 11, paddingVertical: 9, fontSize: 13 },
|
|
936
|
+
inlineError: { color: '#B42318', fontSize: 11, lineHeight: 15, marginTop: 8 },
|
|
937
|
+
primaryButton: { minHeight: 40, borderRadius: 9, backgroundColor: p.primary, paddingHorizontal: 14, alignItems: 'center', justifyContent: 'center', marginTop: 12 },
|
|
938
|
+
primaryButtonCompact: { flex: 1, minHeight: 40, borderRadius: 9, backgroundColor: p.primary, paddingHorizontal: 14, alignItems: 'center', justifyContent: 'center' },
|
|
939
|
+
primaryButtonText: { color: '#FFFFFF', fontSize: 13, fontWeight: '700', textAlign: 'center' },
|
|
940
|
+
secondaryButton: { flex: 1, minHeight: 40, borderWidth: 1, borderColor: p.dark ? '#555B64' : '#C7CCD4', borderRadius: 9, backgroundColor: p.surface, paddingHorizontal: 14, alignItems: 'center', justifyContent: 'center' },
|
|
941
|
+
secondaryButtonText: { color: p.text, fontSize: 13, fontWeight: '700', textAlign: 'center' },
|
|
942
|
+
dangerButton: { flex: 1, minHeight: 40, borderRadius: 9, backgroundColor: p.danger, paddingHorizontal: 14, alignItems: 'center', justifyContent: 'center' },
|
|
478
943
|
disabled: { opacity: 0.55 },
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
bubble: { maxWidth: '100%', borderRadius: 16,
|
|
944
|
+
|
|
945
|
+
messageList: { flex: 1, minHeight: 0 },
|
|
946
|
+
messageListContent: { paddingHorizontal: 16, paddingTop: 18, paddingBottom: 12, flexGrow: 1 },
|
|
947
|
+
historyLoader: { minHeight: 34, alignItems: 'center', justifyContent: 'center', paddingBottom: 10 },
|
|
948
|
+
emptyState: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 30, paddingVertical: 20 },
|
|
949
|
+
emptyText: { maxWidth: 260, paddingHorizontal: 16, paddingVertical: 14, borderRadius: 15, backgroundColor: p.dark ? p.surface : 'rgba(255,255,255,0.84)', color: p.muted, fontSize: 13, lineHeight: 19, textAlign: 'center' },
|
|
950
|
+
messageRow: { width: '100%', marginBottom: 11, flexDirection: 'row' },
|
|
951
|
+
messageRowLeft: { justifyContent: 'flex-start', paddingRight: 52 },
|
|
952
|
+
messageRowRight: { justifyContent: 'flex-end', paddingLeft: 52 },
|
|
953
|
+
bubble: { maxWidth: '100%', paddingHorizontal: 12, paddingTop: 10, paddingBottom: 8, borderRadius: 15, shadowColor: '#172236', shadowOffset: { width: 0, height: 4 }, shadowOpacity: p.dark ? 0.16 : 0.07, shadowRadius: 7, elevation: 2 },
|
|
489
954
|
customerBubble: { backgroundColor: p.customerBubble, borderBottomRightRadius: 5 },
|
|
490
|
-
agentBubble: { backgroundColor: p.agentBubble, borderBottomLeftRadius: 5
|
|
955
|
+
agentBubble: { backgroundColor: p.agentBubble, borderBottomLeftRadius: 5 },
|
|
491
956
|
customerText: { color: p.customerText },
|
|
492
957
|
agentText: { color: p.agentText },
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
958
|
+
messageText: { fontSize: 14, lineHeight: 20 },
|
|
959
|
+
messageMeta: { minHeight: 13, marginTop: 5, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 6 },
|
|
960
|
+
messageSender: { flex: 1, fontSize: 10, lineHeight: 12, fontWeight: '600', opacity: 0.62 },
|
|
961
|
+
timeText: { fontSize: 10, lineHeight: 12, opacity: 0.62 },
|
|
496
962
|
systemRow: { alignSelf: 'center', paddingHorizontal: 16, paddingVertical: 7, marginVertical: 3 },
|
|
497
|
-
systemText: { color: p.muted, fontSize: 11, textAlign: 'center' },
|
|
498
|
-
attachmentImage: { width:
|
|
499
|
-
fileAttachment: { paddingVertical:
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
963
|
+
systemText: { color: p.muted, fontSize: 11, lineHeight: 15, textAlign: 'center' },
|
|
964
|
+
attachmentImage: { width: 220, maxWidth: '100%', height: 180, borderRadius: 10, marginBottom: 4, backgroundColor: p.surfaceAlt },
|
|
965
|
+
fileAttachment: { paddingVertical: 4 },
|
|
966
|
+
fileAttachmentText: { fontSize: 13, fontWeight: '600' },
|
|
967
|
+
typingRow: { flex: 0, paddingHorizontal: 18, paddingTop: 8, paddingBottom: 7 },
|
|
968
|
+
typingText: { color: p.muted, fontSize: 11, lineHeight: 15 },
|
|
969
|
+
|
|
970
|
+
closedStage: { flex: 0, paddingHorizontal: 12, paddingTop: 10, paddingBottom: 12, borderTopWidth: 1, borderTopColor: p.border, backgroundColor: p.background },
|
|
971
|
+
closedText: { color: p.muted, fontSize: 11, lineHeight: 15, textAlign: 'center' },
|
|
972
|
+
composerArea: { position: 'relative', flex: 0, paddingHorizontal: 12, paddingTop: 10, paddingBottom: 12, backgroundColor: p.background },
|
|
973
|
+
composer: { minHeight: 54, flexDirection: 'row', alignItems: 'flex-end', gap: 8, paddingLeft: 13, paddingRight: 7, paddingVertical: 7, borderWidth: 1, borderColor: p.border, borderRadius: 18, backgroundColor: p.surface, shadowColor: '#172236', shadowOffset: { width: 0, height: 8 }, shadowOpacity: p.dark ? 0.22 : 0.10, shadowRadius: 12, elevation: 4 },
|
|
974
|
+
composerButton: { width: 34, height: 38, borderRadius: 19, backgroundColor: 'transparent', alignItems: 'center', justifyContent: 'center' },
|
|
975
|
+
composerPlus: { color: p.text, fontSize: 25, lineHeight: 27, fontWeight: '300', marginTop: -2 },
|
|
976
|
+
composerEmoji: { color: p.text, fontSize: 23, lineHeight: 25 },
|
|
977
|
+
composerInput: { flex: 1, minWidth: 0, minHeight: 38, maxHeight: 110, backgroundColor: 'transparent', color: p.text, paddingHorizontal: 0, paddingTop: 9, paddingBottom: 7, fontSize: 14, lineHeight: 19, textAlignVertical: 'top' },
|
|
978
|
+
sendButton: { width: 38, height: 38, borderRadius: 19, backgroundColor: p.primary, alignItems: 'center', justifyContent: 'center' },
|
|
979
|
+
sendIcon: { color: '#FFFFFF', fontSize: 17, lineHeight: 18, transform: [{ rotate: '-3deg' }] },
|
|
980
|
+
aiCompliance: { color: p.muted, fontSize: 9, lineHeight: 13, textAlign: 'center', paddingHorizontal: 5, paddingBottom: 8 },
|
|
981
|
+
attachmentMenu: { position: 'absolute', zIndex: 12, left: 12, bottom: 72, width: 210, padding: 7, borderWidth: 1, borderColor: p.border, borderRadius: 14, backgroundColor: p.surface, ...shadow },
|
|
982
|
+
attachmentAction: { minHeight: 40, flexDirection: 'row', alignItems: 'center', paddingHorizontal: 10, borderRadius: 9 },
|
|
983
|
+
attachmentActionIcon: { width: 26, color: p.text, fontSize: 15 },
|
|
984
|
+
attachmentActionText: { flex: 1, color: p.text, fontSize: 13 },
|
|
985
|
+
emojiPicker: { position: 'absolute', zIndex: 12, left: 12, right: 12, bottom: 72, flexDirection: 'row', flexWrap: 'wrap', gap: 5, padding: 10, borderWidth: 1, borderColor: p.border, borderRadius: 16, backgroundColor: p.surface, ...shadow },
|
|
986
|
+
emojiButton: { width: '11%', aspectRatio: 1, minHeight: 34, borderRadius: 8, alignItems: 'center', justifyContent: 'center' },
|
|
987
|
+
emojiText: { fontSize: 20, lineHeight: 22 },
|
|
988
|
+
|
|
989
|
+
poweredBy: { flex: 0, minHeight: 17, alignItems: 'center', justifyContent: 'center', paddingBottom: 5, backgroundColor: p.background },
|
|
990
|
+
poweredByText: { color: '#9398A2', fontSize: 9, lineHeight: 11, textAlign: 'center' },
|
|
991
|
+
poweredByBrand: { color: p.text, fontWeight: '700' },
|
|
992
|
+
|
|
993
|
+
modalBackdrop: { flex: 1, backgroundColor: 'rgba(16,20,27,0.55)', justifyContent: 'flex-end', padding: 16 },
|
|
994
|
+
modalBackdropTop: { flex: 1, backgroundColor: 'rgba(16,20,27,0.55)', justifyContent: 'flex-start', padding: 16 },
|
|
995
|
+
modalCard: { width: '100%', maxHeight: '82%', marginTop: 34, paddingHorizontal: 16, paddingTop: 24, paddingBottom: 16, borderRadius: 14, backgroundColor: p.surface, ...shadow },
|
|
996
|
+
modalIcon: { width: 48, height: 48, borderRadius: 24, alignSelf: 'center', alignItems: 'center', justifyContent: 'center', backgroundColor: p.surfaceAlt },
|
|
997
|
+
modalIconText: { color: p.text, fontSize: 22 },
|
|
998
|
+
modalText: { marginHorizontal: 4, marginVertical: 16, color: p.text, fontSize: 14, lineHeight: 20, textAlign: 'center' },
|
|
999
|
+
modalTitle: { color: p.text, fontSize: 16, fontWeight: '700', textAlign: 'center' },
|
|
1000
|
+
modalActions: { flexDirection: 'row', gap: 8, marginTop: 12 },
|
|
1001
|
+
modalStatus: { color: p.muted, fontSize: 11, lineHeight: 15, marginTop: 9, textAlign: 'center' },
|
|
1002
|
+
sheet: { maxHeight: '72%', borderRadius: 18, paddingHorizontal: 14, paddingTop: 14, paddingBottom: 8, backgroundColor: p.surface, ...shadow },
|
|
1003
|
+
sheetTitle: { color: p.text, fontSize: 16, fontWeight: '700', marginBottom: 8 },
|
|
1004
|
+
languageList: { maxHeight: 360 },
|
|
1005
|
+
languageRow: { minHeight: 46, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 8, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: p.border },
|
|
1006
|
+
optionText: { flex: 1, color: p.text, fontSize: 14 },
|
|
527
1007
|
optionValue: { color: p.muted, fontSize: 12, marginLeft: 12 },
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
ratingButton: { width: 66, height: 54, borderRadius: 14, backgroundColor: p.surfaceAlt, alignItems: 'center', justifyContent: 'center' },
|
|
534
|
-
ratingIcon: { fontSize: 27 },
|
|
535
|
-
commentInput: { minHeight: 82, textAlignVertical: 'top', marginTop: 10 },
|
|
1008
|
+
ratingLabel: { color: p.muted, fontSize: 11, lineHeight: 15, textAlign: 'center', marginTop: 3 },
|
|
1009
|
+
ratingButtons: { flexDirection: 'row', gap: 12, justifyContent: 'center', marginVertical: 12 },
|
|
1010
|
+
ratingButton: { width: 60, height: 48, borderWidth: 1, borderColor: p.border, borderRadius: 12, backgroundColor: p.surfaceAlt, alignItems: 'center', justifyContent: 'center' },
|
|
1011
|
+
ratingIcon: { fontSize: 24 },
|
|
1012
|
+
commentInput: { minHeight: 72, textAlignVertical: 'top', marginTop: 6 },
|
|
536
1013
|
});
|
|
537
1014
|
}
|
|
538
1015
|
|