Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,39 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

## [3.0.148] - 2026-09-18

### Features
- **image-crop-picker (iOS and Android)**: Replace the TOCropViewController and uCrop screens with one cropper screen that looks like an app page and is the same on both platforms. The iOS cropper had TOCropViewController's dark toolbar with plain-text Cancel and Done, whatever the app theme, and Android's uCrop screen had a toolbar with a check mark plus aspect, rotate and scale tabs, so the two looked unrelated. The new screen has a header with the title and a rotate button, the crop area, and a footer with capsule Cancel and Confirm buttons, with the same metrics on both platforms. iOS hosts TOCropView in `ImageCropperViewController`; Android hosts uCrop's `UCropView` in `ImageCropperActivity`.
- Add `cropperAppearance`: `colorScheme`, background, title, icon and button colors, title and button fonts, and a size `scale`. Colors are CSS hex strings, and anything left out falls back to OneKey's light or dark palette.
- The area outside the crop box shows the page background at 70% opacity instead of TOCropViewController's dark blur or uCrop's black dimming. The crop box border uses the title color, and its corner handles only show when the box can be resized (`freeStyleCropEnabled`).
- The rule-of-thirds grid shows while the image is moved, on Android as well. Android no longer rotates with two fingers, as iOS never did, and its rotate button animates the turn.
- Android keeps the calling activity's requested orientation and draws edge to edge, with status and navigation bar icons that follow `colorScheme`.
- iOS: `TOCropOverlayView` gains `frameColor`, `gridColor` and `cornerHandlesHidden`.

### Bug Fixes
- **image-crop-picker (iOS)**: Reopen the cropper when the same photo is tapped again after cancelling it. `PHPickerViewController` keeps the photo selected when the cropper is dismissed back to it, so the first tap only deselected it and nothing seemed to happen. On iOS 17 and later the picker's selection is now cleared; the configuration passes the shared photo library to get asset identifiers, which asks for no permission.
- **native-logger (iOS)**: Keep log files after the first roll. `DDLogFileManagerDefault` defaults `logFilesDiskQuota` to 20 MB, the same as the configured `maximumFileSize`, so a single rolled file filled the quota and cleanup deleted every log file, including the one being written. The logger then wrote nothing for the rest of the session and exported log bundles had no `.log` files. The quota now follows the retention of 7 files × 20 MB, matching Android's `TOTAL_SIZE_CAP`.
- **lite-card (iOS)**: Stop Lite card callbacks from firing twice when the NFC sheet is cancelled right after a card connects, which crashed the app with SIGABRT in `RCTTurboModule.mm` ("Callback arg cannot be called more than once") in the backup and restore flow on 6.6.0. The user's cancel was reported while the card operation was still running on another thread, and the operation's failed APDUs then reported a connection failure through the same callback. Each completion is now taken out under a lock when delivered, the app's own `invalidateSession` is no longer treated as a user cancel, `connectToTag` failures and non-Lite cards report a connection failure instead of leaving the JS promise pending, and repeated results are logged and dropped instead of aborting.

### Breaking Changes
- **image-crop-picker**: Remove `cropperChooseColor`, `cropperCancelColor`, `cropperActiveWidgetColor`, `cropperToolbarColor`, `cropperToolbarWidgetColor`, `cropperStatusBarLight`, `cropperNavigationBarLight`, `showCropFrame`, `enableRotationGesture`, `hideBottomControls` and `disableCropperColorSetters`. They styled the TOCropViewController and uCrop screens; use `cropperAppearance` instead.

### Chores
- Bump all 41 publishable packages to 3.0.148.

## [3.0.147] - 2026-09-18

### Features
- **image-crop-picker (new)**: Add `@onekeyfe/react-native-image-crop-picker`, a Nitro module that replaces `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**: Pick photos with `PHPickerViewController`, which needs no photo library permission (OK-48227). `react-native-image-crop-picker` requested full library access before showing its picker. After a user tapped "Don't Allow" once, iOS never asked again and every later `openPicker` call rejected with `E_NO_LIBRARY_PERMISSION`. The OneKey ID avatar and hardware wallpaper entries swallowed that rejection, so tapping them did nothing.
- **iOS**: Crop with a vendored TOCropViewController 3.2.0, up from 2.8.0, with the OK-51551 fix that keeps the crop box from shrinking on every rotation re-applied. Upstream 3.2.0 still has that bug. The app no longer needs its TOCropViewController pod override.
- **Android**: Port the existing flow to Kotlin: the system Photo Picker (no storage or media permission) and uCrop 2.2.11-native, the latest release. Activity results go through the activity's `ActivityResultRegistry` instead of an `ActivityEventListener`.
- Decode photos at most 4096 px on the long side, so a 48 MP photo no longer needs about 200 MB of memory before cropping.

### Chores
- Bump all 41 publishable packages to 3.0.147.

## [3.0.146] - 2026-09-17

### Bug Fixes
Expand Down
1 change: 1 addition & 0 deletions example/react-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@onekeyfe/react-native-dns-lookup": "workspace:*",
"@onekeyfe/react-native-get-random-values": "workspace:*",
"@onekeyfe/react-native-image": "workspace:*",
"@onekeyfe/react-native-image-crop-picker": "workspace:*",
"@onekeyfe/react-native-keychain-module": "workspace:*",
"@onekeyfe/react-native-lite-card": "workspace:*",
"@onekeyfe/react-native-native-list": "workspace:*",
Expand Down
156 changes: 156 additions & 0 deletions example/react-native/pages/ImageCropPickerTestPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { useCallback, useState } from 'react';
import { Image, StyleSheet, Text, View } from 'react-native';
import ImageCropPicker, {
ImageCropPickerError,
type Image as PickedImage,
} from '@onekeyfe/react-native-image-crop-picker';
import { TestButton, TestPageBase, TestResult } from './TestPageBase';

// Production consumers import from `react-native-image-crop-picker`, an npm
// alias mapped to @onekeyfe/react-native-image-crop-picker.
export function ImageCropPickerTestPage() {
const [image, setImage] = useState<PickedImage | null>(null);
const [result, setResult] = useState<unknown>(null);
const [error, setError] = useState<string | null>(null);

const run = useCallback(async (task: () => Promise<PickedImage | void>) => {
setResult(null);
setError(null);
try {
const picked = await task();
if (picked) {
setImage(picked);
// Keep the output readable: base64 payloads are only summarized.
setResult({
...picked,
data: picked.data ? `<${picked.data.length} base64 chars>` : undefined,
});
} else {
setResult('done');
}
} catch (err) {
setError(
err instanceof ImageCropPickerError
? `${err.code}: ${err.message}`
: String(err)
);
}
}, []);

return (
<TestPageBase title="Image Crop Picker">
<Text style={s.hint}>
The picker needs no photo library permission. To check the OK-48227
regression, deny Photos access for this app in Settings first; the
picker must still open.
</Text>

<TestButton
title="openPicker: crop 500×500 + base64"
onPress={() =>
run(() =>
ImageCropPicker.openPicker({
width: 500,
height: 500,
cropping: true,
includeBase64: true,
cropperChooseText: 'Confirm',
cropperCancelText: 'Cancel',
})
)
}
/>
<TestButton
title="openPicker: avatar 240×240, circle, quality 0.8"
onPress={() =>
run(() =>
ImageCropPicker.openPicker({
width: 240,
height: 240,
cropping: true,
cropperCircleOverlay: true,
compressImageQuality: 0.8,
})
)
}
/>
<TestButton
title="openPicker: wallpaper 480×800"
onPress={() =>
run(() =>
ImageCropPicker.openPicker({
width: 480,
height: 800,
cropping: true,
includeBase64: true,
})
)
}
/>
<TestButton
title="openPicker: no crop, max 1024"
onPress={() =>
run(() =>
ImageCropPicker.openPicker({
compressImageMaxWidth: 1024,
compressImageMaxHeight: 1024,
})
)
}
/>
<TestButton
title="openCropper: last result, 300×200"
disabled={!image}
onPress={() =>
run(() =>
ImageCropPicker.openCropper({
path: image?.path ?? '',
width: 300,
height: 200,
})
)
}
/>
<TestButton
title="openCropper: missing file"
onPress={() =>
run(() =>
ImageCropPicker.openCropper({
path: 'file:///does/not/exist.jpg',
width: 100,
height: 100,
})
)
}
/>
<TestButton title="clean()" onPress={() => run(ImageCropPicker.clean)} />

{image ? (
<View style={s.preview}>
<Image
source={{ uri: image.path }}
style={[s.previewImage, { aspectRatio: image.width / image.height }]}
resizeMode="contain"
/>
</View>
) : null}

<TestResult result={result} error={error} />
</TestPageBase>
);
}

const s = StyleSheet.create({
hint: {
fontSize: 13,
color: '#636366',
lineHeight: 18,
},
preview: {
alignItems: 'center',
},
previewImage: {
width: 240,
backgroundColor: '#e5e5ea',
},
});
14 changes: 14 additions & 0 deletions example/react-native/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { AppUpdateTestPage } from './pages/AppUpdateTestPage';
import { RangeDownloaderTestPage } from './pages/RangeDownloaderTestPage';
import { BundleCryptoTestPage } from './pages/BundleCryptoTestPage';
import { ZipArchiveTestPage } from './pages/ZipArchiveTestPage';
import { ImageCropPickerTestPage } from './pages/ImageCropPickerTestPage';
import { OtaPipelineTestPage } from './pages/OtaPipelineTestPage';
import { ApkOtaPipelineTestPage } from './pages/ApkOtaPipelineTestPage';
import { ChartWebViewTestPage } from './pages/ChartWebViewTestPage';
Expand Down Expand Up @@ -99,6 +100,7 @@ export type RootStackParamList = {
RangeDownloader: undefined;
BundleCrypto: undefined;
ZipArchive: undefined;
ImageCropPicker: undefined;
ScrollGuard: undefined;
SegmentSlider: undefined;
Skeleton: undefined;
Expand Down Expand Up @@ -205,6 +207,13 @@ const modules: {
'unzip, getUncompressedSize, isPasswordProtected for OTA bundle archives',
icon: '🗜️',
},
{
screen: 'ImageCropPicker',
name: 'Image Crop Picker',
description:
'Permission-free photo picker with cropping for avatars and wallpapers',
icon: '🖼️',
},
{
screen: 'CloudKit',
name: 'CloudKit Module',
Expand Down Expand Up @@ -578,6 +587,11 @@ export function AppNavigator() {
component={ZipArchiveTestPage}
options={{ title: 'Zip Archive' }}
/>
<Stack.Screen
name="ImageCropPicker"
component={ImageCropPickerTestPage}
options={{ title: 'Image Crop Picker' }}
/>
<Stack.Screen
name="OtaPipeline"
component={OtaPipelineTestPage}
Expand Down
16 changes: 14 additions & 2 deletions native-modules/native-logger/ios/OneKeyLog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ private class OneKeyLogFileManager: DDLogFileManagerDefault {

private static let maxMessageLength = 4096

// Log retention, mirroring Android's MAX_FILE_SIZE / MAX_HISTORY / TOTAL_SIZE_CAP.
private static let maxLogFileSize: UInt64 = 20 * 1024 * 1024 // 20 MB
private static let maxLogFiles: UInt = 7 // 1 active + 6 archived

private struct TokenBucket {
let ratePerSecond: Double
let burstCapacity: Double
Expand Down Expand Up @@ -164,11 +168,19 @@ private class OneKeyLogFileManager: DDLogFileManagerDefault {
// NOTE: DDLogFileManagerDefault.maximumNumberOfLogFiles counts ALL log files
// including the current active file (app-latest.log).
// Set to 7 = 1 active + 6 archived, matching Android MAX_HISTORY=6.
fileManager.maximumNumberOfLogFiles = 7
fileManager.maximumNumberOfLogFiles = OneKeyLog.maxLogFiles
// logFilesDiskQuota defaults to 20 MB — the size of one full log file. The
// first roll then exceeds the quota, and because the rolled file is already
// archived, cleanup deletes every log file (the "don't delete the active
// file" guard only covers unarchived ones) and the logger writes nothing for
// the rest of the session. Size the quota after the retention above so a full
// set of files fits, matching Android's TOTAL_SIZE_CAP.
fileManager.logFilesDiskQuota =
UInt64(OneKeyLog.maxLogFiles) * OneKeyLog.maxLogFileSize

let logger = DDFileLogger(logFileManager: fileManager)
logger.rollingFrequency = 86400 // daily rolling
logger.maximumFileSize = 20_971_520 // 20 MB
logger.maximumFileSize = OneKeyLog.maxLogFileSize
logger.logFormatter = OneKeyLogFormatter()

DDLog.add(logger)
Expand Down
2 changes: 1 addition & 1 deletion native-modules/native-logger/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-native-logger",
"version": "3.0.146",
"version": "3.0.148",
"description": "react-native-native-logger",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-aes-crypto/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-aes-crypto",
"version": "3.0.146",
"version": "3.0.148",
"description": "react-native-aes-crypto",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-app-update/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-app-update",
"version": "3.0.146",
"version": "3.0.148",
"description": "react-native-app-update",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-async-storage/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-async-storage",
"version": "3.0.146",
"version": "3.0.148",
"description": "react-native-async-storage",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-background-thread/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-background-thread",
"version": "3.0.146",
"version": "3.0.148",
"description": "react-native-background-thread",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-bundle-crypto/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-bundle-crypto",
"version": "3.0.146",
"version": "3.0.148",
"description": "react-native-bundle-crypto",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-bundle-update/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-bundle-update",
"version": "3.0.146",
"version": "3.0.148",
"description": "react-native-bundle-update",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-check-biometric-auth-changed",
"version": "3.0.146",
"version": "3.0.148",
"description": "react-native-check-biometric-auth-changed",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-cloud-fs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-cloud-fs",
"version": "3.0.146",
"version": "3.0.148",
"description": "react-native-cloud-fs TurboModule for OneKey",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-cloud-kit-module/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-cloud-kit-module",
"version": "3.0.146",
"version": "3.0.148",
"description": "react-native-cloud-kit-module",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-device-utils/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-device-utils",
"version": "3.0.146",
"version": "3.0.148",
"description": "react-native-device-utils",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-dns-lookup/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-dns-lookup",
"version": "3.0.146",
"version": "3.0.148",
"description": "react-native-dns-lookup",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-get-random-values/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-get-random-values",
"version": "3.0.146",
"version": "3.0.148",
"description": "react-native-get-random-values",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
Loading
Loading