Thumbnails cache & Don't do full reload on selection change

This commit is contained in:
Matéo Duparc 2022-04-09 19:20:20 +02:00
parent 4f9aa55dfe
commit 4de5b41102
Signed by: hardcoresushi
GPG Key ID: AFE384344A45E13A
5 changed files with 124 additions and 63 deletions

View File

@ -145,8 +145,8 @@ class MainActivity : BaseActivity() {
binding.textNoVolumes.visibility = View.GONE binding.textNoVolumes.visibility = View.GONE
} }
private fun unselectAll() { private fun unselectAll(notifyChange: Boolean = true) {
volumeAdapter.unSelectAll() volumeAdapter.unSelectAll(notifyChange)
invalidateOptionsMenu() invalidateOptionsMenu()
} }
@ -233,7 +233,7 @@ class MainActivity : BaseActivity() {
if (volumeDatabase.removeHash(volumeAdapter.volumes[i])) if (volumeDatabase.removeHash(volumeAdapter.volumes[i]))
volumeAdapter.onVolumeChanged(i) volumeAdapter.onVolumeChanged(i)
} }
unselectAll() unselectAll(false)
true true
} }
R.id.change_password -> { R.id.change_password -> {

View File

@ -1,12 +1,16 @@
package sushi.hardcore.droidfs.adapters package sushi.hardcore.droidfs.adapters
import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.LruCache
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.drawable.toBitmap
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.DrawableImageViewTarget import com.bumptech.glide.request.target.DrawableImageViewTarget
@ -18,6 +22,7 @@ import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import java.text.DateFormat import java.text.DateFormat
import java.util.* import java.util.*
import kotlin.collections.HashSet
class ExplorerElementAdapter( class ExplorerElementAdapter(
val activity: AppCompatActivity, val activity: AppCompatActivity,
@ -28,8 +33,21 @@ class ExplorerElementAdapter(
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
val dateFormat: DateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.getDefault()) val dateFormat: DateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.getDefault())
var explorerElements = listOf<ExplorerElement>() var explorerElements = listOf<ExplorerElement>()
val selectedItems: MutableList<Int> = ArrayList() @SuppressLint("NotifyDataSetChanged")
set(value) {
field = value
thumbnailsCache?.evictAll()
notifyDataSetChanged()
}
var selectedItems: MutableSet<Int> = HashSet()
var isUsingListLayout = true var isUsingListLayout = true
private var thumbnailsCache: LruCache<String, Bitmap>? = null
init {
if (gocryptfsVolume != null) {
thumbnailsCache = LruCache((Runtime.getRuntime().maxMemory() / 1024 / 8).toInt())
}
}
override fun getItemCount(): Int { override fun getItemCount(): Int {
return explorerElements.size return explorerElements.size
@ -64,14 +82,21 @@ class ExplorerElementAdapter(
for (i in explorerElements.indices) { for (i in explorerElements.indices) {
if (!selectedItems.contains(i) && !explorerElements[i].isParentFolder) { if (!selectedItems.contains(i) && !explorerElements[i].isParentFolder) {
selectedItems.add(i) selectedItems.add(i)
notifyItemChanged(i)
} }
} }
notifyDataSetChanged()
} }
fun unSelectAll() { fun unSelectAll(notifyChange: Boolean) {
if (notifyChange) {
val whatWasSelected = selectedItems
selectedItems = HashSet()
whatWasSelected.forEach {
notifyItemChanged(it)
}
} else {
selectedItems.clear() selectedItems.clear()
notifyDataSetChanged() }
} }
open class ExplorerElementViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { open class ExplorerElementViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
@ -124,20 +149,21 @@ class ExplorerElementAdapter(
var displayThumbnail = true var displayThumbnail = true
var target: DrawableImageViewTarget? = null var target: DrawableImageViewTarget? = null
private fun loadThumbnail(fullPath: String) { private fun loadThumbnail(fullPath: String, adapter: ExplorerElementAdapter) {
(bindingAdapter as ExplorerElementAdapter?)?.let { adapter ->
adapter.gocryptfsVolume?.let { volume -> adapter.gocryptfsVolume?.let { volume ->
displayThumbnail = true displayThumbnail = true
Thread { Thread {
volume.loadWholeFile(fullPath, maxSize = adapter.thumbnailMaxSize).first?.let { volume.loadWholeFile(fullPath, maxSize = adapter.thumbnailMaxSize).first?.let {
if (displayThumbnail) { if (displayThumbnail) {
adapter.activity.runOnUiThread { adapter.activity.runOnUiThread {
if (displayThumbnail) { if (displayThumbnail && !adapter.activity.isFinishing) {
target = Glide.with(adapter.activity).load(it).into(object : DrawableImageViewTarget(icon) { target = Glide.with(adapter.activity).load(it).skipMemoryCache(true).into(object : DrawableImageViewTarget(icon) {
override fun onResourceReady( override fun onResourceReady(
resource: Drawable, resource: Drawable,
transition: Transition<in Drawable>? transition: Transition<in Drawable>?
) { ) {
val bitmap = resource.toBitmap()
adapter.thumbnailsCache!!.put(fullPath, bitmap.copy(bitmap.config, true))
super.onResourceReady(resource, transition) super.onResourceReady(resource, transition)
target = null target = null
} }
@ -149,19 +175,36 @@ class ExplorerElementAdapter(
}.start() }.start()
} }
} }
private fun setThumbnailOrDefaultIcon(fullPath: String, defaultIconId: Int) {
var setDefaultIcon = true
(bindingAdapter as ExplorerElementAdapter?)?.let { adapter ->
adapter.thumbnailsCache?.let {
val thumbnail = it.get(fullPath)
if (thumbnail != null) {
icon.setImageBitmap(thumbnail)
setDefaultIcon = false
} else {
loadThumbnail(fullPath, adapter)
} }
}
}
if (setDefaultIcon) {
icon.setImageResource(defaultIconId)
}
}
override fun bind(explorerElement: ExplorerElement, position: Int, isSelected: Boolean) { override fun bind(explorerElement: ExplorerElement, position: Int, isSelected: Boolean) {
super.bind(explorerElement, position, isSelected) super.bind(explorerElement, position, isSelected)
icon.setImageResource(
when { when {
ConstValues.isImage(explorerElement.name) -> { ConstValues.isImage(explorerElement.name) -> {
loadThumbnail(explorerElement.fullPath) setThumbnailOrDefaultIcon(explorerElement.fullPath, R.drawable.icon_file_image)
R.drawable.icon_file_image
} }
ConstValues.isVideo(explorerElement.name) -> { ConstValues.isVideo(explorerElement.name) -> {
loadThumbnail(explorerElement.fullPath) setThumbnailOrDefaultIcon(explorerElement.fullPath, R.drawable.icon_file_video)
R.drawable.icon_file_video
} }
else -> icon.setImageResource(
when {
ConstValues.isText(explorerElement.name) -> R.drawable.icon_file_text ConstValues.isText(explorerElement.name) -> R.drawable.icon_file_text
ConstValues.isPDF(explorerElement.name) -> R.drawable.icon_file_pdf ConstValues.isPDF(explorerElement.name) -> R.drawable.icon_file_pdf
ConstValues.isAudio(explorerElement.name) -> R.drawable.icon_file_audio ConstValues.isAudio(explorerElement.name) -> R.drawable.icon_file_audio
@ -170,6 +213,7 @@ class ExplorerElementAdapter(
) )
} }
} }
}
class DirectoryViewHolder(itemView: View) : RegularElementViewHolder(itemView) { class DirectoryViewHolder(itemView: View) : RegularElementViewHolder(itemView) {
override fun bind(explorerElement: ExplorerElement, position: Int, isSelected: Boolean) { override fun bind(explorerElement: ExplorerElement, position: Int, isSelected: Boolean) {

View File

@ -1,6 +1,5 @@
package sushi.hardcore.droidfs.adapters package sushi.hardcore.droidfs.adapters
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -12,7 +11,6 @@ import androidx.recyclerview.widget.RecyclerView
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.Volume import sushi.hardcore.droidfs.Volume
import sushi.hardcore.droidfs.VolumeDatabase import sushi.hardcore.droidfs.VolumeDatabase
import java.io.File
class VolumeAdapter( class VolumeAdapter(
private val context: Context, private val context: Context,
@ -24,7 +22,7 @@ class VolumeAdapter(
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context) private val inflater: LayoutInflater = LayoutInflater.from(context)
lateinit var volumes: List<Volume> lateinit var volumes: List<Volume>
val selectedItems: MutableSet<Int> = HashSet() var selectedItems: MutableSet<Int> = HashSet()
init { init {
reloadVolumes() reloadVolumes()
@ -69,24 +67,30 @@ class VolumeAdapter(
notifyItemChanged(position) notifyItemChanged(position)
} }
@SuppressLint("NotifyDataSetChanged")
fun selectAll() { fun selectAll() {
for (i in volumes.indices) { for (i in volumes.indices) {
if (!selectedItems.contains(i)) if (!selectedItems.contains(i)) {
selectedItems.add(i) selectedItems.add(i)
notifyItemChanged(i)
}
} }
notifyDataSetChanged()
} }
@SuppressLint("NotifyDataSetChanged") fun unSelectAll(notifyChange: Boolean) {
fun unSelectAll() { if (notifyChange) {
val whatWasSelected = selectedItems
selectedItems = HashSet()
whatWasSelected.forEach {
notifyItemChanged(it)
}
} else {
selectedItems.clear() selectedItems.clear()
notifyDataSetChanged() }
} }
fun refresh() { fun refresh() {
reloadVolumes() reloadVolumes()
unSelectAll() unSelectAll(true)
} }
inner class VolumeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { inner class VolumeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

View File

@ -240,8 +240,8 @@ open class BaseExplorerActivity : BaseActivity() {
invalidateOptionsMenu() invalidateOptionsMenu()
} }
protected fun unselectAll(){ protected fun unselectAll(notifyChange: Boolean = true) {
explorerAdapter.unSelectAll() explorerAdapter.unSelectAll(notifyChange)
invalidateOptionsMenu() invalidateOptionsMenu()
} }
@ -250,8 +250,8 @@ open class BaseExplorerActivity : BaseActivity() {
synchronized(this) { synchronized(this) {
ExplorerElement.sortBy(sortOrderValues[currentSortOrderIndex], foldersFirst, explorerElements) ExplorerElement.sortBy(sortOrderValues[currentSortOrderIndex], foldersFirst, explorerElements)
} }
unselectAll(false)
explorerAdapter.explorerElements = explorerElements explorerAdapter.explorerElements = explorerElements
unselectAll()
val sharedPrefsEditor = sharedPrefs.edit() val sharedPrefsEditor = sharedPrefs.edit()
sharedPrefsEditor.putString(ConstValues.SORT_ORDER_KEY, sortOrderValues[currentSortOrderIndex]) sharedPrefsEditor.putString(ConstValues.SORT_ORDER_KEY, sortOrderValues[currentSortOrderIndex])
sharedPrefsEditor.apply() sharedPrefsEditor.apply()
@ -494,7 +494,7 @@ open class BaseExplorerActivity : BaseActivity() {
if (!noItemSelected) { if (!noItemSelected) {
if (explorerAdapter.selectedItems.size == 1) { if (explorerAdapter.selectedItems.size == 1) {
menu.findItem(R.id.rename).isVisible = true menu.findItem(R.id.rename).isVisible = true
if (explorerElements[explorerAdapter.selectedItems[0]].isRegularFile) { if (explorerElements[explorerAdapter.selectedItems.first()].isRegularFile) {
menu.findItem(R.id.open_as)?.isVisible = true menu.findItem(R.id.open_as)?.isVisible = true
if (usf_open) { if (usf_open) {
menu.findItem(R.id.external_open)?.isVisible = true menu.findItem(R.id.external_open)?.isVisible = true
@ -523,7 +523,7 @@ open class BaseExplorerActivity : BaseActivity() {
true true
} }
R.id.rename -> { R.id.rename -> {
val oldName = explorerElements[explorerAdapter.selectedItems[0]].name val oldName = explorerElements[explorerAdapter.selectedItems.first()].name
with(EditTextDialog(this, R.string.rename_title) { with(EditTextDialog(this, R.string.rename_title) {
rename(oldName, it) rename(oldName, it)
}) { }) {
@ -536,12 +536,22 @@ open class BaseExplorerActivity : BaseActivity() {
true true
} }
R.id.open_as -> { R.id.open_as -> {
showOpenAsDialog(PathUtils.pathJoin(currentDirectoryPath, explorerElements[explorerAdapter.selectedItems[0]].name)) showOpenAsDialog(
PathUtils.pathJoin(
currentDirectoryPath,
explorerElements[explorerAdapter.selectedItems.first()].name
)
)
true true
} }
R.id.external_open -> { R.id.external_open -> {
if (usf_open){ if (usf_open){
openWithExternalApp(PathUtils.pathJoin(currentDirectoryPath, explorerElements[explorerAdapter.selectedItems[0]].name)) openWithExternalApp(
PathUtils.pathJoin(
currentDirectoryPath,
explorerElements[explorerAdapter.selectedItems.first()].name
)
)
unselectAll() unselectAll()
} }
true true

View File

@ -362,7 +362,10 @@ class ExplorerActivity : BaseExplorerActivity() {
if (size > 1) { if (size > 1) {
dialog.setMessage(getString(R.string.multiple_delete_confirm, explorerAdapter.selectedItems.size.toString())) dialog.setMessage(getString(R.string.multiple_delete_confirm, explorerAdapter.selectedItems.size.toString()))
} else { } else {
dialog.setMessage(getString(R.string.single_delete_confirm, explorerAdapter.explorerElements[explorerAdapter.selectedItems[0]].name)) dialog.setMessage(getString(
R.string.single_delete_confirm,
explorerAdapter.explorerElements[explorerAdapter.selectedItems.first()].name
))
} }
dialog.show() dialog.show()
true true