@zezosoft/zezo-ott-react-native-ui-kit 1.1.2 → 1.1.3

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 (136) hide show
  1. package/lib/module/components/Auth/QrLogin/QrLogin.js +304 -138
  2. package/lib/module/components/Auth/QrLogin/QrLogin.js.map +1 -1
  3. package/lib/module/components/Auth/QrLogin/components/QrViewArea.js +193 -141
  4. package/lib/module/components/Auth/QrLogin/components/QrViewArea.js.map +1 -1
  5. package/lib/module/components/Content/Card/Category/Category.js +83 -11
  6. package/lib/module/components/Content/Card/Category/Category.js.map +1 -1
  7. package/lib/module/components/Content/Card/NowWatching/NowWatching.js +237 -108
  8. package/lib/module/components/Content/Card/NowWatching/NowWatching.js.map +1 -1
  9. package/lib/module/components/Content/Card/Sliders/Styles/One.js +185 -126
  10. package/lib/module/components/Content/Card/Sliders/Styles/One.js.map +1 -1
  11. package/lib/module/components/Content/Card/Sliders/Styles/Two.js +139 -92
  12. package/lib/module/components/Content/Card/Sliders/Styles/Two.js.map +1 -1
  13. package/lib/module/components/Content/Card/Styles/Five.js +131 -48
  14. package/lib/module/components/Content/Card/Styles/Five.js.map +1 -1
  15. package/lib/module/components/Content/Card/Styles/Four.js +126 -59
  16. package/lib/module/components/Content/Card/Styles/Four.js.map +1 -1
  17. package/lib/module/components/Content/Card/Styles/One.js +125 -50
  18. package/lib/module/components/Content/Card/Styles/One.js.map +1 -1
  19. package/lib/module/components/Content/Card/Styles/RotateInOut.js +138 -53
  20. package/lib/module/components/Content/Card/Styles/RotateInOut.js.map +1 -1
  21. package/lib/module/components/Content/Card/Styles/Six.js +207 -115
  22. package/lib/module/components/Content/Card/Styles/Six.js.map +1 -1
  23. package/lib/module/components/Content/Card/Styles/Three.js +134 -79
  24. package/lib/module/components/Content/Card/Styles/Three.js.map +1 -1
  25. package/lib/module/components/Content/Card/Styles/TopTen.js +186 -171
  26. package/lib/module/components/Content/Card/Styles/TopTen.js.map +1 -1
  27. package/lib/module/components/Content/Card/Styles/Two.js +144 -64
  28. package/lib/module/components/Content/Card/Styles/Two.js.map +1 -1
  29. package/lib/module/components/Content/Card/components/AdsPoster.js +162 -0
  30. package/lib/module/components/Content/Card/components/AdsPoster.js.map +1 -0
  31. package/lib/module/components/Content/Card/components/CardPoster.js +120 -136
  32. package/lib/module/components/Content/Card/components/CardPoster.js.map +1 -1
  33. package/lib/module/components/Content/Card/components/index.js +4 -0
  34. package/lib/module/components/Content/Card/components/index.js.map +1 -0
  35. package/lib/module/components/Content/Content.js +67 -27
  36. package/lib/module/components/Content/Content.js.map +1 -1
  37. package/lib/module/components/Content/Sections.js +32 -11
  38. package/lib/module/components/Content/Sections.js.map +1 -1
  39. package/lib/module/constants/dummySections.js +44 -4
  40. package/lib/module/constants/dummySections.js.map +1 -1
  41. package/lib/module/hooks/Images/index.js +5 -0
  42. package/lib/module/hooks/Images/index.js.map +1 -0
  43. package/lib/module/hooks/Images/useImageLoader.js +168 -0
  44. package/lib/module/hooks/Images/useImageLoader.js.map +1 -0
  45. package/lib/module/hooks/Images/useImageValidation.js +36 -0
  46. package/lib/module/hooks/Images/useImageValidation.js.map +1 -0
  47. package/lib/module/hooks/index.js +3 -0
  48. package/lib/module/hooks/index.js.map +1 -1
  49. package/lib/module/hooks/useAdTracking.js +270 -0
  50. package/lib/module/hooks/useAdTracking.js.map +1 -0
  51. package/lib/module/hooks/useCards.js +164 -0
  52. package/lib/module/hooks/useCards.js.map +1 -0
  53. package/lib/module/hooks/usePaginatedSection.js +11 -6
  54. package/lib/module/hooks/usePaginatedSection.js.map +1 -1
  55. package/lib/typescript/src/components/Auth/QrLogin/QrLogin.d.ts +2 -0
  56. package/lib/typescript/src/components/Auth/QrLogin/QrLogin.d.ts.map +1 -1
  57. package/lib/typescript/src/components/Auth/QrLogin/components/QrViewArea.d.ts.map +1 -1
  58. package/lib/typescript/src/components/Content/Card/Category/Category.d.ts.map +1 -1
  59. package/lib/typescript/src/components/Content/Card/NowWatching/NowWatching.d.ts.map +1 -1
  60. package/lib/typescript/src/components/Content/Card/Sliders/Styles/One.d.ts.map +1 -1
  61. package/lib/typescript/src/components/Content/Card/Sliders/Styles/Two.d.ts.map +1 -1
  62. package/lib/typescript/src/components/Content/Card/Styles/Five.d.ts +13 -1
  63. package/lib/typescript/src/components/Content/Card/Styles/Five.d.ts.map +1 -1
  64. package/lib/typescript/src/components/Content/Card/Styles/Four.d.ts +13 -1
  65. package/lib/typescript/src/components/Content/Card/Styles/Four.d.ts.map +1 -1
  66. package/lib/typescript/src/components/Content/Card/Styles/One.d.ts +15 -3
  67. package/lib/typescript/src/components/Content/Card/Styles/One.d.ts.map +1 -1
  68. package/lib/typescript/src/components/Content/Card/Styles/RotateInOut.d.ts +13 -1
  69. package/lib/typescript/src/components/Content/Card/Styles/RotateInOut.d.ts.map +1 -1
  70. package/lib/typescript/src/components/Content/Card/Styles/Six.d.ts +1 -0
  71. package/lib/typescript/src/components/Content/Card/Styles/Six.d.ts.map +1 -1
  72. package/lib/typescript/src/components/Content/Card/Styles/Three.d.ts +13 -5
  73. package/lib/typescript/src/components/Content/Card/Styles/Three.d.ts.map +1 -1
  74. package/lib/typescript/src/components/Content/Card/Styles/TopTen.d.ts +1 -0
  75. package/lib/typescript/src/components/Content/Card/Styles/TopTen.d.ts.map +1 -1
  76. package/lib/typescript/src/components/Content/Card/Styles/Two.d.ts +13 -1
  77. package/lib/typescript/src/components/Content/Card/Styles/Two.d.ts.map +1 -1
  78. package/lib/typescript/src/components/Content/Card/components/AdsPoster.d.ts +26 -0
  79. package/lib/typescript/src/components/Content/Card/components/AdsPoster.d.ts.map +1 -0
  80. package/lib/typescript/src/components/Content/Card/components/CardPoster.d.ts +3 -1
  81. package/lib/typescript/src/components/Content/Card/components/CardPoster.d.ts.map +1 -1
  82. package/lib/typescript/src/components/Content/Card/components/index.d.ts +2 -0
  83. package/lib/typescript/src/components/Content/Card/components/index.d.ts.map +1 -0
  84. package/lib/typescript/src/components/Content/Card/index.d.ts +76 -6
  85. package/lib/typescript/src/components/Content/Card/index.d.ts.map +1 -1
  86. package/lib/typescript/src/components/Content/Content.d.ts +4 -3
  87. package/lib/typescript/src/components/Content/Content.d.ts.map +1 -1
  88. package/lib/typescript/src/components/Content/Sections.d.ts +20 -6
  89. package/lib/typescript/src/components/Content/Sections.d.ts.map +1 -1
  90. package/lib/typescript/src/constants/dummySections.d.ts +5 -0
  91. package/lib/typescript/src/constants/dummySections.d.ts.map +1 -1
  92. package/lib/typescript/src/hooks/Images/index.d.ts +3 -0
  93. package/lib/typescript/src/hooks/Images/index.d.ts.map +1 -0
  94. package/lib/typescript/src/hooks/Images/useImageLoader.d.ts +36 -0
  95. package/lib/typescript/src/hooks/Images/useImageLoader.d.ts.map +1 -0
  96. package/lib/typescript/src/hooks/Images/useImageValidation.d.ts +17 -0
  97. package/lib/typescript/src/hooks/Images/useImageValidation.d.ts.map +1 -0
  98. package/lib/typescript/src/hooks/index.d.ts +3 -0
  99. package/lib/typescript/src/hooks/index.d.ts.map +1 -1
  100. package/lib/typescript/src/hooks/useAdTracking.d.ts +39 -0
  101. package/lib/typescript/src/hooks/useAdTracking.d.ts.map +1 -0
  102. package/lib/typescript/src/hooks/useCards.d.ts +36 -0
  103. package/lib/typescript/src/hooks/useCards.d.ts.map +1 -0
  104. package/lib/typescript/src/hooks/usePaginatedSection.d.ts +12 -2
  105. package/lib/typescript/src/hooks/usePaginatedSection.d.ts.map +1 -1
  106. package/lib/typescript/src/types/sections/index.d.ts +7 -4
  107. package/lib/typescript/src/types/sections/index.d.ts.map +1 -1
  108. package/package.json +6 -3
  109. package/src/components/Auth/QrLogin/QrLogin.tsx +382 -122
  110. package/src/components/Auth/QrLogin/components/QrViewArea.tsx +291 -197
  111. package/src/components/Content/Card/Category/Category.tsx +95 -8
  112. package/src/components/Content/Card/NowWatching/NowWatching.tsx +281 -136
  113. package/src/components/Content/Card/Sliders/Styles/One.tsx +244 -148
  114. package/src/components/Content/Card/Sliders/Styles/Two.tsx +171 -102
  115. package/src/components/Content/Card/Styles/Five.tsx +161 -62
  116. package/src/components/Content/Card/Styles/Four.tsx +164 -85
  117. package/src/components/Content/Card/Styles/One.tsx +161 -71
  118. package/src/components/Content/Card/Styles/RotateInOut.tsx +157 -60
  119. package/src/components/Content/Card/Styles/Six.tsx +242 -142
  120. package/src/components/Content/Card/Styles/Three.tsx +166 -133
  121. package/src/components/Content/Card/Styles/TopTen.tsx +230 -191
  122. package/src/components/Content/Card/Styles/Two.tsx +182 -79
  123. package/src/components/Content/Card/components/AdsPoster.tsx +202 -0
  124. package/src/components/Content/Card/components/CardPoster.tsx +134 -154
  125. package/src/components/Content/Card/components/index.ts +1 -0
  126. package/src/components/Content/Content.tsx +83 -45
  127. package/src/components/Content/Sections.tsx +51 -10
  128. package/src/constants/dummySections.ts +48 -1
  129. package/src/hooks/Images/index.ts +2 -0
  130. package/src/hooks/Images/useImageLoader.ts +206 -0
  131. package/src/hooks/Images/useImageValidation.ts +36 -0
  132. package/src/hooks/index.ts +3 -0
  133. package/src/hooks/useAdTracking.ts +349 -0
  134. package/src/hooks/useCards.ts +228 -0
  135. package/src/hooks/usePaginatedSection.ts +26 -7
  136. package/src/types/sections/index.ts +7 -4
@@ -35,6 +35,50 @@ import { RFValue } from 'react-native-responsive-fontsize';
35
35
  import RentOrBuyIcon from '../components/RentOrBuyIcon';
36
36
  import type { IContentData } from '@zezosoft/zezo-ott-api-client';
37
37
 
38
+ // ============================================================================
39
+ // Constants
40
+ // ============================================================================
41
+ const ITEM_WIDTH = moderateScale(230);
42
+ const BORDER_RADIUS = moderateScale(8);
43
+ const DEFAULT_SKELETON_COUNT = 4;
44
+ const ASPECT_RATIO = 9 / 16;
45
+ const PROGRESS_ANIMATION_DURATION = 500;
46
+ const TOUCH_OPACITY = 0.9;
47
+ const END_REACHED_THRESHOLD = 0.5;
48
+ const ITEM_MARGIN_RIGHT = moderateScale(12);
49
+ const FIRST_ITEM_MARGIN_LEFT = moderateScale(10);
50
+ const LIST_PADDING_RIGHT = moderateScale(15);
51
+ const GRADIENT_COLORS = ['rgba(0,0,0,0.95)', 'transparent'];
52
+ const GRADIENT_START = { x: 0, y: 1 };
53
+ const GRADIENT_END = { x: 0, y: 0 };
54
+ const PROGRESS_BG_COLOR = '#4F4E4E';
55
+ const PROGRESS_BORDER_RADIUS = 6;
56
+ const SKELETON_TITLE_WIDTH = '80%';
57
+ const SKELETON_SUBTITLE_WIDTH = '60%';
58
+ const SKELETON_BORDER_RADIUS = 4;
59
+ const SHADOW_COLOR = '#000';
60
+ const SHADOW_OFFSET = { width: 0, height: 2 };
61
+ const SHADOW_OPACITY = 0.08;
62
+ const SHADOW_RADIUS = 4;
63
+ const ELEVATION = 2;
64
+ const PLAY_ICON_SIZE = moderateScale(20);
65
+ const PLAY_ICON_CONTAINER_SIZE = moderateScale(32);
66
+ const PLAY_ICON_CONTAINER_RADIUS = moderateScale(16);
67
+ const SKELETON_TITLE_HEIGHT = verticalScale(10);
68
+ const SKELETON_SUBTITLE_HEIGHT = verticalScale(8);
69
+ const SKELETON_HEADER_WIDTH = moderateScale(100);
70
+ const SKELETON_HEADER_HEIGHT = verticalScale(14);
71
+ const SKELETON_HEADER_BORDER_RADIUS = moderateScale(4);
72
+ const SKELETON_HEADER_MARGIN_LEFT = moderateScale(12);
73
+ const SKELETON_HEADER_MARGIN_BOTTOM = verticalScale(6);
74
+ const INFO_CONTAINER_BOTTOM = 6;
75
+ const INFO_CONTAINER_HORIZONTAL = 8;
76
+ const TEXT_SECTION_PADDING_RIGHT = 8;
77
+ const SUBTITLE_OPACITY = 0.85;
78
+
79
+ // ============================================================================
80
+ // Types
81
+ // ============================================================================
38
82
  export interface IHistoryItem {
39
83
  _id: string;
40
84
  content: IContentData;
@@ -61,7 +105,6 @@ type Props = {
61
105
  name: IGetSectionData['name'];
62
106
  type: IGetSectionData['type'];
63
107
  }) => void;
64
-
65
108
  renderItemImage?: (params: {
66
109
  item: IContentData;
67
110
  index: number;
@@ -76,10 +119,25 @@ type Props = {
76
119
  isLoadingHistory?: boolean;
77
120
  };
78
121
 
79
- const ITEM_WIDTH = moderateScale(230);
80
- const BORDER_RADIUS = moderateScale(8);
122
+ type NowWatchingItemProps = {
123
+ item: IHistoryItem;
124
+ index: number;
125
+ isLast: boolean;
126
+ appliedTheme: ITheme;
127
+ onPressItem?: (item: IContentData) => void;
128
+ renderItemImage?: (params: {
129
+ item: IContentData;
130
+ index: number;
131
+ }) => React.ReactNode;
132
+ itemStyle?: StyleProp<ViewStyle>;
133
+ itemWidth: number;
134
+ borderRadius: number;
135
+ };
81
136
 
82
- const NowWatchingItem = memo(
137
+ // ============================================================================
138
+ // Helper Components
139
+ // ============================================================================
140
+ const NowWatchingItem = memo<NowWatchingItemProps>(
83
141
  ({
84
142
  item,
85
143
  index,
@@ -90,48 +148,57 @@ const NowWatchingItem = memo(
90
148
  itemStyle,
91
149
  itemWidth,
92
150
  borderRadius,
93
- }: {
94
- item: IHistoryItem;
95
- index: number;
96
- isLast: boolean;
97
- appliedTheme: ITheme;
98
- onPressItem?: (item: IContentData) => void;
99
- renderItemImage?: (params: {
100
- item: IContentData;
101
- index: number;
102
- }) => React.ReactNode;
103
- itemStyle?: StyleProp<ViewStyle>;
104
- itemWidth: number;
105
- borderRadius: number;
106
151
  }) => {
107
- const percentage = Math.max(
108
- 0,
109
- Math.min(100, Math.round((item.currentTime / item.duration) * 100))
110
- );
152
+ // ========================================================================
153
+ // Progress Calculation
154
+ // ========================================================================
155
+ const percentage = useMemo(() => {
156
+ if (!item.duration || item.duration === 0) return 0;
157
+ return Math.max(
158
+ 0,
159
+ Math.min(100, Math.round((item.currentTime / item.duration) * 100))
160
+ );
161
+ }, [item.currentTime, item.duration]);
111
162
 
163
+ // ========================================================================
164
+ // Animation
165
+ // ========================================================================
112
166
  const progress = useSharedValue(0);
113
167
 
114
168
  useEffect(() => {
115
- progress.value = withTiming(percentage, { duration: 500 });
116
- }, [percentage, progress]);
169
+ progress.value = withTiming(percentage, {
170
+ duration: PROGRESS_ANIMATION_DURATION,
171
+ });
172
+ // eslint-disable-next-line react-hooks/exhaustive-deps
173
+ }, [percentage]);
117
174
 
118
175
  const animatedProgressStyle = useAnimatedStyle(() => ({
119
176
  width: `${progress.value}%`,
120
177
  }));
121
178
 
179
+ // ========================================================================
180
+ // Styles
181
+ // ========================================================================
122
182
  const styles = useMemo(
123
183
  () => getStyles(appliedTheme, itemWidth, borderRadius),
124
184
  [appliedTheme, itemWidth, borderRadius]
125
185
  );
186
+
187
+ const isFirstItem = index === 0;
188
+ const itemMarginStyle = isLast ? { marginRight: ITEM_MARGIN_RIGHT } : null;
189
+
190
+ // ========================================================================
191
+ // Render
192
+ // ========================================================================
126
193
  return (
127
194
  <TouchableOpacity
128
195
  style={[
129
196
  styles.item,
130
197
  itemStyle,
131
- index === 0 && styles.firstItem,
132
- isLast && { marginRight: moderateScale(12) },
198
+ isFirstItem && styles.firstItem,
199
+ itemMarginStyle,
133
200
  ]}
134
- activeOpacity={0.9}
201
+ activeOpacity={TOUCH_OPACITY}
135
202
  onPress={() => onPressItem?.(item.content)}
136
203
  >
137
204
  <View style={styles.cardWrapper}>
@@ -151,9 +218,9 @@ const NowWatchingItem = memo(
151
218
  content_offering_type={item.content.content_offering_type}
152
219
  />
153
220
  <LinearGradient
154
- colors={['rgba(0,0,0,0.95)', 'transparent']}
155
- start={{ x: 0, y: 1 }}
156
- end={{ x: 0, y: 0 }}
221
+ colors={GRADIENT_COLORS}
222
+ start={GRADIENT_START}
223
+ end={GRADIENT_END}
157
224
  style={styles.gradientOverlay}
158
225
  />
159
226
  </FastImage>
@@ -177,7 +244,7 @@ const NowWatchingItem = memo(
177
244
  </View>
178
245
  <View style={styles.playIconWrapper}>
179
246
  <Play
180
- size={moderateScale(20)}
247
+ size={PLAY_ICON_SIZE}
181
248
  color={appliedTheme.colors.primary}
182
249
  fill={appliedTheme.colors.primary}
183
250
  />
@@ -189,6 +256,11 @@ const NowWatchingItem = memo(
189
256
  }
190
257
  );
191
258
 
259
+ NowWatchingItem.displayName = 'NowWatchingItem';
260
+
261
+ // ============================================================================
262
+ // Main Component
263
+ // ============================================================================
192
264
  const NowWatching: React.FC<Props> = ({
193
265
  section_id,
194
266
  title,
@@ -203,48 +275,67 @@ const NowWatching: React.FC<Props> = ({
203
275
  renderItemImage,
204
276
  itemWidth = ITEM_WIDTH,
205
277
  borderRadius = BORDER_RADIUS,
206
- skeletonCount = 4,
278
+ skeletonCount = DEFAULT_SKELETON_COUNT,
207
279
  containerStyle,
208
280
  itemStyle,
209
281
  isLoadingHistory,
210
282
  }) => {
211
- const flatListRef = useRef<FlatList<IHistoryItem>>(null);
283
+ // ========================================================================
284
+ // Refs & Hooks
285
+ // ========================================================================
286
+ const flatListRef = useRef<FlatList<IHistoryItem | number>>(null);
212
287
  const onEndReachedCalledDuringMomentum = useRef(false);
213
288
  const { theme: appliedTheme } = useInternalTheme(theme);
289
+
290
+ // ========================================================================
291
+ // Data Processing
292
+ // ========================================================================
293
+ const isLoadingState = isLoading || isLoadingHistory;
294
+
295
+ const { data, loadMoreData, pagination, isPaginating } =
296
+ usePaginatedSection<IHistoryItem>(
297
+ section_id,
298
+ moreFetchDataHistory,
299
+ initialData,
300
+ isLoadingState
301
+ );
302
+
303
+ const listData = useMemo(() => data ?? [], [data]);
304
+
305
+ // ========================================================================
306
+ // Styles
307
+ // ========================================================================
214
308
  const styles = useMemo(
215
309
  () => getStyles(appliedTheme, itemWidth, borderRadius),
216
310
  [appliedTheme, itemWidth, borderRadius]
217
311
  );
218
312
 
219
- const {
220
- data,
221
- loadMoreData,
222
- pagination,
223
- loading: isPaginating,
224
- } = usePaginatedSection<IHistoryItem>(
225
- section_id,
226
- moreFetchDataHistory,
227
- initialData,
228
- isLoading || isLoadingHistory
229
- );
230
-
313
+ // ========================================================================
314
+ // Event Handlers
315
+ // ========================================================================
231
316
  const handlePressMore = useCallback(() => {
232
317
  onPressMore?.({ section_id, name: title, type });
233
318
  }, [onPressMore, section_id, title, type]);
234
319
 
235
- const listData = useMemo(() => data ?? [], [data]);
236
-
237
320
  const handleEndReached = useCallback(() => {
238
- if (
321
+ const canLoadMore =
239
322
  !onEndReachedCalledDuringMomentum.current &&
240
323
  pagination?.hasNextPage &&
241
- pagination?.nextPage
242
- ) {
324
+ pagination?.nextPage;
325
+
326
+ if (canLoadMore) {
243
327
  onEndReachedCalledDuringMomentum.current = true;
244
328
  loadMoreData(pagination.nextPage);
245
329
  }
246
330
  }, [loadMoreData, pagination]);
247
331
 
332
+ const handleMomentumScrollBegin = useCallback(() => {
333
+ onEndReachedCalledDuringMomentum.current = false;
334
+ }, []);
335
+
336
+ // ========================================================================
337
+ // Render Functions
338
+ // ========================================================================
248
339
  const renderItem: ListRenderItem<IHistoryItem> = useCallback(
249
340
  ({ item, index }) => {
250
341
  const isLast = index === listData.length - 1;
@@ -273,41 +364,110 @@ const NowWatching: React.FC<Props> = ({
273
364
  ]
274
365
  );
275
366
 
276
- const renderSkeletonItem = (index: number) => (
277
- <View
278
- key={index}
279
- style={[
280
- styles.item,
281
- itemStyle,
282
- index === 0 && styles.firstItem,
283
- { marginRight: moderateScale(12) },
284
- ]}
285
- >
286
- <SkeletonPlaceholder
287
- backgroundColor={appliedTheme.colors.skeletonBaseColor}
288
- highlightColor={appliedTheme.colors.skeletonHighlightColor}
289
- >
290
- <View style={styles.cardWrapper}>
291
- <View style={styles.poster} />
292
- <View style={styles.infoContainer}>
293
- <View style={styles.textSection}>
294
- <View style={styles.skeletonTitle} />
295
- <View style={styles.skeletonSubTitle} />
296
- <View style={styles.progressBackground} />
367
+ const renderSkeletonItem = useCallback(
368
+ (index: number) => {
369
+ const isFirstItem = index === 0;
370
+ return (
371
+ <View
372
+ key={index}
373
+ style={[
374
+ styles.item,
375
+ itemStyle,
376
+ isFirstItem && styles.firstItem,
377
+ { marginRight: ITEM_MARGIN_RIGHT },
378
+ ]}
379
+ >
380
+ <SkeletonPlaceholder
381
+ backgroundColor={appliedTheme.colors.skeletonBaseColor}
382
+ highlightColor={appliedTheme.colors.skeletonHighlightColor}
383
+ >
384
+ <View style={styles.cardWrapper}>
385
+ <View style={styles.poster} />
386
+ <View style={styles.infoContainer}>
387
+ <View style={styles.textSection}>
388
+ <View style={styles.skeletonTitle} />
389
+ <View style={styles.skeletonSubTitle} />
390
+ <View style={styles.progressBackground} />
391
+ </View>
392
+ <View style={styles.playIconSkeleton} />
393
+ </View>
297
394
  </View>
298
- <View style={styles.playIconSkeleton} />
299
- </View>
395
+ </SkeletonPlaceholder>
300
396
  </View>
301
- </SkeletonPlaceholder>
302
- </View>
397
+ );
398
+ },
399
+ [
400
+ styles.item,
401
+ styles.firstItem,
402
+ styles.cardWrapper,
403
+ styles.poster,
404
+ styles.infoContainer,
405
+ styles.textSection,
406
+ styles.skeletonTitle,
407
+ styles.skeletonSubTitle,
408
+ styles.progressBackground,
409
+ styles.playIconSkeleton,
410
+ itemStyle,
411
+ appliedTheme.colors,
412
+ ]
413
+ );
414
+
415
+ // ========================================================================
416
+ // FlatList Helpers
417
+ // ========================================================================
418
+ const flatListData = isLoadingState ? Array(skeletonCount).fill(0) : listData;
419
+
420
+ const keyExtractor = useCallback(
421
+ (item: IHistoryItem | number, index: number) =>
422
+ isLoadingState
423
+ ? `skeleton-${index}`
424
+ : (item as IHistoryItem)._id || index.toString(),
425
+ [isLoadingState]
303
426
  );
304
- if (!isLoadingHistory && !isLoading && initialData?.length === 0) return null;
305
427
 
306
- if (!isLoading && !isLoadingHistory && listData.length === 0) return null;
428
+ const renderFlatListItem = useCallback(
429
+ ({ item, index }: { item: IHistoryItem | number; index: number }) => {
430
+ if (isLoadingState) {
431
+ return renderSkeletonItem(index);
432
+ }
433
+ return renderItem({
434
+ item: item as IHistoryItem,
435
+ index,
436
+ separators: {
437
+ highlight: () => {},
438
+ unhighlight: () => {},
439
+ updateProps: () => {},
440
+ },
441
+ });
442
+ },
443
+ [isLoadingState, renderSkeletonItem, renderItem]
444
+ );
445
+
446
+ // ========================================================================
447
+ // Early Returns
448
+ // ========================================================================
449
+ const hasNoData =
450
+ !isLoadingState &&
451
+ (!initialData || initialData.length === 0) &&
452
+ listData.length === 0 &&
453
+ !moreFetchDataHistory;
454
+
455
+ if (hasNoData) {
456
+ return null;
457
+ }
458
+
459
+ // ========================================================================
460
+ // Computed Values
461
+ // ========================================================================
462
+ const shouldShowTitle = !isLoadingState && title;
463
+ const shouldShowSkeletonHeader = isLoadingState;
307
464
 
465
+ // ========================================================================
466
+ // Render
467
+ // ========================================================================
308
468
  return (
309
469
  <View style={[styles.container, containerStyle]}>
310
- {!isLoading && !isLoadingHistory && title && (
470
+ {shouldShowTitle && (
311
471
  <NavigateToMore
312
472
  title={title}
313
473
  onPress={handlePressMore}
@@ -320,71 +480,56 @@ const NowWatching: React.FC<Props> = ({
320
480
  showAllProps={{ iconColor: appliedTheme.colors.textPrimary, theme }}
321
481
  />
322
482
  )}
323
- {(isLoading || isLoadingHistory) && (
483
+ {shouldShowSkeletonHeader && (
324
484
  <SkeletonPlaceholder
325
485
  highlightColor={appliedTheme.colors.skeletonHighlightColor}
326
486
  backgroundColor={appliedTheme.colors.skeletonBaseColor}
327
487
  >
328
488
  <SkeletonPlaceholder.Item
329
- width={moderateScale(100)}
330
- height={verticalScale(14)}
331
- borderRadius={moderateScale(4)}
332
- marginLeft={moderateScale(12)}
333
- marginBottom={verticalScale(6)}
489
+ width={SKELETON_HEADER_WIDTH}
490
+ height={SKELETON_HEADER_HEIGHT}
491
+ borderRadius={SKELETON_HEADER_BORDER_RADIUS}
492
+ marginLeft={SKELETON_HEADER_MARGIN_LEFT}
493
+ marginBottom={SKELETON_HEADER_MARGIN_BOTTOM}
334
494
  />
335
495
  </SkeletonPlaceholder>
336
496
  )}
337
497
  <FlatList
338
498
  ref={flatListRef}
339
499
  horizontal
340
- data={
341
- isLoading || isLoadingHistory
342
- ? Array(skeletonCount).fill(0)
343
- : listData
344
- }
345
- keyExtractor={(_, index) => index.toString()}
346
- renderItem={({ item, index }) =>
347
- isLoading || isLoadingHistory
348
- ? renderSkeletonItem(index)
349
- : renderItem({
350
- item,
351
- index,
352
- separators: {
353
- highlight: () => {},
354
- unhighlight: () => {},
355
- updateProps: () => {},
356
- },
357
- })
358
- }
500
+ data={flatListData}
501
+ keyExtractor={keyExtractor}
502
+ renderItem={renderFlatListItem}
359
503
  showsHorizontalScrollIndicator={false}
360
504
  contentContainerStyle={styles.listContent}
361
505
  onEndReached={handleEndReached}
362
- onEndReachedThreshold={0.5}
363
- onMomentumScrollBegin={() => {
364
- onEndReachedCalledDuringMomentum.current = false;
365
- }}
506
+ onEndReachedThreshold={END_REACHED_THRESHOLD}
507
+ onMomentumScrollBegin={handleMomentumScrollBegin}
366
508
  ListFooterComponent={isPaginating ? renderSkeletonItem(0) : null}
367
509
  />
368
510
  </View>
369
511
  );
370
512
  };
371
513
 
514
+ // ============================================================================
515
+ // Styles
516
+ // ============================================================================
372
517
  const getStyles = (theme: ITheme, itemWidth: number, borderRadius: number) =>
373
518
  StyleSheet.create({
374
519
  container: {
375
520
  marginVertical: verticalScale(6),
376
521
  },
377
522
  listContent: {
378
- paddingRight: moderateScale(15),
523
+ paddingRight: LIST_PADDING_RIGHT,
379
524
  },
380
525
  item: {
381
- marginRight: moderateScale(12),
526
+ marginRight: ITEM_MARGIN_RIGHT,
382
527
  },
383
528
  firstItem: {
384
- marginLeft: moderateScale(10),
529
+ marginLeft: FIRST_ITEM_MARGIN_LEFT,
385
530
  },
386
531
  navigateToMoreContainer: {
387
- paddingHorizontal: moderateScale(10),
532
+ paddingHorizontal: FIRST_ITEM_MARGIN_LEFT,
388
533
  marginBottom: verticalScale(8),
389
534
  },
390
535
  title: {
@@ -396,15 +541,15 @@ const getStyles = (theme: ITheme, itemWidth: number, borderRadius: number) =>
396
541
  borderRadius,
397
542
  overflow: 'hidden',
398
543
  backgroundColor: theme.colors.surfaceVariant,
399
- shadowColor: '#000',
400
- shadowOffset: { width: 0, height: 2 },
401
- shadowOpacity: 0.08,
402
- shadowRadius: 4,
403
- elevation: 2,
544
+ shadowColor: SHADOW_COLOR,
545
+ shadowOffset: SHADOW_OFFSET,
546
+ shadowOpacity: SHADOW_OPACITY,
547
+ shadowRadius: SHADOW_RADIUS,
548
+ elevation: ELEVATION,
404
549
  },
405
550
  poster: {
406
551
  width: '100%',
407
- height: itemWidth * (9 / 16),
552
+ height: itemWidth * ASPECT_RATIO,
408
553
  borderRadius,
409
554
  overflow: 'hidden',
410
555
  backgroundColor: theme.colors.surfaceVariant,
@@ -415,16 +560,16 @@ const getStyles = (theme: ITheme, itemWidth: number, borderRadius: number) =>
415
560
  },
416
561
  infoContainer: {
417
562
  position: 'absolute',
418
- bottom: 6,
419
- left: 8,
420
- right: 8,
563
+ bottom: INFO_CONTAINER_BOTTOM,
564
+ left: INFO_CONTAINER_HORIZONTAL,
565
+ right: INFO_CONTAINER_HORIZONTAL,
421
566
  flexDirection: 'row',
422
567
  alignItems: 'center',
423
568
  justifyContent: 'space-between',
424
569
  },
425
570
  textSection: {
426
571
  flex: 1,
427
- paddingRight: 8,
572
+ paddingRight: TEXT_SECTION_PADDING_RIGHT,
428
573
  },
429
574
  titleText: {
430
575
  color: theme.colors.white,
@@ -434,45 +579,45 @@ const getStyles = (theme: ITheme, itemWidth: number, borderRadius: number) =>
434
579
  subTitleText: {
435
580
  color: theme.colors.white,
436
581
  fontSize: RFValue(10),
437
- opacity: 0.85,
582
+ opacity: SUBTITLE_OPACITY,
438
583
  marginTop: verticalScale(2),
439
584
  },
440
585
  progressBackground: {
441
586
  marginTop: verticalScale(4),
442
587
  height: verticalScale(4),
443
- backgroundColor: '#4F4E4E',
444
- borderRadius: 6,
588
+ backgroundColor: PROGRESS_BG_COLOR,
589
+ borderRadius: PROGRESS_BORDER_RADIUS,
445
590
  width: '100%',
446
591
  },
447
592
  progressForeground: {
448
593
  height: '100%',
449
- borderRadius: 6,
594
+ borderRadius: PROGRESS_BORDER_RADIUS,
450
595
  backgroundColor: theme.colors.primary,
451
596
  },
452
597
  playIconWrapper: {
453
598
  justifyContent: 'center',
454
599
  alignItems: 'center',
455
- width: moderateScale(32),
456
- height: moderateScale(32),
457
- borderRadius: moderateScale(16),
600
+ width: PLAY_ICON_CONTAINER_SIZE,
601
+ height: PLAY_ICON_CONTAINER_SIZE,
602
+ borderRadius: PLAY_ICON_CONTAINER_RADIUS,
458
603
  backgroundColor: theme.colors.blueBackground,
459
604
  },
460
605
  skeletonTitle: {
461
- width: '80%',
462
- height: verticalScale(10),
463
- borderRadius: 4,
606
+ width: SKELETON_TITLE_WIDTH,
607
+ height: SKELETON_TITLE_HEIGHT,
608
+ borderRadius: SKELETON_BORDER_RADIUS,
464
609
  marginBottom: verticalScale(4),
465
610
  },
466
611
  skeletonSubTitle: {
467
- width: '60%',
468
- height: verticalScale(8),
469
- borderRadius: 4,
612
+ width: SKELETON_SUBTITLE_WIDTH,
613
+ height: SKELETON_SUBTITLE_HEIGHT,
614
+ borderRadius: SKELETON_BORDER_RADIUS,
470
615
  marginBottom: verticalScale(6),
471
616
  },
472
617
  playIconSkeleton: {
473
- width: moderateScale(32),
474
- height: moderateScale(32),
475
- borderRadius: moderateScale(16),
618
+ width: PLAY_ICON_CONTAINER_SIZE,
619
+ height: PLAY_ICON_CONTAINER_SIZE,
620
+ borderRadius: PLAY_ICON_CONTAINER_RADIUS,
476
621
  },
477
622
  });
478
623