Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

saveToInternalStorage (also fix of external storage for Android 11) option (Android only) and onError callback #417

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ Add the following uses-permission to your `AndroidManifest.xml` (usually found a
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
```

The second is only needed if you are not using internal storage

#### iOS

Add the following usage descriptions to your `Info.plist` (usually found at: `ios/PROJECT_NAME/`)
Expand Down Expand Up @@ -98,6 +100,8 @@ import { CameraScreen } from 'react-native-camera-kit';
torchOffImage={require('path/to/image')} // optional, image for toggling off flash light
hideControls={false} // (default false) optional, hides camera controls
showCapturedImageCount={false} // (default false) optional, show count for photos taken during that capture session
saveToInternalStorage={false} // (default false) optional, save files to internal storage
onError={(err) => console.error(err)}
/>
```

Expand Down Expand Up @@ -145,8 +149,10 @@ import { Camera, CameraType } from 'react-native-camera-kit';
| `resetFocusTimeout` | Number | iOS only. Dismiss tap to focus after this many milliseconds. Default `0` (disabled). Example: `5000` is 5 seconds. |
| `resetFocusWhenMotionDetected` | Boolean | iOS only. Dismiss tap to focus when focus area content changes. Native iOS feature, see documentation: https://developer.apple.com/documentation/avfoundation/avcapturedevice/1624644-subjectareachangemonitoringenabl?language=objc). Default `true`. |
| `saveToCameraRoll` | Boolean | Using the camera roll is slower than using regular files stored in your app. On an iPhone X in debug mode, on a real phone, we measured around 100-150ms processing time to save to the camera roll. _<span style="color: red">**Note:**</span> This only work on real devices. It will hang indefinitly on simulators._ |
| `saveToCameraRollWithPhUrl` | Boolean | iOS only. If true, speeds up photo taking by about 5-50ms (measured on iPhone X) by only returning a [rn-cameraroll-compatible](https://github.com/react-native-community/react-native-cameraroll/blob/a09af08f0a46a98b29f6ad470e59d3dc627864a2/ios/RNCAssetsLibraryRequestHandler.m#L36) `ph://..` URL instead of a regular `file://..` URL. | |
| `onOrientationChange` | Function | Callback when physical device orientation changes. Returned event contains `orientation`. Ex: `onOrientationChange={(event) => console.log(event.nativeEvent.orientation)}`. Use `import { Orientation } from 'react-native-camera-kit'; if (event.nativeEvent.orientation === Orientation.PORTRAIT) { ... }` to understand the new value |
| `saveToInternalStorage` | Boolean | Save files to internal storage (Android only) |
| `saveToCameraRollWithPhUrl` | Boolean | iOS only. If true, speeds up photo taking by about 5-50ms (measured on iPhone X) by only returning a [rn-cameraroll-compatible](https://github.com/react-native-community/react-native-cameraroll/blob/a09af08f0a46a98b29f6ad470e59d3dc627864a2/ios/RNCAssetsLibraryRequestHandler.m#L36) `ph://..` URL instead of a regular `file://..` URL. |
| `onOrientationChange` | Function | Callback when physical device orientation changes. Returned event contains `orientation`. Ex: `onOrientationChange={(event) => console.log(event.nativeEvent.orientation)}`. Use `import { Orientation } from 'react-native-camera-kit'; if (event.nativeEvent.orientation === Orientation.PORTRAIT) { ... }` to understand the new value |
| `onError` | Function | Callback when when an error occurs |

### Barcode Props (Optional)

Expand Down
39 changes: 22 additions & 17 deletions android/src/main/java/com/rncamerakit/CKCamera.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.annotation.SuppressLint
import android.app.Activity
import android.os.Environment
import android.content.ContentValues
import android.content.Context
import android.content.pm.PackageManager
Expand All @@ -13,6 +14,7 @@ import android.hardware.SensorManager
import android.media.AudioManager
import android.media.MediaActionSound
import android.provider.MediaStore
import android.net.Uri
import android.util.DisplayMetrics
import android.util.Log
import android.view.*
Expand Down Expand Up @@ -55,6 +57,7 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs
private var cameraProvider: ProcessCameraProvider? = null
private var outputPath: String? = null
private var shutterAnimationDuration: Int = 50
private var saveToInternalStorage: Boolean = false
private var effectLayer = View(context)

// Camera Props
Expand Down Expand Up @@ -279,30 +282,32 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs
shutterAnimationDuration = duration
}

fun setSaveToInternalStorage(save: Boolean) {
saveToInternalStorage = save
}

fun capture(options: Map<String, Any>, promise: Promise) {
// Create output options object which contains file + metadata
val activity = getActivity()
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "IMG_" + System.currentTimeMillis())
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
}

// Create the output file option to store the captured image in MediaStore
val outputOptions = when (outputPath) {
null -> ImageCapture.OutputFileOptions
.Builder(
context.contentResolver,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
contentValues
)
.build()
else -> ImageCapture.OutputFileOptions
.Builder(File(outputPath))
.build()
if (saveToInternalStorage) {
outputPath = outputPath ?: "${activity.getFilesDir()}/${System.currentTimeMillis()}.jpg"
} else {
outputPath = outputPath ?: "${activity.getExternalFilesDir(Environment.DIRECTORY_DCIM)}/${System.currentTimeMillis()}.jpg"
}

val outputFile = File(outputPath)

// Create the output file option to store the captured image in MediaStore
val outputOptions = ImageCapture.OutputFileOptions.Builder(outputFile).build()

flashViewFinder()

val audio = getActivity().getSystemService(Context.AUDIO_SERVICE) as AudioManager
val audio = activity.getSystemService(Context.AUDIO_SERVICE) as AudioManager
if (audio.ringerMode == AudioManager.RINGER_MODE_NORMAL) {
MediaActionSound().play(MediaActionSound.SHUTTER_CLICK);
}
Expand All @@ -318,17 +323,17 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs

override fun onImageSaved(output: ImageCapture.OutputFileResults) {
try {
val savedUri = output.savedUri.toString()
val savedUri = Uri.fromFile(outputFile).toString()
onPictureTaken(savedUri)
Log.d(TAG, "CameraView: Photo capture succeeded: $savedUri")

val imageInfo = Arguments.createMap()
imageInfo.putString("uri", savedUri)
imageInfo.putString("id", output.savedUri?.path)
imageInfo.putString("name", output.savedUri?.lastPathSegment)
imageInfo.putString("id", outputPath)
imageInfo.putString("name", outputPath?.substringAfterLast("/"))
imageInfo.putInt("width", width)
imageInfo.putInt("height", height)
imageInfo.putString("path", output.savedUri?.path)
imageInfo.putString("path", outputPath)

promise.resolve(imageInfo)
} catch (ex: Exception) {
Expand Down
5 changes: 5 additions & 0 deletions android/src/main/java/com/rncamerakit/CKCameraManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,9 @@ class CKCameraManager : SimpleViewManager<CKCamera>() {
fun setShutterAnimationDuration(view: CKCamera, duration: Int) {
view.setShutterAnimationDuration(duration)
}

@ReactProp(name = "saveToInternalStorage")
fun setSaveToInternalStorage(view: CKCamera, save: Boolean) {
view.setSaveToInternalStorage(save)
}
}
1 change: 1 addition & 0 deletions example/src/CameraExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default class CameraExample extends Component {
resetFocusTimeout={0}
resetFocusWhenMotionDetected={false}
saveToCameraRole={false} // iOS only
saveToInternalStorage={false} // Android only
scanBarcode={false} // optional
showFrame={false} // Barcode only, optional
laserColor="red" // Barcode only, optional
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"pod-install": "^0.1.0",
"prettier": "^2.1.2",
"react": "16.11.0",
"react-native": "0.62.2",
"react-native": "0.62.3",
"react-test-renderer": "16.6.0",
"typescript": "^4.1.2"
},
Expand Down
40 changes: 28 additions & 12 deletions src/CameraScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type Props = {
ratioOverlay?: string,
ratioOverlayColor?: string,
allowCaptureRetake: boolean,
saveToInternalStorage: boolean,
cameraRatioOverlay: any,
showCapturedImageCount?: boolean,
captureButtonImage: any,
Expand All @@ -41,6 +42,7 @@ export type Props = {
torchOffImage: any,
onReadCode: (any) => void;
onBottomButtonPressed: (any) => void;
onError: (any) => void;
}

type State = {
Expand All @@ -57,10 +59,12 @@ type State = {
export default class CameraScreen extends Component<Props, State> {
static propTypes = {
allowCaptureRetake: PropTypes.bool,
saveToInternalStorage: PropTypes.bool,
};

static defaultProps = {
allowCaptureRetake: false,
saveToInternalStorage: false,
};

currentFlashArrayPosition: number;
Expand Down Expand Up @@ -188,6 +192,7 @@ export default class CameraScreen extends Component<Props, State> {
laserColor={this.props.laserColor}
frameColor={this.props.frameColor}
onReadCode={this.props.onReadCode}
saveToInternalStorage={this.props.saveToInternalStorage}
/>
)}
</View>
Expand Down Expand Up @@ -304,19 +309,30 @@ export default class CameraScreen extends Component<Props, State> {
}

async onCaptureImagePressed() {
const image = await this.camera.capture();

if (this.props.allowCaptureRetake) {
this.setState({ imageCaptured: image });
} else {
if (image) {
this.setState({
captured: true,
imageCaptured: image,
captureImages: _.concat(this.state.captureImages, image),
});
try {
const image = await this.camera.capture();

if (this.props.allowCaptureRetake) {
this.setState({imageCaptured: image});
} else {
if (image) {
this.setState({
captured: true,
imageCaptured: image,
captureImages: _.concat(this.state.captureImages, image),
});
}
this.sendBottomButtonPressedAction('capture', false, image);
}
} catch (err: any) {
const { onError } = this.props;

if (onError != null) {
onError?.(err);
} else {
// eslint-disable-next-line no-console
console.warn('Camera error:', err.message);
}
this.sendBottomButtonPressedAction('capture', false, image);
}
}

Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6148,10 +6148,10 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==

[email protected].2:
version "0.62.2"
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.62.2.tgz#d831e11a3178705449142df19a70ac2ca16bad10"
integrity sha512-gADZZ3jcm2WFTjh8CCBCbl5wRSbdxqZGd+8UpNwLQFgrkp/uHorwAhLNqcd4+fHmADgPBltNL0uR1Vhv47zcOw==
[email protected].3:
version "0.62.3"
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.62.3.tgz#9a2e96af3dedd0723c8657831eec4ed3c30f3299"
integrity sha512-b2/hDHDlO5Of2Zn1K5ZgvzOoUrvMEvlYU4miS9rcEBp5jK/9cRQn81HZ7NBmf4gzigGl8CxbSx1l10GUj1k8XQ==
dependencies:
"@babel/runtime" "^7.0.0"
"@react-native-community/cli" "^4.5.1"
Expand Down