mirror of
https://github.com/gedoor/legado.git
synced 2024-09-03 09:55:00 +08:00
优化
This commit is contained in:
parent
00881414c2
commit
2a6dc8b9ed
@ -11,10 +11,11 @@
|
|||||||
* 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则或简繁转换出现问题。
|
* 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则或简繁转换出现问题。
|
||||||
* 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源!
|
* 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源!
|
||||||
|
|
||||||
**2023/03/30**
|
**2023/03/31**
|
||||||
|
|
||||||
* 原app最低支持版本提升到android7.0
|
* 原app最低支持版本提升到android7.0
|
||||||
* 创建lollipop版本支持android5.0
|
* 创建lollipop版本支持android5.0
|
||||||
|
* 增加内置文件管理,管理私有文件
|
||||||
|
|
||||||
**2023/03/29**
|
**2023/03/29**
|
||||||
|
|
||||||
|
@ -1,23 +1,35 @@
|
|||||||
package io.legado.app.ui.file
|
package io.legado.app.ui.file
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.widget.Toolbar
|
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.FragmentManager
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.base.BaseDialogFragment
|
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.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.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.HandleFileContract.Companion.FILE
|
||||||
import io.legado.app.ui.file.adapter.FileAdapter
|
import io.legado.app.ui.file.utils.FilePickerIcon
|
||||||
import io.legado.app.ui.file.adapter.PathAdapter
|
|
||||||
import io.legado.app.ui.widget.recycler.VerticalDivider
|
import io.legado.app.ui.widget.recycler.VerticalDivider
|
||||||
import io.legado.app.utils.*
|
import io.legado.app.utils.*
|
||||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||||
@ -25,9 +37,7 @@ import java.io.File
|
|||||||
|
|
||||||
|
|
||||||
class FilePickerDialog : BaseDialogFragment(R.layout.dialog_file_chooser),
|
class FilePickerDialog : BaseDialogFragment(R.layout.dialog_file_chooser),
|
||||||
Toolbar.OnMenuItemClickListener,
|
Toolbar.OnMenuItemClickListener {
|
||||||
FileAdapter.CallBack,
|
|
||||||
PathAdapter.CallBack {
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val tag = "FileChooserDialog"
|
const val tag = "FileChooserDialog"
|
||||||
@ -37,40 +47,26 @@ class FilePickerDialog : BaseDialogFragment(R.layout.dialog_file_chooser),
|
|||||||
mode: Int = FILE,
|
mode: Int = FILE,
|
||||||
title: String? = null,
|
title: String? = null,
|
||||||
initPath: String? = null,
|
initPath: String? = null,
|
||||||
isShowHomeDir: Boolean = false,
|
|
||||||
isShowUpDir: Boolean = true,
|
|
||||||
isShowHideDir: Boolean = false,
|
isShowHideDir: Boolean = false,
|
||||||
allowExtensions: Array<String>? = null,
|
allowExtensions: Array<String>? = null,
|
||||||
menus: Array<String>? = null
|
|
||||||
) {
|
) {
|
||||||
FilePickerDialog().apply {
|
FilePickerDialog().apply {
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
bundle.putInt("mode", mode)
|
bundle.putInt("mode", mode)
|
||||||
bundle.putString("title", title)
|
bundle.putString("title", title)
|
||||||
bundle.putBoolean("isShowHomeDir", isShowHomeDir)
|
|
||||||
bundle.putBoolean("isShowUpDir", isShowUpDir)
|
|
||||||
bundle.putBoolean("isShowHideDir", isShowHideDir)
|
bundle.putBoolean("isShowHideDir", isShowHideDir)
|
||||||
bundle.putString("initPath", initPath)
|
bundle.putString("initPath", initPath)
|
||||||
bundle.putStringArray("allowExtensions", allowExtensions)
|
bundle.putStringArray("allowExtensions", allowExtensions)
|
||||||
bundle.putStringArray("menus", menus)
|
|
||||||
arguments = bundle
|
arguments = bundle
|
||||||
}.show(manager, tag)
|
}.show(manager, tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val binding by viewBinding(DialogFileChooserBinding::bind)
|
private val binding by viewBinding(DialogFileChooserBinding::bind)
|
||||||
override var allowExtensions: Array<String>? = null
|
private val viewModel by viewModels<FilePickerViewModel>()
|
||||||
override val isSelectDir: Boolean
|
private val dirParent = ".."
|
||||||
get() = mode == DIR
|
private val pathAdapter by lazy { PathAdapter() }
|
||||||
override var isShowHomeDir: Boolean = false
|
private val fileAdapter by lazy { FileAdapter() }
|
||||||
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
|
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
@ -80,20 +76,8 @@ class FilePickerDialog : BaseDialogFragment(R.layout.dialog_file_chooser),
|
|||||||
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
binding.toolBar.setBackgroundColor(primaryColor)
|
binding.toolBar.setBackgroundColor(primaryColor)
|
||||||
view.setBackgroundResource(R.color.background_card)
|
view.setBackgroundResource(R.color.background_card)
|
||||||
arguments?.let {
|
binding.toolBar.title = arguments?.getString("title") ?: let {
|
||||||
mode = it.getInt("mode", FILE)
|
if (viewModel.isSelectDir) {
|
||||||
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) {
|
|
||||||
getString(R.string.folder_chooser)
|
getString(R.string.folder_chooser)
|
||||||
} else {
|
} else {
|
||||||
getString(R.string.file_chooser)
|
getString(R.string.file_chooser)
|
||||||
@ -101,93 +85,67 @@ class FilePickerDialog : BaseDialogFragment(R.layout.dialog_file_chooser),
|
|||||||
}
|
}
|
||||||
initMenu()
|
initMenu()
|
||||||
initContentView()
|
initContentView()
|
||||||
refreshCurrentDirPath(initPath)
|
viewModel.filesLiveData.observe(viewLifecycleOwner) {
|
||||||
|
fileAdapter.selectFile = null
|
||||||
|
fileAdapter.setItems(it)
|
||||||
|
}
|
||||||
|
viewModel.initData(arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initMenu() {
|
private fun initMenu() {
|
||||||
binding.toolBar.inflateMenu(R.menu.file_chooser)
|
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.menu.applyTint(requireContext())
|
||||||
binding.toolBar.setOnMenuItemClickListener(this)
|
binding.toolBar.setOnMenuItemClickListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initContentView() {
|
private fun initContentView() {
|
||||||
fileAdapter = FileAdapter(requireContext(), this)
|
binding.rvPath.layoutManager = LinearLayoutManager(activity, RecyclerView.HORIZONTAL, false)
|
||||||
pathAdapter = PathAdapter(requireContext(), this)
|
binding.rvPath.adapter = pathAdapter
|
||||||
|
|
||||||
binding.rvFile.addItemDecoration(VerticalDivider(requireContext()))
|
binding.rvFile.addItemDecoration(VerticalDivider(requireContext()))
|
||||||
binding.rvFile.layoutManager = LinearLayoutManager(activity)
|
binding.rvFile.layoutManager = LinearLayoutManager(activity)
|
||||||
binding.rvFile.adapter = fileAdapter
|
binding.rvFile.adapter = fileAdapter
|
||||||
|
|
||||||
binding.rvPath.layoutManager = LinearLayoutManager(activity, RecyclerView.HORIZONTAL, false)
|
binding.tvOk.setOnClickListener {
|
||||||
binding.rvPath.adapter = pathAdapter
|
if (viewModel.isSelectDir) {
|
||||||
|
viewModel.lastDir?.let {
|
||||||
}
|
setResultData(it.path)
|
||||||
|
|
||||||
override fun onMenuItemClick(item: MenuItem?): Boolean {
|
|
||||||
when (item?.itemId) {
|
|
||||||
R.id.menu_ok -> fileAdapter.currentPath?.let {
|
|
||||||
setData(it)
|
|
||||||
dismissAllowingStateLoss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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()
|
dismissAllowingStateLoss()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val file = fileAdapter.selectFile
|
||||||
|
if (file == null) {
|
||||||
|
toastOnUi("请选择文件")
|
||||||
} else {
|
} else {
|
||||||
toastOnUi("不能打开此文件")
|
setResultData(file.path)
|
||||||
|
dismissAllowingStateLoss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPathClick(position: Int) {
|
override fun onMenuItemClick(item: MenuItem?): Boolean {
|
||||||
refreshCurrentDirPath(pathAdapter.getPath(position))
|
when (item?.itemId) {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshCurrentDirPath(currentPath: String) {
|
private fun setResultData(path: String) {
|
||||||
if (currentPath == "/") {
|
|
||||||
pathAdapter.updatePath("/")
|
|
||||||
} else {
|
|
||||||
pathAdapter.updatePath(currentPath)
|
|
||||||
}
|
|
||||||
fileAdapter.loadData(currentPath)
|
|
||||||
var adapterCount = fileAdapter.itemCount
|
|
||||||
if (isShowHomeDir) {
|
|
||||||
adapterCount--
|
|
||||||
}
|
|
||||||
if (isShowUpDir) {
|
|
||||||
adapterCount--
|
|
||||||
}
|
|
||||||
if (adapterCount < 1) {
|
|
||||||
binding.tvEmpty.visible()
|
|
||||||
binding.tvEmpty.setText(R.string.empty)
|
|
||||||
} else {
|
|
||||||
binding.tvEmpty.gone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setData(path: String) {
|
|
||||||
val data = Intent().setData(Uri.fromFile(File(path)))
|
val data = Intent().setData(Uri.fromFile(File(path)))
|
||||||
(parentFragment as? CallBack)?.onResult(data)
|
(parentFragment as? CallBack)?.onResult(data)
|
||||||
(activity as? CallBack)?.onResult(data)
|
(activity as? CallBack)?.onResult(data)
|
||||||
@ -198,6 +156,134 @@ class FilePickerDialog : BaseDialogFragment(R.layout.dialog_file_chooser),
|
|||||||
activity?.finish()
|
activity?.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getViewBinding(parent: ViewGroup): ItemPathPickerBinding {
|
||||||
|
return ItemPathPickerBinding.inflate(inflater, parent, false).apply {
|
||||||
|
imageView.setImageDrawable(arrowIcon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun convert(
|
||||||
|
holder: ItemViewHolder,
|
||||||
|
binding: ItemPathPickerBinding,
|
||||||
|
item: File,
|
||||||
|
payloads: MutableList<Any>
|
||||||
|
) {
|
||||||
|
binding.textView.text = item.name
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
interface CallBack {
|
||||||
fun onResult(data: Intent)
|
fun onResult(data: Intent)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
package io.legado.app.ui.file
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Environment
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import io.legado.app.base.BaseViewModel
|
||||||
|
import io.legado.app.exception.NoStackTraceException
|
||||||
|
import io.legado.app.utils.toastOnUi
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class FilePickerViewModel(application: Application) : BaseViewModel(application) {
|
||||||
|
|
||||||
|
var rootDoc: File? = Environment.getExternalStorageDirectory()
|
||||||
|
var subDocs = mutableListOf<File>()
|
||||||
|
val filesLiveData = MutableLiveData<List<File>>()
|
||||||
|
var mode: Int = HandleFileContract.FILE
|
||||||
|
var isShowHideDir: Boolean = false
|
||||||
|
var allowExtensions: Array<String>? = null
|
||||||
|
val isSelectDir: Boolean get() = mode == HandleFileContract.DIR
|
||||||
|
val isSelectFile: Boolean get() = mode == HandleFileContract.FILE
|
||||||
|
val lastDir: File? get() = subDocs.lastOrNull() ?: rootDoc
|
||||||
|
|
||||||
|
fun initData(arguments: Bundle?) {
|
||||||
|
arguments?.let {
|
||||||
|
mode = it.getInt("mode", HandleFileContract.FILE)
|
||||||
|
isShowHideDir = it.getBoolean("isShowHideDir")
|
||||||
|
it.getString("initPath")?.let { path ->
|
||||||
|
rootDoc = File(path)
|
||||||
|
}
|
||||||
|
allowExtensions = it.getStringArray("allowExtensions")
|
||||||
|
}
|
||||||
|
upFiles(rootDoc)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun upFiles(parentFile: File?) {
|
||||||
|
execute {
|
||||||
|
parentFile ?: return@execute emptyList()
|
||||||
|
if (parentFile == rootDoc) {
|
||||||
|
parentFile.listFiles()?.sortedWith(
|
||||||
|
compareBy({ it.isFile }, { it.name })
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val list = arrayListOf(parentFile)
|
||||||
|
parentFile.listFiles()?.sortedWith(
|
||||||
|
compareBy({ it.isFile }, { it.name })
|
||||||
|
)?.let {
|
||||||
|
list.addAll(it)
|
||||||
|
}
|
||||||
|
list
|
||||||
|
}
|
||||||
|
}.onStart {
|
||||||
|
filesLiveData.postValue(emptyList())
|
||||||
|
}.onSuccess {
|
||||||
|
filesLiveData.postValue(it ?: emptyList())
|
||||||
|
}.onError {
|
||||||
|
context.toastOnUi(it.localizedMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createFolder(name: String) {
|
||||||
|
execute {
|
||||||
|
val dir = lastDir ?: throw NoStackTraceException("父文件夹不存在")
|
||||||
|
val folder = File(dir, name)
|
||||||
|
if (!folder.canonicalPath.contains(dir.canonicalPath)) {
|
||||||
|
throw NoStackTraceException("非法文件名")
|
||||||
|
}
|
||||||
|
folder.mkdir()
|
||||||
|
}.onSuccess {
|
||||||
|
upFiles(lastDir)
|
||||||
|
}.onError {
|
||||||
|
context.toastOnUi(it.localizedMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,158 +0,0 @@
|
|||||||
package io.legado.app.ui.file.adapter
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import io.legado.app.base.adapter.ItemViewHolder
|
|
||||||
import io.legado.app.base.adapter.RecyclerAdapter
|
|
||||||
import io.legado.app.databinding.ItemFilePickerBinding
|
|
||||||
import io.legado.app.help.config.AppConfig
|
|
||||||
import io.legado.app.lib.theme.getPrimaryDisabledTextColor
|
|
||||||
import io.legado.app.lib.theme.getPrimaryTextColor
|
|
||||||
import io.legado.app.ui.file.entity.FileItem
|
|
||||||
import io.legado.app.ui.file.utils.FilePickerIcon
|
|
||||||
import io.legado.app.utils.ConvertUtils
|
|
||||||
import io.legado.app.utils.FileUtils
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
|
|
||||||
class FileAdapter(context: Context, val callBack: CallBack) :
|
|
||||||
RecyclerAdapter<FileItem, ItemFilePickerBinding>(context) {
|
|
||||||
private var rootPath: String? = null
|
|
||||||
var currentPath: String? = null
|
|
||||||
private set
|
|
||||||
private val homeIcon = ConvertUtils.toDrawable(FilePickerIcon.getHome())!!
|
|
||||||
private val upIcon = ConvertUtils.toDrawable(FilePickerIcon.getUpDir())!!
|
|
||||||
private val folderIcon = ConvertUtils.toDrawable(FilePickerIcon.getFolder())!!
|
|
||||||
private val fileIcon = ConvertUtils.toDrawable(FilePickerIcon.getFile())!!
|
|
||||||
private val primaryTextColor = context.getPrimaryTextColor(!AppConfig.isNightTheme)
|
|
||||||
private val disabledTextColor = context.getPrimaryDisabledTextColor(!AppConfig.isNightTheme)
|
|
||||||
private val dirRoot = "."
|
|
||||||
private val dirParent = ".."
|
|
||||||
|
|
||||||
fun loadData(path: String?) {
|
|
||||||
if (path == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val data = ArrayList<FileItem>()
|
|
||||||
if (rootPath == null) {
|
|
||||||
rootPath = path
|
|
||||||
}
|
|
||||||
currentPath = path
|
|
||||||
if (callBack.isShowHomeDir) {
|
|
||||||
//添加“返回主目录”
|
|
||||||
val fileRoot = FileItem(
|
|
||||||
isDirectory = true,
|
|
||||||
icon = homeIcon,
|
|
||||||
name = dirRoot,
|
|
||||||
path = rootPath ?: path
|
|
||||||
)
|
|
||||||
data.add(fileRoot)
|
|
||||||
}
|
|
||||||
if (callBack.isShowUpDir && path != PathAdapter.sdCardDirectory) {
|
|
||||||
//添加“返回上一级目录”
|
|
||||||
val fileParent = FileItem(
|
|
||||||
isDirectory = true,
|
|
||||||
icon = upIcon,
|
|
||||||
name = dirParent,
|
|
||||||
path = File(path).parent ?: ""
|
|
||||||
)
|
|
||||||
data.add(fileParent)
|
|
||||||
}
|
|
||||||
currentPath?.let { currentPath ->
|
|
||||||
val files: Array<File>? = FileUtils.listDirsAndFiles(currentPath)
|
|
||||||
if (files != null) {
|
|
||||||
for (file in files) {
|
|
||||||
if (!callBack.isShowHideDir && file.name.startsWith(".")) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
val fileItem = if (file.isDirectory) {
|
|
||||||
FileItem(
|
|
||||||
name = file.name,
|
|
||||||
icon = folderIcon,
|
|
||||||
path = file.absolutePath,
|
|
||||||
isDirectory = true
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
FileItem(
|
|
||||||
name = file.name,
|
|
||||||
icon = fileIcon,
|
|
||||||
path = file.absolutePath,
|
|
||||||
size = file.length(),
|
|
||||||
isDirectory = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
data.add(fileItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setItems(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getViewBinding(parent: ViewGroup): ItemFilePickerBinding {
|
|
||||||
return ItemFilePickerBinding.inflate(inflater, parent, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun convert(
|
|
||||||
holder: ItemViewHolder,
|
|
||||||
binding: ItemFilePickerBinding,
|
|
||||||
item: FileItem,
|
|
||||||
payloads: MutableList<Any>
|
|
||||||
) {
|
|
||||||
binding.apply {
|
|
||||||
imageView.setImageDrawable(item.icon)
|
|
||||||
textView.text = item.name
|
|
||||||
if (item.isDirectory) {
|
|
||||||
textView.setTextColor(primaryTextColor)
|
|
||||||
} else {
|
|
||||||
if (callBack.isSelectDir) {
|
|
||||||
textView.setTextColor(disabledTextColor)
|
|
||||||
} else {
|
|
||||||
callBack.allowExtensions?.let {
|
|
||||||
if (it.isEmpty() || it.contains(FileUtils.getExtension(item.path))) {
|
|
||||||
textView.setTextColor(primaryTextColor)
|
|
||||||
} else {
|
|
||||||
textView.setTextColor(disabledTextColor)
|
|
||||||
}
|
|
||||||
} ?: textView.setTextColor(primaryTextColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun registerListener(holder: ItemViewHolder, binding: ItemFilePickerBinding) {
|
|
||||||
holder.itemView.setOnClickListener {
|
|
||||||
callBack.onFileClick(holder.layoutPosition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CallBack {
|
|
||||||
fun onFileClick(position: Int)
|
|
||||||
|
|
||||||
//允许的扩展名
|
|
||||||
var allowExtensions: Array<String>?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否选取目录
|
|
||||||
*/
|
|
||||||
val isSelectDir: Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否显示返回主目录
|
|
||||||
*/
|
|
||||||
var isShowHomeDir: Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否显示返回上一级
|
|
||||||
*/
|
|
||||||
var isShowUpDir: Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否显示隐藏的目录(以“.”开头)
|
|
||||||
*/
|
|
||||||
var isShowHideDir: Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
|||||||
package io.legado.app.ui.file.adapter
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Environment
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import io.legado.app.base.adapter.ItemViewHolder
|
|
||||||
import io.legado.app.base.adapter.RecyclerAdapter
|
|
||||||
import io.legado.app.databinding.ItemPathPickerBinding
|
|
||||||
import io.legado.app.ui.file.utils.FilePickerIcon
|
|
||||||
import io.legado.app.utils.ConvertUtils
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
|
|
||||||
class PathAdapter(context: Context, val callBack: CallBack) :
|
|
||||||
RecyclerAdapter<String, ItemPathPickerBinding>(context) {
|
|
||||||
private val paths = LinkedList<String>()
|
|
||||||
private val arrowIcon = ConvertUtils.toDrawable(FilePickerIcon.getArrow())
|
|
||||||
|
|
||||||
fun getPath(position: Int): String {
|
|
||||||
val tmp = StringBuilder("$sdCardDirectory/")
|
|
||||||
//忽略根目录
|
|
||||||
if (position == 0) {
|
|
||||||
return tmp.toString()
|
|
||||||
}
|
|
||||||
for (i in 1..position) {
|
|
||||||
tmp.append(paths[i]).append("/")
|
|
||||||
}
|
|
||||||
return tmp.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updatePath(path: String) {
|
|
||||||
var path1 = path
|
|
||||||
path1 = path1.replace(sdCardDirectory, "")
|
|
||||||
paths.clear()
|
|
||||||
if (path1 != "/" && path1 != "") {
|
|
||||||
val subDirs = path1.substring(path1.indexOf("/") + 1)
|
|
||||||
.split("/")
|
|
||||||
.dropLastWhile { it.isEmpty() }
|
|
||||||
.toTypedArray()
|
|
||||||
Collections.addAll(paths, *subDirs)
|
|
||||||
}
|
|
||||||
paths.addFirst(ROOT_HINT)
|
|
||||||
setItems(paths)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getViewBinding(parent: ViewGroup): ItemPathPickerBinding {
|
|
||||||
return ItemPathPickerBinding.inflate(inflater, parent, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun convert(
|
|
||||||
holder: ItemViewHolder,
|
|
||||||
binding: ItemPathPickerBinding,
|
|
||||||
item: String,
|
|
||||||
payloads: MutableList<Any>
|
|
||||||
) {
|
|
||||||
binding.apply {
|
|
||||||
textView.text = item
|
|
||||||
imageView.setImageDrawable(arrowIcon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun registerListener(holder: ItemViewHolder, binding: ItemPathPickerBinding) {
|
|
||||||
holder.itemView.setOnClickListener {
|
|
||||||
callBack.onPathClick(holder.layoutPosition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CallBack {
|
|
||||||
fun onPathClick(position: Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val ROOT_HINT = "SD"
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
val sdCardDirectory: String = Environment.getExternalStorageDirectory().absolutePath
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package io.legado.app.ui.file.entity
|
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文件项信息
|
|
||||||
*/
|
|
||||||
data class FileItem(
|
|
||||||
var icon: Drawable,
|
|
||||||
var name: String,
|
|
||||||
var path: String = "/",
|
|
||||||
var size: Long = 0,
|
|
||||||
var isDirectory: Boolean = false,
|
|
||||||
)
|
|
10
app/src/main/res/drawable/ic_create_folder_outline.xml
Normal file
10
app/src/main/res/drawable/ic_create_folder_outline.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector android:height="24dp"
|
||||||
|
android:tint="#000000"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:width="24dp"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M20,6h-8l-2,-2L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM20,18L4,18L4,6h5.17l2,2L20,8v10zM12,14h2v2h2v-2h2v-2h-2v-2h-2v2h-2z" />
|
||||||
|
</vector>
|
@ -25,7 +25,8 @@
|
|||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
android:background="@color/background_card">
|
android:background="@color/background_card">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
@ -42,4 +43,13 @@
|
|||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
<io.legado.app.ui.widget.text.AccentBgTextView
|
||||||
|
android:id="@+id/tv_ok"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:layout_margin="3dp"
|
||||||
|
android:text="@string/ok"
|
||||||
|
android:gravity="center" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -1,11 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:padding="5dp">
|
android:orientation="horizontal"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/image_view"
|
android:id="@+id/image_view"
|
||||||
|
@ -1,22 +1,30 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:padding="5dp">
|
|
||||||
|
|
||||||
<ImageView
|
<LinearLayout
|
||||||
android:id="@+id/image_view"
|
android:orientation="horizontal"
|
||||||
android:layout_width="24dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="24dp"
|
android:layout_height="wrap_content"
|
||||||
tools:ignore="ContentDescription" />
|
android:gravity="center_vertical"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:padding="5dp"
|
||||||
|
tools:ignore="UseCompoundDrawables,UselessParent">
|
||||||
|
|
||||||
<TextView
|
<ImageView
|
||||||
android:id="@+id/text_view"
|
android:id="@+id/image_view"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="24dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="24dp"
|
||||||
android:gravity="center_vertical" />
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
</LinearLayout>
|
<TextView
|
||||||
|
android:id="@+id/text_view"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_vertical" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
@ -3,10 +3,9 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/menu_ok"
|
android:id="@+id/menu_create"
|
||||||
android:title="@string/ok"
|
android:title="@string/create_folder"
|
||||||
android:visible="false"
|
android:icon="@drawable/ic_create_folder_outline"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="always" />
|
||||||
|
|
||||||
|
|
||||||
</menu>
|
</menu>
|
@ -1089,4 +1089,5 @@
|
|||||||
<string name="refuse">拒绝</string>
|
<string name="refuse">拒绝</string>
|
||||||
<string name="file_manage">文件管理</string>
|
<string name="file_manage">文件管理</string>
|
||||||
<string name="file_manage_summary">管理私有文件夹的文件</string>
|
<string name="file_manage_summary">管理私有文件夹的文件</string>
|
||||||
|
<string name="create_folder">创建文件夹</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1092,4 +1092,5 @@
|
|||||||
<string name="refuse">拒绝</string>
|
<string name="refuse">拒绝</string>
|
||||||
<string name="file_manage">文件管理</string>
|
<string name="file_manage">文件管理</string>
|
||||||
<string name="file_manage_summary">管理私有文件夹的文件</string>
|
<string name="file_manage_summary">管理私有文件夹的文件</string>
|
||||||
|
<string name="create_folder">创建文件夹</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1092,4 +1092,5 @@
|
|||||||
<string name="refuse">拒绝</string>
|
<string name="refuse">拒绝</string>
|
||||||
<string name="file_manage">文件管理</string>
|
<string name="file_manage">文件管理</string>
|
||||||
<string name="file_manage_summary">管理私有文件夹的文件</string>
|
<string name="file_manage_summary">管理私有文件夹的文件</string>
|
||||||
|
<string name="create_folder">创建文件夹</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1089,4 +1089,5 @@
|
|||||||
<string name="refuse">拒绝</string>
|
<string name="refuse">拒绝</string>
|
||||||
<string name="file_manage">文件管理</string>
|
<string name="file_manage">文件管理</string>
|
||||||
<string name="file_manage_summary">管理私有文件夹的文件</string>
|
<string name="file_manage_summary">管理私有文件夹的文件</string>
|
||||||
|
<string name="create_folder">创建文件夹</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1091,4 +1091,5 @@
|
|||||||
<string name="refuse">拒绝</string>
|
<string name="refuse">拒绝</string>
|
||||||
<string name="file_manage">文件管理</string>
|
<string name="file_manage">文件管理</string>
|
||||||
<string name="file_manage_summary">管理私有文件夹的文件</string>
|
<string name="file_manage_summary">管理私有文件夹的文件</string>
|
||||||
|
<string name="create_folder">创建文件夹</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1091,4 +1091,5 @@
|
|||||||
<string name="refuse">拒绝</string>
|
<string name="refuse">拒绝</string>
|
||||||
<string name="file_manage">文件管理</string>
|
<string name="file_manage">文件管理</string>
|
||||||
<string name="file_manage_summary">管理私有文件夹的文件</string>
|
<string name="file_manage_summary">管理私有文件夹的文件</string>
|
||||||
|
<string name="create_folder">创建文件夹</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1092,4 +1092,5 @@
|
|||||||
<string name="refuse">拒绝</string>
|
<string name="refuse">拒绝</string>
|
||||||
<string name="file_manage">文件管理</string>
|
<string name="file_manage">文件管理</string>
|
||||||
<string name="file_manage_summary">管理私有文件夹的文件</string>
|
<string name="file_manage_summary">管理私有文件夹的文件</string>
|
||||||
|
<string name="create_folder">创建文件夹</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user