@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.
- package/lib/module/components/Auth/QrLogin/QrLogin.js +304 -138
- package/lib/module/components/Auth/QrLogin/QrLogin.js.map +1 -1
- package/lib/module/components/Auth/QrLogin/components/QrViewArea.js +193 -141
- package/lib/module/components/Auth/QrLogin/components/QrViewArea.js.map +1 -1
- package/lib/module/components/Content/Card/Category/Category.js +83 -11
- package/lib/module/components/Content/Card/Category/Category.js.map +1 -1
- package/lib/module/components/Content/Card/NowWatching/NowWatching.js +237 -108
- package/lib/module/components/Content/Card/NowWatching/NowWatching.js.map +1 -1
- package/lib/module/components/Content/Card/Sliders/Styles/One.js +185 -126
- package/lib/module/components/Content/Card/Sliders/Styles/One.js.map +1 -1
- package/lib/module/components/Content/Card/Sliders/Styles/Two.js +139 -92
- package/lib/module/components/Content/Card/Sliders/Styles/Two.js.map +1 -1
- package/lib/module/components/Content/Card/Styles/Five.js +131 -48
- package/lib/module/components/Content/Card/Styles/Five.js.map +1 -1
- package/lib/module/components/Content/Card/Styles/Four.js +126 -59
- package/lib/module/components/Content/Card/Styles/Four.js.map +1 -1
- package/lib/module/components/Content/Card/Styles/One.js +125 -50
- package/lib/module/components/Content/Card/Styles/One.js.map +1 -1
- package/lib/module/components/Content/Card/Styles/RotateInOut.js +138 -53
- package/lib/module/components/Content/Card/Styles/RotateInOut.js.map +1 -1
- package/lib/module/components/Content/Card/Styles/Six.js +207 -115
- package/lib/module/components/Content/Card/Styles/Six.js.map +1 -1
- package/lib/module/components/Content/Card/Styles/Three.js +134 -79
- package/lib/module/components/Content/Card/Styles/Three.js.map +1 -1
- package/lib/module/components/Content/Card/Styles/TopTen.js +186 -171
- package/lib/module/components/Content/Card/Styles/TopTen.js.map +1 -1
- package/lib/module/components/Content/Card/Styles/Two.js +144 -64
- package/lib/module/components/Content/Card/Styles/Two.js.map +1 -1
- package/lib/module/components/Content/Card/components/AdsPoster.js +162 -0
- package/lib/module/components/Content/Card/components/AdsPoster.js.map +1 -0
- package/lib/module/components/Content/Card/components/CardPoster.js +120 -136
- package/lib/module/components/Content/Card/components/CardPoster.js.map +1 -1
- package/lib/module/components/Content/Card/components/index.js +4 -0
- package/lib/module/components/Content/Card/components/index.js.map +1 -0
- package/lib/module/components/Content/Content.js +67 -27
- package/lib/module/components/Content/Content.js.map +1 -1
- package/lib/module/components/Content/Sections.js +32 -11
- package/lib/module/components/Content/Sections.js.map +1 -1
- package/lib/module/constants/dummySections.js +44 -4
- package/lib/module/constants/dummySections.js.map +1 -1
- package/lib/module/hooks/Images/index.js +5 -0
- package/lib/module/hooks/Images/index.js.map +1 -0
- package/lib/module/hooks/Images/useImageLoader.js +168 -0
- package/lib/module/hooks/Images/useImageLoader.js.map +1 -0
- package/lib/module/hooks/Images/useImageValidation.js +36 -0
- package/lib/module/hooks/Images/useImageValidation.js.map +1 -0
- package/lib/module/hooks/index.js +3 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/useAdTracking.js +270 -0
- package/lib/module/hooks/useAdTracking.js.map +1 -0
- package/lib/module/hooks/useCards.js +164 -0
- package/lib/module/hooks/useCards.js.map +1 -0
- package/lib/module/hooks/usePaginatedSection.js +11 -6
- package/lib/module/hooks/usePaginatedSection.js.map +1 -1
- package/lib/typescript/src/components/Auth/QrLogin/QrLogin.d.ts +2 -0
- package/lib/typescript/src/components/Auth/QrLogin/QrLogin.d.ts.map +1 -1
- package/lib/typescript/src/components/Auth/QrLogin/components/QrViewArea.d.ts.map +1 -1
- package/lib/typescript/src/components/Content/Card/Category/Category.d.ts.map +1 -1
- package/lib/typescript/src/components/Content/Card/NowWatching/NowWatching.d.ts.map +1 -1
- package/lib/typescript/src/components/Content/Card/Sliders/Styles/One.d.ts.map +1 -1
- package/lib/typescript/src/components/Content/Card/Sliders/Styles/Two.d.ts.map +1 -1
- package/lib/typescript/src/components/Content/Card/Styles/Five.d.ts +13 -1
- package/lib/typescript/src/components/Content/Card/Styles/Five.d.ts.map +1 -1
- package/lib/typescript/src/components/Content/Card/Styles/Four.d.ts +13 -1
- package/lib/typescript/src/components/Content/Card/Styles/Four.d.ts.map +1 -1
- package/lib/typescript/src/components/Content/Card/Styles/One.d.ts +15 -3
- package/lib/typescript/src/components/Content/Card/Styles/One.d.ts.map +1 -1
- package/lib/typescript/src/components/Content/Card/Styles/RotateInOut.d.ts +13 -1
- package/lib/typescript/src/components/Content/Card/Styles/RotateInOut.d.ts.map +1 -1
- package/lib/typescript/src/components/Content/Card/Styles/Six.d.ts +1 -0
- package/lib/typescript/src/components/Content/Card/Styles/Six.d.ts.map +1 -1
- package/lib/typescript/src/components/Content/Card/Styles/Three.d.ts +13 -5
- package/lib/typescript/src/components/Content/Card/Styles/Three.d.ts.map +1 -1
- package/lib/typescript/src/components/Content/Card/Styles/TopTen.d.ts +1 -0
- package/lib/typescript/src/components/Content/Card/Styles/TopTen.d.ts.map +1 -1
- package/lib/typescript/src/components/Content/Card/Styles/Two.d.ts +13 -1
- package/lib/typescript/src/components/Content/Card/Styles/Two.d.ts.map +1 -1
- package/lib/typescript/src/components/Content/Card/components/AdsPoster.d.ts +26 -0
- package/lib/typescript/src/components/Content/Card/components/AdsPoster.d.ts.map +1 -0
- package/lib/typescript/src/components/Content/Card/components/CardPoster.d.ts +3 -1
- package/lib/typescript/src/components/Content/Card/components/CardPoster.d.ts.map +1 -1
- package/lib/typescript/src/components/Content/Card/components/index.d.ts +2 -0
- package/lib/typescript/src/components/Content/Card/components/index.d.ts.map +1 -0
- package/lib/typescript/src/components/Content/Card/index.d.ts +76 -6
- package/lib/typescript/src/components/Content/Card/index.d.ts.map +1 -1
- package/lib/typescript/src/components/Content/Content.d.ts +4 -3
- package/lib/typescript/src/components/Content/Content.d.ts.map +1 -1
- package/lib/typescript/src/components/Content/Sections.d.ts +20 -6
- package/lib/typescript/src/components/Content/Sections.d.ts.map +1 -1
- package/lib/typescript/src/constants/dummySections.d.ts +5 -0
- package/lib/typescript/src/constants/dummySections.d.ts.map +1 -1
- package/lib/typescript/src/hooks/Images/index.d.ts +3 -0
- package/lib/typescript/src/hooks/Images/index.d.ts.map +1 -0
- package/lib/typescript/src/hooks/Images/useImageLoader.d.ts +36 -0
- package/lib/typescript/src/hooks/Images/useImageLoader.d.ts.map +1 -0
- package/lib/typescript/src/hooks/Images/useImageValidation.d.ts +17 -0
- package/lib/typescript/src/hooks/Images/useImageValidation.d.ts.map +1 -0
- package/lib/typescript/src/hooks/index.d.ts +3 -0
- package/lib/typescript/src/hooks/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useAdTracking.d.ts +39 -0
- package/lib/typescript/src/hooks/useAdTracking.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useCards.d.ts +36 -0
- package/lib/typescript/src/hooks/useCards.d.ts.map +1 -0
- package/lib/typescript/src/hooks/usePaginatedSection.d.ts +12 -2
- package/lib/typescript/src/hooks/usePaginatedSection.d.ts.map +1 -1
- package/lib/typescript/src/types/sections/index.d.ts +7 -4
- package/lib/typescript/src/types/sections/index.d.ts.map +1 -1
- package/package.json +6 -3
- package/src/components/Auth/QrLogin/QrLogin.tsx +382 -122
- package/src/components/Auth/QrLogin/components/QrViewArea.tsx +291 -197
- package/src/components/Content/Card/Category/Category.tsx +95 -8
- package/src/components/Content/Card/NowWatching/NowWatching.tsx +281 -136
- package/src/components/Content/Card/Sliders/Styles/One.tsx +244 -148
- package/src/components/Content/Card/Sliders/Styles/Two.tsx +171 -102
- package/src/components/Content/Card/Styles/Five.tsx +161 -62
- package/src/components/Content/Card/Styles/Four.tsx +164 -85
- package/src/components/Content/Card/Styles/One.tsx +161 -71
- package/src/components/Content/Card/Styles/RotateInOut.tsx +157 -60
- package/src/components/Content/Card/Styles/Six.tsx +242 -142
- package/src/components/Content/Card/Styles/Three.tsx +166 -133
- package/src/components/Content/Card/Styles/TopTen.tsx +230 -191
- package/src/components/Content/Card/Styles/Two.tsx +182 -79
- package/src/components/Content/Card/components/AdsPoster.tsx +202 -0
- package/src/components/Content/Card/components/CardPoster.tsx +134 -154
- package/src/components/Content/Card/components/index.ts +1 -0
- package/src/components/Content/Content.tsx +83 -45
- package/src/components/Content/Sections.tsx +51 -10
- package/src/constants/dummySections.ts +48 -1
- package/src/hooks/Images/index.ts +2 -0
- package/src/hooks/Images/useImageLoader.ts +206 -0
- package/src/hooks/Images/useImageValidation.ts +36 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useAdTracking.ts +349 -0
- package/src/hooks/useCards.ts +228 -0
- package/src/hooks/usePaginatedSection.ts +26 -7
- package/src/types/sections/index.ts +7 -4
|
@@ -14,30 +14,46 @@ import {
|
|
|
14
14
|
type ViewStyle,
|
|
15
15
|
type AccessibilityProps,
|
|
16
16
|
type ListRenderItem,
|
|
17
|
-
type ListRenderItemInfo,
|
|
18
17
|
} from 'react-native';
|
|
18
|
+
|
|
19
19
|
import SkeletonPlaceholder from 'react-native-skeleton-placeholder';
|
|
20
20
|
import FastImage from 'react-native-fast-image';
|
|
21
21
|
import { moderateScale, verticalScale } from 'react-native-size-matters';
|
|
22
|
+
import { RFValue } from 'react-native-responsive-fontsize';
|
|
22
23
|
|
|
23
|
-
import { usePaginatedSection } from '../../../../hooks/usePaginatedSection';
|
|
24
24
|
import type {
|
|
25
|
+
IAdItem,
|
|
25
26
|
IGetSectionData,
|
|
26
27
|
ISectionContent,
|
|
27
28
|
MoreFetchData,
|
|
28
29
|
} from '../../../../types';
|
|
30
|
+
|
|
29
31
|
import type { ITheme, ThemeOverride } from '../../../../theme/themes';
|
|
30
32
|
import { useInternalTheme } from '../../../../theme/hook/useInternalTheme';
|
|
33
|
+
|
|
31
34
|
import NavigateToMore from '../components/NavigateToMore';
|
|
32
35
|
import CardPoster from '../components/CardPoster';
|
|
33
|
-
import {
|
|
34
|
-
import type { IContentData } from '@zezosoft/zezo-ott-api-client';
|
|
36
|
+
import { AdsPoster } from '../components';
|
|
37
|
+
import type { IContentData, IServeAd } from '@zezosoft/zezo-ott-api-client';
|
|
38
|
+
import { useCards } from '../../../../hooks';
|
|
35
39
|
|
|
36
|
-
//
|
|
40
|
+
// Constants
|
|
37
41
|
const DEFAULT_WIDTH = moderateScale(172);
|
|
38
42
|
const DEFAULT_BORDER_RADIUS = moderateScale(5);
|
|
39
43
|
const DEFAULT_SKELETON_COUNT = 3;
|
|
44
|
+
const END_REACHED_THRESHOLD = 0.5;
|
|
45
|
+
const ITEM_ASPECT_RATIO = 0.67;
|
|
46
|
+
const SKELETON_TITLE_WIDTH = moderateScale(100);
|
|
47
|
+
const SKELETON_TITLE_HEIGHT = verticalScale(14);
|
|
48
|
+
const SKELETON_TITLE_BORDER_RADIUS = moderateScale(4);
|
|
49
|
+
const SKELETON_ITEM_MARGIN = moderateScale(12);
|
|
50
|
+
const ITEM_MARGIN_LEFT = moderateScale(10);
|
|
51
|
+
const ITEM_MARGIN_RIGHT = moderateScale(12);
|
|
52
|
+
const ITEM_ACTIVE_OPACITY = 0.8;
|
|
53
|
+
const DEFAULT_BACKGROUND_COLOR = '#2c2c2c';
|
|
54
|
+
const DEFAULT_IMAGE_BACKGROUND_COLOR = '#222';
|
|
40
55
|
|
|
56
|
+
// Types
|
|
41
57
|
type MovieCardTwoProps = {
|
|
42
58
|
title: string;
|
|
43
59
|
section_id: string;
|
|
@@ -57,17 +73,27 @@ type MovieCardTwoProps = {
|
|
|
57
73
|
itemWidth?: number;
|
|
58
74
|
borderRadius?: number;
|
|
59
75
|
skeletonCount?: number;
|
|
76
|
+
paginationSkeletonCount?: number;
|
|
60
77
|
containerStyle?: StyleProp<ViewStyle>;
|
|
61
78
|
titleStyle?: StyleProp<TextStyle>;
|
|
62
79
|
itemStyle?: StyleProp<ViewStyle>;
|
|
63
80
|
isLoading?: boolean;
|
|
64
81
|
theme?: ThemeOverride;
|
|
82
|
+
onDisplayAds?: (ad: IServeAd) => void;
|
|
83
|
+
screenDimensions?: { width: number; height: number };
|
|
84
|
+
viewportOffsets?: {
|
|
85
|
+
top: number;
|
|
86
|
+
bottom: number;
|
|
87
|
+
left: number;
|
|
88
|
+
right: number;
|
|
89
|
+
};
|
|
65
90
|
} & AccessibilityProps;
|
|
66
91
|
|
|
92
|
+
// Main Component
|
|
67
93
|
const MovieCardTwo: React.FC<MovieCardTwoProps> = ({
|
|
68
94
|
title,
|
|
69
95
|
section_id,
|
|
70
|
-
data:
|
|
96
|
+
data: externalData,
|
|
71
97
|
moreFetchData,
|
|
72
98
|
onPressMore,
|
|
73
99
|
onPressItem,
|
|
@@ -76,6 +102,7 @@ const MovieCardTwo: React.FC<MovieCardTwoProps> = ({
|
|
|
76
102
|
itemWidth = DEFAULT_WIDTH,
|
|
77
103
|
borderRadius = DEFAULT_BORDER_RADIUS,
|
|
78
104
|
skeletonCount = DEFAULT_SKELETON_COUNT,
|
|
105
|
+
paginationSkeletonCount,
|
|
79
106
|
containerStyle,
|
|
80
107
|
titleStyle,
|
|
81
108
|
itemStyle,
|
|
@@ -83,34 +110,46 @@ const MovieCardTwo: React.FC<MovieCardTwoProps> = ({
|
|
|
83
110
|
theme,
|
|
84
111
|
accessibilityLabel,
|
|
85
112
|
accessibilityHint,
|
|
113
|
+
onDisplayAds,
|
|
114
|
+
screenDimensions,
|
|
115
|
+
viewportOffsets,
|
|
86
116
|
}) => {
|
|
87
|
-
|
|
88
|
-
|
|
117
|
+
// Refs
|
|
118
|
+
const flatListRef = useRef<FlatList<IContentData | IAdItem>>(null);
|
|
89
119
|
const onEndReachedCalledDuringMomentum = useRef(false);
|
|
120
|
+
|
|
121
|
+
// Theme
|
|
90
122
|
const { theme: appliedTheme } = useInternalTheme(theme);
|
|
91
123
|
|
|
124
|
+
// Styles
|
|
92
125
|
const styles = useMemo(
|
|
93
126
|
() => getStyles(appliedTheme, itemWidth, borderRadius),
|
|
94
127
|
[appliedTheme, itemWidth, borderRadius]
|
|
95
128
|
);
|
|
96
129
|
|
|
130
|
+
// Data Management
|
|
97
131
|
const {
|
|
98
|
-
|
|
99
|
-
pagination
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
132
|
+
listData,
|
|
133
|
+
pagination,
|
|
134
|
+
loadMore,
|
|
135
|
+
isEmpty,
|
|
136
|
+
isPaging: isPaginating,
|
|
137
|
+
} = useCards({
|
|
138
|
+
sectionId: section_id,
|
|
139
|
+
data: externalData,
|
|
140
|
+
fetchMore: moreFetchData,
|
|
141
|
+
loading: isLoading,
|
|
142
|
+
initialSkeleton: skeletonCount,
|
|
143
|
+
pagingSkeleton: paginationSkeletonCount,
|
|
144
|
+
adsRender: false,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Event Handlers
|
|
108
148
|
const handleItemPress = useCallback(
|
|
109
|
-
(item: IContentData) =>
|
|
110
|
-
onPressItem?.(item);
|
|
111
|
-
},
|
|
149
|
+
(item: IContentData) => onPressItem?.(item),
|
|
112
150
|
[onPressItem]
|
|
113
151
|
);
|
|
152
|
+
|
|
114
153
|
const handlePressMore = useCallback(() => {
|
|
115
154
|
onPressMore?.({
|
|
116
155
|
section_id,
|
|
@@ -118,39 +157,83 @@ const MovieCardTwo: React.FC<MovieCardTwoProps> = ({
|
|
|
118
157
|
type,
|
|
119
158
|
});
|
|
120
159
|
}, [onPressMore, section_id, title, type]);
|
|
160
|
+
|
|
121
161
|
const handleEndReached = useCallback(() => {
|
|
122
162
|
if (
|
|
123
163
|
!onEndReachedCalledDuringMomentum.current &&
|
|
124
|
-
|
|
125
|
-
|
|
164
|
+
pagination?.hasNextPage &&
|
|
165
|
+
pagination?.nextPage
|
|
126
166
|
) {
|
|
127
167
|
onEndReachedCalledDuringMomentum.current = true;
|
|
128
|
-
|
|
168
|
+
loadMore(pagination.nextPage);
|
|
129
169
|
}
|
|
130
|
-
}, [
|
|
170
|
+
}, [pagination, loadMore]);
|
|
171
|
+
|
|
172
|
+
const handleMomentumScrollBegin = useCallback(() => {
|
|
173
|
+
onEndReachedCalledDuringMomentum.current = false;
|
|
174
|
+
}, []);
|
|
175
|
+
|
|
176
|
+
// Render Functions
|
|
177
|
+
|
|
131
178
|
const renderItem = useCallback(
|
|
132
|
-
({ item, index }:
|
|
179
|
+
({ item, index }: { item: IContentData | IAdItem; index: number }) => {
|
|
180
|
+
// Handle ads
|
|
181
|
+
if ('type' in item && item.type === 'ads') {
|
|
182
|
+
const adItem = item as IAdItem;
|
|
183
|
+
return (
|
|
184
|
+
<View
|
|
185
|
+
key={`ad-${adItem.tracking?.impression || index}`}
|
|
186
|
+
style={[styles.item, index === 0 && styles.firstAdItem, itemStyle]}
|
|
187
|
+
>
|
|
188
|
+
<AdsPoster
|
|
189
|
+
ad={adItem}
|
|
190
|
+
theme={appliedTheme}
|
|
191
|
+
isLoading={isLoading}
|
|
192
|
+
containerStyle={{
|
|
193
|
+
width: itemWidth,
|
|
194
|
+
height: itemWidth / ITEM_ASPECT_RATIO,
|
|
195
|
+
}}
|
|
196
|
+
imageStyle={styles.image}
|
|
197
|
+
borderRadius={borderRadius}
|
|
198
|
+
onDisplayAds={onDisplayAds}
|
|
199
|
+
screenDimensions={screenDimensions}
|
|
200
|
+
viewportOffsets={viewportOffsets}
|
|
201
|
+
/>
|
|
202
|
+
</View>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Handle content items
|
|
207
|
+
const contentItem = item as IContentData;
|
|
208
|
+
const isSkeleton =
|
|
209
|
+
contentItem._id?.startsWith?.('pagination-skeleton') ?? false;
|
|
210
|
+
|
|
133
211
|
return (
|
|
134
212
|
<TouchableOpacity
|
|
135
|
-
key={
|
|
136
|
-
style={[
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
213
|
+
key={contentItem._id}
|
|
214
|
+
style={[
|
|
215
|
+
styles.item,
|
|
216
|
+
index === 0 && styles.firstContentItem,
|
|
217
|
+
itemStyle,
|
|
218
|
+
]}
|
|
219
|
+
activeOpacity={ITEM_ACTIVE_OPACITY}
|
|
220
|
+
onPress={() => {
|
|
221
|
+
if (!isSkeleton && !isLoading) {
|
|
222
|
+
handleItemPress(contentItem);
|
|
223
|
+
}
|
|
224
|
+
}}
|
|
142
225
|
>
|
|
143
226
|
{renderItemImage ? (
|
|
144
227
|
renderItemImage({ item, index })
|
|
145
228
|
) : (
|
|
146
229
|
<CardPoster
|
|
147
|
-
|
|
148
|
-
|
|
230
|
+
posterUri={contentItem.poster}
|
|
231
|
+
content_offering_type={contentItem.content_offering_type}
|
|
149
232
|
theme={appliedTheme}
|
|
150
233
|
borderRadius={borderRadius}
|
|
151
234
|
posterWrapperStyle={styles.image}
|
|
152
235
|
resizeMode={FastImage.resizeMode.cover}
|
|
153
|
-
isLoading={isLoading}
|
|
236
|
+
isLoading={isSkeleton || isLoading}
|
|
154
237
|
/>
|
|
155
238
|
)}
|
|
156
239
|
</TouchableOpacity>
|
|
@@ -158,16 +241,22 @@ const MovieCardTwo: React.FC<MovieCardTwoProps> = ({
|
|
|
158
241
|
},
|
|
159
242
|
[
|
|
160
243
|
styles.item,
|
|
161
|
-
styles.
|
|
244
|
+
styles.firstContentItem,
|
|
162
245
|
styles.image,
|
|
246
|
+
styles.firstAdItem,
|
|
163
247
|
itemStyle,
|
|
164
248
|
renderItemImage,
|
|
165
249
|
appliedTheme,
|
|
166
250
|
borderRadius,
|
|
167
251
|
isLoading,
|
|
252
|
+
itemWidth,
|
|
253
|
+
onDisplayAds,
|
|
254
|
+
screenDimensions,
|
|
255
|
+
viewportOffsets,
|
|
168
256
|
handleItemPress,
|
|
169
257
|
]
|
|
170
258
|
);
|
|
259
|
+
|
|
171
260
|
const renderSkeletonItem: ListRenderItem<null> = useCallback(
|
|
172
261
|
({ index }) => (
|
|
173
262
|
<SkeletonPlaceholder
|
|
@@ -177,24 +266,47 @@ const MovieCardTwo: React.FC<MovieCardTwoProps> = ({
|
|
|
177
266
|
>
|
|
178
267
|
<SkeletonPlaceholder.Item
|
|
179
268
|
width={itemWidth}
|
|
180
|
-
height={itemWidth /
|
|
269
|
+
height={itemWidth / ITEM_ASPECT_RATIO}
|
|
181
270
|
borderRadius={borderRadius}
|
|
182
|
-
marginRight={
|
|
183
|
-
marginLeft={index === 0 ?
|
|
271
|
+
marginRight={SKELETON_ITEM_MARGIN}
|
|
272
|
+
marginLeft={index === 0 ? SKELETON_ITEM_MARGIN : 0}
|
|
184
273
|
/>
|
|
185
274
|
</SkeletonPlaceholder>
|
|
186
275
|
),
|
|
187
276
|
[appliedTheme, itemWidth, borderRadius]
|
|
188
277
|
);
|
|
189
|
-
if (!initialData) {
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
const listData = data ?? [];
|
|
193
278
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
279
|
+
const keyExtractor = useCallback(
|
|
280
|
+
(item: IContentData | IAdItem, index: number) => {
|
|
281
|
+
return '_id' in item ? item._id : `ad-${index}`;
|
|
282
|
+
},
|
|
283
|
+
[]
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
const renderPaginationFooter = useCallback(() => {
|
|
287
|
+
if (!isPaginating) return null;
|
|
197
288
|
|
|
289
|
+
return (
|
|
290
|
+
<SkeletonPlaceholder
|
|
291
|
+
highlightColor={appliedTheme.colors.skeletonHighlightColor}
|
|
292
|
+
backgroundColor={appliedTheme.colors.skeletonBaseColor}
|
|
293
|
+
borderRadius={borderRadius}
|
|
294
|
+
>
|
|
295
|
+
<SkeletonPlaceholder.Item
|
|
296
|
+
width={itemWidth}
|
|
297
|
+
height={itemWidth / ITEM_ASPECT_RATIO}
|
|
298
|
+
borderRadius={borderRadius}
|
|
299
|
+
marginRight={SKELETON_ITEM_MARGIN}
|
|
300
|
+
/>
|
|
301
|
+
</SkeletonPlaceholder>
|
|
302
|
+
);
|
|
303
|
+
}, [isPaginating, appliedTheme, borderRadius, itemWidth]);
|
|
304
|
+
|
|
305
|
+
// Early Returns
|
|
306
|
+
if (!externalData) return null;
|
|
307
|
+
if (!isLoading && listData.length === 0 && !isEmpty) return null;
|
|
308
|
+
|
|
309
|
+
// Loading State
|
|
198
310
|
if (isLoading) {
|
|
199
311
|
return (
|
|
200
312
|
<View style={[styles.root, containerStyle]}>
|
|
@@ -203,13 +315,14 @@ const MovieCardTwo: React.FC<MovieCardTwoProps> = ({
|
|
|
203
315
|
backgroundColor={appliedTheme.colors.skeletonBaseColor}
|
|
204
316
|
>
|
|
205
317
|
<SkeletonPlaceholder.Item
|
|
206
|
-
width={
|
|
207
|
-
height={
|
|
208
|
-
borderRadius={
|
|
209
|
-
marginLeft={
|
|
318
|
+
width={SKELETON_TITLE_WIDTH}
|
|
319
|
+
height={SKELETON_TITLE_HEIGHT}
|
|
320
|
+
borderRadius={SKELETON_TITLE_BORDER_RADIUS}
|
|
321
|
+
marginLeft={SKELETON_ITEM_MARGIN}
|
|
210
322
|
marginBottom={verticalScale(6)}
|
|
211
323
|
/>
|
|
212
324
|
</SkeletonPlaceholder>
|
|
325
|
+
|
|
213
326
|
<FlatList
|
|
214
327
|
data={Array.from({ length: skeletonCount }, () => null)}
|
|
215
328
|
horizontal
|
|
@@ -222,6 +335,7 @@ const MovieCardTwo: React.FC<MovieCardTwoProps> = ({
|
|
|
222
335
|
);
|
|
223
336
|
}
|
|
224
337
|
|
|
338
|
+
// Main Render
|
|
225
339
|
return (
|
|
226
340
|
<View
|
|
227
341
|
style={[styles.root, containerStyle]}
|
|
@@ -240,61 +354,50 @@ const MovieCardTwo: React.FC<MovieCardTwoProps> = ({
|
|
|
240
354
|
]}
|
|
241
355
|
showAllProps={{ iconColor: appliedTheme.colors.textPrimary, theme }}
|
|
242
356
|
/>
|
|
357
|
+
|
|
243
358
|
<FlatList
|
|
244
359
|
ref={flatListRef}
|
|
245
360
|
data={listData}
|
|
246
361
|
horizontal
|
|
247
362
|
showsHorizontalScrollIndicator={false}
|
|
248
|
-
keyExtractor={
|
|
363
|
+
keyExtractor={keyExtractor}
|
|
249
364
|
renderItem={renderItem}
|
|
250
365
|
contentContainerStyle={styles.listContent}
|
|
251
366
|
onEndReached={handleEndReached}
|
|
252
|
-
onEndReachedThreshold={
|
|
253
|
-
onMomentumScrollBegin={
|
|
254
|
-
|
|
255
|
-
}}
|
|
256
|
-
ListFooterComponent={
|
|
257
|
-
isPaginating ? (
|
|
258
|
-
<SkeletonPlaceholder
|
|
259
|
-
highlightColor={appliedTheme.colors.skeletonHighlightColor}
|
|
260
|
-
backgroundColor={appliedTheme.colors.skeletonBaseColor}
|
|
261
|
-
borderRadius={borderRadius}
|
|
262
|
-
>
|
|
263
|
-
<SkeletonPlaceholder.Item
|
|
264
|
-
width={itemWidth}
|
|
265
|
-
height={itemWidth / 0.67}
|
|
266
|
-
borderRadius={borderRadius}
|
|
267
|
-
marginRight={moderateScale(12)}
|
|
268
|
-
/>
|
|
269
|
-
</SkeletonPlaceholder>
|
|
270
|
-
) : null
|
|
271
|
-
}
|
|
367
|
+
onEndReachedThreshold={END_REACHED_THRESHOLD}
|
|
368
|
+
onMomentumScrollBegin={handleMomentumScrollBegin}
|
|
369
|
+
ListFooterComponent={renderPaginationFooter()}
|
|
272
370
|
/>
|
|
273
371
|
</View>
|
|
274
372
|
);
|
|
275
373
|
};
|
|
276
374
|
|
|
375
|
+
// Styles
|
|
277
376
|
const getStyles = (theme: ITheme, width: number, radius: number) =>
|
|
278
377
|
StyleSheet.create({
|
|
279
378
|
root: {
|
|
280
379
|
marginVertical: verticalScale(6),
|
|
281
380
|
},
|
|
282
381
|
item: {
|
|
283
|
-
width
|
|
284
|
-
height: width /
|
|
285
|
-
marginRight:
|
|
382
|
+
width,
|
|
383
|
+
height: width / ITEM_ASPECT_RATIO,
|
|
384
|
+
marginRight: ITEM_MARGIN_RIGHT,
|
|
286
385
|
borderRadius: radius,
|
|
287
386
|
overflow: 'hidden',
|
|
288
|
-
backgroundColor: theme.colors.background ??
|
|
387
|
+
backgroundColor: theme.colors.background ?? DEFAULT_BACKGROUND_COLOR,
|
|
388
|
+
},
|
|
389
|
+
firstContentItem: {
|
|
390
|
+
marginLeft: ITEM_MARGIN_LEFT,
|
|
289
391
|
},
|
|
290
|
-
|
|
291
|
-
marginLeft:
|
|
392
|
+
firstAdItem: {
|
|
393
|
+
marginLeft: ITEM_MARGIN_LEFT,
|
|
292
394
|
},
|
|
293
395
|
image: {
|
|
294
396
|
width: '100%',
|
|
295
397
|
height: '100%',
|
|
296
398
|
borderRadius: radius,
|
|
297
|
-
backgroundColor:
|
|
399
|
+
backgroundColor:
|
|
400
|
+
theme.colors.background ?? DEFAULT_IMAGE_BACKGROUND_COLOR,
|
|
298
401
|
},
|
|
299
402
|
navigateToMoreContainer: {
|
|
300
403
|
paddingHorizontal: moderateScale(10),
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { memo, useMemo } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
TouchableOpacity,
|
|
4
|
+
View,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
type StyleProp,
|
|
7
|
+
type ViewStyle,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
import SkeletonPlaceholder from 'react-native-skeleton-placeholder';
|
|
10
|
+
import { scale } from 'react-native-size-matters';
|
|
11
|
+
import type { IServeAd } from '@zezosoft/zezo-ott-api-client';
|
|
12
|
+
import CardPoster from './CardPoster';
|
|
13
|
+
import { Text } from '../../../Text';
|
|
14
|
+
import type { ITheme } from '../../../../theme';
|
|
15
|
+
import { useAdTracking } from '../../../../hooks';
|
|
16
|
+
|
|
17
|
+
type Props = {
|
|
18
|
+
ad: IServeAd;
|
|
19
|
+
theme: ITheme;
|
|
20
|
+
isLoading?: boolean;
|
|
21
|
+
containerStyle?: StyleProp<ViewStyle>;
|
|
22
|
+
imageStyle?: StyleProp<ViewStyle>;
|
|
23
|
+
onPress?: (item: IServeAd) => void;
|
|
24
|
+
borderRadius?: number;
|
|
25
|
+
onDisplayAds?: (ad: IServeAd) => void;
|
|
26
|
+
screenDimensions?: { width: number; height: number };
|
|
27
|
+
viewportOffsets?: {
|
|
28
|
+
top: number;
|
|
29
|
+
bottom: number;
|
|
30
|
+
left: number;
|
|
31
|
+
right: number;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Constants
|
|
36
|
+
const DEFAULT_BORDER_RADIUS = scale(6);
|
|
37
|
+
const DEFAULT_AD_COLOR = '#CA091E';
|
|
38
|
+
const TOUCHABLE_OPACITY = 0.85;
|
|
39
|
+
|
|
40
|
+
// Badge styling constants
|
|
41
|
+
const BADGE_POSITION = scale(4);
|
|
42
|
+
const BADGE_MIN_WIDTH = scale(28);
|
|
43
|
+
const BADGE_HEIGHT = scale(19);
|
|
44
|
+
const BADGE_PADDING_HORIZONTAL = scale(8);
|
|
45
|
+
const BADGE_BORDER_RADIUS = scale(12);
|
|
46
|
+
const BADGE_FONT_SIZE = scale(9);
|
|
47
|
+
const BADGE_LETTER_SPACING = 1.2;
|
|
48
|
+
const BADGE_LINE_HEIGHT = scale(12);
|
|
49
|
+
|
|
50
|
+
// Badge background with semi-transparent white for better visibility
|
|
51
|
+
const BADGE_BACKGROUND = 'rgba(255, 255, 255, 0.95)';
|
|
52
|
+
const BADGE_BORDER_WIDTH = scale(0.5);
|
|
53
|
+
const BADGE_BORDER_COLOR = 'rgba(0, 0, 0, 0.08)';
|
|
54
|
+
|
|
55
|
+
// Enhanced shadow for better depth and visibility
|
|
56
|
+
const BADGE_SHADOW = {
|
|
57
|
+
shadowColor: '#000',
|
|
58
|
+
shadowOffset: { width: 0, height: scale(2) },
|
|
59
|
+
shadowOpacity: 0.25,
|
|
60
|
+
shadowRadius: scale(4),
|
|
61
|
+
elevation: 5,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const AdsPoster = memo(
|
|
65
|
+
({
|
|
66
|
+
ad,
|
|
67
|
+
theme,
|
|
68
|
+
isLoading = false,
|
|
69
|
+
containerStyle,
|
|
70
|
+
imageStyle,
|
|
71
|
+
borderRadius = DEFAULT_BORDER_RADIUS,
|
|
72
|
+
onPress,
|
|
73
|
+
onDisplayAds,
|
|
74
|
+
screenDimensions: propScreenDimensions,
|
|
75
|
+
viewportOffsets: propViewportOffsets,
|
|
76
|
+
}: Props) => {
|
|
77
|
+
const { colors } = theme;
|
|
78
|
+
|
|
79
|
+
const { viewRef, handleLayout } = useAdTracking({
|
|
80
|
+
ad,
|
|
81
|
+
onDisplayAds,
|
|
82
|
+
isLoading,
|
|
83
|
+
screenDimensions: propScreenDimensions,
|
|
84
|
+
viewportOffsets: propViewportOffsets,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Memoize badge styles with dynamic positioning
|
|
88
|
+
const badgeStyle = useMemo(
|
|
89
|
+
() => [
|
|
90
|
+
styles.badge,
|
|
91
|
+
{
|
|
92
|
+
top: BADGE_POSITION,
|
|
93
|
+
left: BADGE_POSITION,
|
|
94
|
+
minWidth: BADGE_MIN_WIDTH,
|
|
95
|
+
height: BADGE_HEIGHT,
|
|
96
|
+
paddingHorizontal: BADGE_PADDING_HORIZONTAL,
|
|
97
|
+
borderRadius: BADGE_BORDER_RADIUS,
|
|
98
|
+
},
|
|
99
|
+
isLoading && styles.badgeSkeleton,
|
|
100
|
+
],
|
|
101
|
+
[isLoading]
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Memoize badge text style with better typography
|
|
105
|
+
const badgeTextStyle = useMemo(
|
|
106
|
+
() => [
|
|
107
|
+
styles.badgeText,
|
|
108
|
+
{
|
|
109
|
+
color: colors.premiumIcon || DEFAULT_AD_COLOR,
|
|
110
|
+
lineHeight: BADGE_LINE_HEIGHT,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
[colors.premiumIcon]
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// Memoize skeleton item style matching badge dimensions
|
|
117
|
+
const skeletonItemStyle = useMemo(
|
|
118
|
+
() => ({
|
|
119
|
+
width: BADGE_MIN_WIDTH,
|
|
120
|
+
height: BADGE_HEIGHT,
|
|
121
|
+
borderRadius: BADGE_BORDER_RADIUS,
|
|
122
|
+
}),
|
|
123
|
+
[]
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Early return if no media URL
|
|
127
|
+
if (!ad?.mediaUrl) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<TouchableOpacity
|
|
133
|
+
ref={viewRef}
|
|
134
|
+
activeOpacity={TOUCHABLE_OPACITY}
|
|
135
|
+
disabled={isLoading}
|
|
136
|
+
onPress={() => onPress?.(ad)}
|
|
137
|
+
style={[styles.wrapper, containerStyle]}
|
|
138
|
+
accessibilityRole="button"
|
|
139
|
+
accessibilityLabel="Advertisement"
|
|
140
|
+
onLayout={handleLayout}
|
|
141
|
+
>
|
|
142
|
+
<View style={[styles.posterWrapper, { borderRadius }]}>
|
|
143
|
+
<CardPoster
|
|
144
|
+
posterUri={ad.mediaUrl}
|
|
145
|
+
theme={theme}
|
|
146
|
+
isLoading={isLoading}
|
|
147
|
+
posterWrapperStyle={imageStyle}
|
|
148
|
+
borderRadius={borderRadius}
|
|
149
|
+
/>
|
|
150
|
+
|
|
151
|
+
{/* AD Badge */}
|
|
152
|
+
<View style={badgeStyle}>
|
|
153
|
+
{isLoading ? (
|
|
154
|
+
<SkeletonPlaceholder
|
|
155
|
+
backgroundColor={colors.skeletonBaseColor}
|
|
156
|
+
highlightColor={colors.skeletonHighlightColor}
|
|
157
|
+
>
|
|
158
|
+
<SkeletonPlaceholder.Item style={skeletonItemStyle} />
|
|
159
|
+
</SkeletonPlaceholder>
|
|
160
|
+
) : (
|
|
161
|
+
<Text type="caption" weight="700" style={badgeTextStyle}>
|
|
162
|
+
AD
|
|
163
|
+
</Text>
|
|
164
|
+
)}
|
|
165
|
+
</View>
|
|
166
|
+
</View>
|
|
167
|
+
</TouchableOpacity>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
AdsPoster.displayName = 'AdsPoster';
|
|
173
|
+
|
|
174
|
+
const styles = StyleSheet.create({
|
|
175
|
+
wrapper: {
|
|
176
|
+
position: 'relative',
|
|
177
|
+
},
|
|
178
|
+
posterWrapper: {
|
|
179
|
+
position: 'relative',
|
|
180
|
+
},
|
|
181
|
+
badge: {
|
|
182
|
+
position: 'absolute',
|
|
183
|
+
alignItems: 'center',
|
|
184
|
+
justifyContent: 'center',
|
|
185
|
+
zIndex: 10,
|
|
186
|
+
backgroundColor: BADGE_BACKGROUND,
|
|
187
|
+
borderWidth: BADGE_BORDER_WIDTH,
|
|
188
|
+
borderColor: BADGE_BORDER_COLOR,
|
|
189
|
+
...BADGE_SHADOW,
|
|
190
|
+
},
|
|
191
|
+
badgeSkeleton: {
|
|
192
|
+
backgroundColor: 'transparent',
|
|
193
|
+
borderWidth: 0,
|
|
194
|
+
shadowOpacity: 0,
|
|
195
|
+
elevation: 0,
|
|
196
|
+
},
|
|
197
|
+
badgeText: {
|
|
198
|
+
fontSize: BADGE_FONT_SIZE,
|
|
199
|
+
letterSpacing: BADGE_LETTER_SPACING,
|
|
200
|
+
fontWeight: '700',
|
|
201
|
+
},
|
|
202
|
+
});
|