kasunk99-livestream-core 0.3.16 → 0.3.18

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,KAAqD,MAAM,OAAO,CAAC;AAiB1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAU/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,uDAub/B,CAAC"}
1
+ {"version":3,"file":"LiveStreamViewerItem.d.ts","sourceRoot":"","sources":["../../src/components/LiveStreamViewerItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkE,MAAM,OAAO,CAAC;AAkBvF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAQ/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,uDAqe/B,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { memo, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { ActivityIndicator, Animated, Dimensions, Keyboard, NativeModules, Platform, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View, } from 'react-native';
4
4
  import { useViewerSocket } from '../hooks/useViewerSocket';
5
5
  const SCREEN_HEIGHT = Dimensions.get('window').height;
@@ -7,8 +7,6 @@ const CHAT_NAME_COLORS = ['#f97316', '#22c55e', '#3b82f6', '#eab308', '#ec4899',
7
7
  const BOTTOM_SAFE = Platform.OS === 'ios' ? 28 : 10;
8
8
  const BOTTOM_BAR_H = 58;
9
9
  const CHAT_BOTTOM_DEFAULT = BOTTOM_SAFE + BOTTOM_BAR_H + 8;
10
- // Chat shifts above the floating input bar when typing
11
- const CHAT_BOTTOM_TYPING = BOTTOM_SAFE + 68;
12
10
  let RTCViewComponent = null;
13
11
  try {
14
12
  const webrtc = require('react-native-webrtc');
@@ -36,60 +34,97 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
36
34
  ? displayStreamObj.toURL()
37
35
  : undefined;
38
36
  const showVideo = RTCViewComponent && remoteStream && !!streamURL && hasVideoTrack;
37
+ // chatInput persists when keyboard closes without sending — cleared only on send
39
38
  const [chatInput, setChatInput] = useState('');
40
39
  const [chatMessages, setChatMessages] = useState([]);
40
+ // isTyping: user has opened the input panel
41
41
  const [isTyping, setIsTyping] = useState(false);
42
42
  const seededRoomRef = useRef(null);
43
43
  const chatListRef = useRef(null);
44
44
  const textInputRef = useRef(null);
45
- // inputBottom: Android stays fixed at BOTTOM_SAFE (adjustResize handles the rest).
46
- // iOS animates to keyboard height.
47
- const inputBottom = useRef(new Animated.Value(BOTTOM_SAFE)).current;
45
+ // Cell layout tracking used to compensate for adjustResize shrinking the cell.
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;
48
58
  const chatAnim = useRef(new Animated.Value(CHAT_BOTTOM_DEFAULT)).current;
49
- // Android: adjustResize shrinks the container when the keyboard appears, so the
50
- // input at bottom:BOTTOM_SAFE naturally ends up above the keyboard — no height math.
51
- // Chat column animates up when typing starts (before keyboard appears) so it clears
52
- // the floating input bar. keyboardDidHide resets everything.
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.
53
62
  //
54
- // iOS: adjustResize does NOT apply. Keyboard covers content. We must use
55
- // keyboardWillShow to push both the input bar and chat above the keyboard.
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
56
85
  useEffect(() => {
57
86
  if (Platform.OS === 'android') {
58
- const sub = Keyboard.addListener('keyboardDidHide', () => {
59
- setIsTyping(false);
60
- chatAnim.setValue(CHAT_BOTTOM_DEFAULT);
87
+ // keyboardDidShow fires AFTER keyboard animation + AFTER adjustResize layout.
88
+ // currCellH.current has the post-resize value at this point.
89
+ const s1 = Keyboard.addListener('keyboardDidShow', (e) => {
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();
61
97
  });
62
- return () => sub.remove();
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(); };
63
109
  }
64
- // iOS — animate with keyboard
110
+ // iOS — keyboardWillShow fires before keyboard so we can animate in sync
65
111
  const s1 = Keyboard.addListener('keyboardWillShow', (e) => {
66
- const h = e.endCoordinates.height;
67
- const dur = e.duration || 250;
112
+ const offset = computeOffset(e.endCoordinates.height);
113
+ lastOffsetRef.current = offset;
68
114
  Animated.parallel([
69
- Animated.timing(inputBottom, { toValue: h, duration: dur, useNativeDriver: false }),
70
- Animated.timing(chatAnim, { toValue: h + 60, duration: dur, useNativeDriver: false }),
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 }),
71
117
  ]).start();
72
118
  });
73
119
  const s2 = Keyboard.addListener('keyboardWillHide', (e) => {
74
- const dur = e.duration || 250;
75
120
  Animated.parallel([
76
- Animated.timing(inputBottom, { toValue: BOTTOM_SAFE, duration: dur, useNativeDriver: false }),
77
- Animated.timing(chatAnim, { toValue: CHAT_BOTTOM_DEFAULT, duration: dur, useNativeDriver: false }),
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 }),
78
123
  ]).start(() => setIsTyping(false));
79
124
  });
80
125
  return () => { s1.remove(); s2.remove(); };
81
- }, [inputBottom, chatAnim]);
82
- // Animate chat up immediately when isTyping flips (Android: before keyboard appears)
83
- useEffect(() => {
84
- if (Platform.OS !== 'android')
85
- return;
86
- Animated.timing(chatAnim, {
87
- toValue: isTyping ? CHAT_BOTTOM_TYPING : CHAT_BOTTOM_DEFAULT,
88
- duration: 200,
89
- useNativeDriver: false,
90
- }).start();
91
- }, [isTyping, chatAnim]);
92
- // Dismiss keyboard and reset when stream becomes inactive
126
+ }, [inputBottom, chatAnim, computeOffset]);
127
+ // Dismiss keyboard when stream becomes inactive
93
128
  useEffect(() => {
94
129
  if (!isActive) {
95
130
  Keyboard.dismiss();
@@ -109,7 +144,7 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
109
144
  setChatInput('');
110
145
  setIsTyping(false);
111
146
  Keyboard.dismiss();
112
- inputBottom.setValue(BOTTOM_SAFE);
147
+ inputBottom.setValue(0);
113
148
  chatAnim.setValue(CHAT_BOTTOM_DEFAULT);
114
149
  return;
115
150
  }
@@ -233,16 +268,17 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
233
268
  const text = chatInput.trim();
234
269
  if (!text || !socket || !joined)
235
270
  return;
271
+ // Emit FIRST so the message reaches the server before any cleanup.
272
+ // keyboardDidHide will set isTyping=false and reset animations.
273
+ socket.emit('chat-message', { text });
236
274
  setChatInput('');
237
- socket.emit('chat-message', { text }, (res) => {
238
- if (res?.error)
239
- console.log('[viewer] chat send error', res.error);
240
- });
275
+ Keyboard.dismiss();
241
276
  };
242
277
  const hostLabel = stream.hostDisplayName || stream.title || 'Live';
243
278
  const avatarLetter = hostLabel[0]?.toUpperCase() ?? 'L';
244
- return (_jsxs(View, { style: styles.container, children: [showVideo && RTCViewComponent && streamURL ? (_jsx(RTCViewComponent, { streamURL: streamURL, stream: displayStream, style: styles.rtcView, objectFit: "cover", mirror: false }, `rtc-${trackCount}-${streamURL}`)) : (_jsxs(View, { style: 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: styles.gradientOverlay, 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(Animated.View, { style: [styles.chatColumn, { bottom: chatAnim }], pointerEvents: "box-none", children: _jsx(ScrollView, { ref: chatListRef, 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 && (_jsxs(View, { style: styles.bottomBar, children: [_jsx(TouchableOpacity, { style: styles.typeTouchable, onPress: () => { if (joined && !joining && !error)
245
- setIsTyping(true); }, activeOpacity: 0.7, children: _jsx(Text, { style: styles.typePlaceholder, children: 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" }) })] })), isTyping && (_jsxs(Animated.View, { style: [styles.floatingInputWrap, { bottom: inputBottom }], children: [_jsx(TextInput, { ref: textInputRef, style: styles.floatingTextInput, value: chatInput, onChangeText: setChatInput, placeholder: "Say something...", placeholderTextColor: "rgba(255,255,255,0.40)", editable: joined && !joining && !error, onSubmitEditing: sendChat, returnKeyType: "send", submitBehavior: "submit", autoFocus: true }), _jsx(TouchableOpacity, { style: [styles.floatingSendBtn, (!chatInput.trim() || !joined) && styles.sendBtnOff], onPress: sendChat, disabled: !joined || !chatInput.trim(), activeOpacity: 0.75, children: _jsx(Text, { style: styles.sendIcon, children: "\u25B6" }) })] })), 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" })] }) }))] }));
279
+ const hasUnsent = chatInput.trim().length > 0;
280
+ return (_jsxs(View, { style: styles.container, onLayout: onCellLayout, children: [showVideo && RTCViewComponent && streamURL ? (_jsx(RTCViewComponent, { streamURL: streamURL, stream: displayStream, style: styles.rtcView, objectFit: "cover", mirror: false }, `rtc-${trackCount}-${streamURL}`)) : (_jsxs(View, { style: 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: styles.gradientOverlay, 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(Animated.View, { style: [styles.chatColumn, { bottom: chatAnim }], pointerEvents: "box-none", children: _jsx(ScrollView, { ref: chatListRef, 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 && (_jsxs(View, { style: styles.bottomBar, children: [_jsx(TouchableOpacity, { style: [styles.typeTouchable, hasUnsent && styles.typeTouchableFilled], onPress: () => { if (joined && !joining && !error)
281
+ 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" }) })] })), isTyping && (_jsxs(Animated.View, { style: [styles.floatingInputWrap, { bottom: inputBottom }], children: [_jsx(TextInput, { ref: textInputRef, style: styles.floatingTextInput, value: chatInput, onChangeText: setChatInput, placeholder: "Say something...", placeholderTextColor: "rgba(255,255,255,0.40)", editable: joined && !joining && !error, onSubmitEditing: sendChat, returnKeyType: "send", submitBehavior: "submit", autoFocus: true }), _jsx(TouchableOpacity, { style: [styles.floatingSendBtn, (!hasUnsent || !joined) && styles.sendBtnOff], onPress: sendChat, disabled: !joined || !hasUnsent, activeOpacity: 0.75, children: _jsx(Text, { style: styles.sendIcon, children: "\u25B6" }) })] })), 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" })] }) }))] }));
246
282
  });
247
283
  // ─────────────────────────────────────────────────────────────────────────────
248
284
  const styles = StyleSheet.create({
@@ -375,6 +411,7 @@ const styles = StyleSheet.create({
375
411
  chatUsername: { fontWeight: '700' },
376
412
  chatMsg: { color: '#e5e5e5', fontWeight: '400' },
377
413
  joinText: { color: 'rgba(255,255,255,0.48)', fontSize: 12, fontStyle: 'italic' },
414
+ // Bottom action bar
378
415
  bottomBar: {
379
416
  position: 'absolute',
380
417
  bottom: BOTTOM_SAFE,
@@ -393,9 +430,20 @@ const styles = StyleSheet.create({
393
430
  borderWidth: StyleSheet.hairlineWidth,
394
431
  borderColor: 'rgba(255,255,255,0.18)',
395
432
  },
396
- typePlaceholder: { color: 'rgba(255,255,255,0.50)', fontSize: 14 },
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
+ },
397
444
  bottomIconBtn: { width: 40, height: 40, alignItems: 'center', justifyContent: 'center' },
398
445
  bottomIconGlyph: { fontSize: 22 },
446
+ // Floating input — absolute, bottom driven by Animated.Value
399
447
  floatingInputWrap: {
400
448
  position: 'absolute',
401
449
  left: 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasunk99-livestream-core",
3
- "version": "0.3.16",
3
+ "version": "0.3.18",
4
4
  "description": "Reusable livestream viewer/host module for React Native (Expo) — mediasoup + Socket.IO",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",