@revrag-ai/embed-react-native 1.0.5 → 1.0.7
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/LICENSE +1 -1
- package/README.md +13 -382
- package/android/CMakeLists.txt +15 -0
- package/android/build.gradle +77 -25
- package/android/cpp-adapter.cpp +8 -0
- package/android/generated/java/com/revragai/embedreactnative/NativeEmbedReactNativeSpec.java +37 -0
- package/android/generated/jni/CMakeLists.txt +49 -0
- package/android/generated/jni/RNEmbedReactNativeSpec-generated.cpp +32 -0
- package/android/generated/jni/RNEmbedReactNativeSpec.h +31 -0
- package/android/generated/jni/react/renderer/components/RNEmbedReactNativeSpec/RNEmbedReactNativeSpecJSI-generated.cpp +28 -0
- package/android/generated/jni/react/renderer/components/RNEmbedReactNativeSpec/RNEmbedReactNativeSpecJSI.h +67 -0
- package/android/gradle.properties +5 -5
- package/cpp/revrag-ai-embed-react-native.cpp +7 -0
- package/cpp/revrag-ai-embed-react-native.h +8 -0
- package/ios/EmbedReactNative.h +9 -0
- package/ios/{Onwid.mm → EmbedReactNative.mm} +4 -4
- package/ios/generated/RNEmbedReactNativeSpec/RNEmbedReactNativeSpec-generated.mm +29 -0
- package/ios/generated/RNEmbedReactNativeSpec/RNEmbedReactNativeSpec.h +50 -0
- package/ios/generated/RNEmbedReactNativeSpecJSI-generated.cpp +28 -0
- package/ios/generated/RNEmbedReactNativeSpecJSI.h +67 -0
- package/lib/commonjs/NativeEmbedReactNative.js +9 -0
- package/lib/commonjs/api/api.js +256 -0
- package/lib/commonjs/api/types/embed.api.types.js +2 -0
- package/lib/commonjs/components/Embed/EmbedAudioWave.js +157 -0
- package/lib/commonjs/components/Embed/EmbedButton.js +511 -0
- package/lib/commonjs/components/Embed/EmbedVoice.js +131 -0
- package/lib/commonjs/components/styles/EmbedButton.style.js +248 -0
- package/lib/commonjs/events/embed.event.js +74 -0
- package/lib/commonjs/hooks/initialize.js +102 -0
- package/lib/commonjs/hooks/initialize.livekit.js +20 -0
- package/lib/commonjs/hooks/types/initialize.types.js +2 -0
- package/lib/commonjs/hooks/types/voiceAgent.types.js +6 -0
- package/lib/commonjs/hooks/voiceagent.js +358 -0
- package/lib/commonjs/index.js +34 -0
- package/lib/commonjs/index.types.js +22 -0
- package/lib/commonjs/store/store.key.js +46 -0
- package/lib/commonjs/utils/reanimated.helper.js +100 -0
- package/lib/module/NativeEmbedReactNative.js +5 -0
- package/lib/module/api/api.js +248 -0
- package/lib/module/api/types/embed.api.types.js +2 -0
- package/lib/module/components/Embed/EmbedAudioWave.js +152 -0
- package/lib/module/components/Embed/EmbedButton.js +506 -0
- package/lib/module/components/Embed/EmbedVoice.js +127 -0
- package/lib/module/components/styles/EmbedButton.style.js +243 -0
- package/lib/module/events/embed.event.js +70 -0
- package/lib/module/hooks/initialize.js +79 -75
- package/lib/module/hooks/{initializelivekit.js → initialize.livekit.js} +7 -4
- package/lib/module/hooks/types/initialize.types.js +2 -0
- package/lib/module/hooks/types/voiceAgent.types.js +4 -0
- package/lib/module/hooks/voiceagent.js +353 -0
- package/lib/module/index.js +6 -60
- package/lib/module/index.types.js +23 -0
- package/lib/module/store/store.key.js +38 -0
- package/lib/module/utils/reanimated.helper.js +94 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/package.json +69 -27
- package/react-native.config.js +8 -14
- package/revrag-ai-embed-react-native.podspec +41 -0
- package/Onwid.podspec +0 -20
- package/ios/Onwid.h +0 -5
- package/lib/index.d.ts +0 -77
- package/lib/module/Event/onwid.js +0 -74
- package/lib/module/NativeOnwid.js +0 -4
- package/lib/module/component/OnwidButton.js +0 -366
- package/lib/module/component/audiowave.js +0 -137
- package/lib/module/component/voice.js +0 -103
- package/lib/module/hooks/initialize.types.js +0 -2
- package/lib/module/hooks/voiceAgent.js +0 -334
- package/lib/module/hooks/voiceAgent.types.js +0 -2
- package/lib/module/onwidApi/api.js +0 -184
- package/lib/module/onwidApi/api.types.js +0 -2
- package/lib/module/store.key.js +0 -47
- package/lib/module/style/onwidButton.style.js +0 -230
- package/lib/module/utils/reanimatedHelpers.js +0 -87
- package/lib/module/utils/utils.js +0 -1
- package/lib/typescript/Event/onwid.d.ts +0 -13
- package/lib/typescript/NativeOnwid.d.ts +0 -6
- package/lib/typescript/component/OnwidButton.d.ts +0 -28
- package/lib/typescript/component/audiowave.d.ts +0 -6
- package/lib/typescript/component/voice.d.ts +0 -15
- package/lib/typescript/hooks/initialize.d.ts +0 -2
- package/lib/typescript/hooks/initialize.types.d.ts +0 -5
- package/lib/typescript/hooks/initializelivekit.d.ts +0 -3
- package/lib/typescript/hooks/voiceAgent.d.ts +0 -2
- package/lib/typescript/hooks/voiceAgent.types.d.ts +0 -16
- package/lib/typescript/index.d.ts +0 -27
- package/lib/typescript/onwidApi/api.d.ts +0 -53
- package/lib/typescript/onwidApi/api.types.d.ts +0 -21
- package/lib/typescript/store.key.d.ts +0 -3
- package/lib/typescript/style/onwidButton.style.d.ts +0 -98
- package/lib/typescript/utils/reanimatedHelpers.d.ts +0 -29
- package/lib/typescript/utils/utils.d.ts +0 -0
- package/scripts/verify-setup.js +0 -90
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file OnwidButton.tsx
|
|
5
|
+
* @description A customizable floating action button component for React Native applications.
|
|
6
|
+
* This component provides a draggable, expandable button with animation support and gradient styling.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import LottieView from 'lottie-react-native';
|
|
10
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
11
|
+
import { Dimensions, Image, Text, TouchableOpacity, View } from 'react-native';
|
|
12
|
+
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
13
|
+
import LinearGradient from 'react-native-linear-gradient';
|
|
14
|
+
import { useVoiceAgent } from "../../hooks/voiceagent.js";
|
|
15
|
+
import { getAgentData } from "../../store/store.key.js";
|
|
16
|
+
import { createEmbedButtonStyles } from "../styles/EmbedButton.style.js";
|
|
17
|
+
import { getReanimatedAPI, showReanimatedSetupError } from "../../utils/reanimated.helper.js";
|
|
18
|
+
import Voice from "./EmbedVoice.js";
|
|
19
|
+
import { WaveformVisualizer } from "./EmbedAudioWave.js";
|
|
20
|
+
|
|
21
|
+
// Get reanimated API with fallbacks
|
|
22
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
23
|
+
const {
|
|
24
|
+
useSharedValue,
|
|
25
|
+
useAnimatedStyle,
|
|
26
|
+
withTiming,
|
|
27
|
+
withSpring,
|
|
28
|
+
withRepeat,
|
|
29
|
+
withSequence,
|
|
30
|
+
runOnJS,
|
|
31
|
+
Easing,
|
|
32
|
+
Animated,
|
|
33
|
+
isAvailable: isReanimatedAvailable
|
|
34
|
+
} = getReanimatedAPI();
|
|
35
|
+
|
|
36
|
+
// Show warning if reanimated is not available
|
|
37
|
+
if (!isReanimatedAvailable) {
|
|
38
|
+
showReanimatedSetupError();
|
|
39
|
+
}
|
|
40
|
+
const {
|
|
41
|
+
width: SCREEN_WIDTH,
|
|
42
|
+
height: SCREEN_HEIGHT
|
|
43
|
+
} = Dimensions.get('window');
|
|
44
|
+
const BUTTON_WIDTH = 60;
|
|
45
|
+
const EXPANDED_WIDTH = SCREEN_WIDTH * 0.9;
|
|
46
|
+
const BUTTON_HEIGHT = 60;
|
|
47
|
+
const GRADIENT_COLORS = ['#1E0844', '#B391F3'];
|
|
48
|
+
|
|
49
|
+
// Define mic icons as base64 images for portability
|
|
50
|
+
const MIC_ON_ICON = 'https://revrag-dev.s3.ap-south-1.amazonaws.com/Avatars/Mute+button.png';
|
|
51
|
+
const MIC_OFF_ICON = 'https://revrag-dev.s3.ap-south-1.amazonaws.com/Avatars/unmute.png';
|
|
52
|
+
|
|
53
|
+
// Add end call icon
|
|
54
|
+
const END_CALL_ICON = 'https://revrag-dev.s3.ap-south-1.amazonaws.com/Avatars/end+button.png';
|
|
55
|
+
const AMPLIFY_ANIMATION = 'https://revrag-dev.s3.ap-south-1.amazonaws.com/Avatars/amplify.json';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Default styles configuration for the button
|
|
59
|
+
*/
|
|
60
|
+
const defaultStyles = {
|
|
61
|
+
buttonWidth: 60,
|
|
62
|
+
buttonHeight: 60,
|
|
63
|
+
borderRadius: 100,
|
|
64
|
+
marginBottom: 20,
|
|
65
|
+
spacing: {
|
|
66
|
+
SMALL: 10,
|
|
67
|
+
MEDIUM: 15,
|
|
68
|
+
LARGE: 25
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* OnwidButton Component
|
|
74
|
+
*
|
|
75
|
+
* A floating action button that can be dragged around the screen and expanded to show additional content.
|
|
76
|
+
* Features include:
|
|
77
|
+
* - Draggable functionality
|
|
78
|
+
* - Expandable menu
|
|
79
|
+
* - Animated transitions
|
|
80
|
+
* - Gradient background
|
|
81
|
+
* - Customizable styling
|
|
82
|
+
*
|
|
83
|
+
* @component
|
|
84
|
+
* @example
|
|
85
|
+
* ```tsx
|
|
86
|
+
* <OnwidButton
|
|
87
|
+
* isOpen={false}
|
|
88
|
+
* onPress={(isOpen) => console.log('Button pressed:', isOpen)}
|
|
89
|
+
* menuComponent={<YourMenuComponent />}
|
|
90
|
+
* />
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export function EmbedButton() {
|
|
94
|
+
const {
|
|
95
|
+
initializeVoiceAgent,
|
|
96
|
+
tokenDetails,
|
|
97
|
+
endCall,
|
|
98
|
+
isLoading,
|
|
99
|
+
isMicMuted,
|
|
100
|
+
muteMic,
|
|
101
|
+
unmuteMic,
|
|
102
|
+
connectionState,
|
|
103
|
+
roomRef
|
|
104
|
+
} = useVoiceAgent();
|
|
105
|
+
// State management
|
|
106
|
+
const [configData, setConfigData] = useState(null);
|
|
107
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
108
|
+
const [callDuration, setCallDuration] = useState(0);
|
|
109
|
+
const timerRef = useRef(null);
|
|
110
|
+
const lottieRef = useRef(null);
|
|
111
|
+
|
|
112
|
+
// Animation values
|
|
113
|
+
const isPressed = useSharedValue(false);
|
|
114
|
+
const offset = useSharedValue({
|
|
115
|
+
x: 0,
|
|
116
|
+
y: 0
|
|
117
|
+
});
|
|
118
|
+
const start = useSharedValue({
|
|
119
|
+
x: 0,
|
|
120
|
+
y: 0
|
|
121
|
+
});
|
|
122
|
+
const menuAnimation = useSharedValue(0);
|
|
123
|
+
const buttonWidth = useSharedValue(BUTTON_WIDTH);
|
|
124
|
+
const buttonScale = useSharedValue(1);
|
|
125
|
+
// Styles
|
|
126
|
+
const styles = createEmbedButtonStyles(defaultStyles);
|
|
127
|
+
const [isAutoOpen, setIsAutoOpen] = useState(false);
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
const autoOpenTimer = setTimeout(() => {
|
|
130
|
+
if (!isOpen) {
|
|
131
|
+
console.log('autoOpenTimer', isOpen);
|
|
132
|
+
setIsAutoOpen(true);
|
|
133
|
+
}
|
|
134
|
+
}, 15000); // 15 seconds
|
|
135
|
+
|
|
136
|
+
return () => {
|
|
137
|
+
clearTimeout(autoOpenTimer);
|
|
138
|
+
};
|
|
139
|
+
}, [isOpen]);
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Fetch agent configuration data
|
|
143
|
+
*/
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
const fetchAgentData = async () => {
|
|
146
|
+
try {
|
|
147
|
+
const data = await getAgentData('@config_data');
|
|
148
|
+
setConfigData(data?.ui_config);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error('Error retrieving agent data:', error);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
fetchAgentData();
|
|
154
|
+
}, []);
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Set up a timer to track call duration when connected
|
|
158
|
+
*/
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (connectionState === 'connected' && !timerRef.current) {
|
|
161
|
+
timerRef.current = setInterval(() => {
|
|
162
|
+
setCallDuration(prev => prev + 1);
|
|
163
|
+
}, 1000);
|
|
164
|
+
} else if (connectionState !== 'connected' && timerRef.current) {
|
|
165
|
+
clearInterval(timerRef.current);
|
|
166
|
+
timerRef.current = null;
|
|
167
|
+
|
|
168
|
+
// If we were previously connected and now disconnected, show an error
|
|
169
|
+
if (callDuration > 0) {
|
|
170
|
+
console.log('Call unexpectedly disconnected after', callDuration, 'seconds');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return () => {
|
|
174
|
+
if (timerRef.current) {
|
|
175
|
+
clearInterval(timerRef.current);
|
|
176
|
+
timerRef.current = null;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}, [connectionState, callDuration]);
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Handle menu animation and button width transitions
|
|
183
|
+
*/
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
menuAnimation.value = withTiming(isOpen ? 0.8 : 0, {
|
|
186
|
+
duration: 300
|
|
187
|
+
});
|
|
188
|
+
buttonWidth.value = withTiming(isOpen ? EXPANDED_WIDTH : BUTTON_WIDTH);
|
|
189
|
+
}, [isOpen, menuAnimation, buttonWidth]);
|
|
190
|
+
|
|
191
|
+
// Add breathing animation when button is closed but isAutoOpen is true
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
if (!isOpen && isAutoOpen) {
|
|
194
|
+
// Start breathing animation with faster speed
|
|
195
|
+
buttonScale.value = withRepeat(withSequence(withTiming(1.1, {
|
|
196
|
+
duration: 1500,
|
|
197
|
+
// Reduced from 1000ms to 600ms
|
|
198
|
+
easing: Easing.inOut(Easing.ease)
|
|
199
|
+
}), withTiming(1, {
|
|
200
|
+
duration: 1500,
|
|
201
|
+
// Reduced from 1000ms to 600ms
|
|
202
|
+
easing: Easing.inOut(Easing.ease)
|
|
203
|
+
})), -1,
|
|
204
|
+
// Infinite repeat
|
|
205
|
+
false // Don't reverse
|
|
206
|
+
);
|
|
207
|
+
} else {
|
|
208
|
+
// Reset animation
|
|
209
|
+
buttonScale.value = withTiming(1, {
|
|
210
|
+
duration: 300
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}, [isOpen, isAutoOpen]);
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Animated styles for the button
|
|
217
|
+
*/
|
|
218
|
+
const animatedStyles = useAnimatedStyle(() => {
|
|
219
|
+
const maxX = SCREEN_WIDTH - (isOpen ? EXPANDED_WIDTH : BUTTON_WIDTH) - 35;
|
|
220
|
+
const clampedX = Math.min(Math.max(offset.value.x, -maxX), 0);
|
|
221
|
+
return {
|
|
222
|
+
width: buttonWidth.value,
|
|
223
|
+
height: BUTTON_HEIGHT,
|
|
224
|
+
transform: [{
|
|
225
|
+
translateX: clampedX
|
|
226
|
+
}, {
|
|
227
|
+
translateY: offset.value.y
|
|
228
|
+
}, {
|
|
229
|
+
scale: withSpring(isPressed.value ? 0.95 : buttonScale.value)
|
|
230
|
+
}],
|
|
231
|
+
justifyContent: isOpen ? 'space-between' : 'flex-start',
|
|
232
|
+
overflow: 'hidden'
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Animated styles for the text
|
|
238
|
+
*/
|
|
239
|
+
const animatedTextStyles = useAnimatedStyle(() => {
|
|
240
|
+
const maxX = SCREEN_WIDTH;
|
|
241
|
+
const clampedX = Math.min(Math.max(offset.value.x, -maxX), 0);
|
|
242
|
+
return {
|
|
243
|
+
transform: [{
|
|
244
|
+
translateX: clampedX
|
|
245
|
+
}, {
|
|
246
|
+
translateY: offset.value.y
|
|
247
|
+
}, {
|
|
248
|
+
scale: withSpring(isPressed.value ? 1 : 1)
|
|
249
|
+
}]
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Pan gesture handler for drag functionality
|
|
255
|
+
*/
|
|
256
|
+
const gesture = Gesture.Pan().onBegin(() => {
|
|
257
|
+
isPressed.value = true;
|
|
258
|
+
if (isAutoOpen) {
|
|
259
|
+
runOnJS(setIsAutoOpen)(false);
|
|
260
|
+
}
|
|
261
|
+
}).onUpdate(e => {
|
|
262
|
+
const maxX = SCREEN_WIDTH - (isOpen ? EXPANDED_WIDTH : BUTTON_WIDTH) - 0;
|
|
263
|
+
const newX = Math.min(Math.max(e.translationX + start.value.x, -maxX), 0);
|
|
264
|
+
const maxY = SCREEN_HEIGHT - 150;
|
|
265
|
+
const newY = Math.min(Math.max(e.translationY + start.value.y, -maxY), 0);
|
|
266
|
+
offset.value = {
|
|
267
|
+
x: newX,
|
|
268
|
+
y: newY
|
|
269
|
+
};
|
|
270
|
+
}).onEnd(() => {
|
|
271
|
+
start.value = {
|
|
272
|
+
x: offset.value.x,
|
|
273
|
+
y: offset.value.y
|
|
274
|
+
};
|
|
275
|
+
}).onFinalize(() => {
|
|
276
|
+
isPressed.value = false;
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Handle button press events
|
|
281
|
+
*/
|
|
282
|
+
const handlePress = () => {
|
|
283
|
+
console.log('handlePress', isOpen);
|
|
284
|
+
console.log('isAutoOpen', isAutoOpen);
|
|
285
|
+
// cleanup();
|
|
286
|
+
setIsOpen(!isOpen);
|
|
287
|
+
setIsAutoOpen(false);
|
|
288
|
+
};
|
|
289
|
+
const handleStartCall = async () => {
|
|
290
|
+
setCallDuration(0);
|
|
291
|
+
await initializeVoiceAgent();
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Render the button icon/animation
|
|
296
|
+
*/
|
|
297
|
+
|
|
298
|
+
const remoteSource = useMemo(() => ({
|
|
299
|
+
uri: configData?.icon_animation || AMPLIFY_ANIMATION
|
|
300
|
+
}), [configData?.icon_animation]);
|
|
301
|
+
const renderIcon = () => {
|
|
302
|
+
// When isAutoOpen is true, we don't play the Lottie animation
|
|
303
|
+
return /*#__PURE__*/_jsx(View, {
|
|
304
|
+
children: /*#__PURE__*/_jsx(LottieView, {
|
|
305
|
+
ref: lottieRef,
|
|
306
|
+
source: remoteSource,
|
|
307
|
+
autoPlay: true,
|
|
308
|
+
loop: true,
|
|
309
|
+
style: styles.iconImage,
|
|
310
|
+
enableMergePathsAndroidForKitKatAndAbove: true,
|
|
311
|
+
enableSafeModeAndroid: true
|
|
312
|
+
})
|
|
313
|
+
});
|
|
314
|
+
};
|
|
315
|
+
const handleConnected = () => {
|
|
316
|
+
console.log('Call connected');
|
|
317
|
+
};
|
|
318
|
+
const handleEndCall = async () => {
|
|
319
|
+
setIsOpen(false);
|
|
320
|
+
if (timerRef.current) {
|
|
321
|
+
clearInterval(timerRef.current);
|
|
322
|
+
timerRef.current = null;
|
|
323
|
+
}
|
|
324
|
+
setCallDuration(0);
|
|
325
|
+
await endCall();
|
|
326
|
+
};
|
|
327
|
+
const handleMicToggle = () => {
|
|
328
|
+
if (isMicMuted) {
|
|
329
|
+
unmuteMic();
|
|
330
|
+
} else {
|
|
331
|
+
muteMic();
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// Format duration to MM:SS
|
|
336
|
+
const formatDuration = seconds => {
|
|
337
|
+
const minutes = Math.floor(seconds / 60);
|
|
338
|
+
const remainingSeconds = seconds % 60;
|
|
339
|
+
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// Get the status text based on current state
|
|
343
|
+
const getStatusText = () => {
|
|
344
|
+
if (isLoading) {
|
|
345
|
+
return 'Connecting...';
|
|
346
|
+
} else if (tokenDetails?.token) {
|
|
347
|
+
return `${formatDuration(callDuration)}`;
|
|
348
|
+
} else {
|
|
349
|
+
return configData?.agent_type || 'Onboarding Agent';
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
if (!configData) return null;
|
|
353
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
354
|
+
style: styles.container,
|
|
355
|
+
children: [isAutoOpen && !isOpen && /*#__PURE__*/_jsx(Animated.View, {
|
|
356
|
+
style: [animatedTextStyles, {
|
|
357
|
+
position: 'absolute',
|
|
358
|
+
borderRadius: 5,
|
|
359
|
+
paddingVertical: 2,
|
|
360
|
+
paddingHorizontal: 10,
|
|
361
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
362
|
+
bottom: BUTTON_HEIGHT + 40
|
|
363
|
+
// right: Math.abs(offset.value.x) + BUTTON_WIDTH + 0,
|
|
364
|
+
}],
|
|
365
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
366
|
+
style: {
|
|
367
|
+
color: 'white',
|
|
368
|
+
fontSize: 10,
|
|
369
|
+
fontWeight: '500'
|
|
370
|
+
},
|
|
371
|
+
children: configData?.popup_description || 'Revrag'
|
|
372
|
+
})
|
|
373
|
+
}), /*#__PURE__*/_jsx(GestureDetector, {
|
|
374
|
+
gesture: gesture,
|
|
375
|
+
children: /*#__PURE__*/_jsx(Animated.View, {
|
|
376
|
+
style: [styles.button, animatedStyles, styles.buttonContent, {
|
|
377
|
+
pointerEvents: 'auto'
|
|
378
|
+
}],
|
|
379
|
+
children: /*#__PURE__*/_jsxs(LinearGradient, {
|
|
380
|
+
colors: configData?.gradient || GRADIENT_COLORS,
|
|
381
|
+
start: {
|
|
382
|
+
x: 0,
|
|
383
|
+
y: 0
|
|
384
|
+
},
|
|
385
|
+
end: {
|
|
386
|
+
x: 1,
|
|
387
|
+
y: 0
|
|
388
|
+
},
|
|
389
|
+
style: [styles.linearGradient, {
|
|
390
|
+
width: '100%',
|
|
391
|
+
flexDirection: 'row',
|
|
392
|
+
alignItems: 'center',
|
|
393
|
+
paddingHorizontal: 0,
|
|
394
|
+
paddingLeft: 0,
|
|
395
|
+
paddingRight: isOpen ? 5 : 0
|
|
396
|
+
}],
|
|
397
|
+
angle: 0,
|
|
398
|
+
angleCenter: {
|
|
399
|
+
x: 0.5,
|
|
400
|
+
y: 0.5
|
|
401
|
+
},
|
|
402
|
+
children: [tokenDetails?.token && /*#__PURE__*/_jsx(Voice, {
|
|
403
|
+
url: tokenDetails?.server_url,
|
|
404
|
+
token: tokenDetails?.token,
|
|
405
|
+
onDisconnected: handleEndCall,
|
|
406
|
+
roomRef: roomRef,
|
|
407
|
+
onConnected: handleConnected
|
|
408
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
409
|
+
style: [styles.rowContainer, {
|
|
410
|
+
flexShrink: 0,
|
|
411
|
+
width: BUTTON_WIDTH,
|
|
412
|
+
padding: 0,
|
|
413
|
+
margin: 0
|
|
414
|
+
}],
|
|
415
|
+
children: /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
416
|
+
onPress: handlePress,
|
|
417
|
+
style: styles.pressable,
|
|
418
|
+
children: renderIcon()
|
|
419
|
+
})
|
|
420
|
+
}), isOpen && /*#__PURE__*/_jsxs(View, {
|
|
421
|
+
style: {
|
|
422
|
+
flex: 1,
|
|
423
|
+
flexDirection: 'row',
|
|
424
|
+
height: BUTTON_HEIGHT - 10,
|
|
425
|
+
marginLeft: 0,
|
|
426
|
+
marginRight: 0
|
|
427
|
+
},
|
|
428
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
429
|
+
style: {
|
|
430
|
+
flex: 1,
|
|
431
|
+
justifyContent: 'center',
|
|
432
|
+
alignItems: 'flex-start',
|
|
433
|
+
paddingLeft: 8,
|
|
434
|
+
paddingRight: 4
|
|
435
|
+
},
|
|
436
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
437
|
+
style: [styles.agentNameText, {
|
|
438
|
+
flexShrink: 1,
|
|
439
|
+
textAlign: 'left'
|
|
440
|
+
}],
|
|
441
|
+
children: configData?.agent_name || 'Revrag'
|
|
442
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
443
|
+
style: [styles.statusText, {
|
|
444
|
+
flexShrink: 1,
|
|
445
|
+
textAlign: 'left'
|
|
446
|
+
}],
|
|
447
|
+
children: getStatusText()
|
|
448
|
+
})]
|
|
449
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
450
|
+
style: {
|
|
451
|
+
flex: 1,
|
|
452
|
+
justifyContent: 'center',
|
|
453
|
+
alignItems: 'center',
|
|
454
|
+
paddingHorizontal: 4
|
|
455
|
+
},
|
|
456
|
+
children: /*#__PURE__*/_jsx(WaveformVisualizer, {
|
|
457
|
+
roomRef: roomRef
|
|
458
|
+
})
|
|
459
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
460
|
+
style: {
|
|
461
|
+
flex: 1,
|
|
462
|
+
justifyContent: 'center',
|
|
463
|
+
alignItems: 'flex-end',
|
|
464
|
+
paddingLeft: 4,
|
|
465
|
+
paddingRight: 8
|
|
466
|
+
},
|
|
467
|
+
children: [!tokenDetails?.token && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
468
|
+
onPress: handleStartCall,
|
|
469
|
+
style: styles.startCallButton,
|
|
470
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
471
|
+
style: styles.startCallText,
|
|
472
|
+
children: configData?.start_call_text || 'Start Call'
|
|
473
|
+
})
|
|
474
|
+
}), tokenDetails?.token && /*#__PURE__*/_jsxs(View, {
|
|
475
|
+
style: [styles.buttonContainer],
|
|
476
|
+
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
477
|
+
style: styles.muteButton,
|
|
478
|
+
onPress: handleMicToggle,
|
|
479
|
+
children: /*#__PURE__*/_jsx(Image, {
|
|
480
|
+
source: {
|
|
481
|
+
uri: isMicMuted ? MIC_OFF_ICON : MIC_ON_ICON
|
|
482
|
+
},
|
|
483
|
+
style: styles.buttonImage
|
|
484
|
+
})
|
|
485
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
486
|
+
onPress: handleEndCall,
|
|
487
|
+
style: styles.endCallButton,
|
|
488
|
+
children: /*#__PURE__*/_jsx(Image, {
|
|
489
|
+
source: {
|
|
490
|
+
uri: END_CALL_ICON
|
|
491
|
+
},
|
|
492
|
+
style: styles.buttonImage
|
|
493
|
+
})
|
|
494
|
+
})]
|
|
495
|
+
})]
|
|
496
|
+
})]
|
|
497
|
+
})]
|
|
498
|
+
})
|
|
499
|
+
})
|
|
500
|
+
})]
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Export default for easier imports
|
|
505
|
+
export default EmbedButton;
|
|
506
|
+
//# sourceMappingURL=EmbedButton.js.map
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { AudioSession, LiveKitRoom } from '@livekit/react-native';
|
|
4
|
+
import { useEffect, useRef, useState } from 'react';
|
|
5
|
+
|
|
6
|
+
// export interface VoiceProps {
|
|
7
|
+
// url: string;
|
|
8
|
+
// token: string;
|
|
9
|
+
// onDisconnected: (data: string) => void;
|
|
10
|
+
// onConnected: (data: string) => void;
|
|
11
|
+
// }
|
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
// Track audio session state globally to prevent multiple starts/stops
|
|
14
|
+
let audioSessionActive = false;
|
|
15
|
+
let audioSessionStarting = false;
|
|
16
|
+
let audioSessionStopping = false;
|
|
17
|
+
|
|
18
|
+
// Track connection to prevent unmounting while connected
|
|
19
|
+
let activeConnectionToken = null;
|
|
20
|
+
const Voice = props => {
|
|
21
|
+
const {
|
|
22
|
+
url,
|
|
23
|
+
token,
|
|
24
|
+
onDisconnected,
|
|
25
|
+
onConnected,
|
|
26
|
+
roomRef
|
|
27
|
+
} = props;
|
|
28
|
+
const [audioSessionStarted, setAudioSessionStarted] = useState(audioSessionActive);
|
|
29
|
+
const mountedRef = useRef(true);
|
|
30
|
+
const connectedRef = useRef(false);
|
|
31
|
+
|
|
32
|
+
// Start audio session safely - prevent multiple simultaneous starts
|
|
33
|
+
const startAudioSession = async () => {
|
|
34
|
+
if (audioSessionActive || audioSessionStarting) {
|
|
35
|
+
console.log('Audio session already active or starting, skipping');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
audioSessionStarting = true;
|
|
40
|
+
console.log('Starting audio session');
|
|
41
|
+
await AudioSession.startAudioSession();
|
|
42
|
+
audioSessionActive = true;
|
|
43
|
+
if (mountedRef.current) {
|
|
44
|
+
setAudioSessionStarted(true);
|
|
45
|
+
}
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error('Failed to start audio session:', err);
|
|
48
|
+
} finally {
|
|
49
|
+
audioSessionStarting = false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Stop audio session safely - prevent multiple simultaneous stops
|
|
54
|
+
const stopAudioSession = async () => {
|
|
55
|
+
if (!audioSessionActive || audioSessionStopping) {
|
|
56
|
+
console.log('Audio session already inactive or stopping, skipping');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
audioSessionStopping = true;
|
|
61
|
+
console.log('Stopping audio session');
|
|
62
|
+
await AudioSession.stopAudioSession();
|
|
63
|
+
audioSessionActive = false;
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.error('Failed to stop audio session:', err);
|
|
66
|
+
} finally {
|
|
67
|
+
audioSessionStopping = false;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Setup audio session
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
mountedRef.current = true;
|
|
74
|
+
startAudioSession();
|
|
75
|
+
return () => {
|
|
76
|
+
mountedRef.current = false;
|
|
77
|
+
|
|
78
|
+
// IMPORTANT: Don't stop the audio session on unmount if there might be an active call
|
|
79
|
+
// This prevents audio session start/stop loops when components remount
|
|
80
|
+
if (roomRef.current?.state !== 'connected' && !connectedRef.current) {
|
|
81
|
+
stopAudioSession();
|
|
82
|
+
} else {
|
|
83
|
+
console.log('Skipping audio session stop because room is still connected');
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}, []);
|
|
87
|
+
|
|
88
|
+
// Track connection state changes to avoid unmounting while connected
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (token) {
|
|
91
|
+
activeConnectionToken = token;
|
|
92
|
+
}
|
|
93
|
+
return () => {
|
|
94
|
+
// Only clear token when unmounting with this specific token
|
|
95
|
+
if (activeConnectionToken === token) {
|
|
96
|
+
activeConnectionToken = null;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}, [token]);
|
|
100
|
+
|
|
101
|
+
// Only render LiveKitRoom when audio session is ready
|
|
102
|
+
if (!audioSessionStarted) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// IMPORTANT: Never return empty fragment when connected!
|
|
107
|
+
// Instead, always render the LiveKitRoom component to maintain the connection
|
|
108
|
+
return /*#__PURE__*/_jsx(LiveKitRoom, {
|
|
109
|
+
serverUrl: url,
|
|
110
|
+
token: token,
|
|
111
|
+
screen: false,
|
|
112
|
+
audio: true,
|
|
113
|
+
onConnected: () => {
|
|
114
|
+
console.log('LiveKitRoom connected');
|
|
115
|
+
connectedRef.current = true;
|
|
116
|
+
onConnected('connected');
|
|
117
|
+
},
|
|
118
|
+
room: roomRef.current || undefined,
|
|
119
|
+
onDisconnected: () => {
|
|
120
|
+
console.log('LiveKitRoom disconnected');
|
|
121
|
+
connectedRef.current = false;
|
|
122
|
+
onDisconnected('disconnected');
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
export default Voice;
|
|
127
|
+
//# sourceMappingURL=EmbedVoice.js.map
|