kasunk99-livestream-core 0.3.17 → 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,93 +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
|
-
const baseCellH = useRef(0); // largest height seen = keyboard-free baseline
|
|
47
|
-
const currCellH = useRef(0); // current cell height (may shrink with adjustResize)
|
|
48
|
-
const onCellLayout = useCallback((e) => {
|
|
49
|
-
const h = Math.round(e.nativeEvent.layout.height);
|
|
50
|
-
currCellH.current = h;
|
|
51
|
-
if (h > baseCellH.current)
|
|
52
|
-
baseCellH.current = h;
|
|
53
|
-
}, []);
|
|
54
|
-
// Animated values
|
|
55
|
-
const inputBottom = useRef(new Animated.Value(0)).current;
|
|
56
|
-
const chatAnim = useRef(new Animated.Value(CHAT_BOTTOM_DEFAULT)).current;
|
|
57
|
-
// Remember last offset so re-open positions instantly instead of jumping
|
|
58
|
-
const lastOffsetRef = useRef(0);
|
|
59
|
-
// Compute the correct bottom offset for the floating input.
|
|
60
|
-
// If adjustResize has shrunk the cell (shrink > 0), subtract that from keyboard height
|
|
61
|
-
// so we don't double-offset. If cell hasn't shrunk, use full keyboard height.
|
|
62
|
-
const computeOffset = useCallback((kbH) => {
|
|
63
|
-
const base = baseCellH.current || SCREEN_HEIGHT;
|
|
64
|
-
const cur = currCellH.current || base;
|
|
65
|
-
const shrink = Math.max(0, base - cur);
|
|
66
|
-
return Math.max(BOTTOM_SAFE, kbH - shrink);
|
|
67
|
-
}, []);
|
|
68
|
-
// Pre-position the input immediately when typing starts (uses last known offset)
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
if (isTyping && lastOffsetRef.current > 0) {
|
|
71
|
-
inputBottom.setValue(lastOffsetRef.current);
|
|
72
|
-
chatAnim.setValue(lastOffsetRef.current + 60);
|
|
73
|
-
}
|
|
74
|
-
else if (!isTyping) {
|
|
75
|
-
// Only reset if keyboard is not currently shown (let keyboard events handle active state)
|
|
76
|
-
inputBottom.setValue(0);
|
|
77
|
-
chatAnim.setValue(CHAT_BOTTOM_DEFAULT);
|
|
78
|
-
}
|
|
79
|
-
}, [isTyping, inputBottom, chatAnim]);
|
|
80
|
-
// Keyboard event listeners — platform-specific
|
|
43
|
+
// When keyboard hides (back button, swipe, or after Keyboard.dismiss()), close the input panel.
|
|
81
44
|
useEffect(() => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const offset = computeOffset(e.endCoordinates.height);
|
|
87
|
-
lastOffsetRef.current = offset;
|
|
88
|
-
// Short animation so input slides into position rather than hard-jumping
|
|
89
|
-
Animated.parallel([
|
|
90
|
-
Animated.timing(inputBottom, { toValue: offset, duration: 120, useNativeDriver: false }),
|
|
91
|
-
Animated.timing(chatAnim, { toValue: offset + 60, duration: 120, useNativeDriver: false }),
|
|
92
|
-
]).start();
|
|
93
|
-
});
|
|
94
|
-
const s2 = Keyboard.addListener('keyboardDidHide', () => {
|
|
95
|
-
// Only close typing if it wasn't already closed by sendChat
|
|
96
|
-
setIsTyping((prev) => {
|
|
97
|
-
if (prev) {
|
|
98
|
-
inputBottom.setValue(0);
|
|
99
|
-
chatAnim.setValue(CHAT_BOTTOM_DEFAULT);
|
|
100
|
-
}
|
|
101
|
-
return false;
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
return () => { s1.remove(); s2.remove(); };
|
|
105
|
-
}
|
|
106
|
-
// iOS — keyboardWillShow fires before keyboard so we can animate in sync
|
|
107
|
-
const s1 = Keyboard.addListener('keyboardWillShow', (e) => {
|
|
108
|
-
const offset = computeOffset(e.endCoordinates.height);
|
|
109
|
-
lastOffsetRef.current = offset;
|
|
110
|
-
Animated.parallel([
|
|
111
|
-
Animated.timing(inputBottom, { toValue: offset, duration: e.duration || 250, useNativeDriver: false }),
|
|
112
|
-
Animated.timing(chatAnim, { toValue: offset + 60, duration: e.duration || 250, useNativeDriver: false }),
|
|
113
|
-
]).start();
|
|
114
|
-
});
|
|
115
|
-
const s2 = Keyboard.addListener('keyboardWillHide', (e) => {
|
|
116
|
-
Animated.parallel([
|
|
117
|
-
Animated.timing(inputBottom, { toValue: 0, duration: e.duration || 250, useNativeDriver: false }),
|
|
118
|
-
Animated.timing(chatAnim, { toValue: CHAT_BOTTOM_DEFAULT, duration: e.duration || 250, useNativeDriver: false }),
|
|
119
|
-
]).start(() => setIsTyping(false));
|
|
120
|
-
});
|
|
121
|
-
return () => { s1.remove(); s2.remove(); };
|
|
122
|
-
}, [inputBottom, chatAnim, computeOffset]);
|
|
123
|
-
// 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)
|
|
124
49
|
useEffect(() => {
|
|
125
50
|
if (!isActive) {
|
|
126
51
|
Keyboard.dismiss();
|
|
@@ -140,8 +65,6 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
140
65
|
setChatInput('');
|
|
141
66
|
setIsTyping(false);
|
|
142
67
|
Keyboard.dismiss();
|
|
143
|
-
inputBottom.setValue(0);
|
|
144
|
-
chatAnim.setValue(CHAT_BOTTOM_DEFAULT);
|
|
145
68
|
return;
|
|
146
69
|
}
|
|
147
70
|
if (seededRoomRef.current !== roomId) {
|
|
@@ -149,7 +72,7 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
149
72
|
setChatMessages([]);
|
|
150
73
|
setChatInput('');
|
|
151
74
|
}
|
|
152
|
-
}, [roomId
|
|
75
|
+
}, [roomId]);
|
|
153
76
|
useEffect(() => {
|
|
154
77
|
if (!roomId)
|
|
155
78
|
return;
|
|
@@ -264,21 +187,25 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
264
187
|
const text = chatInput.trim();
|
|
265
188
|
if (!text || !socket || !joined)
|
|
266
189
|
return;
|
|
267
|
-
//
|
|
268
|
-
|
|
190
|
+
// Emit first — state teardown must not precede the network call.
|
|
191
|
+
socket.emit('chat-message', { text });
|
|
269
192
|
setChatInput('');
|
|
270
|
-
setIsTyping(false)
|
|
271
|
-
Keyboard.dismiss();
|
|
272
|
-
socket.emit('chat-message', { text }, (res) => {
|
|
273
|
-
if (res?.error)
|
|
274
|
-
console.log('[viewer] chat send error', res.error);
|
|
275
|
-
});
|
|
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