@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
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @author Naresh Dhamu
|
|
3
|
-
* @lastModified Sun 08 Jun 2025
|
|
3
|
+
* @lastModified Sun 08 Jun 2025
|
|
4
4
|
*/
|
|
5
5
|
import React, { useMemo } from 'react';
|
|
6
6
|
import Sliders from './Card/Sliders';
|
|
7
7
|
import Cards from './Card';
|
|
8
|
-
import type { IGetSectionData, MoreFetchData } from '../../types';
|
|
8
|
+
import type { IGetSectionData, MoreFetchData, IAdItem } from '../../types';
|
|
9
9
|
import type { IHistoryItem } from './Card/NowWatching/NowWatching';
|
|
10
10
|
import type { ICategory } from './Card/Category/Category';
|
|
11
11
|
import type { ThemeOverride } from '../../theme/themes';
|
|
12
12
|
import type { IContentLoading } from './Content';
|
|
13
|
-
import type { IContentData } from '@zezosoft/zezo-ott-api-client';
|
|
13
|
+
import type { IContentData, IServeAd } from '@zezosoft/zezo-ott-api-client';
|
|
14
14
|
|
|
15
15
|
export interface ICustomComponentsForSections {
|
|
16
16
|
type: string;
|
|
@@ -19,6 +19,7 @@ export interface ICustomComponentsForSections {
|
|
|
19
19
|
|
|
20
20
|
export interface IProps {
|
|
21
21
|
type: IGetSectionData['type'];
|
|
22
|
+
ads?: IServeAd[];
|
|
22
23
|
data: {
|
|
23
24
|
sectionData: IGetSectionData['content'] | null;
|
|
24
25
|
historyData?: IHistoryItem[];
|
|
@@ -30,13 +31,15 @@ export interface IProps {
|
|
|
30
31
|
index: number;
|
|
31
32
|
section_id: string;
|
|
32
33
|
events?: {
|
|
33
|
-
onPressItem?: (item: IContentData) => void;
|
|
34
|
+
onPressItem?: (item: IContentData | IAdItem) => void;
|
|
34
35
|
onPressMore?: (params: {
|
|
35
|
-
section_id:
|
|
36
|
-
name:
|
|
36
|
+
section_id: string;
|
|
37
|
+
name: string;
|
|
37
38
|
type: IGetSectionData['type'];
|
|
38
39
|
}) => void;
|
|
39
40
|
onPressCategory?: (category: ICategory) => void;
|
|
41
|
+
onAdVisible?: (ad: IServeAd, percent: number) => void;
|
|
42
|
+
onDisplayAds?: (ad: IServeAd) => void;
|
|
40
43
|
};
|
|
41
44
|
sectionProps?: {
|
|
42
45
|
showPlayBtn?: boolean;
|
|
@@ -44,15 +47,32 @@ export interface IProps {
|
|
|
44
47
|
isLoading?: IContentLoading;
|
|
45
48
|
moreFetchData?: MoreFetchData<IContentData>;
|
|
46
49
|
moreFetchDataHistory?: MoreFetchData<IHistoryItem>;
|
|
50
|
+
onDisplayAds?: (ad: IServeAd) => void;
|
|
51
|
+
screenDimensions?: { width: number; height: number };
|
|
52
|
+
viewportOffsets?: {
|
|
53
|
+
top: number;
|
|
54
|
+
bottom: number;
|
|
55
|
+
left: number;
|
|
56
|
+
right: number;
|
|
57
|
+
};
|
|
47
58
|
}
|
|
48
|
-
|
|
49
|
-
// Memoized common props creator to prevent object recreation
|
|
50
59
|
const createCommonProps = (
|
|
51
60
|
type: IGetSectionData['type'],
|
|
52
61
|
props: Omit<IProps, 'customComponents' | 'type'>
|
|
53
62
|
) => {
|
|
54
63
|
const effectiveIsLoading =
|
|
55
64
|
props.isLoading?.section || props.section_id === `dummy-${type}`;
|
|
65
|
+
|
|
66
|
+
const onPressMoreHandler = props.events?.onPressMore
|
|
67
|
+
? (params: { section_id: string; name: string; type: string }) => {
|
|
68
|
+
props.events?.onPressMore?.({
|
|
69
|
+
section_id: params.section_id,
|
|
70
|
+
name: params.name,
|
|
71
|
+
type,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
: undefined;
|
|
75
|
+
|
|
56
76
|
return {
|
|
57
77
|
theme: props.theme,
|
|
58
78
|
title: props.name,
|
|
@@ -61,10 +81,14 @@ const createCommonProps = (
|
|
|
61
81
|
data: props.data?.sectionData || null,
|
|
62
82
|
index: props.index,
|
|
63
83
|
onPressItem: props.events?.onPressItem,
|
|
64
|
-
onPressMore:
|
|
84
|
+
onPressMore: onPressMoreHandler,
|
|
85
|
+
onAdVisible: props.events?.onAdVisible,
|
|
65
86
|
isLoading: effectiveIsLoading,
|
|
66
87
|
moreFetchDataHistory: props.moreFetchDataHistory,
|
|
67
88
|
moreFetchData: props.moreFetchData,
|
|
89
|
+
onDisplayAds: props.onDisplayAds || props.events?.onDisplayAds,
|
|
90
|
+
screenDimensions: props.screenDimensions,
|
|
91
|
+
viewportOffsets: props.viewportOffsets,
|
|
68
92
|
};
|
|
69
93
|
};
|
|
70
94
|
|
|
@@ -114,6 +138,9 @@ const renderDefaultComponent = (
|
|
|
114
138
|
}
|
|
115
139
|
};
|
|
116
140
|
|
|
141
|
+
// ----------------------------------------
|
|
142
|
+
// Sections Component
|
|
143
|
+
// ----------------------------------------
|
|
117
144
|
const Sections: React.FC<IProps> = ({
|
|
118
145
|
type,
|
|
119
146
|
data,
|
|
@@ -127,6 +154,9 @@ const Sections: React.FC<IProps> = ({
|
|
|
127
154
|
isLoading,
|
|
128
155
|
moreFetchData,
|
|
129
156
|
moreFetchDataHistory,
|
|
157
|
+
onDisplayAds,
|
|
158
|
+
screenDimensions,
|
|
159
|
+
viewportOffsets,
|
|
130
160
|
}) => {
|
|
131
161
|
const commonProps = useMemo(
|
|
132
162
|
() => ({ data: data.sectionData, title: name }),
|
|
@@ -151,6 +181,9 @@ const Sections: React.FC<IProps> = ({
|
|
|
151
181
|
isLoading,
|
|
152
182
|
moreFetchData,
|
|
153
183
|
moreFetchDataHistory,
|
|
184
|
+
onDisplayAds,
|
|
185
|
+
screenDimensions,
|
|
186
|
+
viewportOffsets,
|
|
154
187
|
}),
|
|
155
188
|
[
|
|
156
189
|
type,
|
|
@@ -164,10 +197,12 @@ const Sections: React.FC<IProps> = ({
|
|
|
164
197
|
isLoading,
|
|
165
198
|
moreFetchData,
|
|
166
199
|
moreFetchDataHistory,
|
|
200
|
+
onDisplayAds,
|
|
201
|
+
screenDimensions,
|
|
202
|
+
viewportOffsets,
|
|
167
203
|
]
|
|
168
204
|
);
|
|
169
205
|
|
|
170
|
-
// Memoize props object to avoid recreating in filter
|
|
171
206
|
const filterProps = useMemo(
|
|
172
207
|
() => ({
|
|
173
208
|
data,
|
|
@@ -180,6 +215,9 @@ const Sections: React.FC<IProps> = ({
|
|
|
180
215
|
isLoading,
|
|
181
216
|
moreFetchData,
|
|
182
217
|
moreFetchDataHistory,
|
|
218
|
+
onDisplayAds,
|
|
219
|
+
screenDimensions,
|
|
220
|
+
viewportOffsets,
|
|
183
221
|
}),
|
|
184
222
|
[
|
|
185
223
|
data,
|
|
@@ -192,6 +230,9 @@ const Sections: React.FC<IProps> = ({
|
|
|
192
230
|
isLoading,
|
|
193
231
|
moreFetchData,
|
|
194
232
|
moreFetchDataHistory,
|
|
233
|
+
onDisplayAds,
|
|
234
|
+
screenDimensions,
|
|
235
|
+
viewportOffsets,
|
|
195
236
|
]
|
|
196
237
|
);
|
|
197
238
|
|
|
@@ -1,5 +1,50 @@
|
|
|
1
|
+
import type { IContentData } from '@zezosoft/zezo-ott-api-client';
|
|
1
2
|
import type { IGetSectionData } from '../types';
|
|
2
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Creates a dummy content data object for skeleton loading states
|
|
6
|
+
*/
|
|
7
|
+
export const dummyContentData = (
|
|
8
|
+
id: string = 'dummy-content-id'
|
|
9
|
+
): IContentData => {
|
|
10
|
+
return {
|
|
11
|
+
_id: id,
|
|
12
|
+
poster: '',
|
|
13
|
+
thumbnail: '',
|
|
14
|
+
content_offering_type: 'movie',
|
|
15
|
+
name: '',
|
|
16
|
+
slug: '',
|
|
17
|
+
u_age: '',
|
|
18
|
+
description: '',
|
|
19
|
+
release_date: '',
|
|
20
|
+
runtime: '',
|
|
21
|
+
duration: '',
|
|
22
|
+
genre: '',
|
|
23
|
+
director: '',
|
|
24
|
+
cast: [],
|
|
25
|
+
rating: '',
|
|
26
|
+
language: '',
|
|
27
|
+
subtitles: [],
|
|
28
|
+
audio_tracks: [],
|
|
29
|
+
type: 'movie',
|
|
30
|
+
year: '',
|
|
31
|
+
tags: [],
|
|
32
|
+
trailer: '',
|
|
33
|
+
trailer_source_link: '',
|
|
34
|
+
banner: '',
|
|
35
|
+
logo: '',
|
|
36
|
+
source_link: '',
|
|
37
|
+
source_type: '',
|
|
38
|
+
is_featured: false,
|
|
39
|
+
is_premium: false,
|
|
40
|
+
status: 'active',
|
|
41
|
+
views: 0,
|
|
42
|
+
likes: 0,
|
|
43
|
+
created_at: new Date().toISOString(),
|
|
44
|
+
updated_at: new Date().toISOString(),
|
|
45
|
+
} as unknown as IContentData;
|
|
46
|
+
};
|
|
47
|
+
|
|
3
48
|
const generateDummySection = (
|
|
4
49
|
type: IGetSectionData['type']
|
|
5
50
|
): IGetSectionData => {
|
|
@@ -14,7 +59,9 @@ const generateDummySection = (
|
|
|
14
59
|
slug: 'dummy',
|
|
15
60
|
},
|
|
16
61
|
content: {
|
|
17
|
-
data: Array
|
|
62
|
+
data: Array.from({ length: 5 }, (_, i) =>
|
|
63
|
+
dummyContentData(`dummy-content-${i}`)
|
|
64
|
+
),
|
|
18
65
|
meta: {
|
|
19
66
|
pagination: {
|
|
20
67
|
total: 5,
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author Naresh Dhamu
|
|
3
|
+
* @lastModified Wed 26 Nov 2025 at 02:19 PM
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
|
7
|
+
import FastImage from 'react-native-fast-image';
|
|
8
|
+
import { isValidImageUrl, isLocalFile } from './useImageValidation';
|
|
9
|
+
|
|
10
|
+
// Module-level cache to track successfully loaded images
|
|
11
|
+
const loadedImageCache = new Set<string>();
|
|
12
|
+
|
|
13
|
+
export type UseImageLoaderOptions = {
|
|
14
|
+
imageUri: string;
|
|
15
|
+
isLoading?: boolean;
|
|
16
|
+
enablePreload?: boolean;
|
|
17
|
+
onError?: () => void;
|
|
18
|
+
onLoad?: () => void;
|
|
19
|
+
onLoadStart?: () => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type UseImageLoaderReturn = {
|
|
23
|
+
imageLoading: boolean;
|
|
24
|
+
imageError: boolean;
|
|
25
|
+
hasValidImage: boolean;
|
|
26
|
+
isImageCached: boolean;
|
|
27
|
+
showSkeleton: boolean;
|
|
28
|
+
showFallback: boolean;
|
|
29
|
+
imageSource: {
|
|
30
|
+
uri: string;
|
|
31
|
+
cache?: typeof FastImage.cacheControl.immutable;
|
|
32
|
+
priority?: typeof FastImage.priority.normal;
|
|
33
|
+
};
|
|
34
|
+
handleLoad: () => void;
|
|
35
|
+
handleError: () => void;
|
|
36
|
+
handleLoadStart?: () => void;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Custom hook to manage image loading state, caching, and validation
|
|
41
|
+
* @param options - Configuration options for image loading
|
|
42
|
+
* @returns Object containing image state and handlers
|
|
43
|
+
*/
|
|
44
|
+
export const useImageLoader = ({
|
|
45
|
+
imageUri,
|
|
46
|
+
isLoading = false,
|
|
47
|
+
enablePreload = false,
|
|
48
|
+
onError,
|
|
49
|
+
onLoad,
|
|
50
|
+
onLoadStart,
|
|
51
|
+
}: UseImageLoaderOptions): UseImageLoaderReturn => {
|
|
52
|
+
const [imageLoading, setImageLoading] = useState(true);
|
|
53
|
+
const [imageError, setImageError] = useState(false);
|
|
54
|
+
const previousUriRef = useRef<string>('');
|
|
55
|
+
const onErrorRef = useRef(onError);
|
|
56
|
+
|
|
57
|
+
// Keep onError ref updated
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
onErrorRef.current = onError;
|
|
60
|
+
}, [onError]);
|
|
61
|
+
|
|
62
|
+
// Validate image URL
|
|
63
|
+
const hasValidImage = useMemo(() => isValidImageUrl(imageUri), [imageUri]);
|
|
64
|
+
|
|
65
|
+
// Check if image is already cached
|
|
66
|
+
const isImageCached = useMemo(
|
|
67
|
+
() => loadedImageCache.has(imageUri),
|
|
68
|
+
[imageUri]
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Handle image state changes
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const uriChanged = previousUriRef.current !== imageUri;
|
|
74
|
+
|
|
75
|
+
// Update ref when URI changes
|
|
76
|
+
if (uriChanged) {
|
|
77
|
+
previousUriRef.current = imageUri;
|
|
78
|
+
// Reset error state when URI changes to allow new image to load
|
|
79
|
+
setImageError(false);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Reset states when loading
|
|
83
|
+
if (isLoading) {
|
|
84
|
+
setImageLoading(true);
|
|
85
|
+
setImageError(false);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Handle valid image URL
|
|
90
|
+
if (hasValidImage) {
|
|
91
|
+
const isLocal = isLocalFile(imageUri);
|
|
92
|
+
|
|
93
|
+
if (isImageCached) {
|
|
94
|
+
// Image is cached, mark as loaded
|
|
95
|
+
setImageLoading(false);
|
|
96
|
+
setImageError(false);
|
|
97
|
+
} else {
|
|
98
|
+
// Image not cached, start loading
|
|
99
|
+
setImageLoading(true);
|
|
100
|
+
setImageError(false);
|
|
101
|
+
// Preload image if enabled (only for remote URLs, not local files)
|
|
102
|
+
if (enablePreload && !isLocal && uriChanged) {
|
|
103
|
+
FastImage.preload([{ uri: imageUri }]);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
// For empty or invalid URLs
|
|
108
|
+
if (!imageUri || imageUri.trim() === '') {
|
|
109
|
+
// Empty URI - show error immediately (no point in trying to load)
|
|
110
|
+
setImageLoading(false);
|
|
111
|
+
setImageError(true);
|
|
112
|
+
} else {
|
|
113
|
+
// Invalid URL format - attempt to load anyway
|
|
114
|
+
// FastImage might still be able to load it, so keep loading state
|
|
115
|
+
setImageLoading(true);
|
|
116
|
+
setImageError(false);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}, [imageUri, hasValidImage, isLoading, isImageCached, enablePreload]);
|
|
120
|
+
|
|
121
|
+
// Determine if skeleton should be shown
|
|
122
|
+
const showSkeleton = useMemo(
|
|
123
|
+
() => isLoading || (imageLoading && !imageError),
|
|
124
|
+
[isLoading, imageLoading, imageError]
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// Determine if fallback should be shown
|
|
128
|
+
// Show fallback when:
|
|
129
|
+
// 1. Not showing skeleton
|
|
130
|
+
// 2. AND (imageError is true OR URI is empty/invalid)
|
|
131
|
+
// This allows images with invalid format to attempt loading, but shows fallback for empty URIs
|
|
132
|
+
const showFallback = useMemo(() => {
|
|
133
|
+
if (showSkeleton) return false;
|
|
134
|
+
// Empty URI - show fallback immediately
|
|
135
|
+
if (!imageUri || imageUri.trim() === '') return true;
|
|
136
|
+
// Show fallback only after actual error (not just invalid format)
|
|
137
|
+
return imageError && !imageLoading;
|
|
138
|
+
}, [showSkeleton, imageError, imageLoading, imageUri]);
|
|
139
|
+
|
|
140
|
+
// Check if image is a local file
|
|
141
|
+
const isLocal = useMemo(() => isLocalFile(imageUri), [imageUri]);
|
|
142
|
+
|
|
143
|
+
// Image source configuration
|
|
144
|
+
// Local files don't need cache control, cloud URIs do
|
|
145
|
+
const imageSource = useMemo(() => {
|
|
146
|
+
const source: {
|
|
147
|
+
uri: string;
|
|
148
|
+
cache?: typeof FastImage.cacheControl.immutable;
|
|
149
|
+
priority?: typeof FastImage.priority.normal;
|
|
150
|
+
} = {
|
|
151
|
+
uri: imageUri,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Only add cache control for remote URLs, not local files
|
|
155
|
+
if (!isLocal) {
|
|
156
|
+
source.cache = FastImage.cacheControl.immutable;
|
|
157
|
+
source.priority = FastImage.priority.normal;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return source;
|
|
161
|
+
}, [imageUri, isLocal]);
|
|
162
|
+
|
|
163
|
+
// Image event handlers
|
|
164
|
+
const handleLoad = useCallback(() => {
|
|
165
|
+
setImageLoading(false);
|
|
166
|
+
setImageError(false);
|
|
167
|
+
if (imageUri) {
|
|
168
|
+
loadedImageCache.add(imageUri);
|
|
169
|
+
}
|
|
170
|
+
if (onLoad) {
|
|
171
|
+
onLoad();
|
|
172
|
+
}
|
|
173
|
+
}, [imageUri, onLoad]);
|
|
174
|
+
|
|
175
|
+
const handleError = useCallback(() => {
|
|
176
|
+
setImageLoading(false);
|
|
177
|
+
|
|
178
|
+
setImageError(true);
|
|
179
|
+
if (onError) {
|
|
180
|
+
onError();
|
|
181
|
+
}
|
|
182
|
+
}, [onError]);
|
|
183
|
+
|
|
184
|
+
const handleLoadStart = useCallback(() => {
|
|
185
|
+
if (!isImageCached) {
|
|
186
|
+
setImageLoading(true);
|
|
187
|
+
setImageError(false);
|
|
188
|
+
if (onLoadStart) {
|
|
189
|
+
onLoadStart();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}, [isImageCached, onLoadStart]);
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
imageLoading,
|
|
196
|
+
imageError,
|
|
197
|
+
hasValidImage,
|
|
198
|
+
isImageCached,
|
|
199
|
+
showSkeleton,
|
|
200
|
+
showFallback,
|
|
201
|
+
imageSource,
|
|
202
|
+
handleLoad,
|
|
203
|
+
handleError,
|
|
204
|
+
handleLoadStart: enablePreload ? handleLoadStart : undefined,
|
|
205
|
+
};
|
|
206
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author Naresh Dhamu
|
|
3
|
+
* @lastModified Wed 26 Nov 2025 at 02:19 PM
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const IMAGE_URL_REGEX = /\.(jpg|jpeg|png|webp|gif|bmp)$/i;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Utility function to check if URL is a local file path
|
|
10
|
+
* @param url - The URL to check
|
|
11
|
+
* @returns true if the URL is a local file path, false otherwise
|
|
12
|
+
*/
|
|
13
|
+
export const isLocalFile = (url: string): boolean => {
|
|
14
|
+
return url?.startsWith?.('file://') || url?.startsWith?.('/');
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Utility function to validate image URL (supports both cloud URIs and local file paths)
|
|
19
|
+
* @param url - The URL to validate
|
|
20
|
+
* @returns true if the URL is a valid image URL, false otherwise
|
|
21
|
+
*/
|
|
22
|
+
export const isValidImageUrl = (url: string | null | undefined): boolean => {
|
|
23
|
+
if (!url || typeof url !== 'string') return false;
|
|
24
|
+
const cleaned = url.trim();
|
|
25
|
+
|
|
26
|
+
// Check for local file paths
|
|
27
|
+
if (isLocalFile(cleaned)) {
|
|
28
|
+
return IMAGE_URL_REGEX.test(cleaned) || cleaned.length > 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check for cloud URIs (http/https)
|
|
32
|
+
return (
|
|
33
|
+
(cleaned?.startsWith?.('http://') || cleaned?.startsWith?.('https://')) &&
|
|
34
|
+
IMAGE_URL_REGEX.test(cleaned)
|
|
35
|
+
);
|
|
36
|
+
};
|
package/src/hooks/index.ts
CHANGED