Thumbnails

This commit is contained in:
Matéo Duparc 2021-11-11 15:05:33 +01:00
parent e3df7be3b5
commit fd5ddc02b1
Signed by: hardcoresushi
GPG Key ID: 007F84120107191E
13 changed files with 180 additions and 122 deletions

View File

@ -91,7 +91,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
binding = ActivityCameraBinding.inflate(layoutInflater)
setContentView(binding.root)
gocryptfsVolume = GocryptfsVolume(intent.getIntExtra("sessionID", -1))
gocryptfsVolume = GocryptfsVolume(applicationContext, intent.getIntExtra("sessionID", -1))
outputDirectory = intent.getStringExtra("path")!!
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

View File

@ -38,14 +38,5 @@ class ConstValues {
fun isText(path: String): Boolean {
return isExtensionType("text", path)
}
fun getAssociatedDrawable(path: String): Int {
return when {
isAudio(path) -> R.drawable.icon_file_audio
isImage(path) -> R.drawable.icon_file_image
isVideo(path) -> R.drawable.icon_file_video
isText(path) -> R.drawable.icon_file_text
else -> R.drawable.icon_file_unknown
}
}
}
}

View File

@ -167,7 +167,7 @@ class CreateActivity : VolumeActionActivity() {
super.onPause()
//Closing volume if leaving activity while showing dialog
if (sessionID != -1 && !isStartingExplorer) {
GocryptfsVolume(sessionID).close()
GocryptfsVolume(applicationContext, sessionID).close()
finish()
}
}

View File

@ -9,7 +9,7 @@ import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
class GocryptfsVolume(var sessionID: Int) {
class GocryptfsVolume(val applicationContext: Context, var sessionID: Int) {
private external fun native_close(sessionID: Int)
private external fun native_is_closed(sessionID: Int): Boolean
private external fun native_list_dir(sessionID: Int, dir_path: String): MutableList<ExplorerElement>
@ -47,79 +47,79 @@ class GocryptfsVolume(var sessionID: Int) {
}
fun close() {
synchronized(this){
synchronized(applicationContext) {
native_close(sessionID)
}
}
fun isClosed(): Boolean {
synchronized(this){
synchronized(applicationContext) {
return native_is_closed(sessionID)
}
}
fun listDir(dir_path: String): MutableList<ExplorerElement> {
synchronized(this){
synchronized(applicationContext) {
return native_list_dir(sessionID, dir_path)
}
}
fun mkdir(dir_path: String): Boolean {
synchronized(this){
synchronized(applicationContext) {
return native_mkdir(sessionID, dir_path, ConstValues.DIRECTORY_MODE)
}
}
fun rmdir(dir_path: String): Boolean {
synchronized(this){
synchronized(applicationContext) {
return native_rmdir(sessionID, dir_path)
}
}
fun removeFile(file_path: String): Boolean {
synchronized(this){
synchronized(applicationContext) {
return native_remove_file(sessionID, file_path)
}
}
fun pathExists(file_path: String): Boolean {
synchronized(this){
synchronized(applicationContext) {
return native_path_exists(sessionID, file_path)
}
}
fun getSize(file_path: String): Long {
synchronized(this){
synchronized(applicationContext) {
return native_get_size(sessionID, file_path)
}
}
fun closeFile(handleID: Int) {
synchronized(this){
synchronized(applicationContext) {
native_close_file(sessionID, handleID)
}
}
fun openReadMode(file_path: String): Int {
synchronized(this){
synchronized(applicationContext) {
return native_open_read_mode(sessionID, file_path)
}
}
fun openWriteMode(file_path: String): Int {
synchronized(this){
synchronized(applicationContext) {
return native_open_write_mode(sessionID, file_path, ConstValues.FILE_MODE)
}
}
fun readFile(handleID: Int, offset: Long, buff: ByteArray): Int {
synchronized(this){
synchronized(applicationContext) {
return native_read_file(sessionID, handleID, offset, buff)
}
}
fun writeFile(handleID: Int, offset: Long, buff: ByteArray, buff_size: Int): Int {
synchronized(this){
synchronized(applicationContext) {
return native_write_file(sessionID, handleID, offset, buff, buff_size)
}
}
@ -237,4 +237,40 @@ class GocryptfsVolume(var sessionID: Int) {
null
}
}
fun loadWholeFile(fullPath: String, maxSize: Long? = null): Pair<ByteArray?, Int> {
val fileSize = getSize(fullPath)
return if (fileSize >= 0) {
maxSize?.let {
if (fileSize > it) {
return Pair(null, 0)
}
}
try {
val fileBuff = ByteArray(fileSize.toInt())
val handleID = openReadMode(fullPath)
if (handleID == -1) {
Pair(null, 3)
} else {
var offset: Long = 0
val ioBuffer = ByteArray(DefaultBS)
var length: Int
while (readFile(handleID, offset, ioBuffer).also { length = it } > 0) {
System.arraycopy(ioBuffer, 0, fileBuff, offset.toInt(), length)
offset += length.toLong()
}
closeFile(handleID)
if (offset == fileBuff.size.toLong()) {
Pair(fileBuff, 0)
} else {
Pair(null, 4)
}
}
} catch (e: OutOfMemoryError) {
Pair(null, 2)
}
} else {
Pair(null, 1)
}
}
}

View File

@ -170,7 +170,7 @@ class OpenActivity : VolumeActionActivity() {
if (success){
startExplorer()
} else {
GocryptfsVolume(sessionID).close()
GocryptfsVolume(applicationContext, sessionID).close()
}
}
}
@ -257,7 +257,7 @@ class OpenActivity : VolumeActionActivity() {
if (intent.action == "pick" && !isFinishingIntentionally){
val sessionID = intent.getIntExtra("sessionID", -1)
if (sessionID != -1){
GocryptfsVolume(sessionID).close()
GocryptfsVolume(applicationContext, sessionID).close()
RestrictedFileProvider.wipeAll(this)
}
}

View File

@ -1,14 +1,18 @@
package sushi.hardcore.droidfs.adapters
import android.content.Context
import android.view.LayoutInflater
import android.graphics.drawable.Drawable
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import sushi.hardcore.droidfs.ConstValues.Companion.getAssociatedDrawable
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.DrawableImageViewTarget
import com.bumptech.glide.request.transition.Transition
import sushi.hardcore.droidfs.ConstValues
import sushi.hardcore.droidfs.GocryptfsVolume
import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.util.PathUtils
@ -16,17 +20,17 @@ import java.text.DateFormat
import java.util.*
class ExplorerElementAdapter(
context: Context,
private val onExplorerElementClick: (Int) -> Unit,
private val onExplorerElementLongClick: (Int) -> Unit
val activity: AppCompatActivity,
val gocryptfsVolume: GocryptfsVolume?,
val onExplorerElementClick: (Int) -> Unit,
val onExplorerElementLongClick: (Int) -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val dateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.getDefault())
val dateFormat: DateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.getDefault())
var explorerElements = listOf<ExplorerElement>()
set(value) {
field = value
unSelectAll()
}
private val inflater: LayoutInflater = LayoutInflater.from(context)
val selectedItems: MutableList<Int> = ArrayList()
override fun getItemCount(): Int {
@ -72,11 +76,7 @@ class ExplorerElementAdapter(
notifyDataSetChanged()
}
open class ExplorerElementViewHolder(
itemView: View,
private val onClick: (Int) -> Boolean,
private val onLongClick: (Int) -> Boolean,
) : RecyclerView.ViewHolder(itemView) {
open class ExplorerElementViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val textElementName by lazy {
itemView.findViewById<TextView>(R.id.text_element_name)
}
@ -99,57 +99,87 @@ class ExplorerElementAdapter(
open fun bind(explorerElement: ExplorerElement, position: Int) {
textElementName.text = explorerElement.name
selectableContainer.setOnClickListener {
setBackground(onClick(position))
}
selectableContainer.setOnLongClickListener {
setBackground(onLongClick(position))
true
(bindingAdapter as ExplorerElementAdapter?)?.let { adapter ->
selectableContainer.setOnClickListener {
setBackground(adapter.onItemClick(position))
}
selectableContainer.setOnLongClickListener {
setBackground(adapter.onItemLongClick(position))
true
}
}
}
}
open class RegularElementViewHolder(
itemView: View,
private val dateFormat: DateFormat,
onClick: (Int) -> Boolean,
onLongClick: (Int) -> Boolean,
) : ExplorerElementViewHolder(itemView, onClick, onLongClick) {
open class RegularElementViewHolder(itemView: View) : ExplorerElementViewHolder(itemView) {
open fun bind(explorerElement: ExplorerElement, position: Int, isSelected: Boolean) {
super.bind(explorerElement, position)
textElementSize.text = PathUtils.formatSize(explorerElement.size)
textElementMtime.text = dateFormat.format(explorerElement.mTime)
(bindingAdapter as ExplorerElementAdapter?)?.let {
textElementMtime.text = it.dateFormat.format(explorerElement.mTime)
}
setBackground(isSelected)
}
}
class FileViewHolder(
itemView: View,
dateFormat: DateFormat,
onClick: (Int) -> Boolean,
onLongClick: (Int) -> Boolean,
) : RegularElementViewHolder(itemView, dateFormat, onClick, onLongClick) {
class FileViewHolder(itemView: View) : RegularElementViewHolder(itemView) {
var displayThumbnail = true
var target: DrawableImageViewTarget? = null
private fun loadThumbnail(fullPath: String) {
(bindingAdapter as ExplorerElementAdapter?)?.let { adapter ->
adapter.gocryptfsVolume?.let { volume ->
displayThumbnail = true
Thread {
volume.loadWholeFile(fullPath, 50_000_000).first?.let {
if (displayThumbnail) {
adapter.activity.runOnUiThread {
if (displayThumbnail) {
target = Glide.with(adapter.activity).load(it).into(object : DrawableImageViewTarget(icon) {
override fun onResourceReady(
resource: Drawable,
transition: Transition<in Drawable>?
) {
super.onResourceReady(resource, transition)
target = null
}
})
}
}
}
}
}.start()
}
}
}
override fun bind(explorerElement: ExplorerElement, position: Int, isSelected: Boolean) {
super.bind(explorerElement, position, isSelected)
icon.setImageResource(getAssociatedDrawable(explorerElement.name))
icon.setImageResource(
when {
ConstValues.isImage(explorerElement.name) -> {
loadThumbnail(explorerElement.fullPath)
R.drawable.icon_file_image
}
ConstValues.isVideo(explorerElement.name) -> {
loadThumbnail(explorerElement.fullPath)
R.drawable.icon_file_video
}
ConstValues.isText(explorerElement.name) -> R.drawable.icon_file_text
ConstValues.isAudio(explorerElement.name) -> R.drawable.icon_file_audio
else -> R.drawable.icon_file_unknown
}
)
}
}
class DirectoryViewHolder(
itemView: View,
dateFormat: DateFormat,
onClick: (Int) -> Boolean,
onLongClick: (Int) -> Boolean,
) : RegularElementViewHolder(itemView, dateFormat, onClick, onLongClick) {
class DirectoryViewHolder(itemView: View) : RegularElementViewHolder(itemView) {
override fun bind(explorerElement: ExplorerElement, position: Int, isSelected: Boolean) {
super.bind(explorerElement, position, isSelected)
icon.setImageResource(R.drawable.icon_folder)
}
}
class ParentFolderViewHolder(
itemView: View,
onClick: (Int) -> Boolean,
onLongClick: (Int) -> Boolean,
): ExplorerElementViewHolder(itemView, onClick, onLongClick) {
class ParentFolderViewHolder(itemView: View): ExplorerElementViewHolder(itemView) {
override fun bind(explorerElement: ExplorerElement, position: Int) {
super.bind(explorerElement, position)
textElementSize.text = ""
@ -158,12 +188,22 @@ class ExplorerElementAdapter(
}
}
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
if (holder is FileViewHolder) {
//cancel pending thumbnail display
holder.displayThumbnail = false
holder.target?.let {
Glide.with(activity).clear(it)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = inflater.inflate(R.layout.adapter_explorer_element, parent, false)
val view = activity.layoutInflater.inflate(R.layout.adapter_explorer_element, parent, false)
return when (viewType) {
ExplorerElement.REGULAR_FILE_TYPE -> FileViewHolder(view, dateFormat, ::onItemClick, ::onItemLongClick)
ExplorerElement.DIRECTORY_TYPE -> DirectoryViewHolder(view, dateFormat, ::onItemClick, ::onItemLongClick)
ExplorerElement.PARENT_FOLDER_TYPE -> ParentFolderViewHolder(view, ::onItemClick, ::onItemLongClick)
ExplorerElement.REGULAR_FILE_TYPE -> FileViewHolder(view)
ExplorerElement.DIRECTORY_TYPE -> DirectoryViewHolder(view)
ExplorerElement.PARENT_FOLDER_TYPE -> ParentFolderViewHolder(view)
else -> throw IllegalArgumentException()
}
}

View File

@ -78,7 +78,7 @@ open class BaseExplorerActivity : BaseActivity() {
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
volumeName = intent.getStringExtra("volume_name") ?: ""
val sessionID = intent.getIntExtra("sessionID", -1)
gocryptfsVolume = GocryptfsVolume(sessionID)
gocryptfsVolume = GocryptfsVolume(applicationContext, sessionID)
sortOrderEntries = resources.getStringArray(R.array.sort_orders_entries)
sortOrderValues = resources.getStringArray(R.array.sort_orders_values)
foldersFirst = sharedPrefs.getBoolean("folders_first", true)
@ -95,7 +95,16 @@ open class BaseExplorerActivity : BaseActivity() {
setSupportActionBar(toolbar)
title = ""
titleText.text = getString(R.string.volume, volumeName)
explorerAdapter = ExplorerElementAdapter(this, ::onExplorerItemClick, ::onExplorerItemLongClick)
explorerAdapter = ExplorerElementAdapter(
this,
if (sharedPrefs.getBoolean("thumbnails", true)) {
gocryptfsVolume
} else {
null
},
::onExplorerItemClick,
::onExplorerItemLongClick
)
explorerViewModel= ViewModelProvider(this).get(ExplorerViewModel::class.java)
currentDirectoryPath = explorerViewModel.currentDirectoryPath
setCurrentPath(currentDirectoryPath)

View File

@ -36,7 +36,7 @@ class ExplorerActivity : BaseExplorerActivity() {
if (result.resultCode == Activity.RESULT_OK) {
result.data?.let { resultIntent ->
val remoteSessionID = resultIntent.getIntExtra("sessionID", -1)
val remoteGocryptfsVolume = GocryptfsVolume(remoteSessionID)
val remoteGocryptfsVolume = GocryptfsVolume(applicationContext, remoteSessionID)
val path = resultIntent.getStringExtra("path")
val operationFiles = ArrayList<OperationFile>()
if (path == null){ //multiples elements

View File

@ -86,8 +86,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
if (!isFinishingIntentionally && !usf_keep_open){
val sessionID = intent.getIntExtra("originalSessionID", -1)
if (sessionID != -1){
val v = GocryptfsVolume(sessionID)
v.close()
GocryptfsVolume(applicationContext, sessionID).close()
}
super.closeVolumeOnDestroy()
}

View File

@ -30,7 +30,7 @@ abstract class FileViewerActivity: BaseActivity() {
filePath = intent.getStringExtra("path")!!
originalParentPath = PathUtils.getParentPath(filePath)
val sessionID = intent.getIntExtra("sessionID", -1)
gocryptfsVolume = GocryptfsVolume(sessionID)
gocryptfsVolume = GocryptfsVolume(applicationContext, sessionID)
usf_keep_open = sharedPrefs.getBoolean("usf_keep_open", false)
foldersFirst = sharedPrefs.getBoolean("folders_first", true)
windowInsetsController = WindowInsetsControllerCompat(window, window.decorView)
@ -56,51 +56,20 @@ abstract class FileViewerActivity: BaseActivity() {
}
protected fun loadWholeFile(path: String): ByteArray? {
val fileSize = gocryptfsVolume.getSize(path)
if (fileSize >= 0){
try {
val fileBuff = ByteArray(fileSize.toInt())
var success = false
val handleID = gocryptfsVolume.openReadMode(path)
if (handleID != -1) {
var offset: Long = 0
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
var length: Int
while (gocryptfsVolume.readFile(handleID, offset, ioBuffer).also { length = it } > 0){
System.arraycopy(ioBuffer, 0, fileBuff, offset.toInt(), length)
offset += length.toLong()
}
gocryptfsVolume.closeFile(handleID)
success = offset == fileBuff.size.toLong()
}
if (success){
return fileBuff
} else {
CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.error)
.setMessage(R.string.read_file_failed)
.setCancelable(false)
.setPositiveButton(R.string.ok) { _, _ -> goBackToExplorer() }
.show()
}
} catch (e: OutOfMemoryError){
CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.error)
.setMessage(R.string.outofmemoryerror_msg)
.setCancelable(false)
.setPositiveButton(R.string.ok) { _, _ -> goBackToExplorer() }
.show()
}
} else {
CustomAlertDialogBuilder(this, themeValue)
val result = gocryptfsVolume.loadWholeFile(path)
if (result.second != 0) {
val dialog = CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.error)
.setMessage(R.string.get_size_failed)
.setCancelable(false)
.setPositiveButton(R.string.ok) { _, _ -> goBackToExplorer() }
.show()
when (result.second) {
1 -> dialog.setMessage(R.string.get_size_failed)
2 -> dialog.setMessage(R.string.outofmemoryerror_msg)
else -> dialog.setMessage(R.string.read_file_failed)
}
dialog.show()
}
return null
return result.first
}
protected fun createPlaylist() {

View File

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?attr/colorAccent" android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
</vector>

View File

@ -204,4 +204,6 @@
<string name="encryption_cipher_label">Encryption cipher:</string>
<string name="theme">Theme</string>
<string name="theme_summary">Customize app theme</string>
<string name="thumbnails">Thumbnails</string>
<string name="thumbnails_summary">Show images and videos thumbnails</string>
</resources>

View File

@ -38,6 +38,13 @@
android:title="@string/folders_first"
android:summary="@string/folders_first_summary"/>
<SwitchPreferenceCompat
android:defaultValue="true"
android:icon="@drawable/icon_image"
android:key="thumbnails"
android:title="@string/thumbnails"
android:summary="@string/thumbnails_summary"/>
<SwitchPreferenceCompat
android:defaultValue="true"
app:icon="@drawable/icon_folder_search"