@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
@@ -10,7 +10,10 @@ import { toast } from '../../lib/sonner';
10
10
  import { Ionicons } from '@expo/vector-icons';
11
11
  import { useFileStore, useFiles, useUploading as useUploadingStore, useUploadAggregateProgress, useDeleting as useDeletingStore } from '../stores/fileStore';
12
12
  import Header from '../components/Header';
13
+ import JustifiedPhotoGrid from '../components/photogrid/JustifiedPhotoGrid';
13
14
  import { GroupedSection } from '../components';
15
+
16
+ // Exporting props & callback types so external callers (e.g. showBottomSheet config objects) can annotate
14
17
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
15
18
  // Add this helper function near the top (after imports):
16
19
  async function uploadFileRaw(file, userId, oxyServices) {
@@ -22,7 +25,17 @@ const FileManagementScreen = ({
22
25
  goBack,
23
26
  navigate,
24
27
  userId,
25
- containerWidth = 400 // Fallback for when not provided by the router
28
+ containerWidth = 400,
29
+ // Fallback for when not provided by the router
30
+ selectMode = false,
31
+ multiSelect = false,
32
+ onSelect,
33
+ onConfirmSelection,
34
+ initialSelectedIds = [],
35
+ maxSelection,
36
+ disabledMimeTypes = [],
37
+ afterSelect = 'close',
38
+ allowUploadInSelectMode = true
26
39
  }) => {
27
40
  const {
28
41
  user,
@@ -46,8 +59,16 @@ const FileManagementScreen = ({
46
59
  const deleting = useDeletingStore();
47
60
  const [loading, setLoading] = useState(true);
48
61
  const [refreshing, setRefreshing] = useState(false);
62
+ const [paging, setPaging] = useState({
63
+ offset: 0,
64
+ limit: 40,
65
+ total: 0,
66
+ hasMore: true,
67
+ loadingMore: false
68
+ });
49
69
  const [selectedFile, setSelectedFile] = useState(null);
50
70
  const [showFileDetails, setShowFileDetails] = useState(false);
71
+ // In selectMode we never open the detailed viewer
51
72
  const [openedFile, setOpenedFile] = useState(null);
52
73
  const [fileContent, setFileContent] = useState(null);
53
74
  const [loadingFileContent, setLoadingFileContent] = useState(false);
@@ -72,6 +93,56 @@ const FileManagementScreen = ({
72
93
  const [hoveredPreview, setHoveredPreview] = useState(null);
73
94
  const uploadStartRef = useRef(null);
74
95
  const MIN_BANNER_MS = 600;
96
+ // Selection state
97
+ const [selectedIds, setSelectedIds] = useState(new Set(initialSelectedIds));
98
+ useEffect(() => {
99
+ if (initialSelectedIds && initialSelectedIds.length) {
100
+ setSelectedIds(new Set(initialSelectedIds));
101
+ }
102
+ }, [initialSelectedIds]);
103
+ const toggleSelect = useCallback(file => {
104
+ if (!selectMode) return;
105
+ if (disabledMimeTypes.length) {
106
+ const blocked = disabledMimeTypes.some(mt => file.contentType === mt || file.contentType.startsWith(mt.endsWith('/') ? mt : mt + '/'));
107
+ if (blocked) {
108
+ toast.error('This file type cannot be selected');
109
+ return;
110
+ }
111
+ }
112
+ if (!multiSelect) {
113
+ onSelect?.(file);
114
+ if (afterSelect === 'back') {
115
+ goBack?.();
116
+ } else if (afterSelect === 'close') {
117
+ onClose?.();
118
+ }
119
+ return;
120
+ }
121
+ setSelectedIds(prev => {
122
+ const next = new Set(prev);
123
+ const already = next.has(file.id);
124
+ if (!already) {
125
+ if (maxSelection && next.size >= maxSelection) {
126
+ toast.error(`You can select up to ${maxSelection}`);
127
+ return prev;
128
+ }
129
+ next.add(file.id);
130
+ } else {
131
+ next.delete(file.id);
132
+ }
133
+ return next;
134
+ });
135
+ }, [selectMode, multiSelect, onSelect, onClose, goBack, disabledMimeTypes, maxSelection, afterSelect]);
136
+ const confirmMultiSelection = useCallback(() => {
137
+ if (!selectMode || !multiSelect) return;
138
+ const map = {};
139
+ files.forEach(f => {
140
+ map[f.id] = f;
141
+ });
142
+ const chosen = Array.from(selectedIds).map(id => map[id]).filter(Boolean);
143
+ onConfirmSelection?.(chosen);
144
+ onClose?.();
145
+ }, [selectMode, multiSelect, selectedIds, files, onConfirmSelection, onClose]);
75
146
  const endUpload = useCallback(() => {
76
147
  const started = uploadStartRef.current;
77
148
  const elapsed = started ? Date.now() - started : MIN_BANNER_MS;
@@ -144,8 +215,21 @@ const FileManagementScreen = ({
144
215
  setRefreshing(true);
145
216
  } else if (mode === 'initial') {
146
217
  setLoading(true);
218
+ setPaging(p => ({
219
+ ...p,
220
+ offset: 0,
221
+ hasMore: true
222
+ }));
223
+ } else if (mode === 'more') {
224
+ // Prevent duplicate fetches
225
+ setPaging(p => ({
226
+ ...p,
227
+ loadingMore: true
228
+ }));
147
229
  }
148
- const response = await oxyServices.listUserFiles();
230
+ const currentPaging = mode === 'more' ? prevPagingRef.current ?? paging : paging;
231
+ const effectiveOffset = mode === 'more' ? currentPaging.offset + currentPaging.limit : 0;
232
+ const response = await oxyServices.listUserFiles(currentPaging.limit, effectiveOffset);
149
233
  const assets = (response.files || []).map(f => ({
150
234
  id: f.id,
151
235
  filename: f.originalName || f.sha256,
@@ -156,18 +240,48 @@ const FileManagementScreen = ({
156
240
  metadata: f.metadata || {},
157
241
  variants: f.variants || []
158
242
  }));
159
- // Merge to preserve existing order & allow incremental updates
160
- useFileStore.getState().setFiles(assets, {
161
- merge: true
162
- });
243
+ if (mode === 'more') {
244
+ // append
245
+ useFileStore.getState().setFiles(assets, {
246
+ merge: true
247
+ });
248
+ setPaging(p => ({
249
+ ...p,
250
+ offset: effectiveOffset,
251
+ total: response.total || effectiveOffset + assets.length,
252
+ hasMore: response.hasMore,
253
+ loadingMore: false
254
+ }));
255
+ } else {
256
+ useFileStore.getState().setFiles(assets, {
257
+ merge: false
258
+ });
259
+ setPaging(p => ({
260
+ ...p,
261
+ offset: 0,
262
+ total: response.total || assets.length,
263
+ hasMore: response.hasMore,
264
+ loadingMore: false
265
+ }));
266
+ }
163
267
  } catch (error) {
164
268
  console.error('Failed to load files:', error);
165
269
  toast.error(error.message || 'Failed to load files');
166
270
  } finally {
167
271
  setLoading(false);
168
272
  setRefreshing(false);
273
+ setPaging(p => ({
274
+ ...p,
275
+ loadingMore: false
276
+ }));
169
277
  }
170
- }, [targetUserId, oxyServices]);
278
+ }, [targetUserId, oxyServices, paging]);
279
+
280
+ // Keep a ref to avoid stale closure when calculating next offset
281
+ const prevPagingRef = useRef(paging);
282
+ useEffect(() => {
283
+ prevPagingRef.current = paging;
284
+ }, [paging]);
171
285
 
172
286
  // (removed effect; filteredFiles is memoized)
173
287
 
@@ -591,6 +705,10 @@ const FileManagementScreen = ({
591
705
  return 'document-outline';
592
706
  };
593
707
  const handleFileOpen = async file => {
708
+ if (selectMode) {
709
+ toggleSelect(file);
710
+ return;
711
+ }
594
712
  try {
595
713
  setLoadingFileContent(true);
596
714
  setOpenedFile(file);
@@ -654,13 +772,17 @@ const FileManagementScreen = ({
654
772
  style: [styles.simplePhotoItem, {
655
773
  width: itemWidth,
656
774
  height: itemWidth,
657
- marginRight: (index + 1) % itemsPerRow === 0 ? 0 : 4
775
+ marginRight: (index + 1) % itemsPerRow === 0 ? 0 : 4,
776
+ ...(selectMode && selectedIds.has(photo.id) ? {
777
+ borderWidth: 2,
778
+ borderColor: themeStyles.primaryColor
779
+ } : {})
658
780
  }],
659
781
  onPress: () => handleFileOpen(photo),
660
782
  activeOpacity: 0.8,
661
- children: /*#__PURE__*/_jsx(View, {
783
+ children: /*#__PURE__*/_jsxs(View, {
662
784
  style: styles.simplePhotoContainer,
663
- children: /*#__PURE__*/_jsx(ExpoImage, {
785
+ children: [/*#__PURE__*/_jsx(ExpoImage, {
664
786
  source: {
665
787
  uri: downloadUrl
666
788
  },
@@ -672,22 +794,36 @@ const FileManagementScreen = ({
672
794
  console.error('Photo failed to load:', e?.nativeEvent ?? e);
673
795
  },
674
796
  accessibilityLabel: photo.filename
675
- })
797
+ }), selectMode && /*#__PURE__*/_jsx(View, {
798
+ style: styles.selectionBadge,
799
+ children: /*#__PURE__*/_jsx(Ionicons, {
800
+ name: selectedIds.has(photo.id) ? 'checkmark-circle' : 'ellipse-outline',
801
+ size: 20,
802
+ color: selectedIds.has(photo.id) ? themeStyles.primaryColor : themeStyles.textColor
803
+ })
804
+ })]
676
805
  })
677
806
  }, photo.id);
678
- }, [oxyServices, containerWidth]);
807
+ }, [oxyServices, containerWidth, selectMode, selectedIds, themeStyles.primaryColor, themeStyles.textColor]);
679
808
  const renderJustifiedPhotoItem = useCallback((photo, width, height, isLast) => {
680
809
  const downloadUrl = getSafeDownloadUrl(photo, 'thumb');
681
810
  return /*#__PURE__*/_jsx(TouchableOpacity, {
682
811
  style: [styles.justifiedPhotoItem, {
683
812
  width,
684
- height
813
+ height,
814
+ ...(selectMode && selectedIds.has(photo.id) ? {
815
+ borderWidth: 2,
816
+ borderColor: themeStyles.primaryColor
817
+ } : {}),
818
+ ...(selectMode && multiSelect && selectedIds.size > 0 && !selectedIds.has(photo.id) ? {
819
+ opacity: 0.4
820
+ } : {})
685
821
  }],
686
822
  onPress: () => handleFileOpen(photo),
687
823
  activeOpacity: 0.8,
688
- children: /*#__PURE__*/_jsx(View, {
824
+ children: /*#__PURE__*/_jsxs(View, {
689
825
  style: styles.justifiedPhotoContainer,
690
- children: /*#__PURE__*/_jsx(ExpoImage, {
826
+ children: [/*#__PURE__*/_jsx(ExpoImage, {
691
827
  source: {
692
828
  uri: downloadUrl
693
829
  },
@@ -699,10 +835,17 @@ const FileManagementScreen = ({
699
835
  console.error('Photo failed to load:', e?.nativeEvent ?? e);
700
836
  },
701
837
  accessibilityLabel: photo.filename
702
- })
838
+ }), selectMode && /*#__PURE__*/_jsx(View, {
839
+ style: styles.selectionBadge,
840
+ children: /*#__PURE__*/_jsx(Ionicons, {
841
+ name: selectedIds.has(photo.id) ? 'checkmark-circle' : 'ellipse-outline',
842
+ size: 20,
843
+ color: selectedIds.has(photo.id) ? themeStyles.primaryColor : themeStyles.textColor
844
+ })
845
+ })]
703
846
  })
704
847
  }, photo.id);
705
- }, [oxyServices]);
848
+ }, [oxyServices, selectMode, selectedIds, multiSelect, themeStyles.primaryColor, themeStyles.textColor]);
706
849
 
707
850
  // Run initial load once per targetUserId change to avoid accidental loops
708
851
  const lastLoadedFor = useRef(undefined);
@@ -725,6 +868,9 @@ const FileManagementScreen = ({
725
868
  style: [styles.fileItem, {
726
869
  backgroundColor: themeStyles.secondaryBackgroundColor,
727
870
  borderColor
871
+ }, selectMode && selectedIds.has(file.id) && {
872
+ borderColor: themeStyles.primaryColor,
873
+ borderWidth: 2
728
874
  }],
729
875
  children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
730
876
  style: styles.fileContent,
@@ -795,13 +941,20 @@ const FileManagementScreen = ({
795
941
  size: 32,
796
942
  color: themeStyles.primaryColor
797
943
  })
798
- }), Platform.OS === 'web' && hoveredPreview === file.id && isImage && /*#__PURE__*/_jsx(View, {
944
+ }), !selectMode && Platform.OS === 'web' && hoveredPreview === file.id && isImage && /*#__PURE__*/_jsx(View, {
799
945
  style: styles.previewOverlay,
800
946
  children: /*#__PURE__*/_jsx(Ionicons, {
801
947
  name: "eye",
802
948
  size: 24,
803
949
  color: "#FFFFFF"
804
950
  })
951
+ }), selectMode && /*#__PURE__*/_jsx(View, {
952
+ style: styles.selectionBadge,
953
+ children: /*#__PURE__*/_jsx(Ionicons, {
954
+ name: selectedIds.has(file.id) ? 'checkmark-circle' : 'ellipse-outline',
955
+ size: 22,
956
+ color: selectedIds.has(file.id) ? themeStyles.primaryColor : themeStyles.textColor
957
+ })
805
958
  })]
806
959
  }) : /*#__PURE__*/_jsx(View, {
807
960
  style: styles.fileIconContainer,
@@ -832,7 +985,7 @@ const FileManagementScreen = ({
832
985
  children: file.metadata.description
833
986
  })]
834
987
  })]
835
- }), /*#__PURE__*/_jsxs(View, {
988
+ }), !selectMode && /*#__PURE__*/_jsxs(View, {
836
989
  style: styles.fileActions,
837
990
  children: [hasPreview && /*#__PURE__*/_jsx(TouchableOpacity, {
838
991
  style: [styles.actionButton, {
@@ -877,12 +1030,12 @@ const FileManagementScreen = ({
877
1030
 
878
1031
  // GroupedSection-based file items (for 'all' view) replacing legacy flat list look
879
1032
  const groupedFileItems = useMemo(() => {
880
- return filteredFiles.filter(f => true) // placeholder for future filtering
881
- .sort((a, b) => new Date(b.uploadDate).getTime() - new Date(a.uploadDate).getTime()).map(file => {
1033
+ return filteredFiles.filter(f => true).sort((a, b) => new Date(b.uploadDate).getTime() - new Date(a.uploadDate).getTime()).map(file => {
882
1034
  const isImage = file.contentType.startsWith('image/');
883
1035
  const isVideo = file.contentType.startsWith('video/');
884
1036
  const hasPreview = isImage || isVideo;
885
1037
  const previewUrl = hasPreview ? isVideo ? getSafeDownloadUrl(file, 'poster') : getSafeDownloadUrl(file, 'thumb') : undefined;
1038
+ const isSelected = selectedIds.has(file.id);
886
1039
  return {
887
1040
  id: file.id,
888
1041
  image: previewUrl,
@@ -896,7 +1049,9 @@ const FileManagementScreen = ({
896
1049
  showChevron: false,
897
1050
  dense: true,
898
1051
  multiRow: !!file.metadata?.description,
899
- customContent: /*#__PURE__*/_jsxs(View, {
1052
+ selected: selectMode && isSelected,
1053
+ // Hide action buttons when selecting
1054
+ customContent: !selectMode ? /*#__PURE__*/_jsxs(View, {
900
1055
  style: styles.groupedActions,
901
1056
  children: [(isImage || isVideo || file.contentType.includes('pdf')) && /*#__PURE__*/_jsx(TouchableOpacity, {
902
1057
  style: [styles.groupedActionBtn, {
@@ -933,7 +1088,7 @@ const FileManagementScreen = ({
933
1088
  color: themeStyles.dangerColor
934
1089
  })
935
1090
  })]
936
- }),
1091
+ }) : undefined,
937
1092
  customContentBelow: file.metadata?.description ? /*#__PURE__*/_jsx(Text, {
938
1093
  style: [styles.groupedDescription, {
939
1094
  color: themeStyles.isDarkTheme ? '#AAAAAA' : '#666666'
@@ -941,9 +1096,9 @@ const FileManagementScreen = ({
941
1096
  numberOfLines: 2,
942
1097
  children: file.metadata.description
943
1098
  }) : undefined
944
- }; // GroupedSectionItem shape
1099
+ };
945
1100
  });
946
- }, [filteredFiles, theme, themeStyles, deleting, handleFileDownload, handleFileDelete, handleFileOpen, getSafeDownloadUrl]);
1101
+ }, [filteredFiles, theme, themeStyles, deleting, handleFileDownload, handleFileDelete, handleFileOpen, getSafeDownloadUrl, selectMode, selectedIds]);
947
1102
  const renderPhotoItem = (photo, index) => {
948
1103
  const downloadUrl = getSafeDownloadUrl(photo, 'thumb');
949
1104
 
@@ -1032,6 +1187,20 @@ const FileManagementScreen = ({
1032
1187
  tintColor: themeStyles.primaryColor
1033
1188
  }),
1034
1189
  showsVerticalScrollIndicator: false,
1190
+ onScroll: ({
1191
+ nativeEvent
1192
+ }) => {
1193
+ const {
1194
+ layoutMeasurement,
1195
+ contentOffset,
1196
+ contentSize
1197
+ } = nativeEvent;
1198
+ const distanceFromBottom = contentSize.height - (contentOffset.y + layoutMeasurement.height);
1199
+ if (distanceFromBottom < 200 && !paging.loadingMore && paging.hasMore) {
1200
+ loadFiles('more');
1201
+ }
1202
+ },
1203
+ scrollEventThrottle: 250,
1035
1204
  children: [loadingDimensions && /*#__PURE__*/_jsxs(View, {
1036
1205
  style: styles.dimensionsLoadingIndicator,
1037
1206
  children: [/*#__PURE__*/_jsx(ActivityIndicator, {
@@ -1056,98 +1225,8 @@ const FileManagementScreen = ({
1056
1225
  });
1057
1226
  }, [filteredFiles, themeStyles, user?.id, targetUserId, uploading, handleFileUpload, refreshing, loadFiles, loadingDimensions, photoDimensions, loadPhotoDimensions, createJustifiedRows, renderJustifiedPhotoItem, renderPhotoItem, containerWidth]);
1058
1227
 
1059
- // Separate component for the photo grid to optimize rendering
1060
- const JustifiedPhotoGrid = /*#__PURE__*/React.memo(({
1061
- photos,
1062
- photoDimensions,
1063
- loadPhotoDimensions,
1064
- createJustifiedRows,
1065
- renderJustifiedPhotoItem,
1066
- renderSimplePhotoItem,
1067
- textColor,
1068
- containerWidth
1069
- }) => {
1070
- // Load dimensions for new photos
1071
- React.useEffect(() => {
1072
- loadPhotoDimensions(photos);
1073
- // Depend only on photo IDs to avoid re-running from dimension state changes
1074
- // eslint-disable-next-line react-hooks/exhaustive-deps
1075
- }, [photos.map(p => p.id).join(',')]);
1076
-
1077
- // Group photos by date
1078
- const photosByDate = React.useMemo(() => {
1079
- return photos.reduce((groups, photo) => {
1080
- const date = new Date(photo.uploadDate).toDateString();
1081
- if (!groups[date]) {
1082
- groups[date] = [];
1083
- }
1084
- groups[date].push(photo);
1085
- return groups;
1086
- }, {});
1087
- }, [photos]);
1088
- const sortedDates = React.useMemo(() => {
1089
- return Object.keys(photosByDate).sort((a, b) => new Date(b).getTime() - new Date(a).getTime());
1090
- }, [photosByDate]);
1091
- return /*#__PURE__*/_jsx(_Fragment, {
1092
- children: sortedDates.map(date => {
1093
- const dayPhotos = photosByDate[date];
1094
- const justifiedRows = createJustifiedRows(dayPhotos, containerWidth);
1095
- return /*#__PURE__*/_jsxs(View, {
1096
- style: styles.photoDateSection,
1097
- children: [/*#__PURE__*/_jsx(Text, {
1098
- style: [styles.photoDateHeader, {
1099
- color: themeStyles.textColor
1100
- }],
1101
- children: new Date(date).toLocaleDateString('en-US', {
1102
- weekday: 'long',
1103
- year: 'numeric',
1104
- month: 'long',
1105
- day: 'numeric'
1106
- })
1107
- }), /*#__PURE__*/_jsx(View, {
1108
- style: styles.justifiedPhotoGrid,
1109
- children: justifiedRows.map((row, rowIndex) => {
1110
- // Calculate row height based on available width
1111
- const gap = 4;
1112
- let totalAspectRatio = 0;
1113
-
1114
- // Calculate total aspect ratio for this row
1115
- row.forEach(photo => {
1116
- const dimensions = photoDimensions[photo.id];
1117
- const aspectRatio = dimensions ? dimensions.width / dimensions.height : 1.33; // Default 4:3 ratio
1118
- totalAspectRatio += aspectRatio;
1119
- });
1120
-
1121
- // Calculate the height that makes the row fill the available width
1122
- // Account for photoScrollContainer padding (32px total) and gaps between photos
1123
- const scrollContainerPadding = 32;
1124
- const availableWidth = containerWidth - scrollContainerPadding - gap * (row.length - 1);
1125
- const calculatedHeight = availableWidth / totalAspectRatio;
1228
+ // Inline justified grid removed (moved to components/photogrid/JustifiedPhotoGrid.tsx)
1126
1229
 
1127
- // Clamp height for visual consistency
1128
- const rowHeight = Math.max(120, Math.min(calculatedHeight, 300));
1129
- return /*#__PURE__*/_jsx(View, {
1130
- style: [styles.justifiedPhotoRow, {
1131
- height: rowHeight,
1132
- maxWidth: containerWidth - 32,
1133
- // Account for scroll container padding
1134
- gap: 4 // Add horizontal gap between photos in row
1135
- }],
1136
- children: row.map((photo, photoIndex) => {
1137
- const dimensions = photoDimensions[photo.id];
1138
- const aspectRatio = dimensions ? dimensions.width / dimensions.height : 1.33; // Default 4:3 ratio
1139
-
1140
- const photoWidth = rowHeight * aspectRatio;
1141
- const isLast = photoIndex === row.length - 1;
1142
- return renderJustifiedPhotoItem(photo, photoWidth, rowHeight, isLast);
1143
- })
1144
- }, `row-${rowIndex}`);
1145
- })
1146
- })]
1147
- }, date);
1148
- })
1149
- });
1150
- });
1151
1230
  const renderFileDetailsModal = () => {
1152
1231
  const backgroundColor = themeStyles.backgroundColor;
1153
1232
  const borderColor = themeStyles.borderColor;
@@ -1683,7 +1762,7 @@ const FileManagementScreen = ({
1683
1762
  }
1684
1763
 
1685
1764
  // If a file is opened, show the file viewer
1686
- if (openedFile) {
1765
+ if (!selectMode && openedFile) {
1687
1766
  return /*#__PURE__*/_jsxs(_Fragment, {
1688
1767
  children: [renderFileViewer(), renderFileDetailsModal()]
1689
1768
  });
@@ -1697,8 +1776,19 @@ const FileManagementScreen = ({
1697
1776
  onDrop: handleDrop
1698
1777
  } : {}),
1699
1778
  children: [/*#__PURE__*/_jsx(Header, {
1700
- title: viewMode === 'photos' ? 'Photos' : 'File Management',
1701
- subtitle: `${filteredFiles.length} ${filteredFiles.length === 1 ? 'item' : 'items'}`,
1779
+ title: selectMode ? multiSelect ? `${selectedIds.size}${maxSelection ? '/' + maxSelection : ''} Selected` : 'Select a File' : viewMode === 'photos' ? 'Photos' : 'File Management',
1780
+ subtitle: selectMode ? multiSelect ? `${filteredFiles.length} available` : 'Tap to select' : `${filteredFiles.length} ${filteredFiles.length === 1 ? 'item' : 'items'}`,
1781
+ rightActions: selectMode && multiSelect ? [{
1782
+ key: 'clear',
1783
+ text: 'Clear',
1784
+ onPress: () => setSelectedIds(new Set()),
1785
+ disabled: selectedIds.size === 0
1786
+ }, {
1787
+ key: 'confirm',
1788
+ text: 'Confirm',
1789
+ onPress: confirmMultiSelection,
1790
+ disabled: selectedIds.size === 0
1791
+ }] : undefined,
1702
1792
  onBack: onClose || goBack,
1703
1793
  theme: theme,
1704
1794
  showBackButton: true,
@@ -1734,7 +1824,7 @@ const FileManagementScreen = ({
1734
1824
  color: viewMode === 'photos' ? '#FFFFFF' : themeStyles.textColor
1735
1825
  })
1736
1826
  })]
1737
- }), user?.id === targetUserId && /*#__PURE__*/_jsx(TouchableOpacity, {
1827
+ }), user?.id === targetUserId && (!selectMode || selectMode && allowUploadInSelectMode) && /*#__PURE__*/_jsx(TouchableOpacity, {
1738
1828
  style: [styles.uploadButton, {
1739
1829
  backgroundColor: themeStyles.primaryColor
1740
1830
  }],
@@ -1834,6 +1924,20 @@ const FileManagementScreen = ({
1834
1924
  onRefresh: () => loadFiles('refresh'),
1835
1925
  tintColor: themeStyles.primaryColor
1836
1926
  }),
1927
+ onScroll: ({
1928
+ nativeEvent
1929
+ }) => {
1930
+ const {
1931
+ layoutMeasurement,
1932
+ contentOffset,
1933
+ contentSize
1934
+ } = nativeEvent;
1935
+ const distanceFromBottom = contentSize.height - (contentOffset.y + layoutMeasurement.height);
1936
+ if (distanceFromBottom < 200 && !paging.loadingMore && paging.hasMore) {
1937
+ loadFiles('more');
1938
+ }
1939
+ },
1940
+ scrollEventThrottle: 250,
1837
1941
  children: filteredFiles.length === 0 && searchQuery.length > 0 ? /*#__PURE__*/_jsxs(View, {
1838
1942
  style: styles.emptyState,
1839
1943
  children: [/*#__PURE__*/_jsx(Ionicons, {
@@ -1864,11 +1968,24 @@ const FileManagementScreen = ({
1864
1968
  children: "Clear Search"
1865
1969
  })]
1866
1970
  })]
1867
- }) : filteredFiles.length === 0 ? renderEmptyState() : /*#__PURE__*/_jsx(GroupedSection, {
1868
- items: groupedFileItems,
1869
- theme: theme
1971
+ }) : filteredFiles.length === 0 ? renderEmptyState() : /*#__PURE__*/_jsxs(_Fragment, {
1972
+ children: [/*#__PURE__*/_jsx(GroupedSection, {
1973
+ items: groupedFileItems,
1974
+ theme: theme
1975
+ }), paging.loadingMore && /*#__PURE__*/_jsxs(View, {
1976
+ style: styles.loadingMoreBar,
1977
+ children: [/*#__PURE__*/_jsx(ActivityIndicator, {
1978
+ size: "small",
1979
+ color: themeStyles.primaryColor
1980
+ }), /*#__PURE__*/_jsx(Text, {
1981
+ style: [styles.loadingMoreText, {
1982
+ color: themeStyles.textColor
1983
+ }],
1984
+ children: "Loading more..."
1985
+ })]
1986
+ })]
1870
1987
  })
1871
- }), renderFileDetailsModal(), uploading && /*#__PURE__*/_jsx(View, {
1988
+ }), !selectMode && renderFileDetailsModal(), !selectMode && uploading && /*#__PURE__*/_jsx(View, {
1872
1989
  pointerEvents: "none",
1873
1990
  style: styles.uploadBannerContainer,
1874
1991
  children: /*#__PURE__*/_jsxs(View, {
@@ -1921,6 +2038,48 @@ const styles = StyleSheet.create({
1921
2038
  container: {
1922
2039
  flex: 1
1923
2040
  },
2041
+ selectionBadge: {
2042
+ position: 'absolute',
2043
+ top: 4,
2044
+ right: 4,
2045
+ backgroundColor: 'rgba(0,0,0,0.4)',
2046
+ borderRadius: 12,
2047
+ padding: 2
2048
+ },
2049
+ selectionBar: {
2050
+ position: 'absolute',
2051
+ left: 0,
2052
+ right: 0,
2053
+ bottom: 0,
2054
+ flexDirection: 'row',
2055
+ justifyContent: 'space-between',
2056
+ padding: 12,
2057
+ backgroundColor: 'rgba(0,0,0,0.55)',
2058
+ gap: 12
2059
+ },
2060
+ selectionBarButton: {
2061
+ flex: 1,
2062
+ paddingVertical: 14,
2063
+ borderRadius: 10,
2064
+ alignItems: 'center',
2065
+ justifyContent: 'center'
2066
+ },
2067
+ selectionBarButtonText: {
2068
+ color: '#FFFFFF',
2069
+ fontSize: 15,
2070
+ fontWeight: '600'
2071
+ },
2072
+ loadingMoreBar: {
2073
+ flexDirection: 'row',
2074
+ alignItems: 'center',
2075
+ justifyContent: 'center',
2076
+ paddingVertical: 12,
2077
+ gap: 8
2078
+ },
2079
+ loadingMoreText: {
2080
+ fontSize: 13,
2081
+ fontWeight: '500'
2082
+ },
1924
2083
  dragOverlay: {
1925
2084
  backgroundColor: 'rgba(0, 122, 255, 0.06)',
1926
2085
  borderWidth: 1,
@@ -2081,7 +2240,8 @@ const styles = StyleSheet.create({
2081
2240
  letterSpacing: 0.2
2082
2241
  },
2083
2242
  scrollView: {
2084
- flex: 1
2243
+ flex: 1,
2244
+ backgroundColor: '#e5f1ff'
2085
2245
  },
2086
2246
  scrollContainer: {
2087
2247
  padding: 12