kasunk99-livestream-core 0.3.18 → 0.3.19
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LiveStreamViewerItem.d.ts","sourceRoot":"","sources":["../../src/components/LiveStreamViewerItem.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"LiveStreamViewerItem.d.ts","sourceRoot":"","sources":["../../src/components/LiveStreamViewerItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqD,MAAM,OAAO,CAAC;AAgB1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAM/C,KAAK,yBAAyB,GAAG;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAwBF,eAAO,MAAM,oBAAoB,uDAoZ/B,CAAC"}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { memo,
|
|
3
|
-
import { ActivityIndicator,
|
|
2
|
+
import { memo, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { ActivityIndicator, Keyboard, KeyboardAvoidingView, NativeModules, Platform, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View, } from 'react-native';
|
|
4
4
|
import { useViewerSocket } from '../hooks/useViewerSocket';
|
|
5
|
-
const SCREEN_HEIGHT = Dimensions.get('window').height;
|
|
6
5
|
const CHAT_NAME_COLORS = ['#f97316', '#22c55e', '#3b82f6', '#eab308', '#ec4899', '#a855f7'];
|
|
7
6
|
const BOTTOM_SAFE = Platform.OS === 'ios' ? 28 : 10;
|
|
8
7
|
const BOTTOM_BAR_H = 58;
|
|
9
|
-
const CHAT_BOTTOM_DEFAULT = BOTTOM_SAFE + BOTTOM_BAR_H + 8;
|
|
10
8
|
let RTCViewComponent = null;
|
|
11
9
|
try {
|
|
12
10
|
const webrtc = require('react-native-webrtc');
|
|
@@ -34,97 +32,20 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
34
32
|
? displayStreamObj.toURL()
|
|
35
33
|
: undefined;
|
|
36
34
|
const showVideo = RTCViewComponent && remoteStream && !!streamURL && hasVideoTrack;
|
|
37
|
-
// chatInput persists when keyboard
|
|
35
|
+
// chatInput persists when keyboard is dismissed without sending (cleared only on send)
|
|
38
36
|
const [chatInput, setChatInput] = useState('');
|
|
39
37
|
const [chatMessages, setChatMessages] = useState([]);
|
|
40
|
-
// isTyping
|
|
38
|
+
// isTyping controls whether we show the TextInput bar or the pill bar
|
|
41
39
|
const [isTyping, setIsTyping] = useState(false);
|
|
42
40
|
const seededRoomRef = useRef(null);
|
|
43
41
|
const chatListRef = useRef(null);
|
|
44
42
|
const textInputRef = useRef(null);
|
|
45
|
-
//
|
|
46
|
-
// Initialise baseCellH with the same estimate LiveStreamFeed uses for viewportHeight
|
|
47
|
-
// so the gap formula works correctly even before onCellLayout fires.
|
|
48
|
-
const baseCellH = useRef(SCREEN_HEIGHT - 120);
|
|
49
|
-
const currCellH = useRef(SCREEN_HEIGHT - 120);
|
|
50
|
-
const onCellLayout = useCallback((e) => {
|
|
51
|
-
const h = Math.round(e.nativeEvent.layout.height);
|
|
52
|
-
currCellH.current = h;
|
|
53
|
-
if (h > baseCellH.current)
|
|
54
|
-
baseCellH.current = h;
|
|
55
|
-
}, []);
|
|
56
|
-
// Animated values
|
|
57
|
-
const inputBottom = useRef(new Animated.Value(0)).current;
|
|
58
|
-
const chatAnim = useRef(new Animated.Value(CHAT_BOTTOM_DEFAULT)).current;
|
|
59
|
-
// Remember last offset so re-open positions instantly instead of jumping
|
|
60
|
-
const lastOffsetRef = useRef(0);
|
|
61
|
-
// Compute the correct bottom offset for the floating input.
|
|
62
|
-
//
|
|
63
|
-
// The cell sits inside a tab navigator whose height is less than the full window.
|
|
64
|
-
// `kbH` from the keyboard event is measured from the bottom of the WINDOW, not the
|
|
65
|
-
// bottom of the CELL. We subtract `cellBottomGap` (window bottom → cell bottom) to
|
|
66
|
-
// get the offset relative to the cell.
|
|
67
|
-
//
|
|
68
|
-
// If adjustResize has also shrunk the cell, we subtract that too (avoids double-offset).
|
|
69
|
-
const computeOffset = useCallback((kbH) => {
|
|
70
|
-
const base = baseCellH.current;
|
|
71
|
-
const cur = currCellH.current;
|
|
72
|
-
const cellBottomGap = Math.max(0, SCREEN_HEIGHT - base); // tab-nav area below cell
|
|
73
|
-
const shrink = Math.max(0, base - cur); // adjustResize shrink (if any)
|
|
74
|
-
return Math.max(BOTTOM_SAFE, kbH - cellBottomGap - shrink);
|
|
75
|
-
}, []);
|
|
76
|
-
// Pre-position the input immediately when typing starts (uses last known offset).
|
|
77
|
-
// Keyboard listeners own the reset when isTyping becomes false.
|
|
78
|
-
useEffect(() => {
|
|
79
|
-
if (isTyping && lastOffsetRef.current > 0) {
|
|
80
|
-
inputBottom.setValue(lastOffsetRef.current);
|
|
81
|
-
chatAnim.setValue(lastOffsetRef.current + 60);
|
|
82
|
-
}
|
|
83
|
-
}, [isTyping, inputBottom, chatAnim]);
|
|
84
|
-
// Keyboard event listeners — platform-specific
|
|
43
|
+
// When keyboard hides (back button, swipe, or after Keyboard.dismiss()), close the input panel.
|
|
85
44
|
useEffect(() => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const offset = computeOffset(e.endCoordinates.height);
|
|
91
|
-
lastOffsetRef.current = offset;
|
|
92
|
-
// Short animation so input slides into position rather than hard-jumping
|
|
93
|
-
Animated.parallel([
|
|
94
|
-
Animated.timing(inputBottom, { toValue: offset, duration: 120, useNativeDriver: false }),
|
|
95
|
-
Animated.timing(chatAnim, { toValue: offset + 60, duration: 120, useNativeDriver: false }),
|
|
96
|
-
]).start();
|
|
97
|
-
});
|
|
98
|
-
const s2 = Keyboard.addListener('keyboardDidHide', () => {
|
|
99
|
-
// Only close typing if it wasn't already closed by sendChat
|
|
100
|
-
setIsTyping((prev) => {
|
|
101
|
-
if (prev) {
|
|
102
|
-
inputBottom.setValue(0);
|
|
103
|
-
chatAnim.setValue(CHAT_BOTTOM_DEFAULT);
|
|
104
|
-
}
|
|
105
|
-
return false;
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
return () => { s1.remove(); s2.remove(); };
|
|
109
|
-
}
|
|
110
|
-
// iOS — keyboardWillShow fires before keyboard so we can animate in sync
|
|
111
|
-
const s1 = Keyboard.addListener('keyboardWillShow', (e) => {
|
|
112
|
-
const offset = computeOffset(e.endCoordinates.height);
|
|
113
|
-
lastOffsetRef.current = offset;
|
|
114
|
-
Animated.parallel([
|
|
115
|
-
Animated.timing(inputBottom, { toValue: offset, duration: e.duration || 250, useNativeDriver: false }),
|
|
116
|
-
Animated.timing(chatAnim, { toValue: offset + 60, duration: e.duration || 250, useNativeDriver: false }),
|
|
117
|
-
]).start();
|
|
118
|
-
});
|
|
119
|
-
const s2 = Keyboard.addListener('keyboardWillHide', (e) => {
|
|
120
|
-
Animated.parallel([
|
|
121
|
-
Animated.timing(inputBottom, { toValue: 0, duration: e.duration || 250, useNativeDriver: false }),
|
|
122
|
-
Animated.timing(chatAnim, { toValue: CHAT_BOTTOM_DEFAULT, duration: e.duration || 250, useNativeDriver: false }),
|
|
123
|
-
]).start(() => setIsTyping(false));
|
|
124
|
-
});
|
|
125
|
-
return () => { s1.remove(); s2.remove(); };
|
|
126
|
-
}, [inputBottom, chatAnim, computeOffset]);
|
|
127
|
-
// Dismiss keyboard when stream becomes inactive
|
|
45
|
+
const sub = Keyboard.addListener('keyboardDidHide', () => setIsTyping(false));
|
|
46
|
+
return () => sub.remove();
|
|
47
|
+
}, []);
|
|
48
|
+
// Dismiss input when stream becomes inactive (e.g. user swipes to next stream)
|
|
128
49
|
useEffect(() => {
|
|
129
50
|
if (!isActive) {
|
|
130
51
|
Keyboard.dismiss();
|
|
@@ -144,8 +65,6 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
144
65
|
setChatInput('');
|
|
145
66
|
setIsTyping(false);
|
|
146
67
|
Keyboard.dismiss();
|
|
147
|
-
inputBottom.setValue(0);
|
|
148
|
-
chatAnim.setValue(CHAT_BOTTOM_DEFAULT);
|
|
149
68
|
return;
|
|
150
69
|
}
|
|
151
70
|
if (seededRoomRef.current !== roomId) {
|
|
@@ -153,7 +72,7 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
153
72
|
setChatMessages([]);
|
|
154
73
|
setChatInput('');
|
|
155
74
|
}
|
|
156
|
-
}, [roomId
|
|
75
|
+
}, [roomId]);
|
|
157
76
|
useEffect(() => {
|
|
158
77
|
if (!roomId)
|
|
159
78
|
return;
|
|
@@ -268,17 +187,25 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
268
187
|
const text = chatInput.trim();
|
|
269
188
|
if (!text || !socket || !joined)
|
|
270
189
|
return;
|
|
271
|
-
// Emit
|
|
272
|
-
// keyboardDidHide will set isTyping=false and reset animations.
|
|
190
|
+
// Emit first — state teardown must not precede the network call.
|
|
273
191
|
socket.emit('chat-message', { text });
|
|
274
192
|
setChatInput('');
|
|
275
|
-
Keyboard.dismiss();
|
|
193
|
+
Keyboard.dismiss(); // triggers keyboardDidHide → setIsTyping(false)
|
|
276
194
|
};
|
|
277
195
|
const hostLabel = stream.hostDisplayName || stream.title || 'Live';
|
|
278
196
|
const avatarLetter = hostLabel[0]?.toUpperCase() ?? 'L';
|
|
279
197
|
const hasUnsent = chatInput.trim().length > 0;
|
|
280
|
-
|
|
281
|
-
|
|
198
|
+
const canType = joined && !joining && !error;
|
|
199
|
+
return (
|
|
200
|
+
// KeyboardAvoidingView is the root. It measures its own screen rect, computes the exact
|
|
201
|
+
// overlap with the system keyboard, and shrinks its content area accordingly.
|
|
202
|
+
// No manual offset formulas or layout listeners are needed.
|
|
203
|
+
_jsxs(KeyboardAvoidingView, { style: styles.container, behavior: Platform.OS === 'ios' ? 'padding' : 'height', children: [showVideo && RTCViewComponent && streamURL ? (_jsx(RTCViewComponent, { streamURL: streamURL, stream: displayStream, style: StyleSheet.absoluteFillObject, objectFit: "cover", mirror: false }, `rtc-${trackCount}-${streamURL}`)) : (_jsxs(View, { style: [StyleSheet.absoluteFillObject, styles.videoPlaceholder], children: [joining && _jsx(ActivityIndicator, { size: "large", color: "rgba(255,255,255,0.6)" }), !joining && error && _jsx(Text, { style: styles.placeholderError, children: error }), joined && !joining && !error && hasVideo && !remoteStream && (_jsx(ActivityIndicator, { size: "large", color: "rgba(255,255,255,0.45)" })), joined && !joining && !error && hasVideo && webrtcUnavailable && (_jsx(Text, { style: styles.placeholderHint, children: "Video needs a development build" })), joined && !joining && !error && hasVideo && consumeError && (_jsx(Text, { style: styles.placeholderError, children: consumeError }))] })), LinearGradient ? (_jsx(LinearGradient, { colors: ['rgba(0,0,0,0.70)', 'rgba(0,0,0,0.0)', 'rgba(0,0,0,0.0)', 'rgba(0,0,0,0.78)'], locations: [0, 0.28, 0.48, 1], style: StyleSheet.absoluteFillObject, pointerEvents: "none" })) : null, _jsxs(View, { style: styles.topBar, pointerEvents: "box-none", children: [_jsxs(View, { style: styles.hostRow, pointerEvents: "none", children: [_jsx(View, { style: styles.avatar, children: _jsx(Text, { style: styles.avatarLetter, children: avatarLetter }) }), _jsx(Text, { style: styles.hostName, numberOfLines: 1, children: hostLabel }), _jsx(View, { style: styles.livePill, children: _jsx(Text, { style: styles.livePillText, children: "LIVE" }) })] }), _jsxs(View, { style: styles.viewerChip, pointerEvents: "none", children: [_jsx(Text, { style: styles.viewerEye, children: "\uD83D\uDC41" }), _jsx(Text, { style: styles.viewerCount, children: viewerCount > 0 ? viewerCount.toLocaleString() : '—' })] })] }), !isTyping && (_jsxs(View, { style: styles.rightColumn, pointerEvents: "box-none", children: [_jsxs(TouchableOpacity, { style: styles.actionBtn, activeOpacity: 0.75, children: [_jsx(View, { style: styles.actionCircle, children: _jsx(Text, { style: styles.actionIcon, children: "\u2665" }) }), _jsx(Text, { style: styles.actionLabel, children: "Like" })] }), _jsxs(TouchableOpacity, { style: styles.actionBtn, activeOpacity: 0.75, children: [_jsx(View, { style: styles.actionCircle, children: _jsx(Text, { style: styles.actionIcon, children: "\uD83C\uDF81" }) }), _jsx(Text, { style: styles.actionLabel, children: "Gift" })] }), _jsxs(TouchableOpacity, { style: styles.actionBtn, activeOpacity: 0.75, children: [_jsx(View, { style: styles.actionCircle, children: _jsx(Text, { style: styles.actionIcon, children: "\u2197" }) }), _jsx(Text, { style: styles.actionLabel, children: "Share" })] })] })), _jsx(View, { style: styles.spacer, pointerEvents: "none" }), _jsx(ScrollView, { ref: chatListRef, style: styles.chatList, contentContainerStyle: styles.chatContent, showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "always", children: chatData.map((item) => item.displayName ? (_jsx(View, { style: styles.chatBubble, children: _jsxs(Text, { style: styles.chatLine, numberOfLines: 3, children: [_jsxs(Text, { style: [styles.chatUsername, { color: getNameColor(item.displayName) }], children: [item.displayName, ' '] }), _jsx(Text, { style: styles.chatMsg, children: item.text })] }) }, item.id)) : (_jsx(View, { style: styles.joinBubble, children: _jsx(Text, { style: styles.joinText, children: item.text }) }, item.id))) }), isTyping ? (
|
|
204
|
+
// Input bar — KAV lifts this to sit flush against the keyboard top.
|
|
205
|
+
_jsxs(View, { style: styles.inputBar, children: [_jsx(TextInput, { ref: textInputRef, style: styles.textInput, value: chatInput, onChangeText: setChatInput, placeholder: "Say something...", placeholderTextColor: "rgba(255,255,255,0.40)", editable: canType, onSubmitEditing: sendChat, returnKeyType: "send", submitBehavior: "submit", autoFocus: true }), _jsx(TouchableOpacity, { style: [styles.sendBtn, (!hasUnsent || !joined) && styles.sendBtnOff], onPress: sendChat, disabled: !joined || !hasUnsent, activeOpacity: 0.75, children: _jsx(Text, { style: styles.sendIcon, children: "\u25B6" }) })] })) : (
|
|
206
|
+
// Pill bar — tapping opens the input bar.
|
|
207
|
+
_jsxs(View, { style: styles.bottomBar, children: [_jsx(TouchableOpacity, { style: [styles.typeTouchable, hasUnsent && styles.typeTouchableFilled], onPress: () => { if (canType)
|
|
208
|
+
setIsTyping(true); }, activeOpacity: 0.7, children: _jsx(Text, { style: [styles.typePlaceholder, hasUnsent && styles.typePlaceholderFilled], numberOfLines: 1, children: hasUnsent ? chatInput : (joined && !joining ? 'Say something...' : 'Connecting...') }) }), _jsx(TouchableOpacity, { style: styles.bottomIconBtn, activeOpacity: 0.7, children: _jsx(Text, { style: styles.bottomIconGlyph, children: "\u263A" }) }), _jsx(TouchableOpacity, { style: styles.bottomIconBtn, activeOpacity: 0.7, children: _jsx(Text, { style: styles.bottomIconGlyph, children: "\uD83C\uDF39" }) }), _jsx(TouchableOpacity, { style: styles.bottomIconBtn, activeOpacity: 0.7, children: _jsx(Text, { style: styles.bottomIconGlyph, children: "\uD83C\uDF81" }) }), _jsx(TouchableOpacity, { style: styles.bottomIconBtn, activeOpacity: 0.7, children: _jsx(Text, { style: styles.bottomIconGlyph, children: "\u2197" }) })] })), streamEnded && (_jsx(View, { style: styles.endedOverlay, children: _jsxs(View, { style: styles.endedCard, children: [_jsx(Text, { style: styles.endedTitle, children: "Stream ended" }), _jsx(Text, { style: styles.endedSub, children: "The host has ended this live stream" })] }) }))] }));
|
|
282
209
|
});
|
|
283
210
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
284
211
|
const styles = StyleSheet.create({
|
|
@@ -286,12 +213,7 @@ const styles = StyleSheet.create({
|
|
|
286
213
|
flex: 1,
|
|
287
214
|
backgroundColor: '#000',
|
|
288
215
|
},
|
|
289
|
-
rtcView: {
|
|
290
|
-
position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
|
|
291
|
-
backgroundColor: '#000',
|
|
292
|
-
},
|
|
293
216
|
videoPlaceholder: {
|
|
294
|
-
position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
|
|
295
217
|
alignItems: 'center',
|
|
296
218
|
justifyContent: 'center',
|
|
297
219
|
backgroundColor: '#0d0d0d',
|
|
@@ -309,9 +231,6 @@ const styles = StyleSheet.create({
|
|
|
309
231
|
textAlign: 'center',
|
|
310
232
|
paddingHorizontal: 28,
|
|
311
233
|
},
|
|
312
|
-
gradientOverlay: {
|
|
313
|
-
position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
|
|
314
|
-
},
|
|
315
234
|
topBar: {
|
|
316
235
|
position: 'absolute',
|
|
317
236
|
top: 10,
|
|
@@ -382,13 +301,15 @@ const styles = StyleSheet.create({
|
|
|
382
301
|
},
|
|
383
302
|
actionIcon: { fontSize: 22, color: '#fff' },
|
|
384
303
|
actionLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 11, fontWeight: '500' },
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
304
|
+
// Flex spacer — fills remaining vertical space, pushes chat + bars to bottom
|
|
305
|
+
spacer: { flex: 1 },
|
|
306
|
+
// Chat list — normal flow block, grows upward from input bar
|
|
307
|
+
chatList: {
|
|
308
|
+
marginLeft: 12,
|
|
388
309
|
width: '66%',
|
|
389
|
-
maxHeight:
|
|
310
|
+
maxHeight: 200,
|
|
390
311
|
},
|
|
391
|
-
chatContent: { paddingBottom:
|
|
312
|
+
chatContent: { paddingBottom: 4 },
|
|
392
313
|
chatBubble: {
|
|
393
314
|
alignSelf: 'flex-start',
|
|
394
315
|
marginBottom: 5,
|
|
@@ -411,53 +332,19 @@ const styles = StyleSheet.create({
|
|
|
411
332
|
chatUsername: { fontWeight: '700' },
|
|
412
333
|
chatMsg: { color: '#e5e5e5', fontWeight: '400' },
|
|
413
334
|
joinText: { color: 'rgba(255,255,255,0.48)', fontSize: 12, fontStyle: 'italic' },
|
|
414
|
-
//
|
|
415
|
-
|
|
416
|
-
position: 'absolute',
|
|
417
|
-
bottom: BOTTOM_SAFE,
|
|
418
|
-
left: 12,
|
|
419
|
-
right: 12,
|
|
420
|
-
flexDirection: 'row',
|
|
421
|
-
alignItems: 'center',
|
|
422
|
-
gap: 6,
|
|
423
|
-
},
|
|
424
|
-
typeTouchable: {
|
|
425
|
-
flex: 1,
|
|
426
|
-
backgroundColor: 'rgba(255,255,255,0.12)',
|
|
427
|
-
borderRadius: 20,
|
|
428
|
-
paddingHorizontal: 14,
|
|
429
|
-
paddingVertical: 10,
|
|
430
|
-
borderWidth: StyleSheet.hairlineWidth,
|
|
431
|
-
borderColor: 'rgba(255,255,255,0.18)',
|
|
432
|
-
},
|
|
433
|
-
typeTouchableFilled: {
|
|
434
|
-
backgroundColor: 'rgba(255,255,255,0.18)',
|
|
435
|
-
borderColor: 'rgba(255,255,255,0.30)',
|
|
436
|
-
},
|
|
437
|
-
typePlaceholder: {
|
|
438
|
-
color: 'rgba(255,255,255,0.50)',
|
|
439
|
-
fontSize: 14,
|
|
440
|
-
},
|
|
441
|
-
typePlaceholderFilled: {
|
|
442
|
-
color: '#fff',
|
|
443
|
-
},
|
|
444
|
-
bottomIconBtn: { width: 40, height: 40, alignItems: 'center', justifyContent: 'center' },
|
|
445
|
-
bottomIconGlyph: { fontSize: 22 },
|
|
446
|
-
// Floating input — absolute, bottom driven by Animated.Value
|
|
447
|
-
floatingInputWrap: {
|
|
448
|
-
position: 'absolute',
|
|
449
|
-
left: 0,
|
|
450
|
-
right: 0,
|
|
335
|
+
// Input bar — normal flow, KAV lifts it above keyboard automatically
|
|
336
|
+
inputBar: {
|
|
451
337
|
flexDirection: 'row',
|
|
452
338
|
alignItems: 'center',
|
|
453
339
|
backgroundColor: 'rgba(20,20,20,0.97)',
|
|
454
340
|
paddingHorizontal: 12,
|
|
455
|
-
|
|
341
|
+
paddingTop: 8,
|
|
342
|
+
paddingBottom: BOTTOM_SAFE + 8,
|
|
456
343
|
gap: 8,
|
|
457
344
|
borderTopWidth: StyleSheet.hairlineWidth,
|
|
458
345
|
borderTopColor: 'rgba(255,255,255,0.10)',
|
|
459
346
|
},
|
|
460
|
-
|
|
347
|
+
textInput: {
|
|
461
348
|
flex: 1,
|
|
462
349
|
backgroundColor: 'rgba(255,255,255,0.10)',
|
|
463
350
|
borderRadius: 20,
|
|
@@ -467,7 +354,7 @@ const styles = StyleSheet.create({
|
|
|
467
354
|
fontSize: 14,
|
|
468
355
|
minHeight: 40,
|
|
469
356
|
},
|
|
470
|
-
|
|
357
|
+
sendBtn: {
|
|
471
358
|
width: 40,
|
|
472
359
|
height: 40,
|
|
473
360
|
borderRadius: 20,
|
|
@@ -477,6 +364,32 @@ const styles = StyleSheet.create({
|
|
|
477
364
|
},
|
|
478
365
|
sendBtnOff: { backgroundColor: 'rgba(255,255,255,0.12)' },
|
|
479
366
|
sendIcon: { color: '#fff', fontSize: 15, fontWeight: '700' },
|
|
367
|
+
// Pill bar — normal flow, visible when keyboard is closed
|
|
368
|
+
bottomBar: {
|
|
369
|
+
flexDirection: 'row',
|
|
370
|
+
alignItems: 'center',
|
|
371
|
+
paddingHorizontal: 12,
|
|
372
|
+
paddingBottom: BOTTOM_SAFE,
|
|
373
|
+
paddingTop: 8,
|
|
374
|
+
gap: 6,
|
|
375
|
+
},
|
|
376
|
+
typeTouchable: {
|
|
377
|
+
flex: 1,
|
|
378
|
+
backgroundColor: 'rgba(255,255,255,0.12)',
|
|
379
|
+
borderRadius: 20,
|
|
380
|
+
paddingHorizontal: 14,
|
|
381
|
+
paddingVertical: 10,
|
|
382
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
383
|
+
borderColor: 'rgba(255,255,255,0.18)',
|
|
384
|
+
},
|
|
385
|
+
typeTouchableFilled: {
|
|
386
|
+
backgroundColor: 'rgba(255,255,255,0.18)',
|
|
387
|
+
borderColor: 'rgba(255,255,255,0.30)',
|
|
388
|
+
},
|
|
389
|
+
typePlaceholder: { color: 'rgba(255,255,255,0.50)', fontSize: 14 },
|
|
390
|
+
typePlaceholderFilled: { color: '#fff' },
|
|
391
|
+
bottomIconBtn: { width: 40, height: 40, alignItems: 'center', justifyContent: 'center' },
|
|
392
|
+
bottomIconGlyph: { fontSize: 22 },
|
|
480
393
|
endedOverlay: {
|
|
481
394
|
position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
|
|
482
395
|
backgroundColor: 'rgba(0,0,0,0.80)',
|
package/package.json
CHANGED