DroidFS/app/src/main/java/sushi/hardcore/droidfs/file_viewers/ImageViewer.kt

259 lines
10 KiB
Kotlin
Raw Normal View History

2020-07-17 16:35:39 +02:00
package sushi.hardcore.droidfs.file_viewers
2020-08-25 15:43:47 +02:00
import android.content.res.Configuration
2020-07-17 16:35:39 +02:00
import android.graphics.Bitmap
import android.graphics.Matrix
2020-07-18 18:44:53 +02:00
import android.graphics.drawable.Drawable
2020-07-17 16:35:39 +02:00
import android.os.Handler
2020-08-01 16:43:48 +02:00
import android.view.MotionEvent
2020-07-17 16:35:39 +02:00
import android.view.View
2020-09-29 11:14:30 +02:00
import android.view.WindowManager
2020-08-25 15:43:47 +02:00
import android.widget.Toast
2020-07-18 18:44:53 +02:00
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
2020-08-01 16:43:48 +02:00
import sushi.hardcore.droidfs.ConstValues
2020-07-17 16:35:39 +02:00
import sushi.hardcore.droidfs.R
2021-06-11 20:23:54 +02:00
import sushi.hardcore.droidfs.databinding.ActivityImageViewerBinding
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
2020-08-25 15:43:47 +02:00
import sushi.hardcore.droidfs.widgets.ZoomableImageView
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
2020-08-25 15:43:47 +02:00
import java.io.File
2020-07-18 18:44:53 +02:00
import java.security.MessageDigest
2020-08-01 16:43:48 +02:00
import kotlin.math.abs
2020-07-17 16:35:39 +02:00
class ImageViewer: FileViewerActivity() {
companion object {
private const val hideDelay: Long = 3000
2020-08-01 16:43:48 +02:00
private const val MIN_SWIPE_DISTANCE = 150
2020-07-17 16:35:39 +02:00
}
2021-06-11 20:23:54 +02:00
private lateinit var fileName: String
2021-09-01 20:15:11 +02:00
private lateinit var handler: Handler
2020-07-18 18:44:53 +02:00
private lateinit var glideImage: RequestBuilder<Drawable>
2020-08-01 16:43:48 +02:00
private var x1 = 0F
private var x2 = 0F
2020-08-25 15:43:47 +02:00
private var slideshowActive = false
2020-07-18 18:44:53 +02:00
private var rotationAngle: Float = 0F
private var rotatedBitmap: Bitmap? = null
2020-08-25 15:43:47 +02:00
private val hideUI = Runnable {
2021-06-11 20:23:54 +02:00
binding.actionButtons.visibility = View.GONE
binding.actionBar.visibility = View.GONE
2020-08-25 15:43:47 +02:00
}
2020-11-03 13:34:40 +01:00
private val slideshowNext = Runnable {
if (slideshowActive){
2021-06-11 20:23:54 +02:00
binding.imageViewer.resetZoomFactor()
2020-11-03 13:34:40 +01:00
swipeImage(-1F, true)
}
}
2021-06-11 20:23:54 +02:00
private lateinit var binding: ActivityImageViewerBinding
2021-03-17 21:11:14 +01:00
override fun getFileType(): String {
return "image"
}
2020-07-17 16:35:39 +02:00
override fun viewFile() {
2021-06-11 20:23:54 +02:00
binding = ActivityImageViewerBinding.inflate(layoutInflater)
setContentView(binding.root)
2021-09-01 20:15:11 +02:00
handler = Handler(mainLooper)
2021-06-11 20:23:54 +02:00
binding.imageViewer.setOnInteractionListener(object : ZoomableImageView.OnInteractionListener {
override fun onSingleTap(event: MotionEvent?) {
handler.removeCallbacks(hideUI)
2021-06-11 20:23:54 +02:00
if (binding.actionButtons.visibility == View.GONE) {
binding.actionButtons.visibility = View.VISIBLE
binding.actionBar.visibility = View.VISIBLE
handler.postDelayed(hideUI, hideDelay)
} else {
hideUI.run()
2020-08-01 16:43:48 +02:00
}
}
override fun onTouch(event: MotionEvent?) {
2021-06-11 20:23:54 +02:00
if (!binding.imageViewer.isZoomed) {
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
x1 = event.x
}
MotionEvent.ACTION_UP -> {
x2 = event.x
val deltaX = x2 - x1
if (abs(deltaX) > MIN_SWIPE_DISTANCE) {
askSaveRotation { swipeImage(deltaX) }
2020-08-01 16:43:48 +02:00
}
}
}
}
}
})
2021-06-11 20:23:54 +02:00
binding.imageDelete.setOnClickListener {
2021-06-07 16:34:50 +02:00
ColoredAlertDialogBuilder(this)
.keepFullScreen()
.setTitle(R.string.warning)
.setPositiveButton(R.string.ok) { _, _ ->
createPlaylist() //be sure the playlist is created before deleting if there is only one image
if (gocryptfsVolume.removeFile(filePath)) {
playlistNext(true)
refreshPlaylist()
if (mappedPlaylist.size == 0) { //deleted all images of the playlist
goBackToExplorer()
} else {
loadImage()
}
} else {
ColoredAlertDialogBuilder(this)
.keepFullScreen()
.setTitle(R.string.error)
.setMessage(getString(R.string.remove_failed, fileName))
.setPositiveButton(R.string.ok, null)
.show()
}
}
.setNegativeButton(R.string.cancel, null)
.setMessage(getString(R.string.single_delete_confirm, fileName))
.show()
}
2021-06-11 20:23:54 +02:00
binding.imageButtonSlideshow.setOnClickListener {
2021-06-07 16:34:50 +02:00
if (!slideshowActive){
slideshowActive = true
handler.postDelayed(slideshowNext, ConstValues.slideshow_delay)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
hideUI.run()
Toast.makeText(this, R.string.slideshow_started, Toast.LENGTH_SHORT).show()
} else {
stopSlideshow()
}
}
2021-06-11 20:23:54 +02:00
binding.imagePrevious.setOnClickListener {
2021-06-07 16:34:50 +02:00
askSaveRotation {
2021-06-11 20:23:54 +02:00
binding.imageViewer.resetZoomFactor()
2021-06-07 16:34:50 +02:00
swipeImage(1F)
}
}
2021-06-11 20:23:54 +02:00
binding.imageNext.setOnClickListener {
2021-06-07 16:34:50 +02:00
askSaveRotation {
2021-06-11 20:23:54 +02:00
binding.imageViewer.resetZoomFactor()
2021-06-07 16:34:50 +02:00
swipeImage(-1F)
}
}
2021-06-11 20:23:54 +02:00
binding.imageRotateRight.setOnClickListener {
2021-06-07 16:34:50 +02:00
rotationAngle += 90
rotateImage()
}
2021-06-11 20:23:54 +02:00
binding.imageRotateLeft.setOnClickListener {
2021-06-07 16:34:50 +02:00
rotationAngle -= 90
rotateImage()
}
loadImage()
handler.postDelayed(hideUI, hideDelay)
}
private fun loadImage(){
loadWholeFile(filePath)?.let {
glideImage = Glide.with(this).load(it)
2021-06-11 20:23:54 +02:00
glideImage.into(binding.imageViewer)
fileName = File(filePath).name
2021-06-11 20:23:54 +02:00
binding.textFilename.text = fileName
rotationAngle = 0F
2020-08-25 15:43:47 +02:00
}
}
override fun onUserInteraction() {
super.onUserInteraction()
handler.removeCallbacks(hideUI)
handler.postDelayed(hideUI, hideDelay)
}
2020-11-03 13:34:40 +01:00
private fun swipeImage(deltaX: Float, slideshowSwipe: Boolean = false){
2021-03-17 21:11:14 +01:00
playlistNext(deltaX < 0)
loadImage()
if (slideshowActive){
if (!slideshowSwipe) { //reset slideshow delay if user swipes
handler.removeCallbacks(slideshowNext)
2020-11-03 13:34:40 +01:00
}
2021-03-17 21:11:14 +01:00
handler.postDelayed(slideshowNext, ConstValues.slideshow_delay)
2020-08-25 15:43:47 +02:00
}
}
private fun stopSlideshow(){
slideshowActive = false
2020-09-29 11:14:30 +02:00
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
Toast.makeText(this, R.string.slideshow_stopped, Toast.LENGTH_SHORT).show()
}
override fun onBackPressed() {
if (slideshowActive){
stopSlideshow()
2020-08-25 15:43:47 +02:00
} else {
askSaveRotation { super.onBackPressed() }
2020-08-01 16:43:48 +02:00
}
}
class RotateTransformation(private val imageViewer: ImageViewer): BitmapTransformation() {
2020-07-18 18:44:53 +02:00
override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap? {
2020-07-18 18:44:53 +02:00
val matrix = Matrix()
matrix.postRotate(imageViewer.rotationAngle)
imageViewer.rotatedBitmap = Bitmap.createBitmap(toTransform, 0, 0, toTransform.width, toTransform.height, matrix, true)
return imageViewer.rotatedBitmap
2020-07-17 16:35:39 +02:00
}
2020-07-18 18:44:53 +02:00
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update("rotate${imageViewer.rotationAngle}".toByteArray())
2020-07-17 16:35:39 +02:00
}
}
2020-07-18 18:44:53 +02:00
private fun rotateImage(){
2021-06-11 20:23:54 +02:00
binding.imageViewer.restoreZoomNormal()
glideImage.transform(RotateTransformation(this)).into(binding.imageViewer)
2020-07-17 16:35:39 +02:00
}
private fun askSaveRotation(callback: () -> Unit){
2020-11-03 13:34:40 +01:00
if (rotationAngle%360 != 0f && !slideshowActive){
ColoredAlertDialogBuilder(this)
.keepFullScreen()
.setTitle(R.string.warning)
.setMessage(R.string.ask_save_img_rotated)
.setNegativeButton(R.string.no) { _, _ -> callback() }
2020-11-03 13:34:40 +01:00
.setNeutralButton(R.string.cancel, null)
.setPositiveButton(R.string.yes) { _, _ ->
val outputStream = ByteArrayOutputStream()
if (rotatedBitmap?.compress(
if (fileName.endsWith("png", true)){
Bitmap.CompressFormat.PNG
} else {
Bitmap.CompressFormat.JPEG
}, 100, outputStream) == true
){
if (gocryptfsVolume.importFile(ByteArrayInputStream(outputStream.toByteArray()), filePath)){
Toast.makeText(this, R.string.image_saved_successfully, Toast.LENGTH_SHORT).show()
callback()
} else {
ColoredAlertDialogBuilder(this)
.keepFullScreen()
.setTitle(R.string.error)
.setMessage(R.string.file_write_failed)
.setPositiveButton(R.string.ok, null)
.show()
}
} else {
ColoredAlertDialogBuilder(this)
.keepFullScreen()
.setTitle(R.string.error)
.setMessage(R.string.bitmap_compress_failed)
.setPositiveButton(R.string.ok, null)
.show()
}
}
.show()
} else {
callback()
}
}
2020-08-25 15:43:47 +02:00
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
2021-06-11 20:23:54 +02:00
binding.imageViewer.restoreZoomNormal()
2020-07-17 16:35:39 +02:00
}
}