Switching to CameraX

This commit is contained in:
Matéo Duparc 2020-12-19 19:55:54 +01:00
parent b23cb7b8ce
commit eefca5ee0a
Signed by untrusted user: hardcoresushi
GPG Key ID: 007F84120107191E
12 changed files with 209 additions and 117 deletions

View File

@ -47,7 +47,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "androidx.core:core-ktx:1.3.2" implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.appcompat:appcompat:1.2.0" implementation "androidx.appcompat:appcompat:1.2.0"
implementation "androidx.constraintlayout:constraintlayout:2.0.3" implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation "androidx.sqlite:sqlite-ktx:2.1.0" implementation "androidx.sqlite:sqlite-ktx:2.1.0"
implementation "androidx.preference:preference-ktx:1.1.1" implementation "androidx.preference:preference-ktx:1.1.1"
@ -56,6 +56,12 @@ dependencies {
implementation "com.github.bumptech.glide:glide:4.11.0" implementation "com.github.bumptech.glide:glide:4.11.0"
implementation "com.google.android.exoplayer:exoplayer-core:2.11.7" implementation "com.google.android.exoplayer:exoplayer-core:2.11.7"
implementation "com.google.android.exoplayer:exoplayer-ui:2.11.7" implementation "com.google.android.exoplayer:exoplayer-ui:2.11.7"
implementation "com.otaliastudios:cameraview:2.6.4"
implementation "androidx.biometric:biometric:1.0.1" implementation "androidx.biometric:biometric:1.0.1"
def camerax_version = "1.0.0-rc01"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-lifecycle:$camerax_version"
implementation "androidx.camera:camera-view:1.0.0-alpha20"
implementation "androidx.camera:camera-extensions:1.0.0-alpha20"
} }

View File

@ -24,5 +24,4 @@
-keep class sushi.hardcore.droidfs.SettingsActivity$** { -keep class sushi.hardcore.droidfs.SettingsActivity$** {
*; *;
} }
-keep class sushi.hardcore.droidfs.explorers.ExplorerElement -keep class sushi.hardcore.droidfs.explorers.ExplorerElement
-keep class com.otaliastudios.cameraview.markers.DefaultAutoFocusMarker

View File

@ -10,6 +10,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" /> <uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature <uses-feature
android:name="android.hardware.camera.any" android:name="android.hardware.camera.any"

View File

@ -3,8 +3,15 @@ package sushi.hardcore.droidfs
import android.Manifest import android.Manifest
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.InputType import android.text.InputType
import android.util.DisplayMetrics
import android.util.Size
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.view.animation.Animation import android.view.animation.Animation
@ -12,35 +19,34 @@ import android.view.animation.LinearInterpolator
import android.view.animation.RotateAnimation import android.view.animation.RotateAnimation
import android.widget.EditText import android.widget.EditText
import android.widget.ImageView import android.widget.ImageView
import android.widget.RelativeLayout
import android.widget.Toast import android.widget.Toast
import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.core.*
import androidx.camera.extensions.HdrImageCaptureExtender
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.otaliastudios.cameraview.CameraListener
import com.otaliastudios.cameraview.PictureResult
import com.otaliastudios.cameraview.controls.Facing
import com.otaliastudios.cameraview.controls.Flash
import com.otaliastudios.cameraview.controls.Grid
import com.otaliastudios.cameraview.controls.Hdr
import kotlinx.android.synthetic.main.activity_camera.* import kotlinx.android.synthetic.main.activity_camera.*
import sushi.hardcore.droidfs.adapters.DialogSingleChoiceAdapter
import sushi.hardcore.droidfs.provider.RestrictedFileProvider import sushi.hardcore.droidfs.provider.RestrictedFileProvider
import sushi.hardcore.droidfs.util.GocryptfsVolume import sushi.hardcore.droidfs.util.GocryptfsVolume
import sushi.hardcore.droidfs.util.MiscUtils
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
class CameraActivity : BaseActivity(), SensorOrientationListener.Listener { class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
companion object { companion object {
private val flashModes = listOf(Flash.AUTO, Flash.ON, Flash.OFF) private const val CAMERA_PERMISSION_REQUEST_CODE = 1
private val gridTitles = listOf(R.string.grid_none, R.string.grid_3x3, R.string.grid_4x4)
private val gridValues = listOf(Grid.OFF, Grid.DRAW_3X3, Grid.DRAW_4X4)
private const val fileNameRandomMin = 100000 private const val fileNameRandomMin = 100000
private const val fileNameRandomMax = 999999 private const val fileNameRandomMax = 999999
private val dateFormat = SimpleDateFormat("yyyyMMdd_HHmmss") private val dateFormat = SimpleDateFormat("yyyyMMdd_HHmmss")
private val random = Random() private val random = Random()
} }
private var currentFlashModeIndex = 0
private var timerDuration = 0 private var timerDuration = 0
set(value) { set(value) {
field = value field = value
@ -57,22 +63,140 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
private lateinit var outputDirectory: String private lateinit var outputDirectory: String
private lateinit var fileName: String private lateinit var fileName: String
private var isFinishingIntentionally = false private var isFinishingIntentionally = false
private lateinit var context: Context private lateinit var cameraExecutor: ExecutorService
private var imageCapture: ImageCapture? = null
private var resolutions: Array<Size>? = null
private var currentResolutionIndex: Int = 0
private var isBackCamera = true
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camera) setContentView(R.layout.activity_camera)
gocryptfsVolume = GocryptfsVolume(intent.getIntExtra("sessionID", -1)) gocryptfsVolume = GocryptfsVolume(intent.getIntExtra("sessionID", -1))
outputDirectory = intent.getStringExtra("path")!! outputDirectory = intent.getStringExtra("path")!!
context = this
camera.setLifecycleOwner(this) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
camera.addCameraListener(object: CameraListener(){ if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED){
override fun onPictureTaken(result: PictureResult) { setupCamera()
take_photo_button.onPhotoTaken() } else {
val inputStream = ByteArrayInputStream(result.data) requestPermissions(arrayOf(Manifest.permission.CAMERA), CAMERA_PERMISSION_REQUEST_CODE)
if (gocryptfsVolume.importFile(inputStream, PathUtils.pathJoin(outputDirectory, fileName))){ }
Toast.makeText(context, getString(R.string.picture_save_success, fileName), Toast.LENGTH_SHORT).show() } else {
setupCamera()
}
cameraExecutor = Executors.newSingleThreadExecutor()
take_photo_button.onClick = ::onClickTakePhoto
orientedIcons = listOf(image_ratio, image_timer, image_close, image_flash, image_camera_switch)
sensorOrientationListener = SensorOrientationListener(this)
val scaleGestureDetector = ScaleGestureDetector(this, object : ScaleGestureDetector.SimpleOnScaleGestureListener(){
override fun onScale(detector: ScaleGestureDetector): Boolean {
val currentZoomRatio = imageCapture?.camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: 0F
imageCapture?.camera?.cameraControl?.setZoomRatio(currentZoomRatio*detector.scaleFactor)
return true
}
})
camera_preview.setOnTouchListener { _, motionEvent: MotionEvent ->
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> true
MotionEvent.ACTION_UP -> {
val factory = camera_preview.meteringPointFactory
val point = factory.createPoint(motionEvent.x, motionEvent.y)
val action = FocusMeteringAction.Builder(point).build()
imageCapture?.camera?.cameraControl?.startFocusAndMetering(action)
true
}
MotionEvent.ACTION_MOVE -> scaleGestureDetector.onTouchEvent(motionEvent)
else -> false
}
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
when (requestCode) {
CAMERA_PERMISSION_REQUEST_CODE -> if (grantResults.size == 1) {
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.error)
.setMessage(R.string.camera_perm_needed)
.setCancelable(false)
.setPositiveButton(R.string.ok) { _, _ ->
isFinishingIntentionally = true
finish()
}.show()
} else { } else {
ColoredAlertDialogBuilder(context) setupCamera()
}
}
}
}
private fun adaptPreviewSize(resolution: Size){
val metrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(metrics)
//resolution.width and resolution.height seem to be inverted
val width = resolution.height
val height = resolution.width
camera_preview.layoutParams = if (metrics.widthPixels < width){
RelativeLayout.LayoutParams(
metrics.widthPixels,
(height * (metrics.widthPixels.toFloat() / width)).toInt()
)
} else {
RelativeLayout.LayoutParams(width, height)
}
(camera_preview.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.CENTER_IN_PARENT)
}
private fun setupCamera(resolution: Size? = null){
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(camera_preview.surfaceProvider)
}
val builder = ImageCapture.Builder()
.setFlashMode(ImageCapture.FLASH_MODE_AUTO)
resolution?.let {
builder.setTargetResolution(it)
}
val hdrImageCapture = HdrImageCaptureExtender.create(builder)
val cameraSelector = if (isBackCamera){ CameraSelector.DEFAULT_BACK_CAMERA } else { CameraSelector.DEFAULT_FRONT_CAMERA }
if (hdrImageCapture.isExtensionAvailable(cameraSelector)){
hdrImageCapture.enableExtension(cameraSelector)
}
imageCapture = builder.build()
cameraProvider.unbindAll()
val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
adaptPreviewSize(imageCapture!!.attachedSurfaceResolution!!)
val info = Camera2CameraInfo.from(camera.cameraInfo)
val cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
val characteristics = cameraManager.getCameraCharacteristics(info.cameraId)
characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)?.let { streamConfigurationMap ->
resolutions = streamConfigurationMap.getOutputSizes(imageCapture!!.imageFormat)
}
}, ContextCompat.getMainExecutor(this))
}
private fun takePhoto() {
val imageCapture = imageCapture ?: return
val outputBuff = ByteArrayOutputStream()
val outputOptions = ImageCapture.OutputFileOptions.Builder(outputBuff).build()
imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
take_photo_button.onPhotoTaken()
if (gocryptfsVolume.importFile(ByteArrayInputStream(outputBuff.toByteArray()), PathUtils.pathJoin(outputDirectory, fileName))){
Toast.makeText(applicationContext, getString(R.string.picture_save_success, fileName), Toast.LENGTH_SHORT).show()
} else {
ColoredAlertDialogBuilder(applicationContext)
.setTitle(R.string.error) .setTitle(R.string.error)
.setMessage(R.string.picture_save_failed) .setMessage(R.string.picture_save_failed)
.setCancelable(false) .setCancelable(false)
@ -83,10 +207,11 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
.show() .show()
} }
} }
override fun onError(exception: ImageCaptureException) {
take_photo_button.onPhotoTaken()
Toast.makeText(applicationContext, exception.message, Toast.LENGTH_SHORT).show()
}
}) })
take_photo_button.onClick = ::onClickTakePhoto
orientedIcons = listOf(image_hdr, image_timer, image_grid, image_close, image_flash, image_camera_switch)
sensorOrientationListener = SensorOrientationListener(this)
} }
private fun onClickTakePhoto() { private fun onClickTakePhoto() {
@ -102,49 +227,41 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
Thread.sleep(1000) Thread.sleep(1000)
} }
runOnUiThread { runOnUiThread {
camera.takePicture() takePhoto()
text_timer.visibility = View.GONE text_timer.visibility = View.GONE
} }
}.start() }.start()
} else { } else {
camera.takePicture() takePhoto()
} }
} }
fun onClickFlash(view: View) { fun onClickFlash(view: View) {
currentFlashModeIndex = MiscUtils.incrementIndex(currentFlashModeIndex, flashModes) image_flash.setImageResource(when (imageCapture?.flashMode) {
camera.flash = flashModes[currentFlashModeIndex] ImageCapture.FLASH_MODE_AUTO -> {
image_flash.setImageResource(when (camera.flash) { imageCapture?.flashMode = ImageCapture.FLASH_MODE_ON
Flash.AUTO -> R.drawable.icon_flash_auto R.drawable.icon_flash_on
Flash.ON -> R.drawable.icon_flash_on }
else -> R.drawable.icon_flash_off ImageCapture.FLASH_MODE_ON -> {
imageCapture?.flashMode = ImageCapture.FLASH_MODE_OFF
R.drawable.icon_flash_off
}
else -> {
imageCapture?.flashMode = ImageCapture.FLASH_MODE_AUTO
R.drawable.icon_flash_auto
}
}) })
} }
fun onClickCameraSwitch(view: View) { fun onClickCameraSwitch(view: View) {
camera.toggleFacing() isBackCamera = if (isBackCamera) {
if (camera.facing == Facing.FRONT){
image_camera_switch.setImageResource(R.drawable.icon_camera_back)
} else {
image_camera_switch.setImageResource(R.drawable.icon_camera_front) image_camera_switch.setImageResource(R.drawable.icon_camera_front)
Thread { false
Thread.sleep(25) } else {
camera.flash = flashModes[currentFlashModeIndex] //refresh flash mode after switching camera image_camera_switch.setImageResource(R.drawable.icon_camera_back)
}.start() true
}
}
fun onClickHDR(view: View) {
camera.hdr = when (camera.hdr){
Hdr.ON -> {
image_hdr.setImageResource(R.drawable.icon_hdr_off)
Hdr.OFF
}
Hdr.OFF -> {
image_hdr.setImageResource(R.drawable.icon_hdr_on)
Hdr.ON
}
} }
setupCamera()
} }
fun onClickTimer(view: View) { fun onClickTimer(view: View) {
@ -173,16 +290,18 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
dialog.show() dialog.show()
} }
fun onClickGrid(view: View) { fun onClickRatio(view: View) {
ColoredAlertDialogBuilder(this) resolutions?.let {
.setTitle(getString(R.string.choose_grid)) ColoredAlertDialogBuilder(this)
.setSingleChoiceItems(gridTitles.map { getString(it) }.toTypedArray(), gridValues.indexOf(camera.grid)){ dialog, which -> .setTitle(R.string.choose_resolution)
camera.grid = gridValues[which] .setSingleChoiceItems(DialogSingleChoiceAdapter(this, it.map { size -> size.toString() }.toTypedArray()), currentResolutionIndex) { dialog, which ->
image_grid.setImageResource(if (camera.grid == Grid.OFF){ R.drawable.icon_grid_off } else { R.drawable.icon_grid_on }) setupCamera(resolutions!![which])
dialog.dismiss() dialog.dismiss()
} currentResolutionIndex = which
.setNegativeButton(R.string.cancel, null) }
.show() .setNegativeButton(R.string.cancel, null)
.show()
}
} }
fun onClickClose(view: View) { fun onClickClose(view: View) {
@ -192,6 +311,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
cameraExecutor.shutdown()
if (!isFinishingIntentionally) { if (!isFinishingIntentionally) {
gocryptfsVolume.close() gocryptfsVolume.close()
RestrictedFileProvider.wipeAll(this) RestrictedFileProvider.wipeAll(this)

View File

@ -419,7 +419,7 @@ open class BaseExplorerActivity : BaseActivity() {
setCurrentPath(currentDirectoryPath) setCurrentPath(currentDirectoryPath)
dialog.dismiss() dialog.dismiss()
} }
.setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() } .setNegativeButton(R.string.cancel, null)
.show() .show()
true true
} }

View File

@ -1,5 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF" <vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20,2L4,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM8,20L4,20v-4h4v4zM8,14L4,14v-4h4v4zM8,8L4,8L4,4h4v4zM14,20h-4v-4h4v4zM14,14h-4v-4h4v4zM14,8h-4L10,4h4v4zM20,20h-4v-4h4v4zM20,14h-4v-4h4v4zM20,8h-4L16,4h4v4z"/> <path android:fillColor="@android:color/white" android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"/>
</vector> </vector>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M8,4v1.45l2,2L10,4h4v4h-3.45l2,2L14,10v1.45l2,2L16,10h4v4h-3.45l2,2L20,16v1.45l2,2L22,4c0,-1.1 -0.9,-2 -2,-2L4.55,2l2,2L8,4zM16,4h4v4h-4L16,4zM1.27,1.27L0,2.55l2,2L2,20c0,1.1 0.9,2 2,2h15.46l2,2 1.27,-1.27L1.27,1.27zM10,12.55L11.45,14L10,14v-1.45zM4,6.55L5.45,8L4,8L4,6.55zM8,20L4,20v-4h4v4zM8,14L4,14v-4h3.45l0.55,0.55L8,14zM14,20h-4v-4h3.45l0.55,0.54L14,20zM16,20v-1.46L17.46,20L16,20z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M17.5,15v-2h1.1l0.9,2L21,15l-0.9,-2.1c0.5,-0.2 0.9,-0.8 0.9,-1.4v-1c0,-0.8 -0.7,-1.5 -1.5,-1.5L16,9v4.9l1.1,1.1h0.4zM17.5,10.5h2v1h-2v-1zM13,10.5v0.4l1.5,1.5v-1.9c0,-0.8 -0.7,-1.5 -1.5,-1.5h-1.9l1.5,1.5h0.4zM9.5,9.5l-7,-7 -1.1,1L6.9,9h-0.4v2h-2L4.5,9L3,9v6h1.5v-2.5h2L6.5,15L8,15v-4.9l1.5,1.5L9.5,15h3.4l7.6,7.6 1.1,-1.1 -12.1,-12z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M21,11.5v-1c0,-0.8 -0.7,-1.5 -1.5,-1.5L16,9v6h1.5v-2h1.1l0.9,2L21,15l-0.9,-2.1c0.5,-0.3 0.9,-0.8 0.9,-1.4zM19.5,11.5h-2v-1h2v1zM6.5,11h-2L4.5,9L3,9v6h1.5v-2.5h2L6.5,15L8,15L8,9L6.5,9v2zM13,9L9.5,9v6L13,15c0.8,0 1.5,-0.7 1.5,-1.5v-3c0,-0.8 -0.7,-1.5 -1.5,-1.5zM13,13.5h-2v-3h2v3z"/>
</vector>

View File

@ -6,17 +6,11 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".CameraActivity"> tools:context=".CameraActivity">
<com.otaliastudios.cameraview.CameraView <androidx.camera.view.PreviewView
android:id="@+id/camera" android:id="@+id/camera_preview"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="0dp"
app:cameraGesturePinch="zoom" android:layout_centerInParent="true"/>
app:cameraGestureTap="autoFocus"
app:cameraAutoFocusMarker="@string/cameraview_default_autofocus_marker"
app:cameraHdr="on"
app:cameraPictureFormat="jpeg"
app:cameraPlaySounds="false"
app:cameraAudio="off"/>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -24,11 +18,11 @@
android:layout_marginTop="20dp"> android:layout_marginTop="20dp">
<ImageView <ImageView
android:id="@+id/image_hdr" android:id="@+id/image_ratio"
android:layout_width="30dp" android:layout_width="30dp"
android:layout_height="30dp" android:layout_height="30dp"
android:onClick="onClickHDR" android:onClick="onClickRatio"
android:src="@drawable/icon_hdr_on" android:src="@drawable/icon_aspect_ratio"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/image_timer" app:layout_constraintEnd_toStartOf="@id/image_timer"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -41,19 +35,8 @@
android:onClick="onClickTimer" android:onClick="onClickTimer"
android:src="@drawable/icon_timer_off" android:src="@drawable/icon_timer_off"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/image_grid" app:layout_constraintEnd_toStartOf="@id/image_close"
app:layout_constraintStart_toEndOf="@id/image_hdr" app:layout_constraintStart_toEndOf="@id/image_ratio"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/image_grid"
android:layout_width="30dp"
android:layout_height="30dp"
android:onClick="onClickGrid"
android:src="@drawable/icon_grid_off"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/image_close"
app:layout_constraintStart_toEndOf="@+id/image_timer"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<ImageView <ImageView
@ -64,7 +47,7 @@
android:src="@drawable/icon_close" android:src="@drawable/icon_close"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/image_grid" app:layout_constraintStart_toEndOf="@id/image_timer"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -162,10 +162,6 @@
<string name="move_success_msg">The selected items have been successfully moved.</string> <string name="move_success_msg">The selected items have been successfully moved.</string>
<string name="move_success">Move successful !</string> <string name="move_success">Move successful !</string>
<string name="enter_timer_duration">Enter the timer duration (in s)</string> <string name="enter_timer_duration">Enter the timer duration (in s)</string>
<string name="grid_none">None</string>
<string name="grid_3x3">3x3</string>
<string name="grid_4x4">4x4</string>
<string name="choose_grid">Choose grid</string>
<string name="timer_empty_error_msg">Please enter a numeric value</string> <string name="timer_empty_error_msg">Please enter a numeric value</string>
<string name="path_from_uri_null_error_msg">Failed to retrieve the selected path.</string> <string name="path_from_uri_null_error_msg">Failed to retrieve the selected path.</string>
<string name="create_cant_write_error_msg">DroidFS doesn\'t have write access to this path. Please try another location.</string> <string name="create_cant_write_error_msg">DroidFS doesn\'t have write access to this path. Please try another location.</string>
@ -193,4 +189,6 @@
<string name="hidden_volume">Hidden Volume</string> <string name="hidden_volume">Hidden Volume</string>
<string name="error_slash_in_name">Volume name cannot contain slashes</string> <string name="error_slash_in_name">Volume name cannot contain slashes</string>
<string name="hidden_volume_warning">Hidden volumes are stored in the app\'s internal storage. Other apps can\'t see these volumes without root access. However, if you uninstall DroidFS or clear data of the app, all your hidden volumes will be LOST. Be sure to make backups !</string> <string name="hidden_volume_warning">Hidden volumes are stored in the app\'s internal storage. Other apps can\'t see these volumes without root access. However, if you uninstall DroidFS or clear data of the app, all your hidden volumes will be LOST. Be sure to make backups !</string>
<string name="camera_perm_needed">Camera permission is needed to take photo.</string>
<string name="choose_resolution">Choose a resolution</string>
</resources> </resources>

View File

@ -6,7 +6,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.0' classpath 'com.android.tools.build:gradle:4.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }