react-native-srschat 0.1.27 → 0.1.29
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 +215 -0
- package/lib/commonjs/assets/chat-icon-mobile.svg +1 -0
- package/lib/commonjs/assets/posiden.svg +51 -0
- package/lib/commonjs/components/PoseidonLogo.js +127 -0
- package/lib/commonjs/components/PoseidonLogo.js.map +1 -0
- package/lib/commonjs/components/header.js +6 -6
- package/lib/commonjs/components/header.js.map +1 -1
- package/lib/commonjs/components/input.js +20 -1
- package/lib/commonjs/components/input.js.map +1 -1
- package/lib/commonjs/components/productCard.js +164 -37
- package/lib/commonjs/components/productCard.js.map +1 -1
- package/lib/commonjs/components/voice.js +88 -17
- package/lib/commonjs/components/voice.js.map +1 -1
- package/lib/commonjs/components/welcomeInput.js +20 -1
- package/lib/commonjs/components/welcomeInput.js.map +1 -1
- package/lib/commonjs/layout/icon.js +11 -7
- package/lib/commonjs/layout/icon.js.map +1 -1
- package/lib/commonjs/layout/layout.js +10 -6
- package/lib/commonjs/layout/layout.js.map +1 -1
- package/lib/commonjs/layout/welcome.js +5 -6
- package/lib/commonjs/layout/welcome.js.map +1 -1
- package/lib/commonjs/layout/window.js +2 -1
- package/lib/commonjs/layout/window.js.map +1 -1
- package/lib/commonjs/utils/audioRecorder.js +58 -7
- package/lib/commonjs/utils/audioRecorder.js.map +1 -1
- package/lib/module/assets/chat-icon-mobile.svg +1 -0
- package/lib/module/assets/posiden.svg +51 -0
- package/lib/module/components/PoseidonLogo.js +117 -0
- package/lib/module/components/PoseidonLogo.js.map +1 -0
- package/lib/module/components/header.js +6 -6
- package/lib/module/components/header.js.map +1 -1
- package/lib/module/components/input.js +20 -1
- package/lib/module/components/input.js.map +1 -1
- package/lib/module/components/productCard.js +165 -38
- package/lib/module/components/productCard.js.map +1 -1
- package/lib/module/components/voice.js +90 -19
- package/lib/module/components/voice.js.map +1 -1
- package/lib/module/components/welcomeInput.js +20 -1
- package/lib/module/components/welcomeInput.js.map +1 -1
- package/lib/module/layout/icon.js +11 -7
- package/lib/module/layout/icon.js.map +1 -1
- package/lib/module/layout/layout.js +10 -6
- package/lib/module/layout/layout.js.map +1 -1
- package/lib/module/layout/welcome.js +5 -6
- package/lib/module/layout/welcome.js.map +1 -1
- package/lib/module/layout/window.js +2 -1
- package/lib/module/layout/window.js.map +1 -1
- package/lib/module/utils/audioRecorder.js +56 -7
- package/lib/module/utils/audioRecorder.js.map +1 -1
- package/lib/typescript/components/PoseidonLogo.d.ts +7 -0
- package/lib/typescript/components/PoseidonLogo.d.ts.map +1 -0
- package/lib/typescript/components/header.d.ts.map +1 -1
- package/lib/typescript/components/input.d.ts.map +1 -1
- package/lib/typescript/components/productCard.d.ts.map +1 -1
- package/lib/typescript/components/voice.d.ts.map +1 -1
- package/lib/typescript/components/welcomeInput.d.ts.map +1 -1
- package/lib/typescript/layout/icon.d.ts.map +1 -1
- package/lib/typescript/layout/layout.d.ts.map +1 -1
- package/lib/typescript/layout/welcome.d.ts.map +1 -1
- package/lib/typescript/utils/audioRecorder.d.ts +2 -0
- package/lib/typescript/utils/audioRecorder.d.ts.map +1 -1
- package/package.json +5 -3
- package/src/assets/chat-icon-mobile.svg +1 -0
- package/src/assets/posiden.svg +51 -0
- package/src/components/PoseidonLogo.js +55 -0
- package/src/components/header.js +3 -2
- package/src/components/input.js +20 -1
- package/src/components/productCard.js +205 -60
- package/src/components/voice.js +107 -23
- package/src/components/welcomeInput.js +20 -1
- package/src/layout/icon.js +5 -3
- package/src/layout/layout.js +12 -8
- package/src/layout/welcome.js +2 -5
- package/src/layout/window.js +2 -1
- package/src/utils/audioRecorder.js +60 -13
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState, useContext, useEffect } from "react";
|
|
2
|
-
import { View, Text, Image, TouchableOpacity, TextInput, StyleSheet, Platform, Keyboard } from "react-native";
|
|
2
|
+
import { View, Text, Image, TouchableOpacity, TextInput, StyleSheet, Platform, Keyboard, ActionSheetIOS } from "react-native";
|
|
3
3
|
import { AppContext } from "../contexts/AppContext";
|
|
4
4
|
import Ionicons from 'react-native-vector-icons/Ionicons';
|
|
5
5
|
|
|
@@ -24,7 +24,8 @@ export const ProductCard = ({ prod, onFocusQuantityInput }) => {
|
|
|
24
24
|
const grossPrice = uomInfo.gross_price || 0;
|
|
25
25
|
const netPrice = uomInfo.net_price || 0;
|
|
26
26
|
const isOnSale = uomInfo.is_on_sale || false;
|
|
27
|
-
const discounts = uomInfo.discounts || []
|
|
27
|
+
const discounts = uomInfo.discounts || [];
|
|
28
|
+
|
|
28
29
|
|
|
29
30
|
const maxQuantity = Math.floor(prod.inventory_info?.info_by_uom?.EA?.quantity_available || 0);
|
|
30
31
|
const valid = prod.inventory_info?.is_valid || false;
|
|
@@ -64,9 +65,21 @@ export const ProductCard = ({ prod, onFocusQuantityInput }) => {
|
|
|
64
65
|
}
|
|
65
66
|
};
|
|
66
67
|
|
|
68
|
+
const incrementQuantity = () => {
|
|
69
|
+
if (quantity < maxQuantity) {
|
|
70
|
+
setQuantity(prevQuantity => prevQuantity + 1);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const decrementQuantity = () => {
|
|
75
|
+
if (quantity > 0) {
|
|
76
|
+
setQuantity(prevQuantity => prevQuantity - 1);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
67
80
|
const handleAddToCart = () => {
|
|
68
81
|
onAddToCartClick({
|
|
69
|
-
selectedUom:
|
|
82
|
+
selectedUom: selectedUom,
|
|
70
83
|
quantity: quantity,
|
|
71
84
|
product: prod
|
|
72
85
|
});
|
|
@@ -81,6 +94,27 @@ export const ProductCard = ({ prod, onFocusQuantityInput }) => {
|
|
|
81
94
|
onProductCardClick(prodWithSku);
|
|
82
95
|
};
|
|
83
96
|
|
|
97
|
+
const handleUomChange = (value) => {
|
|
98
|
+
setSelectedUom(value);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const openUomPicker = () => {
|
|
102
|
+
if (Platform.OS === 'ios') {
|
|
103
|
+
ActionSheetIOS.showActionSheetWithOptions(
|
|
104
|
+
{
|
|
105
|
+
options: [...uoms, 'Cancel'],
|
|
106
|
+
cancelButtonIndex: uoms.length,
|
|
107
|
+
title: 'Select Unit',
|
|
108
|
+
},
|
|
109
|
+
(buttonIndex) => {
|
|
110
|
+
if (buttonIndex !== uoms.length) {
|
|
111
|
+
handleUomChange(uoms[buttonIndex]);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
84
118
|
return (
|
|
85
119
|
<View style={styles.card}>
|
|
86
120
|
{/* Two-column layout */}
|
|
@@ -88,6 +122,11 @@ export const ProductCard = ({ prod, onFocusQuantityInput }) => {
|
|
|
88
122
|
{/* Left Column (Image, Price, Availability) */}
|
|
89
123
|
<View style={styles.leftColumn}>
|
|
90
124
|
<TouchableOpacity onPress={handleProductClick}>
|
|
125
|
+
{isOnSale && (
|
|
126
|
+
<View style={styles.saleTag}>
|
|
127
|
+
<Text style={styles.saleTagText}>SALE</Text>
|
|
128
|
+
</View>
|
|
129
|
+
)}
|
|
91
130
|
<Image source={{ uri: prod.product_details.image_url }} style={styles.image} />
|
|
92
131
|
</TouchableOpacity>
|
|
93
132
|
{valid &&
|
|
@@ -123,43 +162,89 @@ export const ProductCard = ({ prod, onFocusQuantityInput }) => {
|
|
|
123
162
|
<Text style={styles.boldText}>Part # </Text>{prod.part_number}
|
|
124
163
|
</Text>
|
|
125
164
|
|
|
126
|
-
{/*
|
|
127
|
-
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
{
|
|
135
|
-
height: 40,
|
|
136
|
-
minHeight: 40,
|
|
137
|
-
maxHeight: 40
|
|
138
|
-
}
|
|
139
|
-
]}
|
|
140
|
-
editable={valid && inStock}
|
|
141
|
-
underlineColorAndroid="transparent"
|
|
142
|
-
autoCorrect={false}
|
|
143
|
-
autoCapitalize="none"
|
|
144
|
-
caretHidden={false}
|
|
145
|
-
contextMenuHidden={true}
|
|
146
|
-
disableFullscreenUI={true}
|
|
147
|
-
defaultValue="1"
|
|
148
|
-
/>
|
|
165
|
+
{/* Display discounts if available */}
|
|
166
|
+
{discounts.length > 0 && (
|
|
167
|
+
<View style={styles.discountContainer}>
|
|
168
|
+
{discounts.map((discount, index) => (
|
|
169
|
+
<Text key={index} style={styles.discountText}>{discount}</Text>
|
|
170
|
+
))}
|
|
171
|
+
</View>
|
|
172
|
+
)}
|
|
149
173
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
style={
|
|
154
|
-
styles.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
174
|
+
{/* Input Section with UOM Selector and Quantity */}
|
|
175
|
+
<View style={styles.inputRow}>
|
|
176
|
+
{uoms.length > 1 ? (
|
|
177
|
+
<View style={styles.uomSelectorContainer}>
|
|
178
|
+
<Text style={styles.inputLabel}>Unit</Text>
|
|
179
|
+
<TouchableOpacity
|
|
180
|
+
style={styles.dropdownButton}
|
|
181
|
+
onPress={openUomPicker}
|
|
182
|
+
disabled={!valid}
|
|
183
|
+
>
|
|
184
|
+
<Text style={styles.dropdownButtonText}>{selectedUom}</Text>
|
|
185
|
+
<Ionicons name="chevron-down" size={16} color="rgba(0, 0, 0, 0.6)" />
|
|
186
|
+
</TouchableOpacity>
|
|
187
|
+
</View>
|
|
188
|
+
) : null}
|
|
189
|
+
|
|
190
|
+
<View style={styles.quantityContainer}>
|
|
191
|
+
<Text style={styles.inputLabel}>Qty</Text>
|
|
192
|
+
<View style={styles.quantityInputContainer}>
|
|
193
|
+
<TouchableOpacity
|
|
194
|
+
style={styles.quantityButton}
|
|
195
|
+
onPress={decrementQuantity}
|
|
196
|
+
disabled={!valid || quantity <= 0}
|
|
197
|
+
>
|
|
198
|
+
<Ionicons
|
|
199
|
+
name="remove"
|
|
200
|
+
size={18}
|
|
201
|
+
color={valid && quantity > 0 ? "#367cb6" : "rgba(0, 0, 0, 0.23)"}
|
|
202
|
+
/>
|
|
203
|
+
</TouchableOpacity>
|
|
204
|
+
|
|
205
|
+
<TextInput
|
|
206
|
+
value={quantity.toString()}
|
|
207
|
+
onChangeText={handleQuantityChange}
|
|
208
|
+
keyboardType="numeric"
|
|
209
|
+
style={styles.quantityInput}
|
|
210
|
+
editable={valid && inStock}
|
|
211
|
+
underlineColorAndroid="transparent"
|
|
212
|
+
autoCorrect={false}
|
|
213
|
+
autoCapitalize="none"
|
|
214
|
+
caretHidden={false}
|
|
215
|
+
contextMenuHidden={true}
|
|
216
|
+
disableFullscreenUI={true}
|
|
217
|
+
defaultValue="1"
|
|
218
|
+
/>
|
|
219
|
+
|
|
220
|
+
<TouchableOpacity
|
|
221
|
+
style={styles.quantityButton}
|
|
222
|
+
onPress={incrementQuantity}
|
|
223
|
+
disabled={!valid || quantity >= maxQuantity}
|
|
224
|
+
>
|
|
225
|
+
<Ionicons
|
|
226
|
+
name="add"
|
|
227
|
+
size={18}
|
|
228
|
+
color={valid && quantity < maxQuantity ? "#367cb6" : "rgba(0, 0, 0, 0.23)"}
|
|
229
|
+
/>
|
|
230
|
+
</TouchableOpacity>
|
|
231
|
+
</View>
|
|
232
|
+
</View>
|
|
162
233
|
</View>
|
|
234
|
+
|
|
235
|
+
{/* Add to Cart Button on a new line */}
|
|
236
|
+
<TouchableOpacity
|
|
237
|
+
onPress={handleAddToCart}
|
|
238
|
+
disabled={!valid || !inStock || quantity === 0}
|
|
239
|
+
style={[
|
|
240
|
+
styles.addToCartButton,
|
|
241
|
+
valid && inStock && quantity > 0 ? styles.activeButton : styles.disabledButton
|
|
242
|
+
]}
|
|
243
|
+
>
|
|
244
|
+
<Text style={[styles.buttonText, valid && inStock && quantity > 0 ? styles.activeText : styles.disabledText]}>
|
|
245
|
+
Add to Cart
|
|
246
|
+
</Text>
|
|
247
|
+
</TouchableOpacity>
|
|
163
248
|
</View>
|
|
164
249
|
</View>
|
|
165
250
|
</View>
|
|
@@ -170,7 +255,6 @@ const styles = StyleSheet.create({
|
|
|
170
255
|
card: {
|
|
171
256
|
backgroundColor: "#fff",
|
|
172
257
|
width: '100%',
|
|
173
|
-
padding: 10,
|
|
174
258
|
padding: 12,
|
|
175
259
|
paddingHorizontal: 16,
|
|
176
260
|
borderRadius: 12,
|
|
@@ -183,6 +267,7 @@ const styles = StyleSheet.create({
|
|
|
183
267
|
leftColumn: {
|
|
184
268
|
width: "25%",
|
|
185
269
|
alignItems: "center",
|
|
270
|
+
position: "relative",
|
|
186
271
|
},
|
|
187
272
|
rightColumn: {
|
|
188
273
|
width: "75%",
|
|
@@ -194,24 +279,31 @@ const styles = StyleSheet.create({
|
|
|
194
279
|
resizeMode: "contain",
|
|
195
280
|
marginBottom: 5,
|
|
196
281
|
},
|
|
282
|
+
saleTag: {
|
|
283
|
+
position: "absolute",
|
|
284
|
+
top: 0,
|
|
285
|
+
left: 0,
|
|
286
|
+
backgroundColor: "red",
|
|
287
|
+
borderRadius: 4,
|
|
288
|
+
paddingVertical: 2,
|
|
289
|
+
paddingHorizontal: 5,
|
|
290
|
+
zIndex: 1,
|
|
291
|
+
},
|
|
292
|
+
saleTagText: {
|
|
293
|
+
color: "#fff",
|
|
294
|
+
fontSize: 10,
|
|
295
|
+
fontWeight: "bold",
|
|
296
|
+
},
|
|
197
297
|
price: {
|
|
198
298
|
fontSize: 14,
|
|
199
299
|
fontWeight: "bold",
|
|
200
300
|
textAlign: "center",
|
|
201
301
|
},
|
|
202
|
-
saleTag: {
|
|
203
|
-
backgroundColor: "#ff4757",
|
|
204
|
-
color: "#fff",
|
|
205
|
-
fontSize: 12,
|
|
206
|
-
fontWeight: "bold",
|
|
207
|
-
paddingVertical: 2,
|
|
208
|
-
paddingHorizontal: 5,
|
|
209
|
-
borderRadius: 4,
|
|
210
|
-
marginBottom: 5,
|
|
211
|
-
},
|
|
212
302
|
priceContainer: {
|
|
213
303
|
flexDirection: "row",
|
|
214
304
|
alignItems: "center",
|
|
305
|
+
flexWrap: "wrap",
|
|
306
|
+
justifyContent: "center",
|
|
215
307
|
},
|
|
216
308
|
originalPrice: {
|
|
217
309
|
fontSize: 14,
|
|
@@ -222,7 +314,7 @@ const styles = StyleSheet.create({
|
|
|
222
314
|
salePrice: {
|
|
223
315
|
fontSize: 16,
|
|
224
316
|
fontWeight: "bold",
|
|
225
|
-
color: "
|
|
317
|
+
color: "red",
|
|
226
318
|
},
|
|
227
319
|
perUnit: {
|
|
228
320
|
fontSize: 12,
|
|
@@ -250,29 +342,82 @@ const styles = StyleSheet.create({
|
|
|
250
342
|
boldText: {
|
|
251
343
|
fontWeight: "bold",
|
|
252
344
|
},
|
|
253
|
-
|
|
345
|
+
discountContainer: {
|
|
346
|
+
marginTop: 5,
|
|
347
|
+
marginBottom: 5,
|
|
348
|
+
},
|
|
349
|
+
discountText: {
|
|
350
|
+
fontSize: 13,
|
|
351
|
+
color: "#e41e31",
|
|
352
|
+
fontWeight: "500",
|
|
353
|
+
// backgroundColor: "#f0f7ff",
|
|
354
|
+
// borderWidth: 1,
|
|
355
|
+
// borderColor: "#bae0ff",
|
|
356
|
+
// borderRadius: 4,
|
|
357
|
+
// padding: 3,
|
|
358
|
+
marginTop: 3,
|
|
359
|
+
},
|
|
360
|
+
inputRow: {
|
|
254
361
|
flexDirection: "row",
|
|
255
|
-
alignItems: "
|
|
362
|
+
alignItems: "flex-end",
|
|
256
363
|
marginTop: 10,
|
|
364
|
+
marginBottom: 10,
|
|
257
365
|
},
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
366
|
+
uomSelectorContainer: {
|
|
367
|
+
marginRight: 10,
|
|
368
|
+
},
|
|
369
|
+
inputLabel: {
|
|
370
|
+
fontSize: 12,
|
|
371
|
+
color: "rgba(0, 0, 0, 0.6)",
|
|
372
|
+
marginBottom: 4,
|
|
373
|
+
},
|
|
374
|
+
dropdownButton: {
|
|
375
|
+
flexDirection: 'row',
|
|
376
|
+
justifyContent: 'space-between',
|
|
377
|
+
alignItems: 'center',
|
|
261
378
|
borderWidth: 1,
|
|
262
|
-
borderColor:
|
|
379
|
+
borderColor: 'rgba(0, 0, 0, 0.23)',
|
|
263
380
|
borderRadius: 4,
|
|
264
381
|
paddingHorizontal: 10,
|
|
382
|
+
height: 40,
|
|
383
|
+
minWidth: 80,
|
|
384
|
+
},
|
|
385
|
+
dropdownButtonText: {
|
|
386
|
+
fontSize: 14,
|
|
387
|
+
color: '#555',
|
|
388
|
+
marginRight: 8,
|
|
389
|
+
},
|
|
390
|
+
quantityContainer: {
|
|
391
|
+
flex: 1,
|
|
392
|
+
},
|
|
393
|
+
quantityInputContainer: {
|
|
394
|
+
flexDirection: "row",
|
|
395
|
+
alignItems: "center",
|
|
396
|
+
height: 40,
|
|
397
|
+
borderWidth: 1,
|
|
398
|
+
borderColor: "rgba(0, 0, 0, 0.23)",
|
|
399
|
+
borderRadius: 4,
|
|
400
|
+
},
|
|
401
|
+
quantityButton: {
|
|
402
|
+
width: 40,
|
|
403
|
+
height: 40,
|
|
404
|
+
justifyContent: "center",
|
|
405
|
+
alignItems: "center",
|
|
406
|
+
},
|
|
407
|
+
quantityInput: {
|
|
408
|
+
flex: 1,
|
|
409
|
+
height: 40,
|
|
265
410
|
textAlign: "center",
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
411
|
+
paddingHorizontal: 5,
|
|
412
|
+
fontSize: 14,
|
|
413
|
+
minWidth: 40,
|
|
269
414
|
},
|
|
270
415
|
addToCartButton: {
|
|
271
|
-
flex: 1,
|
|
272
416
|
height: 40,
|
|
273
417
|
justifyContent: "center",
|
|
274
418
|
alignItems: "center",
|
|
275
419
|
borderRadius: 4,
|
|
420
|
+
marginTop: 5,
|
|
276
421
|
},
|
|
277
422
|
activeButton: {
|
|
278
423
|
backgroundColor: "#367cb6",
|
package/src/components/voice.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// VoiceButton.js
|
|
2
2
|
|
|
3
3
|
import React, { useState, useContext, useEffect } from 'react';
|
|
4
|
-
import { TouchableOpacity, ActivityIndicator, StyleSheet, Alert } from 'react-native';
|
|
4
|
+
import { TouchableOpacity, ActivityIndicator, StyleSheet, Alert, Linking, Platform } from 'react-native';
|
|
5
5
|
import Ionicons from 'react-native-vector-icons/Ionicons';
|
|
6
|
+
import useAsyncStorage from '../hooks/useAsyncStorage';
|
|
6
7
|
|
|
7
8
|
import {
|
|
8
9
|
startRecording,
|
|
@@ -10,60 +11,129 @@ import {
|
|
|
10
11
|
cancelRecording,
|
|
11
12
|
requestAudioPermission,
|
|
12
13
|
cleanup,
|
|
13
|
-
initVoice
|
|
14
|
+
initVoice,
|
|
15
|
+
resetStoredPermission,
|
|
16
|
+
setPermissionStatusHandlers
|
|
14
17
|
} from '../utils/audioRecorder';
|
|
15
18
|
import { AppContext } from '../contexts/AppContext';
|
|
16
19
|
|
|
20
|
+
const PERMISSION_STORAGE_KEY = '@voice_permission_status';
|
|
21
|
+
|
|
17
22
|
export const VoiceButton = () => {
|
|
18
23
|
const { handleVoiceSend } = useContext(AppContext);
|
|
19
24
|
const [isListening, setIsListening] = useState(false);
|
|
20
25
|
const [loading, setLoading] = useState(false);
|
|
26
|
+
const [permissionChecked, setPermissionChecked] = useState(false);
|
|
27
|
+
const [hasPermission, setHasPermission] = useState(null);
|
|
28
|
+
|
|
29
|
+
// Use your custom AsyncStorage hook
|
|
30
|
+
const [permissionStatus, setPermissionStatus] = useAsyncStorage(PERMISSION_STORAGE_KEY, null);
|
|
21
31
|
|
|
22
32
|
useEffect(() => {
|
|
33
|
+
// Register our permission handlers
|
|
34
|
+
setPermissionStatusHandlers(() => permissionStatus, setPermissionStatus);
|
|
35
|
+
|
|
23
36
|
const setupVoice = async () => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
try {
|
|
38
|
+
// Check stored permission first
|
|
39
|
+
if (permissionStatus === 'denied') {
|
|
40
|
+
// We already know permission was denied, don't show alert again
|
|
41
|
+
setHasPermission(false);
|
|
42
|
+
setPermissionChecked(true);
|
|
29
43
|
return;
|
|
30
44
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
|
|
46
|
+
// Only request permission if not already denied
|
|
47
|
+
const permissionResult = await requestAudioPermission();
|
|
48
|
+
setHasPermission(permissionResult);
|
|
49
|
+
|
|
50
|
+
if (permissionResult) {
|
|
51
|
+
const initialized = await initVoice((result, error) => {
|
|
52
|
+
if (error) {
|
|
53
|
+
// Don't show alert for permission errors since we handle that elsewhere
|
|
54
|
+
if (!error.includes('permission')) {
|
|
55
|
+
Alert.alert('Error', error);
|
|
56
|
+
}
|
|
57
|
+
setIsListening(false);
|
|
58
|
+
setLoading(false);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (result) {
|
|
62
|
+
handleVoiceSend(null, result);
|
|
63
|
+
}
|
|
64
|
+
// Always reset states when the recognition ends
|
|
65
|
+
setIsListening(false);
|
|
66
|
+
setLoading(false);
|
|
67
|
+
});
|
|
37
68
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
69
|
+
if (!initialized) {
|
|
70
|
+
// Only show this alert once per session
|
|
71
|
+
if (!permissionChecked) {
|
|
72
|
+
Alert.alert(
|
|
73
|
+
'Error',
|
|
74
|
+
'Speech recognition is not available on this device'
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('Error in setupVoice:', error);
|
|
81
|
+
} finally {
|
|
82
|
+
setPermissionChecked(true);
|
|
43
83
|
}
|
|
44
84
|
};
|
|
45
85
|
|
|
46
|
-
|
|
86
|
+
if (!permissionChecked) {
|
|
87
|
+
setupVoice();
|
|
88
|
+
}
|
|
47
89
|
|
|
48
90
|
return () => {
|
|
49
91
|
cleanup();
|
|
50
92
|
};
|
|
51
|
-
}, []);
|
|
93
|
+
}, [permissionStatus, permissionChecked]);
|
|
52
94
|
|
|
53
95
|
const toggleRecording = async () => {
|
|
54
96
|
try {
|
|
55
97
|
if (!isListening) {
|
|
56
|
-
|
|
57
|
-
if (
|
|
58
|
-
Alert.alert(
|
|
98
|
+
// If we already know we don't have permission, show a better message
|
|
99
|
+
if (hasPermission === false) {
|
|
100
|
+
Alert.alert(
|
|
101
|
+
'Permission Required',
|
|
102
|
+
'Voice recognition requires microphone permission. Would you like to update your settings?',
|
|
103
|
+
[
|
|
104
|
+
{
|
|
105
|
+
text: 'Cancel',
|
|
106
|
+
style: 'cancel'
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
text: 'Settings',
|
|
110
|
+
onPress: () => {
|
|
111
|
+
// Reset stored permission so we can check again
|
|
112
|
+
setPermissionStatus(null);
|
|
113
|
+
// Open device settings
|
|
114
|
+
openAppSettings();
|
|
115
|
+
// Reset our permission check
|
|
116
|
+
setPermissionChecked(false);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
);
|
|
59
121
|
return;
|
|
60
122
|
}
|
|
61
123
|
|
|
62
124
|
setLoading(true);
|
|
125
|
+
const checkPermission = await requestAudioPermission();
|
|
126
|
+
if (!checkPermission) {
|
|
127
|
+
setHasPermission(false);
|
|
128
|
+
setLoading(false);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
63
132
|
const started = await startRecording();
|
|
64
133
|
if (started) {
|
|
65
134
|
setIsListening(true);
|
|
66
135
|
} else {
|
|
136
|
+
// Only show error if not permission related
|
|
67
137
|
Alert.alert('Error', 'Failed to start voice recognition');
|
|
68
138
|
}
|
|
69
139
|
} else {
|
|
@@ -78,7 +148,21 @@ export const VoiceButton = () => {
|
|
|
78
148
|
await cleanup();
|
|
79
149
|
} finally {
|
|
80
150
|
setLoading(false);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
81
153
|
|
|
154
|
+
const openAppSettings = async () => {
|
|
155
|
+
try {
|
|
156
|
+
if (Platform.OS === 'ios') {
|
|
157
|
+
// For iOS
|
|
158
|
+
await Linking.openURL('app-settings://');
|
|
159
|
+
} else {
|
|
160
|
+
// For Android
|
|
161
|
+
await Linking.openSettings();
|
|
162
|
+
}
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error('Cannot open settings', error);
|
|
165
|
+
Alert.alert('Error', 'Unable to open settings. Please open settings manually.');
|
|
82
166
|
}
|
|
83
167
|
};
|
|
84
168
|
|
|
@@ -18,6 +18,20 @@ export const WelcomeInput = ({ onProductCardClick, onAddToCartClick }) => {
|
|
|
18
18
|
|
|
19
19
|
const { data, handleSend, input, setInput, showModal, theme } = useContext(AppContext);
|
|
20
20
|
|
|
21
|
+
const handleKeyPress = ({ nativeEvent }) => {
|
|
22
|
+
if (nativeEvent.key === 'return' && !nativeEvent.shiftKey) {
|
|
23
|
+
nativeEvent.preventDefault && nativeEvent.preventDefault();
|
|
24
|
+
handleSend(input);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const onSubmitEditing = () => {
|
|
30
|
+
if (input.trim()) {
|
|
31
|
+
handleSend(input);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
21
35
|
return (
|
|
22
36
|
<View style={styles.inputContainer}>
|
|
23
37
|
<TextInput
|
|
@@ -26,7 +40,12 @@ export const WelcomeInput = ({ onProductCardClick, onAddToCartClick }) => {
|
|
|
26
40
|
onChangeText={setInput}
|
|
27
41
|
placeholder="Ask a question..."
|
|
28
42
|
placeholderTextColor="#999"
|
|
29
|
-
multiline
|
|
43
|
+
multiline={false}
|
|
44
|
+
returnKeyType="send"
|
|
45
|
+
enablesReturnKeyAutomatically={true}
|
|
46
|
+
onKeyPress={handleKeyPress}
|
|
47
|
+
onSubmitEditing={onSubmitEditing}
|
|
48
|
+
blurOnSubmit={false}
|
|
30
49
|
/>
|
|
31
50
|
<VoiceButton/>
|
|
32
51
|
<TouchableOpacity
|
package/src/layout/icon.js
CHANGED
|
@@ -2,13 +2,14 @@ import React, { useContext } from 'react';
|
|
|
2
2
|
import { TouchableOpacity, View, StyleSheet, Text } from 'react-native';
|
|
3
3
|
import { AppContext } from '../contexts/AppContext';
|
|
4
4
|
import Ionicons from 'react-native-vector-icons/Ionicons';
|
|
5
|
+
import Svg, { Path } from 'react-native-svg';
|
|
5
6
|
|
|
6
7
|
export const ChatIcon = () => {
|
|
7
8
|
const { setShowModal, messages, maintenance, disclaimer, uiConfig } = useContext(AppContext);
|
|
8
9
|
|
|
9
10
|
// Determine position from uiConfig or default
|
|
10
11
|
const iconPosition = {
|
|
11
|
-
|
|
12
|
+
top: uiConfig?.iconPosition?.top ?? 50,
|
|
12
13
|
right: uiConfig?.iconPosition?.right ?? 20,
|
|
13
14
|
};
|
|
14
15
|
|
|
@@ -36,7 +37,9 @@ export const ChatIcon = () => {
|
|
|
36
37
|
return (
|
|
37
38
|
<TouchableOpacity style={containerStyle} onPress={handleClick} activeOpacity={0.7}>
|
|
38
39
|
<View style={styles.iconContent}>
|
|
39
|
-
<
|
|
40
|
+
<Svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" fill="#ffffff" viewBox="0 0 256 256">
|
|
41
|
+
<Path d="M132,24A100.11,100.11,0,0,0,32,124v84.33A15.69,15.69,0,0,0,47.67,224H132a100,100,0,0,0,0-200Zm28,128H96a8,8,0,0,1,0-16h64a8,8,0,0,1,0,16Zm0-32H96a8,8,0,0,1,0-16h64a8,8,0,0,1,0,16Z" />
|
|
42
|
+
</Svg>
|
|
40
43
|
{iconType === 'tab' && (
|
|
41
44
|
<Text style={styles.tabText}>Chat with Heritage</Text>
|
|
42
45
|
)}
|
|
@@ -52,7 +55,6 @@ const styles = StyleSheet.create({
|
|
|
52
55
|
width: 60,
|
|
53
56
|
height: 60,
|
|
54
57
|
borderRadius: 30,
|
|
55
|
-
backgroundColor: '#FFA500',
|
|
56
58
|
alignItems: 'center',
|
|
57
59
|
justifyContent: 'center',
|
|
58
60
|
zIndex: 10,
|
package/src/layout/layout.js
CHANGED
|
@@ -64,15 +64,19 @@ export const Layout = () => {
|
|
|
64
64
|
// uiConfig.showIcon, uiConfig.toggleChat
|
|
65
65
|
|
|
66
66
|
useEffect(() => {
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
if (uiConfig.toggleChat) {
|
|
68
|
+
if (!disclaimer) {
|
|
69
|
+
setShowModal("Form");
|
|
70
|
+
} else {
|
|
71
|
+
if (messages.length > 1 || maintenance) {
|
|
72
|
+
setShowModal("ChatWindow");
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
setShowModal("Welcome")
|
|
76
|
+
}
|
|
75
77
|
}
|
|
78
|
+
} else {
|
|
79
|
+
setShowModal("Icon"); // Default to Icon/bubble state
|
|
76
80
|
}
|
|
77
81
|
}, [uiConfig.toggleChat]);
|
|
78
82
|
|
package/src/layout/welcome.js
CHANGED
|
@@ -6,6 +6,7 @@ import { AppContext } from '../contexts/AppContext';
|
|
|
6
6
|
import { WelcomeInput } from '../components/welcomeInput';
|
|
7
7
|
import ButtonComponent from '../components/welcomeButton';
|
|
8
8
|
import { Testing } from '../components/testing';
|
|
9
|
+
import { PoseidonLogo } from '../components/PoseidonLogo';
|
|
9
10
|
|
|
10
11
|
export const Welcome = ({ panHandlers }) => {
|
|
11
12
|
const { setShowModal, uiConfig, onProductCardClick, onAddToCartClick } = useContext(AppContext);
|
|
@@ -25,11 +26,7 @@ export const Welcome = ({ panHandlers }) => {
|
|
|
25
26
|
{/* Top section */}
|
|
26
27
|
<View style={styles.topContainer}>
|
|
27
28
|
<View style={styles.topHeader}>
|
|
28
|
-
<
|
|
29
|
-
source={require('../assets/heritage.png')}
|
|
30
|
-
style={[styles.logo, { tintColor: "white" }]}
|
|
31
|
-
resizeMode="contain"
|
|
32
|
-
/>
|
|
29
|
+
<PoseidonLogo width={150} height={35} color="white" />
|
|
33
30
|
<TouchableOpacity onPress={() => handleClick()} style={styles.collapseButton}>
|
|
34
31
|
<Ionicons name="chevron-down" size={24} color="white" />
|
|
35
32
|
</TouchableOpacity>
|
package/src/layout/window.js
CHANGED