kasunk99-livestream-core 0.3.13 → 0.3.14

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.
@@ -5,9 +5,6 @@ type LiveStreamViewerItemProps = {
5
5
  isActive: boolean;
6
6
  index: number;
7
7
  };
8
- /**
9
- * Single full-screen viewer cell. When isActive, joins the stream room and consumes video/audio.
10
- */
11
8
  export declare const LiveStreamViewerItem: React.NamedExoticComponent<LiveStreamViewerItemProps>;
12
9
  export {};
13
10
  //# sourceMappingURL=LiveStreamViewerItem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"LiveStreamViewerItem.d.ts","sourceRoot":"","sources":["../../src/components/LiveStreamViewerItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqD,MAAM,OAAO,CAAC;AAwB1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAO/C,KAAK,yBAAyB,GAAG;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AA8BF;;GAEG;AACH,eAAO,MAAM,oBAAoB,uDAiX/B,CAAC"}
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;AAQ/C,KAAK,yBAAyB,GAAG;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAiCF,eAAO,MAAM,oBAAoB,uDA8Z/B,CAAC"}
@@ -1,40 +1,28 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { memo, useEffect, useMemo, useRef, useState } from 'react';
3
- import { ActivityIndicator, Dimensions, KeyboardAvoidingView, NativeModules, Platform, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View, } from 'react-native';
4
- // react-native-keyboard-controller handles Android/iOS keyboard edge cases better
5
- // than the built-in KeyboardAvoidingView. Fall back to the built-in if unavailable.
6
- let KAV = KeyboardAvoidingView;
7
- try {
8
- KAV = require('react-native-keyboard-controller').KeyboardAvoidingView;
9
- }
10
- catch {
11
- // use built-in
12
- }
3
+ import { ActivityIndicator, Animated, Dimensions, Keyboard, NativeModules, Platform, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View, } from 'react-native';
13
4
  import { useViewerSocket } from '../hooks/useViewerSocket';
14
5
  const SCREEN_HEIGHT = Dimensions.get('window').height;
15
6
  const CHAT_NAME_COLORS = ['#f97316', '#22c55e', '#3b82f6', '#eab308', '#ec4899', '#a855f7'];
16
- // iOS needs bottom safe-area padding; Android auto-adjusts via adjustResize
17
7
  const BOTTOM_SAFE = Platform.OS === 'ios' ? 28 : 10;
8
+ const BOTTOM_BAR_H = 58;
9
+ const CHAT_BOTTOM_DEFAULT = BOTTOM_SAFE + BOTTOM_BAR_H + 8;
18
10
  let RTCViewComponent = null;
19
11
  try {
20
12
  const webrtc = require('react-native-webrtc');
21
13
  RTCViewComponent = webrtc.RTCView;
22
14
  }
23
- catch {
24
- // react-native-webrtc not available in Expo Go
25
- }
26
- // expo-linear-gradient — lazy require so the package stays safe in non-Expo environments.
27
- // The consuming app (social-casino-mobile) has expo-linear-gradient as a dependency.
15
+ catch { }
28
16
  let LinearGradient = null;
29
17
  try {
30
18
  LinearGradient = require('expo-linear-gradient').LinearGradient;
31
19
  }
32
- catch {
33
- // expo-linear-gradient not available
20
+ catch { }
21
+ let RNKCEvents = null;
22
+ try {
23
+ RNKCEvents = require('react-native-keyboard-controller').KeyboardEvents;
34
24
  }
35
- /**
36
- * Single full-screen viewer cell. When isActive, joins the stream room and consumes video/audio.
37
- */
25
+ catch { }
38
26
  export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream, isActive, index: _index, }) {
39
27
  const roomId = isActive ? stream.roomId : null;
40
28
  const { joined, joining, error, producerList, roomState, remoteStream, remoteVideoStream, webrtcUnavailable, consumeError, socket, viewerCount: liveViewerCount, streamEnded, } = useViewerSocket(roomId);
@@ -53,8 +41,31 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
53
41
  const showVideo = RTCViewComponent && remoteStream && !!streamURL && hasVideoTrack;
54
42
  const [chatInput, setChatInput] = useState('');
55
43
  const [chatMessages, setChatMessages] = useState([]);
44
+ const [isTyping, setIsTyping] = useState(false);
56
45
  const seededRoomRef = useRef(null);
57
46
  const chatListRef = useRef(null);
47
+ const textInputRef = useRef(null);
48
+ // Keyboard-driven animated positions
49
+ const inputBottom = useRef(new Animated.Value(0)).current;
50
+ const chatAnim = useRef(new Animated.Value(CHAT_BOTTOM_DEFAULT)).current;
51
+ // Animate floating input and chat column with keyboard
52
+ useEffect(() => {
53
+ const animate = (toInput, toChat, dur, onDone) => {
54
+ Animated.parallel([
55
+ Animated.timing(inputBottom, { toValue: toInput, duration: dur, useNativeDriver: false }),
56
+ Animated.timing(chatAnim, { toValue: toChat, duration: dur, useNativeDriver: false }),
57
+ ]).start(() => onDone?.());
58
+ };
59
+ if (RNKCEvents) {
60
+ const s1 = RNKCEvents.addListener('keyboardWillShow', (e) => animate(e.height, e.height + 60, e.duration || 250));
61
+ const s2 = RNKCEvents.addListener('keyboardWillHide', (e) => animate(0, CHAT_BOTTOM_DEFAULT, e.duration || 250, () => setIsTyping(false)));
62
+ return () => { s1.remove(); s2.remove(); };
63
+ }
64
+ // Standard Keyboard API fallback
65
+ const sub1 = Keyboard.addListener((Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow'), (e) => animate(e.endCoordinates.height, e.endCoordinates.height + 60, e.duration || 250));
66
+ const sub2 = Keyboard.addListener((Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide'), (e) => animate(0, CHAT_BOTTOM_DEFAULT, e.duration || 200, () => setIsTyping(false)));
67
+ return () => { sub1.remove(); sub2.remove(); };
68
+ }, [inputBottom, chatAnim]);
58
69
  const seededFromRoomState = useMemo(() => {
59
70
  const chat = roomState && typeof roomState === 'object'
60
71
  ? roomState.chat
@@ -66,6 +77,10 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
66
77
  seededRoomRef.current = null;
67
78
  setChatMessages([]);
68
79
  setChatInput('');
80
+ setIsTyping(false);
81
+ Keyboard.dismiss();
82
+ inputBottom.setValue(0);
83
+ chatAnim.setValue(CHAT_BOTTOM_DEFAULT);
69
84
  return;
70
85
  }
71
86
  if (seededRoomRef.current !== roomId) {
@@ -73,7 +88,7 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
73
88
  setChatMessages([]);
74
89
  setChatInput('');
75
90
  }
76
- }, [roomId]);
91
+ }, [roomId, inputBottom, chatAnim]);
77
92
  useEffect(() => {
78
93
  if (!roomId)
79
94
  return;
@@ -145,7 +160,6 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
145
160
  }
146
161
  catch { /* ignore */ }
147
162
  }, [chatData.length]);
148
- // System audio playback (Android only)
149
163
  useEffect(() => {
150
164
  if (!socket || !joined || !isActive || Platform.OS !== 'android')
151
165
  return;
@@ -197,12 +211,8 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
197
211
  };
198
212
  const hostLabel = stream.hostDisplayName || stream.title || 'Live';
199
213
  const avatarLetter = hostLabel[0]?.toUpperCase() ?? 'L';
200
- return (_jsxs(KAV, { style: styles.container, behavior: Platform.OS === 'ios' ? 'padding' : 'height', 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: [
201
- 'rgba(0,0,0,0.70)', // top edge opaque for host info readability
202
- 'rgba(0,0,0,0.0)', // clears out ~30% down
203
- 'rgba(0,0,0,0.0)', // stays clear through mid-screen
204
- 'rgba(0,0,0,0.78)', // darkens toward bottom for chat / input
205
- ], 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() : '—' })] })] }), _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" })] })] }), _jsxs(View, { style: styles.bottomSection, pointerEvents: "box-none", children: [_jsx(View, { style: styles.chatColumn, pointerEvents: "box-none", children: _jsx(ScrollView, { ref: chatListRef, contentContainerStyle: styles.chatContent, showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "always", keyboardDismissMode: "none", 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))) }) }), _jsxs(View, { style: styles.inputBar, children: [_jsx(TouchableOpacity, { style: styles.inputIconBtn, activeOpacity: 0.7, children: _jsx(Text, { style: styles.inputIconGlyph, children: "\u263A" }) }), _jsx(TextInput, { style: styles.textInput, value: chatInput, onChangeText: setChatInput, placeholder: joined ? 'Say something...' : 'Connecting...', placeholderTextColor: "rgba(255,255,255,0.35)", editable: joined && !joining && !error, onSubmitEditing: sendChat, returnKeyType: "send", submitBehavior: "submit" }), _jsx(TouchableOpacity, { style: styles.inputIconBtn, activeOpacity: 0.7, children: _jsx(Text, { style: styles.inputIconGlyph, children: "\uD83C\uDF39" }) }), _jsx(TouchableOpacity, { style: [styles.sendBtn, (!chatInput.trim() || !joined) && styles.sendBtnOff], onPress: sendChat, activeOpacity: 0.75, disabled: !joined || !chatInput.trim(), 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" })] }) }))] }));
214
+ 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", keyboardDismissMode: "on-drag", 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)
215
+ 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" })] }) }))] }));
206
216
  });
207
217
  // ─────────────────────────────────────────────────────────────────────────────
208
218
  const styles = StyleSheet.create({
@@ -210,7 +220,6 @@ const styles = StyleSheet.create({
210
220
  flex: 1,
211
221
  backgroundColor: '#000',
212
222
  },
213
- // ── Video ─────────────────────────────────────────────────────────────────
214
223
  rtcView: {
215
224
  position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
216
225
  backgroundColor: '#000',
@@ -234,7 +243,9 @@ const styles = StyleSheet.create({
234
243
  textAlign: 'center',
235
244
  paddingHorizontal: 28,
236
245
  },
237
- // ── Top bar ───────────────────────────────────────────────────────────────
246
+ gradientOverlay: {
247
+ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
248
+ },
238
249
  topBar: {
239
250
  position: 'absolute',
240
251
  top: 10,
@@ -263,17 +274,8 @@ const styles = StyleSheet.create({
263
274
  justifyContent: 'center',
264
275
  flexShrink: 0,
265
276
  },
266
- avatarLetter: {
267
- color: '#fff',
268
- fontSize: 15,
269
- fontWeight: '700',
270
- },
271
- hostName: {
272
- color: '#fff',
273
- fontSize: 14,
274
- fontWeight: '600',
275
- flex: 1,
276
- },
277
+ avatarLetter: { color: '#fff', fontSize: 15, fontWeight: '700' },
278
+ hostName: { color: '#fff', fontSize: 14, fontWeight: '600', flex: 1 },
277
279
  livePill: {
278
280
  backgroundColor: '#ef4444',
279
281
  borderRadius: 6,
@@ -281,12 +283,7 @@ const styles = StyleSheet.create({
281
283
  paddingVertical: 3,
282
284
  flexShrink: 0,
283
285
  },
284
- livePillText: {
285
- color: '#fff',
286
- fontSize: 11,
287
- fontWeight: '700',
288
- letterSpacing: 0.8,
289
- },
286
+ livePillText: { color: '#fff', fontSize: 11, fontWeight: '700', letterSpacing: 0.8 },
290
287
  viewerChip: {
291
288
  flexDirection: 'row',
292
289
  alignItems: 'center',
@@ -298,23 +295,15 @@ const styles = StyleSheet.create({
298
295
  flexShrink: 0,
299
296
  },
300
297
  viewerEye: { fontSize: 11 },
301
- viewerCount: {
302
- color: 'rgba(255,255,255,0.88)',
303
- fontSize: 12,
304
- fontWeight: '500',
305
- },
306
- // ── Right action column ───────────────────────────────────────────────────
298
+ viewerCount: { color: 'rgba(255,255,255,0.88)', fontSize: 12, fontWeight: '500' },
307
299
  rightColumn: {
308
300
  position: 'absolute',
309
301
  right: 12,
310
- bottom: 92,
302
+ bottom: BOTTOM_SAFE + BOTTOM_BAR_H + 8,
311
303
  alignItems: 'center',
312
304
  gap: 14,
313
305
  },
314
- actionBtn: {
315
- alignItems: 'center',
316
- gap: 5,
317
- },
306
+ actionBtn: { alignItems: 'center', gap: 5 },
318
307
  actionCircle: {
319
308
  width: 48,
320
309
  height: 48,
@@ -325,31 +314,16 @@ const styles = StyleSheet.create({
325
314
  alignItems: 'center',
326
315
  justifyContent: 'center',
327
316
  },
328
- actionIcon: {
329
- fontSize: 22,
330
- color: '#fff',
331
- },
332
- actionLabel: {
333
- color: 'rgba(255,255,255,0.7)',
334
- fontSize: 11,
335
- fontWeight: '500',
336
- },
337
- // ── Bottom section (in-flow — KAV lifts this above the keyboard) ─────────
338
- bottomSection: {
339
- flex: 1,
340
- justifyContent: 'flex-end',
341
- paddingHorizontal: 12,
342
- paddingBottom: BOTTOM_SAFE,
343
- gap: 8,
344
- },
345
- // Chat messages
317
+ actionIcon: { fontSize: 22, color: '#fff' },
318
+ actionLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 11, fontWeight: '500' },
319
+ // Chat column — absolute, bottom driven by animation
346
320
  chatColumn: {
321
+ position: 'absolute',
322
+ left: 12,
347
323
  width: '66%',
348
324
  maxHeight: Math.round(SCREEN_HEIGHT * 0.30),
349
325
  },
350
- chatContent: {
351
- paddingBottom: 2,
352
- },
326
+ chatContent: { paddingBottom: 2 },
353
327
  chatBubble: {
354
328
  alignSelf: 'flex-start',
355
329
  marginBottom: 5,
@@ -368,53 +342,57 @@ const styles = StyleSheet.create({
368
342
  paddingVertical: 4,
369
343
  maxWidth: '100%',
370
344
  },
371
- chatLine: {
372
- fontSize: 13,
373
- lineHeight: 18,
374
- },
375
- chatUsername: {
376
- fontWeight: '700',
377
- },
378
- chatMsg: {
379
- color: '#e5e5e5',
380
- fontWeight: '400',
381
- },
382
- joinText: {
383
- color: 'rgba(255,255,255,0.48)',
384
- fontSize: 12,
385
- fontStyle: 'italic',
386
- },
387
- // Input bar
388
- inputBar: {
345
+ chatLine: { fontSize: 13, lineHeight: 18 },
346
+ chatUsername: { fontWeight: '700' },
347
+ chatMsg: { color: '#e5e5e5', fontWeight: '400' },
348
+ joinText: { color: 'rgba(255,255,255,0.48)', fontSize: 12, fontStyle: 'italic' },
349
+ // Bottom action bar
350
+ bottomBar: {
351
+ position: 'absolute',
352
+ bottom: BOTTOM_SAFE,
353
+ left: 12,
354
+ right: 12,
389
355
  flexDirection: 'row',
390
356
  alignItems: 'center',
391
- backgroundColor: 'rgba(18,18,18,0.75)',
392
- borderRadius: 999,
393
- borderWidth: StyleSheet.hairlineWidth,
394
- borderColor: 'rgba(255,255,255,0.12)',
395
- paddingHorizontal: 4,
396
- paddingVertical: 4,
397
- gap: 2,
357
+ gap: 6,
398
358
  },
399
- inputIconBtn: {
400
- width: 40,
401
- height: 40,
402
- alignItems: 'center',
403
- justifyContent: 'center',
359
+ typeTouchable: {
360
+ flex: 1,
361
+ backgroundColor: 'rgba(255,255,255,0.12)',
404
362
  borderRadius: 20,
363
+ paddingHorizontal: 14,
364
+ paddingVertical: 10,
365
+ borderWidth: StyleSheet.hairlineWidth,
366
+ borderColor: 'rgba(255,255,255,0.18)',
405
367
  },
406
- inputIconGlyph: {
407
- fontSize: 21,
368
+ typePlaceholder: { color: 'rgba(255,255,255,0.50)', fontSize: 14 },
369
+ bottomIconBtn: { width: 40, height: 40, alignItems: 'center', justifyContent: 'center' },
370
+ bottomIconGlyph: { fontSize: 22 },
371
+ // Floating input bar — sits directly above keyboard, animated
372
+ floatingInputWrap: {
373
+ position: 'absolute',
374
+ left: 0,
375
+ right: 0,
376
+ flexDirection: 'row',
377
+ alignItems: 'center',
378
+ backgroundColor: 'rgba(20,20,20,0.96)',
379
+ paddingHorizontal: 12,
380
+ paddingVertical: 8,
381
+ gap: 8,
382
+ borderTopWidth: StyleSheet.hairlineWidth,
383
+ borderTopColor: 'rgba(255,255,255,0.10)',
408
384
  },
409
- textInput: {
385
+ floatingTextInput: {
410
386
  flex: 1,
387
+ backgroundColor: 'rgba(255,255,255,0.10)',
388
+ borderRadius: 20,
389
+ paddingHorizontal: 14,
390
+ paddingVertical: Platform.OS === 'ios' ? 10 : 8,
411
391
  color: '#fff',
412
392
  fontSize: 14,
413
- paddingHorizontal: 6,
414
- paddingVertical: Platform.OS === 'ios' ? 10 : 6,
415
393
  minHeight: 40,
416
394
  },
417
- sendBtn: {
395
+ floatingSendBtn: {
418
396
  width: 40,
419
397
  height: 40,
420
398
  borderRadius: 20,
@@ -422,42 +400,15 @@ const styles = StyleSheet.create({
422
400
  alignItems: 'center',
423
401
  justifyContent: 'center',
424
402
  },
425
- sendBtnOff: {
426
- backgroundColor: 'rgba(255,255,255,0.1)',
427
- },
428
- sendIcon: {
429
- color: '#fff',
430
- fontSize: 15,
431
- fontWeight: '700',
432
- },
433
- // ── Stream ended ──────────────────────────────────────────────────────────
403
+ sendBtnOff: { backgroundColor: 'rgba(255,255,255,0.12)' },
404
+ sendIcon: { color: '#fff', fontSize: 15, fontWeight: '700' },
434
405
  endedOverlay: {
435
406
  position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
436
407
  backgroundColor: 'rgba(0,0,0,0.80)',
437
408
  alignItems: 'center',
438
409
  justifyContent: 'center',
439
410
  },
440
- endedCard: {
441
- alignItems: 'center',
442
- gap: 10,
443
- paddingHorizontal: 36,
444
- },
445
- endedTitle: {
446
- color: '#fff',
447
- fontSize: 22,
448
- fontWeight: '700',
449
- },
450
- endedSub: {
451
- color: 'rgba(255,255,255,0.5)',
452
- fontSize: 14,
453
- textAlign: 'center',
454
- lineHeight: 20,
455
- },
456
- gradientOverlay: {
457
- position: 'absolute',
458
- top: 0,
459
- left: 0,
460
- right: 0,
461
- bottom: 0,
462
- },
411
+ endedCard: { alignItems: 'center', gap: 10, paddingHorizontal: 36 },
412
+ endedTitle: { color: '#fff', fontSize: 22, fontWeight: '700' },
413
+ endedSub: { color: 'rgba(255,255,255,0.5)', fontSize: 14, textAlign: 'center', lineHeight: 20 },
463
414
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasunk99-livestream-core",
3
- "version": "0.3.13",
3
+ "version": "0.3.14",
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",