forked from hardcoresushi/DroidFS
ImageViewer title & slideshow
This commit is contained in:
parent
f44a702647
commit
5c0f317a04
@ -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")),
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
5
app/src/main/res/drawable/icon_slideshow.xml
Normal file
5
app/src/main/res/drawable/icon_slideshow.xml
Normal 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>
|
@ -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"
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user