Encrypted overlay filesystems implementation for Android. Also available on GitHub: https://github.com/hardcore-sushi/DroidFS
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DroidFS/app/src/main/java/sushi/hardcore/droidfs/adapters/ExplorerElementAdapter.kt

242 lines
9.6 KiB

2 years ago
package sushi.hardcore.droidfs.adapters
import android.annotation.SuppressLint
import android.graphics.Bitmap
11 months ago
import android.graphics.drawable.Drawable
import android.util.LruCache
2 years ago
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
2 years ago
import android.widget.TextView
11 months ago
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.drawable.toBitmap
import androidx.recyclerview.widget.RecyclerView
11 months ago
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.DrawableImageViewTarget
import com.bumptech.glide.request.transition.Transition
import kotlinx.coroutines.*
11 months ago
import sushi.hardcore.droidfs.ConstValues
import sushi.hardcore.droidfs.GocryptfsVolume
2 years ago
import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.util.PathUtils
2 years ago
import java.text.DateFormat
import java.util.*
class ExplorerElementAdapter(
11 months ago
val activity: AppCompatActivity,
val gocryptfsVolume: GocryptfsVolume?,
private val listener: Listener,
val thumbnailMaxSize: Long,
) : SelectableAdapter<ExplorerElement>(listener::onSelectionChanged) {
11 months ago
val dateFormat: DateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.getDefault())
var explorerElements = listOf<ExplorerElement>()
@SuppressLint("NotifyDataSetChanged")
set(value) {
field = value
thumbnailsCache?.evictAll()
notifyDataSetChanged()
}
var isUsingListLayout = true
private var thumbnailsCache: LruCache<String, Bitmap>? = null
init {
if (gocryptfsVolume != null) {
thumbnailsCache = LruCache((Runtime.getRuntime().maxMemory() / 1024 / 8).toInt())
}
}
2 years ago
interface Listener {
fun onSelectionChanged(size: Int)
fun onExplorerElementClick(position: Int)
fun onExplorerElementLongClick(position: Int)
}
override fun getItems(): List<ExplorerElement> {
return explorerElements
2 years ago
}
override fun toggleSelection(position: Int): Boolean {
return if (!explorerElements[position].isParentFolder) {
super.toggleSelection(position)
} else {
false
2 years ago
}
}
override fun onItemClick(position: Int): Boolean {
listener.onExplorerElementClick(position)
return super.onItemClick(position)
2 years ago
}
override fun onItemLongClick(position: Int): Boolean {
listener.onExplorerElementLongClick(position)
return super.onItemLongClick(position)
2 years ago
}
override fun isSelectable(position: Int): Boolean {
return !explorerElements[position].isParentFolder
2 years ago
}
11 months ago
open class ExplorerElementViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val textElementName by lazy {
itemView.findViewById<TextView>(R.id.text_element_name)
}
protected val textElementSize: TextView by lazy {
itemView.findViewById(R.id.text_element_size)
}
protected val textElementMtime: TextView by lazy {
itemView.findViewById(R.id.text_element_mtime)
}
protected val icon: ImageView by lazy {
itemView.findViewById(R.id.icon_element)
}
private val selectableContainer: LinearLayout by lazy {
itemView.findViewById(R.id.selectable_container)
}
open fun bind(explorerElement: ExplorerElement, position: Int) {
textElementName.text = explorerElement.name
(bindingAdapter as ExplorerElementAdapter?)?.setSelectable(selectableContainer, itemView, position)
}
}
11 months ago
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)
11 months ago
(bindingAdapter as ExplorerElementAdapter?)?.let {
textElementMtime.text = it.dateFormat.format(explorerElement.mTime)
}
}
}
11 months ago
class FileViewHolder(itemView: View) : RegularElementViewHolder(itemView) {
private var target: DrawableImageViewTarget? = null
private var job: Job? = null
private val scope = CoroutineScope(Dispatchers.IO)
11 months ago
private fun loadThumbnail(fullPath: String, adapter: ExplorerElementAdapter) {
adapter.gocryptfsVolume?.let { volume ->
job = scope.launch {
volume.loadWholeFile(fullPath, maxSize = adapter.thumbnailMaxSize).first?.let {
if (isActive) {
withContext(Dispatchers.Main) {
if (isActive && !adapter.activity.isFinishing) {
target = Glide.with(adapter.activity).load(it).skipMemoryCache(true).into(object : DrawableImageViewTarget(icon) {
override fun onResourceReady(
resource: Drawable,
transition: Transition<in Drawable>?
) {
target = null
val bitmap = resource.toBitmap()
adapter.thumbnailsCache!!.put(fullPath, bitmap.copy(bitmap.config, true))
super.onResourceReady(resource, transition)
}
})
11 months ago
}
}
}
}
}
}
}
fun cancelThumbnailLoading(adapter: ExplorerElementAdapter) {
job?.cancel()
target?.let {
Glide.with(adapter.activity).clear(it)
}
}
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)
}
11 months ago
}
}
if (setDefaultIcon) {
icon.setImageResource(defaultIconId)
}
11 months ago
}
override fun bind(explorerElement: ExplorerElement, position: Int, isSelected: Boolean) {
super.bind(explorerElement, position, isSelected)
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)
11 months ago
}
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
}
)
}
}
}
11 months ago
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)
}
}
11 months ago
class ParentFolderViewHolder(itemView: View): ExplorerElementViewHolder(itemView) {
override fun bind(explorerElement: ExplorerElement, position: Int) {
super.bind(explorerElement, position)
textElementSize.text = ""
textElementMtime.setText(R.string.parent_folder)
icon.setImageResource(R.drawable.icon_folder)
}
}
11 months ago
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
if (holder is FileViewHolder) {
holder.cancelThumbnailLoading(this)
11 months ago
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = activity.layoutInflater.inflate(
if (isUsingListLayout) {
R.layout.adapter_explorer_element_list
} else {
R.layout.adapter_explorer_element_grid
}, parent, false
)
return when (viewType) {
11 months ago
ExplorerElement.REGULAR_FILE_TYPE -> FileViewHolder(view)
ExplorerElement.DIRECTORY_TYPE -> DirectoryViewHolder(view)
ExplorerElement.PARENT_FOLDER_TYPE -> ParentFolderViewHolder(view)
else -> throw IllegalArgumentException()
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val element = explorerElements[position]
if (element.isParentFolder) {
(holder as ParentFolderViewHolder).bind(element, position)
} else {
(holder as RegularElementViewHolder).bind(element, position, selectedItems.contains(position))
}
}
override fun getItemViewType(position: Int): Int {
return explorerElements[position].elementType.toInt()
2 years ago
}
}