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")
const val MAX_KERNEL_WRITE = 128*1024
const val wipe_passes = 2
const val slideshow_delay: Long = 4000
private val fileExtensions = mapOf(
Pair("image", listOf("png", "jpg", "jpeg")),
Pair("video", listOf("mp4", "webm")),

View File

@ -1,11 +1,13 @@
package sushi.hardcore.droidfs.file_viewers
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Matrix
import android.graphics.drawable.Drawable
import android.os.Handler
import android.view.MotionEvent
import android.view.View
import android.widget.Toast
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder
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.explorers.ExplorerElement
import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.widgets.ZoomableImageView
import java.io.File
import java.security.MessageDigest
import kotlin.math.abs
@ -30,60 +34,96 @@ class ImageViewer: FileViewerActivity() {
private val mappedImages = mutableListOf<ExplorerElement>()
private lateinit var sortOrder: String
private var wasMapped = false
private var slideshowActive = false
private var currentMappedImageIndex = -1
private var rotationAngle: Float = 0F
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() {
val imageBuff = loadWholeFile(filePath)
if (imageBuff != null){
setContentView(R.layout.activity_image_viewer)
glideImage = Glide.with(this).load(imageBuff)
glideImage.into(image_viewer)
handler.postDelayed(hideActionButtons, hideDelay)
}
}
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
if (!image_viewer.isZoomed){
when(event?.action){
MotionEvent.ACTION_DOWN -> {
x1 = event.x
image_viewer.setOnInteractionListener(object : ZoomableImageView.OnInteractionListener{
override fun onSingleTap(event: MotionEvent?) {
handler.removeCallbacks(hideUI)
if (action_buttons.visibility == View.GONE){
action_buttons.visibility = View.VISIBLE
action_bar.visibility = View.VISIBLE
handler.postDelayed(hideUI, hideDelay)
} else {
hideUI.run()
}
}
MotionEvent.ACTION_UP -> {
x2 = event.x
val deltaX = x2 - x1
if (abs(deltaX) > MIN_SWIPE_DISTANCE){
if (!wasMapped){
for (e in gocryptfsVolume.recursiveMapFiles(PathUtils.getParentPath(filePath))){
if (e.isRegularFile && ConstValues.isImage(e.name)){
mappedImages.add(e)
override fun onTouch(event: MotionEvent?) {
if (!image_viewer.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){
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() {
@ -112,16 +152,8 @@ class ImageViewer: FileViewerActivity() {
rotateImage()
}
override fun onUserInteraction() {
super.onUserInteraction()
action_buttons?.let {
handler.removeCallbacks(hideActionButtons)
if (it.visibility == View.GONE){
it.visibility = View.VISIBLE
handler.postDelayed(hideActionButtons, hideDelay)
} else {
it.visibility = View.GONE
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
image_viewer.restoreZoomNormal()
}
}

View File

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

View File

@ -35,6 +35,13 @@ public class ZoomableImageView extends androidx.appcompat.widget.AppCompatImageV
ScaleGestureDetector mScaleDetector;
public interface OnInteractionListener {
void onTouch(MotionEvent event);
void onSingleTap(MotionEvent event);
}
OnInteractionListener onInteractionListener = null;
Context context;
public ZoomableImageView(Context context) {
@ -124,15 +131,28 @@ public class ZoomableImageView extends androidx.appcompat.widget.AppCompatImageV
fixTrans();
}
public void setOnInteractionListener(OnInteractionListener listener){
onInteractionListener = listener;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (onInteractionListener != null){
onInteractionListener.onSingleTap(e);
}
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
// Double tap is detected
public boolean dispatchTouchEvent(MotionEvent event) {
if (onInteractionListener != null){
onInteractionListener.onTouch(event);
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onDoubleTap(MotionEvent e) {
if (saveScale >= maxScale) {
restoreZoomNormal();
} 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"?>
<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:layout_width="match_parent"
android:layout_height="match_parent"
@ -11,6 +12,38 @@
android:layout_height="match_parent"
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
android:id="@+id/action_buttons"
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="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="slideshow_stopped">Slideshow stopped</string>
</resources>