kasunk99-livestream-core 0.3.38 โ†’ 0.3.40

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,KAAkE,MAAM,OAAO,CAAC;AAmBvF,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;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CAChD,CAAC;AAmEF,eAAO,MAAM,oBAAoB,uDAoe/B,CAAC"}
1
+ {"version":3,"file":"LiveStreamViewerItem.d.ts","sourceRoot":"","sources":["../../src/components/LiveStreamViewerItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkE,MAAM,OAAO,CAAC;AAmBvF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAmB/C,KAAK,yBAAyB,GAAG;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CAChD,CAAC;AAmEF,eAAO,MAAM,oBAAoB,uDAsiB/B,CAAC"}
@@ -5,6 +5,17 @@ import { useViewerSocket } from '../hooks/useViewerSocket';
5
5
  const CHAT_NAME_COLORS = ['#f97316', '#22c55e', '#3b82f6', '#eab308', '#ec4899', '#a855f7'];
6
6
  const BOTTOM_SAFE = Platform.OS === 'ios' ? 28 : 10;
7
7
  const BRAND_GREEN = '#009F68';
8
+ const EMOJIS = [
9
+ '๐Ÿ˜€', '๐Ÿ˜', '๐Ÿ˜‚', '๐Ÿคฃ', '๐Ÿ˜ƒ', '๐Ÿ˜„', '๐Ÿ˜…', '๐Ÿ˜†', '๐Ÿ˜‰', '๐Ÿ˜Š',
10
+ '๐Ÿ˜‹', '๐Ÿ˜Ž', '๐Ÿ˜', '๐Ÿฅฐ', '๐Ÿ˜˜', '๐Ÿคฉ', '๐Ÿฅณ', '๐Ÿ˜', '๐Ÿ˜’', '๐Ÿ™„',
11
+ '๐Ÿ˜ค', '๐Ÿ˜ก', '๐Ÿคฌ', '๐Ÿ˜ญ', '๐Ÿ˜ข', '๐Ÿ˜ฑ', '๐Ÿ˜จ', '๐Ÿค”', '๐Ÿคญ', '๐Ÿ˜ด',
12
+ 'โค๏ธ', '๐Ÿงก', '๐Ÿ’›', '๐Ÿ’š', '๐Ÿ’™', '๐Ÿ’œ', '๐Ÿ–ค', '๐Ÿค', '๐Ÿ’”', '๐Ÿ’•',
13
+ '๐Ÿ’ฏ', '๐Ÿ”ฅ', 'โœจ', 'โญ', '๐ŸŒŸ', '๐Ÿ’ฅ', '๐ŸŽ‰', '๐ŸŽŠ', '๐Ÿ†', '๐Ÿ‘‘',
14
+ '๐Ÿ‘', '๐Ÿ‘Ž', '๐Ÿ‘', '๐Ÿ™Œ', '๐Ÿ‘Š', 'โœŒ๏ธ', '๐Ÿคž', '๐Ÿค™', '๐Ÿ’ช', '๐Ÿ™',
15
+ '๐Ÿ˜ˆ', '๐Ÿ‘ป', '๐Ÿ’€', '๐Ÿถ', '๐Ÿฑ', '๐Ÿผ', '๐ŸฆŠ', '๐Ÿธ', '๐Ÿฆ‹', '๐ŸŒธ',
16
+ '๐Ÿ•', '๐Ÿ”', '๐ŸŸ', '๐ŸŒฎ', '๐Ÿœ', '๐Ÿฃ', '๐Ÿฐ', '๐ŸŽ‚', '๐Ÿซ', '๐Ÿญ',
17
+ 'โšฝ', '๐Ÿ€', '๐ŸŽฎ', '๐ŸŽต', '๐ŸŽถ', '๐Ÿ“ฑ', '๐Ÿ’ป', '๐Ÿ“ธ', '๐Ÿš€', '๐ŸŒˆ',
18
+ ];
8
19
  let RTCViewComponent = null;
9
20
  try {
10
21
  const webrtc = require('react-native-webrtc');
@@ -62,6 +73,7 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
62
73
  const [chatMessages, setChatMessages] = useState([]);
63
74
  // isTyping controls whether we show the TextInput bar or the pill bar
64
75
  const [isTyping, setIsTyping] = useState(false);
76
+ const [showEmoji, setShowEmoji] = useState(false);
65
77
  const [floatingHearts, setFloatingHearts] = useState([]);
66
78
  const heartIdRef = useRef(0);
67
79
  const handleLike = useCallback(() => {
@@ -76,6 +88,29 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
76
88
  setFloatingHearts((prev) => prev.filter((h) => h.id !== id));
77
89
  });
78
90
  }, [joined, emitLike]);
91
+ const openEmoji = useCallback(() => {
92
+ Keyboard.dismiss();
93
+ setIsTyping(false);
94
+ setShowEmoji(true);
95
+ }, []);
96
+ // Switches from emoji panel back to system keyboard
97
+ const switchToKeyboard = useCallback(() => {
98
+ setShowEmoji(false);
99
+ setIsTyping(true);
100
+ }, []);
101
+ // Focus TextInput programmatically when switching to keyboard mode (not emoji mode)
102
+ useEffect(() => {
103
+ if (isTyping && !showEmoji) {
104
+ const t = setTimeout(() => textInputRef.current?.focus(), 50);
105
+ return () => clearTimeout(t);
106
+ }
107
+ }, [isTyping, showEmoji]);
108
+ const handleEmojiSelect = useCallback((emoji) => {
109
+ const next = chatInputRef.current + emoji;
110
+ chatInputRef.current = next;
111
+ setChatInput(next);
112
+ // Input bar is already visible (showEmoji=true), no state change needed
113
+ }, []);
79
114
  // Incremented every time the app returns to foreground โ€” forces RTCView to remount,
80
115
  // which restarts the Android SurfaceView renderer that freezes during background.
81
116
  const [videoKey, setVideoKey] = useState(0);
@@ -106,6 +141,7 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
106
141
  if (!isActive) {
107
142
  Keyboard.dismiss();
108
143
  setIsTyping(false);
144
+ setShowEmoji(false);
109
145
  }
110
146
  }, [isActive]);
111
147
  const seededFromRoomState = useMemo(() => {
@@ -263,6 +299,8 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
263
299
  chatInputRef.current = '';
264
300
  setChatInput('');
265
301
  Keyboard.dismiss();
302
+ setShowEmoji(false);
303
+ setIsTyping(false);
266
304
  };
267
305
  const hostLabel = stream.hostDisplayName || stream.title || 'Live';
268
306
  const avatarLetter = hostLabel[0]?.toUpperCase() ?? 'L';
@@ -279,14 +317,16 @@ export const LiveStreamViewerItem = memo(function LiveStreamViewerItem({ stream,
279
317
  ],
280
318
  opacity: anim.interpolate({ inputRange: [0, 0.65, 1], outputRange: [1, 0.85, 0] }),
281
319
  },
282
- ], children: "\u2665" }, id))) }), isTyping ? (_jsxs(View, { style: styles.inputBar, children: [_jsx(TextInput, { ref: textInputRef, style: styles.textInput, value: chatInput, onChangeText: (text) => { chatInputRef.current = text; setChatInput(text); }, placeholder: "Say something...", placeholderTextColor: "rgba(255,255,255,0.40)", editable: canType, onSubmitEditing: sendChat, returnKeyType: "send", submitBehavior: "submit", autoFocus: true }), RNGHTouchable ? (_jsx(RNGHTouchable, { onPress: sendChat, activeOpacity: 0.75, style: [styles.sendBtn, (!hasUnsent || !joined) && styles.sendBtnOff], hitSlop: { top: 8, right: 8, bottom: 8, left: 8 }, children: IoniconsComponent
320
+ ], children: "\u2665" }, id))) }), isTyping || showEmoji ? (_jsxs(View, { style: styles.inputBar, children: [_jsx(TouchableOpacity, { style: styles.emojiToggleBtn, onPress: showEmoji ? switchToKeyboard : openEmoji, activeOpacity: 0.7, hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, children: _jsx(Text, { style: [styles.bottomIconGlyph, showEmoji && styles.emojiIconActive], children: showEmoji ? 'โŒจ' : 'โ˜บ' }) }), _jsx(TextInput, { ref: textInputRef, style: styles.textInput, value: chatInput, onChangeText: (text) => { chatInputRef.current = text; setChatInput(text); }, placeholder: "Say something...", placeholderTextColor: "rgba(255,255,255,0.40)", editable: canType, onFocus: () => setShowEmoji(false), onSubmitEditing: sendChat, returnKeyType: "send", submitBehavior: "submit" }), RNGHTouchable ? (_jsx(RNGHTouchable, { onPress: sendChat, activeOpacity: 0.75, style: [styles.sendBtn, (!hasUnsent || !joined) && styles.sendBtnOff], hitSlop: { top: 8, right: 8, bottom: 8, left: 8 }, children: IoniconsComponent
283
321
  ? _jsx(IoniconsComponent, { name: "send", size: 18, color: "#fff" })
284
322
  : _jsx(Text, { style: styles.sendIcon, children: "\u27A4" }) })) : (_jsx(Pressable, { style: [styles.sendBtn, (!hasUnsent || !joined) && styles.sendBtnOff], onPressIn: sendChat, hitSlop: { top: 8, right: 8, bottom: 8, left: 8 }, children: IoniconsComponent
285
323
  ? _jsx(IoniconsComponent, { name: "send", size: 18, color: "#fff" })
286
324
  : _jsx(Text, { style: styles.sendIcon, children: "\u27A4" }) }))] })) : (
287
- // Pill bar โ€” tapping opens the input bar.
288
- _jsxs(View, { style: styles.bottomBar, children: [_jsx(TouchableOpacity, { style: [styles.typeTouchable, hasUnsent && styles.typeTouchableFilled], onPress: () => { if (canType)
289
- 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.likeBtn, onPress: handleLike, activeOpacity: 0.75, children: _jsx(Text, { style: [styles.bottomIconGlyph, styles.heartIcon], children: "\u2665" }) })] })), 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" })] }) }))] }));
325
+ // Pill bar โ€” shown when neither keyboard nor emoji panel is active
326
+ _jsxs(View, { style: styles.bottomBar, children: [_jsx(TouchableOpacity, { style: [styles.typeTouchable, hasUnsent && styles.typeTouchableFilled], onPress: () => { if (canType) {
327
+ setShowEmoji(false);
328
+ setIsTyping(true);
329
+ } }, 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, onPress: openEmoji, children: _jsx(Text, { style: styles.bottomIconGlyph, children: "\u263A" }) }), _jsx(TouchableOpacity, { style: styles.likeBtn, onPress: handleLike, activeOpacity: 0.75, children: _jsx(Text, { style: [styles.bottomIconGlyph, styles.heartIcon], children: "\u2665" }) })] })), showEmoji ? (_jsx(View, { style: styles.emojiContainer, children: _jsx(ScrollView, { style: styles.emojiScroll, showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "always", children: _jsx(View, { style: styles.emojiGrid, children: EMOJIS.map((emoji, idx) => (_jsx(TouchableOpacity, { style: styles.emojiCell, onPress: () => handleEmojiSelect(emoji), activeOpacity: 0.6, children: _jsx(Text, { style: styles.emojiText, children: emoji }) }, idx))) }) }) })) : null, 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" })] }) }))] }));
290
330
  });
291
331
  // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
292
332
  const styles = StyleSheet.create({
@@ -488,4 +528,27 @@ const styles = StyleSheet.create({
488
528
  endedCard: { alignItems: 'center', gap: 10, paddingHorizontal: 36 },
489
529
  endedTitle: { color: '#fff', fontSize: 22, fontWeight: '700' },
490
530
  endedSub: { color: 'rgba(255,255,255,0.5)', fontSize: 14, textAlign: 'center', lineHeight: 20 },
531
+ // โ”€โ”€ Emoji picker โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
532
+ emojiToggleBtn: { width: 36, height: 40, alignItems: 'center', justifyContent: 'center' },
533
+ emojiIconActive: { color: BRAND_GREEN },
534
+ emojiContainer: {
535
+ height: 260,
536
+ backgroundColor: 'rgba(18,18,18,0.97)',
537
+ borderTopWidth: StyleSheet.hairlineWidth,
538
+ borderTopColor: 'rgba(255,255,255,0.10)',
539
+ },
540
+ emojiScroll: { flex: 1 },
541
+ emojiGrid: {
542
+ flexDirection: 'row',
543
+ flexWrap: 'wrap',
544
+ paddingHorizontal: 4,
545
+ paddingVertical: 8,
546
+ },
547
+ emojiCell: {
548
+ width: '12.5%',
549
+ aspectRatio: 1,
550
+ alignItems: 'center',
551
+ justifyContent: 'center',
552
+ },
553
+ emojiText: { fontSize: 26 },
491
554
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasunk99-livestream-core",
3
- "version": "0.3.38",
3
+ "version": "0.3.40",
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",