kasunk99-livestream-core 0.3.18 → 0.3.20
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;AAiB1E,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,uDAob/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, StatusBar, 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,103 +32,53 @@ 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();
|
|
131
52
|
setIsTyping(false);
|
|
132
53
|
}
|
|
133
54
|
}, [isActive]);
|
|
55
|
+
// On Android, override adjustResize → adjustNothing while this stream is active.
|
|
56
|
+
//
|
|
57
|
+
// Why: the FlatList cell has a fixed height (set by LiveStreamFeed's viewportHeight state).
|
|
58
|
+
// With adjustResize the system shrinks the window, which eventually propagates to the cell,
|
|
59
|
+
// but by the time KeyboardAvoidingView fires the cell height has already changed — so KAV
|
|
60
|
+
// sees zero overlap with the keyboard and doesn't lift the content at all.
|
|
61
|
+
//
|
|
62
|
+
// With adjustNothing the window is untouched; KAV measures the exact overlap between its
|
|
63
|
+
// own screen rect and the keyboard rect, reduces its height by that amount, and the input
|
|
64
|
+
// bar ends up flush against the keyboard top with zero manual calculation.
|
|
65
|
+
//
|
|
66
|
+
// keyboardVerticalOffset corrects for the status bar: KAV's onLayout returns a y offset
|
|
67
|
+
// relative to its parent (= 0, top of the cell), but keyboardFrame.screenY is absolute
|
|
68
|
+
// screen coords. Without the offset KAV under-lifts by exactly StatusBar.currentHeight.
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (Platform.OS !== 'android' || !isActive)
|
|
71
|
+
return;
|
|
72
|
+
let cleanup;
|
|
73
|
+
try {
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
75
|
+
const rnkc = require('react-native-keyboard-controller');
|
|
76
|
+
rnkc.KeyboardController.setInputMode(rnkc.AndroidSoftInputModes.SOFT_INPUT_ADJUST_NOTHING);
|
|
77
|
+
cleanup = () => rnkc.KeyboardController.setDefaultMode();
|
|
78
|
+
}
|
|
79
|
+
catch { /* RNKC unavailable — falls back to adjustResize with partial lift */ }
|
|
80
|
+
return cleanup;
|
|
81
|
+
}, [isActive]);
|
|
134
82
|
const seededFromRoomState = useMemo(() => {
|
|
135
83
|
const chat = roomState && typeof roomState === 'object'
|
|
136
84
|
? roomState.chat
|
|
@@ -144,8 +92,6 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
144
92
|
setChatInput('');
|
|
145
93
|
setIsTyping(false);
|
|
146
94
|
Keyboard.dismiss();
|
|
147
|
-
inputBottom.setValue(0);
|
|
148
|
-
chatAnim.setValue(CHAT_BOTTOM_DEFAULT);
|
|
149
95
|
return;
|
|
150
96
|
}
|
|
151
97
|
if (seededRoomRef.current !== roomId) {
|
|
@@ -153,7 +99,7 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
153
99
|
setChatMessages([]);
|
|
154
100
|
setChatInput('');
|
|
155
101
|
}
|
|
156
|
-
}, [roomId
|
|
102
|
+
}, [roomId]);
|
|
157
103
|
useEffect(() => {
|
|
158
104
|
if (!roomId)
|
|
159
105
|
return;
|
|
@@ -268,17 +214,25 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
|
|
|
268
214
|
const text = chatInput.trim();
|
|
269
215
|
if (!text || !socket || !joined)
|
|
270
216
|
return;
|
|
271
|
-
// Emit
|
|
272
|
-
// keyboardDidHide will set isTyping=false and reset animations.
|
|
217
|
+
// Emit first — state teardown must not precede the network call.
|
|
273
218
|
socket.emit('chat-message', { text });
|
|
274
219
|
setChatInput('');
|
|
275
|
-
Keyboard.dismiss();
|
|
220
|
+
Keyboard.dismiss(); // triggers keyboardDidHide → setIsTyping(false)
|
|
276
221
|
};
|
|
277
222
|
const hostLabel = stream.hostDisplayName || stream.title || 'Live';
|
|
278
223
|
const avatarLetter = hostLabel[0]?.toUpperCase() ?? 'L';
|
|
279
224
|
const hasUnsent = chatInput.trim().length > 0;
|
|
280
|
-
|
|
281
|
-
|
|
225
|
+
const canType = joined && !joining && !error;
|
|
226
|
+
return (
|
|
227
|
+
// KeyboardAvoidingView is the root. It measures its own screen rect, computes the exact
|
|
228
|
+
// overlap with the system keyboard, and shrinks its content area accordingly.
|
|
229
|
+
// No manual offset formulas or layout listeners are needed.
|
|
230
|
+
_jsxs(KeyboardAvoidingView, { style: styles.container, behavior: Platform.OS === 'ios' ? 'padding' : 'height', keyboardVerticalOffset: Platform.OS === 'android' ? (StatusBar.currentHeight ?? 0) : 0, 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 ? (
|
|
231
|
+
// Input bar — KAV lifts this to sit flush against the keyboard top.
|
|
232
|
+
_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" }) })] })) : (
|
|
233
|
+
// Pill bar — tapping opens the input bar.
|
|
234
|
+
_jsxs(View, { style: styles.bottomBar, children: [_jsx(TouchableOpacity, { style: [styles.typeTouchable, hasUnsent && styles.typeTouchableFilled], onPress: () => { if (canType)
|
|
235
|
+
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
236
|
});
|
|
283
237
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
284
238
|
const styles = StyleSheet.create({
|
|
@@ -286,12 +240,7 @@ const styles = StyleSheet.create({
|
|
|
286
240
|
flex: 1,
|
|
287
241
|
backgroundColor: '#000',
|
|
288
242
|
},
|
|
289
|
-
rtcView: {
|
|
290
|
-
position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
|
|
291
|
-
backgroundColor: '#000',
|
|
292
|
-
},
|
|
293
243
|
videoPlaceholder: {
|
|
294
|
-
position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
|
|
295
244
|
alignItems: 'center',
|
|
296
245
|
justifyContent: 'center',
|
|
297
246
|
backgroundColor: '#0d0d0d',
|
|
@@ -309,9 +258,6 @@ const styles = StyleSheet.create({
|
|
|
309
258
|
textAlign: 'center',
|
|
310
259
|
paddingHorizontal: 28,
|
|
311
260
|
},
|
|
312
|
-
gradientOverlay: {
|
|
313
|
-
position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
|
|
314
|
-
},
|
|
315
261
|
topBar: {
|
|
316
262
|
position: 'absolute',
|
|
317
263
|
top: 10,
|
|
@@ -382,13 +328,15 @@ const styles = StyleSheet.create({
|
|
|
382
328
|
},
|
|
383
329
|
actionIcon: { fontSize: 22, color: '#fff' },
|
|
384
330
|
actionLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 11, fontWeight: '500' },
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
331
|
+
// Flex spacer — fills remaining vertical space, pushes chat + bars to bottom
|
|
332
|
+
spacer: { flex: 1 },
|
|
333
|
+
// Chat list — normal flow block, grows upward from input bar
|
|
334
|
+
chatList: {
|
|
335
|
+
marginLeft: 12,
|
|
388
336
|
width: '66%',
|
|
389
|
-
maxHeight:
|
|
337
|
+
maxHeight: 200,
|
|
390
338
|
},
|
|
391
|
-
chatContent: { paddingBottom:
|
|
339
|
+
chatContent: { paddingBottom: 4 },
|
|
392
340
|
chatBubble: {
|
|
393
341
|
alignSelf: 'flex-start',
|
|
394
342
|
marginBottom: 5,
|
|
@@ -411,53 +359,19 @@ const styles = StyleSheet.create({
|
|
|
411
359
|
chatUsername: { fontWeight: '700' },
|
|
412
360
|
chatMsg: { color: '#e5e5e5', fontWeight: '400' },
|
|
413
361
|
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,
|
|
362
|
+
// Input bar — normal flow, KAV lifts it above keyboard automatically
|
|
363
|
+
inputBar: {
|
|
451
364
|
flexDirection: 'row',
|
|
452
365
|
alignItems: 'center',
|
|
453
366
|
backgroundColor: 'rgba(20,20,20,0.97)',
|
|
454
367
|
paddingHorizontal: 12,
|
|
455
|
-
|
|
368
|
+
paddingTop: 8,
|
|
369
|
+
paddingBottom: BOTTOM_SAFE + 8,
|
|
456
370
|
gap: 8,
|
|
457
371
|
borderTopWidth: StyleSheet.hairlineWidth,
|
|
458
372
|
borderTopColor: 'rgba(255,255,255,0.10)',
|
|
459
373
|
},
|
|
460
|
-
|
|
374
|
+
textInput: {
|
|
461
375
|
flex: 1,
|
|
462
376
|
backgroundColor: 'rgba(255,255,255,0.10)',
|
|
463
377
|
borderRadius: 20,
|
|
@@ -467,7 +381,7 @@ const styles = StyleSheet.create({
|
|
|
467
381
|
fontSize: 14,
|
|
468
382
|
minHeight: 40,
|
|
469
383
|
},
|
|
470
|
-
|
|
384
|
+
sendBtn: {
|
|
471
385
|
width: 40,
|
|
472
386
|
height: 40,
|
|
473
387
|
borderRadius: 20,
|
|
@@ -477,6 +391,32 @@ const styles = StyleSheet.create({
|
|
|
477
391
|
},
|
|
478
392
|
sendBtnOff: { backgroundColor: 'rgba(255,255,255,0.12)' },
|
|
479
393
|
sendIcon: { color: '#fff', fontSize: 15, fontWeight: '700' },
|
|
394
|
+
// Pill bar — normal flow, visible when keyboard is closed
|
|
395
|
+
bottomBar: {
|
|
396
|
+
flexDirection: 'row',
|
|
397
|
+
alignItems: 'center',
|
|
398
|
+
paddingHorizontal: 12,
|
|
399
|
+
paddingBottom: BOTTOM_SAFE,
|
|
400
|
+
paddingTop: 8,
|
|
401
|
+
gap: 6,
|
|
402
|
+
},
|
|
403
|
+
typeTouchable: {
|
|
404
|
+
flex: 1,
|
|
405
|
+
backgroundColor: 'rgba(255,255,255,0.12)',
|
|
406
|
+
borderRadius: 20,
|
|
407
|
+
paddingHorizontal: 14,
|
|
408
|
+
paddingVertical: 10,
|
|
409
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
410
|
+
borderColor: 'rgba(255,255,255,0.18)',
|
|
411
|
+
},
|
|
412
|
+
typeTouchableFilled: {
|
|
413
|
+
backgroundColor: 'rgba(255,255,255,0.18)',
|
|
414
|
+
borderColor: 'rgba(255,255,255,0.30)',
|
|
415
|
+
},
|
|
416
|
+
typePlaceholder: { color: 'rgba(255,255,255,0.50)', fontSize: 14 },
|
|
417
|
+
typePlaceholderFilled: { color: '#fff' },
|
|
418
|
+
bottomIconBtn: { width: 40, height: 40, alignItems: 'center', justifyContent: 'center' },
|
|
419
|
+
bottomIconGlyph: { fontSize: 22 },
|
|
480
420
|
endedOverlay: {
|
|
481
421
|
position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
|
|
482
422
|
backgroundColor: 'rgba(0,0,0,0.80)',
|
package/package.json
CHANGED