@oxyhq/services 22.12.1 → 22.12.2
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/FollowButton.js +5 -8
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/commonjs/ui/navigation/surfaceNavStack.js +8 -7
- package/lib/commonjs/ui/navigation/surfaceNavStack.js.map +1 -1
- package/lib/commonjs/ui/screens/ChangeAvatarScreen.js +48 -42
- package/lib/commonjs/ui/screens/ChangeAvatarScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/CreateAccountScreen.js +55 -98
- package/lib/commonjs/ui/screens/CreateAccountScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +21 -8
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js +14 -6
- package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
- package/lib/module/ui/components/FollowButton.js +5 -8
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/navigation/surfaceNavStack.js +8 -7
- package/lib/module/ui/navigation/surfaceNavStack.js.map +1 -1
- package/lib/module/ui/screens/ChangeAvatarScreen.js +48 -42
- package/lib/module/ui/screens/ChangeAvatarScreen.js.map +1 -1
- package/lib/module/ui/screens/CreateAccountScreen.js +58 -101
- package/lib/module/ui/screens/CreateAccountScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +21 -8
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js +14 -6
- package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
- package/lib/typescript/commonjs/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/surfaceNavStack.d.ts +7 -4
- package/lib/typescript/commonjs/ui/navigation/surfaceNavStack.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/ChangeAvatarScreen.d.ts +9 -5
- package/lib/typescript/commonjs/ui/screens/ChangeAvatarScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/CreateAccountScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.d.ts +3 -1
- package/lib/typescript/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/types/fileManagement.d.ts +10 -0
- package/lib/typescript/commonjs/ui/types/fileManagement.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/surfaceNavStack.d.ts +7 -4
- package/lib/typescript/module/ui/navigation/surfaceNavStack.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/ChangeAvatarScreen.d.ts +9 -5
- package/lib/typescript/module/ui/screens/ChangeAvatarScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/CreateAccountScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/fileManagement/hooks/useFileUploadState.d.ts +3 -1
- package/lib/typescript/module/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
- package/lib/typescript/module/ui/types/fileManagement.d.ts +10 -0
- package/lib/typescript/module/ui/types/fileManagement.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ui/components/FollowButton.tsx +5 -8
- package/src/ui/navigation/__tests__/surfaceNavStack.test.ts +49 -17
- package/src/ui/navigation/surfaceNavStack.ts +15 -11
- package/src/ui/screens/ChangeAvatarScreen.tsx +46 -39
- package/src/ui/screens/CreateAccountScreen.tsx +123 -167
- package/src/ui/screens/FileManagementScreen.tsx +21 -8
- package/src/ui/screens/fileManagement/hooks/useFileUploadState.ts +16 -6
- package/src/ui/types/fileManagement.ts +10 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreateAccountScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/CreateAccountScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"CreateAccountScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/CreateAccountScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAqB,CAAC;AAiF3D;;;;;GAKG;AACH,QAAA,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAsQlD,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileManagementScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/FileManagementScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAe/B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAAyB,CAAC;AAiEzE,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,
|
|
1
|
+
{"version":3,"file":"FileManagementScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/FileManagementScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAe/B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAAyB,CAAC;AAiEzE,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAyzC7D,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
|
|
@@ -16,6 +16,8 @@ export interface UseFileUploadStateParams {
|
|
|
16
16
|
multiSelect: boolean;
|
|
17
17
|
afterSelect: 'close' | 'back' | 'none';
|
|
18
18
|
onSelect?: (file: FileMetadata) => void;
|
|
19
|
+
/** Forward-navigation picker: see {@link FileManagementScreenProps.onPicked}. */
|
|
20
|
+
onPicked?: (file: FileMetadata) => void;
|
|
19
21
|
goBack?: () => void;
|
|
20
22
|
onClose?: () => void;
|
|
21
23
|
selectedIds: Set<string>;
|
|
@@ -42,6 +44,6 @@ export interface UseFileUploadStateResult {
|
|
|
42
44
|
* orchestrator simply threads in the values these handlers depend on and
|
|
43
45
|
* consumes the returned state/handlers.
|
|
44
46
|
*/
|
|
45
|
-
export declare const useFileUploadState: ({ targetUserId, uploadFileMutation, defaultVisibility, selectMode, multiSelect, afterSelect, onSelect, goBack, onClose, selectedIds, setSelectedIds, t, }: UseFileUploadStateParams) => UseFileUploadStateResult;
|
|
47
|
+
export declare const useFileUploadState: ({ targetUserId, uploadFileMutation, defaultVisibility, selectMode, multiSelect, afterSelect, onSelect, onPicked, goBack, onClose, selectedIds, setSelectedIds, t, }: UseFileUploadStateParams) => UseFileUploadStateResult;
|
|
46
48
|
export default useFileUploadState;
|
|
47
49
|
//# sourceMappingURL=useFileUploadState.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFileUploadState.d.ts","sourceRoot":"","sources":["../../../../../../../src/ui/screens/fileManagement/hooks/useFileUploadState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EAAoB,YAAY,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iDAA8C,CAAC;AAQlF,OAAO,EAEH,KAAK,iBAAiB,EAQzB,MAAM,cAAW,CAAC;AAEnB,qDAAqD;AACrD,MAAM,WAAW,2BAA2B;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACjB;AAsBD,8EAA8E;AAC9E,MAAM,WAAW,wBAAwB;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;IACrD,iBAAiB,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;IACrD,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACvC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACxC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,cAAc,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAClE,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAC;CACtE;AAED,mDAAmD;AACnD,MAAM,WAAW,wBAAwB;IACrC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAClC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,uEAAuE;IACvE,SAAS,EAAE,OAAO,CAAC;IACnB,6DAA6D;IAC7D,cAAc,EAAE,2BAA2B,GAAG,IAAI,CAAC;IACnD,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,mBAAmB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,iBAAiB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,
|
|
1
|
+
{"version":3,"file":"useFileUploadState.d.ts","sourceRoot":"","sources":["../../../../../../../src/ui/screens/fileManagement/hooks/useFileUploadState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EAAoB,YAAY,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iDAA8C,CAAC;AAQlF,OAAO,EAEH,KAAK,iBAAiB,EAQzB,MAAM,cAAW,CAAC;AAEnB,qDAAqD;AACrD,MAAM,WAAW,2BAA2B;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACjB;AAsBD,8EAA8E;AAC9E,MAAM,WAAW,wBAAwB;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;IACrD,iBAAiB,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;IACrD,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACvC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACxC,iFAAiF;IACjF,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACxC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,cAAc,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAClE,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAC;CACtE;AAED,mDAAmD;AACnD,MAAM,WAAW,wBAAwB;IACrC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAClC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,uEAAuE;IACvE,SAAS,EAAE,OAAO,CAAC;IACnB,6DAA6D;IAC7D,cAAc,EAAE,2BAA2B,GAAG,IAAI,CAAC;IACnD,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,mBAAmB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,iBAAiB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,qKAchC,wBAAwB,KAAG,wBAsZ7B,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
|
|
@@ -7,6 +7,16 @@ export interface FileManagementScreenProps extends BaseScreenProps {
|
|
|
7
7
|
selectMode?: boolean;
|
|
8
8
|
multiSelect?: boolean;
|
|
9
9
|
onSelect?: (file: FileMetadata) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Forward-navigation picker: called with the picked file in single-select
|
|
12
|
+
* mode when the picker is one FRAME of a larger in-surface flow. Unlike
|
|
13
|
+
* {@link onSelect}, the caller is expected to NAVIGATE the surface onward
|
|
14
|
+
* (e.g. the avatar flow pushes the cropper on top of this picker) — so on
|
|
15
|
+
* select the picker neither dismisses nor closes the session, and its
|
|
16
|
+
* Cancel/back pops THIS frame (returning to the frame that opened the picker)
|
|
17
|
+
* rather than closing. Takes precedence over `onSelect`/`dismiss` when set.
|
|
18
|
+
*/
|
|
19
|
+
onPicked?: (file: FileMetadata) => void;
|
|
10
20
|
onConfirmSelection?: OnConfirmFileSelection;
|
|
11
21
|
initialSelectedIds?: string[];
|
|
12
22
|
maxSelection?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileManagement.d.ts","sourceRoot":"","sources":["../../../../../src/ui/types/fileManagement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAc,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,MAAM,MAAM,sBAAsB,GAAG,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;AAErE,MAAM,WAAW,yBAA0B,SAAQ,eAAe;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IAExC,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;IAE5C,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACxC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;IACtD;;;OAGG;IACH,WAAW,CAAC,EAAE;QACV,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;CACL;AAED,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAC;AAC3E,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AACvD,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,MAAM,CAAC"}
|
|
1
|
+
{"version":3,"file":"fileManagement.d.ts","sourceRoot":"","sources":["../../../../../src/ui/types/fileManagement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAc,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,MAAM,MAAM,sBAAsB,GAAG,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;AAErE,MAAM,WAAW,yBAA0B,SAAQ,eAAe;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACxC;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IAExC,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;IAE5C,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACxC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;IACtD;;;OAGG;IACH,WAAW,CAAC,EAAE;QACV,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;CACL;AAED,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAC;AAC3E,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AACvD,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,MAAM,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/services",
|
|
3
|
-
"version": "22.12.
|
|
3
|
+
"version": "22.12.2",
|
|
4
4
|
"description": "OxyHQ Expo/React Native SDK — UI components, screens, and native features",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
},
|
|
128
128
|
"dependencies": {
|
|
129
129
|
"@oxyhq/contracts": "0.17.0",
|
|
130
|
-
"@oxyhq/core": "^12.10.
|
|
130
|
+
"@oxyhq/core": "^12.10.4",
|
|
131
131
|
"@simplewebauthn/browser": "^13.3.0",
|
|
132
132
|
"color": "^4.2.3"
|
|
133
133
|
},
|
|
@@ -136,7 +136,7 @@
|
|
|
136
136
|
"@commitlint/cli": "^17.6.5",
|
|
137
137
|
"@commitlint/config-conventional": "^17.6.5",
|
|
138
138
|
"@expo/vector-icons": "^15.0.3",
|
|
139
|
-
"@oxyhq/core": "12.10.
|
|
139
|
+
"@oxyhq/core": "12.10.4",
|
|
140
140
|
"@react-native-async-storage/async-storage": "^2.0.0",
|
|
141
141
|
"@react-native-community/netinfo": "^11.4.1",
|
|
142
142
|
"@react-native/babel-preset": "^0.86.0",
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
},
|
|
177
177
|
"peerDependencies": {
|
|
178
178
|
"@expo/vector-icons": "^15.0.3",
|
|
179
|
-
"@oxyhq/bloom": "^0.50.
|
|
179
|
+
"@oxyhq/bloom": "^0.50.1",
|
|
180
180
|
"@oxyhq/core": "^12.10.0",
|
|
181
181
|
"@react-native-async-storage/async-storage": "^2.0.0",
|
|
182
182
|
"@react-native-community/netinfo": "^11.4.1",
|
|
@@ -178,17 +178,14 @@ const FollowButtonMultiInner = memo(function FollowButtonMultiInner({
|
|
|
178
178
|
|
|
179
179
|
const isLoading = isSubmitting || isAnyLoading;
|
|
180
180
|
|
|
181
|
-
// Populate the store with each member's follow status
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
// memoizes `multiUserIds`), so depending on it plus the stable joined-string
|
|
186
|
-
// key cannot self-retrigger — `allFollowing`/loading are intentionally NOT in
|
|
181
|
+
// Populate the store with each member's follow status on mount and whenever
|
|
182
|
+
// the target set changes. `fetchAllStatuses` is recreated when `userIds`
|
|
183
|
+
// changes inside `useFollow`, so it is the sole effect dependency — no need
|
|
184
|
+
// for a joined-string key. `allFollowing`/loading are intentionally NOT in
|
|
187
185
|
// the deps.
|
|
188
|
-
const userIdsKey = useMemo(() => userIds.join(','), [userIds]);
|
|
189
186
|
useEffect(() => {
|
|
190
187
|
fetchAllStatuses?.();
|
|
191
|
-
}, [
|
|
188
|
+
}, [fetchAllStatuses]);
|
|
192
189
|
|
|
193
190
|
const handlePress = useCallback(async (event?: { preventDefault?: () => void; stopPropagation?: () => void }) => {
|
|
194
191
|
if (preventParentActions && event?.preventDefault) {
|
|
@@ -45,8 +45,9 @@ describe('createSurfaceNavStack', () => {
|
|
|
45
45
|
expect(stack.store.getState().closeResult).toBe('done');
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
describe('result-bearing sub-flow (morphed-in avatar
|
|
48
|
+
describe('result-bearing sub-flow (morphed-in avatar flow)', () => {
|
|
49
49
|
it('resolves with the descendant dismiss result and pops back to the caller frame', async () => {
|
|
50
|
+
// Device / camera path: ChangeAvatar navigates straight to the cropper.
|
|
50
51
|
const stack = createSurfaceNavStack('EditProfile');
|
|
51
52
|
const flow = stack.beginFlow('ChangeAvatar');
|
|
52
53
|
expect(stack.getTop().route).toBe('ChangeAvatar');
|
|
@@ -84,39 +85,70 @@ describe('createSurfaceNavStack', () => {
|
|
|
84
85
|
await expect(flow).resolves.toBeUndefined();
|
|
85
86
|
});
|
|
86
87
|
|
|
87
|
-
it('
|
|
88
|
-
//
|
|
88
|
+
it('My Oxy files: the picker and cropper are FORWARD frames; the cropper confirm resolves the flow', async () => {
|
|
89
|
+
// EditProfile → (flow) ChangeAvatar → FileManagement picker → AvatarCrop, all
|
|
90
|
+
// plain forward frames in the ONE avatar flow. Selecting a file forward-
|
|
91
|
+
// navigates to the cropper (it does NOT resolve the flow); the cropper's
|
|
92
|
+
// confirm resolves the flow and pops all the way back to EditProfile.
|
|
89
93
|
const stack = createSurfaceNavStack('EditProfile');
|
|
90
|
-
const
|
|
91
|
-
|
|
94
|
+
const flow = stack.beginFlow('ChangeAvatar');
|
|
95
|
+
stack.navigate('FileManagement', { selectMode: true }); // "My Oxy files" picker
|
|
96
|
+
stack.navigate('AvatarCrop', { imageFileId: 'file-1' }); // forward-nav on pick
|
|
97
|
+
stack.resolveFlowOrDismiss({ uri: 'file:///cropped.jpg' }); // cropper confirm
|
|
98
|
+
await expect(flow).resolves.toEqual({ uri: 'file:///cropped.jpg' });
|
|
99
|
+
expect(stack.getTop().route).toBe('EditProfile');
|
|
100
|
+
expect(stack.store.getState().closing).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('My Oxy files: Back unwinds cropper → picker → list, then cancels the flow', async () => {
|
|
104
|
+
// The bug this fixes: Back from the cropper must return to the PICKER (re-pick),
|
|
105
|
+
// not straight to the ChangeAvatar list.
|
|
106
|
+
const stack = createSurfaceNavStack('EditProfile');
|
|
107
|
+
const flow = stack.beginFlow('ChangeAvatar');
|
|
108
|
+
stack.navigate('FileManagement', { selectMode: true });
|
|
109
|
+
stack.navigate('AvatarCrop', { imageFileId: 'file-1' });
|
|
110
|
+
expect(stack.goBack()).toBe(true); // crop -> picker (re-pick)
|
|
111
|
+
expect(stack.getTop().route).toBe('FileManagement');
|
|
112
|
+
expect(stack.goBack()).toBe(true); // picker -> ChangeAvatar
|
|
113
|
+
expect(stack.getTop().route).toBe('ChangeAvatar');
|
|
114
|
+
expect(stack.goBack()).toBe(true); // ChangeAvatar -> EditProfile (cancels flow)
|
|
115
|
+
await expect(flow).resolves.toBeUndefined();
|
|
116
|
+
expect(stack.getTop().route).toBe('EditProfile');
|
|
117
|
+
expect(stack.store.getState().closing).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// The stack still supports NESTED flows generically, even though no current
|
|
121
|
+
// caller opens a flow inside another (the avatar flow uses one flow + forward
|
|
122
|
+
// frames). These guard that capability.
|
|
123
|
+
it('supports NESTED flows: the inner flow resolves first, then the outer one', async () => {
|
|
124
|
+
const stack = createSurfaceNavStack('EditProfile');
|
|
125
|
+
const outer = stack.beginFlow('ChangeAvatar');
|
|
126
|
+
const inner = stack.beginFlow('FileManagement');
|
|
92
127
|
expect(stack.getTop().route).toBe('FileManagement');
|
|
93
|
-
// Pick a file → inner flow resolves, pops back to ChangeAvatar (outer still open).
|
|
94
128
|
stack.resolveFlowOrDismiss({ id: 'file-1' });
|
|
95
|
-
await expect(
|
|
129
|
+
await expect(inner).resolves.toEqual({ id: 'file-1' });
|
|
96
130
|
expect(stack.getTop().route).toBe('ChangeAvatar');
|
|
97
131
|
expect(stack.store.getState().closing).toBe(false);
|
|
98
|
-
// Now drill to crop and confirm → outer flow resolves, pops to EditProfile.
|
|
99
132
|
stack.navigate('AvatarCrop');
|
|
100
133
|
stack.resolveFlowOrDismiss({ uri: 'file:///c.jpg' });
|
|
101
|
-
await expect(
|
|
134
|
+
await expect(outer).resolves.toEqual({ uri: 'file:///c.jpg' });
|
|
102
135
|
expect(stack.getTop().route).toBe('EditProfile');
|
|
103
136
|
});
|
|
104
137
|
|
|
105
|
-
it('
|
|
138
|
+
it('backing out of a NESTED inner flow leaves the outer flow pending', async () => {
|
|
106
139
|
const stack = createSurfaceNavStack('EditProfile');
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
expect(stack.goBack()).toBe(true); //
|
|
110
|
-
await expect(
|
|
140
|
+
const outer = stack.beginFlow('ChangeAvatar');
|
|
141
|
+
const inner = stack.beginFlow('FileManagement');
|
|
142
|
+
expect(stack.goBack()).toBe(true); // inner entry backed out -> cancels inner only
|
|
143
|
+
await expect(inner).resolves.toBeUndefined();
|
|
111
144
|
expect(stack.getTop().route).toBe('ChangeAvatar');
|
|
112
|
-
// The outer avatar flow is still pending.
|
|
113
145
|
let outerSettled = false;
|
|
114
|
-
void
|
|
146
|
+
void outer.then(() => { outerSettled = true; });
|
|
115
147
|
await Promise.resolve();
|
|
116
148
|
expect(outerSettled).toBe(false);
|
|
117
149
|
});
|
|
118
150
|
|
|
119
|
-
it('tearing down the surface abandons ALL
|
|
151
|
+
it('tearing down the surface abandons ALL pending flows (undefined)', async () => {
|
|
120
152
|
const stack = createSurfaceNavStack('EditProfile');
|
|
121
153
|
const outer = stack.beginFlow('ChangeAvatar');
|
|
122
154
|
const inner = stack.beginFlow('FileManagement');
|
|
@@ -54,10 +54,13 @@ export interface SurfaceNavStack {
|
|
|
54
54
|
* `dismiss(result)` resolves it with `result`, backing out of the flow's entry
|
|
55
55
|
* frame resolves it with `undefined` (cancelled) — popping the surface back to
|
|
56
56
|
* the frame that started the flow WITHOUT dismissing it. This is how a screen
|
|
57
|
-
* already living inside a surface (EditProfile, ManageAccount
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* Oxy files"
|
|
57
|
+
* already living inside a surface (EditProfile, ManageAccount) opens the avatar
|
|
58
|
+
* flow as a MORPH instead of a stacked surface while still awaiting its result.
|
|
59
|
+
* The avatar flow is ONE flow (`ChangeAvatar`) with plain forward frames pushed
|
|
60
|
+
* on top (the "My Oxy files" `FileManagement` picker, then `AvatarCrop`); the
|
|
61
|
+
* cropper's `dismiss(result)` resolves THIS flow. Flows are kept as a STACK so
|
|
62
|
+
* they can nest generically, even though no current caller opens a flow inside
|
|
63
|
+
* another.
|
|
61
64
|
*/
|
|
62
65
|
beginFlow(route: RouteName, props?: Record<string, unknown>): Promise<unknown>;
|
|
63
66
|
/**
|
|
@@ -94,13 +97,14 @@ export function createSurfaceNavStack(
|
|
|
94
97
|
return frames[frames.length - 1];
|
|
95
98
|
};
|
|
96
99
|
|
|
97
|
-
// Result-bearing sub-flows currently running in this surface (the morphed-
|
|
98
|
-
// avatar
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
// re-render frames). Each flow's `returnLength`
|
|
102
|
-
// entry frame was pushed, so settling pops back to
|
|
103
|
-
// started it. `flowStack` is ordered outer → inner; the
|
|
100
|
+
// Result-bearing sub-flows currently running in this surface (e.g. the morphed-
|
|
101
|
+
// in avatar flow, whose `ChangeAvatar → FileManagement picker → AvatarCrop`
|
|
102
|
+
// frames are plain forward pushes ON TOP of the ONE flow entry frame). Held as a
|
|
103
|
+
// STACK so flows can nest generically, and OUTSIDE the store (resolvers are
|
|
104
|
+
// functions; flow presence must not re-render frames). Each flow's `returnLength`
|
|
105
|
+
// is the frame count BEFORE its entry frame was pushed, so settling pops back to
|
|
106
|
+
// exactly the frame that started it. `flowStack` is ordered outer → inner; the
|
|
107
|
+
// top is the innermost.
|
|
104
108
|
const flowStack: { returnLength: number; resolve: (result: unknown) => void }[] = [];
|
|
105
109
|
|
|
106
110
|
const requestDismiss = (result?: unknown): void => {
|
|
@@ -4,11 +4,15 @@
|
|
|
4
4
|
* Shows the current avatar large, then the four sources the user can pick from:
|
|
5
5
|
* device gallery, camera, their existing Oxy files, and removing the current
|
|
6
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
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* `AvatarCrop` (never presents it on top), so the panel morphs from frame to
|
|
8
|
+
* frame instead of stacking sheets. The device / camera sources navigate straight
|
|
9
|
+
* to the cropper; the "My Oxy files" source first navigates to the `FileManagement`
|
|
10
|
+
* picker frame, which then forward-navigates to the cropper on selection — a real
|
|
11
|
+
* three-frame stack (this list → picker → cropper). So Cancel on the cropper
|
|
12
|
+
* returns to whichever frame opened it (the picker when re-picking from Oxy files,
|
|
13
|
+
* this list otherwise), and Cancel on the picker returns here. The forward morph
|
|
14
|
+
* is flash-free because only the top frame renders and the cropper does all its
|
|
15
|
+
* own async work (URL resolution + measurement), so no frame is left on screen
|
|
12
16
|
* waiting.
|
|
13
17
|
*
|
|
14
18
|
* The surface resolves with an {@link AvatarCropResult} (the crop confirmed) or
|
|
@@ -37,10 +41,10 @@ import { surfaces as bloomSurfaces } from '@oxyhq/bloom/surfaces';
|
|
|
37
41
|
import { useTheme } from '@oxyhq/bloom/theme';
|
|
38
42
|
import { toast } from '@oxyhq/bloom';
|
|
39
43
|
import { getAccountDisplayName, logger } from '@oxyhq/core';
|
|
44
|
+
import type { FileMetadata } from '@oxyhq/core';
|
|
40
45
|
import { useOxy } from '../context/OxyContext';
|
|
41
46
|
import { useI18n } from '../hooks/useI18n';
|
|
42
47
|
import { useSurfaceHeader } from '../hooks/useSurfaceHeader';
|
|
43
|
-
import { openWithinOrPresent } from '../navigation/surfaces';
|
|
44
48
|
import { SettingsIcon } from '../components/SettingsIcon';
|
|
45
49
|
import type { BaseScreenProps } from '../types/navigation';
|
|
46
50
|
|
|
@@ -229,44 +233,47 @@ const ChangeAvatarScreen: React.FC<BaseScreenProps> = ({ navigate, dismiss }) =>
|
|
|
229
233
|
);
|
|
230
234
|
|
|
231
235
|
/**
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
+
* Forward the file the user picked from their Oxy library on to the crop
|
|
237
|
+
* editor. Given to the `FileManagement` picker as its `onPicked` handler, so
|
|
238
|
+
* SELECTING a file pushes the cropper ON TOP of the picker frame — the picker
|
|
239
|
+
* stays below, and Cancel on the cropper returns to it to re-pick.
|
|
240
|
+
*
|
|
241
|
+
* The file's ID is handed straight to the cropper WITHOUT resolving its
|
|
242
|
+
* (usually private) download URL here: the cropper resolves it and shows its
|
|
243
|
+
* own loading state on the dark canvas, so the picker never lingers for the
|
|
244
|
+
* network round-trip (the "flash back to the avatar screen" the resolve-here
|
|
245
|
+
* approach caused).
|
|
236
246
|
*/
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const file = await openWithinOrPresent('FileManagement', {
|
|
242
|
-
selectMode: true,
|
|
243
|
-
multiSelect: false,
|
|
244
|
-
disabledMimeTypes: NON_IMAGE_MIME_TYPES,
|
|
245
|
-
});
|
|
246
|
-
if (!file) return;
|
|
247
|
-
|
|
247
|
+
const handleOxyFilePicked = useCallback(
|
|
248
|
+
(file: FileMetadata) => {
|
|
249
|
+
// The image-only picker never surfaces non-images, but guard
|
|
250
|
+
// defensively — a non-image would break the cropper.
|
|
248
251
|
if (!file.contentType?.startsWith('image/')) {
|
|
249
252
|
toast.error(t('editProfile.toasts.selectImage'));
|
|
250
253
|
return;
|
|
251
254
|
}
|
|
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
255
|
goToCrop({ imageFileId: file.id });
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
256
|
+
},
|
|
257
|
+
[goToCrop, t],
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Pick from the files the user already has on Oxy. Navigates WITHIN this
|
|
262
|
+
* surface to the image-only `FileManagement` picker as a real frame (not a
|
|
263
|
+
* resolve-and-pop sub-flow): selecting a file forward-navigates to the crop
|
|
264
|
+
* editor via {@link handleOxyFilePicked}, giving a three-frame stack
|
|
265
|
+
* (ChangeAvatar → picker → crop). Back from the cropper returns to the picker
|
|
266
|
+
* to re-pick; back from the picker returns here. The cropper's confirm
|
|
267
|
+
* resolves the whole avatar flow with the crop result.
|
|
268
|
+
*/
|
|
269
|
+
const pickFromOxyFiles = useCallback(() => {
|
|
270
|
+
navigate?.('FileManagement', {
|
|
271
|
+
selectMode: true,
|
|
272
|
+
multiSelect: false,
|
|
273
|
+
disabledMimeTypes: NON_IMAGE_MIME_TYPES,
|
|
274
|
+
onPicked: handleOxyFilePicked,
|
|
275
|
+
});
|
|
276
|
+
}, [navigate, handleOxyFilePicked]);
|
|
270
277
|
|
|
271
278
|
/** Confirm, then resolve the surface with the removal so the caller writes it. */
|
|
272
279
|
const removeCurrentPhoto = useCallback(async () => {
|
|
@@ -341,7 +348,7 @@ const ChangeAvatarScreen: React.FC<BaseScreenProps> = ({ navigate, dismiss }) =>
|
|
|
341
348
|
title={t('changeAvatar.sources.files.title')}
|
|
342
349
|
description={t('changeAvatar.sources.files.description')}
|
|
343
350
|
disabled={busy}
|
|
344
|
-
onPress={() =>
|
|
351
|
+
onPress={() => pickFromOxyFiles()}
|
|
345
352
|
/>
|
|
346
353
|
<SettingsListItem
|
|
347
354
|
icon={<SettingsIcon name="trash-can-outline" color={bloomTheme.colors.error} />}
|