kasunk99-livestream-core 0.3.16 → 0.3.17

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,uDAme/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,93 @@ 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
+ 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;
48
56
  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.
53
- //
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.
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
56
81
  useEffect(() => {
57
82
  if (Platform.OS === 'android') {
58
- const sub = Keyboard.addListener('keyboardDidHide', () => {
59
- setIsTyping(false);
60
- chatAnim.setValue(CHAT_BOTTOM_DEFAULT);
83
+ // keyboardDidShow fires AFTER keyboard animation + AFTER adjustResize layout.
84
+ // currCellH.current has the post-resize value at this point.
85
+ const s1 = Keyboard.addListener('keyboardDidShow', (e) => {
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
+ });
61
103
  });
62
- return () => sub.remove();
104
+ return () => { s1.remove(); s2.remove(); };
63
105
  }
64
- // iOS — animate with keyboard
106
+ // iOS — keyboardWillShow fires before keyboard so we can animate in sync
65
107
  const s1 = Keyboard.addListener('keyboardWillShow', (e) => {
66
- const h = e.endCoordinates.height;
67
- const dur = e.duration || 250;
108
+ const offset = computeOffset(e.endCoordinates.height);
109
+ lastOffsetRef.current = offset;
68
110
  Animated.parallel([
69
- Animated.timing(inputBottom, { toValue: h, duration: dur, useNativeDriver: false }),
70
- Animated.timing(chatAnim, { toValue: h + 60, duration: dur, useNativeDriver: false }),
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 }),
71
113
  ]).start();
72
114
  });
73
115
  const s2 = Keyboard.addListener('keyboardWillHide', (e) => {
74
- const dur = e.duration || 250;
75
116
  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 }),
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 }),
78
119
  ]).start(() => setIsTyping(false));
79
120
  });
80
121
  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
122
+ }, [inputBottom, chatAnim, computeOffset]);
123
+ // Dismiss keyboard when stream becomes inactive
93
124
  useEffect(() => {
94
125
  if (!isActive) {
95
126
  Keyboard.dismiss();
@@ -109,7 +140,7 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
109
140
  setChatInput('');
110
141
  setIsTyping(false);
111
142
  Keyboard.dismiss();
112
- inputBottom.setValue(BOTTOM_SAFE);
143
+ inputBottom.setValue(0);
113
144
  chatAnim.setValue(CHAT_BOTTOM_DEFAULT);
114
145
  return;
115
146
  }
@@ -233,7 +264,11 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
233
264
  const text = chatInput.trim();
234
265
  if (!text || !socket || !joined)
235
266
  return;
267
+ // Clear input, close panel, dismiss keyboard — all before the emit
268
+ // so the button press completes synchronously before any state teardown.
236
269
  setChatInput('');
270
+ setIsTyping(false);
271
+ Keyboard.dismiss();
237
272
  socket.emit('chat-message', { text }, (res) => {
238
273
  if (res?.error)
239
274
  console.log('[viewer] chat send error', res.error);
@@ -241,8 +276,9 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
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.17",
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",