|
|
@@ -1,23 +1,35 @@
|
|
|
package io.legado.app.ui.file
|
|
|
|
|
|
+import android.annotation.SuppressLint
|
|
|
import android.content.DialogInterface
|
|
|
import android.content.Intent
|
|
|
import android.net.Uri
|
|
|
import android.os.Bundle
|
|
|
import android.view.MenuItem
|
|
|
import android.view.View
|
|
|
+import android.view.ViewGroup
|
|
|
import androidx.appcompat.widget.Toolbar
|
|
|
+import androidx.core.content.res.ResourcesCompat
|
|
|
+import androidx.core.graphics.drawable.DrawableCompat
|
|
|
import androidx.fragment.app.FragmentManager
|
|
|
+import androidx.fragment.app.viewModels
|
|
|
import androidx.recyclerview.widget.LinearLayoutManager
|
|
|
import androidx.recyclerview.widget.RecyclerView
|
|
|
import io.legado.app.R
|
|
|
import io.legado.app.base.BaseDialogFragment
|
|
|
+import io.legado.app.base.adapter.ItemViewHolder
|
|
|
+import io.legado.app.base.adapter.RecyclerAdapter
|
|
|
+import io.legado.app.databinding.DialogEditTextBinding
|
|
|
import io.legado.app.databinding.DialogFileChooserBinding
|
|
|
+import io.legado.app.databinding.ItemFilePickerBinding
|
|
|
+import io.legado.app.databinding.ItemPathPickerBinding
|
|
|
+import io.legado.app.help.config.AppConfig
|
|
|
+import io.legado.app.lib.dialogs.alert
|
|
|
+import io.legado.app.lib.theme.getPrimaryDisabledTextColor
|
|
|
+import io.legado.app.lib.theme.getPrimaryTextColor
|
|
|
import io.legado.app.lib.theme.primaryColor
|
|
|
-import io.legado.app.ui.file.HandleFileContract.Companion.DIR
|
|
|
import io.legado.app.ui.file.HandleFileContract.Companion.FILE
|
|
|
-import io.legado.app.ui.file.adapter.FileAdapter
|
|
|
-import io.legado.app.ui.file.adapter.PathAdapter
|
|
|
+import io.legado.app.ui.file.utils.FilePickerIcon
|
|
|
import io.legado.app.ui.widget.recycler.VerticalDivider
|
|
|
import io.legado.app.utils.*
|
|
|
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
|
|
@@ -25,9 +37,7 @@ import java.io.File
|
|
|
|
|
|
|
|
|
class FilePickerDialog : BaseDialogFragment(R.layout.dialog_file_chooser),
|
|
|
- Toolbar.OnMenuItemClickListener,
|
|
|
- FileAdapter.CallBack,
|
|
|
- PathAdapter.CallBack {
|
|
|
+ Toolbar.OnMenuItemClickListener {
|
|
|
|
|
|
companion object {
|
|
|
const val tag = "FileChooserDialog"
|
|
|
@@ -37,40 +47,26 @@ class FilePickerDialog : BaseDialogFragment(R.layout.dialog_file_chooser),
|
|
|
mode: Int = FILE,
|
|
|
title: String? = null,
|
|
|
initPath: String? = null,
|
|
|
- isShowHomeDir: Boolean = false,
|
|
|
- isShowUpDir: Boolean = true,
|
|
|
isShowHideDir: Boolean = false,
|
|
|
allowExtensions: Array<String>? = null,
|
|
|
- menus: Array<String>? = null
|
|
|
) {
|
|
|
FilePickerDialog().apply {
|
|
|
val bundle = Bundle()
|
|
|
bundle.putInt("mode", mode)
|
|
|
bundle.putString("title", title)
|
|
|
- bundle.putBoolean("isShowHomeDir", isShowHomeDir)
|
|
|
- bundle.putBoolean("isShowUpDir", isShowUpDir)
|
|
|
bundle.putBoolean("isShowHideDir", isShowHideDir)
|
|
|
bundle.putString("initPath", initPath)
|
|
|
bundle.putStringArray("allowExtensions", allowExtensions)
|
|
|
- bundle.putStringArray("menus", menus)
|
|
|
arguments = bundle
|
|
|
}.show(manager, tag)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private val binding by viewBinding(DialogFileChooserBinding::bind)
|
|
|
- override var allowExtensions: Array<String>? = null
|
|
|
- override val isSelectDir: Boolean
|
|
|
- get() = mode == DIR
|
|
|
- override var isShowHomeDir: Boolean = false
|
|
|
- override var isShowUpDir: Boolean = true
|
|
|
- override var isShowHideDir: Boolean = false
|
|
|
- var title: String? = null
|
|
|
- private var initPath = FileUtils.getSdCardPath()
|
|
|
- private var mode: Int = FILE
|
|
|
- private lateinit var fileAdapter: FileAdapter
|
|
|
- private lateinit var pathAdapter: PathAdapter
|
|
|
- private var menus: Array<String>? = null
|
|
|
+ private val viewModel by viewModels<FilePickerViewModel>()
|
|
|
+ private val dirParent = ".."
|
|
|
+ private val pathAdapter by lazy { PathAdapter() }
|
|
|
+ private val fileAdapter by lazy { FileAdapter() }
|
|
|
|
|
|
override fun onStart() {
|
|
|
super.onStart()
|
|
|
@@ -80,20 +76,8 @@ class FilePickerDialog : BaseDialogFragment(R.layout.dialog_file_chooser),
|
|
|
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
|
|
|
binding.toolBar.setBackgroundColor(primaryColor)
|
|
|
view.setBackgroundResource(R.color.background_card)
|
|
|
- arguments?.let {
|
|
|
- mode = it.getInt("mode", FILE)
|
|
|
- title = it.getString("title")
|
|
|
- isShowHomeDir = it.getBoolean("isShowHomeDir")
|
|
|
- isShowUpDir = it.getBoolean("isShowUpDir")
|
|
|
- isShowHideDir = it.getBoolean("isShowHideDir")
|
|
|
- it.getString("initPath")?.let { path ->
|
|
|
- initPath = path
|
|
|
- }
|
|
|
- allowExtensions = it.getStringArray("allowExtensions")
|
|
|
- menus = it.getStringArray("menus")
|
|
|
- }
|
|
|
- binding.toolBar.title = title ?: let {
|
|
|
- if (isSelectDir) {
|
|
|
+ binding.toolBar.title = arguments?.getString("title") ?: let {
|
|
|
+ if (viewModel.isSelectDir) {
|
|
|
getString(R.string.folder_chooser)
|
|
|
} else {
|
|
|
getString(R.string.file_chooser)
|
|
|
@@ -101,101 +85,203 @@ class FilePickerDialog : BaseDialogFragment(R.layout.dialog_file_chooser),
|
|
|
}
|
|
|
initMenu()
|
|
|
initContentView()
|
|
|
- refreshCurrentDirPath(initPath)
|
|
|
+ viewModel.filesLiveData.observe(viewLifecycleOwner) {
|
|
|
+ fileAdapter.selectFile = null
|
|
|
+ fileAdapter.setItems(it)
|
|
|
+ }
|
|
|
+ viewModel.initData(arguments)
|
|
|
}
|
|
|
|
|
|
private fun initMenu() {
|
|
|
binding.toolBar.inflateMenu(R.menu.file_chooser)
|
|
|
- if (isSelectDir) {
|
|
|
- binding.toolBar.menu.findItem(R.id.menu_ok).isVisible = true
|
|
|
- }
|
|
|
- menus?.let {
|
|
|
- it.forEach { menuTitle ->
|
|
|
- binding.toolBar.menu.add(menuTitle)
|
|
|
- }
|
|
|
- }
|
|
|
binding.toolBar.menu.applyTint(requireContext())
|
|
|
binding.toolBar.setOnMenuItemClickListener(this)
|
|
|
}
|
|
|
|
|
|
private fun initContentView() {
|
|
|
- fileAdapter = FileAdapter(requireContext(), this)
|
|
|
- pathAdapter = PathAdapter(requireContext(), this)
|
|
|
+ binding.rvPath.layoutManager = LinearLayoutManager(activity, RecyclerView.HORIZONTAL, false)
|
|
|
+ binding.rvPath.adapter = pathAdapter
|
|
|
|
|
|
binding.rvFile.addItemDecoration(VerticalDivider(requireContext()))
|
|
|
binding.rvFile.layoutManager = LinearLayoutManager(activity)
|
|
|
binding.rvFile.adapter = fileAdapter
|
|
|
|
|
|
- binding.rvPath.layoutManager = LinearLayoutManager(activity, RecyclerView.HORIZONTAL, false)
|
|
|
- binding.rvPath.adapter = pathAdapter
|
|
|
-
|
|
|
+ binding.tvOk.setOnClickListener {
|
|
|
+ if (viewModel.isSelectDir) {
|
|
|
+ viewModel.lastDir?.let {
|
|
|
+ setResultData(it.path)
|
|
|
+ dismissAllowingStateLoss()
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ val file = fileAdapter.selectFile
|
|
|
+ if (file == null) {
|
|
|
+ toastOnUi("请选择文件")
|
|
|
+ } else {
|
|
|
+ setResultData(file.path)
|
|
|
+ dismissAllowingStateLoss()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
override fun onMenuItemClick(item: MenuItem?): Boolean {
|
|
|
when (item?.itemId) {
|
|
|
- R.id.menu_ok -> fileAdapter.currentPath?.let {
|
|
|
- setData(it)
|
|
|
- dismissAllowingStateLoss()
|
|
|
+ R.id.menu_create -> alert(R.string.create_folder) {
|
|
|
+ val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
|
|
|
+ editView.hint = "文件夹名"
|
|
|
+ }
|
|
|
+ customView { alertBinding.root }
|
|
|
+ okButton {
|
|
|
+ val text = alertBinding.editView.text?.toString()
|
|
|
+ if (text.isNullOrBlank()) {
|
|
|
+ toastOnUi("文件夹名不能为空")
|
|
|
+ } else {
|
|
|
+ viewModel.createFolder(text.trim())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ cancelButton()
|
|
|
}
|
|
|
}
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
- override fun onFileClick(position: Int) {
|
|
|
- val fileItem = fileAdapter.getItem(position)
|
|
|
- if (fileItem?.isDirectory == true) {
|
|
|
- refreshCurrentDirPath(fileItem.path)
|
|
|
- } else {
|
|
|
- fileItem?.path?.let { path ->
|
|
|
- if (mode == DIR) {
|
|
|
- toastOnUi("这是文件夹选择,不能选择文件,点击右上角的确定选择文件夹")
|
|
|
- } else if (allowExtensions.isNullOrEmpty() ||
|
|
|
- allowExtensions?.contains(FileUtils.getExtension(path)) == true
|
|
|
- ) {
|
|
|
- setData(path)
|
|
|
- dismissAllowingStateLoss()
|
|
|
- } else {
|
|
|
- toastOnUi("不能打开此文件")
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ private fun setResultData(path: String) {
|
|
|
+ val data = Intent().setData(Uri.fromFile(File(path)))
|
|
|
+ (parentFragment as? CallBack)?.onResult(data)
|
|
|
+ (activity as? CallBack)?.onResult(data)
|
|
|
}
|
|
|
|
|
|
- override fun onPathClick(position: Int) {
|
|
|
- refreshCurrentDirPath(pathAdapter.getPath(position))
|
|
|
+ override fun onDismiss(dialog: DialogInterface) {
|
|
|
+ super.onDismiss(dialog)
|
|
|
+ activity?.finish()
|
|
|
}
|
|
|
|
|
|
- private fun refreshCurrentDirPath(currentPath: String) {
|
|
|
- if (currentPath == "/") {
|
|
|
- pathAdapter.updatePath("/")
|
|
|
- } else {
|
|
|
- pathAdapter.updatePath(currentPath)
|
|
|
+ @SuppressLint("SetTextI18n")
|
|
|
+ inner class PathAdapter :
|
|
|
+ RecyclerAdapter<File, ItemPathPickerBinding>(requireContext()) {
|
|
|
+
|
|
|
+ private val arrowIcon = ConvertUtils.toDrawable(FilePickerIcon.getArrow())
|
|
|
+
|
|
|
+ init {
|
|
|
+ addHeaderView {
|
|
|
+ ItemPathPickerBinding.inflate(inflater, it, false).apply {
|
|
|
+ textView.text = "root"
|
|
|
+ imageView.setImageDrawable(arrowIcon)
|
|
|
+ root.setOnClickListener {
|
|
|
+ viewModel.subDocs.clear()
|
|
|
+ setItems(viewModel.subDocs)
|
|
|
+ viewModel.upFiles(viewModel.rootDoc)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- fileAdapter.loadData(currentPath)
|
|
|
- var adapterCount = fileAdapter.itemCount
|
|
|
- if (isShowHomeDir) {
|
|
|
- adapterCount--
|
|
|
+
|
|
|
+ override fun getViewBinding(parent: ViewGroup): ItemPathPickerBinding {
|
|
|
+ return ItemPathPickerBinding.inflate(inflater, parent, false).apply {
|
|
|
+ imageView.setImageDrawable(arrowIcon)
|
|
|
+ }
|
|
|
}
|
|
|
- if (isShowUpDir) {
|
|
|
- adapterCount--
|
|
|
+
|
|
|
+ override fun registerListener(holder: ItemViewHolder, binding: ItemPathPickerBinding) {
|
|
|
+ binding.root.setOnClickListener {
|
|
|
+ viewModel.subDocs = viewModel.subDocs.subList(0, holder.layoutPosition)
|
|
|
+ setItems(viewModel.subDocs)
|
|
|
+ viewModel.upFiles(viewModel.subDocs.lastOrNull())
|
|
|
+ }
|
|
|
}
|
|
|
- if (adapterCount < 1) {
|
|
|
- binding.tvEmpty.visible()
|
|
|
- binding.tvEmpty.setText(R.string.empty)
|
|
|
- } else {
|
|
|
- binding.tvEmpty.gone()
|
|
|
+
|
|
|
+ override fun convert(
|
|
|
+ holder: ItemViewHolder,
|
|
|
+ binding: ItemPathPickerBinding,
|
|
|
+ item: File,
|
|
|
+ payloads: MutableList<Any>
|
|
|
+ ) {
|
|
|
+ binding.textView.text = item.name
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- private fun setData(path: String) {
|
|
|
- val data = Intent().setData(Uri.fromFile(File(path)))
|
|
|
- (parentFragment as? CallBack)?.onResult(data)
|
|
|
- (activity as? CallBack)?.onResult(data)
|
|
|
}
|
|
|
|
|
|
- override fun onDismiss(dialog: DialogInterface) {
|
|
|
- super.onDismiss(dialog)
|
|
|
- activity?.finish()
|
|
|
+ inner class FileAdapter : RecyclerAdapter<File, ItemFilePickerBinding>(requireContext()) {
|
|
|
+ private val primaryTextColor = context.getPrimaryTextColor(!AppConfig.isNightTheme)
|
|
|
+ private val disabledTextColor = context.getPrimaryDisabledTextColor(!AppConfig.isNightTheme)
|
|
|
+ private val upIcon = ConvertUtils.toDrawable(FilePickerIcon.getUpDir())!!
|
|
|
+ private val folderIcon = ConvertUtils.toDrawable(FilePickerIcon.getFolder())!!
|
|
|
+ private val fileIcon = ConvertUtils.toDrawable(FilePickerIcon.getFile())!!
|
|
|
+ private val selectDrawable =
|
|
|
+ ResourcesCompat.getDrawable(resources, R.drawable.shape_radius_1dp, null)!!.apply {
|
|
|
+ DrawableCompat.setTint(this, primaryTextColor)
|
|
|
+ }
|
|
|
+ var selectFile: File? = null
|
|
|
+
|
|
|
+ override fun getViewBinding(parent: ViewGroup): ItemFilePickerBinding {
|
|
|
+ return ItemFilePickerBinding.inflate(inflater, parent, false)
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun registerListener(holder: ItemViewHolder, binding: ItemFilePickerBinding) {
|
|
|
+ binding.root.setOnClickListener {
|
|
|
+ val item = getItemByLayoutPosition(holder.layoutPosition)
|
|
|
+ item?.let {
|
|
|
+ if (item == viewModel.lastDir) {
|
|
|
+ viewModel.subDocs.removeLastOrNull()
|
|
|
+ pathAdapter.setItems(viewModel.subDocs)
|
|
|
+ viewModel.upFiles(viewModel.subDocs.lastOrNull() ?: viewModel.rootDoc)
|
|
|
+ } else if (item.isDirectory) {
|
|
|
+ viewModel.subDocs.add(item)
|
|
|
+ pathAdapter.setItems(viewModel.subDocs)
|
|
|
+ viewModel.upFiles(item)
|
|
|
+ } else if (viewModel.isSelectFile) {
|
|
|
+ viewModel.allowExtensions.let {
|
|
|
+ if (it.isNullOrEmpty() || it.contains(FileUtils.getExtension(item.path))) {
|
|
|
+ selectFile = item
|
|
|
+ notifyItemRangeChanged(getHeaderCount(), itemCount, "selectFile")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun convert(
|
|
|
+ holder: ItemViewHolder,
|
|
|
+ binding: ItemFilePickerBinding,
|
|
|
+ item: File,
|
|
|
+ payloads: MutableList<Any>
|
|
|
+ ) {
|
|
|
+ if (payloads.isEmpty()) {
|
|
|
+ if (item == viewModel.lastDir) {
|
|
|
+ binding.imageView.setImageDrawable(upIcon)
|
|
|
+ binding.textView.text = dirParent
|
|
|
+ } else if (item.isDirectory) {
|
|
|
+ binding.imageView.setImageDrawable(folderIcon)
|
|
|
+ binding.textView.text = item.name
|
|
|
+ } else {
|
|
|
+ binding.imageView.setImageDrawable(fileIcon)
|
|
|
+ binding.textView.text = item.name
|
|
|
+ }
|
|
|
+ if (item.isDirectory) {
|
|
|
+ binding.textView.setTextColor(primaryTextColor)
|
|
|
+ } else {
|
|
|
+ if (viewModel.isSelectDir) {
|
|
|
+ binding.textView.setTextColor(disabledTextColor)
|
|
|
+ } else {
|
|
|
+ viewModel.allowExtensions?.let {
|
|
|
+ if (it.isEmpty() || it.contains(FileUtils.getExtension(item.path))) {
|
|
|
+ binding.textView.setTextColor(primaryTextColor)
|
|
|
+ } else {
|
|
|
+ binding.textView.setTextColor(disabledTextColor)
|
|
|
+ }
|
|
|
+ } ?: binding.textView.setTextColor(primaryTextColor)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ binding.root.isSelected = item == selectFile
|
|
|
+ if (item == selectFile) {
|
|
|
+ binding.root.background = selectDrawable
|
|
|
+ } else {
|
|
|
+ binding.root.setBackgroundColor(getCompatColor(R.color.transparent))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
interface CallBack {
|