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