ImageViewer title & slideshow

This commit is contained in:
Hardcore Sushi 2020-08-25 15:43:47 +02:00
parent f44a702647
commit 5c0f317a04
7 changed files with 148 additions and 56 deletions

View File

@ -11,6 +11,7 @@ class ConstValues {
val fakeUri: Uri = Uri.parse("fakeuri://droidfs") val fakeUri: Uri = Uri.parse("fakeuri://droidfs")
const val MAX_KERNEL_WRITE = 128*1024 const val MAX_KERNEL_WRITE = 128*1024
const val wipe_passes = 2 const val wipe_passes = 2
const val slideshow_delay: Long = 4000
private val fileExtensions = mapOf( private val fileExtensions = mapOf(
Pair("image", listOf("png", "jpg", "jpeg")), Pair("image", listOf("png", "jpg", "jpeg")),
Pair("video", listOf("mp4", "webm")), Pair("video", listOf("mp4", "webm")),

View File

@ -1,11 +1,13 @@
package sushi.hardcore.droidfs.file_viewers package sushi.hardcore.droidfs.file_viewers
import android.content.res.Configuration
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Matrix import android.graphics.Matrix
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Handler import android.os.Handler
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.widget.Toast
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
@ -16,6 +18,8 @@ import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.util.MiscUtils import sushi.hardcore.droidfs.util.MiscUtils
import sushi.hardcore.droidfs.explorers.ExplorerElement import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.widgets.ZoomableImageView
import java.io.File
import java.security.MessageDigest import java.security.MessageDigest
import kotlin.math.abs import kotlin.math.abs
@ -30,60 +34,96 @@ class ImageViewer: FileViewerActivity() {
private val mappedImages = mutableListOf<ExplorerElement>() private val mappedImages = mutableListOf<ExplorerElement>()
private lateinit var sortOrder: String private lateinit var sortOrder: String
private var wasMapped = false private var wasMapped = false
private var slideshowActive = false
private var currentMappedImageIndex = -1 private var currentMappedImageIndex = -1
private var rotationAngle: Float = 0F private var rotationAngle: Float = 0F
private val handler = Handler() private val handler = Handler()
private val hideActionButtons = Runnable { action_buttons.visibility = View.GONE } private val hideUI = Runnable {
action_buttons.visibility = View.GONE
action_bar.visibility = View.GONE
}
override fun viewFile() { override fun viewFile() {
val imageBuff = loadWholeFile(filePath) val imageBuff = loadWholeFile(filePath)
if (imageBuff != null){ if (imageBuff != null){
setContentView(R.layout.activity_image_viewer) setContentView(R.layout.activity_image_viewer)
glideImage = Glide.with(this).load(imageBuff) image_viewer.setOnInteractionListener(object : ZoomableImageView.OnInteractionListener{
glideImage.into(image_viewer) override fun onSingleTap(event: MotionEvent?) {
handler.postDelayed(hideActionButtons, hideDelay) handler.removeCallbacks(hideUI)
} if (action_buttons.visibility == View.GONE){
} action_buttons.visibility = View.VISIBLE
action_bar.visibility = View.VISIBLE
override fun dispatchTouchEvent(event: MotionEvent?): Boolean { handler.postDelayed(hideUI, hideDelay)
if (!image_viewer.isZoomed){ } else {
when(event?.action){ hideUI.run()
MotionEvent.ACTION_DOWN -> { }
x1 = event.x
} }
MotionEvent.ACTION_UP -> { override fun onTouch(event: MotionEvent?) {
x2 = event.x if (!image_viewer.isZoomed){
val deltaX = x2 - x1 when(event?.action){
if (abs(deltaX) > MIN_SWIPE_DISTANCE){ MotionEvent.ACTION_DOWN -> {
if (!wasMapped){ x1 = event.x
for (e in gocryptfsVolume.recursiveMapFiles(PathUtils.getParentPath(filePath))){ }
if (e.isRegularFile && ConstValues.isImage(e.name)){ MotionEvent.ACTION_UP -> {
mappedImages.add(e) x2 = event.x
val deltaX = x2 - x1
if (abs(deltaX) > MIN_SWIPE_DISTANCE){
swipeImage(deltaX)
} }
} }
sortOrder = intent.getStringExtra("sortOrder") ?: "name"
ExplorerElement.sortBy(sortOrder, mappedImages)
for ((i, e) in mappedImages.withIndex()){
if (filePath == e.fullPath){
currentMappedImageIndex = i
break
}
}
wasMapped = true
}
currentMappedImageIndex = if (deltaX < 0){
MiscUtils.incrementIndex(currentMappedImageIndex, mappedImages)
} else {
MiscUtils.decrementIndex(currentMappedImageIndex, mappedImages)
}
loadWholeFile(mappedImages[currentMappedImageIndex].fullPath)?.let {
glideImage = Glide.with(this).load(it)
glideImage.into(image_viewer)
} }
} }
} }
} })
glideImage = Glide.with(this).load(imageBuff)
glideImage.into(image_viewer)
text_filename.text = File(filePath).name
handler.postDelayed(hideUI, hideDelay)
}
}
private fun swipeImage(deltaX: Float){
if (!wasMapped){
for (e in gocryptfsVolume.recursiveMapFiles(PathUtils.getParentPath(filePath))){
if (e.isRegularFile && ConstValues.isImage(e.name)){
mappedImages.add(e)
}
}
sortOrder = intent.getStringExtra("sortOrder") ?: "name"
ExplorerElement.sortBy(sortOrder, mappedImages)
for ((i, e) in mappedImages.withIndex()){
if (filePath == e.fullPath){
currentMappedImageIndex = i
break
}
}
wasMapped = true
}
currentMappedImageIndex = if (deltaX < 0){
MiscUtils.incrementIndex(currentMappedImageIndex, mappedImages)
} else {
MiscUtils.decrementIndex(currentMappedImageIndex, mappedImages)
}
loadWholeFile(mappedImages[currentMappedImageIndex].fullPath)?.let {
glideImage = Glide.with(applicationContext).load(it)
glideImage.into(image_viewer)
text_filename.text = File(mappedImages[currentMappedImageIndex].fullPath).name
rotationAngle = 0F
}
}
fun onClickSlideshow(view: View) {
if (!slideshowActive){
slideshowActive = true
Thread {
while (slideshowActive){
runOnUiThread { swipeImage(-1F) }
Thread.sleep(ConstValues.slideshow_delay)
}
}.start()
} else {
slideshowActive = false
Toast.makeText(this, R.string.slideshow_stopped, Toast.LENGTH_SHORT).show()
} }
return super.dispatchTouchEvent(event)
} }
class RotateTransformation(private val rotationAngle: Float): BitmapTransformation() { class RotateTransformation(private val rotationAngle: Float): BitmapTransformation() {
@ -112,16 +152,8 @@ class ImageViewer: FileViewerActivity() {
rotateImage() rotateImage()
} }
override fun onUserInteraction() { override fun onConfigurationChanged(newConfig: Configuration) {
super.onUserInteraction() super.onConfigurationChanged(newConfig)
action_buttons?.let { image_viewer.restoreZoomNormal()
handler.removeCallbacks(hideActionButtons)
if (it.visibility == View.GONE){
it.visibility = View.VISIBLE
handler.postDelayed(hideActionButtons, hideDelay)
} else {
it.visibility = View.GONE
}
}
} }
} }

View File

@ -72,10 +72,10 @@ class TextEditor: FileViewerActivity() {
if (handleID != -1){ if (handleID != -1){
val buff = ByteArrayInputStream(content) val buff = ByteArrayInputStream(content)
var offset: Long = 0 var offset: Long = 0
val io_buffer = ByteArray(GocryptfsVolume.DefaultBS) val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
var length: Int var length: Int
while (buff.read(io_buffer).also { length = it } > 0) { while (buff.read(ioBuffer).also { length = it } > 0) {
val written = gocryptfsVolume.writeFile(handleID, offset, io_buffer, length).toLong() val written = gocryptfsVolume.writeFile(handleID, offset, ioBuffer, length).toLong()
if (written == length.toLong()) { if (written == length.toLong()) {
offset += written offset += written
} else { } else {

View File

@ -35,6 +35,13 @@ public class ZoomableImageView extends androidx.appcompat.widget.AppCompatImageV
ScaleGestureDetector mScaleDetector; ScaleGestureDetector mScaleDetector;
public interface OnInteractionListener {
void onTouch(MotionEvent event);
void onSingleTap(MotionEvent event);
}
OnInteractionListener onInteractionListener = null;
Context context; Context context;
public ZoomableImageView(Context context) { public ZoomableImageView(Context context) {
@ -124,15 +131,28 @@ public class ZoomableImageView extends androidx.appcompat.widget.AppCompatImageV
fixTrans(); fixTrans();
} }
public void setOnInteractionListener(OnInteractionListener listener){
onInteractionListener = listener;
}
@Override @Override
public boolean onSingleTapConfirmed(MotionEvent e) { public boolean onSingleTapConfirmed(MotionEvent e) {
if (onInteractionListener != null){
onInteractionListener.onSingleTap(e);
}
return false; return false;
} }
@Override @Override
public boolean onDoubleTap(MotionEvent e) { public boolean dispatchTouchEvent(MotionEvent event) {
// Double tap is detected if (onInteractionListener != null){
onInteractionListener.onTouch(event);
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onDoubleTap(MotionEvent e) {
if (saveScale >= maxScale) { if (saveScale >= maxScale) {
restoreZoomNormal(); restoreZoomNormal();
} else { } else {

View File

@ -0,0 +1,5 @@
<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="M10,8v8l5,-4 -5,-4zM19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,5h14v14z"/>
</vector>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root_layout" android:id="@+id/root_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -11,6 +12,38 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center"/> android:layout_gravity="center"/>
<RelativeLayout
android:id="@+id/action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_margin="20dp"
android:orientation="horizontal">
<TextView
android:id="@+id/text_filename"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="end"
android:textSize="20sp"
android:layout_marginEnd="40dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"/>
<ImageButton
android:id="@+id/image_button_slideshow"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="#00000000"
android:scaleType="fitCenter"
android:src="@drawable/icon_slideshow"
android:onClick="onClickSlideshow"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"/>
</RelativeLayout>
<LinearLayout <LinearLayout
android:id="@+id/action_buttons" android:id="@+id/action_buttons"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -173,4 +173,5 @@
<string name="open_cant_write_warning">DroidFS doesn\'t have write access to this path. You will only have read-only access to this volume.</string> <string name="open_cant_write_warning">DroidFS doesn\'t have write access to this path. You will only have read-only access to this volume.</string>
<string name="change_pwd_cant_write_error_msg">DroidFS doesn\'t have write access to this path. You can try to move the volume to a writable location.</string> <string name="change_pwd_cant_write_error_msg">DroidFS doesn\'t have write access to this path. You can try to move the volume to a writable location.</string>
<string name="change_pwd_on_sdcard_error_msg">DroidFS can\'t write on removable SD cards, please move the volume to internal storage.</string> <string name="change_pwd_on_sdcard_error_msg">DroidFS can\'t write on removable SD cards, please move the volume to internal storage.</string>
<string name="slideshow_stopped">Slideshow stopped</string>
</resources> </resources>