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

Devops: KTLint to lint Kotlin code #6

Merged
merged 16 commits into from
Feb 26, 2021
11 changes: 11 additions & 0 deletions .github/workflows/validate-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ on:
paths:
- '.github/workflows/validate-android.yml'
- 'android/**'
- '.editorconfig'
pull_request:
paths:
- '.github/workflows/validate-android.yml'
- 'android/**'
- '.editorconfig'

jobs:
lint:
Expand All @@ -32,3 +34,12 @@ jobs:
- uses: yutailang0119/[email protected]
with:
xml_path: android/build/reports/lint-results.xml
ktlint:
name: Kotlin Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run KTLint
uses: mrousavy/[email protected]
with:
github_token: ${{ secrets.github_token }}
5 changes: 5 additions & 0 deletions android/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[*.{kt,kts}]
indent_size=2
insert_final_newline=true
max_line_length=off
disabled_rules=no-wildcard-imports
16 changes: 16 additions & 0 deletions android/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# android

This folder contains the Android-platform-specific code for react-native-vision-camera.

## Prerequesites

1. Install ktlint
```sh
brew install ktlint
```

## Getting Started

It is recommended that you work on the code using the Example project (`example/android/`), since that always includes the React Native header files, plus you can easily test changes that way.

You can however still edit the library project here by opening this folder with Android Studio.
3 changes: 3 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ buildscript {
// noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
// ktlint
classpath "org.jlleitschuh.gradle:ktlint-gradle:10.0.0"
}
}

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'org.jlleitschuh.gradle.ktlint'

def getExtOrDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['VisionCamera_' + name]
Expand Down
12 changes: 6 additions & 6 deletions android/src/main/java/com/mrousavy/camera/CameraPackage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager

class CameraPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(CameraViewModule(reactContext))
}
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(CameraViewModule(reactContext))
}

override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return listOf(CameraViewManager())
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return listOf(CameraViewManager())
}
}
26 changes: 13 additions & 13 deletions android/src/main/java/com/mrousavy/camera/CameraView+Focus.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ import kotlinx.coroutines.guava.await
import java.util.concurrent.TimeUnit

suspend fun CameraView.focus(pointMap: ReadableMap) {
val cameraControl = camera?.cameraControl ?: throw CameraNotReadyError()
if (!pointMap.hasKey("x") || !pointMap.hasKey("y")) {
throw InvalidTypeScriptUnionError("point", pointMap.toString())
}
val cameraControl = camera?.cameraControl ?: throw CameraNotReadyError()
if (!pointMap.hasKey("x") || !pointMap.hasKey("y")) {
throw InvalidTypeScriptUnionError("point", pointMap.toString())
}

val dpi = resources.displayMetrics.density
val x = pointMap.getDouble("x") * dpi
val y = pointMap.getDouble("y") * dpi
val dpi = resources.displayMetrics.density
val x = pointMap.getDouble("x") * dpi
val y = pointMap.getDouble("y") * dpi

val factory = SurfaceOrientedMeteringPointFactory(this.width.toFloat(), this.height.toFloat())
val point = factory.createPoint(x.toFloat(), y.toFloat())
val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE)
.setAutoCancelDuration(5, TimeUnit.SECONDS) // auto-reset after 5 seconds
.build()
val factory = SurfaceOrientedMeteringPointFactory(this.width.toFloat(), this.height.toFloat())
val point = factory.createPoint(x.toFloat(), y.toFloat())
val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE)
.setAutoCancelDuration(5, TimeUnit.SECONDS) // auto-reset after 5 seconds
.build()

cameraControl.startFocusAndMetering(action).await()
cameraControl.startFocusAndMetering(action).await()
}
94 changes: 48 additions & 46 deletions android/src/main/java/com/mrousavy/camera/CameraView+RecordVideo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,71 @@ package com.mrousavy.camera

import android.annotation.SuppressLint
import androidx.camera.core.VideoCapture
import com.mrousavy.camera.utils.makeErrorMap
import com.facebook.react.bridge.*
import com.mrousavy.camera.utils.makeErrorMap
import kotlinx.coroutines.*
import java.io.File

data class TemporaryFile(val path: String)

@SuppressLint("RestrictedApi")
suspend fun CameraView.startRecording(options: ReadableMap, onRecordCallback: Callback): TemporaryFile {
if (videoCapture == null) {
throw CameraNotReadyError()
}
if (options.hasKey("flash")) {
val enableFlash = options.getString("flash") == "on"
// overrides current torch mode value to enable flash while recording
camera!!.cameraControl.enableTorch(enableFlash)
}
if (videoCapture == null) {
throw CameraNotReadyError()
}
if (options.hasKey("flash")) {
val enableFlash = options.getString("flash") == "on"
// overrides current torch mode value to enable flash while recording
camera!!.cameraControl.enableTorch(enableFlash)
}

@Suppress("BlockingMethodInNonBlockingContext") // in withContext we are not blocking. False positive.
val videoFile = withContext(Dispatchers.IO) {
File.createTempFile("video", ".mp4", context.cacheDir).apply { deleteOnExit() }
}
val videoFileOptions = VideoCapture.OutputFileOptions.Builder(videoFile)
@Suppress("BlockingMethodInNonBlockingContext") // in withContext we are not blocking. False positive.
val videoFile = withContext(Dispatchers.IO) {
File.createTempFile("video", ".mp4", context.cacheDir).apply { deleteOnExit() }
}
val videoFileOptions = VideoCapture.OutputFileOptions.Builder(videoFile)

videoCapture!!.startRecording(videoFileOptions.build(), recordVideoExecutor, object : VideoCapture.OnVideoSavedCallback {
override fun onVideoSaved(outputFileResults: VideoCapture.OutputFileResults) {
val map = Arguments.createMap()
map.putString("path", videoFile.absolutePath)
// TODO: duration and size
onRecordCallback(map, null)
videoCapture!!.startRecording(
videoFileOptions.build(), recordVideoExecutor,
object : VideoCapture.OnVideoSavedCallback {
override fun onVideoSaved(outputFileResults: VideoCapture.OutputFileResults) {
val map = Arguments.createMap()
map.putString("path", videoFile.absolutePath)
// TODO: duration and size
onRecordCallback(map, null)

// reset the torch mode
camera!!.cameraControl.enableTorch(torch == "on")
}
// reset the torch mode
camera!!.cameraControl.enableTorch(torch == "on")
}

override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
val error = when (videoCaptureError) {
VideoCapture.ERROR_ENCODER -> VideoEncoderError(message, cause)
VideoCapture.ERROR_FILE_IO -> FileIOError(message, cause)
VideoCapture.ERROR_INVALID_CAMERA -> InvalidCameraError(message, cause)
VideoCapture.ERROR_MUXER -> VideoMuxerError(message, cause)
VideoCapture.ERROR_RECORDING_IN_PROGRESS -> RecordingInProgressError(message, cause)
else -> UnknownCameraError(Error(message, cause))
}
val map = makeErrorMap("${error.domain}/${error.id}", error.message, error)
onRecordCallback(null, map)

// reset the torch mode
camera!!.cameraControl.enableTorch(torch == "on")
override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
val error = when (videoCaptureError) {
VideoCapture.ERROR_ENCODER -> VideoEncoderError(message, cause)
VideoCapture.ERROR_FILE_IO -> FileIOError(message, cause)
VideoCapture.ERROR_INVALID_CAMERA -> InvalidCameraError(message, cause)
VideoCapture.ERROR_MUXER -> VideoMuxerError(message, cause)
VideoCapture.ERROR_RECORDING_IN_PROGRESS -> RecordingInProgressError(message, cause)
else -> UnknownCameraError(Error(message, cause))
}
})
val map = makeErrorMap("${error.domain}/${error.id}", error.message, error)
onRecordCallback(null, map)

return TemporaryFile(videoFile.absolutePath)
}
// reset the torch mode
camera!!.cameraControl.enableTorch(torch == "on")
}
}
)

return TemporaryFile(videoFile.absolutePath)
}

@SuppressLint("RestrictedApi")
fun CameraView.stopRecording() {
if (videoCapture == null) {
throw CameraNotReadyError()
}
if (videoCapture == null) {
throw CameraNotReadyError()
}

videoCapture!!.stopRecording()
// reset torch mode to original value
camera!!.cameraControl.enableTorch(torch == "on")
videoCapture!!.stopRecording()
// reset torch mode to original value
camera!!.cameraControl.enableTorch(torch == "on")
}
Loading