@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
@@ -13,13 +13,12 @@ import {
13
13
  type TextStyle,
14
14
  type ViewStyle,
15
15
  type AccessibilityProps,
16
- type ListRenderItem,
17
16
  } from 'react-native';
18
17
  import SkeletonPlaceholder from 'react-native-skeleton-placeholder';
19
18
  import { moderateScale, scale, verticalScale } from 'react-native-size-matters';
20
19
  import { useInternalTheme } from '../../../../theme/hook/useInternalTheme';
21
20
  import NavigateToMore from '../components/NavigateToMore';
22
- import { usePaginatedSection } from '../../../../hooks/usePaginatedSection';
21
+
23
22
  import type {
24
23
  IGetSectionData,
25
24
  ISectionContent,
@@ -27,17 +26,63 @@ import type {
27
26
  } from '../../../../types';
28
27
  import type { ITheme, ThemeOverride } from '../../../../theme/themes';
29
28
  import FastImage from 'react-native-fast-image';
30
- import LinearGradient from 'react-native-linear-gradient';
31
- import { Text } from '../../../Text';
32
29
  import CardPoster from '../components/CardPoster';
33
30
  import { RFValue } from 'react-native-responsive-fontsize';
34
31
  import type { IContentData } from '@zezosoft/zezo-ott-api-client';
32
+ import { useCards } from '../../../../hooks';
33
+
34
+ // ============================================
35
+ // Constants
36
+ // ============================================
35
37
 
36
38
  const DEFAULT_ITEM_WIDTH = moderateScale(120);
37
39
  const DEFAULT_ITEM_HEIGHT = moderateScale(180);
38
40
  const DEFAULT_BORDER_RADIUS = moderateScale(8);
39
41
  const DEFAULT_SKELETON_COUNT = 5;
40
42
 
43
+ // Spacing
44
+ const ITEM_MARGIN_RIGHT = moderateScale(12);
45
+ const ITEM_MARGIN_LEFT = moderateScale(10);
46
+ const FIRST_ITEM_MARGIN_LEFT = moderateScale(12);
47
+ const LIST_PADDING_RIGHT = moderateScale(20);
48
+ const CONTAINER_PADDING_HORIZONTAL = moderateScale(16);
49
+ const CONTAINER_MARGIN_BOTTOM = verticalScale(8);
50
+ const ROOT_MARGIN_VERTICAL = verticalScale(6);
51
+
52
+ // Skeleton
53
+ const SKELETON_TITLE_WIDTH = moderateScale(100);
54
+ const SKELETON_TITLE_HEIGHT = verticalScale(14);
55
+ const SKELETON_TITLE_BORDER_RADIUS = moderateScale(4);
56
+ const SKELETON_TITLE_MARGIN_LEFT = moderateScale(12);
57
+ const SKELETON_TITLE_MARGIN_BOTTOM = verticalScale(6);
58
+
59
+ // FlatList Performance
60
+ const INITIAL_NUM_TO_RENDER = 5;
61
+ const MAX_TO_RENDER_PER_BATCH = 10;
62
+ const WINDOW_SIZE = 5;
63
+ const ON_END_REACHED_THRESHOLD = 0.5;
64
+
65
+ // Interaction
66
+ const ACTIVE_OPACITY = 0.8;
67
+
68
+ // ============================================
69
+ // Helper Functions
70
+ // ============================================
71
+
72
+ /**
73
+ * Checks if an item is a skeleton placeholder
74
+ */
75
+ const isSkeletonItem = (item: IContentData): boolean => {
76
+ const hasEmptyPoster = !item.poster || item.poster.trim() === '';
77
+ const isSkeletonId =
78
+ item._id?.startsWith('sk-') || item._id?.startsWith('pagination-skeleton-');
79
+ return hasEmptyPoster || isSkeletonId;
80
+ };
81
+
82
+ // ============================================
83
+ // Types
84
+ // ============================================
85
+
41
86
  type TopTenCardProps = {
42
87
  title: string;
43
88
  section_id: string;
@@ -54,6 +99,7 @@ type TopTenCardProps = {
54
99
  itemHeight?: number;
55
100
  borderRadius?: number;
56
101
  skeletonCount?: number;
102
+ paginationSkeletonCount?: number;
57
103
  containerStyle?: StyleProp<ViewStyle>;
58
104
  titleStyle?: StyleProp<TextStyle>;
59
105
  itemStyle?: StyleProp<ViewStyle>;
@@ -64,7 +110,7 @@ type TopTenCardProps = {
64
110
  const TopTenCard: React.FC<TopTenCardProps> = ({
65
111
  title,
66
112
  section_id,
67
- data: initialData,
113
+ data: externalData,
68
114
  moreFetchData,
69
115
  onPressMore,
70
116
  type,
@@ -76,218 +122,245 @@ const TopTenCard: React.FC<TopTenCardProps> = ({
76
122
  containerStyle,
77
123
  titleStyle,
78
124
  itemStyle,
125
+ paginationSkeletonCount,
79
126
  isLoading = false,
80
127
  theme,
81
128
  accessibilityLabel,
82
129
  accessibilityHint,
83
130
  }) => {
131
+ // ============================================
132
+ // Refs
133
+ // ============================================
84
134
  const flatListRef = useRef<FlatList<IContentData>>(null);
85
135
  const onEndReachedDuringMomentum = useRef(false);
136
+
137
+ // ============================================
138
+ // Theme & Styles
139
+ // ============================================
86
140
  const { theme: appliedTheme } = useInternalTheme(theme);
87
141
  const styles = useMemo(
88
142
  () => getStyles(appliedTheme, itemWidth, itemHeight, borderRadius),
89
143
  [appliedTheme, itemWidth, itemHeight, borderRadius]
90
144
  );
91
145
 
146
+ // ============================================
147
+ // Data Management
148
+ // ============================================
92
149
  const {
93
- data,
94
- pagination: paginated,
95
- loading: isPaginating,
96
- loadMoreData,
97
- } = usePaginatedSection(
98
- section_id,
99
- moreFetchData,
100
- initialData?.data || [],
101
- isLoading
150
+ listData,
151
+ pagination,
152
+ loadMore,
153
+ isEmpty,
154
+ isPaging: isPaginating,
155
+ } = useCards({
156
+ sectionId: section_id,
157
+ data: externalData,
158
+ fetchMore: moreFetchData,
159
+ loading: isLoading,
160
+ initialSkeleton: skeletonCount,
161
+ pagingSkeleton: paginationSkeletonCount,
162
+ adsRender: false,
163
+ });
164
+ const contentData = useMemo(
165
+ () =>
166
+ listData.filter(
167
+ (item): item is IContentData => !('type' in item && item.type === 'ads')
168
+ ),
169
+ [listData]
102
170
  );
103
171
 
104
- const listData = useMemo(() => (Array.isArray(data) ? data : []), [data]);
105
-
106
- const handleEndReached = useCallback(() => {
107
- if (
108
- !onEndReachedDuringMomentum.current &&
109
- paginated?.hasNextPage &&
110
- paginated?.nextPage
111
- ) {
112
- onEndReachedDuringMomentum.current = true;
113
- loadMoreData(paginated.nextPage);
114
- }
115
- }, [loadMoreData, paginated?.hasNextPage, paginated?.nextPage]);
172
+ // ============================================
173
+ // Event Handlers
174
+ // ============================================
116
175
  const handleItemPress = useCallback(
117
176
  (item: IContentData) => {
118
177
  onPressItem?.(item);
119
178
  },
120
179
  [onPressItem]
121
180
  );
181
+
182
+ const handleMorePress = useCallback(() => {
183
+ onPressMore?.({
184
+ section_id,
185
+ name: title,
186
+ type,
187
+ });
188
+ }, [onPressMore, section_id, title, type]);
189
+
190
+ const handleEndReached = useCallback(() => {
191
+ const canLoadMore =
192
+ !onEndReachedDuringMomentum.current &&
193
+ pagination?.hasNextPage &&
194
+ pagination?.nextPage;
195
+
196
+ if (canLoadMore) {
197
+ onEndReachedDuringMomentum.current = true;
198
+ loadMore(pagination.nextPage);
199
+ }
200
+ }, [loadMore, pagination?.hasNextPage, pagination?.nextPage]);
201
+
202
+ const handleMomentumScrollBegin = useCallback(() => {
203
+ onEndReachedDuringMomentum.current = false;
204
+ }, []);
205
+
206
+ // ============================================
207
+ // Render Functions
208
+ // ============================================
209
+ const renderSkeleton = useCallback(
210
+ (isFirst: boolean) => (
211
+ <SkeletonPlaceholder
212
+ highlightColor={appliedTheme.colors.skeletonHighlightColor}
213
+ backgroundColor={appliedTheme.colors.skeletonBaseColor}
214
+ borderRadius={borderRadius}
215
+ >
216
+ <SkeletonPlaceholder.Item
217
+ width={itemWidth}
218
+ height={itemHeight}
219
+ borderRadius={borderRadius}
220
+ marginRight={ITEM_MARGIN_RIGHT}
221
+ marginLeft={isFirst ? FIRST_ITEM_MARGIN_LEFT : 0}
222
+ />
223
+ </SkeletonPlaceholder>
224
+ ),
225
+ [appliedTheme, borderRadius, itemWidth, itemHeight]
226
+ );
227
+
122
228
  const renderItem = useCallback(
123
229
  ({ item, index }: { item: IContentData; index: number }) => {
230
+ const isFirstItem = index === 0;
231
+ const rankNumber = index + 1;
232
+
233
+ if (isSkeletonItem(item)) {
234
+ return renderSkeleton(isFirstItem);
235
+ }
236
+
124
237
  return (
125
238
  <TouchableOpacity
126
- style={[styles.item, index === 0 && styles.firstItem, itemStyle]}
239
+ style={[styles.item, isFirstItem && styles.firstItem, itemStyle]}
127
240
  onPress={() => handleItemPress(item)}
128
- activeOpacity={0.8}
241
+ activeOpacity={ACTIVE_OPACITY}
129
242
  >
130
- <LinearGradient
131
- colors={['rgba(0,0,0,0)', 'rgba(0,0,0,0.8)']}
132
- style={styles.imageOverlay}
133
- >
134
- <CardPoster
135
- content_offering_type={item.content_offering_type}
136
- posterUri={item.poster}
137
- isLoading={isLoading}
138
- borderRadius={borderRadius}
139
- posterWrapperStyle={[styles.image]}
140
- resizeMode={FastImage.resizeMode.cover}
141
- theme={appliedTheme}
142
- />
143
- <View style={styles.numberContainer}>
144
- <LinearGradient
145
- colors={appliedTheme.colors.backgroundLayoutGradient}
146
- start={{ x: 1, y: 0.5 }}
147
- end={{ x: 0.5, y: 1 }}
148
- style={styles.rankNumberBox}
149
- >
150
- <Text style={styles.rankNumberText}>{index + 1}</Text>
151
- </LinearGradient>
152
- </View>
153
- </LinearGradient>
243
+ <CardPoster
244
+ content_offering_type={item.content_offering_type}
245
+ posterUri={item.poster}
246
+ isLoading={isLoading}
247
+ borderRadius={borderRadius}
248
+ posterWrapperStyle={[styles.image]}
249
+ resizeMode={FastImage.resizeMode.cover}
250
+ theme={appliedTheme}
251
+ showRankNumber={true}
252
+ rankNumber={rankNumber}
253
+ />
154
254
  </TouchableOpacity>
155
255
  );
156
256
  },
157
257
  [
158
258
  styles.item,
159
259
  styles.firstItem,
160
- styles.imageOverlay,
161
260
  styles.image,
162
- styles.numberContainer,
163
- styles.rankNumberBox,
164
- styles.rankNumberText,
165
261
  itemStyle,
166
262
  isLoading,
167
263
  borderRadius,
168
264
  appliedTheme,
169
265
  handleItemPress,
266
+ renderSkeleton,
170
267
  ]
171
268
  );
172
269
 
173
- const renderSkeletonItem: ListRenderItem<null> = useCallback(
174
- ({ index }) => (
175
- <SkeletonPlaceholder
176
- key={`skeleton-${index}`}
177
- highlightColor={appliedTheme.colors.skeletonHighlightColor}
178
- backgroundColor={appliedTheme.colors.skeletonBaseColor}
179
- borderRadius={borderRadius}
180
- >
181
- <SkeletonPlaceholder.Item
182
- width={itemWidth}
183
- height={itemHeight}
270
+ const renderPaginationSkeleton = useCallback(() => {
271
+ if (!isPaginating) return null;
272
+
273
+ return (
274
+ <View style={[styles.item, styles.skeletonItem]}>
275
+ <CardPoster
276
+ posterUri=""
277
+ theme={appliedTheme}
278
+ isLoading={true}
184
279
  borderRadius={borderRadius}
185
- marginRight={moderateScale(12)}
186
- marginLeft={index === 0 ? moderateScale(12) : 0}
280
+ showRankNumber={true}
187
281
  />
188
- </SkeletonPlaceholder>
189
- ),
190
- [appliedTheme, itemWidth, itemHeight, borderRadius]
282
+ </View>
283
+ );
284
+ }, [
285
+ isPaginating,
286
+ appliedTheme,
287
+ borderRadius,
288
+ styles.item,
289
+ styles.skeletonItem,
290
+ ]);
291
+
292
+ const keyExtractor = useCallback(
293
+ (item: IContentData, index: number) => `${item._id ?? item.name}-${index}`,
294
+ []
191
295
  );
192
- if (!initialData) {
193
- return null;
194
- }
195
- if (!isLoading && (!listData || listData.length === 0)) return null;
196
296
 
197
- if (isLoading) {
198
- return (
199
- <View
200
- style={[styles.root, containerStyle]}
201
- testID={`movie-card-skeleton-${section_id}`}
202
- >
297
+ // ============================================
298
+ // Early Returns
299
+ // ============================================
300
+ if (!externalData) return null;
301
+ if (!isLoading && contentData.length === 0 && !isEmpty) return null;
302
+
303
+ // ============================================
304
+ // Render
305
+ // ============================================
306
+ return (
307
+ <View
308
+ style={[styles.root, containerStyle]}
309
+ accessibilityLabel={accessibilityLabel || `Top Ten Section: ${title}`}
310
+ accessibilityHint={accessibilityHint || 'Scroll to view top content'}
311
+ testID={isLoading ? `movie-card-skeleton-${section_id}` : undefined}
312
+ >
313
+ {/* Title Section */}
314
+ {isLoading ? (
203
315
  <SkeletonPlaceholder
204
316
  highlightColor={appliedTheme.colors.skeletonHighlightColor}
205
317
  backgroundColor={appliedTheme.colors.skeletonBaseColor}
206
318
  >
207
319
  <SkeletonPlaceholder.Item
208
- width={moderateScale(100)}
209
- height={verticalScale(14)}
210
- borderRadius={moderateScale(4)}
211
- marginLeft={moderateScale(12)}
212
- marginBottom={verticalScale(6)}
320
+ width={SKELETON_TITLE_WIDTH}
321
+ height={SKELETON_TITLE_HEIGHT}
322
+ borderRadius={SKELETON_TITLE_BORDER_RADIUS}
323
+ marginLeft={SKELETON_TITLE_MARGIN_LEFT}
324
+ marginBottom={SKELETON_TITLE_MARGIN_BOTTOM}
213
325
  />
214
326
  </SkeletonPlaceholder>
215
- <FlatList
216
- data={Array.from({ length: skeletonCount }, () => null)}
217
- horizontal
218
- keyExtractor={(_, index) => `skeleton-${index}`}
219
- renderItem={renderSkeletonItem}
220
- showsHorizontalScrollIndicator={false}
221
- contentContainerStyle={styles.listContent}
327
+ ) : (
328
+ <NavigateToMore
329
+ title={title}
330
+ onPress={handleMorePress}
331
+ titleStyle={[styles.title, titleStyle]}
332
+ containerStyle={styles.navigateToMoreContainer}
333
+ showAllProps={{
334
+ iconColor: appliedTheme.colors.textPrimary,
335
+ theme,
336
+ }}
222
337
  />
223
- </View>
224
- );
225
- }
226
- return (
227
- <View
228
- style={[styles.root, containerStyle]}
229
- accessibilityLabel={accessibilityLabel || `Top Ten Section: ${title}`}
230
- accessibilityHint={accessibilityHint || 'Scroll to view top content'}
231
- >
232
- <NavigateToMore
233
- title={title}
234
- onPress={() =>
235
- onPressMore?.({
236
- section_id,
237
- name: title,
238
- type,
239
- })
240
- }
241
- titleStyle={[styles.title, titleStyle]}
242
- containerStyle={styles.navigateToMoreContainer}
243
- showAllProps={{ iconColor: appliedTheme.colors.textPrimary, theme }}
244
- />
338
+ )}
339
+
340
+ {/* Content List */}
245
341
  <FlatList
246
342
  ref={flatListRef}
247
- data={listData}
343
+ data={contentData}
248
344
  horizontal
249
345
  showsHorizontalScrollIndicator={false}
250
- keyExtractor={(item, index) => `${item._id ?? item.name}-${index}`}
346
+ keyExtractor={keyExtractor}
251
347
  renderItem={renderItem}
252
348
  contentContainerStyle={styles.listContent}
253
- initialNumToRender={5}
254
- maxToRenderPerBatch={10}
255
- windowSize={5}
256
- onEndReachedThreshold={0.5}
257
- onMomentumScrollBegin={() => {
258
- onEndReachedDuringMomentum.current = false;
259
- }}
349
+ initialNumToRender={INITIAL_NUM_TO_RENDER}
350
+ maxToRenderPerBatch={MAX_TO_RENDER_PER_BATCH}
351
+ windowSize={WINDOW_SIZE}
352
+ onEndReachedThreshold={ON_END_REACHED_THRESHOLD}
353
+ onMomentumScrollBegin={handleMomentumScrollBegin}
260
354
  onEndReached={handleEndReached}
261
- ListFooterComponent={
262
- isPaginating ? (
263
- <SkeletonPlaceholder
264
- highlightColor={appliedTheme.colors.skeletonHighlightColor}
265
- backgroundColor={appliedTheme.colors.skeletonBaseColor}
266
- borderRadius={borderRadius}
267
- speed={800}
268
- >
269
- <View style={[styles.item, styles.skeletonItem]}>
270
- <SkeletonPlaceholder.Item
271
- width={itemWidth}
272
- height={itemHeight}
273
- borderRadius={borderRadius}
274
- />
275
- <View style={styles.numberContainer}>
276
- <SkeletonPlaceholder.Item
277
- width={scale(40)}
278
- height={scale(40)}
279
- borderRadius={borderRadius}
280
- />
281
- </View>
282
- </View>
283
- </SkeletonPlaceholder>
284
- ) : null
285
- }
355
+ ListFooterComponent={renderPaginationSkeleton}
286
356
  />
287
357
  </View>
288
358
  );
289
359
  };
290
360
 
361
+ // ============================================
362
+ // Styles
363
+ // ============================================
291
364
  const getStyles = (
292
365
  theme: ITheme,
293
366
  width: number,
@@ -296,64 +369,30 @@ const getStyles = (
296
369
  ) =>
297
370
  StyleSheet.create({
298
371
  root: {
299
- marginVertical: verticalScale(6),
372
+ marginVertical: ROOT_MARGIN_VERTICAL,
300
373
  },
301
374
  item: {
302
375
  width,
303
376
  height,
304
- marginRight: moderateScale(12),
305
- overflow: 'hidden',
377
+ marginRight: ITEM_MARGIN_RIGHT,
306
378
  borderColor: theme.colors.outlineDisabled,
307
- borderWidth: scale(0.5),
308
- aspectRatio: 2 / 3,
379
+ borderWidth: scale(0.7),
309
380
  borderRadius: radius,
310
381
  position: 'relative',
311
382
  },
312
383
  firstItem: {
313
- marginLeft: moderateScale(10),
314
- },
315
- imageOverlay: {
316
- width: '100%',
317
- height: '100%',
318
- justifyContent: 'flex-end',
319
- alignItems: 'flex-start',
384
+ marginLeft: ITEM_MARGIN_LEFT,
320
385
  },
321
386
  image: {
322
387
  width: '100%',
323
388
  height: '100%',
324
- borderRadius: radius,
325
- },
326
-
327
- rankNumberBox: {
328
- width: scale(40),
329
- height: scale(40),
330
- borderTopRightRadius: scale(30),
331
- justifyContent: 'center',
332
- alignItems: 'center',
333
- },
334
- rankNumberText: {
335
- fontSize: RFValue(27),
336
- fontWeight: '900',
337
- color: theme.colors.textPrimary,
338
- textShadowColor: 'rgba(0, 0, 0, 0.6)',
339
- textShadowOffset: { width: 1, height: 1 },
340
- textShadowRadius: 3,
341
- marginTop: scale(5),
342
- marginRight: scale(5),
343
- letterSpacing: 0.5,
344
- },
345
- numberContainer: {
346
- position: 'absolute',
347
- bottom: 0,
348
- left: 0,
349
- zIndex: 10,
350
389
  },
351
390
  navigateToMoreContainer: {
352
- paddingHorizontal: moderateScale(16),
353
- marginBottom: verticalScale(8),
391
+ paddingHorizontal: CONTAINER_PADDING_HORIZONTAL,
392
+ marginBottom: CONTAINER_MARGIN_BOTTOM,
354
393
  },
355
394
  listContent: {
356
- paddingRight: moderateScale(20),
395
+ paddingRight: LIST_PADDING_RIGHT,
357
396
  },
358
397
  title: {
359
398
  fontSize: RFValue(13),