@oxyhq/services 22.10.0 → 22.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/ui/components/AvatarCameraBadge.js +42 -0
- package/lib/commonjs/ui/components/AvatarCameraBadge.js.map +1 -0
- package/lib/commonjs/ui/components/OxyAccountDialogScreen.js +41 -19
- package/lib/commonjs/ui/components/OxyAccountDialogScreen.js.map +1 -1
- package/lib/commonjs/ui/components/OxyAuthChooser.js +640 -168
- package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
- package/lib/commonjs/ui/components/ProfileButton.js +14 -0
- package/lib/commonjs/ui/components/ProfileButton.js.map +1 -1
- package/lib/commonjs/ui/components/ProfileSummaryCard.js +3 -23
- package/lib/commonjs/ui/components/ProfileSummaryCard.js.map +1 -1
- package/lib/commonjs/ui/components/SurfaceScreen.js +41 -5
- package/lib/commonjs/ui/components/SurfaceScreen.js.map +1 -1
- package/lib/commonjs/ui/components/oxyAuthChooserStyles.js +78 -26
- package/lib/commonjs/ui/components/oxyAuthChooserStyles.js.map +1 -1
- package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js +83 -23
- package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +73 -37
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/queryKeys.js +18 -1
- package/lib/commonjs/ui/hooks/queries/queryKeys.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/useFileQueries.js +129 -0
- package/lib/commonjs/ui/hooks/queries/useFileQueries.js.map +1 -0
- package/lib/commonjs/ui/hooks/useAvatarPicker.js +36 -48
- package/lib/commonjs/ui/hooks/useAvatarPicker.js.map +1 -1
- package/lib/commonjs/ui/hooks/useReduceMotion.js +57 -0
- package/lib/commonjs/ui/hooks/useReduceMotion.js.map +1 -0
- package/lib/commonjs/ui/hooks/useSurfaceHeader.js +1 -1
- package/lib/commonjs/ui/hooks/useSurfaceHeader.js.map +1 -1
- package/lib/commonjs/ui/navigation/accountDialogManager.js +22 -0
- package/lib/commonjs/ui/navigation/accountDialogManager.js.map +1 -1
- package/lib/commonjs/ui/navigation/bottomSheetManager.js +5 -6
- package/lib/commonjs/ui/navigation/bottomSheetManager.js.map +1 -1
- package/lib/commonjs/ui/navigation/routes.js +1 -0
- package/lib/commonjs/ui/navigation/routes.js.map +1 -1
- package/lib/commonjs/ui/navigation/surfaceNavStack.js +62 -15
- package/lib/commonjs/ui/navigation/surfaceNavStack.js.map +1 -1
- package/lib/commonjs/ui/navigation/surfaceRegistry.js +38 -17
- package/lib/commonjs/ui/navigation/surfaceRegistry.js.map +1 -1
- package/lib/commonjs/ui/navigation/surfaces.js +98 -21
- package/lib/commonjs/ui/navigation/surfaces.js.map +1 -1
- package/lib/commonjs/ui/screens/AvatarCropScreen.js +281 -279
- package/lib/commonjs/ui/screens/AvatarCropScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ChangeAvatarScreen.js +301 -0
- package/lib/commonjs/ui/screens/ChangeAvatarScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/FileManagementScreen.js +194 -431
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +16 -48
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/fileManagement/FileLibraryError.js +63 -0
- package/lib/commonjs/ui/screens/fileManagement/FileLibraryError.js.map +1 -0
- package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js +54 -41
- package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
- package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js +44 -81
- package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
- package/lib/commonjs/ui/stores/resetSessionScopedStores.js +8 -3
- package/lib/commonjs/ui/stores/resetSessionScopedStores.js.map +1 -1
- package/lib/commonjs/ui/utils/avatarUtils.js +11 -2
- package/lib/commonjs/ui/utils/avatarUtils.js.map +1 -1
- package/lib/commonjs/ui/utils/fileManagement.js +0 -79
- package/lib/commonjs/ui/utils/fileManagement.js.map +1 -1
- package/lib/module/ui/components/AvatarCameraBadge.js +40 -0
- package/lib/module/ui/components/AvatarCameraBadge.js.map +1 -0
- package/lib/module/ui/components/OxyAccountDialogScreen.js +41 -19
- package/lib/module/ui/components/OxyAccountDialogScreen.js.map +1 -1
- package/lib/module/ui/components/OxyAuthChooser.js +642 -171
- package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
- package/lib/module/ui/components/ProfileButton.js +15 -1
- package/lib/module/ui/components/ProfileButton.js.map +1 -1
- package/lib/module/ui/components/ProfileSummaryCard.js +2 -23
- package/lib/module/ui/components/ProfileSummaryCard.js.map +1 -1
- package/lib/module/ui/components/SurfaceScreen.js +43 -7
- package/lib/module/ui/components/SurfaceScreen.js.map +1 -1
- package/lib/module/ui/components/oxyAuthChooserStyles.js +78 -26
- package/lib/module/ui/components/oxyAuthChooserStyles.js.map +1 -1
- package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js +83 -25
- package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +74 -38
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/queries/queryKeys.js +16 -0
- package/lib/module/ui/hooks/queries/queryKeys.js.map +1 -1
- package/lib/module/ui/hooks/queries/useFileQueries.js +120 -0
- package/lib/module/ui/hooks/queries/useFileQueries.js.map +1 -0
- package/lib/module/ui/hooks/useAvatarPicker.js +37 -49
- package/lib/module/ui/hooks/useAvatarPicker.js.map +1 -1
- package/lib/module/ui/hooks/useReduceMotion.js +53 -0
- package/lib/module/ui/hooks/useReduceMotion.js.map +1 -0
- package/lib/module/ui/hooks/useSurfaceHeader.js +1 -1
- package/lib/module/ui/hooks/useSurfaceHeader.js.map +1 -1
- package/lib/module/ui/navigation/accountDialogManager.js +20 -0
- package/lib/module/ui/navigation/accountDialogManager.js.map +1 -1
- package/lib/module/ui/navigation/bottomSheetManager.js +6 -7
- package/lib/module/ui/navigation/bottomSheetManager.js.map +1 -1
- package/lib/module/ui/navigation/routes.js +1 -0
- package/lib/module/ui/navigation/routes.js.map +1 -1
- package/lib/module/ui/navigation/surfaceNavStack.js +62 -15
- package/lib/module/ui/navigation/surfaceNavStack.js.map +1 -1
- package/lib/module/ui/navigation/surfaceRegistry.js +38 -17
- package/lib/module/ui/navigation/surfaceRegistry.js.map +1 -1
- package/lib/module/ui/navigation/surfaces.js +97 -22
- package/lib/module/ui/navigation/surfaces.js.map +1 -1
- package/lib/module/ui/screens/AvatarCropScreen.js +283 -281
- package/lib/module/ui/screens/AvatarCropScreen.js.map +1 -1
- package/lib/module/ui/screens/ChangeAvatarScreen.js +298 -0
- package/lib/module/ui/screens/ChangeAvatarScreen.js.map +1 -0
- package/lib/module/ui/screens/FileManagementScreen.js +196 -433
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/WelcomeNewUserScreen.js +17 -49
- package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/module/ui/screens/fileManagement/FileLibraryError.js +59 -0
- package/lib/module/ui/screens/fileManagement/FileLibraryError.js.map +1 -0
- package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js +54 -42
- package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
- package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js +45 -81
- package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
- package/lib/module/ui/stores/resetSessionScopedStores.js +8 -3
- package/lib/module/ui/stores/resetSessionScopedStores.js.map +1 -1
- package/lib/module/ui/utils/avatarUtils.js +11 -2
- package/lib/module/ui/utils/avatarUtils.js.map +1 -1
- package/lib/module/ui/utils/fileManagement.js +0 -78
- package/lib/module/ui/utils/fileManagement.js.map +1 -1
- package/lib/typescript/commonjs/ui/components/AvatarCameraBadge.d.ts +4 -0
- package/lib/typescript/commonjs/ui/components/AvatarCameraBadge.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/OxyAccountDialogScreen.d.ts +2 -1
- package/lib/typescript/commonjs/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ProfileSummaryCard.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/SurfaceScreen.d.ts +1 -4
- package/lib/typescript/commonjs/ui/components/SurfaceScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts +71 -19
- package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/photogrid/JustifiedPhotoGrid.d.ts +10 -8
- package/lib/typescript/commonjs/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/queryKeys.d.ts +9 -0
- package/lib/typescript/commonjs/ui/hooks/queries/queryKeys.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/useFileQueries.d.ts +50 -0
- package/lib/typescript/commonjs/ui/hooks/queries/useFileQueries.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hooks/useAvatarPicker.d.ts +15 -10
- package/lib/typescript/commonjs/ui/hooks/useAvatarPicker.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useReduceMotion.d.ts +14 -0
- package/lib/typescript/commonjs/ui/hooks/useReduceMotion.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hooks/useSurfaceHeader.d.ts +1 -1
- package/lib/typescript/commonjs/ui/hooks/useSurfaceHeader.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts +13 -0
- package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts +4 -5
- package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/surfaceNavStack.d.ts +21 -0
- package/lib/typescript/commonjs/ui/navigation/surfaceNavStack.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts +73 -13
- package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/surfaces.d.ts +42 -11
- package/lib/typescript/commonjs/ui/navigation/surfaces.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/AvatarCropScreen.d.ts +18 -5
- package/lib/typescript/commonjs/ui/screens/AvatarCropScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/ChangeAvatarScreen.d.ts +34 -0
- package/lib/typescript/commonjs/ui/screens/ChangeAvatarScreen.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/fileManagement/FileLibraryError.d.ts +23 -0
- package/lib/typescript/commonjs/ui/screens/fileManagement/FileLibraryError.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts +4 -0
- package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.d.ts +10 -2
- package/lib/typescript/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/stores/resetSessionScopedStores.d.ts +8 -1
- package/lib/typescript/commonjs/ui/stores/resetSessionScopedStores.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/types/navigation.d.ts +2 -0
- package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/avatarUtils.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/fileManagement.d.ts +0 -48
- package/lib/typescript/commonjs/ui/utils/fileManagement.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/AvatarCameraBadge.d.ts +4 -0
- package/lib/typescript/module/ui/components/AvatarCameraBadge.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/OxyAccountDialogScreen.d.ts +2 -1
- package/lib/typescript/module/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ProfileButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ProfileSummaryCard.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/SurfaceScreen.d.ts +1 -4
- package/lib/typescript/module/ui/components/SurfaceScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts +71 -19
- package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/photogrid/JustifiedPhotoGrid.d.ts +10 -8
- package/lib/typescript/module/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/queries/queryKeys.d.ts +9 -0
- package/lib/typescript/module/ui/hooks/queries/queryKeys.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/queries/useFileQueries.d.ts +50 -0
- package/lib/typescript/module/ui/hooks/queries/useFileQueries.d.ts.map +1 -0
- package/lib/typescript/module/ui/hooks/useAvatarPicker.d.ts +15 -10
- package/lib/typescript/module/ui/hooks/useAvatarPicker.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useReduceMotion.d.ts +14 -0
- package/lib/typescript/module/ui/hooks/useReduceMotion.d.ts.map +1 -0
- package/lib/typescript/module/ui/hooks/useSurfaceHeader.d.ts +1 -1
- package/lib/typescript/module/ui/hooks/useSurfaceHeader.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts +13 -0
- package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts +4 -5
- package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/surfaceNavStack.d.ts +21 -0
- package/lib/typescript/module/ui/navigation/surfaceNavStack.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts +73 -13
- package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/surfaces.d.ts +42 -11
- package/lib/typescript/module/ui/navigation/surfaces.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/AvatarCropScreen.d.ts +18 -5
- package/lib/typescript/module/ui/screens/AvatarCropScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/ChangeAvatarScreen.d.ts +34 -0
- package/lib/typescript/module/ui/screens/ChangeAvatarScreen.d.ts.map +1 -0
- package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/fileManagement/FileLibraryError.d.ts +23 -0
- package/lib/typescript/module/ui/screens/fileManagement/FileLibraryError.d.ts.map +1 -0
- package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts +4 -0
- package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/fileManagement/hooks/useFileUploadState.d.ts +10 -2
- package/lib/typescript/module/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
- package/lib/typescript/module/ui/stores/resetSessionScopedStores.d.ts +8 -1
- package/lib/typescript/module/ui/stores/resetSessionScopedStores.d.ts.map +1 -1
- package/lib/typescript/module/ui/types/navigation.d.ts +2 -0
- package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/avatarUtils.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/fileManagement.d.ts +0 -48
- package/lib/typescript/module/ui/utils/fileManagement.d.ts.map +1 -1
- package/package.json +8 -4
- package/src/ui/components/AvatarCameraBadge.tsx +35 -0
- package/src/ui/components/OxyAccountDialogScreen.tsx +38 -15
- package/src/ui/components/OxyAuthChooser.tsx +722 -185
- package/src/ui/components/ProfileButton.tsx +16 -1
- package/src/ui/components/ProfileSummaryCard.tsx +2 -25
- package/src/ui/components/SurfaceScreen.tsx +48 -14
- package/src/ui/components/oxyAuthChooserStyles.ts +75 -23
- package/src/ui/components/photogrid/JustifiedPhotoGrid.tsx +158 -84
- package/src/ui/components/photogrid/__tests__/chunkPhotos.test.ts +51 -0
- package/src/ui/context/OxyContext.tsx +74 -34
- package/src/ui/hooks/queries/queryKeys.ts +15 -0
- package/src/ui/hooks/queries/useFileQueries.ts +162 -0
- package/src/ui/hooks/useAvatarPicker.ts +38 -53
- package/src/ui/hooks/useReduceMotion.ts +58 -0
- package/src/ui/hooks/useSurfaceHeader.ts +2 -1
- package/src/ui/navigation/__tests__/getSurfaceConfig.test.ts +55 -7
- package/src/ui/navigation/__tests__/surfaceNavStack.test.ts +81 -0
- package/src/ui/navigation/accountDialogManager.ts +28 -0
- package/src/ui/navigation/bottomSheetManager.ts +6 -7
- package/src/ui/navigation/routes.ts +3 -1
- package/src/ui/navigation/surfaceNavStack.ts +72 -9
- package/src/ui/navigation/surfaceRegistry.ts +91 -17
- package/src/ui/navigation/surfaces.ts +108 -29
- package/src/ui/screens/AvatarCropScreen.tsx +336 -298
- package/src/ui/screens/ChangeAvatarScreen.tsx +359 -0
- package/src/ui/screens/FileManagementScreen.tsx +189 -432
- package/src/ui/screens/WelcomeNewUserScreen.tsx +18 -48
- package/src/ui/screens/fileManagement/FileLibraryError.tsx +56 -0
- package/src/ui/screens/fileManagement/PhotoPickerSection.tsx +56 -46
- package/src/ui/screens/fileManagement/hooks/useFileUploadState.ts +55 -71
- package/src/ui/stores/resetSessionScopedStores.ts +8 -3
- package/src/ui/types/navigation.ts +2 -0
- package/src/ui/utils/avatarUtils.ts +11 -2
- package/src/ui/utils/fileManagement.ts +1 -101
- package/lib/commonjs/ui/stores/fileStore.js +0 -153
- package/lib/commonjs/ui/stores/fileStore.js.map +0 -1
- package/lib/module/ui/stores/fileStore.js +0 -145
- package/lib/module/ui/stores/fileStore.js.map +0 -1
- package/lib/typescript/commonjs/ui/stores/fileStore.d.ts +0 -31
- package/lib/typescript/commonjs/ui/stores/fileStore.d.ts.map +0 -1
- package/lib/typescript/module/ui/stores/fileStore.d.ts +0 -31
- package/lib/typescript/module/ui/stores/fileStore.d.ts.map +0 -1
- package/src/ui/stores/fileStore.ts +0 -118
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChangeAvatarScreen — the single entry into changing a profile picture.
|
|
3
|
+
*
|
|
4
|
+
* Shows the current avatar large, then the four sources the user can pick from:
|
|
5
|
+
* device gallery, camera, their existing Oxy files, and removing the current
|
|
6
|
+
* photo. Every source that yields an image NAVIGATES WITHIN THIS SURFACE to
|
|
7
|
+
* `AvatarCrop` (never presents it on top), so the panel morphs from this list's
|
|
8
|
+
* size to the crop editor's and back — Cancel on the cropper returns here to
|
|
9
|
+
* re-pick. One container reshaping, not two sheets. The forward morph is
|
|
10
|
+
* flash-free because only the top frame renders and the cropper does all its own
|
|
11
|
+
* async work (URL resolution + measurement), so this list is never left on screen
|
|
12
|
+
* waiting.
|
|
13
|
+
*
|
|
14
|
+
* The surface resolves with an {@link AvatarCropResult} (the crop confirmed) or
|
|
15
|
+
* an {@link AvatarRemovalResult} (the user removed their photo). This screen
|
|
16
|
+
* performs NO writes: `useAvatarPicker` owns the single upload/removal path.
|
|
17
|
+
*
|
|
18
|
+
* `expo-image-picker` is an optional peer dependency — it is loaded with
|
|
19
|
+
* `await import(...)` so an app that never changes avatars need not install it,
|
|
20
|
+
* and a platform without a given source (camera on web) simply doesn't offer it.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type React from 'react';
|
|
24
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
25
|
+
import { Linking, Platform, View } from 'react-native';
|
|
26
|
+
import { Avatar } from '@oxyhq/bloom/avatar';
|
|
27
|
+
import { SettingsListGroup, SettingsListItem } from '@oxyhq/bloom/settings-list';
|
|
28
|
+
import {
|
|
29
|
+
AdmonitionRoot,
|
|
30
|
+
AdmonitionRow,
|
|
31
|
+
AdmonitionIcon,
|
|
32
|
+
AdmonitionContent,
|
|
33
|
+
AdmonitionText,
|
|
34
|
+
AdmonitionButton,
|
|
35
|
+
} from '@oxyhq/bloom/admonition';
|
|
36
|
+
import { surfaces as bloomSurfaces } from '@oxyhq/bloom/surfaces';
|
|
37
|
+
import { useTheme } from '@oxyhq/bloom/theme';
|
|
38
|
+
import { toast } from '@oxyhq/bloom';
|
|
39
|
+
import { getAccountDisplayName, logger } from '@oxyhq/core';
|
|
40
|
+
import { useOxy } from '../context/OxyContext';
|
|
41
|
+
import { useI18n } from '../hooks/useI18n';
|
|
42
|
+
import { useSurfaceHeader } from '../hooks/useSurfaceHeader';
|
|
43
|
+
import { openWithinOrPresent } from '../navigation/surfaces';
|
|
44
|
+
import { SettingsIcon } from '../components/SettingsIcon';
|
|
45
|
+
import type { BaseScreenProps } from '../types/navigation';
|
|
46
|
+
|
|
47
|
+
/** Component name used in `logger` context for filtered diagnostics. */
|
|
48
|
+
const LOG_COMPONENT = 'ChangeAvatarScreen';
|
|
49
|
+
|
|
50
|
+
/** Diameter (dp) of the current-avatar hero at the top of the screen. */
|
|
51
|
+
const PREVIEW_SIZE = 132;
|
|
52
|
+
|
|
53
|
+
/** MIME families the Oxy-files picker hides so it renders as an image-only picker. */
|
|
54
|
+
const NON_IMAGE_MIME_TYPES = ['video/', 'audio/', 'application/pdf'];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The surface resolves with this when the user confirms removing their photo.
|
|
58
|
+
* Distinguished from a crop result by the presence of `removed`, so the awaiting
|
|
59
|
+
* caller narrows without a cast.
|
|
60
|
+
*/
|
|
61
|
+
export interface AvatarRemovalResult {
|
|
62
|
+
removed: true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Which permission was refused, so the screen can explain the right thing. */
|
|
66
|
+
type DeniedSource = 'library' | 'camera';
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The slice of `expo-image-picker` this screen uses. Declared locally (rather
|
|
70
|
+
* than imported) because the module is an optional peer: a type-only import
|
|
71
|
+
* would still make `tsc` require it to be installed.
|
|
72
|
+
*/
|
|
73
|
+
interface ImagePickerAsset {
|
|
74
|
+
uri: string;
|
|
75
|
+
width: number;
|
|
76
|
+
height: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface ImagePickerResult {
|
|
80
|
+
canceled: boolean;
|
|
81
|
+
assets: ImagePickerAsset[] | null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface ImagePickerPermission {
|
|
85
|
+
granted: boolean;
|
|
86
|
+
canAskAgain: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface ImagePickerOptions {
|
|
90
|
+
mediaTypes: 'images'[];
|
|
91
|
+
allowsMultipleSelection: false;
|
|
92
|
+
quality: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface ImagePickerModule {
|
|
96
|
+
launchImageLibraryAsync: (options: ImagePickerOptions) => Promise<ImagePickerResult>;
|
|
97
|
+
launchCameraAsync: (options: ImagePickerOptions) => Promise<ImagePickerResult>;
|
|
98
|
+
requestMediaLibraryPermissionsAsync: () => Promise<ImagePickerPermission>;
|
|
99
|
+
requestCameraPermissionsAsync: () => Promise<ImagePickerPermission>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Options shared by both launch paths — an image, one at a time, at full quality. */
|
|
103
|
+
const PICKER_OPTIONS: ImagePickerOptions = {
|
|
104
|
+
mediaTypes: ['images'],
|
|
105
|
+
allowsMultipleSelection: false,
|
|
106
|
+
// The crop editor downsamples to 512x512, so the source is taken untouched.
|
|
107
|
+
quality: 1,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Resolve `expo-image-picker` on demand. Returns `null` when the app has not
|
|
112
|
+
* installed it, so the caller can explain that the source is unavailable rather
|
|
113
|
+
* than crashing.
|
|
114
|
+
*/
|
|
115
|
+
async function loadImagePicker(): Promise<ImagePickerModule | null> {
|
|
116
|
+
try {
|
|
117
|
+
const mod = (await import('expo-image-picker')) as unknown as ImagePickerModule;
|
|
118
|
+
if (typeof mod?.launchImageLibraryAsync !== 'function') return null;
|
|
119
|
+
return mod;
|
|
120
|
+
} catch (err) {
|
|
121
|
+
logger.debug(
|
|
122
|
+
'expo-image-picker unavailable',
|
|
123
|
+
{ component: LOG_COMPONENT },
|
|
124
|
+
{ reason: err instanceof Error ? err.message : String(err) },
|
|
125
|
+
);
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const ChangeAvatarScreen: React.FC<BaseScreenProps> = ({ navigate, dismiss }) => {
|
|
131
|
+
const bloomTheme = useTheme();
|
|
132
|
+
const { t, locale } = useI18n();
|
|
133
|
+
const { user, oxyServices } = useOxy();
|
|
134
|
+
|
|
135
|
+
/** Set when a permission was refused — drives the inline denied notice. */
|
|
136
|
+
const [denied, setDenied] = useState<DeniedSource | null>(null);
|
|
137
|
+
/** True while a source is being opened, so rows can't be double-fired. */
|
|
138
|
+
const [busy, setBusy] = useState(false);
|
|
139
|
+
|
|
140
|
+
useSurfaceHeader(useMemo(() => ({ title: t('changeAvatar.title') }), [t]));
|
|
141
|
+
|
|
142
|
+
const displayName = useMemo(() => getAccountDisplayName(user, locale), [user, locale]);
|
|
143
|
+
const avatarUri = useMemo(
|
|
144
|
+
// `thumb` is 256x256 — exactly a 2x source for the 132dp hero.
|
|
145
|
+
() => (user?.avatar ? oxyServices.getFileDownloadUrl(user.avatar, 'thumb') : undefined),
|
|
146
|
+
[user?.avatar, oxyServices],
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Hand a source to the crop editor by navigating WITHIN this surface (a push,
|
|
151
|
+
* so Cancel on the cropper morphs back to this source list to re-pick). The
|
|
152
|
+
* push is flash-free because only the top frame renders and the cropper does
|
|
153
|
+
* ALL its own async work: the device / camera sources pass a ready `imageUri`;
|
|
154
|
+
* the Oxy-files source passes only the file's `imageFileId`, which the cropper
|
|
155
|
+
* resolves to a private-safe download URL itself (resolving it HERE would keep
|
|
156
|
+
* this list on screen for the whole network round-trip — the flash the user
|
|
157
|
+
* reported).
|
|
158
|
+
*/
|
|
159
|
+
const goToCrop = useCallback(
|
|
160
|
+
(cropProps: Record<string, unknown>) => {
|
|
161
|
+
navigate?.('AvatarCrop', cropProps);
|
|
162
|
+
},
|
|
163
|
+
[navigate],
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Shared body of the two `expo-image-picker` sources. They differ only in
|
|
168
|
+
* which permission they ask for and which launcher they call.
|
|
169
|
+
*/
|
|
170
|
+
const pickFromDevice = useCallback(
|
|
171
|
+
async (source: DeniedSource) => {
|
|
172
|
+
if (busy) return;
|
|
173
|
+
setBusy(true);
|
|
174
|
+
try {
|
|
175
|
+
const picker = await loadImagePicker();
|
|
176
|
+
if (!picker) {
|
|
177
|
+
toast.error(
|
|
178
|
+
t(
|
|
179
|
+
source === 'camera'
|
|
180
|
+
? 'changeAvatar.errors.cameraUnavailable'
|
|
181
|
+
: 'changeAvatar.errors.pickerUnavailable',
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const permission =
|
|
188
|
+
source === 'camera'
|
|
189
|
+
? await picker.requestCameraPermissionsAsync()
|
|
190
|
+
: await picker.requestMediaLibraryPermissionsAsync();
|
|
191
|
+
if (!permission.granted) {
|
|
192
|
+
setDenied(source);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
setDenied(null);
|
|
196
|
+
|
|
197
|
+
const result =
|
|
198
|
+
source === 'camera'
|
|
199
|
+
? await picker.launchCameraAsync(PICKER_OPTIONS)
|
|
200
|
+
: await picker.launchImageLibraryAsync(PICKER_OPTIONS);
|
|
201
|
+
const asset = result.canceled ? undefined : result.assets?.[0];
|
|
202
|
+
if (!asset?.uri) return;
|
|
203
|
+
|
|
204
|
+
// The picker reports the asset's natural size, so the crop editor
|
|
205
|
+
// skips its own `Image.getSize` measurement round-trip.
|
|
206
|
+
goToCrop({
|
|
207
|
+
imageUri: asset.uri,
|
|
208
|
+
sourceWidth: asset.width > 0 ? asset.width : undefined,
|
|
209
|
+
sourceHeight: asset.height > 0 ? asset.height : undefined,
|
|
210
|
+
});
|
|
211
|
+
} catch (err) {
|
|
212
|
+
logger.error(
|
|
213
|
+
`${source} picker failed`,
|
|
214
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
215
|
+
{ component: LOG_COMPONENT },
|
|
216
|
+
);
|
|
217
|
+
toast.error(
|
|
218
|
+
t(
|
|
219
|
+
source === 'camera'
|
|
220
|
+
? 'changeAvatar.errors.cameraFailed'
|
|
221
|
+
: 'changeAvatar.errors.pickFailed',
|
|
222
|
+
),
|
|
223
|
+
);
|
|
224
|
+
} finally {
|
|
225
|
+
setBusy(false);
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
[busy, goToCrop, t],
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Pick from the files the user already has on Oxy. The image-only media
|
|
233
|
+
* selector MORPHS into this surface (a nested sub-flow) like every other
|
|
234
|
+
* screen and resolves with the chosen file; picking one then morphs on to the
|
|
235
|
+
* crop editor. When triggered with no surface open it presents cold instead.
|
|
236
|
+
*/
|
|
237
|
+
const pickFromOxyFiles = useCallback(async () => {
|
|
238
|
+
if (busy) return;
|
|
239
|
+
setBusy(true);
|
|
240
|
+
try {
|
|
241
|
+
const file = await openWithinOrPresent('FileManagement', {
|
|
242
|
+
selectMode: true,
|
|
243
|
+
multiSelect: false,
|
|
244
|
+
disabledMimeTypes: NON_IMAGE_MIME_TYPES,
|
|
245
|
+
});
|
|
246
|
+
if (!file) return;
|
|
247
|
+
|
|
248
|
+
if (!file.contentType?.startsWith('image/')) {
|
|
249
|
+
toast.error(t('editProfile.toasts.selectImage'));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Hand the file's ID straight to the cropper WITHOUT resolving its
|
|
254
|
+
// (usually private) download URL here. The cropper resolves it — and
|
|
255
|
+
// shows its own loading state on the dark canvas while it does — so
|
|
256
|
+
// this source list is never left on screen for the network round-trip
|
|
257
|
+
// that resolution takes (the "goes back to the avatar screen" flash).
|
|
258
|
+
goToCrop({ imageFileId: file.id });
|
|
259
|
+
} catch (err) {
|
|
260
|
+
logger.error(
|
|
261
|
+
'Oxy files pick failed',
|
|
262
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
263
|
+
{ component: LOG_COMPONENT },
|
|
264
|
+
);
|
|
265
|
+
toast.error(t('changeAvatar.errors.loadImageFailed'));
|
|
266
|
+
} finally {
|
|
267
|
+
setBusy(false);
|
|
268
|
+
}
|
|
269
|
+
}, [busy, goToCrop, t]);
|
|
270
|
+
|
|
271
|
+
/** Confirm, then resolve the surface with the removal so the caller writes it. */
|
|
272
|
+
const removeCurrentPhoto = useCallback(async () => {
|
|
273
|
+
const confirmed = await bloomSurfaces.confirm({
|
|
274
|
+
title: t('changeAvatar.remove.confirmTitle'),
|
|
275
|
+
message: t('changeAvatar.remove.confirmMessage'),
|
|
276
|
+
confirmLabel: t('changeAvatar.remove.confirmLabel'),
|
|
277
|
+
cancelLabel: t('common.cancel'),
|
|
278
|
+
destructive: true,
|
|
279
|
+
});
|
|
280
|
+
if (!confirmed) return;
|
|
281
|
+
const removal: AvatarRemovalResult = { removed: true };
|
|
282
|
+
dismiss?.(removal);
|
|
283
|
+
}, [dismiss, t]);
|
|
284
|
+
|
|
285
|
+
const openSystemSettings = useCallback(() => {
|
|
286
|
+
void Linking.openSettings();
|
|
287
|
+
}, []);
|
|
288
|
+
|
|
289
|
+
// The camera is a native-only source: on web `launchCameraAsync` has no
|
|
290
|
+
// meaningful surface to open, so the row is not offered at all.
|
|
291
|
+
const canUseCamera = Platform.OS !== 'web';
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<View className="px-screen-margin pt-space-8 pb-space-24 gap-space-16">
|
|
295
|
+
<View className="items-center" accessible accessibilityRole="image"
|
|
296
|
+
accessibilityLabel={t('changeAvatar.a11y.currentPhoto')}>
|
|
297
|
+
<Avatar source={avatarUri} name={displayName} size={PREVIEW_SIZE} />
|
|
298
|
+
</View>
|
|
299
|
+
|
|
300
|
+
{denied ? (
|
|
301
|
+
<AdmonitionRoot type="warning">
|
|
302
|
+
<AdmonitionRow>
|
|
303
|
+
<AdmonitionIcon />
|
|
304
|
+
<AdmonitionContent>
|
|
305
|
+
<AdmonitionText>
|
|
306
|
+
{t(
|
|
307
|
+
denied === 'camera'
|
|
308
|
+
? 'changeAvatar.permission.cameraDenied'
|
|
309
|
+
: 'changeAvatar.permission.libraryDenied',
|
|
310
|
+
)}
|
|
311
|
+
</AdmonitionText>
|
|
312
|
+
{Platform.OS === 'web' ? null : (
|
|
313
|
+
<AdmonitionButton onPress={openSystemSettings}>
|
|
314
|
+
{t('changeAvatar.permission.openSettings')}
|
|
315
|
+
</AdmonitionButton>
|
|
316
|
+
)}
|
|
317
|
+
</AdmonitionContent>
|
|
318
|
+
</AdmonitionRow>
|
|
319
|
+
</AdmonitionRoot>
|
|
320
|
+
) : null}
|
|
321
|
+
|
|
322
|
+
<SettingsListGroup>
|
|
323
|
+
<SettingsListItem
|
|
324
|
+
icon={<SettingsIcon name="image-outline" color={bloomTheme.colors.primary} />}
|
|
325
|
+
title={t('changeAvatar.sources.upload.title')}
|
|
326
|
+
description={t('changeAvatar.sources.upload.description')}
|
|
327
|
+
disabled={busy}
|
|
328
|
+
onPress={() => void pickFromDevice('library')}
|
|
329
|
+
/>
|
|
330
|
+
{canUseCamera ? (
|
|
331
|
+
<SettingsListItem
|
|
332
|
+
icon={<SettingsIcon name="camera-outline" color={bloomTheme.colors.info} />}
|
|
333
|
+
title={t('changeAvatar.sources.camera.title')}
|
|
334
|
+
description={t('changeAvatar.sources.camera.description')}
|
|
335
|
+
disabled={busy}
|
|
336
|
+
onPress={() => void pickFromDevice('camera')}
|
|
337
|
+
/>
|
|
338
|
+
) : null}
|
|
339
|
+
<SettingsListItem
|
|
340
|
+
icon={<SettingsIcon name="folder-image" color={bloomTheme.colors.success} />}
|
|
341
|
+
title={t('changeAvatar.sources.files.title')}
|
|
342
|
+
description={t('changeAvatar.sources.files.description')}
|
|
343
|
+
disabled={busy}
|
|
344
|
+
onPress={() => void pickFromOxyFiles()}
|
|
345
|
+
/>
|
|
346
|
+
<SettingsListItem
|
|
347
|
+
icon={<SettingsIcon name="trash-can-outline" color={bloomTheme.colors.error} />}
|
|
348
|
+
title={t('changeAvatar.sources.remove.title')}
|
|
349
|
+
description={t('changeAvatar.sources.remove.description')}
|
|
350
|
+
destructive
|
|
351
|
+
disabled={busy || !user?.avatar}
|
|
352
|
+
onPress={() => void removeCurrentPhoto()}
|
|
353
|
+
/>
|
|
354
|
+
</SettingsListGroup>
|
|
355
|
+
</View>
|
|
356
|
+
);
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
export default ChangeAvatarScreen;
|