@messenger-box/platform-mobile 10.0.3-alpha.34 → 10.0.3-alpha.37

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 (34) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/lib/screens/inbox/components/CachedImage/index.js +125 -93
  3. package/lib/screens/inbox/components/CachedImage/index.js.map +1 -1
  4. package/lib/screens/inbox/components/DialogsListItem.js +80 -256
  5. package/lib/screens/inbox/components/DialogsListItem.js.map +1 -1
  6. package/lib/screens/inbox/components/ServiceDialogsListItem.js +222 -324
  7. package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +1 -1
  8. package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js +0 -2
  9. package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js.map +1 -1
  10. package/lib/screens/inbox/containers/ConversationView.js +487 -888
  11. package/lib/screens/inbox/containers/ConversationView.js.map +1 -1
  12. package/lib/screens/inbox/containers/Dialogs.js +243 -547
  13. package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
  14. package/lib/screens/inbox/containers/ThreadConversationView.js +409 -1364
  15. package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
  16. package/package.json +4 -4
  17. package/src/screens/inbox/components/CachedImage/index.tsx +191 -140
  18. package/src/screens/inbox/components/DialogsListItem.tsx +112 -345
  19. package/src/screens/inbox/components/ServiceDialogsListItem.tsx +316 -437
  20. package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +2 -4
  21. package/src/screens/inbox/containers/ConversationView.tsx +676 -993
  22. package/src/screens/inbox/containers/ConversationView.tsx.bk +1467 -0
  23. package/src/screens/inbox/containers/Dialogs.tsx +345 -636
  24. package/src/screens/inbox/containers/ThreadConversationView.tsx +661 -1887
  25. package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js +0 -175
  26. package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js.map +0 -1
  27. package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js +0 -191
  28. package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js.map +0 -1
  29. package/lib/screens/inbox/containers/workflow/conversation-xstate.js +0 -380
  30. package/lib/screens/inbox/containers/workflow/conversation-xstate.js.map +0 -1
  31. package/lib/screens/inbox/containers/workflow/dialogs-xstate.js +0 -211
  32. package/lib/screens/inbox/containers/workflow/dialogs-xstate.js.map +0 -1
  33. package/lib/screens/inbox/containers/workflow/thread-conversation-xstate.js +0 -438
  34. package/lib/screens/inbox/containers/workflow/thread-conversation-xstate.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@messenger-box/platform-mobile",
3
- "version": "10.0.3-alpha.34",
3
+ "version": "10.0.3-alpha.37",
4
4
  "description": "Sample core for higher packages to depend on",
5
5
  "license": "ISC",
6
6
  "author": "CDMBase LLC",
@@ -22,8 +22,8 @@
22
22
  "watch-ts": "tsc --watch"
23
23
  },
24
24
  "dependencies": {
25
- "@messenger-box/core": "10.0.3-alpha.34",
26
- "@messenger-box/platform-client": "10.0.3-alpha.34",
25
+ "@messenger-box/core": "10.0.3-alpha.36",
26
+ "@messenger-box/platform-client": "10.0.3-alpha.37",
27
27
  "base-64": "1.0.0",
28
28
  "react-native-gifted-chat": "1.0.4"
29
29
  },
@@ -43,5 +43,5 @@
43
43
  "typescript": {
44
44
  "definition": "lib/index.d.ts"
45
45
  },
46
- "gitHead": "33c7bdb4f36c40b6368a5c96fe6312de009188a1"
46
+ "gitHead": "4a360a1be756050ed8c3c1cd61a037c6109f8866"
47
47
  }
@@ -1,20 +1,25 @@
1
- import React, { useEffect, useState, useRef } from 'react';
1
+ import React, { useEffect, useState, useRef, useMemo, useCallback } from 'react';
2
2
  import { Image, View, Text } from 'react-native';
3
3
  // import { Image } from "react-native"
4
4
  import * as FileSystem from 'expo-file-system';
5
5
 
6
6
  import * as CONST from './consts';
7
7
 
8
+ // Global download tracking to prevent duplicate downloads
9
+ const downloadTracker = new Map();
10
+
8
11
  // Ensure the cache directory exists
9
12
  const ensureCacheDirectory = async () => {
10
13
  try {
11
14
  const dirInfo = await FileSystem.getInfoAsync(CONST.IMAGE_CACHE_FOLDER);
12
15
  if (!dirInfo.exists) {
13
- console.log('Creating cache directory:', CONST.IMAGE_CACHE_FOLDER);
16
+ // console.log('Creating cache directory:', CONST.IMAGE_CACHE_FOLDER);
14
17
  await FileSystem.makeDirectoryAsync(CONST.IMAGE_CACHE_FOLDER, { intermediates: true });
15
18
  }
19
+ return true;
16
20
  } catch (error) {
17
- console.error('Failed to create cache directory:', error);
21
+ // console.error('Failed to create cache directory:', error);
22
+ return false;
18
23
  }
19
24
  };
20
25
 
@@ -29,7 +34,7 @@ const validateImageUri = (uri: string): string | null => {
29
34
  try {
30
35
  new URL(uri);
31
36
  } catch (e) {
32
- console.log('Invalid URL format:', uri);
37
+ // console.log('Invalid URL format:', uri);
33
38
  return null;
34
39
  }
35
40
 
@@ -37,175 +42,211 @@ const validateImageUri = (uri: string): string | null => {
37
42
  return uri;
38
43
  };
39
44
 
40
- const CachedImage = (props: any) => {
41
- const { source, cacheKey, placeholderContent } = props;
42
- const { uri: originalUri, headers, expiresIn } = source;
43
-
44
- // Validate and sanitize the URI
45
- const uri = validateImageUri(originalUri);
46
-
47
- const fileURI = `${CONST.IMAGE_CACHE_FOLDER}${cacheKey}`;
48
-
49
- const [imgUri, setImgUri] = useState<any>(fileURI);
50
- const [loadError, setLoadError] = useState<boolean>(false);
51
- const [useFallbackUri, setUseFallbackUri] = useState<boolean>(false);
52
-
53
- const componentIsMounted = useRef(true);
54
- const requestOption = headers ? { headers } : {};
55
-
56
- const _callback = (downloadProgress: any) => {
57
- if (componentIsMounted.current === false) {
58
- downloadResumableRef.current?.pauseAsync();
59
- FileSystem.deleteAsync(fileURI, { idempotent: true }); // delete file locally if it was not downloaded properly
60
- }
61
- };
62
-
63
- const downloadResumableRef = useRef(
64
- uri ? FileSystem.createDownloadResumable(uri, fileURI, requestOption, _callback) : null,
65
- );
66
-
67
- useEffect(() => {
68
- const initCache = async () => {
69
- await ensureCacheDirectory();
70
- if (uri) {
71
- loadImage();
72
- } else {
73
- console.log('Image URI is invalid, not loading');
74
- setLoadError(true);
75
- }
76
- };
77
-
78
- // console.log('CachedImage loading with URI:', uri);
79
- console.log('Cache key:', cacheKey);
80
-
81
- initCache();
82
-
83
- return () => {
84
- componentIsMounted.current = false;
85
- };
86
- }, []); // eslint-disable-line react-hooks/exhaustive-deps
87
-
88
- const loadImage = async () => {
89
- try {
90
- // Use the cached image if it exists
91
- const metadata: any = await FileSystem.getInfoAsync(fileURI);
92
- const expired = expiresIn && new Date().getTime() / 1000 - metadata?.modificationTime > expiresIn;
93
- console.log({ expiresIn, expired });
45
+ const CachedImage = React.memo(
46
+ (props: any) => {
47
+ const { source, cacheKey, placeholderContent, style, resizeMode, alt, ...restProps } = props;
48
+ const { uri: originalUri, headers, expiresIn = 86400 } = source;
49
+
50
+ // Validate and sanitize the URI
51
+ const uri = validateImageUri(originalUri);
52
+ const fileURI = `${CONST.IMAGE_CACHE_FOLDER}${cacheKey}`;
53
+
54
+ const [imgUri, setImgUri] = useState<string | null>(null);
55
+ const [loadError, setLoadError] = useState<boolean>(false);
56
+ const [useFallbackUri, setUseFallbackUri] = useState<boolean>(false);
57
+ const [isLoading, setIsLoading] = useState<boolean>(true);
58
+
59
+ const componentIsMounted = useRef(true);
60
+ const hasAttemptedLoad = useRef(false);
61
+ const requestOption = headers ? { headers } : {};
62
+
63
+ // Create a memoized download callback
64
+ const downloadCallback = useCallback(
65
+ (downloadProgress: any) => {
66
+ if (componentIsMounted.current === false) {
67
+ downloadTracker.delete(cacheKey);
68
+ downloadResumableRef.current?.pauseAsync();
69
+ FileSystem.deleteAsync(fileURI, { idempotent: true });
70
+ }
71
+ },
72
+ [fileURI, cacheKey],
73
+ );
74
+
75
+ // Create a memoized download resumable
76
+ const downloadResumableRef = useRef(
77
+ uri ? FileSystem.createDownloadResumable(uri, fileURI, requestOption, downloadCallback) : null,
78
+ );
79
+
80
+ // Memoized load image function to avoid recreating on every render
81
+ const loadImage = useCallback(async () => {
82
+ if (!uri || hasAttemptedLoad.current) return;
83
+
84
+ hasAttemptedLoad.current = true;
85
+ setIsLoading(true);
86
+
87
+ try {
88
+ // Check if this image is already being downloaded
89
+ if (downloadTracker.has(cacheKey)) {
90
+ // Wait for the existing download to complete
91
+ await downloadTracker.get(cacheKey);
92
+ if (componentIsMounted.current) {
93
+ setImgUri(fileURI);
94
+ setIsLoading(false);
95
+ }
96
+ return;
97
+ }
94
98
 
95
- // console.log({ modificationTime: metadata.modificationTime, currentTime: new Date().getTime() / 1000 });
96
- // console.log({ metadata });
99
+ // Use the cached image if it exists
100
+ const metadata: any = await FileSystem.getInfoAsync(fileURI);
101
+ const expired = expiresIn && new Date().getTime() / 1000 - metadata?.modificationTime > expiresIn;
97
102
 
98
- if (!metadata.exists || metadata?.size === 0 || expired) {
99
- if (componentIsMounted.current) {
100
- setImgUri(null);
103
+ if (!metadata.exists || metadata?.size === 0 || expired) {
104
+ if (!componentIsMounted.current) return;
101
105
 
102
- if (expired) {
106
+ if (expired && metadata.exists) {
103
107
  await FileSystem.deleteAsync(fileURI, { idempotent: true });
104
108
  }
105
- // download to cache
106
- setImgUri(null);
107
109
 
108
- // console.log('Downloading image from URI:', uri);
109
110
  if (!uri) {
110
- // console.log('Image URI is undefined or null');
111
111
  setLoadError(true);
112
+ setIsLoading(false);
112
113
  return;
113
114
  }
114
115
 
115
116
  if (!downloadResumableRef.current) {
116
- console.log('Download resumable is null');
117
117
  setUseFallbackUri(true);
118
118
  setImgUri(uri);
119
+ setIsLoading(false);
119
120
  return;
120
121
  }
121
122
 
122
123
  try {
123
- const response: any = await downloadResumableRef.current.downloadAsync();
124
- // console.log('Download response:', response);
125
-
126
- if (componentIsMounted.current && response && response.status === 200) {
127
- setImgUri(`${fileURI}?`); // deep clone to force re-render
128
- // console.log('Image cached successfully, new URI:', `${fileURI}?`);
129
- } else {
130
- console.log('Failed to download image, status:', response?.status);
131
- console.log('Falling back to original URI');
124
+ // Record this download in the global tracker
125
+ const downloadPromise = downloadResumableRef.current.downloadAsync();
126
+ downloadTracker.set(cacheKey, downloadPromise);
127
+
128
+ const response: any = await downloadPromise;
129
+
130
+ // Remove from tracker when done
131
+ downloadTracker.delete(cacheKey);
132
+
133
+ if (componentIsMounted.current) {
134
+ if (response && response.status === 200) {
135
+ setImgUri(fileURI);
136
+ } else {
137
+ setUseFallbackUri(true);
138
+ setImgUri(uri);
139
+ FileSystem.deleteAsync(fileURI, { idempotent: true });
140
+ }
141
+ setIsLoading(false);
142
+ }
143
+ } catch (downloadError) {
144
+ downloadTracker.delete(cacheKey);
145
+ if (componentIsMounted.current) {
132
146
  setUseFallbackUri(true);
133
147
  setImgUri(uri);
134
- FileSystem.deleteAsync(fileURI, { idempotent: true }); // delete file locally if it was not downloaded properly
148
+ setIsLoading(false);
135
149
  }
136
- } catch (downloadError) {
137
- console.log('Error downloading image:', downloadError);
138
- console.log('Falling back to original URI');
139
- setUseFallbackUri(true);
140
- setImgUri(uri);
141
150
  }
142
- }
143
- } else {
144
- //console.log('Using cached image at:', fileURI);
145
- }
146
- } catch (err) {
147
- console.log({ err });
148
- console.log('Falling back to original URI');
149
- setUseFallbackUri(true);
150
- setImgUri(uri);
151
- }
152
- };
153
-
154
- //console.log({ placeholderContent, imgUri, loadError, useFallbackUri });
155
-
156
- // Default placeholder if none is provided
157
- const defaultPlaceholder = (
158
- <View
159
- style={{
160
- width: '100%',
161
- height: '100%',
162
- backgroundColor: '#e1e1e1',
163
- justifyContent: 'center',
164
- alignItems: 'center',
165
- borderRadius: 3,
166
- }}
167
- >
168
- <Text>Image not available</Text>
169
- </View>
170
- );
171
-
172
- if (!imgUri) {
173
- return placeholderContent || defaultPlaceholder;
174
- }
175
-
176
- return (
177
- <Image
178
- // eslint-disable-next-line react/jsx-props-no-spreading
179
- {...props}
180
- source={{
181
- ...source,
182
- uri: useFallbackUri ? uri : imgUri,
183
- }}
184
- onError={(e) => {
185
- console.log('Image loading error:', e.nativeEvent.error);
186
- // If we're already using the fallback URI and still getting an error,
187
- // then show the placeholder
188
- if (useFallbackUri) {
189
- setLoadError(true);
190
- setImgUri(null);
191
151
  } else {
192
- console.log('Falling back to original URI after onError');
152
+ // Use cached version
153
+ setImgUri(fileURI);
154
+ setIsLoading(false);
155
+ }
156
+ } catch (err) {
157
+ if (componentIsMounted.current) {
193
158
  setUseFallbackUri(true);
194
159
  setImgUri(uri);
160
+ setIsLoading(false);
195
161
  }
196
- }}
197
- />
198
- );
199
- };
162
+ }
163
+ }, [uri, fileURI, cacheKey, expiresIn]);
164
+
165
+ // Setup effect
166
+ useEffect(() => {
167
+ const setup = async () => {
168
+ const directoryExists = await ensureCacheDirectory();
169
+ if (directoryExists && uri) {
170
+ loadImage();
171
+ } else {
172
+ setLoadError(true);
173
+ setIsLoading(false);
174
+ }
175
+ };
176
+
177
+ setup();
178
+
179
+ return () => {
180
+ componentIsMounted.current = false;
181
+ };
182
+ }, [uri, loadImage]);
183
+
184
+ // Default placeholder
185
+ const defaultPlaceholder = useMemo(
186
+ () => (
187
+ <View
188
+ style={[
189
+ {
190
+ width: '100%',
191
+ height: '100%',
192
+ backgroundColor: '#e1e1e1',
193
+ justifyContent: 'center',
194
+ alignItems: 'center',
195
+ borderRadius: 3,
196
+ },
197
+ style,
198
+ ]}
199
+ >
200
+ <Text>Image not available</Text>
201
+ </View>
202
+ ),
203
+ [style],
204
+ );
205
+
206
+ // Show placeholder while loading or if there's an error
207
+ if (isLoading || loadError || !imgUri) {
208
+ return placeholderContent || defaultPlaceholder;
209
+ }
210
+
211
+ // Actual image component
212
+ return (
213
+ <Image
214
+ {...restProps}
215
+ style={style}
216
+ source={{
217
+ ...source,
218
+ uri: useFallbackUri ? uri : imgUri,
219
+ }}
220
+ resizeMode={resizeMode}
221
+ onError={(e) => {
222
+ if (useFallbackUri) {
223
+ setLoadError(true);
224
+ setImgUri(null);
225
+ } else {
226
+ setUseFallbackUri(true);
227
+ setImgUri(uri);
228
+ }
229
+ }}
230
+ />
231
+ );
232
+ },
233
+ (prevProps, nextProps) => {
234
+ // Custom comparison function for memoization
235
+ return (
236
+ prevProps.cacheKey === nextProps.cacheKey &&
237
+ prevProps.source.uri === nextProps.source.uri &&
238
+ prevProps.style === nextProps.style
239
+ );
240
+ },
241
+ );
200
242
 
201
243
  export const CacheManager = {
202
244
  addToCache: async ({ file, key }: any) => {
245
+ await ensureCacheDirectory();
203
246
  await FileSystem.copyAsync({
204
247
  from: file,
205
248
  to: `${CONST.IMAGE_CACHE_FOLDER}${key}`,
206
249
  });
207
- // const uri = await FileSystem.getContentUriAsync(`${CONST.IMAGE_CACHE_FOLDER}${key}`)
208
- // return uri
209
250
  const uri = await CacheManager.getCachedUri({ key });
210
251
  return uri;
211
252
  },
@@ -216,8 +257,18 @@ export const CacheManager = {
216
257
  },
217
258
 
218
259
  downloadAsync: async ({ uri, key, options }: any) => {
260
+ await ensureCacheDirectory();
219
261
  return await FileSystem.downloadAsync(uri, `${CONST.IMAGE_CACHE_FOLDER}${key}`, options);
220
262
  },
263
+
264
+ clearCache: async () => {
265
+ try {
266
+ await FileSystem.deleteAsync(CONST.IMAGE_CACHE_FOLDER, { idempotent: true });
267
+ await ensureCacheDirectory();
268
+ } catch (error) {
269
+ console.error('Error clearing cache:', error);
270
+ }
271
+ },
221
272
  };
222
273
 
223
274
  export default CachedImage;