@oxyhq/services 5.11.9 → 5.11.10

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 (192) hide show
  1. package/lib/commonjs/ui/components/AnimationExample.js +213 -0
  2. package/lib/commonjs/ui/components/AnimationExample.js.map +1 -0
  3. package/lib/commonjs/ui/components/FollowButton.js +58 -47
  4. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  5. package/lib/commonjs/ui/components/GroupedItem.js +2 -1
  6. package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
  7. package/lib/commonjs/ui/components/GroupedSection.js +3 -0
  8. package/lib/commonjs/ui/components/GroupedSection.js.map +1 -1
  9. package/lib/commonjs/ui/components/Header.js +25 -11
  10. package/lib/commonjs/ui/components/Header.js.map +1 -1
  11. package/lib/commonjs/ui/components/OxyProvider.js +69 -33
  12. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  13. package/lib/commonjs/ui/components/ProfileCard.js +5 -1
  14. package/lib/commonjs/ui/components/ProfileCard.js.map +1 -1
  15. package/lib/commonjs/ui/components/index.js +0 -7
  16. package/lib/commonjs/ui/components/index.js.map +1 -1
  17. package/lib/commonjs/ui/components/internal/TextField.js +8 -4
  18. package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
  19. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js +161 -0
  20. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -0
  21. package/lib/commonjs/ui/context/OxyContext.js +97 -38
  22. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  23. package/lib/commonjs/ui/hooks/useFollow.types.js +2 -0
  24. package/lib/commonjs/ui/hooks/useFollow.types.js.map +1 -0
  25. package/lib/commonjs/ui/navigation/OxyRouter.js +10 -0
  26. package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
  27. package/lib/commonjs/ui/screens/AccountCenterScreen.js +26 -14
  28. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  29. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +3 -3
  30. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  31. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +64 -15
  32. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  33. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +4 -4
  34. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  35. package/lib/commonjs/ui/screens/FeedbackScreen.js +72 -75
  36. package/lib/commonjs/ui/screens/FeedbackScreen.js.map +1 -1
  37. package/lib/commonjs/ui/screens/FileManagementScreen.js +286 -126
  38. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  39. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +322 -0
  40. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -0
  41. package/lib/commonjs/ui/screens/ProfileScreen.js +1 -1
  42. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  43. package/lib/commonjs/ui/screens/SessionManagementScreen.js +176 -174
  44. package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
  45. package/lib/commonjs/ui/screens/SignInScreen.js +43 -52
  46. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  47. package/lib/commonjs/ui/screens/SignUpScreen.js +6 -4
  48. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  49. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +386 -0
  50. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -0
  51. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +25 -15
  52. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  53. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +16 -9
  54. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  55. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +1 -1
  56. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  57. package/lib/commonjs/ui/styles/authStyles.js +1 -1
  58. package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
  59. package/lib/module/ui/components/AnimationExample.js +209 -0
  60. package/lib/module/ui/components/AnimationExample.js.map +1 -0
  61. package/lib/module/ui/components/FollowButton.js +58 -47
  62. package/lib/module/ui/components/FollowButton.js.map +1 -1
  63. package/lib/module/ui/components/GroupedItem.js +2 -1
  64. package/lib/module/ui/components/GroupedItem.js.map +1 -1
  65. package/lib/module/ui/components/GroupedSection.js +3 -0
  66. package/lib/module/ui/components/GroupedSection.js.map +1 -1
  67. package/lib/module/ui/components/Header.js +25 -11
  68. package/lib/module/ui/components/Header.js.map +1 -1
  69. package/lib/module/ui/components/OxyProvider.js +70 -34
  70. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  71. package/lib/module/ui/components/ProfileCard.js +5 -1
  72. package/lib/module/ui/components/ProfileCard.js.map +1 -1
  73. package/lib/module/ui/components/index.js +0 -1
  74. package/lib/module/ui/components/index.js.map +1 -1
  75. package/lib/module/ui/components/internal/TextField.js +8 -4
  76. package/lib/module/ui/components/internal/TextField.js.map +1 -1
  77. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js +156 -0
  78. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -0
  79. package/lib/module/ui/context/OxyContext.js +97 -39
  80. package/lib/module/ui/context/OxyContext.js.map +1 -1
  81. package/lib/module/ui/hooks/useFollow.types.js +2 -0
  82. package/lib/module/ui/hooks/useFollow.types.js.map +1 -0
  83. package/lib/module/ui/navigation/OxyRouter.js +10 -0
  84. package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
  85. package/lib/module/ui/screens/AccountCenterScreen.js +12 -1
  86. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  87. package/lib/module/ui/screens/AccountOverviewScreen.js +3 -3
  88. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  89. package/lib/module/ui/screens/AccountSettingsScreen.js +64 -15
  90. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  91. package/lib/module/ui/screens/AccountSwitcherScreen.js +4 -4
  92. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  93. package/lib/module/ui/screens/FeedbackScreen.js +72 -75
  94. package/lib/module/ui/screens/FeedbackScreen.js.map +1 -1
  95. package/lib/module/ui/screens/FileManagementScreen.js +285 -125
  96. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  97. package/lib/module/ui/screens/LanguageSelectorScreen.js +319 -0
  98. package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -0
  99. package/lib/module/ui/screens/ProfileScreen.js +1 -1
  100. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  101. package/lib/module/ui/screens/SessionManagementScreen.js +177 -175
  102. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  103. package/lib/module/ui/screens/SignInScreen.js +44 -53
  104. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  105. package/lib/module/ui/screens/SignUpScreen.js +6 -4
  106. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  107. package/lib/module/ui/screens/WelcomeNewUserScreen.js +382 -0
  108. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -0
  109. package/lib/module/ui/screens/internal/SignInPasswordStep.js +23 -14
  110. package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  111. package/lib/module/ui/screens/internal/SignInUsernameStep.js +15 -9
  112. package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  113. package/lib/module/ui/screens/karma/KarmaCenterScreen.js +1 -1
  114. package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  115. package/lib/module/ui/styles/authStyles.js +1 -1
  116. package/lib/module/ui/styles/authStyles.js.map +1 -1
  117. package/lib/typescript/models/interfaces.d.ts +1 -5
  118. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  119. package/lib/typescript/models/session.d.ts +1 -4
  120. package/lib/typescript/models/session.d.ts.map +1 -1
  121. package/lib/typescript/ui/components/AnimationExample.d.ts +4 -0
  122. package/lib/typescript/ui/components/AnimationExample.d.ts.map +1 -0
  123. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  124. package/lib/typescript/ui/components/GroupedItem.d.ts.map +1 -1
  125. package/lib/typescript/ui/components/Header.d.ts +9 -0
  126. package/lib/typescript/ui/components/Header.d.ts.map +1 -1
  127. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  128. package/lib/typescript/ui/components/ProfileCard.d.ts +1 -3
  129. package/lib/typescript/ui/components/ProfileCard.d.ts.map +1 -1
  130. package/lib/typescript/ui/components/index.d.ts +0 -1
  131. package/lib/typescript/ui/components/index.d.ts.map +1 -1
  132. package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
  133. package/lib/typescript/ui/components/photogrid/JustifiedPhotoGrid.d.ts +27 -0
  134. package/lib/typescript/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -0
  135. package/lib/typescript/ui/context/OxyContext.d.ts +6 -2
  136. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  137. package/lib/typescript/ui/hooks/useFollow.types.d.ts +33 -0
  138. package/lib/typescript/ui/hooks/useFollow.types.d.ts.map +1 -0
  139. package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
  140. package/lib/typescript/ui/navigation/types.d.ts +5 -0
  141. package/lib/typescript/ui/navigation/types.d.ts.map +1 -1
  142. package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
  143. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  144. package/lib/typescript/ui/screens/FeedbackScreen.d.ts.map +1 -1
  145. package/lib/typescript/ui/screens/FileManagementScreen.d.ts +18 -1
  146. package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
  147. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts +7 -0
  148. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts.map +1 -0
  149. package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -1
  150. package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
  151. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  152. package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
  153. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts +13 -0
  154. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -0
  155. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts +5 -5
  156. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
  157. package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts +4 -4
  158. package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts.map +1 -1
  159. package/lib/typescript/ui/styles/authStyles.d.ts +1 -1
  160. package/package.json +10 -2
  161. package/src/models/interfaces.ts +2 -5
  162. package/src/models/session.ts +1 -4
  163. package/src/ui/components/AnimationExample.tsx +194 -0
  164. package/src/ui/components/FollowButton.tsx +65 -45
  165. package/src/ui/components/GroupedItem.tsx +1 -0
  166. package/src/ui/components/GroupedSection.tsx +1 -1
  167. package/src/ui/components/Header.tsx +36 -12
  168. package/src/ui/components/OxyProvider.tsx +66 -32
  169. package/src/ui/components/ProfileCard.tsx +6 -8
  170. package/src/ui/components/index.ts +0 -1
  171. package/src/ui/components/internal/TextField.tsx +12 -6
  172. package/src/ui/components/photogrid/JustifiedPhotoGrid.tsx +158 -0
  173. package/src/ui/context/OxyContext.tsx +84 -54
  174. package/src/ui/hooks/useFollow.types.ts +33 -0
  175. package/src/ui/navigation/OxyRouter.tsx +10 -0
  176. package/src/ui/navigation/types.ts +6 -0
  177. package/src/ui/screens/AccountCenterScreen.tsx +13 -7
  178. package/src/ui/screens/AccountOverviewScreen.tsx +3 -3
  179. package/src/ui/screens/AccountSettingsScreen.tsx +65 -13
  180. package/src/ui/screens/AccountSwitcherScreen.tsx +4 -4
  181. package/src/ui/screens/FeedbackScreen.tsx +57 -80
  182. package/src/ui/screens/FileManagementScreen.tsx +278 -175
  183. package/src/ui/screens/LanguageSelectorScreen.tsx +322 -0
  184. package/src/ui/screens/ProfileScreen.tsx +6 -1
  185. package/src/ui/screens/SessionManagementScreen.tsx +148 -151
  186. package/src/ui/screens/SignInScreen.tsx +43 -62
  187. package/src/ui/screens/SignUpScreen.tsx +3 -5
  188. package/src/ui/screens/WelcomeNewUserScreen.tsx +272 -0
  189. package/src/ui/screens/internal/SignInPasswordStep.tsx +28 -13
  190. package/src/ui/screens/internal/SignInUsernameStep.tsx +21 -11
  191. package/src/ui/screens/karma/KarmaCenterScreen.tsx +1 -1
  192. package/src/ui/styles/authStyles.ts +1 -1
@@ -22,10 +22,34 @@ import { Ionicons } from '@expo/vector-icons';
22
22
  import type { FileMetadata } from '../../models/interfaces';
23
23
  import { useFileStore, useFiles, useUploading as useUploadingStore, useUploadAggregateProgress, useDeleting as useDeletingStore } from '../stores/fileStore';
24
24
  import Header from '../components/Header';
25
+ import JustifiedPhotoGrid from '../components/photogrid/JustifiedPhotoGrid';
25
26
  import { GroupedSection } from '../components';
26
27
 
27
- interface FileManagementScreenProps extends BaseScreenProps {
28
+ // Exporting props & callback types so external callers (e.g. showBottomSheet config objects) can annotate
29
+ export type OnConfirmFileSelection = (files: FileMetadata[]) => void;
30
+
31
+ export interface FileManagementScreenProps extends BaseScreenProps {
28
32
  userId?: string;
33
+ // Enable selection mode (acts like a picker). When true, opening a file selects it instead of showing viewer
34
+ selectMode?: boolean;
35
+ // Allow selecting multiple files; only used if selectMode is true
36
+ multiSelect?: boolean;
37
+ // Callback when a file is selected (single select mode)
38
+ onSelect?: (file: FileMetadata) => void;
39
+ // Callback when confirm pressed in multi-select mode
40
+ onConfirmSelection?: OnConfirmFileSelection;
41
+ // Initial selected file IDs for multi-select
42
+ initialSelectedIds?: string[];
43
+ maxSelection?: number;
44
+ disabledMimeTypes?: string[];
45
+ /**
46
+ * What to do after a single selection (non-multiSelect) is made.
47
+ * 'close' (default) will dismiss the bottom sheet via onClose.
48
+ * 'back' will navigate back to the previous screen (e.g., return to AccountSettings without closing sheet).
49
+ * 'none' will keep the picker open (caller can manually close or navigate).
50
+ */
51
+ afterSelect?: 'close' | 'back' | 'none';
52
+ allowUploadInSelectMode?: boolean;
29
53
  }
30
54
 
31
55
  // Add this helper function near the top (after imports):
@@ -40,6 +64,15 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
40
64
  navigate,
41
65
  userId,
42
66
  containerWidth = 400, // Fallback for when not provided by the router
67
+ selectMode = false,
68
+ multiSelect = false,
69
+ onSelect,
70
+ onConfirmSelection,
71
+ initialSelectedIds = [],
72
+ maxSelection,
73
+ disabledMimeTypes = [],
74
+ afterSelect = 'close',
75
+ allowUploadInSelectMode = true,
43
76
  }) => {
44
77
  const { user, oxyServices } = useOxy();
45
78
 
@@ -60,8 +93,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
60
93
  const deleting = useDeletingStore();
61
94
  const [loading, setLoading] = useState(true);
62
95
  const [refreshing, setRefreshing] = useState(false);
96
+ const [paging, setPaging] = useState({ offset: 0, limit: 40, total: 0, hasMore: true, loadingMore: false });
63
97
  const [selectedFile, setSelectedFile] = useState<FileMetadata | null>(null);
64
98
  const [showFileDetails, setShowFileDetails] = useState(false);
99
+ // In selectMode we never open the detailed viewer
65
100
  const [openedFile, setOpenedFile] = useState<FileMetadata | null>(null);
66
101
  const [fileContent, setFileContent] = useState<string | null>(null);
67
102
  const [loadingFileContent, setLoadingFileContent] = useState(false);
@@ -90,6 +125,57 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
90
125
  const [hoveredPreview, setHoveredPreview] = useState<string | null>(null);
91
126
  const uploadStartRef = useRef<number | null>(null);
92
127
  const MIN_BANNER_MS = 600;
128
+ // Selection state
129
+ const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set(initialSelectedIds));
130
+ useEffect(() => {
131
+ if (initialSelectedIds && initialSelectedIds.length) {
132
+ setSelectedIds(new Set(initialSelectedIds));
133
+ }
134
+ }, [initialSelectedIds]);
135
+
136
+ const toggleSelect = useCallback((file: FileMetadata) => {
137
+ if (!selectMode) return;
138
+ if (disabledMimeTypes.length) {
139
+ const blocked = disabledMimeTypes.some(mt => file.contentType === mt || file.contentType.startsWith(mt.endsWith('/') ? mt : mt + '/'));
140
+ if (blocked) {
141
+ toast.error('This file type cannot be selected');
142
+ return;
143
+ }
144
+ }
145
+ if (!multiSelect) {
146
+ onSelect?.(file);
147
+ if (afterSelect === 'back') {
148
+ goBack?.();
149
+ } else if (afterSelect === 'close') {
150
+ onClose?.();
151
+ }
152
+ return;
153
+ }
154
+ setSelectedIds(prev => {
155
+ const next = new Set(prev);
156
+ const already = next.has(file.id);
157
+ if (!already) {
158
+ if (maxSelection && next.size >= maxSelection) {
159
+ toast.error(`You can select up to ${maxSelection}`);
160
+ return prev;
161
+ }
162
+ next.add(file.id);
163
+ } else {
164
+ next.delete(file.id);
165
+ }
166
+ return next;
167
+ });
168
+ }, [selectMode, multiSelect, onSelect, onClose, goBack, disabledMimeTypes, maxSelection, afterSelect]);
169
+
170
+ const confirmMultiSelection = useCallback(() => {
171
+ if (!selectMode || !multiSelect) return;
172
+ const map: Record<string, FileMetadata> = {};
173
+ files.forEach(f => { map[f.id] = f; });
174
+ const chosen = Array.from(selectedIds).map(id => map[id]).filter(Boolean);
175
+ onConfirmSelection?.(chosen);
176
+ onClose?.();
177
+ }, [selectMode, multiSelect, selectedIds, files, onConfirmSelection, onClose]);
178
+
93
179
  const endUpload = useCallback(() => {
94
180
  const started = uploadStartRef.current;
95
181
  const elapsed = started ? Date.now() - started : MIN_BANNER_MS;
@@ -162,7 +248,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
162
248
  const storeSetUploadProgress = useFileStore(s => s.setUploadProgress);
163
249
  const storeSetDeleting = useFileStore(s => s.setDeleting);
164
250
 
165
- const loadFiles = useCallback(async (mode: 'initial' | 'refresh' | 'silent' = 'initial') => {
251
+ const loadFiles = useCallback(async (mode: 'initial' | 'refresh' | 'silent' | 'more' = 'initial') => {
166
252
  if (!targetUserId) return;
167
253
 
168
254
  try {
@@ -170,9 +256,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
170
256
  setRefreshing(true);
171
257
  } else if (mode === 'initial') {
172
258
  setLoading(true);
259
+ setPaging(p => ({ ...p, offset: 0, hasMore: true }));
260
+ } else if (mode === 'more') {
261
+ // Prevent duplicate fetches
262
+ setPaging(p => ({ ...p, loadingMore: true }));
173
263
  }
174
-
175
- const response = await oxyServices.listUserFiles();
264
+ const currentPaging = mode === 'more' ? (prevPagingRef.current ?? paging) : paging;
265
+ const effectiveOffset = mode === 'more' ? currentPaging.offset + currentPaging.limit : 0;
266
+ const response = await oxyServices.listUserFiles(currentPaging.limit, effectiveOffset);
176
267
  const assets: FileMetadata[] = (response.files || []).map((f: any) => ({
177
268
  id: f.id,
178
269
  filename: f.originalName || f.sha256,
@@ -183,16 +274,39 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
183
274
  metadata: f.metadata || {},
184
275
  variants: f.variants || [],
185
276
  }));
186
- // Merge to preserve existing order & allow incremental updates
187
- useFileStore.getState().setFiles(assets, { merge: true });
277
+ if (mode === 'more') {
278
+ // append
279
+ useFileStore.getState().setFiles(assets, { merge: true });
280
+ setPaging(p => ({
281
+ ...p,
282
+ offset: effectiveOffset,
283
+ total: response.total || (effectiveOffset + assets.length),
284
+ hasMore: response.hasMore,
285
+ loadingMore: false,
286
+ }));
287
+ } else {
288
+ useFileStore.getState().setFiles(assets, { merge: false });
289
+ setPaging(p => ({
290
+ ...p,
291
+ offset: 0,
292
+ total: response.total || assets.length,
293
+ hasMore: response.hasMore,
294
+ loadingMore: false,
295
+ }));
296
+ }
188
297
  } catch (error: any) {
189
298
  console.error('Failed to load files:', error);
190
299
  toast.error(error.message || 'Failed to load files');
191
300
  } finally {
192
301
  setLoading(false);
193
302
  setRefreshing(false);
303
+ setPaging(p => ({ ...p, loadingMore: false }));
194
304
  }
195
- }, [targetUserId, oxyServices]);
305
+ }, [targetUserId, oxyServices, paging]);
306
+
307
+ // Keep a ref to avoid stale closure when calculating next offset
308
+ const prevPagingRef = useRef(paging);
309
+ useEffect(() => { prevPagingRef.current = paging; }, [paging]);
196
310
 
197
311
  // (removed effect; filteredFiles is memoized)
198
312
 
@@ -607,6 +721,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
607
721
  };
608
722
 
609
723
  const handleFileOpen = async (file: FileMetadata) => {
724
+ if (selectMode) {
725
+ toggleSelect(file);
726
+ return;
727
+ }
610
728
  try {
611
729
  setLoadingFileContent(true);
612
730
  setOpenedFile(file);
@@ -691,6 +809,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
691
809
  width: itemWidth,
692
810
  height: itemWidth,
693
811
  marginRight: (index + 1) % itemsPerRow === 0 ? 0 : 4,
812
+ ...(selectMode && selectedIds.has(photo.id) ? { borderWidth: 2, borderColor: themeStyles.primaryColor } : {})
694
813
  }
695
814
  ]}
696
815
  onPress={() => handleFileOpen(photo)}
@@ -708,10 +827,15 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
708
827
  }}
709
828
  accessibilityLabel={photo.filename}
710
829
  />
830
+ {selectMode && (
831
+ <View style={styles.selectionBadge}>
832
+ <Ionicons name={selectedIds.has(photo.id) ? 'checkmark-circle' : 'ellipse-outline'} size={20} color={selectedIds.has(photo.id) ? themeStyles.primaryColor : themeStyles.textColor} />
833
+ </View>
834
+ )}
711
835
  </View>
712
836
  </TouchableOpacity>
713
837
  );
714
- }, [oxyServices, containerWidth]);
838
+ }, [oxyServices, containerWidth, selectMode, selectedIds, themeStyles.primaryColor, themeStyles.textColor]);
715
839
 
716
840
  const renderJustifiedPhotoItem = useCallback((photo: FileMetadata, width: number, height: number, isLast: boolean) => {
717
841
  const downloadUrl = getSafeDownloadUrl(photo, 'thumb');
@@ -724,7 +848,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
724
848
  {
725
849
  width,
726
850
  height,
727
- }
851
+ ...(selectMode && selectedIds.has(photo.id) ? { borderWidth: 2, borderColor: themeStyles.primaryColor } : {}),
852
+ ...(selectMode && multiSelect && selectedIds.size > 0 && !selectedIds.has(photo.id) ? { opacity: 0.4 } : {}),
853
+ },
728
854
  ]}
729
855
  onPress={() => handleFileOpen(photo)}
730
856
  activeOpacity={0.8}
@@ -741,10 +867,15 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
741
867
  }}
742
868
  accessibilityLabel={photo.filename}
743
869
  />
870
+ {selectMode && (
871
+ <View style={styles.selectionBadge}>
872
+ <Ionicons name={selectedIds.has(photo.id) ? 'checkmark-circle' : 'ellipse-outline'} size={20} color={selectedIds.has(photo.id) ? themeStyles.primaryColor : themeStyles.textColor} />
873
+ </View>
874
+ )}
744
875
  </View>
745
876
  </TouchableOpacity>
746
877
  );
747
- }, [oxyServices]);
878
+ }, [oxyServices, selectMode, selectedIds, multiSelect, themeStyles.primaryColor, themeStyles.textColor]);
748
879
 
749
880
  // Run initial load once per targetUserId change to avoid accidental loops
750
881
  const lastLoadedFor = useRef<string | undefined>(undefined);
@@ -768,7 +899,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
768
899
  return (
769
900
  <View
770
901
  key={file.id}
771
- style={[styles.fileItem, { backgroundColor: themeStyles.secondaryBackgroundColor, borderColor }]}
902
+ style={[styles.fileItem, { backgroundColor: themeStyles.secondaryBackgroundColor, borderColor }, selectMode && selectedIds.has(file.id) && { borderColor: themeStyles.primaryColor, borderWidth: 2 }]}
772
903
  >
773
904
  <TouchableOpacity
774
905
  style={styles.fileContent}
@@ -834,11 +965,16 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
834
965
  </View>
835
966
 
836
967
  {/* Preview overlay for hover effect */}
837
- {Platform.OS === 'web' && hoveredPreview === file.id && isImage && (
968
+ {!selectMode && Platform.OS === 'web' && hoveredPreview === file.id && isImage && (
838
969
  <View style={styles.previewOverlay}>
839
970
  <Ionicons name="eye" size={24} color="#FFFFFF" />
840
971
  </View>
841
972
  )}
973
+ {selectMode && (
974
+ <View style={styles.selectionBadge}>
975
+ <Ionicons name={selectedIds.has(file.id) ? 'checkmark-circle' : 'ellipse-outline'} size={22} color={selectedIds.has(file.id) ? themeStyles.primaryColor : themeStyles.textColor} />
976
+ </View>
977
+ )}
842
978
  </View>
843
979
  ) : (
844
980
  <View style={styles.fileIconContainer}>
@@ -869,39 +1005,41 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
869
1005
  </View>
870
1006
  </TouchableOpacity>
871
1007
 
872
- <View style={styles.fileActions}>
873
- {/* Preview button for supported files */}
874
- {hasPreview && (
1008
+ {!selectMode && (
1009
+ <View style={styles.fileActions}>
1010
+ {/* Preview button for supported files */}
1011
+ {hasPreview && (
1012
+ <TouchableOpacity
1013
+ style={[styles.actionButton, { backgroundColor: themeStyles.isDarkTheme ? '#333333' : '#F0F0F0' }]}
1014
+ onPress={() => handleFileOpen(file)}
1015
+ >
1016
+ <Ionicons name="eye" size={20} color={themeStyles.primaryColor} />
1017
+ </TouchableOpacity>
1018
+ )}
1019
+
875
1020
  <TouchableOpacity
876
1021
  style={[styles.actionButton, { backgroundColor: themeStyles.isDarkTheme ? '#333333' : '#F0F0F0' }]}
877
- onPress={() => handleFileOpen(file)}
1022
+ onPress={() => handleFileDownload(file.id, file.filename)}
878
1023
  >
879
- <Ionicons name="eye" size={20} color={themeStyles.primaryColor} />
1024
+ <Ionicons name="download" size={20} color={themeStyles.primaryColor} />
880
1025
  </TouchableOpacity>
881
- )}
882
-
883
- <TouchableOpacity
884
- style={[styles.actionButton, { backgroundColor: themeStyles.isDarkTheme ? '#333333' : '#F0F0F0' }]}
885
- onPress={() => handleFileDownload(file.id, file.filename)}
886
- >
887
- <Ionicons name="download" size={20} color={themeStyles.primaryColor} />
888
- </TouchableOpacity>
889
1026
 
890
- {/* Always show delete button for debugging */}
891
- <TouchableOpacity
892
- style={[styles.actionButton, { backgroundColor: themeStyles.isDarkTheme ? '#400000' : '#FFEBEE' }]}
893
- onPress={() => {
894
- handleFileDelete(file.id, file.filename);
895
- }}
896
- disabled={deleting === file.id}
897
- >
898
- {deleting === file.id ? (
899
- <ActivityIndicator size="small" color={themeStyles.dangerColor} />
900
- ) : (
901
- <Ionicons name="trash" size={20} color={themeStyles.dangerColor} />
902
- )}
903
- </TouchableOpacity>
904
- </View>
1027
+ {/* Always show delete button for debugging */}
1028
+ <TouchableOpacity
1029
+ style={[styles.actionButton, { backgroundColor: themeStyles.isDarkTheme ? '#400000' : '#FFEBEE' }]}
1030
+ onPress={() => {
1031
+ handleFileDelete(file.id, file.filename);
1032
+ }}
1033
+ disabled={deleting === file.id}
1034
+ >
1035
+ {deleting === file.id ? (
1036
+ <ActivityIndicator size="small" color={themeStyles.dangerColor} />
1037
+ ) : (
1038
+ <Ionicons name="trash" size={20} color={themeStyles.dangerColor} />
1039
+ )}
1040
+ </TouchableOpacity>
1041
+ </View>
1042
+ )}
905
1043
  </View>
906
1044
  );
907
1045
  };
@@ -909,13 +1047,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
909
1047
  // GroupedSection-based file items (for 'all' view) replacing legacy flat list look
910
1048
  const groupedFileItems = useMemo(() => {
911
1049
  return filteredFiles
912
- .filter(f => true) // placeholder for future filtering
1050
+ .filter(f => true)
913
1051
  .sort((a, b) => new Date(b.uploadDate).getTime() - new Date(a.uploadDate).getTime())
914
1052
  .map((file) => {
915
1053
  const isImage = file.contentType.startsWith('image/');
916
1054
  const isVideo = file.contentType.startsWith('video/');
917
1055
  const hasPreview = isImage || isVideo;
918
1056
  const previewUrl = hasPreview ? (isVideo ? getSafeDownloadUrl(file, 'poster') : getSafeDownloadUrl(file, 'thumb')) : undefined;
1057
+ const isSelected = selectedIds.has(file.id);
919
1058
  return {
920
1059
  id: file.id,
921
1060
  image: previewUrl,
@@ -929,7 +1068,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
929
1068
  showChevron: false,
930
1069
  dense: true,
931
1070
  multiRow: !!file.metadata?.description,
932
- customContent: (
1071
+ selected: selectMode && isSelected,
1072
+ // Hide action buttons when selecting
1073
+ customContent: !selectMode ? (
933
1074
  <View style={styles.groupedActions}>
934
1075
  {(isImage || isVideo || file.contentType.includes('pdf')) && (
935
1076
  <TouchableOpacity
@@ -957,15 +1098,15 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
957
1098
  )}
958
1099
  </TouchableOpacity>
959
1100
  </View>
960
- ),
1101
+ ) : undefined,
961
1102
  customContentBelow: file.metadata?.description ? (
962
1103
  <Text style={[styles.groupedDescription, { color: themeStyles.isDarkTheme ? '#AAAAAA' : '#666666' }]} numberOfLines={2}>
963
1104
  {file.metadata.description}
964
1105
  </Text>
965
1106
  ) : undefined,
966
- } as any; // GroupedSectionItem shape
1107
+ } as any;
967
1108
  });
968
- }, [filteredFiles, theme, themeStyles, deleting, handleFileDownload, handleFileDelete, handleFileOpen, getSafeDownloadUrl]);
1109
+ }, [filteredFiles, theme, themeStyles, deleting, handleFileDownload, handleFileDelete, handleFileOpen, getSafeDownloadUrl, selectMode, selectedIds]);
969
1110
 
970
1111
  const renderPhotoItem = (photo: FileMetadata, index: number) => {
971
1112
  const downloadUrl = getSafeDownloadUrl(photo, 'thumb');
@@ -1056,6 +1197,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1056
1197
  />
1057
1198
  }
1058
1199
  showsVerticalScrollIndicator={false}
1200
+ onScroll={({ nativeEvent }) => {
1201
+ const { layoutMeasurement, contentOffset, contentSize } = nativeEvent;
1202
+ const distanceFromBottom = contentSize.height - (contentOffset.y + layoutMeasurement.height);
1203
+ if (distanceFromBottom < 200 && !paging.loadingMore && paging.hasMore) {
1204
+ loadFiles('more');
1205
+ }
1206
+ }}
1207
+ scrollEventThrottle={250}
1059
1208
  >
1060
1209
  {loadingDimensions && (
1061
1210
  <View style={styles.dimensionsLoadingIndicator}>
@@ -1094,129 +1243,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1094
1243
  containerWidth
1095
1244
  ]);
1096
1245
 
1097
- // Separate component for the photo grid to optimize rendering
1098
- const JustifiedPhotoGrid = React.memo(({
1099
- photos,
1100
- photoDimensions,
1101
- loadPhotoDimensions,
1102
- createJustifiedRows,
1103
- renderJustifiedPhotoItem,
1104
- renderSimplePhotoItem,
1105
- textColor,
1106
- containerWidth
1107
- }: {
1108
- photos: FileMetadata[];
1109
- photoDimensions: { [key: string]: { width: number, height: number } };
1110
- loadPhotoDimensions: (photos: FileMetadata[]) => Promise<void>;
1111
- createJustifiedRows: (photos: FileMetadata[], containerWidth: number) => FileMetadata[][];
1112
- renderJustifiedPhotoItem: (photo: FileMetadata, width: number, height: number, isLast: boolean) => React.ReactElement;
1113
- renderSimplePhotoItem: (photo: FileMetadata, index: number) => React.ReactElement;
1114
- textColor: string;
1115
- containerWidth: number;
1116
- }) => {
1117
- // Load dimensions for new photos
1118
- React.useEffect(() => {
1119
- loadPhotoDimensions(photos);
1120
- // Depend only on photo IDs to avoid re-running from dimension state changes
1121
- // eslint-disable-next-line react-hooks/exhaustive-deps
1122
- }, [photos.map(p => p.id).join(',')]);
1123
-
1124
- // Group photos by date
1125
- const photosByDate = React.useMemo(() => {
1126
- return photos.reduce((groups: { [key: string]: FileMetadata[] }, photo) => {
1127
- const date = new Date(photo.uploadDate).toDateString();
1128
- if (!groups[date]) {
1129
- groups[date] = [];
1130
- }
1131
- groups[date].push(photo);
1132
- return groups;
1133
- }, {});
1134
- }, [photos]);
1135
-
1136
- const sortedDates = React.useMemo(() => {
1137
- return Object.keys(photosByDate).sort((a, b) =>
1138
- new Date(b).getTime() - new Date(a).getTime()
1139
- );
1140
- }, [photosByDate]);
1141
-
1142
- return (
1143
- <>
1144
- {sortedDates.map(date => {
1145
- const dayPhotos = photosByDate[date];
1146
- const justifiedRows = createJustifiedRows(dayPhotos, containerWidth);
1147
-
1148
- return (
1149
- <View key={date} style={styles.photoDateSection}>
1150
- <Text style={[styles.photoDateHeader, { color: themeStyles.textColor }]}>
1151
- {new Date(date).toLocaleDateString('en-US', {
1152
- weekday: 'long',
1153
- year: 'numeric',
1154
- month: 'long',
1155
- day: 'numeric'
1156
- })}
1157
- </Text>
1158
- <View style={styles.justifiedPhotoGrid}>
1159
- {justifiedRows.map((row, rowIndex) => {
1160
- // Calculate row height based on available width
1161
- const gap = 4;
1162
- let totalAspectRatio = 0;
1163
-
1164
- // Calculate total aspect ratio for this row
1165
- row.forEach(photo => {
1166
- const dimensions = photoDimensions[photo.id];
1167
- const aspectRatio = dimensions ?
1168
- (dimensions.width / dimensions.height) :
1169
- 1.33; // Default 4:3 ratio
1170
- totalAspectRatio += aspectRatio;
1171
- });
1172
-
1173
- // Calculate the height that makes the row fill the available width
1174
- // Account for photoScrollContainer padding (32px total) and gaps between photos
1175
- const scrollContainerPadding = 32;
1176
- const availableWidth = containerWidth - scrollContainerPadding - (gap * (row.length - 1));
1177
- const calculatedHeight = availableWidth / totalAspectRatio;
1178
-
1179
- // Clamp height for visual consistency
1180
- const rowHeight = Math.max(120, Math.min(calculatedHeight, 300));
1181
-
1182
- return (
1183
- <View
1184
- key={`row-${rowIndex}`}
1185
- style={[
1186
- styles.justifiedPhotoRow,
1187
- {
1188
- height: rowHeight,
1189
- maxWidth: containerWidth - 32, // Account for scroll container padding
1190
- gap: 4, // Add horizontal gap between photos in row
1191
- }
1192
- ]}
1193
- >
1194
- {row.map((photo, photoIndex) => {
1195
- const dimensions = photoDimensions[photo.id];
1196
- const aspectRatio = dimensions ?
1197
- (dimensions.width / dimensions.height) :
1198
- 1.33; // Default 4:3 ratio
1199
-
1200
- const photoWidth = rowHeight * aspectRatio;
1201
- const isLast = photoIndex === row.length - 1;
1202
-
1203
- return renderJustifiedPhotoItem(
1204
- photo,
1205
- photoWidth,
1206
- rowHeight,
1207
- isLast
1208
- );
1209
- })}
1210
- </View>
1211
- );
1212
- })}
1213
- </View>
1214
- </View>
1215
- );
1216
- })}
1217
- </>
1218
- );
1219
- });
1246
+ // Inline justified grid removed (moved to components/photogrid/JustifiedPhotoGrid.tsx)
1220
1247
 
1221
1248
  const renderFileDetailsModal = () => {
1222
1249
  const backgroundColor = themeStyles.backgroundColor;
@@ -1641,7 +1668,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1641
1668
  }
1642
1669
 
1643
1670
  // If a file is opened, show the file viewer
1644
- if (openedFile) {
1671
+ if (!selectMode && openedFile) {
1645
1672
  return (
1646
1673
  <>
1647
1674
  {renderFileViewer()}
@@ -1664,8 +1691,22 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1664
1691
  } : {})}
1665
1692
  >
1666
1693
  <Header
1667
- title={viewMode === 'photos' ? 'Photos' : 'File Management'}
1668
- subtitle={`${filteredFiles.length} ${filteredFiles.length === 1 ? 'item' : 'items'}`}
1694
+ title={selectMode ? (multiSelect ? `${selectedIds.size}${maxSelection ? '/' + maxSelection : ''} Selected` : 'Select a File') : (viewMode === 'photos' ? 'Photos' : 'File Management')}
1695
+ subtitle={selectMode ? (multiSelect ? `${filteredFiles.length} available` : 'Tap to select') : `${filteredFiles.length} ${filteredFiles.length === 1 ? 'item' : 'items'}`}
1696
+ rightActions={selectMode && multiSelect ? [
1697
+ {
1698
+ key: 'clear',
1699
+ text: 'Clear',
1700
+ onPress: () => setSelectedIds(new Set()),
1701
+ disabled: selectedIds.size === 0,
1702
+ },
1703
+ {
1704
+ key: 'confirm',
1705
+ text: 'Confirm',
1706
+ onPress: confirmMultiSelection,
1707
+ disabled: selectedIds.size === 0,
1708
+ }
1709
+ ] : undefined}
1669
1710
  onBack={onClose || goBack}
1670
1711
  theme={theme}
1671
1712
  showBackButton
@@ -1710,7 +1751,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1710
1751
  />
1711
1752
  </TouchableOpacity>
1712
1753
  </View>
1713
- {user?.id === targetUserId && (
1754
+ {user?.id === targetUserId && (!selectMode || (selectMode && allowUploadInSelectMode)) && (
1714
1755
  <TouchableOpacity
1715
1756
  style={[styles.uploadButton, { backgroundColor: themeStyles.primaryColor }]}
1716
1757
  onPress={handleFileUpload}
@@ -1808,6 +1849,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1808
1849
  tintColor={themeStyles.primaryColor}
1809
1850
  />
1810
1851
  }
1852
+ onScroll={({ nativeEvent }) => {
1853
+ const { layoutMeasurement, contentOffset, contentSize } = nativeEvent;
1854
+ const distanceFromBottom = contentSize.height - (contentOffset.y + layoutMeasurement.height);
1855
+ if (distanceFromBottom < 200 && !paging.loadingMore && paging.hasMore) {
1856
+ loadFiles('more');
1857
+ }
1858
+ }}
1859
+ scrollEventThrottle={250}
1811
1860
  >
1812
1861
  {filteredFiles.length === 0 && searchQuery.length > 0 ? (
1813
1862
  <View style={styles.emptyState}>
@@ -1825,15 +1874,23 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1825
1874
  </TouchableOpacity>
1826
1875
  </View>
1827
1876
  ) : filteredFiles.length === 0 ? renderEmptyState() : (
1828
- <GroupedSection items={groupedFileItems} theme={theme as 'light' | 'dark'} />
1877
+ <>
1878
+ <GroupedSection items={groupedFileItems} theme={theme as 'light' | 'dark'} />
1879
+ {paging.loadingMore && (
1880
+ <View style={styles.loadingMoreBar}>
1881
+ <ActivityIndicator size="small" color={themeStyles.primaryColor} />
1882
+ <Text style={[styles.loadingMoreText, { color: themeStyles.textColor }]}>Loading more...</Text>
1883
+ </View>
1884
+ )}
1885
+ </>
1829
1886
  )}
1830
1887
  </ScrollView>
1831
1888
  )}
1832
1889
 
1833
- {renderFileDetailsModal()}
1890
+ {!selectMode && renderFileDetailsModal()}
1834
1891
 
1835
1892
  {/* Uploading banner overlay */}
1836
- {uploading && (
1893
+ {!selectMode && uploading && (
1837
1894
  <View pointerEvents="none" style={styles.uploadBannerContainer}>
1838
1895
  <View style={[styles.uploadBanner, { backgroundColor: themeStyles.isDarkTheme ? '#222831EE' : '#FFFFFFEE', borderColor: themeStyles.borderColor }]}>
1839
1896
  <Ionicons name="cloud-upload" size={18} color={themeStyles.primaryColor} />
@@ -1847,6 +1904,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1847
1904
  </View>
1848
1905
  )}
1849
1906
 
1907
+ {/* Selection bar removed; actions are now in header */}
1908
+ {/* Global loadingMore bar removed; now inline in scroll areas */}
1909
+
1850
1910
  {/* Drag and Drop Overlay */}
1851
1911
  {isDragging && Platform.OS === 'web' && (
1852
1912
  <View style={styles.dragDropOverlay}>
@@ -1869,6 +1929,48 @@ const styles = StyleSheet.create({
1869
1929
  container: {
1870
1930
  flex: 1,
1871
1931
  },
1932
+ selectionBadge: {
1933
+ position: 'absolute',
1934
+ top: 4,
1935
+ right: 4,
1936
+ backgroundColor: 'rgba(0,0,0,0.4)',
1937
+ borderRadius: 12,
1938
+ padding: 2,
1939
+ },
1940
+ selectionBar: {
1941
+ position: 'absolute',
1942
+ left: 0,
1943
+ right: 0,
1944
+ bottom: 0,
1945
+ flexDirection: 'row',
1946
+ justifyContent: 'space-between',
1947
+ padding: 12,
1948
+ backgroundColor: 'rgba(0,0,0,0.55)',
1949
+ gap: 12,
1950
+ },
1951
+ selectionBarButton: {
1952
+ flex: 1,
1953
+ paddingVertical: 14,
1954
+ borderRadius: 10,
1955
+ alignItems: 'center',
1956
+ justifyContent: 'center',
1957
+ },
1958
+ selectionBarButtonText: {
1959
+ color: '#FFFFFF',
1960
+ fontSize: 15,
1961
+ fontWeight: '600',
1962
+ },
1963
+ loadingMoreBar: {
1964
+ flexDirection: 'row',
1965
+ alignItems: 'center',
1966
+ justifyContent: 'center',
1967
+ paddingVertical: 12,
1968
+ gap: 8,
1969
+ },
1970
+ loadingMoreText: {
1971
+ fontSize: 13,
1972
+ fontWeight: '500',
1973
+ },
1872
1974
  dragOverlay: {
1873
1975
  backgroundColor: 'rgba(0, 122, 255, 0.06)',
1874
1976
  borderWidth: 1,
@@ -2026,6 +2128,7 @@ const styles = StyleSheet.create({
2026
2128
  },
2027
2129
  scrollView: {
2028
2130
  flex: 1,
2131
+ backgroundColor: '#e5f1ff',
2029
2132
  },
2030
2133
  scrollContainer: {
2031
2134
  padding: 12,