feat: add image crop picker module (OK-48227) - #117
Conversation
Add @onekeyfe/react-native-image-crop-picker, a Nitro replacement for react-native-image-crop-picker 0.51.1. app-monorepo installs it under the react-native-image-crop-picker npm alias, so imports stay the same. It keeps openPicker, openCropper, clean, cleanSingle and the E_* rejection codes that OneKey uses, and drops multiple selection, video and the camera. iOS picks with PHPickerViewController, which runs out of process and needs no photo library permission (OK-48227). The old library asked for full library access first, so after one "Don't Allow" iOS never asked again and every later openPicker call rejected with E_NO_LIBRARY_PERMISSION, which the OneKey ID avatar and hardware wallpaper entries swallowed. Cropping uses a vendored TOCropViewController 3.2.0, up from the 2.8.0 the app carried as a pod override, with the OK-51551 crop box shrink fix re-applied; upstream still has that bug. Android ports the existing flow to Kotlin: the system Photo Picker, which needs no storage or media permission, and uCrop 2.2.11-native. Activity results go through the activity's ActivityResultRegistry instead of an ActivityEventListener. - decode photos at most 4096 px on the long side - scale aspect-locked crops to exactly the requested size - add an example page and the 3.0.146 changelog entry Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bump all 41 publishable packages, including the new image crop picker, to 3.0.147. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
15da3d1 to
b21fdcd
Compare
DDLogFileManagerDefault defaults logFilesDiskQuota to 20 MB, the same size as the configured maximumFileSize, so a single rolled file already filled the quota. Cleanup then deleted every log file — its "don't delete the active file" guard only covers unarchived files — and the logger wrote nothing for the rest of the session, so exported log bundles came back with no .log files at all. Size the quota after the retention above (7 files x 20 MB), matching Android's TOTAL_SIZE_CAP. Reproduced on a simulator with a function-trace build, which fills 20 MB in about 15 seconds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tapping Cancel on the iOS NFC sheet right after a Lite card connected
crashed the app with SIGABRT in RCTTurboModule.mm ("Callback arg cannot be
called more than once"), seen in the backup and restore flow on 6.6.0.
The user's cancel invalidates the session, and didInvalidateWithError:
reported it as a cancel because sessionType is only reset after the whole
card operation returns. The card operation was still running on another
thread of the concurrent delegate queue. Its APDUs then failed and it
reported a connection failure through the same React Native callback, which
aborts on the second call.
- Take each completion out of completionBlocks under a lock when it is
delivered, so the card operation and the invalidation can't both
deliver it.
- Don't treat the app's own invalidateSession as a user cancel. CoreNFC
reports it with the same code 200, which could race a finished read and
report it as cancelled.
- Report a connection failure when connectToTag fails or the card is not
a Lite V1/V2. These paths used to end the session and rely on the racy
cancel report, or leave the JS promise pending.
- Wrap the React Native callbacks so any repeated result is logged and
dropped instead of aborting the app.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The iOS cropper was TOCropViewController's own screen: a dark toolbar with plain-text Cancel and Done that ignored the app theme. Android showed uCrop's screen, with a toolbar check mark and aspect, rotate and scale tabs, so the two platforms looked unrelated to each other and to the app. Both platforms now show the same page, drawn by this package: - iOS hosts TOCropView in ImageCropperViewController, Android hosts uCrop's UCropView in ImageCropperActivity. Both have a header with the title and OneKey's rotate icon, the crop area with 20pt margins, and a footer with capsule Cancel and Confirm buttons that match the app's large Button, including pressed colors and a loading spinner. - cropperAppearance sets the color scheme, colors, fonts and a size scale. Colors are CSS hex strings; anything left out falls back to OneKey's light or dark palette. - The area outside the crop box shows the page background at 70% instead of a dark blur or black dimming. Corner handles only show when the box can be resized, and the grid only shows while the image is moved. - Android draws edge to edge with bar icons that follow the scheme, keeps the caller's requested orientation, animates the rotate button and drops two-finger rotation, which iOS never had. - TOCropOverlayView gains frameColor, gridColor and cornerHandlesHidden. - Remove the options that styled the old screens. Also reopen the cropper on iOS when the same photo is tapped again after cancelling it. PHPicker keeps the photo selected when the cropper is dismissed back to it, so the first tap only deselected it. On iOS 17 and later the selection is now cleared; asking for asset identifiers needs no photo library permission. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@codex review |
|
@codex security review |
|
Cursor review failed. Remote error: |
|
Skipping Bugbot: Bugbot is disabled for this repository. Visit the Bugbot dashboard to update your settings. |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a2a43c278c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Summary
@onekeyfe/react-native-image-crop-picker, a Nitro module that replacesreact-native-image-crop-picker0.51.1 in app-monorepo. It is installed under thereact-native-image-crop-pickernpm alias, so app imports stay the same.PHPickerViewController, which needs no photo library permission. This is the fix for OK-48227: the old library asked for full library access first, and after one accidental "Don't Allow" iOS never asked again, so the OneKey ID avatar and hardware wallpaper entries did nothing on every later tap.podgit override), with the OK-51551 crop-box shrink fix re-applied. app-monorepo can drop its Podfile override.2.2.11-native, the latest release.Root cause (OK-48227)
react-native-image-crop-picker's iOSopenPickercalls[PHPhotoLibrary requestAuthorization:]before showing its ownQBImagePickerController, and rejects withE_NO_LIBRARY_PERMISSIONwhen the status is notauthorized. iOS shows that system prompt only once. After a user taps "Don't Allow", every later call rejects immediately, and the app has no way to re-request.HardwareHomeScreenModalawaits the promise without acatch, andProfileEditPagemaps unknown errors to a toast, so the button appeared dead — which is what the OK-48227 recording shows.PHPickerViewControllerruns in a separate process and returns only the items the user picked, so it needs no authorization at all and the denied state cannot block it.API
openPicker,openCropper,clean,cleanSingle, and theE_*codes OneKey relies on (E_PICKER_CANCELLEDand friends) are preserved; rejections areImageCropPickerErrorinstances with acode. Multiple selection, video, the camera andincludeExifare dropped — OneKey never used them.mediaType,forceJpgandsortOrderare accepted and ignored.Behavior notes:
<tmp>/react-native-image-crop-picker/(iOS) or<cache>/react-native-image-crop-picker/(Android).width/heightlock the crop aspect ratio, and the crop is scaled to exactly that size; free-style crops fit inside it.E_PICKER_IN_PROGRESS.ActivityResultRegistry, so noActivityEventListeneris involved and the background React runtime does not matter.Verification
app-monorepo
x(1ba681f9f6) on an iPhone 17 Pro simulator (iOS 26.5), Developer → Gallery → ImageCrop, with Photos access denied for the app the whole time (kTCCServicePhotos|so.onekey.wallet|0).Baseline, the unmodified branch with
react-native-image-crop-picker0.51.1:Uncaught (in promise): Error: User did not grant library permission.This package (local build of this branch, installed into app-monorepo):
{width: 500, height: 500, mime: "image/jpeg", size: 77531}with base64 data and the file undertmp/react-native-image-crop-picker/.sipsconfirms the saved JPEG is 500×500.openCropper, both reject withE_PICKER_CANCELLED: User cancelled image selection.openPickeropened returns to the picker, and picking another photo opens the cropper again.openCropperon the previous result'sfile://path opens the cropper with a 3:2 box and returns a JPEG of that size.Android, on an API 36 emulator with the same Gallery page:
com.google.android.photopickerwith no permission dialog ("OneKey Dev will only have access to the photos you select").com.yalantis.ucrop.UCropActivitywith the locked 1:1 box, and confirming returns{width: 500, height: 500, mime: "image/jpeg", size: 29389}with base64 data, acropRect, the media-storefilename, and the file under the app'scache/react-native-image-crop-picker/.openCropperon that result'sfile://path opens uCrop again and returns a 3:2 JPEG.E_PICKER_CANCELLED: User cancelled image selection.Not covered: real devices, iPad, iOS versions below 26, Android versions without the system Photo Picker, and the OneKey ID avatar / hardware wallpaper screens themselves (both call the same
ImageCrop.openPicker).Release
4222e4cc7b21fdcd7aRebased onto main after #116, so 3.0.147 carries that Android font fix. Published from this branch with the
latestdist-tag in run 35259897741; the run's registry check passed.The published
@onekeyfe/react-native-image-crop-picker@3.0.147tarball'sios/,android/src/andsrc/index.tsxare byte-identical to this branch. app-monorepo consumes it in app-monorepo#13553, where both platforms were re-verified against the published package.3.0.148: one cropper screen on both platforms
The 3.0.147 cropper was TOCropViewController's own screen on iOS (a dark toolbar with plain-text Cancel and Done that ignored the app theme) and uCrop's screen on Android (a toolbar check mark plus aspect, rotate and scale tabs). Neither looked like the app, and they looked nothing like each other.
Both platforms now show the same page, drawn by this package:
RotateCounterclockwiseicon on the right, the crop area with the crop box 20 pt from every edge, and a footer with capsule Cancel and Confirm buttons. The buttons match the app's largeButton: 50 pt tall, 10 pt apart,$bgStrong/$bgPrimarywith their pressed colors, and a spinner on Confirm while the image is saved.TOCropViewin the newImageCropperViewController; Android hosts uCrop'sUCropViewin the newImageCropperActivity, which replacesUCropActivity.cropperAppearanceoption:colorScheme, background, title, icon and button colors, title and button fonts (iOSUIFont(name:), AndroidReactFontManager), and a sizescale. Colors are CSS hex strings; anything left out falls back to OneKey's light or dark palette.cropperChooseColor,cropperCancelColor,cropperActiveWidgetColor,cropperToolbarColor,cropperToolbarWidgetColor,cropperStatusBarLight,cropperNavigationBarLight,showCropFrame,enableRotationGesture,hideBottomControls,disableCropperColorSetters.Also fixed on iOS: after cancelling the cropper that
openPickeropened, tapping the same photo again did nothing, becausePHPickerViewControllerkeeps the photo selected and the first tap only deselected it. On iOS 17 and later the selection is now cleared when returning to the picker. The configuration passes the shared photo library to get asset identifiers, which asks for no permission; the picker still opened without a prompt.Verification (3.0.148)
app-monorepo #13553 with this branch's package synced into
node_modules, Developer → Gallery → ImageCrop, native shells built locally (--shell local):openPicker500×500 returns a 500×500 JPEG,openCropper300×200 opens a 3:2 box and returns 300×200, Cancel fromopenCropperrejects withE_PICKER_CANCELLED, and Cancel fromopenPickerreturns to the picker, where one tap on the same photo now reopens the cropper.E_PICKER_CANCELLED.angle 0 → -90per tap with the crop still centered, and both outputs matched a counterclockwise rotation of the source photo (mean pixel difference 2.7 on iOS and 3.5 on Android, against 73 or more for clockwise).Release (3.0.148)
4fd15800ef2aaa9e74aea437922a2a43c278The CHANGELOG 3.0.148 section covers all four. Not published yet.
🤖 Generated with Claude Code