@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
|
@@ -8,10 +8,14 @@ var _react = require("react");
|
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
var _reactNativeGestureHandler = require("react-native-gesture-handler");
|
|
10
10
|
var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
|
|
11
|
-
var
|
|
11
|
+
var _reactQuery = require("@tanstack/react-query");
|
|
12
12
|
var _theme = require("@oxyhq/bloom/theme");
|
|
13
|
+
var _button = require("@oxyhq/bloom/button");
|
|
13
14
|
var _core = require("@oxyhq/core");
|
|
15
|
+
var _OxyContext = require("../context/OxyContext.js");
|
|
14
16
|
var _useI18n = require("../hooks/useI18n.js");
|
|
17
|
+
var _useReduceMotion = require("../hooks/useReduceMotion.js");
|
|
18
|
+
var _useSurfaceHeader = require("../hooks/useSurfaceHeader.js");
|
|
15
19
|
var _bloom = require("@oxyhq/bloom");
|
|
16
20
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
17
21
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /**
|
|
@@ -21,11 +25,17 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
21
25
|
* but before the avatar is uploaded. Inspired by iOS Photos, Google Photos and
|
|
22
26
|
* Instagram crop tools.
|
|
23
27
|
*
|
|
28
|
+
* Reached ONLY by navigating within a `ChangeAvatar` surface, so the panel
|
|
29
|
+
* morphs from the source list into the editor. The surface owns the chrome: this
|
|
30
|
+
* screen declares its title and its "Use photo" action through `useSurfaceHeader`
|
|
31
|
+
* and renders no bar of its own — the nav bar's back arrow is Cancel, and it
|
|
32
|
+
* returns to the source list.
|
|
33
|
+
*
|
|
24
34
|
* Architecture:
|
|
25
|
-
* -
|
|
26
|
-
*
|
|
27
|
-
* - Circular viewport with white ring
|
|
28
|
-
*
|
|
35
|
+
* - A dark crop stage, independent of theme, so photos always read well, inset
|
|
36
|
+
* inside the surface's own themed panel.
|
|
37
|
+
* - Circular viewport with white ring and a 3x3 rule-of-thirds grid that
|
|
38
|
+
* appears during gestures and fades after 800ms.
|
|
29
39
|
* - Floating zoom chip during pinch.
|
|
30
40
|
* - Pan + pinch via Gesture Handler, transform driven by Reanimated.
|
|
31
41
|
* - Reduced-motion aware entrance animation.
|
|
@@ -45,14 +55,20 @@ const VIEWPORT_SIZE = 320;
|
|
|
45
55
|
const OUTPUT_SIZE = 512;
|
|
46
56
|
const MIN_SCALE = 1;
|
|
47
57
|
const MAX_SCALE = 4;
|
|
58
|
+
/** Zoom increment per +/- button press (web, where there is no pinch). */
|
|
59
|
+
const ZOOM_STEP = 0.5;
|
|
60
|
+
/** Tween duration for a button-driven zoom (collapses to 0 under reduce-motion). */
|
|
61
|
+
const ZOOM_BUTTON_DURATION_MS = 180;
|
|
48
62
|
/** Duration (ms) that the rule-of-thirds grid lingers after a gesture ends. */
|
|
49
63
|
const GRID_FADE_DELAY_MS = 800;
|
|
50
64
|
const GRID_FADE_DURATION_MS = 220;
|
|
51
65
|
/** Duration the zoom chip stays visible after a pinch ends. */
|
|
52
66
|
const ZOOM_CHIP_FADE_DELAY_MS = 600;
|
|
53
67
|
const ZOOM_CHIP_FADE_DURATION_MS = 200;
|
|
54
|
-
/**
|
|
68
|
+
/** Stage color is fixed independent of theme so the photo always reads well. */
|
|
55
69
|
const CANVAS_BG = '#000000';
|
|
70
|
+
/** Corner radius of the dark stage block inside the surface's themed panel. */
|
|
71
|
+
const STAGE_RADIUS = 24;
|
|
56
72
|
const RING_COLOR = '#ffffff';
|
|
57
73
|
const RING_WIDTH = 2;
|
|
58
74
|
const REMOTE_HTTP_URI_PATTERN = /^https?:\/\//i;
|
|
@@ -74,6 +90,26 @@ function clampTranslation(tx, ty, s, baseW, baseH) {
|
|
|
74
90
|
ty: Math.min(Math.max(ty, -maxY), maxY)
|
|
75
91
|
};
|
|
76
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Measure an image's intrinsic size via `Image.getSize`, wrapped as a Promise so
|
|
95
|
+
* it can be a React Query `queryFn` (no component state / effects). Rejects on a
|
|
96
|
+
* platform failure or degenerate dimensions so the query surfaces `isError`.
|
|
97
|
+
* Mirrors the `measurePhotoDimensions` helper the photo grid uses.
|
|
98
|
+
*/
|
|
99
|
+
function measureImageSize(uri) {
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
_reactNative.Image.getSize(uri, (width, height) => {
|
|
102
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
|
103
|
+
reject(new Error('Image reported invalid dimensions'));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
resolve({
|
|
107
|
+
width,
|
|
108
|
+
height
|
|
109
|
+
});
|
|
110
|
+
}, () => reject(new Error('Image measurement failed')));
|
|
111
|
+
});
|
|
112
|
+
}
|
|
77
113
|
|
|
78
114
|
/**
|
|
79
115
|
* Lazy-loaded reference to the expo-image-manipulator module. The module is an
|
|
@@ -195,7 +231,9 @@ async function hapticSelection() {
|
|
|
195
231
|
}
|
|
196
232
|
const AvatarCropScreen = ({
|
|
197
233
|
dismiss,
|
|
234
|
+
goBack,
|
|
198
235
|
imageUri,
|
|
236
|
+
imageFileId,
|
|
199
237
|
sourceWidth,
|
|
200
238
|
sourceHeight
|
|
201
239
|
}) => {
|
|
@@ -203,19 +241,75 @@ const AvatarCropScreen = ({
|
|
|
203
241
|
const {
|
|
204
242
|
t
|
|
205
243
|
} = (0, _useI18n.useI18n)();
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
244
|
+
const {
|
|
245
|
+
oxyServices
|
|
246
|
+
} = (0, _OxyContext.useOxy)();
|
|
247
|
+
// Reactive OS reduce-motion preference (useSyncExternalStore, no effect). Pins
|
|
248
|
+
// the entrance spring off. Declared before `entrance` so its initial rest value
|
|
249
|
+
// is correct under reduce-motion.
|
|
250
|
+
const reduceMotion = (0, _useReduceMotion.useReduceMotion)();
|
|
251
|
+
|
|
252
|
+
// The "My Oxy files" source hands us a file ID instead of a ready URL. Its
|
|
253
|
+
// download URL is usually private, so it must be resolved through the SDK
|
|
254
|
+
// (which throws on failure). Resolving it HERE — not in the source list — is
|
|
255
|
+
// what keeps the list from lingering on screen for the round-trip: this screen
|
|
256
|
+
// shows its own spinner on the dark canvas while the URL resolves. A React
|
|
257
|
+
// Query read (not an effect) so it stays declarative and cached.
|
|
258
|
+
const shouldResolveFile = !imageUri && !!imageFileId;
|
|
259
|
+
const sourceQuery = (0, _reactQuery.useQuery)({
|
|
260
|
+
queryKey: ['avatarCropSource', imageFileId],
|
|
261
|
+
enabled: shouldResolveFile,
|
|
262
|
+
staleTime: Number.POSITIVE_INFINITY,
|
|
263
|
+
retry: false,
|
|
264
|
+
queryFn: async () => {
|
|
265
|
+
if (!imageFileId) {
|
|
266
|
+
throw new Error('No file id to resolve for cropping');
|
|
267
|
+
}
|
|
268
|
+
const resolved = await oxyServices.assetGetUrl(imageFileId);
|
|
269
|
+
if (!resolved?.url) {
|
|
270
|
+
throw new Error('No download URL returned for the selected image');
|
|
271
|
+
}
|
|
272
|
+
return resolved.url;
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// The URL the cropper actually loads: the passed `imageUri`, else the URL the
|
|
277
|
+
// file-ID query resolved. While it resolves, `effectiveUri` is undefined and
|
|
278
|
+
// the stage shows its own spinner (never the empty state).
|
|
279
|
+
const effectiveUri = imageUri ?? sourceQuery.data;
|
|
280
|
+
const isResolvingSource = shouldResolveFile && !sourceQuery.data && !sourceQuery.isError;
|
|
281
|
+
const sourceErrorMessage = sourceQuery.isError ? t('changeAvatar.errors.loadImageFailed') || 'Could not load the selected image' : null;
|
|
282
|
+
|
|
283
|
+
// Natural size of the source image. Known up front (device/camera sources pass
|
|
284
|
+
// the dimensions) OR measured lazily from `effectiveUri` via a React Query read
|
|
285
|
+
// (no effect) — the same Image.getSize→useQuery pattern the photo grid uses.
|
|
286
|
+
const hasPassedDimensions = !!(sourceWidth && sourceHeight);
|
|
287
|
+
const measureQuery = (0, _reactQuery.useQuery)({
|
|
288
|
+
queryKey: ['avatarCropMeasure', effectiveUri],
|
|
289
|
+
enabled: !!effectiveUri && !hasPassedDimensions,
|
|
290
|
+
queryFn: () => {
|
|
291
|
+
if (!effectiveUri) {
|
|
292
|
+
throw new Error('No image to measure');
|
|
293
|
+
}
|
|
294
|
+
return measureImageSize(effectiveUri);
|
|
295
|
+
},
|
|
296
|
+
staleTime: Number.POSITIVE_INFINITY,
|
|
297
|
+
retry: false,
|
|
298
|
+
placeholderData: _reactQuery.keepPreviousData
|
|
299
|
+
});
|
|
300
|
+
const naturalSize = (0, _react.useMemo)(() => {
|
|
301
|
+
if (sourceWidth && sourceHeight) return {
|
|
302
|
+
width: sourceWidth,
|
|
303
|
+
height: sourceHeight
|
|
304
|
+
};
|
|
305
|
+
return measureQuery.data ?? null;
|
|
306
|
+
}, [sourceWidth, sourceHeight, measureQuery.data]);
|
|
307
|
+
/** Measurement failed (bad dimensions / getSize error) — surfaced via the empty UI. */
|
|
308
|
+
const measureErrorMessage = measureQuery.isError ? t('editProfile.toasts.cropMeasureFailed') || 'Could not measure the image' : null;
|
|
214
309
|
const [isProcessing, setIsProcessing] = (0, _react.useState)(false);
|
|
215
310
|
const [zoomLabel, setZoomLabel] = (0, _react.useState)('1.0');
|
|
216
311
|
/** True when scale != MIN_SCALE OR translate != 0 — used to reveal the reset link. */
|
|
217
312
|
const [isModified, setIsModified] = (0, _react.useState)(false);
|
|
218
|
-
const [reduceMotion, setReduceMotion] = (0, _react.useState)(false);
|
|
219
313
|
|
|
220
314
|
// Shared values for the active gesture transform.
|
|
221
315
|
const scale = (0, _reactNativeReanimated.useSharedValue)(MIN_SCALE);
|
|
@@ -265,99 +359,18 @@ const AvatarCropScreen = ({
|
|
|
265
359
|
};
|
|
266
360
|
}, [naturalSize]);
|
|
267
361
|
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
_core.logger.debug('Measuring image', {
|
|
281
|
-
component: LOG_COMPONENT
|
|
282
|
-
});
|
|
283
|
-
_reactNative.Image.getSize(uri, (w, h) => {
|
|
284
|
-
_core.logger.debug('Image measured', {
|
|
285
|
-
component: LOG_COMPONENT
|
|
286
|
-
}, {
|
|
287
|
-
width: w,
|
|
288
|
-
height: h
|
|
289
|
-
});
|
|
290
|
-
if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) {
|
|
291
|
-
const message = t('editProfile.toasts.cropMeasureFailed') || 'Could not measure the image';
|
|
292
|
-
setMeasureError(message);
|
|
293
|
-
_bloom.toast.error(message);
|
|
294
|
-
measuringUriRef.current = null;
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
setMeasureError(null);
|
|
298
|
-
setNaturalSize({
|
|
299
|
-
width: w,
|
|
300
|
-
height: h
|
|
301
|
-
});
|
|
302
|
-
}, () => {
|
|
303
|
-
_core.logger.error('Image.getSize failed', new Error('Image measurement failed'), {
|
|
304
|
-
component: LOG_COMPONENT
|
|
305
|
-
});
|
|
306
|
-
const message = t('editProfile.toasts.cropMeasureFailed') || 'Could not measure the image';
|
|
307
|
-
setMeasureError(message);
|
|
308
|
-
_bloom.toast.error(message);
|
|
309
|
-
measuringUriRef.current = null;
|
|
310
|
-
});
|
|
311
|
-
}, [t]);
|
|
312
|
-
|
|
313
|
-
// Kick off measurement once per imageUri. Using useEffect (not a render-body
|
|
314
|
-
// side effect) so the call is scheduled exactly when the URI changes,
|
|
315
|
-
// rather than being re-fired on every parent re-render.
|
|
316
|
-
(0, _react.useEffect)(() => {
|
|
317
|
-
if (!imageUri) return;
|
|
318
|
-
if (sourceWidth && sourceHeight) return;
|
|
319
|
-
if (naturalSize) return;
|
|
320
|
-
handleImageMeasured(imageUri);
|
|
321
|
-
}, [handleImageMeasured, imageUri, naturalSize, sourceHeight, sourceWidth]);
|
|
322
|
-
|
|
323
|
-
// Dev-only one-time mount breadcrumb. `logger.debug` is dev-gated so
|
|
324
|
-
// this is a no-op in production releases; we additionally avoid logging
|
|
325
|
-
// the full `imageUri` to prevent leaking on-device file paths into any
|
|
326
|
-
// breadcrumb sink that picks up debug output.
|
|
327
|
-
(0, _react.useEffect)(() => {
|
|
328
|
-
_core.logger.debug('mount', {
|
|
329
|
-
component: LOG_COMPONENT
|
|
330
|
-
}, {
|
|
331
|
-
hasImageUri: !!imageUri,
|
|
332
|
-
hasSourceDimensions: !!(sourceWidth && sourceHeight)
|
|
333
|
-
});
|
|
334
|
-
}, [imageUri, sourceHeight, sourceWidth]);
|
|
335
|
-
|
|
336
|
-
// Detect reduce-motion preference once on mount + subscribe to changes.
|
|
337
|
-
(0, _react.useEffect)(() => {
|
|
338
|
-
let cancelled = false;
|
|
339
|
-
_reactNative.AccessibilityInfo.isReduceMotionEnabled().then(enabled => {
|
|
340
|
-
if (cancelled) return;
|
|
341
|
-
setReduceMotion(enabled);
|
|
342
|
-
if (enabled) {
|
|
343
|
-
entrance.value = 1;
|
|
344
|
-
}
|
|
345
|
-
}).catch(() => {
|
|
346
|
-
// Best-effort — fall back to motion enabled.
|
|
347
|
-
});
|
|
348
|
-
const sub = _reactNative.AccessibilityInfo.addEventListener('reduceMotionChanged', enabled => {
|
|
349
|
-
setReduceMotion(enabled);
|
|
350
|
-
if (enabled) entrance.value = 1;
|
|
351
|
-
});
|
|
352
|
-
return () => {
|
|
353
|
-
cancelled = true;
|
|
354
|
-
sub.remove();
|
|
355
|
-
};
|
|
356
|
-
}, [entrance]);
|
|
357
|
-
|
|
358
|
-
// Play the entrance pulse exactly once when motion is allowed.
|
|
359
|
-
(0, _react.useEffect)(() => {
|
|
360
|
-
if (reduceMotion) return;
|
|
362
|
+
// Entrance pulse, driven from the crop frame's ref callback (fires on mount)
|
|
363
|
+
// rather than an effect — the media-cleanup `startEntrance` pattern. Reduce
|
|
364
|
+
// motion pins the frame at rest; otherwise it springs in from 0.95→1. The
|
|
365
|
+
// callback depends on `reduceMotion`, so if that preference resolves/flips the
|
|
366
|
+
// callback re-runs with the node and re-pins (snap to 1) — matching the old
|
|
367
|
+
// effect that snapped the entrance to rest when reduce-motion turned on.
|
|
368
|
+
const startEntrance = (0, _react.useCallback)(node => {
|
|
369
|
+
if (!node) return; // unmount
|
|
370
|
+
if (reduceMotion) {
|
|
371
|
+
entrance.value = 1;
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
361
374
|
entrance.value = (0, _reactNativeReanimated.withSpring)(1, {
|
|
362
375
|
damping: 14,
|
|
363
376
|
stiffness: 180,
|
|
@@ -476,6 +489,11 @@ const AvatarCropScreen = ({
|
|
|
476
489
|
(0, _reactNativeReanimated.runOnJS)(hideZoomChip)();
|
|
477
490
|
}), [baseFit, commitTransform, hideGrid, hideZoomChip, logGestureStart, notifyMaxBoundHit, notifyMinBoundHit, resetPinchBounds, savedScale, scale, showGrid, showZoomChip, translateX, translateY, updateZoomLabel]);
|
|
478
491
|
const composedGesture = (0, _react.useMemo)(() => _reactNativeGestureHandler.Gesture.Simultaneous(panGesture, pinchGesture), [panGesture, pinchGesture]);
|
|
492
|
+
|
|
493
|
+
// Deps list every shared value the mapper reads so it re-runs on web (RN-Web
|
|
494
|
+
// has no worklets plugin — a mapper omitting its driver SVs freezes at the
|
|
495
|
+
// first frame, which would leave the button-driven zoom stuck). Native
|
|
496
|
+
// auto-tracks and ignores the extra deps, so this is safe cross-platform.
|
|
479
497
|
const imageAnimatedStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => ({
|
|
480
498
|
transform: [{
|
|
481
499
|
translateX: translateX.value
|
|
@@ -484,12 +502,15 @@ const AvatarCropScreen = ({
|
|
|
484
502
|
}, {
|
|
485
503
|
scale: scale.value
|
|
486
504
|
}]
|
|
487
|
-
}));
|
|
505
|
+
}), [translateX, translateY, scale]);
|
|
506
|
+
|
|
507
|
+
// `[entrance]` in deps so the ref-callback-driven entrance spring ticks on
|
|
508
|
+
// RN-Web (no worklets plugin); native auto-tracks and ignores the extra dep.
|
|
488
509
|
const cropFrameAnimatedStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => ({
|
|
489
510
|
transform: [{
|
|
490
511
|
scale: entrance.value
|
|
491
512
|
}]
|
|
492
|
-
}));
|
|
513
|
+
}), [entrance]);
|
|
493
514
|
const gridAnimatedStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => ({
|
|
494
515
|
opacity: gridOpacity.value
|
|
495
516
|
}));
|
|
@@ -512,6 +533,43 @@ const AvatarCropScreen = ({
|
|
|
512
533
|
_reactNative.AccessibilityInfo.announceForAccessibility(t('editProfile.crop.a11yResetAnnouncement') || 'Crop reset');
|
|
513
534
|
}, [commitTransform, reduceMotion, scale, t, translateX, translateY]);
|
|
514
535
|
|
|
536
|
+
/**
|
|
537
|
+
* Web zoom controls. A desktop pointer has no pinch, so the +/- buttons drive
|
|
538
|
+
* the SAME `scale` shared value the pinch gesture does — clamped to the same
|
|
539
|
+
* bounds, with translation re-clamped through the shared {@link clampTranslation}
|
|
540
|
+
* helper so the image never leaves the viewport. Native keeps pinch (the
|
|
541
|
+
* buttons are hidden there). Reduced motion collapses the tween to an instant
|
|
542
|
+
* set. Reads the committed transform (refs) at press time, never during render.
|
|
543
|
+
*/
|
|
544
|
+
const applyZoom = (0, _react.useCallback)(direction => {
|
|
545
|
+
if (!baseFit || isProcessing) return;
|
|
546
|
+
const current = committedScale.current;
|
|
547
|
+
const target = Math.min(MAX_SCALE, Math.max(MIN_SCALE, current + direction * ZOOM_STEP));
|
|
548
|
+
if (Math.abs(target - current) < 0.001) {
|
|
549
|
+
// Already at the bound — a soft selection tick, no transform change.
|
|
550
|
+
void hapticSelection();
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
const clamped = clampTranslation(committedTranslateX.current, committedTranslateY.current, target, baseFit.width, baseFit.height);
|
|
554
|
+
const duration = reduceMotion ? 0 : ZOOM_BUTTON_DURATION_MS;
|
|
555
|
+
scale.value = (0, _reactNativeReanimated.withTiming)(target, {
|
|
556
|
+
duration
|
|
557
|
+
});
|
|
558
|
+
translateX.value = (0, _reactNativeReanimated.withTiming)(clamped.tx, {
|
|
559
|
+
duration
|
|
560
|
+
});
|
|
561
|
+
translateY.value = (0, _reactNativeReanimated.withTiming)(clamped.ty, {
|
|
562
|
+
duration
|
|
563
|
+
});
|
|
564
|
+
commitTransform(target, clamped.tx, clamped.ty);
|
|
565
|
+
void hapticImpact('light');
|
|
566
|
+
// Mirror the pinch's transient chrome so the buttons feel identical.
|
|
567
|
+
showGrid();
|
|
568
|
+
hideGrid();
|
|
569
|
+
showZoomChip();
|
|
570
|
+
hideZoomChip();
|
|
571
|
+
}, [baseFit, commitTransform, hideGrid, hideZoomChip, isProcessing, reduceMotion, scale, showGrid, showZoomChip, translateX, translateY]);
|
|
572
|
+
|
|
515
573
|
/**
|
|
516
574
|
* Convert the on-screen transform into pixel-space crop coordinates
|
|
517
575
|
* relative to the source image, then invoke expo-image-manipulator to
|
|
@@ -529,7 +587,7 @@ const AvatarCropScreen = ({
|
|
|
529
587
|
committedTranslateX: committedTranslateX.current,
|
|
530
588
|
committedTranslateY: committedTranslateY.current
|
|
531
589
|
});
|
|
532
|
-
if (!
|
|
590
|
+
if (!effectiveUri || !baseFit || !naturalSize) {
|
|
533
591
|
_bloom.toast.error(t('editProfile.toasts.cropNotReady') || 'Image not ready yet');
|
|
534
592
|
return;
|
|
535
593
|
}
|
|
@@ -572,7 +630,7 @@ const AvatarCropScreen = ({
|
|
|
572
630
|
cropSize,
|
|
573
631
|
outputSize: OUTPUT_SIZE
|
|
574
632
|
});
|
|
575
|
-
const preparedImage = await prepareImageForManipulator(
|
|
633
|
+
const preparedImage = await prepareImageForManipulator(effectiveUri);
|
|
576
634
|
preparedImageCleanup = preparedImage.cleanup;
|
|
577
635
|
const result = await manipulateAsync(preparedImage.uri, [{
|
|
578
636
|
crop: {
|
|
@@ -620,85 +678,48 @@ const AvatarCropScreen = ({
|
|
|
620
678
|
preparedImageCleanup?.();
|
|
621
679
|
setIsProcessing(false);
|
|
622
680
|
}
|
|
623
|
-
}, [baseFit,
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
681
|
+
}, [baseFit, effectiveUri, naturalSize, dismiss, t]);
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* The surface's primary action, mounted into the Dialog nav bar's right slot
|
|
685
|
+
* (which is why it must stay referentially stable). While processing it keeps
|
|
686
|
+
* its width and shows a spinner; the a11y label carries the "Saving…" state
|
|
687
|
+
* the visible label can no longer show.
|
|
688
|
+
*/
|
|
689
|
+
const doneAction = (0, _react.useMemo)(() => /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
|
|
690
|
+
variant: "primary",
|
|
691
|
+
size: "small",
|
|
692
|
+
onPress: () => void handleConfirm(),
|
|
693
|
+
disabled: isProcessing || !baseFit,
|
|
694
|
+
loading: isProcessing,
|
|
695
|
+
accessibilityLabel: isProcessing ? t('editProfile.crop.saving') : t('editProfile.crop.confirm'),
|
|
696
|
+
children: t('editProfile.crop.confirm')
|
|
697
|
+
}), [baseFit, handleConfirm, isProcessing, t]);
|
|
698
|
+
|
|
699
|
+
// No bar of our own: the surface owns the chrome. `goBack` pops back to the
|
|
700
|
+
// ChangeAvatar source list (and dismisses the surface if this is somehow the
|
|
701
|
+
// root frame), which is exactly Cancel.
|
|
702
|
+
(0, _useSurfaceHeader.useSurfaceHeader)({
|
|
703
|
+
title: t('editProfile.crop.title'),
|
|
704
|
+
largeTitle: false,
|
|
705
|
+
right: doneAction,
|
|
706
|
+
onBack: goBack
|
|
707
|
+
});
|
|
629
708
|
const styles = (0, _react.useMemo)(() => _reactNative.StyleSheet.create({
|
|
630
709
|
container: {
|
|
631
|
-
|
|
632
|
-
backgroundColor: CANVAS_BG
|
|
633
|
-
},
|
|
634
|
-
topBar: {
|
|
635
|
-
paddingTop: topInset,
|
|
636
|
-
paddingHorizontal: 12,
|
|
637
|
-
paddingBottom: 10,
|
|
638
|
-
flexDirection: 'row',
|
|
639
|
-
alignItems: 'center',
|
|
640
|
-
justifyContent: 'space-between',
|
|
641
|
-
backgroundColor: 'rgba(0,0,0,0.6)',
|
|
642
|
-
zIndex: 10
|
|
643
|
-
},
|
|
644
|
-
topBarTitleWrap: {
|
|
645
|
-
flex: 1,
|
|
646
|
-
alignItems: 'center',
|
|
647
|
-
justifyContent: 'center',
|
|
648
|
-
paddingHorizontal: 4
|
|
649
|
-
},
|
|
650
|
-
topBarTitle: {
|
|
651
|
-
color: '#ffffff',
|
|
652
|
-
fontSize: 17,
|
|
653
|
-
letterSpacing: -0.2,
|
|
654
|
-
...(_reactNative.Platform.OS === 'web' ? {
|
|
655
|
-
fontWeight: '600'
|
|
656
|
-
} : null)
|
|
657
|
-
},
|
|
658
|
-
cancelBtn: {
|
|
659
|
-
minWidth: 64,
|
|
660
|
-
paddingHorizontal: 10,
|
|
661
|
-
paddingVertical: 8,
|
|
662
|
-
borderRadius: 18,
|
|
663
|
-
alignItems: 'flex-start',
|
|
664
|
-
justifyContent: 'center'
|
|
665
|
-
},
|
|
666
|
-
cancelLabel: {
|
|
667
|
-
color: '#ffffff',
|
|
668
|
-
fontSize: 15,
|
|
669
|
-
opacity: 0.85
|
|
670
|
-
},
|
|
671
|
-
doneBtn: {
|
|
672
|
-
minWidth: 76,
|
|
673
|
-
paddingHorizontal: 14,
|
|
674
|
-
paddingVertical: 8,
|
|
675
|
-
borderRadius: 18,
|
|
676
|
-
alignItems: 'center',
|
|
677
|
-
justifyContent: 'center',
|
|
678
|
-
backgroundColor: theme.colors.primary
|
|
679
|
-
},
|
|
680
|
-
doneBtnDisabled: {
|
|
681
|
-
opacity: 0.5
|
|
682
|
-
},
|
|
683
|
-
doneLabel: {
|
|
684
|
-
color: '#ffffff',
|
|
685
|
-
fontSize: 15,
|
|
686
|
-
letterSpacing: -0.1,
|
|
687
|
-
...(_reactNative.Platform.OS === 'web' ? {
|
|
688
|
-
fontWeight: '600'
|
|
689
|
-
} : null)
|
|
690
|
-
},
|
|
691
|
-
doneLabelLoading: {
|
|
692
|
-
flexDirection: 'row',
|
|
693
|
-
alignItems: 'center',
|
|
694
|
-
justifyContent: 'center',
|
|
695
|
-
gap: 8
|
|
710
|
+
backgroundColor: theme.colors.background
|
|
696
711
|
},
|
|
697
712
|
stage: {
|
|
698
|
-
|
|
713
|
+
backgroundColor: CANVAS_BG,
|
|
714
|
+
borderRadius: STAGE_RADIUS,
|
|
715
|
+
overflow: 'hidden',
|
|
716
|
+
// Top padding clears the zoom chip, which floats 44dp above
|
|
717
|
+
// the crop frame and would otherwise be clipped by `overflow`.
|
|
718
|
+
paddingTop: 60,
|
|
719
|
+
paddingBottom: 28,
|
|
720
|
+
paddingHorizontal: 16,
|
|
699
721
|
alignItems: 'center',
|
|
700
|
-
justifyContent: 'center'
|
|
701
|
-
paddingHorizontal: 16
|
|
722
|
+
justifyContent: 'center'
|
|
702
723
|
},
|
|
703
724
|
cropFrame: {
|
|
704
725
|
width: VIEWPORT_SIZE,
|
|
@@ -783,17 +804,14 @@ const AvatarCropScreen = ({
|
|
|
783
804
|
letterSpacing: 0.2
|
|
784
805
|
},
|
|
785
806
|
helperBlock: {
|
|
786
|
-
|
|
787
|
-
paddingTop: 24,
|
|
788
|
-
paddingBottom: bottomInset,
|
|
807
|
+
paddingTop: 16,
|
|
789
808
|
alignItems: 'center',
|
|
790
|
-
|
|
791
|
-
gap: 10
|
|
809
|
+
gap: 8
|
|
792
810
|
},
|
|
793
811
|
helper: {
|
|
794
812
|
fontSize: 13,
|
|
795
813
|
lineHeight: 18,
|
|
796
|
-
color:
|
|
814
|
+
color: theme.colors.textSecondary,
|
|
797
815
|
textAlign: 'center',
|
|
798
816
|
maxWidth: 320
|
|
799
817
|
},
|
|
@@ -803,123 +821,72 @@ const AvatarCropScreen = ({
|
|
|
803
821
|
},
|
|
804
822
|
resetLinkText: {
|
|
805
823
|
fontSize: 13,
|
|
806
|
-
color:
|
|
807
|
-
opacity: 0.85,
|
|
824
|
+
color: theme.colors.primary,
|
|
808
825
|
textDecorationLine: 'underline'
|
|
809
826
|
},
|
|
810
827
|
emptyState: {
|
|
811
|
-
flex: 1,
|
|
812
828
|
alignItems: 'center',
|
|
813
829
|
justifyContent: 'center',
|
|
814
|
-
|
|
830
|
+
paddingVertical: 48
|
|
815
831
|
},
|
|
816
832
|
emptyLabel: {
|
|
817
833
|
fontSize: 14,
|
|
818
|
-
color:
|
|
834
|
+
color: theme.colors.textSecondary,
|
|
819
835
|
textAlign: 'center'
|
|
820
836
|
}
|
|
821
|
-
}), [baseFit,
|
|
837
|
+
}), [baseFit, theme]);
|
|
822
838
|
|
|
823
839
|
// Reset link reveal — only show when the image is not at the default
|
|
824
840
|
// transform. Use derived display state to avoid mounting/unmounting jank.
|
|
825
841
|
const resetLinkOpacity = isModified ? 1 : 0;
|
|
826
842
|
|
|
827
|
-
//
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
843
|
+
// Zoom-button state is derived from `zoomLabel` (reactive state, set on every
|
|
844
|
+
// commit) rather than the committed-scale ref, so it stays correct across
|
|
845
|
+
// renders without reading mutable state during render. The +/- pair is web-only
|
|
846
|
+
// (native has pinch).
|
|
847
|
+
const currentZoom = Number.parseFloat(zoomLabel) || MIN_SCALE;
|
|
848
|
+
const canZoomOut = currentZoom > MIN_SCALE + 0.001;
|
|
849
|
+
const canZoomIn = currentZoom < MAX_SCALE - 0.001;
|
|
850
|
+
const showZoomControls = _reactNative.Platform.OS === 'web';
|
|
851
|
+
const zoomOutDisabled = !canZoomOut || !baseFit || isProcessing;
|
|
852
|
+
const zoomInDisabled = !canZoomIn || !baseFit || isProcessing;
|
|
853
|
+
|
|
854
|
+
// No source at all (and none resolving), a resolution failure, or a
|
|
855
|
+
// measurement failure — render a minimal empty state. While a file ID is still
|
|
856
|
+
// resolving, fall THROUGH to the stage so its own spinner shows, not this.
|
|
857
|
+
// The surface's nav bar (back = cancel) is the only chrome needed here.
|
|
858
|
+
if (!effectiveUri && !isResolvingSource || sourceErrorMessage || measureErrorMessage) {
|
|
859
|
+
const emptyMessage = sourceErrorMessage ? sourceErrorMessage : measureErrorMessage ? measureErrorMessage : t('editProfile.crop.noImage');
|
|
860
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
831
861
|
style: styles.container,
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
835
|
-
accessibilityLabel: t('editProfile.crop.cancel') || 'Cancel',
|
|
836
|
-
accessibilityRole: "button",
|
|
837
|
-
style: styles.cancelBtn,
|
|
838
|
-
onPress: handleCancel,
|
|
839
|
-
activeOpacity: 0.6,
|
|
840
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
841
|
-
style: styles.cancelLabel,
|
|
842
|
-
children: t('editProfile.crop.cancel') || 'Cancel'
|
|
843
|
-
})
|
|
844
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
845
|
-
style: styles.topBarTitleWrap,
|
|
846
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
847
|
-
style: styles.topBarTitle,
|
|
848
|
-
children: t('editProfile.crop.title') || 'Crop avatar'
|
|
849
|
-
})
|
|
850
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
851
|
-
style: styles.cancelBtn
|
|
852
|
-
})]
|
|
853
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
862
|
+
className: "px-screen-margin pb-space-24",
|
|
863
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
854
864
|
style: styles.emptyState,
|
|
855
865
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
856
866
|
style: styles.emptyLabel,
|
|
857
867
|
children: emptyMessage
|
|
858
868
|
})
|
|
859
|
-
})
|
|
869
|
+
})
|
|
860
870
|
});
|
|
861
871
|
}
|
|
862
872
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
863
873
|
style: styles.container,
|
|
874
|
+
className: "px-screen-margin pb-space-24",
|
|
864
875
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
865
|
-
style: styles.topBar,
|
|
866
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
867
|
-
accessibilityLabel: t('editProfile.crop.cancel') || 'Cancel',
|
|
868
|
-
accessibilityRole: "button",
|
|
869
|
-
style: styles.cancelBtn,
|
|
870
|
-
onPress: handleCancel,
|
|
871
|
-
disabled: isProcessing,
|
|
872
|
-
activeOpacity: 0.6,
|
|
873
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
874
|
-
style: styles.cancelLabel,
|
|
875
|
-
children: t('editProfile.crop.cancel') || 'Cancel'
|
|
876
|
-
})
|
|
877
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
878
|
-
style: styles.topBarTitleWrap,
|
|
879
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
880
|
-
style: styles.topBarTitle,
|
|
881
|
-
numberOfLines: 1,
|
|
882
|
-
children: t('editProfile.crop.title') || 'Crop avatar'
|
|
883
|
-
})
|
|
884
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
885
|
-
accessibilityLabel: t('editProfile.crop.confirm') || 'Use photo',
|
|
886
|
-
accessibilityRole: "button",
|
|
887
|
-
accessibilityState: {
|
|
888
|
-
disabled: isProcessing || !baseFit,
|
|
889
|
-
busy: isProcessing
|
|
890
|
-
},
|
|
891
|
-
style: [styles.doneBtn, (isProcessing || !baseFit) && styles.doneBtnDisabled],
|
|
892
|
-
onPress: handleConfirm,
|
|
893
|
-
disabled: isProcessing || !baseFit,
|
|
894
|
-
activeOpacity: 0.85,
|
|
895
|
-
children: isProcessing ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
896
|
-
style: styles.doneLabelLoading,
|
|
897
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
|
|
898
|
-
size: "small",
|
|
899
|
-
color: "#ffffff"
|
|
900
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
901
|
-
style: styles.doneLabel,
|
|
902
|
-
children: t('editProfile.crop.saving') || 'Saving…'
|
|
903
|
-
})]
|
|
904
|
-
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
905
|
-
style: styles.doneLabel,
|
|
906
|
-
children: t('editProfile.crop.confirm') || 'Use photo'
|
|
907
|
-
})
|
|
908
|
-
})]
|
|
909
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
910
876
|
style: styles.stage,
|
|
911
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, {
|
|
877
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, {
|
|
878
|
+
ref: startEntrance,
|
|
912
879
|
style: [styles.cropFrame, cropFrameAnimatedStyle],
|
|
913
880
|
accessible: true,
|
|
914
881
|
accessibilityRole: "image",
|
|
915
|
-
accessibilityLabel: t('editProfile.crop.a11yImage')
|
|
882
|
+
accessibilityLabel: t('editProfile.crop.a11yImage'),
|
|
916
883
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeGestureHandler.GestureDetector, {
|
|
917
884
|
gesture: composedGesture,
|
|
918
885
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
919
886
|
style: styles.viewport,
|
|
920
|
-
children: baseFit ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.Image, {
|
|
887
|
+
children: baseFit && effectiveUri ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.Image, {
|
|
921
888
|
source: {
|
|
922
|
-
uri:
|
|
889
|
+
uri: effectiveUri
|
|
923
890
|
},
|
|
924
891
|
style: [styles.image, imageAnimatedStyle],
|
|
925
892
|
resizeMode: "cover"
|
|
@@ -957,17 +924,52 @@ const AvatarCropScreen = ({
|
|
|
957
924
|
style: styles.zoomChipText,
|
|
958
925
|
children: t('editProfile.crop.zoom', {
|
|
959
926
|
value: zoomLabel
|
|
960
|
-
})
|
|
927
|
+
})
|
|
961
928
|
})
|
|
962
929
|
})]
|
|
963
|
-
})
|
|
930
|
+
}), showZoomControls ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
931
|
+
className: "absolute bottom-4 left-0 right-0 items-center",
|
|
932
|
+
pointerEvents: "box-none",
|
|
933
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
934
|
+
className: "flex-row overflow-hidden rounded-full bg-black/70",
|
|
935
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
936
|
+
accessibilityRole: "button",
|
|
937
|
+
accessibilityLabel: t('editProfile.crop.zoomOut'),
|
|
938
|
+
accessibilityState: {
|
|
939
|
+
disabled: zoomOutDisabled
|
|
940
|
+
},
|
|
941
|
+
disabled: zoomOutDisabled,
|
|
942
|
+
onPress: () => applyZoom(-1),
|
|
943
|
+
className: `h-10 w-[52px] items-center justify-center active:bg-white/10${zoomOutDisabled ? ' opacity-40' : ''}`,
|
|
944
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
945
|
+
className: "text-white text-[22px] font-medium leading-[24px]",
|
|
946
|
+
children: '−'
|
|
947
|
+
})
|
|
948
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
949
|
+
className: "w-px self-stretch bg-white/25"
|
|
950
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
951
|
+
accessibilityRole: "button",
|
|
952
|
+
accessibilityLabel: t('editProfile.crop.zoomIn'),
|
|
953
|
+
accessibilityState: {
|
|
954
|
+
disabled: zoomInDisabled
|
|
955
|
+
},
|
|
956
|
+
disabled: zoomInDisabled,
|
|
957
|
+
onPress: () => applyZoom(1),
|
|
958
|
+
className: `h-10 w-[52px] items-center justify-center active:bg-white/10${zoomInDisabled ? ' opacity-40' : ''}`,
|
|
959
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
960
|
+
className: "text-white text-[22px] font-medium leading-[24px]",
|
|
961
|
+
children: '+'
|
|
962
|
+
})
|
|
963
|
+
})]
|
|
964
|
+
})
|
|
965
|
+
}) : null]
|
|
964
966
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
965
967
|
style: styles.helperBlock,
|
|
966
968
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
967
969
|
style: styles.helper,
|
|
968
|
-
children: t('editProfile.crop.helper')
|
|
970
|
+
children: t('editProfile.crop.helper')
|
|
969
971
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
970
|
-
accessibilityLabel: t('editProfile.crop.a11yReset')
|
|
972
|
+
accessibilityLabel: t('editProfile.crop.a11yReset'),
|
|
971
973
|
accessibilityRole: "button",
|
|
972
974
|
accessibilityState: {
|
|
973
975
|
disabled: !isModified || isProcessing
|
|
@@ -979,7 +981,7 @@ const AvatarCropScreen = ({
|
|
|
979
981
|
}],
|
|
980
982
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
981
983
|
style: styles.resetLinkText,
|
|
982
|
-
children: t('editProfile.crop.resetToCenter')
|
|
984
|
+
children: t('editProfile.crop.resetToCenter')
|
|
983
985
|
})
|
|
984
986
|
})]
|
|
985
987
|
})]
|