@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
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * @author Naresh Dhamu
3
- * @lastModified Sat 25 Oct 2025 at 04:01 PM
3
+ * @lastModified Wed 24 Dec 2025 at 04:01 PM
4
4
  */
5
5
 
6
- import React, { useState, useEffect, useMemo, useCallback } from 'react';
6
+ import React, { useMemo } from 'react';
7
7
  import { View, StyleSheet, type StyleProp, type ViewStyle } from 'react-native';
8
8
  import FastImage, {
9
9
  type ResizeMode,
@@ -16,9 +16,18 @@ import { ImageOff } from 'lucide-react-native';
16
16
  import type { ITheme } from '../../../../theme/themes';
17
17
  import RentOrBuyIcon from './RentOrBuyIcon';
18
18
  import type { IContentData } from '@zezosoft/zezo-ott-api-client';
19
+ import { useImageLoader } from '../../../../hooks';
20
+ import LinearGradient from 'react-native-linear-gradient';
21
+ import { RFValue } from 'react-native-responsive-fontsize';
19
22
 
20
- // Module-level cache to track successfully loaded images
21
- const loadedImageCache = new Set<string>();
23
+ // Rank number styling constants
24
+ const RANK_NUMBER_SIZE = scale(40);
25
+ const RANK_NUMBER_BORDER_RADIUS = scale(30);
26
+ const RANK_NUMBER_TEXT_SIZE = RFValue(25);
27
+ const RANK_NUMBER_TEXT_MARGIN_TOP = scale(2);
28
+ const RANK_NUMBER_TEXT_MARGIN_RIGHT = scale(6);
29
+ const GRADIENT_START = { x: 1, y: 0.5 };
30
+ const GRADIENT_END = { x: 0.5, y: 1 };
22
31
 
23
32
  export type CardPosterProps = {
24
33
  content_offering_type?: IContentData['content_offering_type'];
@@ -29,63 +38,45 @@ export type CardPosterProps = {
29
38
  posterWrapperStyle?: StyleProp<ViewStyle>;
30
39
  imageStyle?: ImageStyle;
31
40
  resizeMode?: ResizeMode;
41
+ showRankNumber?: boolean;
42
+ rankNumber?: number;
32
43
  };
33
44
 
45
+ // ----------------------------------
46
+ // Image Fallback
47
+ // ----------------------------------
48
+ const ImageFallback = React.memo(({ errorColor, bg }: any) => (
49
+ <View style={[styles.absolute, styles.center, { backgroundColor: bg }]}>
50
+ <ImageOff size={scale(18)} color={errorColor} />
51
+ <Text style={styles.fallbackText} color={errorColor}>
52
+ Image Failed
53
+ </Text>
54
+ </View>
55
+ ));
34
56
  const CardPoster: React.FC<CardPosterProps> = ({
35
57
  content_offering_type,
36
58
  posterUri,
37
59
  theme,
38
- isLoading = false,
39
60
  borderRadius = scale(6),
40
61
  posterWrapperStyle,
41
62
  imageStyle,
63
+ isLoading = false,
42
64
  resizeMode = FastImage.resizeMode.cover,
65
+ showRankNumber = false,
66
+ rankNumber,
43
67
  }) => {
44
- const [imageLoading, setImageLoading] = useState(true);
45
- const [imageError, setImageError] = useState(false);
46
-
47
- const hasValidPoster = useMemo(() => {
48
- if (!posterUri || typeof posterUri !== 'string') return false;
49
- const cleaned = posterUri.trim();
50
- return (
51
- (cleaned.startsWith('http://') || cleaned.startsWith('https://')) &&
52
- /\.(jpg|jpeg|png|webp|gif|bmp)$/i.test(cleaned)
53
- );
54
- }, [posterUri]);
55
-
56
- // Reset image loading state when isLoading prop changes
57
- useEffect(() => {
58
- if (isLoading) {
59
- setImageLoading(true);
60
- setImageError(false);
61
- }
62
- }, [isLoading]);
63
-
64
- useEffect(() => {
65
- if (hasValidPoster) {
66
- // Check if this image was already loaded successfully before
67
- const isAlreadyLoaded = loadedImageCache.has(posterUri);
68
-
69
- if (isAlreadyLoaded) {
70
- // Image was loaded before, skip loading state
71
- setImageLoading(false);
72
- setImageError(false);
73
- } else {
74
- // Image not loaded before, show loading
75
- setImageLoading(true);
76
- setImageError(false);
77
- FastImage.preload([{ uri: posterUri }]);
78
- }
79
- } else {
80
- setImageLoading(false);
81
- setImageError(true);
82
- }
83
- }, [posterUri, hasValidPoster]);
84
-
85
- const showSkeleton = useMemo(
86
- () => isLoading || (imageLoading && !imageError && hasValidPoster),
87
- [isLoading, imageLoading, imageError, hasValidPoster]
88
- );
68
+ const {
69
+ showSkeleton,
70
+ showFallback,
71
+ hasValidImage,
72
+ imageSource,
73
+ imageLoading,
74
+ handleLoad,
75
+ handleError,
76
+ } = useImageLoader({
77
+ imageUri: posterUri,
78
+ isLoading,
79
+ });
89
80
 
90
81
  // Memoize theme colors
91
82
  const themeColors = useMemo(
@@ -95,141 +86,113 @@ const CardPoster: React.FC<CardPosterProps> = ({
95
86
  skeletonHighlightColor: theme.colors.skeletonHighlightColor,
96
87
  error: theme.colors.error,
97
88
  errorContainer: theme.colors.errorContainer,
89
+ border: theme.colors.border,
98
90
  }),
99
91
  [theme.colors]
100
92
  );
101
93
 
102
- const fallbackImageStyle: ImageStyle = useMemo(
103
- () => ({
104
- width: '100%',
105
- aspectRatio: 2 / 3,
106
- borderRadius,
107
- position: 'absolute',
108
- top: 0,
109
- left: 0,
110
- }),
111
- [borderRadius]
112
- );
113
-
114
- // Memoize FastImage source
115
- const imageSource = useMemo(
116
- () => ({
117
- uri: posterUri,
118
- cache: FastImage.cacheControl.immutable,
119
- priority: FastImage.priority.normal,
120
- }),
121
- [posterUri]
122
- );
123
-
124
- // Memoize callback handlers
125
- const handleLoadStart = useCallback(() => {
126
- // Only set loading if image is not already in cache
127
- if (!loadedImageCache.has(posterUri)) {
128
- setImageLoading(true);
129
- setImageError(false);
130
- }
131
- }, [posterUri]);
132
-
133
- const handleLoad = useCallback(() => {
134
- setImageLoading(false);
135
- setImageError(false);
136
- // Add to cache when successfully loaded
137
- loadedImageCache.add(posterUri);
138
- }, [posterUri]);
139
-
140
- const handleError = useCallback(() => {
141
- setImageLoading(false);
142
- setImageError(true);
143
- }, []);
144
-
145
94
  // Memoize wrapper style
146
95
  const wrapperStyle = useMemo(
147
96
  () => [
148
97
  styles.posterWrapper,
149
- { borderRadius, backgroundColor: themeColors.background },
98
+ {
99
+ width: '100%' as const,
100
+ aspectRatio: 2 / 3,
101
+ borderRadius,
102
+ backgroundColor: themeColors.background,
103
+ borderWidth: showSkeleton ? scale(0.5) : showFallback ? scale(0.3) : 0,
104
+ borderColor: showSkeleton
105
+ ? themeColors.border
106
+ : showFallback
107
+ ? themeColors.error
108
+ : 'transparent',
109
+ } as ViewStyle,
150
110
  posterWrapperStyle,
151
111
  ],
152
- [borderRadius, themeColors.background, posterWrapperStyle]
153
- );
154
-
155
- // Memoize skeleton wrapper style
156
- const skeletonWrapperStyle = useMemo(
157
- () => [styles.skeletonWrapper, { borderRadius }],
158
- [borderRadius]
112
+ [
113
+ borderRadius,
114
+ themeColors.background,
115
+ themeColors.border,
116
+ themeColors.error,
117
+ showSkeleton,
118
+ showFallback,
119
+ posterWrapperStyle,
120
+ ]
159
121
  );
160
122
 
161
123
  // Memoize skeleton item style
162
- const skeletonItemStyle = useMemo(
163
- () => [styles.skeleton, { borderRadius }],
164
- [borderRadius]
165
- );
166
124
 
167
- // Memoize image style array
125
+ // Memoize image style array - hide image until it successfully loads
168
126
  const imageStyleArray = useMemo(
169
127
  () => [
170
- fallbackImageStyle,
171
- showSkeleton ? styles.hidden : styles.visible,
128
+ styles.absolute,
129
+ // Hide image when skeleton is showing or image is still loading
130
+ showSkeleton || imageLoading ? styles.hidden : styles.visible,
172
131
  imageStyle,
173
132
  ],
174
- [fallbackImageStyle, showSkeleton, imageStyle]
175
- );
176
-
177
- // Memoize fallback wrapper style
178
- const fallbackWrapperStyle = useMemo(
179
- () => [
180
- styles.fallbackWrapper,
181
- {
182
- borderRadius,
183
- backgroundColor: themeColors.background,
184
- borderColor: themeColors.error,
185
- },
186
- ],
187
- [borderRadius, themeColors.background, themeColors.error]
188
- );
189
-
190
- // Memoize fallback inner style
191
- const fallbackInnerStyle = useMemo(
192
- () => [styles.fallback, { backgroundColor: themeColors.errorContainer }],
193
- [themeColors.errorContainer]
133
+ [showSkeleton, imageLoading, imageStyle]
194
134
  );
195
135
 
196
136
  return (
197
137
  <View style={wrapperStyle}>
198
- {!showSkeleton && content_offering_type && (
138
+ {/* Rent/Buy */}
139
+ {!showSkeleton && !showFallback && content_offering_type && (
199
140
  <RentOrBuyIcon
200
- theme={theme}
201
141
  key={posterUri}
202
142
  content_offering_type={content_offering_type}
143
+ theme={theme}
203
144
  />
204
145
  )}
146
+
147
+ {/* Skeleton */}
205
148
  {showSkeleton && (
206
- <View style={skeletonWrapperStyle}>
149
+ <View style={[styles.absolute]}>
207
150
  <SkeletonPlaceholder
208
151
  backgroundColor={themeColors.skeletonBaseColor}
209
152
  highlightColor={themeColors.skeletonHighlightColor}
210
153
  >
211
- <SkeletonPlaceholder.Item style={skeletonItemStyle} />
154
+ <SkeletonPlaceholder.Item width="100%" height="100%" />
212
155
  </SkeletonPlaceholder>
213
156
  </View>
214
157
  )}
215
- {hasValidPoster && (
158
+
159
+ {/* Valid Image - Render when has valid image, but keep hidden until loaded */}
160
+ {hasValidImage && !showFallback && (
216
161
  <FastImage
162
+ key={`${posterUri}-image`}
217
163
  source={imageSource}
218
164
  style={imageStyleArray}
219
165
  resizeMode={resizeMode}
220
- onLoadStart={handleLoadStart}
221
166
  onLoad={handleLoad}
222
167
  onError={handleError}
223
168
  />
224
169
  )}
225
- {!showSkeleton && (!hasValidPoster || imageError) && (
226
- <View style={fallbackWrapperStyle}>
227
- <View style={fallbackInnerStyle}>
228
- <ImageOff size={scale(18)} color={themeColors.error} />
229
- <Text style={styles.fallbackText} color={themeColors.error}>
230
- Image Failed
170
+
171
+ {/* Fallback */}
172
+ {!isLoading && !showSkeleton && showFallback && (
173
+ <ImageFallback
174
+ key={`${posterUri}-fallback`}
175
+ errorColor={themeColors.error}
176
+ bg={themeColors.errorContainer}
177
+ />
178
+ )}
179
+
180
+ {/* Rank Number */}
181
+ {!showSkeleton && showRankNumber && (
182
+ <View style={styles.numberContainer}>
183
+ <LinearGradient
184
+ colors={theme.colors.backgroundLayoutGradient}
185
+ start={GRADIENT_START}
186
+ end={GRADIENT_END}
187
+ style={[styles.rankNumberBox]}
188
+ >
189
+ <Text
190
+ style={styles.rankNumberText}
191
+ color={theme.colors.textPrimary}
192
+ >
193
+ {rankNumber ?? ''}
231
194
  </Text>
232
- </View>
195
+ </LinearGradient>
233
196
  </View>
234
197
  )}
235
198
  </View>
@@ -240,27 +203,23 @@ export default React.memo(CardPoster);
240
203
 
241
204
  const styles = StyleSheet.create({
242
205
  posterWrapper: {
243
- width: '100%',
244
- aspectRatio: 2 / 3,
245
206
  overflow: 'hidden',
246
207
  position: 'relative',
247
208
  },
248
- skeletonWrapper: {
249
- position: 'absolute',
250
- top: 0,
251
- left: 0,
252
- width: '100%',
253
- aspectRatio: 2 / 3,
209
+
210
+ absolute: {
211
+ ...StyleSheet.absoluteFillObject,
212
+ },
213
+ center: {
214
+ justifyContent: 'center',
215
+ alignItems: 'center',
254
216
  },
255
- skeleton: { width: '100%', aspectRatio: 2 / 3 },
256
217
  visible: { opacity: 1 },
257
218
  hidden: { opacity: 0 },
258
219
  fallbackWrapper: {
259
220
  position: 'absolute',
260
221
  top: 0,
261
222
  left: 0,
262
- width: '100%',
263
- aspectRatio: 2 / 3,
264
223
  borderWidth: scale(1),
265
224
  alignItems: 'center',
266
225
  justifyContent: 'center',
@@ -269,10 +228,31 @@ const styles = StyleSheet.create({
269
228
  position: 'absolute',
270
229
  top: 0,
271
230
  left: 0,
272
- width: '100%',
273
- aspectRatio: 2 / 3,
274
231
  alignItems: 'center',
275
232
  justifyContent: 'center',
276
233
  },
277
234
  fallbackText: { marginTop: scale(4), fontSize: 10 },
235
+ numberContainer: {
236
+ position: 'absolute',
237
+ bottom: -1,
238
+ left: 0,
239
+ zIndex: 10,
240
+ },
241
+ rankNumberBox: {
242
+ width: RANK_NUMBER_SIZE,
243
+ height: RANK_NUMBER_SIZE,
244
+ borderTopRightRadius: RANK_NUMBER_BORDER_RADIUS,
245
+ justifyContent: 'center',
246
+ alignItems: 'center',
247
+ },
248
+ rankNumberText: {
249
+ fontSize: RANK_NUMBER_TEXT_SIZE,
250
+ fontWeight: '900',
251
+ textShadowColor: 'rgba(0, 0, 0, 0.6)',
252
+ textShadowOffset: { width: 1, height: 1 },
253
+ textShadowRadius: 3,
254
+ marginTop: RANK_NUMBER_TEXT_MARGIN_TOP,
255
+ marginRight: RANK_NUMBER_TEXT_MARGIN_RIGHT,
256
+ letterSpacing: 0.5,
257
+ },
278
258
  });
@@ -0,0 +1 @@
1
+ export * from './AdsPoster';
@@ -5,6 +5,7 @@
5
5
  import React, { useMemo, useCallback } from 'react';
6
6
  import {
7
7
  ActivityIndicator,
8
+ Dimensions,
8
9
  FlatList,
9
10
  type FlatListProps,
10
11
  type NativeScrollEvent,
@@ -19,7 +20,7 @@ import Animated, {
19
20
  } from 'react-native-reanimated';
20
21
 
21
22
  import SectionType from './Sections';
22
- import type { IGetSectionData, MoreFetchData } from '../../types';
23
+ import type { IAdItem, IGetSectionData, MoreFetchData } from '../../types';
23
24
  import { dummySections } from '../../constants/dummySections';
24
25
  import CategoryCard, { type ICategory } from './Card/Category/Category';
25
26
  import type { IHistoryItem } from './Card/NowWatching/NowWatching';
@@ -27,8 +28,23 @@ import type { ThemeOverride } from '../../theme/themes';
27
28
  import { NoContentFallback, type NoContentFallbackProps } from '../Fallbacks';
28
29
  import { useInternalTheme } from '../../theme/hook';
29
30
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
30
- import type { IContentData } from '@zezosoft/zezo-ott-api-client';
31
+ import type { IContentData, IServeAd } from '@zezosoft/zezo-ott-api-client';
31
32
 
33
+ // Constants
34
+ const VIEWPORT_OFFSETS = {
35
+ top: 140,
36
+ bottom: 140,
37
+ left: 20,
38
+ right: 20,
39
+ } as const;
40
+
41
+ const INITIAL_NUM_TO_RENDER = 5;
42
+ const SCROLL_EVENT_THROTTLE = 16;
43
+ const END_REACHED_THRESHOLD = 0.7;
44
+ const LOADER_PADDING_VERTICAL = 30;
45
+ const CONTENT_PADDING_BOTTOM = 100;
46
+
47
+ // Types
32
48
  export interface ICustomComponentsForContent {
33
49
  type: string;
34
50
  component: React.ReactElement;
@@ -62,7 +78,7 @@ export interface IContentProps {
62
78
  theme?: ThemeOverride;
63
79
  autoSwitchTheme?: boolean;
64
80
  events?: {
65
- onPressItem?: (item: IContentData) => void;
81
+ onPressItem?: (item: IContentData | IAdItem) => void;
66
82
  onPressMore?: (params: {
67
83
  section_id: IGetSectionData['_id'];
68
84
  name: IGetSectionData['name'];
@@ -70,6 +86,7 @@ export interface IContentProps {
70
86
  }) => void;
71
87
  activeCategory?: ICategory;
72
88
  onPressCategory?: (category: ICategory) => void;
89
+ onDisplayAds?: (ad: IServeAd) => void;
73
90
  };
74
91
 
75
92
  sectionProps?: {
@@ -82,9 +99,12 @@ export interface IContentProps {
82
99
  noContentFallbackProps?: NoContentFallbackProps;
83
100
  }
84
101
 
102
+ // Animated Components
85
103
  const AnimatedFlatList = Animated.createAnimatedComponent(
86
104
  FlatList as React.ComponentType<FlatListProps<IGetSectionData>>
87
105
  );
106
+
107
+ // Footer Component Types
88
108
  interface FooterProps {
89
109
  isEmpty: boolean;
90
110
  ErrorComponent?: React.ReactNode;
@@ -94,6 +114,7 @@ interface FooterProps {
94
114
  noContentFallbackProps?: NoContentFallbackProps;
95
115
  }
96
116
 
117
+ // Footer Component
97
118
  const ContentFooter = React.memo(
98
119
  ({
99
120
  isEmpty,
@@ -107,7 +128,7 @@ const ContentFooter = React.memo(
107
128
 
108
129
  const loaderStyle = useMemo(
109
130
  () => ({
110
- paddingVertical: scale(30),
131
+ paddingVertical: scale(LOADER_PADDING_VERTICAL),
111
132
  alignItems: 'center' as const,
112
133
  justifyContent: 'center' as const,
113
134
  backgroundColor: 'transparent',
@@ -144,6 +165,7 @@ const ContentFooter = React.memo(
144
165
  }
145
166
  );
146
167
 
168
+ // Main Content Component
147
169
  export const Content: React.FC<IContentProps> = ({
148
170
  customComponents,
149
171
  InfiniteScrollLoaderComponent,
@@ -161,13 +183,15 @@ export const Content: React.FC<IContentProps> = ({
161
183
  isLoading,
162
184
  noContentFallbackProps,
163
185
  }) => {
186
+ // Hooks
164
187
  const insets = useSafeAreaInsets();
188
+
189
+ // Memoized Data
165
190
  const sectionData = useMemo<IGetSectionData[]>(
166
191
  () => contentData?.sectionData ?? [],
167
192
  [contentData?.sectionData]
168
193
  );
169
194
 
170
- // Memoize section data object to prevent unnecessary re-renders
171
195
  const sectionDataMemo = useMemo(
172
196
  () => ({
173
197
  historyData: contentData.historyData,
@@ -176,7 +200,6 @@ export const Content: React.FC<IContentProps> = ({
176
200
  [contentData.historyData, contentData.category]
177
201
  );
178
202
 
179
- // Memoize loading state object
180
203
  const loadingStateMemo = useMemo(
181
204
  () => ({
182
205
  section: isLoading?.section ?? false,
@@ -186,9 +209,23 @@ export const Content: React.FC<IContentProps> = ({
186
209
  [isLoading?.section, isLoading?.history, isLoading?.category]
187
210
  );
188
211
 
212
+ const screenDimensions = useMemo(() => Dimensions.get('window'), []);
213
+
214
+ const listData = useMemo(
215
+ () => (isLoading?.section ? dummySections : sectionData),
216
+ [isLoading?.section, sectionData]
217
+ );
218
+
219
+ const isEmpty = useMemo(
220
+ () => !isLoading?.section && (!sectionData || sectionData.length === 0),
221
+ [isLoading?.section, sectionData]
222
+ );
223
+
224
+ // Header Component
189
225
  const headerComponent = useMemo(() => {
190
- if (!contentData?.category || contentData.category.length === 0)
226
+ if (!contentData?.category || contentData.category.length === 0) {
191
227
  return null;
228
+ }
192
229
 
193
230
  return (
194
231
  <CategoryCard
@@ -198,7 +235,7 @@ export const Content: React.FC<IContentProps> = ({
198
235
  isLoading={isLoading?.category}
199
236
  mode="filled"
200
237
  theme={theme}
201
- key={'category'}
238
+ key="category"
202
239
  />
203
240
  );
204
241
  }, [
@@ -209,6 +246,7 @@ export const Content: React.FC<IContentProps> = ({
209
246
  theme,
210
247
  ]);
211
248
 
249
+ // Render Item Callback
212
250
  const renderItem = useCallback(
213
251
  ({ item, index }: { item: IGetSectionData | null; index: number }) => {
214
252
  if (!item) return null;
@@ -241,83 +279,83 @@ export const Content: React.FC<IContentProps> = ({
241
279
  sectionProps={sectionProps}
242
280
  moreFetchDataHistory={moreFetchDataHistory}
243
281
  moreFetchData={moreFetchData}
282
+ onDisplayAds={events?.onDisplayAds}
283
+ screenDimensions={screenDimensions}
284
+ viewportOffsets={VIEWPORT_OFFSETS}
244
285
  />
245
286
  );
246
287
  },
247
288
  [
248
- isLoading,
289
+ isLoading?.section,
249
290
  LoaderComponent,
250
291
  sectionDataMemo,
251
- loadingStateMemo,
252
292
  theme,
253
293
  customComponents,
254
294
  events,
295
+ loadingStateMemo,
255
296
  sectionProps,
256
297
  moreFetchDataHistory,
257
298
  moreFetchData,
299
+ screenDimensions,
258
300
  ]
259
301
  );
260
302
 
261
- const isEmpty = useMemo(
262
- () => !isLoading?.section && (!sectionData || sectionData.length === 0),
263
- [isLoading?.section, sectionData]
264
- );
265
-
303
+ // Key Extractor
266
304
  const keyExtractor = useCallback(
267
305
  (_item: IGetSectionData, index: number) => index.toString(),
268
306
  []
269
307
  );
270
308
 
309
+ // Handlers
271
310
  const handleEndReached = useCallback(() => {
272
311
  onEndReached?.();
273
312
  }, [onEndReached]);
274
313
 
314
+ // Styles
275
315
  const contentContainerStyleMemo = useMemo(
276
316
  () => [
277
- { paddingBottom: verticalScale(100) + insets.bottom },
317
+ { paddingBottom: verticalScale(CONTENT_PADDING_BOTTOM) + insets.bottom },
278
318
  contentContainerStyle,
279
319
  ],
280
320
  [insets.bottom, contentContainerStyle]
281
321
  );
282
322
 
283
- const listData = useMemo(
284
- () => (isLoading?.section ? dummySections : sectionData),
285
- [isLoading?.section, sectionData]
323
+ // Footer Component
324
+ const footerComponent = useMemo(
325
+ () => (
326
+ <ContentFooter
327
+ InfiniteScrollLoaderComponent={InfiniteScrollLoaderComponent}
328
+ InfiniteScrollIsLoading={isLoading?.InfiniteScrollIsLoading}
329
+ isEmpty={isEmpty}
330
+ ErrorComponent={ErrorComponent}
331
+ theme={theme}
332
+ noContentFallbackProps={noContentFallbackProps}
333
+ />
334
+ ),
335
+ [
336
+ InfiniteScrollLoaderComponent,
337
+ isLoading?.InfiniteScrollIsLoading,
338
+ isEmpty,
339
+ ErrorComponent,
340
+ theme,
341
+ noContentFallbackProps,
342
+ ]
286
343
  );
287
344
 
288
345
  return (
289
346
  <AnimatedFlatList
290
- initialNumToRender={5}
291
- showsVerticalScrollIndicator={false}
292
- removeClippedSubviews
293
- onScroll={onScroll as any}
294
- scrollEventThrottle={16}
295
347
  data={listData}
296
348
  renderItem={renderItem as any}
297
349
  keyExtractor={keyExtractor}
350
+ onScroll={onScroll as any}
298
351
  onEndReached={handleEndReached}
352
+ onEndReachedThreshold={END_REACHED_THRESHOLD}
353
+ scrollEventThrottle={SCROLL_EVENT_THROTTLE}
354
+ initialNumToRender={INITIAL_NUM_TO_RENDER}
355
+ showsVerticalScrollIndicator={false}
356
+ removeClippedSubviews
299
357
  ListHeaderComponent={headerComponent}
300
- ListFooterComponent={useMemo(
301
- () => (
302
- <ContentFooter
303
- InfiniteScrollLoaderComponent={InfiniteScrollLoaderComponent}
304
- InfiniteScrollIsLoading={isLoading?.InfiniteScrollIsLoading}
305
- isEmpty={isEmpty}
306
- ErrorComponent={ErrorComponent}
307
- theme={theme}
308
- noContentFallbackProps={noContentFallbackProps}
309
- />
310
- ),
311
- [
312
- InfiniteScrollLoaderComponent,
313
- isLoading?.InfiniteScrollIsLoading,
314
- isEmpty,
315
- ErrorComponent,
316
- theme,
317
- noContentFallbackProps,
318
- ]
319
- )}
320
- onEndReachedThreshold={0.7}
358
+ ListFooterComponent={footerComponent}
321
359
  contentContainerStyle={contentContainerStyleMemo}
322
360
  />
323
361
  );