@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,16 +1,16 @@
1
1
  /**
2
2
  * @author Naresh Dhamu
3
- * @lastModified Sun 08 Jun 2025 at 03:50 PM
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: IGetSectionData['_id'];
36
- name: IGetSectionData['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: props.events?.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(5).fill({ id: '', order: 0 }),
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,2 @@
1
+ export * from './useImageLoader';
2
+ export * from './useImageValidation';
@@ -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
+ };
@@ -12,3 +12,6 @@ export * from './usePrevious';
12
12
  export * from './useSafeCallback';
13
13
  export * from './useSkeletonItems';
14
14
  export * from './useThemeColors';
15
+ export * from './useCards';
16
+ export * from './Images';
17
+ export * from './useAdTracking';