diff --git a/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt index 63e67b5..b53a75f 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt @@ -145,8 +145,8 @@ class MainActivity : BaseActivity() { binding.textNoVolumes.visibility = View.GONE } - private fun unselectAll() { - volumeAdapter.unSelectAll() + private fun unselectAll(notifyChange: Boolean = true) { + volumeAdapter.unSelectAll(notifyChange) invalidateOptionsMenu() } @@ -233,7 +233,7 @@ class MainActivity : BaseActivity() { if (volumeDatabase.removeHash(volumeAdapter.volumes[i])) volumeAdapter.onVolumeChanged(i) } - unselectAll() + unselectAll(false) true } R.id.change_password -> { diff --git a/app/src/main/java/sushi/hardcore/droidfs/adapters/ExplorerElementAdapter.kt b/app/src/main/java/sushi/hardcore/droidfs/adapters/ExplorerElementAdapter.kt index b7c5921..22d9f31 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/adapters/ExplorerElementAdapter.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/adapters/ExplorerElementAdapter.kt @@ -1,12 +1,16 @@ package sushi.hardcore.droidfs.adapters +import android.annotation.SuppressLint +import android.graphics.Bitmap import android.graphics.drawable.Drawable +import android.util.LruCache 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.core.graphics.drawable.toBitmap import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.bumptech.glide.request.target.DrawableImageViewTarget @@ -18,6 +22,7 @@ import sushi.hardcore.droidfs.explorers.ExplorerElement import sushi.hardcore.droidfs.util.PathUtils import java.text.DateFormat import java.util.* +import kotlin.collections.HashSet class ExplorerElementAdapter( val activity: AppCompatActivity, @@ -28,8 +33,21 @@ class ExplorerElementAdapter( ) : RecyclerView.Adapter() { val dateFormat: DateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.getDefault()) var explorerElements = listOf() - val selectedItems: MutableList = ArrayList() + @SuppressLint("NotifyDataSetChanged") + set(value) { + field = value + thumbnailsCache?.evictAll() + notifyDataSetChanged() + } + var selectedItems: MutableSet = HashSet() var isUsingListLayout = true + private var thumbnailsCache: LruCache? = null + + init { + if (gocryptfsVolume != null) { + thumbnailsCache = LruCache((Runtime.getRuntime().maxMemory() / 1024 / 8).toInt()) + } + } override fun getItemCount(): Int { return explorerElements.size @@ -64,14 +82,21 @@ class ExplorerElementAdapter( for (i in explorerElements.indices) { if (!selectedItems.contains(i) && !explorerElements[i].isParentFolder) { selectedItems.add(i) + notifyItemChanged(i) } } - notifyDataSetChanged() } - fun unSelectAll() { - selectedItems.clear() - notifyDataSetChanged() + fun unSelectAll(notifyChange: Boolean) { + if (notifyChange) { + val whatWasSelected = selectedItems + selectedItems = HashSet() + whatWasSelected.forEach { + notifyItemChanged(it) + } + } else { + selectedItems.clear() + } } open class ExplorerElementViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { @@ -124,50 +149,69 @@ class ExplorerElementAdapter( 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, maxSize = adapter.thumbnailMaxSize).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? - ) { - super.onResourceReady(resource, transition) - target = null - } - }) - } + private fun loadThumbnail(fullPath: String, adapter: ExplorerElementAdapter) { + adapter.gocryptfsVolume?.let { volume -> + displayThumbnail = true + Thread { + volume.loadWholeFile(fullPath, maxSize = adapter.thumbnailMaxSize).first?.let { + if (displayThumbnail) { + adapter.activity.runOnUiThread { + if (displayThumbnail && !adapter.activity.isFinishing) { + target = Glide.with(adapter.activity).load(it).skipMemoryCache(true).into(object : DrawableImageViewTarget(icon) { + override fun onResourceReady( + resource: Drawable, + transition: Transition? + ) { + val bitmap = resource.toBitmap() + adapter.thumbnailsCache!!.put(fullPath, bitmap.copy(bitmap.config, true)) + super.onResourceReady(resource, transition) + target = null + } + }) } } } - }.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) { super.bind(explorerElement, position, isSelected) - 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.isPDF(explorerElement.name) -> R.drawable.icon_file_pdf - ConstValues.isAudio(explorerElement.name) -> R.drawable.icon_file_audio - else -> R.drawable.icon_file_unknown + when { + ConstValues.isImage(explorerElement.name) -> { + setThumbnailOrDefaultIcon(explorerElement.fullPath, R.drawable.icon_file_image) } - ) + ConstValues.isVideo(explorerElement.name) -> { + setThumbnailOrDefaultIcon(explorerElement.fullPath, R.drawable.icon_file_video) + } + else -> icon.setImageResource( + when { + ConstValues.isText(explorerElement.name) -> R.drawable.icon_file_text + ConstValues.isPDF(explorerElement.name) -> R.drawable.icon_file_pdf + ConstValues.isAudio(explorerElement.name) -> R.drawable.icon_file_audio + else -> R.drawable.icon_file_unknown + } + ) + } } } diff --git a/app/src/main/java/sushi/hardcore/droidfs/adapters/VolumeAdapter.kt b/app/src/main/java/sushi/hardcore/droidfs/adapters/VolumeAdapter.kt index b27acbd..11cae97 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/adapters/VolumeAdapter.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/adapters/VolumeAdapter.kt @@ -1,6 +1,5 @@ package sushi.hardcore.droidfs.adapters -import android.annotation.SuppressLint import android.content.Context import android.view.LayoutInflater import android.view.View @@ -12,7 +11,6 @@ import androidx.recyclerview.widget.RecyclerView import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.Volume import sushi.hardcore.droidfs.VolumeDatabase -import java.io.File class VolumeAdapter( private val context: Context, @@ -24,7 +22,7 @@ class VolumeAdapter( ) : RecyclerView.Adapter() { private val inflater: LayoutInflater = LayoutInflater.from(context) lateinit var volumes: List - val selectedItems: MutableSet = HashSet() + var selectedItems: MutableSet = HashSet() init { reloadVolumes() @@ -69,24 +67,30 @@ class VolumeAdapter( notifyItemChanged(position) } - @SuppressLint("NotifyDataSetChanged") fun selectAll() { for (i in volumes.indices) { - if (!selectedItems.contains(i)) + if (!selectedItems.contains(i)) { selectedItems.add(i) + notifyItemChanged(i) + } } - notifyDataSetChanged() } - @SuppressLint("NotifyDataSetChanged") - fun unSelectAll() { - selectedItems.clear() - notifyDataSetChanged() + fun unSelectAll(notifyChange: Boolean) { + if (notifyChange) { + val whatWasSelected = selectedItems + selectedItems = HashSet() + whatWasSelected.forEach { + notifyItemChanged(it) + } + } else { + selectedItems.clear() + } } fun refresh() { reloadVolumes() - unSelectAll() + unSelectAll(true) } inner class VolumeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt index 175a003..ec355be 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt @@ -240,8 +240,8 @@ open class BaseExplorerActivity : BaseActivity() { invalidateOptionsMenu() } - protected fun unselectAll(){ - explorerAdapter.unSelectAll() + protected fun unselectAll(notifyChange: Boolean = true) { + explorerAdapter.unSelectAll(notifyChange) invalidateOptionsMenu() } @@ -250,8 +250,8 @@ open class BaseExplorerActivity : BaseActivity() { synchronized(this) { ExplorerElement.sortBy(sortOrderValues[currentSortOrderIndex], foldersFirst, explorerElements) } + unselectAll(false) explorerAdapter.explorerElements = explorerElements - unselectAll() val sharedPrefsEditor = sharedPrefs.edit() sharedPrefsEditor.putString(ConstValues.SORT_ORDER_KEY, sortOrderValues[currentSortOrderIndex]) sharedPrefsEditor.apply() @@ -494,7 +494,7 @@ open class BaseExplorerActivity : BaseActivity() { if (!noItemSelected) { if (explorerAdapter.selectedItems.size == 1) { 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 if (usf_open) { menu.findItem(R.id.external_open)?.isVisible = true @@ -523,7 +523,7 @@ open class BaseExplorerActivity : BaseActivity() { true } R.id.rename -> { - val oldName = explorerElements[explorerAdapter.selectedItems[0]].name + val oldName = explorerElements[explorerAdapter.selectedItems.first()].name with(EditTextDialog(this, R.string.rename_title) { rename(oldName, it) }) { @@ -536,12 +536,22 @@ open class BaseExplorerActivity : BaseActivity() { true } R.id.open_as -> { - showOpenAsDialog(PathUtils.pathJoin(currentDirectoryPath, explorerElements[explorerAdapter.selectedItems[0]].name)) + showOpenAsDialog( + PathUtils.pathJoin( + currentDirectoryPath, + explorerElements[explorerAdapter.selectedItems.first()].name + ) + ) true } R.id.external_open -> { if (usf_open){ - openWithExternalApp(PathUtils.pathJoin(currentDirectoryPath, explorerElements[explorerAdapter.selectedItems[0]].name)) + openWithExternalApp( + PathUtils.pathJoin( + currentDirectoryPath, + explorerElements[explorerAdapter.selectedItems.first()].name + ) + ) unselectAll() } true diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt index 5645ee9..ed8491e 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt @@ -362,7 +362,10 @@ class ExplorerActivity : BaseExplorerActivity() { if (size > 1) { dialog.setMessage(getString(R.string.multiple_delete_confirm, explorerAdapter.selectedItems.size.toString())) } 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() true