Devops: KTLint to lint Kotlin code (#6)
* Adds KTLint as a GitHub action * Adds KTLint to the gradle project for IDE integration * Adds .editorconfig to configure KTLint (android/)
This commit is contained in:
parent
2e60110070
commit
03b9246afe
11
.github/workflows/validate-android.yml
vendored
11
.github/workflows/validate-android.yml
vendored
@ -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:
|
||||
@ -32,3 +34,12 @@ jobs:
|
||||
- uses: yutailang0119/action-android-lint@v1.0.2
|
||||
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/action-ktlint@v1.6
|
||||
with:
|
||||
github_token: ${{ secrets.github_token }}
|
||||
|
5
android/.editorconfig
Normal file
5
android/.editorconfig
Normal file
@ -0,0 +1,5 @@
|
||||
[*.{kt,kts}]
|
||||
indent_size=2
|
||||
insert_final_newline=true
|
||||
max_line_length=off
|
||||
disabled_rules=no-wildcard-imports
|
16
android/README.md
Normal file
16
android/README.md
Normal file
@ -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.
|
@ -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]
|
||||
|
@ -2,8 +2,8 @@ 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
|
||||
|
||||
@ -26,7 +26,9 @@ suspend fun CameraView.startRecording(options: ReadableMap, onRecordCallback: Ca
|
||||
}
|
||||
val videoFileOptions = VideoCapture.OutputFileOptions.Builder(videoFile)
|
||||
|
||||
videoCapture!!.startRecording(videoFileOptions.build(), recordVideoExecutor, object : VideoCapture.OnVideoSavedCallback {
|
||||
videoCapture!!.startRecording(
|
||||
videoFileOptions.build(), recordVideoExecutor,
|
||||
object : VideoCapture.OnVideoSavedCallback {
|
||||
override fun onVideoSaved(outputFileResults: VideoCapture.OutputFileResults) {
|
||||
val map = Arguments.createMap()
|
||||
map.putString("path", videoFile.absolutePath)
|
||||
@ -52,12 +54,12 @@ suspend fun CameraView.startRecording(options: ReadableMap, onRecordCallback: Ca
|
||||
// reset the torch mode
|
||||
camera!!.cameraControl.enableTorch(torch == "on")
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
return TemporaryFile(videoFile.absolutePath)
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun CameraView.stopRecording() {
|
||||
if (videoCapture == null) {
|
||||
|
@ -7,10 +7,10 @@ import androidx.camera.camera2.interop.Camera2CameraInfo
|
||||
import androidx.camera.core.ImageCapture
|
||||
import androidx.camera.core.ImageProxy
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import com.mrousavy.camera.utils.*
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
import com.mrousavy.camera.utils.*
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.File
|
||||
|
||||
@ -70,7 +70,8 @@ suspend fun CameraView.takePhoto(options: ReadableMap): WritableMap = coroutineS
|
||||
async(Dispatchers.IO) {
|
||||
Log.d(CameraView.REACT_CLASS, "Creating temp file...")
|
||||
File.createTempFile("mrousavy", ".jpg", context.cacheDir).apply { deleteOnExit() }
|
||||
})
|
||||
}
|
||||
)
|
||||
val photo = results.first { it is ImageProxy } as ImageProxy
|
||||
val file = results.first { it is File } as File
|
||||
|
||||
|
@ -2,10 +2,10 @@ package com.mrousavy.camera
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import com.mrousavy.camera.utils.buildMetadataMap
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
import com.mrousavy.camera.utils.buildMetadataMap
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -20,9 +20,9 @@ import androidx.camera.extensions.NightPreviewExtender
|
||||
import androidx.camera.view.PreviewView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.*
|
||||
import com.mrousavy.camera.utils.*
|
||||
import com.facebook.react.bridge.*
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter
|
||||
import com.mrousavy.camera.utils.*
|
||||
import kotlinx.coroutines.*
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.util.concurrent.Executors
|
||||
|
@ -1,14 +1,12 @@
|
||||
package com.mrousavy.camera
|
||||
|
||||
import android.util.Log
|
||||
import com.facebook.react.bridge.ReactContext
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.facebook.react.common.MapBuilder
|
||||
import com.facebook.react.uimanager.SimpleViewManager
|
||||
import com.facebook.react.uimanager.ThemedReactContext
|
||||
import com.facebook.react.uimanager.annotations.ReactProp
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class CameraViewManager : SimpleViewManager<CameraView>() {
|
||||
private fun addChangedPropToTransaction(view: CameraView, changedProp: String) {
|
||||
|
@ -14,11 +14,11 @@ import androidx.camera.core.ImageCapture
|
||||
import androidx.camera.extensions.HdrImageCaptureExtender
|
||||
import androidx.camera.extensions.NightImageCaptureExtender
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.mrousavy.camera.parsers.*
|
||||
import com.mrousavy.camera.utils.*
|
||||
import com.facebook.react.bridge.*
|
||||
import com.facebook.react.modules.core.PermissionAwareActivity
|
||||
import com.facebook.react.modules.core.PermissionListener
|
||||
import com.mrousavy.camera.parsers.*
|
||||
import com.mrousavy.camera.utils.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
@ -127,8 +127,11 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
|
||||
|
||||
// Filters out cameras that are LEGACY hardware level. Those don't support Preview + Photo Capture + Video Capture at the same time.
|
||||
if (hardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
|
||||
Log.i(REACT_CLASS, "Skipping Camera #${id} because it does not meet the minimum requirements for react-native-vision-camera. " +
|
||||
"See the tables at https://developer.android.com/reference/android/hardware/camera2/CameraDevice#regular-capture for more information.")
|
||||
Log.i(
|
||||
REACT_CLASS,
|
||||
"Skipping Camera #$id because it does not meet the minimum requirements for react-native-vision-camera. " +
|
||||
"See the tables at https://developer.android.com/reference/android/hardware/camera2/CameraDevice#regular-capture for more information."
|
||||
)
|
||||
return@loop
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,6 @@ abstract class CameraError(
|
||||
val CameraError.code: String
|
||||
get() = "$domain/$id"
|
||||
|
||||
|
||||
class MicrophonePermissionError : CameraError("permission", "microphone-permission-denied", "The Microphone permission was denied!")
|
||||
class CameraPermissionError : CameraError("permission", "camera-permission-denied", "The Camera permission was denied!")
|
||||
|
||||
@ -41,10 +40,16 @@ class NoCameraDeviceError: CameraError("device", "no-device", "No device was set
|
||||
class InvalidCameraDeviceError(cause: Throwable) : CameraError("device", "invalid-device", "The given Camera device could not be found for use-case binding!", cause)
|
||||
|
||||
class FpsNotContainedInFormatError(fps: Int) : CameraError("format", "invalid-fps", "The given FPS were not valid for the currently selected format. Make sure you select a format which `frameRateRanges` includes $fps FPS!")
|
||||
class HdrNotContainedInFormatError(): CameraError("format", "invalid-hdr", "The currently selected format does not support HDR capture! " +
|
||||
"Make sure you select a format which `frameRateRanges` includes `supportsPhotoHDR`!")
|
||||
class LowLightBoostNotContainedInFormatError(): CameraError("format", "invalid-low-light-boost", "The currently selected format does not support low-light boost (night mode)! " +
|
||||
"Make sure you select a format which includes `supportsLowLightBoost`.")
|
||||
class HdrNotContainedInFormatError() : CameraError(
|
||||
"format", "invalid-hdr",
|
||||
"The currently selected format does not support HDR capture! " +
|
||||
"Make sure you select a format which `frameRateRanges` includes `supportsPhotoHDR`!"
|
||||
)
|
||||
class LowLightBoostNotContainedInFormatError() : CameraError(
|
||||
"format", "invalid-low-light-boost",
|
||||
"The currently selected format does not support low-light boost (night mode)! " +
|
||||
"Make sure you select a format which includes `supportsLowLightBoost`."
|
||||
)
|
||||
|
||||
class CameraNotReadyError : CameraError("session", "camera-not-ready", "The Camera is not ready yet! Wait for the onInitialized() callback!")
|
||||
|
||||
|
@ -2,14 +2,12 @@ package com.mrousavy.camera.utils
|
||||
|
||||
import android.hardware.camera2.CameraCharacteristics
|
||||
import android.util.Size
|
||||
import com.mrousavy.camera.parsers.bigger
|
||||
import com.mrousavy.camera.parsers.parseLensFacing
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.mrousavy.camera.parsers.bigger
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.atan
|
||||
|
||||
|
||||
// 35mm is 135 film format, a standard in which focal lengths are usually measured
|
||||
val Size35mm = Size(36, 24)
|
||||
|
||||
|
@ -5,7 +5,6 @@ import androidx.camera.camera2.interop.Camera2CameraInfo
|
||||
import androidx.camera.core.CameraSelector
|
||||
import java.lang.IllegalArgumentException
|
||||
|
||||
|
||||
/**
|
||||
* Create a new [CameraSelector] which selects the camera with the given [cameraId]
|
||||
*/
|
||||
|
@ -4,7 +4,6 @@ import androidx.exifinterface.media.ExifInterface
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
|
||||
|
||||
fun ExifInterface.buildMetadataMap(): WritableMap {
|
||||
val metadataMap = Arguments.createMap()
|
||||
metadataMap.putInt("Orientation", this.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL))
|
||||
|
@ -4,14 +4,14 @@ import androidx.camera.core.ImageCapture
|
||||
import androidx.camera.core.ImageCaptureException
|
||||
import androidx.camera.core.ImageProxy
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
|
||||
suspend inline fun ImageCapture.takePicture(options: ImageCapture.OutputFileOptions, executor: Executor) = suspendCoroutine<ImageCapture.OutputFileResults> { cont ->
|
||||
this.takePicture(options, executor, object: ImageCapture.OnImageSavedCallback {
|
||||
this.takePicture(
|
||||
options, executor,
|
||||
object : ImageCapture.OnImageSavedCallback {
|
||||
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
||||
cont.resume(outputFileResults)
|
||||
}
|
||||
@ -19,11 +19,14 @@ suspend inline fun ImageCapture.takePicture(options: ImageCapture.OutputFileOpti
|
||||
override fun onError(exception: ImageCaptureException) {
|
||||
cont.resumeWithException(exception)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
suspend inline fun ImageCapture.takePicture(executor: Executor) = suspendCoroutine<ImageProxy> { cont ->
|
||||
this.takePicture(executor, object: ImageCapture.OnImageCapturedCallback() {
|
||||
this.takePicture(
|
||||
executor,
|
||||
object : ImageCapture.OnImageCapturedCallback() {
|
||||
override fun onCaptureSuccess(image: ImageProxy) {
|
||||
super.onCaptureSuccess(image)
|
||||
cont.resume(image)
|
||||
@ -33,5 +36,6 @@ suspend inline fun ImageCapture.takePicture(executor: Executor) = suspendCorouti
|
||||
super.onError(exception)
|
||||
cont.resumeWithException(exception)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -1,14 +1,12 @@
|
||||
package com.mrousavy.camera.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.ImageFormat
|
||||
import androidx.camera.core.ImageProxy
|
||||
import com.mrousavy.camera.InvalidFormatError
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.stream.Stream.concat
|
||||
|
||||
// TODO: Fix this flip() function (this outputs a black image)
|
||||
fun flip(imageBytes: ByteArray, imageWidth: Int): ByteArray {
|
||||
@ -35,7 +33,6 @@ fun flip(imageBytes: ByteArray, imageWidth: Int): ByteArray {
|
||||
return holder + subArray
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("UnsafeExperimentalUsageError")
|
||||
fun ImageProxy.save(file: File, flipHorizontally: Boolean) {
|
||||
when (format) {
|
||||
|
@ -9,7 +9,10 @@ import kotlin.coroutines.suspendCoroutine
|
||||
suspend fun getCameraProvider(context: Context) = suspendCoroutine<ProcessCameraProvider> { cont ->
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
|
||||
|
||||
cameraProviderFuture.addListener({
|
||||
cameraProviderFuture.addListener(
|
||||
{
|
||||
cont.resume(cameraProviderFuture.get())
|
||||
}, ContextCompat.getMainExecutor(context))
|
||||
},
|
||||
ContextCompat.getMainExecutor(context)
|
||||
)
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package com.mrousavy.camera.utils
|
||||
import android.util.Size
|
||||
import android.view.Surface
|
||||
|
||||
|
||||
/**
|
||||
* Rotate by a given Surface Rotation
|
||||
*/
|
||||
|
@ -1,8 +1,8 @@
|
||||
package com.mrousavy.camera.utils
|
||||
|
||||
import com.facebook.react.bridge.Promise
|
||||
import com.mrousavy.camera.CameraError
|
||||
import com.mrousavy.camera.UnknownCameraError
|
||||
import com.facebook.react.bridge.Promise
|
||||
|
||||
inline fun withPromise(promise: Promise, closure: () -> Any?) {
|
||||
try {
|
||||
|
Loading…
Reference in New Issue
Block a user