@zezosoft/zezo-ott-react-native-ui-kit 1.0.3 → 1.0.4

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.
Files changed (108) hide show
  1. package/README.md +1 -1
  2. package/lib/module/assets/animations/heart.json +788 -0
  3. package/lib/module/components/Auth/QrLogin/QrLogin.js +267 -0
  4. package/lib/module/components/Auth/QrLogin/QrLogin.js.map +1 -0
  5. package/lib/module/components/Auth/QrLogin/components/QrViewArea.js +178 -0
  6. package/lib/module/components/Auth/QrLogin/components/QrViewArea.js.map +1 -0
  7. package/lib/module/components/Auth/index.js +3 -1
  8. package/lib/module/components/Auth/index.js.map +1 -1
  9. package/lib/module/components/Headers/AppHeader.js +1 -1
  10. package/lib/module/components/Headers/AppHeader.js.map +1 -1
  11. package/lib/module/components/Headers/One.js +1 -1
  12. package/lib/module/components/Headers/One.js.map +1 -1
  13. package/lib/module/components/Headers/Two.js +1 -1
  14. package/lib/module/components/Headers/Two.js.map +1 -1
  15. package/lib/module/components/Reels/ReelsSeries/Model/Episodes.js +110 -0
  16. package/lib/module/components/Reels/ReelsSeries/Model/Episodes.js.map +1 -0
  17. package/lib/module/components/Reels/ReelsSeries/Model/Synopsis.js +216 -0
  18. package/lib/module/components/Reels/ReelsSeries/Model/Synopsis.js.map +1 -0
  19. package/lib/module/components/Reels/ReelsSeries/ReelSeriesDetailsModal.js +182 -0
  20. package/lib/module/components/Reels/ReelsSeries/ReelSeriesDetailsModal.js.map +1 -0
  21. package/lib/module/components/Reels/ReelsSeries/ReelSeriesOverlay.js +203 -0
  22. package/lib/module/components/Reels/ReelsSeries/ReelSeriesOverlay.js.map +1 -0
  23. package/lib/module/components/Reels/ReelsSeries/ReelsSeries.js +121 -0
  24. package/lib/module/components/Reels/ReelsSeries/ReelsSeries.js.map +1 -0
  25. package/lib/module/components/Reels/ReelsSeries/ReelsSeriesItem.js +290 -0
  26. package/lib/module/components/Reels/ReelsSeries/ReelsSeriesItem.js.map +1 -0
  27. package/lib/module/components/Reels/ReelsSeries/types.js +2 -0
  28. package/lib/module/components/Reels/ReelsSeries/types.js.map +1 -0
  29. package/lib/module/components/Reels/index.js +11 -0
  30. package/lib/module/components/Reels/index.js.map +1 -0
  31. package/lib/module/components/User/DeviceSessions/DeviceSessions.js +8 -0
  32. package/lib/module/components/User/DeviceSessions/DeviceSessions.js.map +1 -1
  33. package/lib/module/components/User/ProfileUpdate/ProfileUpdate.js +258 -0
  34. package/lib/module/components/User/ProfileUpdate/ProfileUpdate.js.map +1 -0
  35. package/lib/module/components/User/components/UserSection.js +8 -13
  36. package/lib/module/components/User/components/UserSection.js.map +1 -1
  37. package/lib/module/components/User/index.js +2 -1
  38. package/lib/module/components/User/index.js.map +1 -1
  39. package/lib/module/components/index.js +1 -0
  40. package/lib/module/components/index.js.map +1 -1
  41. package/lib/module/theme/ThemeProvider.js +13 -10
  42. package/lib/module/theme/ThemeProvider.js.map +1 -1
  43. package/lib/module/theme/themes.js +2 -0
  44. package/lib/module/theme/themes.js.map +1 -1
  45. package/lib/module/utils/Formater.js +17 -0
  46. package/lib/module/utils/Formater.js.map +1 -0
  47. package/lib/typescript/src/components/Auth/QrLogin/QrLogin.d.ts +32 -0
  48. package/lib/typescript/src/components/Auth/QrLogin/QrLogin.d.ts.map +1 -0
  49. package/lib/typescript/src/components/Auth/QrLogin/components/QrViewArea.d.ts +15 -0
  50. package/lib/typescript/src/components/Auth/QrLogin/components/QrViewArea.d.ts.map +1 -0
  51. package/lib/typescript/src/components/Auth/index.d.ts +1 -0
  52. package/lib/typescript/src/components/Auth/index.d.ts.map +1 -1
  53. package/lib/typescript/src/components/Reels/ReelsSeries/Model/Episodes.d.ts +12 -0
  54. package/lib/typescript/src/components/Reels/ReelsSeries/Model/Episodes.d.ts.map +1 -0
  55. package/lib/typescript/src/components/Reels/ReelsSeries/Model/Synopsis.d.ts +9 -0
  56. package/lib/typescript/src/components/Reels/ReelsSeries/Model/Synopsis.d.ts.map +1 -0
  57. package/lib/typescript/src/components/Reels/ReelsSeries/ReelSeriesDetailsModal.d.ts +13 -0
  58. package/lib/typescript/src/components/Reels/ReelsSeries/ReelSeriesDetailsModal.d.ts.map +1 -0
  59. package/lib/typescript/src/components/Reels/ReelsSeries/ReelSeriesOverlay.d.ts +18 -0
  60. package/lib/typescript/src/components/Reels/ReelsSeries/ReelSeriesOverlay.d.ts.map +1 -0
  61. package/lib/typescript/src/components/Reels/ReelsSeries/ReelsSeries.d.ts +15 -0
  62. package/lib/typescript/src/components/Reels/ReelsSeries/ReelsSeries.d.ts.map +1 -0
  63. package/lib/typescript/src/components/Reels/ReelsSeries/ReelsSeriesItem.d.ts +18 -0
  64. package/lib/typescript/src/components/Reels/ReelsSeries/ReelsSeriesItem.d.ts.map +1 -0
  65. package/lib/typescript/src/components/Reels/ReelsSeries/types.d.ts +24 -0
  66. package/lib/typescript/src/components/Reels/ReelsSeries/types.d.ts.map +1 -0
  67. package/lib/typescript/src/components/Reels/index.d.ts +8 -0
  68. package/lib/typescript/src/components/Reels/index.d.ts.map +1 -0
  69. package/lib/typescript/src/components/User/DeviceSessions/DeviceSessions.d.ts +1 -0
  70. package/lib/typescript/src/components/User/DeviceSessions/DeviceSessions.d.ts.map +1 -1
  71. package/lib/typescript/src/components/User/ProfileUpdate/ProfileUpdate.d.ts +27 -0
  72. package/lib/typescript/src/components/User/ProfileUpdate/ProfileUpdate.d.ts.map +1 -0
  73. package/lib/typescript/src/components/User/components/UserSection.d.ts.map +1 -1
  74. package/lib/typescript/src/components/User/index.d.ts +2 -1
  75. package/lib/typescript/src/components/User/index.d.ts.map +1 -1
  76. package/lib/typescript/src/components/index.d.ts +1 -0
  77. package/lib/typescript/src/components/index.d.ts.map +1 -1
  78. package/lib/typescript/src/theme/ThemeProvider.d.ts.map +1 -1
  79. package/lib/typescript/src/theme/themes.d.ts +1 -0
  80. package/lib/typescript/src/theme/themes.d.ts.map +1 -1
  81. package/lib/typescript/src/utils/Formater.d.ts +2 -0
  82. package/lib/typescript/src/utils/Formater.d.ts.map +1 -0
  83. package/package.json +13 -5
  84. package/src/assets/animations/heart.json +788 -0
  85. package/src/components/Auth/QrLogin/QrLogin.tsx +306 -0
  86. package/src/components/Auth/QrLogin/components/QrViewArea.tsx +213 -0
  87. package/src/components/Auth/index.ts +2 -0
  88. package/src/components/Headers/AppHeader.tsx +1 -1
  89. package/src/components/Headers/One.tsx +1 -1
  90. package/src/components/Headers/Two.tsx +1 -1
  91. package/src/components/Reels/ReelsSeries/Model/Episodes.tsx +133 -0
  92. package/src/components/Reels/ReelsSeries/Model/Synopsis.tsx +249 -0
  93. package/src/components/Reels/ReelsSeries/ReelSeriesDetailsModal.tsx +209 -0
  94. package/src/components/Reels/ReelsSeries/ReelSeriesOverlay.tsx +185 -0
  95. package/src/components/Reels/ReelsSeries/ReelsSeries.tsx +163 -0
  96. package/src/components/Reels/ReelsSeries/ReelsSeriesItem.tsx +333 -0
  97. package/src/components/Reels/ReelsSeries/types.ts +27 -0
  98. package/src/components/Reels/index.ts +8 -0
  99. package/src/components/User/DeviceSessions/DeviceSessions.tsx +11 -0
  100. package/src/components/User/ProfileUpdate/ProfileUpdate.tsx +265 -0
  101. package/src/components/User/components/UserSection.tsx +10 -13
  102. package/src/components/User/index.ts +3 -1
  103. package/src/components/index.ts +1 -0
  104. package/src/theme/ThemeProvider.tsx +12 -9
  105. package/src/theme/themes.ts +3 -0
  106. package/src/utils/Formater.ts +14 -0
  107. /package/lib/module/assets/{img → svg}/h.svg +0 -0
  108. /package/src/assets/{img → svg}/h.svg +0 -0
@@ -0,0 +1,163 @@
1
+ import React, { useState, useCallback, useRef, useMemo } from 'react';
2
+ import {
3
+ FlatList,
4
+ Dimensions,
5
+ StyleSheet,
6
+ View,
7
+ TouchableOpacity,
8
+ type ViewToken,
9
+ } from 'react-native';
10
+ import { debounce } from 'lodash';
11
+ import ReelsSeriesItem from './ReelsSeriesItem';
12
+ import { verticalScale } from 'react-native-size-matters';
13
+ import { Search } from 'lucide-react-native';
14
+ import type { ISeriesEpisode, ISeriesItem } from './types';
15
+
16
+ const { height } = Dimensions.get('window');
17
+ const TAB_BAR_HEIGHT = verticalScale(65);
18
+ const SCREEN_HEIGHT = height - TAB_BAR_HEIGHT;
19
+
20
+ type ReelsProps = {
21
+ data: ISeriesItem;
22
+ autoScroll?: boolean;
23
+ onEndReached?: () => void;
24
+ onLikePress?: (id: string, liked: boolean) => void;
25
+ onEpisodesPress?: (id: string) => void;
26
+ onSharePress?: (id: string) => void;
27
+ onSearchPress?: () => void;
28
+ isFocused?: boolean;
29
+ };
30
+
31
+ export const ReelsSeries: React.FC<ReelsProps> = ({
32
+ data,
33
+ autoScroll,
34
+ onEndReached,
35
+ onLikePress,
36
+ onEpisodesPress,
37
+ onSharePress,
38
+ onSearchPress,
39
+ isFocused = true,
40
+ }) => {
41
+ const [currentVisibleIndex, setCurrentVisibleIndex] = useState<number>(0);
42
+ const listRef = useRef<FlatList<ISeriesEpisode>>(null);
43
+ const keyExtractor = useCallback(
44
+ (item: ISeriesEpisode) => item.episodeId.toString(),
45
+ []
46
+ );
47
+
48
+ const hendlePlayEpisode = useCallback(
49
+ (index: number) => {
50
+ if (index < 0 || index >= (data.episodes?.length || 0)) return;
51
+ listRef.current?.scrollToIndex({ index, animated: true });
52
+ setCurrentVisibleIndex(index);
53
+ },
54
+ [data.episodes]
55
+ );
56
+
57
+ const renderReelList = useCallback(
58
+ ({ item, index }: { item: ISeriesEpisode; index: number }) => {
59
+ return (
60
+ <ReelsSeriesItem
61
+ key={item.episodeId}
62
+ reel={item}
63
+ data={data}
64
+ videoHeight={SCREEN_HEIGHT}
65
+ onLikePress={onLikePress}
66
+ onEpisodesPress={() => onEpisodesPress?.(item.episodeId)}
67
+ onSharePress={() => onSharePress?.(item.episodeId)}
68
+ isVisible={index === currentVisibleIndex && isFocused}
69
+ activeEpisodeIndex={currentVisibleIndex}
70
+ preload={Math.abs(currentVisibleIndex + 3) >= index}
71
+ onEpisodeSelect={hendlePlayEpisode}
72
+ autoScroll={autoScroll}
73
+ />
74
+ );
75
+ },
76
+ [
77
+ data,
78
+ onLikePress,
79
+ currentVisibleIndex,
80
+ isFocused,
81
+ hendlePlayEpisode,
82
+ autoScroll,
83
+ onEpisodesPress,
84
+ onSharePress,
85
+ ]
86
+ );
87
+
88
+ const memoizedRenderReelList = useMemo(
89
+ () => renderReelList,
90
+ [renderReelList]
91
+ );
92
+
93
+ const viewabilityConfig = useRef({
94
+ itemVisiblePercentThreshold: 80,
95
+ }).current;
96
+
97
+ const onViewableItemsChanged = useRef(
98
+ debounce(({ viewableItems }: { viewableItems: Array<ViewToken> }) => {
99
+ if (viewableItems.length > 0) {
100
+ setCurrentVisibleIndex(viewableItems[0]?.index || 0);
101
+ }
102
+ }, 100)
103
+ ).current;
104
+
105
+ const getItemLayout = useCallback(
106
+ (_: any, index: number) => ({
107
+ length: SCREEN_HEIGHT,
108
+ offset: SCREEN_HEIGHT * index,
109
+ index,
110
+ }),
111
+ []
112
+ );
113
+
114
+ return (
115
+ <View style={[styles.container, { backgroundColor: '#000' }]}>
116
+ {onSearchPress && (
117
+ <TouchableOpacity
118
+ style={[
119
+ styles.fixedSearch,
120
+ {
121
+ top: verticalScale(40),
122
+ right: 16,
123
+ },
124
+ ]}
125
+ onPress={onSearchPress}
126
+ >
127
+ <Search color={'#fff'} size={verticalScale(22)} />
128
+ </TouchableOpacity>
129
+ )}
130
+
131
+ <FlatList
132
+ ref={listRef}
133
+ data={data.episodes || []}
134
+ keyExtractor={keyExtractor}
135
+ renderItem={memoizedRenderReelList as any}
136
+ windowSize={2}
137
+ onEndReached={onEndReached}
138
+ pagingEnabled
139
+ viewabilityConfig={viewabilityConfig}
140
+ disableIntervalMomentum={true}
141
+ removeClippedSubviews
142
+ maxToRenderPerBatch={2}
143
+ onViewableItemsChanged={onViewableItemsChanged}
144
+ initialNumToRender={1}
145
+ onEndReachedThreshold={0.1}
146
+ decelerationRate="fast"
147
+ showsVerticalScrollIndicator={false}
148
+ scrollEventThrottle={16}
149
+ snapToInterval={SCREEN_HEIGHT}
150
+ snapToAlignment="start"
151
+ getItemLayout={getItemLayout}
152
+ contentContainerStyle={{ paddingBottom: TAB_BAR_HEIGHT }}
153
+ />
154
+ </View>
155
+ );
156
+ };
157
+
158
+ export default ReelsSeries;
159
+
160
+ const styles = StyleSheet.create({
161
+ container: { flex: 1 },
162
+ fixedSearch: { position: 'absolute', zIndex: 10 },
163
+ });
@@ -0,0 +1,333 @@
1
+ import React, { useState, useEffect, useRef, useCallback, memo } from 'react';
2
+ import { View, Dimensions, StyleSheet } from 'react-native';
3
+ import ReelSeriesOverlay from './ReelSeriesOverlay';
4
+ import ReelSeriesDetailsModal from './ReelSeriesDetailsModal';
5
+ import type { ISeriesEpisode, ISeriesItem } from './types';
6
+ import { Video, type VideoRef } from 'react-native-video';
7
+ import FastImage from 'react-native-fast-image';
8
+ import {
9
+ Gesture,
10
+ GestureDetector,
11
+ GestureHandlerRootView,
12
+ } from 'react-native-gesture-handler';
13
+ import { Pause, Play } from 'lucide-react-native';
14
+ import { moderateScale } from 'react-native-size-matters';
15
+ import LottieView from 'lottie-react-native';
16
+ import DoubleTapAnim from '../../../assets/animations/heart.json';
17
+
18
+ const { width } = Dimensions.get('window');
19
+
20
+ interface IReelItemProps {
21
+ reel: ISeriesEpisode;
22
+ data: ISeriesItem;
23
+ autoScroll?: boolean;
24
+ videoHeight: number;
25
+ onLikePress?: (id: string, liked: boolean) => void;
26
+ onEpisodesPress?: () => void;
27
+ onSharePress?: () => void;
28
+ isVisible: boolean;
29
+ preload: boolean;
30
+ activeEpisodeIndex: number;
31
+ onEpisodeSelect: (index: number) => void;
32
+ }
33
+
34
+ const ReelItem: React.FC<IReelItemProps> = ({
35
+ reel,
36
+ data,
37
+ autoScroll,
38
+ videoHeight,
39
+ onLikePress,
40
+ onSharePress,
41
+ isVisible,
42
+ preload,
43
+ activeEpisodeIndex,
44
+ onEpisodeSelect,
45
+ }) => {
46
+ const videoRef = useRef<VideoRef>(null);
47
+ const timeoutRef = useRef<NodeJS.Timeout | null>(null);
48
+ const [isPaused, setIsPaused] = useState<boolean>(false);
49
+ const [paused, setPaused] = useState<string | null>(null);
50
+ const [modalVisible, setModalVisible] = useState(false);
51
+ const [currentTime, setCurrentTime] = useState(0);
52
+ const [duration, setDuration] = useState(0);
53
+ const [videoLoaded, setVideoLoaded] = useState(false);
54
+ const [showLikeAnim, setShowLikeAnim] = useState<boolean>(false);
55
+
56
+ useEffect(() => {
57
+ setIsPaused(!isVisible);
58
+ if (!isVisible) {
59
+ setPaused(null);
60
+ setVideoLoaded(false);
61
+ }
62
+ }, [isVisible]);
63
+
64
+ // Cleanup timeout on unmount
65
+ useEffect(() => {
66
+ return () => {
67
+ if (timeoutRef.current) {
68
+ clearTimeout(timeoutRef.current);
69
+ }
70
+ };
71
+ }, []);
72
+
73
+ const videoUri = reel.videoUrl || '';
74
+ // Handle like
75
+ const handleLikePress = useCallback(() => {
76
+ const newLiked = !data.isLiked;
77
+ data.isLiked = newLiked;
78
+ data.likes += newLiked ? 1 : -1;
79
+ if (newLiked) {
80
+ setShowLikeAnim(true);
81
+ setTimeout(() => setShowLikeAnim(false), 1200);
82
+ }
83
+ onLikePress?.(reel.episodeId, newLiked);
84
+ }, [data, onLikePress, reel.episodeId]);
85
+
86
+ const handleEpisodeSelect = (index: number) => {
87
+ if (data && index >= 0 && index < data.episodes.length) {
88
+ onEpisodeSelect(index);
89
+ setModalVisible(false);
90
+ }
91
+ };
92
+
93
+ const handleVideoLoad = () => {
94
+ setVideoLoaded(true);
95
+ };
96
+
97
+ const handleTogglePlay = useCallback(() => {
98
+ // Use functional updates to ensure we get the current state
99
+ setIsPaused((prevIsPaused) => {
100
+ const newIsPaused = !prevIsPaused;
101
+ const currentState = newIsPaused ? 'paused' : 'play';
102
+
103
+ setPaused(currentState);
104
+
105
+ // Clear any existing timeout
106
+ if (timeoutRef.current) {
107
+ clearTimeout(timeoutRef.current);
108
+ }
109
+
110
+ // Set new timeout
111
+ timeoutRef.current = setTimeout(() => {
112
+ if (currentState === 'play') {
113
+ setPaused(null);
114
+ }
115
+ }, 700);
116
+
117
+ return newIsPaused;
118
+ });
119
+ }, []);
120
+
121
+ const singleTap = Gesture.Tap()
122
+ .maxDuration(300)
123
+ .onStart((event) => {
124
+ // Check if the tap is in the overlay area (right side icons or bottom area)
125
+ const { x, y } = event;
126
+ const screenWidth = width;
127
+ const screenHeight = videoHeight;
128
+
129
+ // Right side icons area (right 80px)
130
+ const rightIconsArea = x > screenWidth - 80;
131
+
132
+ // Bottom area (bottom 150px)
133
+ const bottomArea = y > screenHeight - 150;
134
+
135
+ // If tap is in overlay area, don't trigger play/pause
136
+ if (rightIconsArea || bottomArea) {
137
+ return;
138
+ }
139
+ handleTogglePlay();
140
+ })
141
+ .runOnJS(true);
142
+
143
+ const doubleTap = Gesture.Tap()
144
+ .maxDuration(300)
145
+ .numberOfTaps(2)
146
+ .onStart((event) => {
147
+ // Check if the tap is in the overlay area (right side icons or bottom area)
148
+ const { x, y } = event;
149
+ const screenWidth = width;
150
+ const screenHeight = videoHeight;
151
+
152
+ // Right side icons area (right 80px)
153
+ const rightIconsArea = x > screenWidth - 80;
154
+
155
+ // Bottom area (bottom 150px)
156
+ const bottomArea = y > screenHeight - 150;
157
+
158
+ // If tap is in overlay area, don't trigger like
159
+ if (rightIconsArea || bottomArea) {
160
+ return;
161
+ }
162
+
163
+ handleLikePress();
164
+ })
165
+ .runOnJS(true);
166
+
167
+ const combined = Gesture.Exclusive(doubleTap, singleTap);
168
+
169
+ return (
170
+ <View
171
+ style={[
172
+ styles.container,
173
+ { height: videoHeight, backgroundColor: '#000' },
174
+ ]}
175
+ >
176
+ <GestureHandlerRootView style={{ flex: 1 }}>
177
+ <GestureDetector gesture={combined}>
178
+ <View style={{ flex: 1 }} collapsable={false}>
179
+ {/* Video Player */}
180
+ {!videoLoaded && (
181
+ <FastImage
182
+ source={{
183
+ uri: data.thumbnail,
184
+ priority: FastImage.priority.high,
185
+ }}
186
+ defaultSource={require('../../../assets/img/play.png')}
187
+ resizeMode={FastImage.resizeMode.cover}
188
+ />
189
+ )}
190
+
191
+ {isVisible || preload ? (
192
+ <Video
193
+ poster={data.thumbnail}
194
+ ref={videoRef}
195
+ source={{ uri: videoUri }}
196
+ style={StyleSheet.absoluteFillObject}
197
+ resizeMode="cover"
198
+ posterResizeMode="cover"
199
+ repeat
200
+ paused={isPaused}
201
+ onLoad={(e) => setDuration(e.duration)}
202
+ onProgress={({ currentTime: ct }) => setCurrentTime(ct)}
203
+ ignoreSilentSwitch="ignore"
204
+ playWhenInactive={false}
205
+ playInBackground={false}
206
+ controls={false}
207
+ controlsStyles={{
208
+ hideForward: true,
209
+ hideDuration: true,
210
+ hideFullscreen: true,
211
+ hidePlayPause: true,
212
+ hideNavigationBarOnFullScreenMode: true,
213
+ hideNotificationBarOnFullScreenMode: true,
214
+ hideNext: true,
215
+ hidePosition: true,
216
+ hidePrevious: true,
217
+ hideRewind: true,
218
+ hideSeekBar: true,
219
+ hideSettingButton: true,
220
+ }}
221
+ disableFocus={true}
222
+ shutterColor="transparent"
223
+ onReadyForDisplay={handleVideoLoad}
224
+ onEnd={() => {
225
+ if (autoScroll) {
226
+ onEpisodeSelect(activeEpisodeIndex + 1);
227
+ }
228
+ }}
229
+ />
230
+ ) : null}
231
+
232
+ {showLikeAnim && (
233
+ <View style={styles.lottieContainer}>
234
+ <LottieView
235
+ style={styles.lottie}
236
+ source={DoubleTapAnim}
237
+ autoPlay
238
+ loop={false}
239
+ />
240
+ </View>
241
+ )}
242
+
243
+ {paused !== null && (
244
+ <View
245
+ style={[
246
+ styles.playPauseButtonContainer,
247
+ { height: videoHeight },
248
+ ]}
249
+ >
250
+ <View style={styles.shadow} pointerEvents="none">
251
+ {paused === 'paused' ? (
252
+ <Pause color={'#fff'} size={moderateScale(25)} />
253
+ ) : (
254
+ <Play color={'#fff'} size={moderateScale(25)} />
255
+ )}
256
+ </View>
257
+ </View>
258
+ )}
259
+
260
+ {/* Overlay */}
261
+ <ReelSeriesOverlay
262
+ data={data}
263
+ reel={reel}
264
+ currentTime={currentTime}
265
+ duration={duration}
266
+ onLikePress={handleLikePress}
267
+ onPlayPausePress={() => setIsPaused((prev) => !prev)}
268
+ onEpisodesPress={() => {
269
+ setModalVisible(true);
270
+ }}
271
+ onSharePress={onSharePress}
272
+ activeEpisodeIndex={activeEpisodeIndex}
273
+ />
274
+
275
+ {/* Modal */}
276
+ <ReelSeriesDetailsModal
277
+ visible={modalVisible}
278
+ onClose={() => setModalVisible(false)}
279
+ reel={reel}
280
+ data={data}
281
+ activeEpisodeIndex={activeEpisodeIndex}
282
+ onEpisodeSelect={handleEpisodeSelect}
283
+ />
284
+ </View>
285
+ </GestureDetector>
286
+ </GestureHandlerRootView>
287
+ </View>
288
+ );
289
+ };
290
+
291
+ const areEqual = (prevProps: IReelItemProps, nextProps: IReelItemProps) => {
292
+ return (
293
+ prevProps?.reel?.episodeId === nextProps?.reel?.episodeId &&
294
+ prevProps?.isVisible === nextProps?.isVisible
295
+ );
296
+ };
297
+
298
+ export default memo(ReelItem, areEqual);
299
+
300
+ const styles = StyleSheet.create({
301
+ container: {
302
+ width,
303
+ position: 'relative',
304
+ overflow: 'hidden',
305
+ flex: 1,
306
+ flexGrow: 1,
307
+ },
308
+ playPauseButtonContainer: {
309
+ position: 'absolute',
310
+ top: 0,
311
+ left: 0,
312
+ right: 0,
313
+ bottom: 0,
314
+ justifyContent: 'center',
315
+ alignItems: 'center',
316
+ },
317
+ shadow: {
318
+ backgroundColor: 'rgba(0,0,0,0.5)',
319
+ padding: moderateScale(10),
320
+ borderRadius: moderateScale(50),
321
+ },
322
+ lottieContainer: {
323
+ width: '100%',
324
+ height: '100%',
325
+ position: 'absolute',
326
+ justifyContent: 'center',
327
+ alignItems: 'center',
328
+ },
329
+ lottie: {
330
+ width: '100%',
331
+ height: '100%',
332
+ },
333
+ });
@@ -0,0 +1,27 @@
1
+ export interface ISeriesEpisode {
2
+ videoUrl: string;
3
+ episodeId: string;
4
+ episodeNumber: number;
5
+ duration: string;
6
+ isLocked: boolean;
7
+ }
8
+
9
+ export interface ISeriesItem {
10
+ id: string;
11
+ title: string;
12
+
13
+ thumbnail: string;
14
+ description: string;
15
+ episodes: ISeriesEpisode[];
16
+ likes: number;
17
+ views: number;
18
+ createdAt: string;
19
+ shares: number;
20
+ isLiked: boolean;
21
+ }
22
+
23
+ // For like state
24
+ export interface LikedState {
25
+ liked: boolean;
26
+ count: number;
27
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @author Naresh Dhamu
3
+ * @lastModified Tue 24 Jun 2025 at 05:02 PM
4
+ */
5
+
6
+ import { ReelsSeries } from './ReelsSeries/ReelsSeries';
7
+ export * from './ReelsSeries/types';
8
+ export { ReelsSeries };
@@ -20,6 +20,7 @@ import AppHeader from '../../Headers/AppHeader';
20
20
  import SkeletonPlaceholder from 'react-native-skeleton-placeholder';
21
21
  import type { AppTheme } from '../../../theme/themes';
22
22
  import { RFValue } from 'react-native-responsive-fontsize';
23
+ import { ScanLine } from 'lucide-react-native';
23
24
 
24
25
  type IUserLocation = {
25
26
  country: string | null;
@@ -57,6 +58,7 @@ type IDeviceSessionsProps = {
57
58
  error?: string;
58
59
  onLogout: ({ session }: { session: ISessions }) => void;
59
60
  onBackPress?: () => void;
61
+ onQrLoginPress?: () => void;
60
62
  style?: StyleProp<ViewStyle>;
61
63
  title?: string;
62
64
  renderLogoutButton?: ({ session }: { session: ISessions }) => React.ReactNode;
@@ -82,6 +84,7 @@ export const DeviceSessions: React.FC<IDeviceSessionsProps> = ({
82
84
  activeSessionId,
83
85
  onLogout,
84
86
  onBackPress = () => {},
87
+ onQrLoginPress,
85
88
  title = 'Manage Devices',
86
89
  style,
87
90
  error,
@@ -309,6 +312,14 @@ export const DeviceSessions: React.FC<IDeviceSessionsProps> = ({
309
312
  <AppHeader
310
313
  title={title}
311
314
  onBackPress={onBackPress}
315
+ onRightPress={onQrLoginPress}
316
+ rightIcon={
317
+ <ScanLine
318
+ size={22}
319
+ strokeWidth={2}
320
+ color={colors.onSurfaceVariant}
321
+ />
322
+ }
312
323
  titleAlign={headerTitleAlign}
313
324
  style={headerStyle}
314
325
  titleStyle={headerTitleStyle}