@alia.onl/sdk 3.1.0 → 3.1.2
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/package.json +47 -16
- package/src/animations.ts +6 -0
- package/src/components/AliaChatMessageList.tsx +2 -3
- package/src/components/AliaChatScreen.tsx +8 -43
- package/src/components/AliaChatSheet.tsx +8 -26
- package/src/components/AliaFace.tsx +108 -58
- package/src/components/Markdown.tsx +59 -39
- package/src/components/PlanPreviewCard.tsx +3 -2
- package/src/components/Reasoning.tsx +1 -3
- package/src/components/ResearchProgressCard.tsx +16 -17
- package/src/components/ThinkingIndicator.tsx +6 -21
- package/src/components/ui/dropdown-menu.web.tsx +2 -2
- package/src/components/voice/AudioWaveVisualizer.tsx +72 -22
- package/src/components/voice/VoiceControls.tsx +25 -42
- package/src/components/voice/VoiceOverlay.tsx +5 -1
- package/src/hooks/useVoiceRoom.ts +24 -8
- package/src/index.ts +2 -2
- package/src/theme.ts +2 -41
- package/src/components/AliaChatInput.tsx +0 -294
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { View, Text, Pressable, ActivityIndicator, StyleSheet } from 'react-native';
|
|
7
|
+
import { Mic, MicOff, PhoneOff, Users } from 'lucide-react-native';
|
|
7
8
|
import type { RoomState, AgentState } from '../../types';
|
|
8
|
-
import { useAliaColors } from '../../theme';
|
|
9
9
|
|
|
10
10
|
interface VoiceControlsProps {
|
|
11
11
|
roomState: RoomState;
|
|
@@ -19,6 +19,8 @@ interface VoiceControlsProps {
|
|
|
19
19
|
onDisableCohost: () => void;
|
|
20
20
|
onContinueCohost: () => void;
|
|
21
21
|
onEnd: () => void;
|
|
22
|
+
/** Override theme primary color (resolved hex/hsl value, not CSS var) */
|
|
23
|
+
primaryColor?: string;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
function getStatusText(
|
|
@@ -52,14 +54,14 @@ export function VoiceControls({
|
|
|
52
54
|
onDisableCohost,
|
|
53
55
|
onContinueCohost,
|
|
54
56
|
onEnd,
|
|
57
|
+
primaryColor,
|
|
55
58
|
}: VoiceControlsProps) {
|
|
56
|
-
const colors = useAliaColors();
|
|
57
59
|
const statusText = getStatusText(roomState, agentState, isMuted, cohostActive, currentSpeaker);
|
|
58
60
|
|
|
59
61
|
return (
|
|
60
62
|
<View style={styles.container}>
|
|
61
63
|
{statusText ? (
|
|
62
|
-
<Text
|
|
64
|
+
<Text className="text-lg font-medium mb-4 text-foreground">
|
|
63
65
|
{statusText}
|
|
64
66
|
</Text>
|
|
65
67
|
) : null}
|
|
@@ -67,9 +69,12 @@ export function VoiceControls({
|
|
|
67
69
|
{roundComplete && (
|
|
68
70
|
<Pressable
|
|
69
71
|
onPress={onContinueCohost}
|
|
70
|
-
|
|
72
|
+
className="bg-primary/30"
|
|
73
|
+
style={[styles.continueButton, primaryColor ? { backgroundColor: primaryColor + '4D' } : undefined]}
|
|
71
74
|
>
|
|
72
|
-
<Text style={
|
|
75
|
+
<Text className="text-sm font-medium text-primary" style={primaryColor ? { color: primaryColor } : undefined}>
|
|
76
|
+
Continue conversation
|
|
77
|
+
</Text>
|
|
73
78
|
</Pressable>
|
|
74
79
|
)}
|
|
75
80
|
|
|
@@ -78,16 +83,16 @@ export function VoiceControls({
|
|
|
78
83
|
<View style={styles.buttonWrapper}>
|
|
79
84
|
<Pressable
|
|
80
85
|
onPress={onToggleMute}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
{ backgroundColor: isMuted ? '#ef4444' : 'rgba(255,255,255,0.15)' },
|
|
84
|
-
]}
|
|
86
|
+
className={isMuted ? undefined : 'bg-muted'}
|
|
87
|
+
style={[styles.button, isMuted ? { backgroundColor: '#ef4444' } : undefined]}
|
|
85
88
|
>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
{isMuted ? (
|
|
90
|
+
<MicOff size={24} color="white" />
|
|
91
|
+
) : (
|
|
92
|
+
<Mic size={24} color="white" />
|
|
93
|
+
)}
|
|
89
94
|
</Pressable>
|
|
90
|
-
<Text
|
|
95
|
+
<Text className="text-xs text-muted-foreground">
|
|
91
96
|
{isMuted ? 'Unmute' : 'Mute'}
|
|
92
97
|
</Text>
|
|
93
98
|
</View>
|
|
@@ -95,14 +100,15 @@ export function VoiceControls({
|
|
|
95
100
|
<View style={styles.buttonWrapper}>
|
|
96
101
|
<Pressable
|
|
97
102
|
onPress={cohostActive ? onDisableCohost : onEnableCohost}
|
|
103
|
+
className={cohostActive ? 'bg-primary' : 'bg-muted'}
|
|
98
104
|
style={[
|
|
99
105
|
styles.button,
|
|
100
|
-
|
|
106
|
+
cohostActive && primaryColor ? { backgroundColor: primaryColor } : undefined,
|
|
101
107
|
]}
|
|
102
108
|
>
|
|
103
|
-
<
|
|
109
|
+
<Users size={24} color="white" />
|
|
104
110
|
</Pressable>
|
|
105
|
-
<Text
|
|
111
|
+
<Text className="text-xs text-muted-foreground">
|
|
106
112
|
{cohostActive ? 'Solo' : 'Cohost'}
|
|
107
113
|
</Text>
|
|
108
114
|
</View>
|
|
@@ -112,15 +118,15 @@ export function VoiceControls({
|
|
|
112
118
|
onPress={onEnd}
|
|
113
119
|
style={[styles.button, { backgroundColor: '#ef4444' }]}
|
|
114
120
|
>
|
|
115
|
-
<
|
|
121
|
+
<PhoneOff size={24} color="white" />
|
|
116
122
|
</Pressable>
|
|
117
|
-
<Text
|
|
123
|
+
<Text className="text-xs text-muted-foreground">End</Text>
|
|
118
124
|
</View>
|
|
119
125
|
</View>
|
|
120
126
|
)}
|
|
121
127
|
|
|
122
128
|
{roomState === 'connecting' && (
|
|
123
|
-
<ActivityIndicator size="large"
|
|
129
|
+
<ActivityIndicator size="large" className="text-muted-foreground" />
|
|
124
130
|
)}
|
|
125
131
|
</View>
|
|
126
132
|
);
|
|
@@ -132,11 +138,6 @@ const styles = StyleSheet.create({
|
|
|
132
138
|
paddingBottom: 24,
|
|
133
139
|
paddingTop: 16,
|
|
134
140
|
},
|
|
135
|
-
statusText: {
|
|
136
|
-
fontSize: 18,
|
|
137
|
-
fontWeight: '500',
|
|
138
|
-
marginBottom: 16,
|
|
139
|
-
},
|
|
140
141
|
buttonRow: {
|
|
141
142
|
flexDirection: 'row',
|
|
142
143
|
alignItems: 'center',
|
|
@@ -153,28 +154,10 @@ const styles = StyleSheet.create({
|
|
|
153
154
|
alignItems: 'center',
|
|
154
155
|
justifyContent: 'center',
|
|
155
156
|
},
|
|
156
|
-
buttonIcon: {
|
|
157
|
-
fontSize: 24,
|
|
158
|
-
},
|
|
159
|
-
endIcon: {
|
|
160
|
-
fontSize: 24,
|
|
161
|
-
color: '#ffffff',
|
|
162
|
-
fontWeight: '700',
|
|
163
|
-
},
|
|
164
|
-
buttonLabel: {
|
|
165
|
-
color: '#8E8E93',
|
|
166
|
-
fontSize: 12,
|
|
167
|
-
},
|
|
168
157
|
continueButton: {
|
|
169
158
|
marginBottom: 16,
|
|
170
159
|
paddingHorizontal: 20,
|
|
171
160
|
paddingVertical: 8,
|
|
172
161
|
borderRadius: 24,
|
|
173
|
-
backgroundColor: 'rgba(139, 92, 246, 0.3)',
|
|
174
|
-
},
|
|
175
|
-
continueText: {
|
|
176
|
-
color: '#a78bfa',
|
|
177
|
-
fontSize: 14,
|
|
178
|
-
fontWeight: '500',
|
|
179
162
|
},
|
|
180
163
|
});
|
|
@@ -14,9 +14,11 @@ interface VoiceOverlayProps {
|
|
|
14
14
|
waveAmplitude: SharedValue<number>;
|
|
15
15
|
agentState: AgentState;
|
|
16
16
|
isConnected: boolean;
|
|
17
|
+
primaryColor?: string;
|
|
18
|
+
isDarkMode?: boolean;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
export function VoiceOverlay({ waveAmplitude, agentState, isConnected }: VoiceOverlayProps) {
|
|
21
|
+
export function VoiceOverlay({ waveAmplitude, agentState, isConnected, primaryColor, isDarkMode }: VoiceOverlayProps) {
|
|
20
22
|
return (
|
|
21
23
|
<Animated.View
|
|
22
24
|
entering={FadeIn.duration(400)}
|
|
@@ -36,6 +38,8 @@ export function VoiceOverlay({ waveAmplitude, agentState, isConnected }: VoiceOv
|
|
|
36
38
|
waveAmplitude={waveAmplitude}
|
|
37
39
|
agentState={agentState}
|
|
38
40
|
isConnected={isConnected}
|
|
41
|
+
primaryColor={primaryColor}
|
|
42
|
+
isDarkMode={isDarkMode}
|
|
39
43
|
/>
|
|
40
44
|
</View>
|
|
41
45
|
</Animated.View>
|
|
@@ -21,6 +21,10 @@ import type { RoomState, AgentState, VoiceMessage, VoiceToolInvocation } from '.
|
|
|
21
21
|
|
|
22
22
|
const API_URL = process.env.EXPO_PUBLIC_ALIA_API_URL ?? 'https://api.alia.onl';
|
|
23
23
|
|
|
24
|
+
/** On web, audio tracks must be attached to a DOM element to play.
|
|
25
|
+
* On native (React Native), the WebRTC layer plays them automatically. */
|
|
26
|
+
const hasDOM = typeof document !== 'undefined';
|
|
27
|
+
|
|
24
28
|
// ============== OPTIONS ==============
|
|
25
29
|
|
|
26
30
|
export interface UseVoiceRoomOptions {
|
|
@@ -106,12 +110,14 @@ export function useVoiceRoom(options: UseVoiceRoomOptions = {}) {
|
|
|
106
110
|
|
|
107
111
|
const cleanup = useCallback(() => {
|
|
108
112
|
if (roomRef.current) {
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
// On web, detach all remote audio tracks before disconnecting
|
|
114
|
+
if (hasDOM) {
|
|
115
|
+
roomRef.current.remoteParticipants.forEach((p) => {
|
|
116
|
+
p.trackPublications.forEach((pub) => {
|
|
117
|
+
pub.track?.detach();
|
|
118
|
+
});
|
|
113
119
|
});
|
|
114
|
-
}
|
|
120
|
+
}
|
|
115
121
|
roomRef.current.disconnect();
|
|
116
122
|
roomRef.current = null;
|
|
117
123
|
}
|
|
@@ -274,6 +280,13 @@ export function useVoiceRoom(options: UseVoiceRoomOptions = {}) {
|
|
|
274
280
|
setError(null);
|
|
275
281
|
setRoomState('connecting');
|
|
276
282
|
|
|
283
|
+
// Guard: WebRTC must be available (requires registerGlobals on RN)
|
|
284
|
+
if (typeof globalThis.RTCPeerConnection === 'undefined') {
|
|
285
|
+
setError('Voice is not available on this device');
|
|
286
|
+
setRoomState('error');
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
277
290
|
const token = getToken();
|
|
278
291
|
if (!token) {
|
|
279
292
|
setError('Not authenticated');
|
|
@@ -309,15 +322,18 @@ export function useVoiceRoom(options: UseVoiceRoomOptions = {}) {
|
|
|
309
322
|
const room = new Room();
|
|
310
323
|
roomRef.current = room;
|
|
311
324
|
|
|
312
|
-
// Event: remote audio tracks (agent speaking)
|
|
325
|
+
// Event: remote audio tracks (agent speaking)
|
|
326
|
+
// On web, must attach to a DOM element to play. On native, WebRTC plays audio automatically.
|
|
313
327
|
room.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {
|
|
314
|
-
if (track.kind === Track.Kind.Audio) {
|
|
328
|
+
if (track.kind === Track.Kind.Audio && hasDOM) {
|
|
315
329
|
track.attach();
|
|
316
330
|
}
|
|
317
331
|
});
|
|
318
332
|
|
|
319
333
|
room.on(RoomEvent.TrackUnsubscribed, (track) => {
|
|
320
|
-
track.
|
|
334
|
+
if (hasDOM && track.kind === Track.Kind.Audio) {
|
|
335
|
+
track.detach();
|
|
336
|
+
}
|
|
321
337
|
});
|
|
322
338
|
|
|
323
339
|
// Event: data messages from agent
|
package/src/index.ts
CHANGED
|
@@ -10,9 +10,10 @@ export type { AliaChatScreenProps } from './components/AliaChatScreen';
|
|
|
10
10
|
|
|
11
11
|
// ── Chat UI components ──
|
|
12
12
|
export { AliaFace } from './components/AliaFace';
|
|
13
|
-
export type { AliaExpression, AliaFaceProps } from './components/AliaFace';
|
|
13
|
+
export type { AliaAccessory, AliaExpression, AliaFaceProps } from './components/AliaFace';
|
|
14
14
|
export { ThinkingIndicator } from './components/ThinkingIndicator';
|
|
15
15
|
export { AliaMarkdown } from './components/Markdown';
|
|
16
|
+
export type { AliaMarkdownProps } from './components/Markdown';
|
|
16
17
|
export { Reasoning, ReasoningTrigger, ReasoningContent } from './components/Reasoning';
|
|
17
18
|
export { AliaWelcomeMessage } from './components/AliaWelcomeMessage';
|
|
18
19
|
export { ResearchProgressCard } from './components/ResearchProgressCard';
|
|
@@ -85,5 +86,4 @@ export type { DocumentPickerResult } from './hooks/useDocumentPicker';
|
|
|
85
86
|
export { KeyboardAwareScrollView, KeyboardAvoidingView, KeyboardProvider } from './lib/keyboard';
|
|
86
87
|
|
|
87
88
|
// ── Theme ──
|
|
88
|
-
export { useAliaColors } from './theme';
|
|
89
89
|
export type { AliaColors } from './theme';
|
package/src/theme.ts
CHANGED
|
@@ -1,35 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { useColorScheme } from 'react-native';
|
|
3
|
-
|
|
4
|
-
const COLORS = {
|
|
5
|
-
light: {
|
|
6
|
-
text: '#11181C',
|
|
7
|
-
background: '#fff',
|
|
8
|
-
card: '#F2F2F7',
|
|
9
|
-
inputBackground: '#F5F5F5',
|
|
10
|
-
border: '#E5E5EA',
|
|
11
|
-
secondaryText: '#8E8E93',
|
|
12
|
-
icon: '#687076',
|
|
13
|
-
tint: '#0a7ea4',
|
|
14
|
-
muted: '#F4F4F5',
|
|
15
|
-
mutedForeground: '#71717A',
|
|
16
|
-
primary: '#0a7ea4',
|
|
17
|
-
},
|
|
18
|
-
dark: {
|
|
19
|
-
text: '#ECEDEE',
|
|
20
|
-
background: '#000000',
|
|
21
|
-
card: '#1C1C1E',
|
|
22
|
-
inputBackground: '#333333',
|
|
23
|
-
border: '#2C2C2E',
|
|
24
|
-
secondaryText: '#8E8E93',
|
|
25
|
-
icon: '#9BA1A6',
|
|
26
|
-
tint: '#fff',
|
|
27
|
-
muted: '#27272A',
|
|
28
|
-
mutedForeground: '#A1A1AA',
|
|
29
|
-
primary: '#38BDF8',
|
|
30
|
-
},
|
|
31
|
-
} as const;
|
|
32
|
-
|
|
1
|
+
/** Color interface used by SDK components that need resolved color values (e.g., Markdown renderer). */
|
|
33
2
|
export interface AliaColors {
|
|
34
3
|
text: string;
|
|
35
4
|
background: string;
|
|
@@ -42,14 +11,6 @@ export interface AliaColors {
|
|
|
42
11
|
muted: string;
|
|
43
12
|
mutedForeground: string;
|
|
44
13
|
primary: string;
|
|
14
|
+
primary10: string;
|
|
45
15
|
isDark: boolean;
|
|
46
16
|
}
|
|
47
|
-
|
|
48
|
-
export function useAliaColors(): AliaColors {
|
|
49
|
-
const scheme = useColorScheme();
|
|
50
|
-
const key = scheme === 'dark' ? 'dark' : 'light';
|
|
51
|
-
return useMemo(() => ({
|
|
52
|
-
...COLORS[key],
|
|
53
|
-
isDark: key === 'dark',
|
|
54
|
-
}), [key]);
|
|
55
|
-
}
|
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
import React, { useState, useCallback } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
View,
|
|
4
|
-
TextInput,
|
|
5
|
-
Pressable,
|
|
6
|
-
StyleSheet,
|
|
7
|
-
ActivityIndicator,
|
|
8
|
-
Platform,
|
|
9
|
-
} from 'react-native';
|
|
10
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
11
|
-
import { useSpeechToText } from '../hooks/useSpeechToText';
|
|
12
|
-
import { useAliaColors } from '../theme';
|
|
13
|
-
|
|
14
|
-
interface AliaChatInputProps {
|
|
15
|
-
onSend: (text: string) => void;
|
|
16
|
-
isStreaming: boolean;
|
|
17
|
-
onStop: () => void;
|
|
18
|
-
onVoiceActivate: () => void;
|
|
19
|
-
apiUrl?: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function AliaChatInput({
|
|
23
|
-
onSend,
|
|
24
|
-
isStreaming,
|
|
25
|
-
onStop,
|
|
26
|
-
onVoiceActivate,
|
|
27
|
-
apiUrl,
|
|
28
|
-
}: AliaChatInputProps) {
|
|
29
|
-
const [input, setInput] = useState('');
|
|
30
|
-
const colors = useAliaColors();
|
|
31
|
-
const insets = useSafeAreaInsets();
|
|
32
|
-
const isDark = colors.isDark;
|
|
33
|
-
const stt = useSpeechToText({ apiUrl });
|
|
34
|
-
|
|
35
|
-
const hasText = input.trim().length > 0;
|
|
36
|
-
const canSend = hasText && !isStreaming;
|
|
37
|
-
|
|
38
|
-
const handleSend = useCallback(() => {
|
|
39
|
-
if (!canSend) return;
|
|
40
|
-
onSend(input.trim());
|
|
41
|
-
setInput('');
|
|
42
|
-
}, [canSend, input, onSend]);
|
|
43
|
-
|
|
44
|
-
const handleMicPress = useCallback(async () => {
|
|
45
|
-
if (stt.isRecording) {
|
|
46
|
-
const text = await stt.stopAndTranscribe();
|
|
47
|
-
if (text) {
|
|
48
|
-
setInput((prev) => (prev ? `${prev} ${text}` : text));
|
|
49
|
-
}
|
|
50
|
-
} else if (!stt.isTranscribing) {
|
|
51
|
-
stt.startRecording();
|
|
52
|
-
}
|
|
53
|
-
}, [stt]);
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<View
|
|
57
|
-
style={[
|
|
58
|
-
styles.container,
|
|
59
|
-
{
|
|
60
|
-
borderTopColor: colors.border,
|
|
61
|
-
paddingBottom: Math.max(insets.bottom, 12),
|
|
62
|
-
},
|
|
63
|
-
]}
|
|
64
|
-
>
|
|
65
|
-
<TextInput
|
|
66
|
-
style={[
|
|
67
|
-
styles.input,
|
|
68
|
-
{
|
|
69
|
-
color: colors.text,
|
|
70
|
-
backgroundColor: isDark ? colors.inputBackground : colors.card,
|
|
71
|
-
},
|
|
72
|
-
]}
|
|
73
|
-
value={input}
|
|
74
|
-
onChangeText={setInput}
|
|
75
|
-
placeholder="Ask Alia..."
|
|
76
|
-
placeholderTextColor={colors.secondaryText}
|
|
77
|
-
multiline
|
|
78
|
-
maxLength={2000}
|
|
79
|
-
onSubmitEditing={handleSend}
|
|
80
|
-
blurOnSubmit={false}
|
|
81
|
-
/>
|
|
82
|
-
|
|
83
|
-
{/* STT mic button */}
|
|
84
|
-
<Pressable
|
|
85
|
-
onPress={handleMicPress}
|
|
86
|
-
disabled={stt.isTranscribing}
|
|
87
|
-
style={styles.micButton}
|
|
88
|
-
>
|
|
89
|
-
{stt.isTranscribing ? (
|
|
90
|
-
<ActivityIndicator size="small" color="#6366f1" />
|
|
91
|
-
) : stt.isRecording ? (
|
|
92
|
-
<MicOffIcon />
|
|
93
|
-
) : (
|
|
94
|
-
<MicIcon color={colors.secondaryText} />
|
|
95
|
-
)}
|
|
96
|
-
</Pressable>
|
|
97
|
-
|
|
98
|
-
{/* Three-state submit button */}
|
|
99
|
-
{isStreaming ? (
|
|
100
|
-
<Pressable
|
|
101
|
-
style={[styles.actionButton, { backgroundColor: colors.tint }]}
|
|
102
|
-
onPress={onStop}
|
|
103
|
-
>
|
|
104
|
-
<StopIcon />
|
|
105
|
-
</Pressable>
|
|
106
|
-
) : hasText ? (
|
|
107
|
-
<Pressable
|
|
108
|
-
style={[
|
|
109
|
-
styles.actionButton,
|
|
110
|
-
{ backgroundColor: colors.tint },
|
|
111
|
-
!canSend && { opacity: 0.4 },
|
|
112
|
-
]}
|
|
113
|
-
onPress={handleSend}
|
|
114
|
-
disabled={!canSend}
|
|
115
|
-
>
|
|
116
|
-
<ArrowUpIcon />
|
|
117
|
-
</Pressable>
|
|
118
|
-
) : (
|
|
119
|
-
<Pressable
|
|
120
|
-
style={[styles.actionButton, { backgroundColor: colors.tint }]}
|
|
121
|
-
onPress={onVoiceActivate}
|
|
122
|
-
>
|
|
123
|
-
<SoundIcon />
|
|
124
|
-
</Pressable>
|
|
125
|
-
)}
|
|
126
|
-
</View>
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// ── Icons (inline SVG on web, View-based on native) ──
|
|
131
|
-
|
|
132
|
-
function ArrowUpIcon() {
|
|
133
|
-
return (
|
|
134
|
-
<View style={styles.iconCenter}>
|
|
135
|
-
{Platform.OS === 'web' ? (
|
|
136
|
-
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
137
|
-
<path d="M12 19V5M5 12l7-7 7 7" />
|
|
138
|
-
</svg>
|
|
139
|
-
) : (
|
|
140
|
-
<View style={styles.arrowIconInner}>
|
|
141
|
-
<View style={[styles.arrowStem, { backgroundColor: '#FFFFFF' }]} />
|
|
142
|
-
<View style={[styles.arrowHead, { borderColor: '#FFFFFF' }]} />
|
|
143
|
-
</View>
|
|
144
|
-
)}
|
|
145
|
-
</View>
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function StopIcon() {
|
|
150
|
-
return (
|
|
151
|
-
<View style={styles.iconCenter}>
|
|
152
|
-
{Platform.OS === 'web' ? (
|
|
153
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="white">
|
|
154
|
-
<rect x="4" y="4" width="16" height="16" rx="2" />
|
|
155
|
-
</svg>
|
|
156
|
-
) : (
|
|
157
|
-
<View style={styles.stopSquare} />
|
|
158
|
-
)}
|
|
159
|
-
</View>
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function SoundIcon() {
|
|
164
|
-
return (
|
|
165
|
-
<View style={styles.iconCenter}>
|
|
166
|
-
{Platform.OS === 'web' ? (
|
|
167
|
-
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
168
|
-
<path d="M11 5L6 9H2v6h4l5 4V5z" />
|
|
169
|
-
<path d="M15.54 8.46a5 5 0 0 1 0 7.07" />
|
|
170
|
-
<path d="M19.07 4.93a10 10 0 0 1 0 14.14" />
|
|
171
|
-
</svg>
|
|
172
|
-
) : (
|
|
173
|
-
<View style={styles.soundBars}>
|
|
174
|
-
<View style={[styles.soundBar, styles.soundBarShort]} />
|
|
175
|
-
<View style={[styles.soundBar, styles.soundBarMed]} />
|
|
176
|
-
<View style={[styles.soundBar, styles.soundBarTall]} />
|
|
177
|
-
</View>
|
|
178
|
-
)}
|
|
179
|
-
</View>
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function MicIcon({ color }: { color: string }) {
|
|
184
|
-
return (
|
|
185
|
-
<View style={styles.iconCenter}>
|
|
186
|
-
{Platform.OS === 'web' ? (
|
|
187
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
188
|
-
<rect x="9" y="1" width="6" height="12" rx="3" />
|
|
189
|
-
<path d="M5 10a7 7 0 0 0 14 0" />
|
|
190
|
-
<line x1="12" y1="17" x2="12" y2="21" />
|
|
191
|
-
<line x1="8" y1="21" x2="16" y2="21" />
|
|
192
|
-
</svg>
|
|
193
|
-
) : (
|
|
194
|
-
<View style={[styles.micDot, { backgroundColor: color }]} />
|
|
195
|
-
)}
|
|
196
|
-
</View>
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function MicOffIcon() {
|
|
201
|
-
return (
|
|
202
|
-
<View style={styles.iconCenter}>
|
|
203
|
-
{Platform.OS === 'web' ? (
|
|
204
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#ef4444" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
205
|
-
<rect x="9" y="1" width="6" height="12" rx="3" />
|
|
206
|
-
<path d="M5 10a7 7 0 0 0 14 0" />
|
|
207
|
-
<line x1="12" y1="17" x2="12" y2="21" />
|
|
208
|
-
<line x1="1" y1="1" x2="23" y2="23" />
|
|
209
|
-
</svg>
|
|
210
|
-
) : (
|
|
211
|
-
<View style={[styles.micDot, { backgroundColor: '#ef4444' }]} />
|
|
212
|
-
)}
|
|
213
|
-
</View>
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const styles = StyleSheet.create({
|
|
218
|
-
container: {
|
|
219
|
-
flexDirection: 'row',
|
|
220
|
-
alignItems: 'flex-end',
|
|
221
|
-
paddingHorizontal: 12,
|
|
222
|
-
paddingTop: 8,
|
|
223
|
-
gap: 8,
|
|
224
|
-
borderTopWidth: StyleSheet.hairlineWidth,
|
|
225
|
-
},
|
|
226
|
-
input: {
|
|
227
|
-
flex: 1,
|
|
228
|
-
fontSize: 15,
|
|
229
|
-
paddingHorizontal: 14,
|
|
230
|
-
paddingVertical: 10,
|
|
231
|
-
borderRadius: 20,
|
|
232
|
-
maxHeight: 100,
|
|
233
|
-
},
|
|
234
|
-
micButton: {
|
|
235
|
-
width: 32,
|
|
236
|
-
height: 40,
|
|
237
|
-
alignItems: 'center',
|
|
238
|
-
justifyContent: 'center',
|
|
239
|
-
},
|
|
240
|
-
actionButton: {
|
|
241
|
-
width: 40,
|
|
242
|
-
height: 40,
|
|
243
|
-
borderRadius: 20,
|
|
244
|
-
alignItems: 'center',
|
|
245
|
-
justifyContent: 'center',
|
|
246
|
-
},
|
|
247
|
-
iconCenter: {
|
|
248
|
-
width: 18,
|
|
249
|
-
height: 18,
|
|
250
|
-
alignItems: 'center',
|
|
251
|
-
justifyContent: 'center',
|
|
252
|
-
},
|
|
253
|
-
arrowIconInner: {
|
|
254
|
-
alignItems: 'center',
|
|
255
|
-
},
|
|
256
|
-
arrowStem: {
|
|
257
|
-
width: 2,
|
|
258
|
-
height: 12,
|
|
259
|
-
borderRadius: 1,
|
|
260
|
-
},
|
|
261
|
-
arrowHead: {
|
|
262
|
-
position: 'absolute',
|
|
263
|
-
top: -1,
|
|
264
|
-
width: 10,
|
|
265
|
-
height: 10,
|
|
266
|
-
borderTopWidth: 2.5,
|
|
267
|
-
borderLeftWidth: 2.5,
|
|
268
|
-
transform: [{ rotate: '45deg' }],
|
|
269
|
-
},
|
|
270
|
-
stopSquare: {
|
|
271
|
-
width: 12,
|
|
272
|
-
height: 12,
|
|
273
|
-
borderRadius: 2,
|
|
274
|
-
backgroundColor: '#FFFFFF',
|
|
275
|
-
},
|
|
276
|
-
soundBars: {
|
|
277
|
-
flexDirection: 'row',
|
|
278
|
-
alignItems: 'flex-end',
|
|
279
|
-
gap: 2,
|
|
280
|
-
},
|
|
281
|
-
soundBar: {
|
|
282
|
-
width: 3,
|
|
283
|
-
backgroundColor: '#FFFFFF',
|
|
284
|
-
borderRadius: 1.5,
|
|
285
|
-
},
|
|
286
|
-
soundBarShort: { height: 6 },
|
|
287
|
-
soundBarMed: { height: 10 },
|
|
288
|
-
soundBarTall: { height: 14 },
|
|
289
|
-
micDot: {
|
|
290
|
-
width: 12,
|
|
291
|
-
height: 12,
|
|
292
|
-
borderRadius: 6,
|
|
293
|
-
},
|
|
294
|
-
});
|