Merge pull request #3 from gedoor/master

merge
pull/42/head
Celeter 6 years ago committed by GitHub
commit b78fd824e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  2. 4
      app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt
  3. 2
      app/src/main/java/io/legado/app/data/entities/RssArticle.kt
  4. 5
      app/src/main/java/io/legado/app/help/BookHelp.kt
  5. 1
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt
  6. 7
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  7. 4
      app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt
  8. 101
      app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt
  9. 34
      app/src/main/java/io/legado/app/ui/config/ConfigFragment.kt
  10. 174
      app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt
  11. 283
      app/src/main/java/io/legado/app/ui/filechooser/FilePickerIcon.java
  12. 147
      app/src/main/java/io/legado/app/ui/filechooser/adapter/FileAdapter.kt
  13. 63
      app/src/main/java/io/legado/app/ui/filechooser/adapter/PathAdapter.kt
  14. 17
      app/src/main/java/io/legado/app/ui/filechooser/entity/FileItem.kt
  15. 54
      app/src/main/java/io/legado/app/ui/filechooser/entity/JavaBean.kt
  16. 122
      app/src/main/java/io/legado/app/ui/filechooser/utils/ConvertUtils.kt
  17. 670
      app/src/main/java/io/legado/app/ui/filechooser/utils/FileUtils.kt
  18. 22
      app/src/main/java/io/legado/app/ui/main/bookshelf/BooksFragment.kt
  19. 2
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt
  20. 6
      app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt
  21. 39
      app/src/main/res/layout/dialog_file_chooser.xml
  22. 22
      app/src/main/res/layout/item_file_filepicker.xml
  23. 22
      app/src/main/res/layout/item_path_filepicker.xml
  24. 2
      app/src/main/res/layout/view_load_more.xml
  25. 12
      app/src/main/res/menu/file_chooser.xml
  26. 3
      app/src/main/res/values/strings.xml

@ -0,0 +1,8 @@
package io.legado.app.constant
object PreferKey {
const val downloadPath = "downloadPath"
}

@ -7,8 +7,8 @@ import io.legado.app.data.entities.RssArticle
@Dao
interface RssArticleDao {
@Query("select * from rssArticles where origin = :origin and title = :title")
fun get(origin: String, title: String): RssArticle?
@Query("select * from rssArticles where origin = :origin and link = :link")
fun get(origin: String, link: String): RssArticle?
@Query("select * from rssArticles where origin = :origin order by `order` desc")
fun liveByOrigin(origin: String): LiveData<List<RssArticle>>

@ -6,7 +6,7 @@ import androidx.room.Ignore
@Entity(
tableName = "rssArticles",
primaryKeys = ["origin", "title"]
primaryKeys = ["origin", "link"]
)
data class RssArticle(
var origin: String = "",

@ -1,6 +1,7 @@
package io.legado.app.help
import io.legado.app.App
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.ReplaceRule
@ -13,13 +14,13 @@ import kotlin.math.min
object BookHelp {
private var downloadPath: String =
App.INSTANCE.getPrefString("downloadPath")
App.INSTANCE.getPrefString(PreferKey.downloadPath)
?: App.INSTANCE.getExternalFilesDir(null)?.absolutePath
?: App.INSTANCE.cacheDir.absolutePath
fun upDownloadPath() {
downloadPath =
App.INSTANCE.getPrefString("downloadPath")
App.INSTANCE.getPrefString(PreferKey.downloadPath)
?: App.INSTANCE.getExternalFilesDir(null)?.absolutePath
?: App.INSTANCE.cacheDir.absolutePath
}

@ -375,6 +375,7 @@ class AnalyzeByJSoup {
val html = elements.html()
textS.add(html)
}
"all" -> textS.add(elements.html())
else -> for (element in elements) {
val url = element.attr(lastRule)
if (!isEmpty(url) && !textS.contains(url)) {

@ -433,9 +433,10 @@ class AnalyzeRule(private var book: BaseBook? = null) {
else -> rule = ruleStr
}
}
//分离put
rule = splitPutRule(rule, putMap)
//分离正则表达式
val ruleStrS =
rule.trim { it <= ' ' }.split("##")
val ruleStrS = rule.trim { it <= ' ' }.split("##")
rule = ruleStrS[0]
if (ruleStrS.size > 1) {
replaceRegex = ruleStrS[1]
@ -446,8 +447,6 @@ class AnalyzeRule(private var book: BaseBook? = null) {
if (ruleStrS.size > 3) {
replaceFirst = true
}
//分离put
rule = splitPutRule(rule, putMap)
//@get,{{ }},$1, 拆分
var start = 0
var tmp: String

@ -4,13 +4,15 @@ import android.os.Bundle
import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.utils.getViewModel
import kotlinx.android.synthetic.main.view_title_bar.*
class AudioPlayActivity : VMBaseActivity<AudioPlayViewModel>(R.layout.activity_audio_play) {
override val viewModel: AudioPlayViewModel
get() = getViewModel(AudioPlayViewModel::class.java)
override fun onActivityCreated(savedInstanceState: Bundle?) {
setSupportActionBar(toolbar)
viewModel.initData(intent)
}

@ -1,9 +1,110 @@
package io.legado.app.ui.audio
import android.app.Application
import android.content.Intent
import androidx.lifecycle.MutableLiveData
import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.BookType
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.model.WebBook
import io.legado.app.ui.book.read.ReadBookViewModel
import kotlinx.coroutines.Dispatchers
class AudioPlayViewModel(application: Application) : BaseViewModel(application) {
var inBookshelf = false
var bookData = MutableLiveData<Book>()
val chapterListFinish = MutableLiveData<Boolean>()
var chapterSize = 0
var callBack: ReadBookViewModel.CallBack? = null
var durChapterIndex = 0
var durPageIndex = 0
var isLocalBook = true
var webBook: WebBook? = null
fun initData(intent: Intent) {
execute {
inBookshelf = intent.getBooleanExtra("inBookshelf", true)
val bookUrl = intent.getStringExtra("bookUrl")
val book = if (!bookUrl.isNullOrEmpty()) {
App.db.bookDao().getBook(bookUrl)
} else {
App.db.bookDao().lastReadBook
}
book?.let {
durChapterIndex = book.durChapterIndex
durPageIndex = book.durChapterPos
isLocalBook = book.origin == BookType.local
bookData.postValue(book)
App.db.bookSourceDao().getBookSource(book.origin)?.let {
webBook = WebBook(it)
}
val count = App.db.bookChapterDao().getChapterCount(book.bookUrl)
if (count == 0) {
if (book.tocUrl.isEmpty()) {
loadBookInfo(book)
} else {
loadChapterList(book)
}
} else {
if (durChapterIndex > count - 1) {
durChapterIndex = count - 1
}
chapterSize = count
chapterListFinish.postValue(true)
}
}
saveRead(book)
}
}
private fun loadBookInfo(
book: Book,
changeDruChapterIndex: ((chapters: List<BookChapter>) -> Unit)? = null
) {
execute {
webBook?.getBookInfo(book, this)
?.onSuccess {
loadChapterList(book, changeDruChapterIndex)
}
}
}
private fun loadChapterList(
book: Book,
changeDruChapterIndex: ((chapters: List<BookChapter>) -> Unit)? = null
) {
execute {
webBook?.getChapterList(book, this)
?.onSuccess(Dispatchers.IO) { cList ->
if (!cList.isNullOrEmpty()) {
if (changeDruChapterIndex == null) {
App.db.bookChapterDao().insert(*cList.toTypedArray())
chapterSize = cList.size
chapterListFinish.postValue(true)
} else {
changeDruChapterIndex(cList)
}
} else {
toast(R.string.error_load_toc)
}
}?.onError {
toast(R.string.error_load_toc)
}
}
}
fun saveRead(book: Book? = bookData.value) {
execute {
book?.let { book ->
book.lastCheckCount = 0
book.durChapterTime = System.currentTimeMillis()
book.durChapterIndex = durChapterIndex
book.durChapterPos = durPageIndex
App.db.bookDao().update(book)
}
}
}
}

@ -10,17 +10,23 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import io.legado.app.App
import io.legado.app.R
import io.legado.app.constant.PreferKey
import io.legado.app.help.BookHelp
import io.legado.app.lib.theme.ATH
import io.legado.app.receiver.SharedReceiverActivity
import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.utils.LogUtils
import io.legado.app.utils.getPrefString
import io.legado.app.utils.putPrefBoolean
import io.legado.app.utils.putPrefString
class ConfigFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener,
class ConfigFragment : PreferenceFragmentCompat(),
FileChooserDialog.CallBack,
Preference.OnPreferenceChangeListener,
SharedPreferences.OnSharedPreferenceChangeListener {
private val downloadPath = 25324
private val packageManager = App.INSTANCE.packageManager
private val componentName = ComponentName(
App.INSTANCE,
@ -30,7 +36,7 @@ class ConfigFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChange
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
putPrefBoolean("process_text", isProcessTextEnabled())
addPreferencesFromResource(R.xml.pref_config)
bindPreferenceSummaryToValue(findPreference("downloadPath"))
bindPreferenceSummaryToValue(findPreference(PreferKey.downloadPath))
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -48,9 +54,24 @@ class ConfigFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChange
super.onPause()
}
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
when (preference?.key) {
PreferKey.downloadPath -> FileChooserDialog.show(
childFragmentManager,
downloadPath,
mode = FileChooserDialog.DIRECTORY,
initPath = getPreferenceString(PreferKey.downloadPath)
)
}
return super.onPreferenceTreeClick(preference)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
when (key) {
"downloadPath" -> BookHelp.upDownloadPath()
PreferKey.downloadPath -> {
BookHelp.upDownloadPath()
findPreference<Preference>(key)?.summary = getPreferenceString(key)
}
"recordLog" -> LogUtils.upLevel()
"process_text" -> sharedPreferences?.let {
setProcessTextEnable(it.getBoolean("process_text", true))
@ -84,7 +105,7 @@ class ConfigFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChange
private fun getPreferenceString(key: String): String {
return when (key) {
"downloadPath" -> getPrefString("downloadPath")
PreferKey.downloadPath -> getPrefString(PreferKey.downloadPath)
?: App.INSTANCE.getExternalFilesDir(null)?.absolutePath
?: App.INSTANCE.cacheDir.absolutePath
else -> getPrefString(key, "")
@ -108,4 +129,9 @@ class ConfigFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChange
)
}
}
override fun onFilePicked(requestCode: Int, currentPath: String) {
super.onFilePicked(requestCode, currentPath)
putPrefString(PreferKey.downloadPath, currentPath)
}
}

@ -1,32 +1,196 @@
package io.legado.app.ui.filechooser
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R
import io.legado.app.lib.theme.ATH
import io.legado.app.ui.filechooser.adapter.FileAdapter
import io.legado.app.ui.filechooser.adapter.PathAdapter
import io.legado.app.utils.FileUtils
import io.legado.app.utils.applyTint
import io.legado.app.utils.gone
import io.legado.app.utils.visible
import kotlinx.android.synthetic.main.dialog_file_chooser.*
class FileChooserDialog : DialogFragment() {
class FileChooserDialog : DialogFragment(),
Toolbar.OnMenuItemClickListener,
FileAdapter.CallBack,
PathAdapter.CallBack {
companion object {
const val tag = "FileChooserDialog"
const val DIRECTORY = 0
const val FILE = 1
fun show(manager: FragmentManager) {
val fragment =
(manager.findFragmentByTag(tag) as? FileChooserDialog) ?: FileChooserDialog()
fun show(
manager: FragmentManager,
requestCode: Int,
mode: Int = FILE,
title: String? = null,
initPath: String? = null,
isShowHomeDir: Boolean = false,
isShowUpDir: Boolean = true,
isShowHideDir: Boolean = false
) {
val fragment = (manager.findFragmentByTag(tag) as? FileChooserDialog)
?: FileChooserDialog().apply {
val bundle = Bundle()
bundle.putInt("mode", mode)
bundle.putInt("requestCode", requestCode)
bundle.putString("title", title)
bundle.putBoolean("isShowHomeDir", isShowHomeDir)
bundle.putBoolean("isShowUpDir", isShowUpDir)
bundle.putBoolean("isShowHideDir", isShowHideDir)
bundle.putString("initPath", initPath)
arguments = bundle
}
fragment.show(manager, tag)
}
}
override var allowExtensions: Array<String?>? = null
override val isOnlyListDir: Boolean
get() = mode == DIRECTORY
override var isShowHomeDir: Boolean = false
override var isShowUpDir: Boolean = true
override var isShowHideDir: Boolean = false
private var requestCode: Int = 0
var title: String? = null
private var initPath = FileUtils.getSdCardPath()
private var mode: Int = FILE
private lateinit var fileAdapter: FileAdapter
private lateinit var pathAdapter: PathAdapter
override fun onStart() {
super.onStart()
val dm = DisplayMetrics()
activity?.windowManager?.defaultDisplay?.getMetrics(dm)
dialog?.window?.setLayout((dm.widthPixels * 0.9).toInt(), (dm.heightPixels * 0.8).toInt())
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.dialog_file_chooser, container, true)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ATH.applyBackgroundTint(view)
ATH.applyBackgroundTint(rv_path)
arguments?.let {
requestCode = it.getInt("requestCode")
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
}
}
tool_bar.title = title ?: let {
if (isOnlyListDir) {
getString(R.string.folder_chooser)
} else {
getString(R.string.file_chooser)
}
}
initMenu()
initContentView()
refreshCurrentDirPath(initPath)
}
private fun initMenu() {
tool_bar.inflateMenu(R.menu.file_chooser)
if (isOnlyListDir) {
tool_bar.menu.findItem(R.id.menu_ok).isVisible = true
}
tool_bar.menu.applyTint(requireContext(), false)
tool_bar.setOnMenuItemClickListener(this)
}
private fun initContentView() {
fileAdapter = FileAdapter(requireContext(), this)
pathAdapter = PathAdapter(requireContext(), this)
rv_file.addItemDecoration(DividerItemDecoration(activity, LinearLayout.VERTICAL))
rv_file.layoutManager = LinearLayoutManager(activity)
rv_file.adapter = fileAdapter
rv_path.layoutManager = LinearLayoutManager(activity, RecyclerView.HORIZONTAL, false)
rv_path.adapter = pathAdapter
}
override fun onMenuItemClick(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.menu_ok -> fileAdapter.currentPath?.let {
(parentFragment as? CallBack)?.onFilePicked(requestCode, it)
(activity as? CallBack)?.onFilePicked(requestCode, it)
dismiss()
}
}
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 != DIRECTORY) {
(parentFragment as? CallBack)?.onFilePicked(requestCode, path)
(activity as? CallBack)?.onFilePicked(requestCode, path)
dismiss()
}
}
}
}
override fun onPathClick(position: Int) {
refreshCurrentDirPath(pathAdapter.getPath(position))
}
private fun refreshCurrentDirPath(currentPath: 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) {
tv_empty.visible()
tv_empty.setText(R.string.empty)
} else {
tv_empty.gone()
}
}
interface CallBack {
fun onFilePicked(requestCode: Int, currentPath: String) {}
}
}

@ -0,0 +1,283 @@
package io.legado.app.ui.filechooser;
/**
* Generated by https://github.com/gzu-liyujiang/Image2ByteVar
*
* @author 李玉江[QQ:1023694760]
* @since 2017/01/04 06:03
*/
public class FilePickerIcon {
public static byte[] getFILE() {
return FILE;
}
public static byte[] getFOLDER() {
return FOLDER;
}
public static byte[] getHOME() {
return HOME;
}
public static byte[] getUPDIR() {
return UPDIR;
}
public static byte[] getARROW() {
return ARROW;
}
// fixed: 17-1-7 "static final" arrays should be "private"
private static final byte[] FILE = {
-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 42,
0, 0, 0, 40, 8, 6, 0, 0, 0, -120, 11, 104, 80, 0, 0, 0, 6, 98, 75, 71,
68, 0, -1, 0, -1, 0, -1, -96, -67, -89, -109, 0, 0, 0, 9, 112, 72, 89, 115, 0,
0, 14, -60, 0, 0, 14, -60, 1, -107, 43, 14, 27, 0, 0, 1, -73, 73, 68, 65, 84,
88, -123, -19, -106, 63, 75, 3, 49, 24, -121, 127, -74, 69, -37, 110, -30, -94, -109, -120, -126,
-125, -101, 56, 72, 63, -125, -97, -64, -63, 77, 28, 69, 20, 81, 68, 113, 83, 113, -12, 91,
-120, -96, -117, -120, -85, 31, 65, 29, 28, -100, -124, -94, -101, 46, -83, -105, 92, 114, 113, -24,
31, -38, -110, 59, -34, -9, 114, 87, 69, -14, 64, 41, -92, 73, -34, -89, -55, -17, 114, 1,
60, 30, -113, -25, 79, 50, 66, -19, 120, -7, 16, 26, 0, -112, 82, -61, 24, 3, 41, 35,
0, -128, 20, 10, 74, 69, -48, 58, -126, 82, -90, -37, -90, -75, -127, 82, 81, 119, 124, 16,
40, -100, 109, 79, -109, -21, 13, 82, -94, 118, -108, 82, 99, -91, 54, -58, 25, 2, 0, 120,
-85, 7, -72, -71, 127, -57, -18, -58, 12, -102, -51, 87, 115, 113, 56, -105, 74, -74, 64, -19,
104, -116, 73, 51, 63, 0, -96, 88, 104, -107, 57, -34, -102, -59, -6, -63, 75, -86, -119, -56,
-94, -99, -83, -26, -96, 123, -122, 20, -37, -107, 78, -9, -25, -79, -74, -13, -52, -106, 37, -117,
114, -23, 72, 42, 109, -96, -93, 8, 66, 0, -97, 95, -83, -49, -55, -34, 2, 86, 55, 31,
89, -78, -12, -116, 10, -59, 18, -20, 48, 90, -86, -96, -47, -48, -72, -66, -5, 64, -40, -98,
-94, 90, -27, -81, 15, 89, -76, -9, 9, -114, 99, 80, 18, 0, -90, 38, -127, -38, -46, 4,
-76, 78, -97, 113, -128, 33, 42, -124, 78, -4, -35, 38, -87, -62, -42, -9, -14, -30, 120, 127,
-69, 6, -82, 110, -21, -44, -46, 0, -72, 103, 77, 12, 73, -110, 125, 109, -55, -1, 53, 17,
86, 70, 109, 66, 113, -72, 72, -39, 32, -89, -38, 53, 99, -82, 100, -14, 48, -39, -74, 57,
107, -100, -49, -47, -84, -77, 24, 7, 121, 69, -125, -64, 126, -114, 82, -91, -66, 3, 106, 37,
59, -12, 21, 85, -46, -87, 80, -91, -20, 52, -100, 46, -38, -108, -87, 111, 104, -103, -64, 58,
71, -121, -15, -48, -60, -63, -54, -24, -80, -14, 104, 35, -73, -37, 83, -42, -112, 69, 5, -15,
-10, -108, 23, -12, 3, 63, -92, -65, 63, 43, 101, -5, -10, 7, 14, -111, 112, -66, -108, -28,
-111, 71, 27, -1, 47, -93, -65, 77, 38, -9, 81, 27, 46, 121, -76, -63, 18, 29, 86, 30,
109, -80, 68, -113, -50, -97, -14, -14, -16, 120, 60, -98, 1, 126, 0, -110, 81, -78, -5, 36,
19, -64, 3, 0, 0, 0, 0, 73, 69, 78, 68, -82, 66, 96, -126};
private static final byte[] FOLDER = {
-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 42,
0, 0, 0, 40, 8, 6, 0, 0, 0, -120, 11, 104, 80, 0, 0, 0, 6, 98, 75, 71,
68, 0, -1, 0, -1, 0, -1, -96, -67, -89, -109, 0, 0, 0, 9, 112, 72, 89, 115, 0,
0, 14, -60, 0, 0, 14, -60, 1, -107, 43, 14, 27, 0, 0, 2, -102, 73, 68, 65, 84,
88, -123, -19, -105, 61, -113, 19, 49, 16, -122, 95, 111, -110, 75, -124, -124, -60, -49, 65, 20,
-48, 34, 81, 80, 32, 81, 83, 80, -94, 52, 72, 8, 78, 66, -94, 64, 20, -108, 124, 20,
39, -47, 64, -51, -11, -7, 87, -96, 11, -38, -75, 61, 99, 15, -59, 110, 54, 118, -42, -5,
113, -71, 77, 117, -5, 54, -55, -50, -38, -42, -77, -81, 61, 99, 27, -104, 52, 105, -46, -92,
73, -73, 66, 106, 104, -61, -51, 57, -92, -21, -3, -29, 79, -61, -57, 58, 70, -13, 33, -115,
54, -25, -112, 71, -17, 127, 0, 0, -106, -38, -105, 65, 93, 64, -56, 0, 108, 33, 76, -72,
-28, -113, -14, -20, -77, 59, 25, 108, 54, 4, -14, -63, -6, 59, -112, 123, -84, -4, -84, -114,
-117, 39, 96, -74, -17, -2, -12, -59, 91, 92, -66, -103, 117, -70, 126, 19, 117, 58, -80, 57,
-121, 60, 92, 127, 5, 0, -84, -106, -53, -3, 11, 93, -108, -96, 0, 96, 52, -124, 9, 96,
6, 116, -127, -33, -65, -66, -44, -51, 56, -117, -121, -73, 62, 3, -7, 56, -74, 123, 126, -11,
-83, -24, 100, -23, -99, -6, -43, -67, -5, -119, -96, -119, -66, 80, 1, -128, 49, 0, -128, -25,
31, -98, -108, 65, 103, -93, 46, -62, -36, 24, -58, -27, -37, -6, -65, -39, -66, -108, -41, 63,
-13, 86, -40, 65, 107, 84, -7, 43, 64, -103, 70, 92, 108, 17, -45, 82, -94, -115, -47, 77,
-40, -22, 35, -108, 53, 32, 34, 56, 34, 112, -49, -14, 30, 4, 90, 66, -2, -119, 99, -34,
2, -69, -23, -33, -119, -10, -32, 18, -66, -37, -63, 114, 16, 99, -122, 2, -64, 87, 127, 97,
-56, -61, -53, 24, -96, -121, -14, -107, 35, 103, 11, -120, -91, 116, 27, -25, -45, -15, 96, 9,
80, -95, 97, -56, -61, 114, 127, 14, -10, 102, 125, 27, 36, -128, 86, -56, -70, 34, -52, 50,
-128, 109, -78, 77, 40, 118, -82, -73, 77, -65, -93, 98, 26, -128, -111, -62, 41, 62, -101, 67,
116, -111, 110, -41, 34, -53, 2, -26, 22, -9, 3, 29, 53, -11, -111, -109, -39, -94, -4, -83,
0, 85, -74, -120, -41, -25, 72, -22, 4, -27, -86, -58, -119, 45, -102, -119, 3, 52, -36, -124,
61, 40, 65, 81, -58, 55, -5, -117, 99, -80, 115, -89, 115, 52, 9, 93, 65, -90, -36, 76,
65, 94, 87, -67, -96, 74, -52, -2, 52, -46, 54, -91, -95, -109, -119, 108, 87, -13, -59, 126,
-9, 74, 40, -109, 27, 58, 106, 124, 6, -79, 40, -117, -7, 33, 100, 0, 23, -71, -72, -37,
-1, -85, 105, 31, -61, 77, 96, -24, -44, -109, 1, 40, -19, 70, 50, 113, -126, -75, -39, -22,
-26, 53, -85, -61, 113, 89, 127, 8, 23, 78, 119, 80, 55, -5, -36, -12, 117, 34, -11, 23,
-4, 78, 80, -14, 10, 112, 22, -30, 92, -5, -6, 28, 2, 25, -70, -103, 112, -46, -75, -19,
98, 67, 65, 57, 60, -110, -11, 13, 118, 4, -92, -9, 2, 79, 4, -94, 17, 118, 38, 97,
6, -104, 64, -108, -34, -103, -124, -35, -63, 115, -20, -68, -46, 58, 124, 2, 0, 56, 91, -18,
118, -123, -74, -48, 122, 88, -78, 117, -126, 90, 95, 102, -80, 49, 5, -88, 26, 80, -101, -46,
33, 83, -24, -42, 126, 53, 116, 42, 97, -104, -22, 2, -97, 111, 115, 108, -13, 17, 64, 1,
-64, 85, 103, 71, 50, 84, 1, -106, 110, 88, 106, 95, 10, 93, -5, -67, -81, 62, -44, 22,
26, 84, 1, -33, -67, -77, -24, 5, -19, -67, -116, 93, -84, 87, 2, 32, -70, 66, -104, -32,
-112, -53, 94, -63, -113, 112, 1, 125, 119, -15, -17, -92, -73, -40, 73, -109, 38, 77, -70, -19,
-6, 15, -2, -54, -98, -96, -19, -118, -95, -10, 0, 0, 0, 0, 73, 69, 78, 68, -82, 66,
96, -126};
private static final byte[] HOME = {
-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 42,
0, 0, 0, 40, 8, 4, 0, 0, 0, 34, 2, -96, -37, 0, 0, 0, 1, 115, 82, 71,
66, 0, -82, -50, 28, -23, 0, 0, 0, 2, 98, 75, 71, 68, 0, -1, -121, -113, -52, -65,
0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 46, 35, 0, 0, 46, 35, 1, 120, -91, 63,
118, 0, 0, 0, 7, 116, 73, 77, 69, 7, -37, 8, 4, 10, 36, 16, -22, -9, -18, -50,
0, 0, 2, -84, 73, 68, 65, 84, 72, -57, -19, -106, 91, 72, 20, 97, 20, -57, 127, -77,
-18, -106, -41, 54, -14, -106, -23, -102, 4, 21, 97, 5, 5, 61, 70, 74, -76, -122, -94, -11,
18, -108, -92, 121, 41, 16, -124, 96, -95, 48, -118, -108, -94, 18, -70, 96, 33, 68, -81, -127,
15, 25, 68, 15, 21, 89, 80, 42, 25, 42, -91, 97, -106, -103, -122, 107, 41, -19, -86, -37,
-22, 122, -39, 77, -73, -103, -81, 7, -105, 84, -36, -85, -26, -125, -44, -127, -31, 59, -52, -103,
-7, 113, -50, -103, -17, -4, -65, -127, -1, -74, 44, 76, -102, 113, -29, 22, -119, 50, -3, -15,
84, 75, -111, -87, -38, -81, -89, 84, 24, -120, 1, -96, -106, -102, -65, 83, -66, -118, 59, -119,
39, 54, 97, 69, -31, -109, 115, -14, 8, 15, 124, -107, -17, 27, 42, 113, 123, 125, 81, 46,
102, 28, 8, -84, -68, 116, 78, -26, 80, 77, 5, -79, 104, -48, -48, 70, -39, 124, -88, -38,
103, 37, -107, -70, -94, 28, -6, 105, 7, 34, 89, 67, -86, -90, -74, 106, 82, -51, 65, 125,
-110, 13, 27, -99, 43, 2, -17, -87, -60, -51, -124, -30, 99, -104, -88, -63, -12, 20, -93, -90,
96, 111, -80, -106, 20, 117, -3, -35, -97, -65, 54, -13, 29, 21, 8, -9, -3, -14, -122, -68,
17, 127, 50, 15, 51, -49, 49, 85, -109, 73, -79, 51, -81, 94, -103, 96, 21, 123, -126, 66,
86, 10, 64, 4, 12, -107, -72, -70, -50, -112, -57, 0, 47, -24, 123, 66, 46, 50, 80, -19,
40, 121, -59, 20, -31, -20, 102, 0, 121, 1, -48, -14, -72, 83, -7, 12, 81, -121, -79, -106,
67, 76, -71, -18, 94, -73, 85, 54, 34, 19, -122, 115, -102, 23, 16, -12, 114, 92, 73, 1,
22, 26, -24, 110, -26, 0, -114, 89, 17, -61, -32, -61, 22, 4, 2, -127, -30, -31, 125, -9,
-48, -117, -79, 103, -13, -79, -48, 68, -57, 123, -46, 25, -101, 19, -109, 57, -38, -41, -40, -127,
64, 16, -127, 70, -113, -34, 63, 104, 105, -52, -7, 2, -84, -76, -48, -42, 69, 26, -42, 121,
113, 59, 89, 93, -35, -67, 8, -126, -39, -87, 81, -35, 103, -69, 111, -24, -71, -24, 11, -123,
12, -45, -58, -37, -81, -20, -61, -20, -74, 18, 11, -23, -19, -125, 102, 20, -76, 36, 107, 121,
76, -68, 119, -24, -103, -88, 75, -123, -116, -16, -111, 38, 51, 122, -66, 121, -4, -116, 95, 68,
-42, 59, -5, 8, -126, 24, 54, -24, 120, 68, -124, 103, -24, -23, -88, -14, -29, -40, -24, -28,
-75, 85, -92, -47, -27, 117, 48, -102, -27, -20, 86, 121, 28, 5, 29, 107, 119, 112, 111, -10,
24, 5, -51, -72, 17, -122, -32, 107, -39, -110, -99, 30, -22, -58, -108, -3, -76, -6, 20, -93,
-49, -78, -59, -102, 17, -50, 40, -126, -47, -115, 66, 59, 94, -29, 78, 80, -86, -40, 74, -108,
20, -119, 34, 50, -88, -13, 83, 58, -81, -112, 73, 47, 70, -116, 52, -104, -34, 120, 86, 41,
-119, 4, 10, 1, 24, -26, -106, 71, 88, 10, 41, 0, 60, -93, -47, 31, -107, 18, -124, -123,
-106, -19, -30, 7, 31, 122, -68, 64, 83, 19, 75, -75, -12, 51, 108, -101, -127, -6, -40, -4,
97, -92, -110, -20, 67, 18, -109, -40, -58, 106, -113, -86, -66, 96, 83, -36, 15, -2, 34, -96,
-110, 88, 8, 84, -8, 60, -37, 60, 67, -43, -18, -127, 2, 1, 26, -74, 120, -124, 70, 11,
20, -108, 64, -113, 104, -119, 88, -99, -24, 112, -105, -71, 112, 117, -44, 22, 88, -90, 32, 8,
-27, 48, 33, 76, 119, 78, -52, 89, 101, 20, -100, -12, 49, 21, 24, 84, 97, 12, 59, 19,
46, -44, -36, 75, 65, 70, 70, 118, -83, -2, 67, 101, -21, 80, -123, -65, -69, -64, -79, -68,
127, -48, -106, 4, -6, -113, -37, 111, 38, -57, 11, 112, 71, 102, 113, -50, 0, 0, 0, 0,
73, 69, 78, 68, -82, 66, 96, -126};
private static final byte[] UPDIR = {
-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 42,
0, 0, 0, 40, 8, 6, 0, 0, 0, -120, 11, 104, 80, 0, 0, 0, 6, 98, 75, 71,
68, 0, -1, 0, -1, 0, -1, -96, -67, -89, -109, 0, 0, 0, 9, 112, 72, 89, 115, 0,
0, 14, -60, 0, 0, 14, -60, 1, -107, 43, 14, 27, 0, 0, 2, -111, 73, 68, 65, 84,
88, -123, -19, -106, -51, 74, -21, 64, 20, -57, -1, -87, -47, 7, -15, 41, 92, 118, -47, 82,
10, -59, 110, 20, -95, 32, 77, -101, 44, -92, -37, -66, 80, -23, 3, 8, -126, -76, -48, -115,
-120, 43, 117, -19, -109, -44, -113, -50, -57, -103, 115, 87, 103, 76, 106, -117, 77, 19, -17, -107,
75, -2, 48, -52, -112, 100, 38, -65, 57, -25, -52, 57, 3, 84, -86, 84, -87, -46, -1, -83,
36, 73, 56, 73, 18, -2, -87, -11, -61, 50, 22, -119, -29, -104, 47, 47, 47, 97, -116, -127,
-75, -106, 39, -109, 73, 80, -58, -70, 105, 21, 94, 48, -114, 99, 78, -110, 4, 68, 4, -91,
20, -116, 49, 120, 121, 121, -63, 120, 60, 46, 21, -74, 86, 100, -14, 112, 56, -28, -85, -85,
43, 0, -128, -75, 22, -50, 57, 48, 51, -114, -113, -113, -47, -21, -11, 74, 13, -125, -67, 65,
-121, -61, 33, -113, 70, 35, 4, 65, 0, 99, -116, 111, -42, 90, 16, 17, 58, -99, 14, 46,
46, 46, 74, -125, -35, 11, 84, 44, -23, -100, -61, -57, -57, 7, 86, -85, 21, -116, 49, 32,
34, 111, 89, 107, 45, 90, -83, 22, -50, -50, -50, 74, -127, -51, 125, -104, 6, -125, 1, -57,
113, -20, -83, -89, -108, -126, -75, 22, 0, -32, -100, -13, 45, 8, 2, 48, 51, -102, -51, 38,
-100, 115, 124, 125, 125, 93, 40, 102, 115, -127, 14, 6, 3, -114, -94, -56, -69, 87, 107, 13,
34, 2, 0, -1, 44, 8, -78, 60, 68, -124, 70, -93, 1, 34, -30, -101, -101, -101, -67, 97,
119, 6, -115, -94, -120, 123, -67, -98, -121, 19, 43, 10, -88, -12, -58, 24, 56, -25, -4, 55,
-78, -127, 122, -67, 14, 34, -30, -37, -37, -37, -67, 96, 119, -102, 20, 69, 17, 119, -69, 93,
15, 98, -83, -59, -63, -63, 1, 0, 32, 12, -61, 12, -88, -124, -61, -37, -37, 27, -34, -33,
-33, -3, -122, 68, 15, 15, 15, -104, -49, -25, -71, 97, -65, -99, -48, -17, -9, -71, -35, 110,
-5, 83, 45, 58, 58, 58, -14, -112, 34, -79, -30, -21, -21, 43, -106, -53, 37, -76, -42, 0,
0, -26, -49, -13, 20, -122, 33, -18, -17, -17, -79, 88, 44, 114, -63, 126, -21, 122, 102, -58,
108, 54, -13, 22, 19, 32, 0, 56, 61, 61, -3, -14, 76, 107, -115, -43, 106, 5, 34, -62,
-45, -45, 83, -26, -35, -6, -72, 84, -48, -23, 116, -70, 113, -25, -25, -25, -25, -20, -100, -53,
-4, 60, 29, -109, 114, -6, 103, -77, 89, 41, 21, 106, -17, 90, 47, -112, 2, 8, -64, 3,
-118, -85, -45, -33, 20, -43, -34, -96, -52, -100, 57, -35, -64, 103, -116, -82, 3, -1, 83, 80,
-87, 62, 2, -72, -98, 79, 5, -74, 44, -107, 2, 42, 112, 0, 50, -15, -7, 43, 64, -103,
121, 35, -24, -81, 118, -67, -28, -41, -76, -53, -91, -43, -21, 117, -106, 119, -23, 94, -58, -113,
-113, -113, 59, 101, -123, -62, -96, -101, -54, -87, 88, -14, -28, -28, 4, -121, -121, -121, 25, 48,
-71, 35, 40, -91, -16, -4, -4, -68, -13, -1, 10, -71, 62, 125, -75, 91, 7, -83, -43, 106,
30, 74, 42, -104, -28, 89, -83, 53, -76, -42, -71, 98, -72, -112, 69, -91, 68, 110, -69, -96,
-92, 123, -7, 70, 41, 5, -91, 84, -18, 10, 85, 10, 104, 58, 70, -45, 125, -6, -80, 73,
47, 22, -51, -85, 66, -82, 87, 74, 1, -128, -17, 55, -127, 109, 2, -1, -85, -96, 98, -47,
-76, 91, -73, 1, -82, -113, 1, -8, 107, -30, -113, -125, -34, -35, -35, 5, 68, -28, 19, -27,
-74, -12, 83, 102, -46, -81, 84, -87, 82, -91, 95, -88, 63, 49, -122, -88, 68, 127, -55, -90,
73, 0, 0, 0, 0, 73, 69, 78, 68, -82, 66, 96, -126};
private static final byte[] ARROW = {-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 48,
0, 0, 0, 117, 8, 3, 0, 0, 0, 63, 73, -110, 106, 0, 0, 2, -9, 80, 76, 84,
69, -46, -46, -46, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -65, -65, -65, 0,
0, 0, -52, -52, -52, -49, -49, -49, -47, -47, -47, -47, -47, -47, -48, -48, -48, -54, -54, -54,
-48, -48, -48, -48, -48, -48, -47, -47, -47, -47, -47, -47, -49, -49, -49, -86, -86, -86, -48, -48,
-48, -48, -48, -48, -47, -47, -47, -52, -52, -52, -49, -49, -49, -48, -48, -48, -48, -48, -48, -1,
-1, -1, -49, -49, -49, -48, -48, -48, -48, -48, -48, -51, -51, -51, -50, -50, -50, -48, -48, -48,
-48, -48, -48, -50, -50, -50, -48, -48, -48, -48, -48, -48, -48, -48, -48, -52, -52, -52, -48, -48,
-48, -48, -48, -48, -48, -48, -48, -46, -46, -46, -47, -47, -47, -52, -52, -52, -48, -48, -48, -48,
-48, -48, -1, -1, -1, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48,
-48, -48, -48, -41, -41, -41, -48, -48, -48, -49, -49, -49, -43, -43, -43, -47, -47, -47, -49, -49,
-49, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -49, -49, -49, -47,
-47, -47, -49, -49, -49, -47, -47, -47, -48, -48, -48, -48, -48, -48, -48, -48, -48, -49, -49, -49,
-47, -47, -47, -44, -44, -44, -48, -48, -48, -48, -48, -48, -1, -1, -1, -46, -46, -46, -48, -48,
-48, -49, -49, -49, -47, -47, -47, -49, -49, -49, -35, -35, -35, -49, -49, -49, -49, -49, -49, -49,
-49, -49, -47, -47, -47, -40, -40, -40, -49, -49, -49, -48, -48, -48, -48, -48, -48, -47, -47, -47,
-47, -47, -47, -47, -47, -47, -48, -48, -48, -60, -60, -60, -53, -53, -53, -48, -48, -48, -46, -46,
-46, -65, -65, -65, -48, -48, -48, -54, -54, -54, -45, -45, -45, -47, -47, -47, -49, -49, -49, -48,
-48, -48, -49, -49, -49, -51, -51, -51, -48, -48, -48, -48, -48, -48, -46, -46, -46, -47, -47, -47,
-47, -47, -47, -37, -37, -37, -47, -47, -47, -49, -49, -49, -50, -50, -50, -48, -48, -48, -128, -128,
-128, -48, -48, -48, -48, -48, -48, -50, -50, -50, -48, -48, -48, -48, -48, -48, -43, -43, -43, -47,
-47, -47, -48, -48, -48, -48, -48, -48, -48, -48, -48, -43, -43, -43, -47, -47, -47, -48, -48, -48,
-50, -50, -50, -49, -49, -49, -48, -48, -48, -48, -48, -48, -33, -33, -33, -48, -48, -48, -52, -52,
-52, -51, -51, -51, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -52,
-52, -52, -48, -48, -48, -46, -46, -46, -48, -48, -48, -37, -37, -37, -29, -29, -29, -48, -48, -48,
-48, -48, -48, -47, -47, -47, -48, -48, -48, -49, -49, -49, -46, -46, -46, -48, -48, -48, -49, -49,
-49, -47, -47, -47, -58, -58, -58, -49, -49, -49, -47, -47, -47, -48, -48, -48, -50, -50, -50, -47,
-47, -47, -50, -50, -50, -48, -48, -48, -48, -48, -48, -50, -50, -50, -48, -48, -48, -48, -48, -48,
-48, -48, -48, -55, -55, -55, -45, -45, -45, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48,
-48, -46, -46, -46, -47, -47, -47, -48, -48, -48, -47, -47, -47, -51, -51, -51, -47, -47, -47, -51,
-51, -51, -48, -48, -48, -47, -47, -47, -48, -48, -48, -65, -65, -65, -48, -48, -48, -50, -50, -50,
-48, -48, -48, -47, -47, -47, -49, -49, -49, -47, -47, -47, -47, -47, -47, -48, -48, -48, -48, -48,
-48, -50, -50, -50, -47, -47, -47, -49, -49, -49, -49, -49, -49, -47, -47, -47, -48, -48, -48, -47,
-47, -47, -49, -49, -49, -39, -39, -39, -50, -50, -50, -47, -47, -47, -48, -48, -48, -42, -42, -42,
-47, -47, -47, -46, -46, -46, -47, -47, -47, -47, -47, -47, -48, -48, -48, -49, -49, -49, -49, -49,
-49, -47, -47, -47, -48, -48, -48, -47, -47, -47, -48, -48, -48, -50, -50, -50, -47, -47, -47, -48,
-48, -48, -47, -47, -47, -49, -49, -49, -48, -48, -48, -49, -49, -49, -50, -50, -50, -47, -47, -47,
-56, -56, -56, -48, -48, -48, -48, -48, -48, -40, -40, -40, -45, -45, -45, -47, -47, -47, -47, -47,
-47, -49, -49, -49, -48, -48, -48, -43, -43, -43, -52, -52, -52, -49, -49, -49, -47, -47, -47, -47,
-47, -47, -50, -50, -50, -49, -49, -49, -47, -47, -47, -49, -49, -49, -48, -48, -48, -49, -49, -49,
-115, 52, -27, 40, 0, 0, 0, -3, 116, 82, 78, 83, 34, -37, -1, -7, 120, 4, 0, 5,
118, -9, -41, 38, 24, -46, -2, -100, 78, 69, 3, -98, -54, 28, 40, -37, -8, 115, 3, 122,
-6, -43, 36, 31, -49, -109, 78, 70, -93, -50, 20, 43, -38, -10, 113, 123, 30, -45, -115, 2,
86, -5, -9, 65, -94, -55, 19, -29, 111, 6, 127, 32, -39, -3, -114, 92, -4, 58, -86, -63,
22, -32, 109, -126, -5, -51, 36, -44, -121, 1, 90, -11, 59, -79, -59, 15, 48, -11, 106, -123,
26, 37, -40, 98, -3, -14, -80, -61, 13, 44, -25, 103, 8, -120, 29, 35, -35, -4, -127, 101,
46, -76, -71, 17, -24, 99, 7, -118, -58, 42, 124, 2, 103, -13, 47, -67, -65, 12, 55, -27,
-116, -57, 24, 44, -35, 119, 112, -21, 49, 8, -70, 10, 51, -19, -14, 93, -111, -62, 25, -30,
119, -78, 14, 9, -108, 113, 117, -18, -53, 63, -23, -15, 89, 9, -106, -2, -66, 21, 50, -125,
-33, -28, 57, 97, -97, -73, 19, 69, -12, -17, 81, -87, 57, 94, -99, -73, 72, -18, 82, -88,
-20, 27, 12, -100, 67, -16, 83, 16, -91, -122, 54, -26, 104, -101, 64, 85, 11, -82, 100, -101,
20, 84, -47, 125, 31, -102, 62, -57, -88, -125, 107, -102, -69, -15, -53, -77, 52, -31, -105, 66,
-21, 87, 121, 110, -67, 23, -60, -84, 13, 46, 111, 88, -75, 119, 42, 50, -32, 106, -107, 63,
91, -78, 117, 114, -108, 41, -51, -28, -65, 0, 0, 3, -107, 73, 68, 65, 84, 88, -61, -107,
-40, 105, 84, -108, 85, 24, 7, 112, -4, -113, -13, -112, -37, -101, -111, -96, 78, 67, -94, 38,
-18, -66, 14, 46, 52, -38, -28, -96, 34, 90, -67, -31, -106, 75, -114, 70, 74, 42, 90, 86,
-109, 4, 38, -88, -111, -90, 50, -126, -72, -46, -54, -102, 38, -102, -53, 104, -101, 123, -88, -71,
78, -18, -91, -90, 88, 82, 46, 89, 90, -71, -107, -27, 7, 95, 62, 120, -114, -100, -93, -121,
-5, 127, 63, -65, -65, 15, -9, -36, 123, -97, -5, 127, -98, -96, -96, 106, -80, 84, -73, -118,
-14, 23, 20, -4, 64, 13, -44, -84, 69, 0, -87, 93, 7, -38, -125, 12, -112, -70, 22, -32,
33, 6, -124, 60, 12, -44, 11, 37, -128, -124, -43, 71, -125, -122, 33, 4, -80, 61, 98, 71,
-8, -93, 4, -112, 70, 17, -48, 26, 51, 64, -102, 0, 53, -102, 50, 64, 30, -45, -48, 44,
-110, 1, -51, 91, -96, 101, 43, 43, 1, -84, -83, -19, -88, -39, -122, 0, 18, -38, 22, 104,
-89, 19, 64, -38, 59, 16, -43, -127, 1, -42, -114, 64, -89, -50, 4, -112, -80, 104, 68, 61,
-82, 19, -64, -39, -59, -126, -120, -82, 4, -112, -48, 39, -32, 122, -110, 1, -46, -51, 1, 119,
12, 3, -84, -35, -127, 30, 61, 9, 32, -51, 99, 17, -43, 75, 39, 64, 112, 92, 111, -12,
105, 68, 0, 121, -22, 105, 104, -49, 48, -64, 120, 22, -120, -17, 75, 0, -111, 126, 64, -1,
1, 12, 24, 24, -117, -25, 6, -39, 8, -32, 28, -20, 64, -77, 33, 4, -112, -95, -49, 3,
113, 6, 1, 100, -104, 7, -61, 99, 24, -32, 28, 1, -68, -112, 64, 0, 121, 113, 36, -30,
71, -23, 4, 72, 124, -55, -114, 78, -93, 9, 32, 99, -58, 34, 105, 28, 3, 100, -68, 27,
13, 94, 102, -128, -13, 21, 96, -62, -85, 4, -112, -127, -81, 33, -2, 117, -125, 0, -34, 55,
-110, 48, 49, -108, 0, -110, -36, 2, -82, 55, 25, -112, -110, 10, 76, 122, -117, 0, 34, -109,
-127, -76, 116, 6, 76, -103, -118, 73, -61, 116, 2, 120, -89, -71, -15, 118, 6, 1, -28, -99,
-23, -64, 12, -125, 0, -58, -69, -64, -52, 89, 4, -112, -39, -26, -70, 51, 125, 4, -112, 57,
89, -56, -98, -101, 66, 0, 95, -114, 3, -13, -26, 19, 64, -110, 23, -64, -79, -112, 1, 82,
-35, -123, -8, -95, 12, -16, -103, -21, 94, -76, -104, 0, 50, 101, 38, -122, 119, 51, 8, -112,
-5, -98, 11, 105, -75, 9, 80, 81, 58, -19, -17, 51, 64, -1, 0, -8, 112, 12, 1, -60,
-6, -111, -71, -18, -39, 4, -112, -113, -13, -112, 95, -112, 66, -128, -62, 34, -83, -8, -109, 37,
4, -112, -91, -79, -16, -92, 50, -64, -8, 20, 88, -58, 0, -55, 1, 74, 24, -80, 124, 36,
44, 43, 8, -112, -16, -103, -85, 120, -27, 42, 2, -84, 94, 3, -1, 90, 67, 29, -92, -41,
1, -42, 89, -43, 55, -50, 54, 24, -120, 14, 34, -114, -58, -25, 37, 112, 127, 65, 28, 62,
-33, -105, -59, -38, 87, -99, 9, -16, -11, 122, -8, 55, 16, 23, -56, 23, 14, 108, -12, -86,
95, 81, 91, 28, -80, 105, 51, 81, 4, -52, 10, 110, 73, 37, -54, -52, -106, -83, 46, -84,
-116, 36, -64, 55, 107, 80, -70, -115, -88, -83, -37, -21, 1, 59, -104, 98, -4, -83, -122, -84,
-47, 68, -71, -33, -71, 11, -10, -35, -60, 11, -108, -66, -57, -115, -52, 72, 2, -20, -51, -122,
127, -97, -95, 14, 2, 17, -64, 119, 78, -11, 103, 87, -17, 98, -34, -29, 90, -60, -61, -66,
60, 26, -98, -3, 68, 116, -16, 29, -48, -76, -52, -125, 4, 88, -67, 9, -2, 67, 68, -4,
-15, 30, 6, -70, 39, -86, 7, 44, -37, 17, 32, -21, 40, 17, -31, 118, -106, -64, 50, -120,
8, -119, -127, -17, 93, -104, -80, -124, 0, 63, 100, 35, -65, 61, -111, -116, -73, 31, 3, -114,
7, 8, 112, -62, 60, -43, 109, -120, -80, -66, 116, 42, -20, 63, 18, -3, 67, -32, -92, 27,
-89, 14, 18, -96, 111, -39, -67, 79, -11, -3, -128, -17, 52, -16, -109, 83, -67, 105, -46, -51,
61, -34, 21, 70, -76, 101, 63, -97, -127, 103, 60, -47, -8, 21, -106, 87, 122, -110, -85, 6,
-65, -4, -118, -46, -77, -122, 58, -16, -98, -82, 28, 43, -86, 2, 33, -25, -52, -32, 114, -108,
104, -64, -25, -100, -127, -3, 60, -47, -30, -5, -54, 93, -72, -16, 27, 1, 98, -4, -56, 46,
32, -26, 26, 9, 23, -127, -33, -1, 32, -64, 17, 13, 101, 42, 73, -27, -50, 63, -105, 74,
-48, -5, 50, 49, -3, 9, 20, 37, -95, -86, 61, -82, 4, 98, 74, -111, -1, -89, -95, 14,
114, -51, 123, -4, 87, -94, -6, -56, -53, -8, 27, -56, -69, 66, 12, -43, -82, 94, -125, -25,
58, 49, -74, 43, -68, -95, 21, 87, 57, -109, -71, 27, -4, -109, -121, -78, 127, 83, -44, -127,
-43, 124, -113, 111, 22, -118, 50, -48, -1, 3, -4, -86, -109, -54, 10, 80, 17, -8, -1, 23,
117, -112, 123, -53, 108, 41, 50, -44, -63, 109, -80, -19, 79, -78, 17, 39, 44, -102, 0, 0,
0, 0, 73, 69, 78, 68, -82, 66, 96, -126};
}

@ -0,0 +1,147 @@
package io.legado.app.ui.filechooser.adapter
import android.content.Context
import android.graphics.drawable.Drawable
import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.ui.filechooser.FilePickerIcon
import io.legado.app.ui.filechooser.entity.FileItem
import io.legado.app.ui.filechooser.utils.ConvertUtils
import io.legado.app.ui.filechooser.utils.FileUtils
import kotlinx.android.synthetic.main.item_path_filepicker.view.*
import org.jetbrains.anko.sdk27.listeners.onClick
import java.io.File
import java.util.*
class FileAdapter(context: Context, val callBack: CallBack) :
SimpleRecyclerAdapter<FileItem>(context, R.layout.item_file_filepicker) {
private var rootPath: String? = null
var currentPath: String? = null
private set
private var homeIcon: Drawable? = null
private var upIcon: Drawable? = null
private var folderIcon: Drawable? = null
private var fileIcon: Drawable? = null
fun loadData(path: String?) {
if (path == null) {
return
}
if (homeIcon == null) {
homeIcon = ConvertUtils.toDrawable(FilePickerIcon.getHOME())
}
if (upIcon == null) {
upIcon = ConvertUtils.toDrawable(FilePickerIcon.getUPDIR())
}
if (folderIcon == null) {
folderIcon = ConvertUtils.toDrawable(FilePickerIcon.getFOLDER())
}
if (fileIcon == null) {
fileIcon = ConvertUtils.toDrawable(FilePickerIcon.getFILE())
}
val data = ArrayList<FileItem>()
if (rootPath == null) {
rootPath = path
}
currentPath = path
if (callBack.isShowHomeDir) {
//添加“返回主目录”
val fileRoot = FileItem()
fileRoot.isDirectory = true
fileRoot.icon = homeIcon
fileRoot.name = DIR_ROOT
fileRoot.size = 0
fileRoot.path = rootPath ?: path
data.add(fileRoot)
}
if (callBack.isShowUpDir && path != "/") {
//添加“返回上一级目录”
val fileParent = FileItem()
fileParent.isDirectory = true
fileParent.icon = upIcon
fileParent.name = DIR_PARENT
fileParent.size = 0
fileParent.path = File(path).parent
data.add(fileParent)
}
currentPath?.let { currentPath ->
val files: Array<File?>? = callBack.allowExtensions?.let { allowExtensions ->
if (callBack.isOnlyListDir) {
FileUtils.listDirs(currentPath, allowExtensions)
} else {
FileUtils.listDirsAndFiles(currentPath, allowExtensions)
}
} ?: let {
if (callBack.isOnlyListDir) {
FileUtils.listDirs(currentPath)
} else {
FileUtils.listDirsAndFiles(currentPath)
}
}
if (files != null) {
for (file in files) {
if (file == null || (!callBack.isShowHideDir && file.name.startsWith("."))) {
continue
}
val fileItem = FileItem()
val isDirectory = file.isDirectory
fileItem.isDirectory = isDirectory
if (isDirectory) {
fileItem.icon = folderIcon
fileItem.size = 0
} else {
fileItem.icon = fileIcon
fileItem.size = file.length()
}
fileItem.name = file.name
fileItem.path = file.absolutePath
data.add(fileItem)
}
}
setItems(data)
}
}
override fun convert(holder: ItemViewHolder, item: FileItem, payloads: MutableList<Any>) {
holder.itemView.apply {
image_view.setImageDrawable(item.icon)
text_view.text = item.name
onClick {
callBack.onFileClick(holder.layoutPosition)
}
}
}
interface CallBack {
fun onFileClick(position: Int)
//允许的扩展名
var allowExtensions: Array<String?>?
/**
* 是否仅仅读取目录
*/
val isOnlyListDir: Boolean
/**
* 是否显示返回主目录
*/
var isShowHomeDir: Boolean
/**
* 是否显示返回上一级
*/
var isShowUpDir: Boolean
/**
* 是否显示隐藏的目录.开头
*/
var isShowHideDir: Boolean
}
companion object {
const val DIR_ROOT = "."
const val DIR_PARENT = ".."
}
}

@ -0,0 +1,63 @@
package io.legado.app.ui.filechooser.adapter
import android.content.Context
import android.os.Environment
import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.ui.filechooser.FilePickerIcon
import io.legado.app.ui.filechooser.utils.ConvertUtils
import kotlinx.android.synthetic.main.item_path_filepicker.view.*
import org.jetbrains.anko.sdk27.listeners.onClick
import java.util.*
class PathAdapter(context: Context, val callBack: CallBack) :
SimpleRecyclerAdapter<String>(context, R.layout.item_path_filepicker) {
private val paths = LinkedList<String>()
private val sdCardDirectory = Environment.getExternalStorageDirectory().absolutePath
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 tmps = path1.substring(path1.indexOf("/") + 1).split("/".toRegex())
.dropLastWhile { it.isEmpty() }.toTypedArray()
Collections.addAll(paths, *tmps)
}
paths.addFirst(ROOT_HINT)
setItems(paths)
}
override fun convert(holder: ItemViewHolder, item: String, payloads: MutableList<Any>) {
holder.itemView.apply {
text_view.text = item
image_view.setImageDrawable(arrowIcon)
onClick {
callBack.onPathClick(holder.layoutPosition)
}
}
}
interface CallBack {
fun onPathClick(position: Int)
}
companion object {
private val ROOT_HINT = "SD"
}
}

@ -0,0 +1,17 @@
package io.legado.app.ui.filechooser.entity
import android.graphics.drawable.Drawable
/**
* 文件项信息
*
* @author 李玉江[QQ:1032694760]
* @since 2014-05-23 18:02
*/
class FileItem : JavaBean() {
var icon: Drawable? = null
var name: String? = null
var path = "/"
var size: Long = 0
var isDirectory = false
}

@ -0,0 +1,54 @@
package io.legado.app.ui.filechooser.entity
import java.io.Serializable
import java.lang.reflect.Field
import java.lang.reflect.Modifier
import java.util.*
/**
* JavaBean类
*
* @author 李玉江[QQ:1032694760]
* @since 2014-04-23 16:14
*/
open class JavaBean : Serializable {
/**
* 反射出所有字段值
*/
override fun toString(): String {
val list = ArrayList<Field>()
var clazz: Class<*>? = javaClass
list.addAll(listOf(*clazz!!.declaredFields))//得到自身的所有字段
val sb = StringBuilder()
while (clazz != Any::class.java) {
clazz = clazz!!.superclass//得到继承自父类的字段
val fields = clazz!!.declaredFields
for (field in fields) {
val modifier = field.modifiers
if (Modifier.isPublic(modifier) || Modifier.isProtected(modifier)) {
list.add(field)
}
}
}
val fields = list.toTypedArray()
for (field in fields) {
val fieldName = field.name
try {
val obj = field.get(this)
sb.append(fieldName)
sb.append("=")
sb.append(obj)
sb.append("\n")
} catch (ignored: IllegalAccessException) {
}
}
return sb.toString()
}
companion object {
private const val serialVersionUID = -6111323241670458039L
}
}

@ -0,0 +1,122 @@
package io.legado.app.ui.filechooser.utils
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import okhttp3.internal.and
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
import java.text.DecimalFormat
/**
* 数据类型转换单位转换
*
* @author 李玉江[QQ:1023694760]
* @since 2014-4-18
*/
object ConvertUtils {
const val GB: Long = 1073741824
const val MB: Long = 1048576
const val KB: Long = 1024
fun toInt(obj: Any): Int {
return try {
Integer.parseInt(obj.toString())
} catch (e: NumberFormatException) {
-1
}
}
fun toInt(bytes: ByteArray): Int {
var result = 0
var abyte: Byte
for (i in bytes.indices) {
abyte = bytes[i]
result += abyte and 0xFF shl 8 * i
}
return result
}
fun toFloat(obj: Any): Float {
return try {
java.lang.Float.parseFloat(obj.toString())
} catch (e: NumberFormatException) {
-1f
}
}
fun toString(objects: Array<Any>, tag: String): String {
val sb = StringBuilder()
for (`object` in objects) {
sb.append(`object`)
sb.append(tag)
}
return sb.toString()
}
@JvmOverloads
fun toBitmap(bytes: ByteArray, width: Int = -1, height: Int = -1): Bitmap? {
var bitmap: Bitmap? = null
if (bytes.isNotEmpty()) {
try {
val options = BitmapFactory.Options()
// 设置让解码器以最佳方式解码
options.inPreferredConfig = null
if (width > 0 && height > 0) {
options.outWidth = width
options.outHeight = height
}
bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size, options)
bitmap!!.density = 96// 96 dpi
} catch (e: Exception) {
}
}
return bitmap
}
private fun toDrawable(bitmap: Bitmap?): Drawable? {
return if (bitmap == null) null else BitmapDrawable(Resources.getSystem(), bitmap)
}
fun toDrawable(bytes: ByteArray): Drawable? {
return toDrawable(toBitmap(bytes))
}
fun toFileSizeString(fileSize: Long): String {
val df = DecimalFormat("0.00")
val fileSizeString: String
fileSizeString = when {
fileSize < KB -> fileSize.toString() + "B"
fileSize < MB -> df.format(fileSize.toDouble() / KB) + "K"
fileSize < GB -> df.format(fileSize.toDouble() / MB) + "M"
else -> df.format(fileSize.toDouble() / GB) + "G"
}
return fileSizeString
}
@JvmOverloads
fun toString(`is`: InputStream, charset: String = "utf-8"): String {
val sb = StringBuilder()
try {
val reader = BufferedReader(InputStreamReader(`is`, charset))
while (true) {
val line = reader.readLine()
if (line == null) {
break
} else {
sb.append(line).append("\n")
}
}
reader.close()
`is`.close()
} catch (e: IOException) {
}
return sb.toString()
}
}

@ -0,0 +1,670 @@
package io.legado.app.ui.filechooser.utils
import android.webkit.MimeTypeMap
import androidx.annotation.IntDef
import java.io.*
import java.nio.charset.Charset
import java.text.SimpleDateFormat
import java.util.*
import java.util.regex.Pattern
/**
* 文件处理
* <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
* <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
*
* @author 李玉江[QQ:1023694760]
* @since 2014-4-18
*/
object FileUtils {
const val BY_NAME_ASC = 0
const val BY_NAME_DESC = 1
const val BY_TIME_ASC = 2
const val BY_TIME_DESC = 3
const val BY_SIZE_ASC = 4
const val BY_SIZE_DESC = 5
const val BY_EXTENSION_ASC = 6
const val BY_EXTENSION_DESC = 7
@IntDef(value = [BY_NAME_ASC, BY_NAME_DESC, BY_TIME_ASC, BY_TIME_DESC, BY_SIZE_ASC, BY_SIZE_DESC, BY_EXTENSION_ASC, BY_EXTENSION_DESC])
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
annotation class SortType
/**
* 将目录分隔符统一为平台默认的分隔符并为目录结尾添加分隔符
*/
fun separator(path: String): String {
var path = path
val separator = File.separator
path = path.replace("\\", separator)
if (!path.endsWith(separator)) {
path += separator
}
return path
}
fun closeSilently(c: Closeable?) {
if (c == null) {
return
}
try {
c.close()
} catch (ignored: IOException) {
}
}
/**
* 列出指定目录下的所有子目录
*/
@JvmOverloads
fun listDirs(
startDirPath: String,
excludeDirs: Array<String?>? = null, @SortType sortType: Int = BY_NAME_ASC
): Array<File?> {
var excludeDirs = excludeDirs
val dirList = ArrayList<File>()
val startDir = File(startDirPath)
if (!startDir.isDirectory) {
return arrayOfNulls(0)
}
val dirs = startDir.listFiles(FileFilter { f ->
if (f == null) {
return@FileFilter false
}
f.isDirectory
})
?: return arrayOfNulls(0)
if (excludeDirs == null) {
excludeDirs = arrayOfNulls(0)
}
for (dir in dirs) {
val file = dir.absoluteFile
if (!excludeDirs.contentDeepToString().contains(file.name)) {
dirList.add(file)
}
}
when (sortType) {
BY_NAME_ASC -> Collections.sort(dirList, SortByName())
BY_NAME_DESC -> {
Collections.sort(dirList, SortByName())
dirList.reverse()
}
BY_TIME_ASC -> Collections.sort(dirList, SortByTime())
BY_TIME_DESC -> {
Collections.sort(dirList, SortByTime())
dirList.reverse()
}
BY_SIZE_ASC -> Collections.sort(dirList, SortBySize())
BY_SIZE_DESC -> {
Collections.sort(dirList, SortBySize())
dirList.reverse()
}
BY_EXTENSION_ASC -> Collections.sort(dirList, SortByExtension())
BY_EXTENSION_DESC -> {
Collections.sort(dirList, SortByExtension())
dirList.reverse()
}
}
return dirList.toTypedArray()
}
/**
* 列出指定目录下的所有子目录及所有文件
*/
@JvmOverloads
fun listDirsAndFiles(
startDirPath: String,
allowExtensions: Array<String?>? = null
): Array<File?>? {
val dirs: Array<File?>?
val files: Array<File?>? = if (allowExtensions == null) {
listFiles(startDirPath)
} else {
listFiles(startDirPath, allowExtensions)
}
val dirsAndFiles: Array<File?>
dirs = listDirs(startDirPath)
if (files == null) {
return null
}
dirsAndFiles = arrayOfNulls(dirs.size + files.size)
System.arraycopy(dirs, 0, dirsAndFiles, 0, dirs.size)
System.arraycopy(files, 0, dirsAndFiles, dirs.size, files.size)
return dirsAndFiles
}
/**
* 列出指定目录下的所有文件
*/
@JvmOverloads
fun listFiles(
startDirPath: String,
filterPattern: Pattern? = null, @SortType sortType: Int = BY_NAME_ASC
): Array<File?> {
val fileList = ArrayList<File>()
val f = File(startDirPath)
if (!f.isDirectory) {
return arrayOfNulls(0)
}
val files = f.listFiles(FileFilter { f ->
if (f == null) {
return@FileFilter false
}
if (f.isDirectory) {
return@FileFilter false
}
filterPattern?.matcher(f.name)?.find() ?: true
})
?: return arrayOfNulls(0)
for (file in files) {
fileList.add(file.absoluteFile)
}
when (sortType) {
BY_NAME_ASC -> Collections.sort(fileList, SortByName())
BY_NAME_DESC -> {
Collections.sort(fileList, SortByName())
fileList.reverse()
}
BY_TIME_ASC -> Collections.sort(fileList, SortByTime())
BY_TIME_DESC -> {
Collections.sort(fileList, SortByTime())
fileList.reverse()
}
BY_SIZE_ASC -> Collections.sort(fileList, SortBySize())
BY_SIZE_DESC -> {
Collections.sort(fileList, SortBySize())
fileList.reverse()
}
BY_EXTENSION_ASC -> Collections.sort(fileList, SortByExtension())
BY_EXTENSION_DESC -> {
Collections.sort(fileList, SortByExtension())
fileList.reverse()
}
}
return fileList.toTypedArray()
}
/**
* 列出指定目录下的所有文件
*/
fun listFiles(startDirPath: String, allowExtensions: Array<String?>): Array<File?>? {
val file = File(startDirPath)
return file.listFiles { dir, name ->
//返回当前目录所有以某些扩展名结尾的文件
val extension = getExtension(name)
allowExtensions.contentDeepToString().contains(extension)
}
}
/**
* 列出指定目录下的所有文件
*/
fun listFiles(startDirPath: String, allowExtension: String?): Array<File?>? {
return listFiles(startDirPath, arrayOf(allowExtension))
}
/**
* 判断文件或目录是否存在
*/
fun exist(path: String): Boolean {
val file = File(path)
return file.exists()
}
/**
* 删除文件或目录
*/
@JvmOverloads
fun delete(file: File, deleteRootDir: Boolean = false): Boolean {
var result = false
if (file.isFile) {
//是文件
result = deleteResolveEBUSY(file)
} else {
//是目录
val files = file.listFiles() ?: return false
if (files.isEmpty()) {
result = deleteRootDir && deleteResolveEBUSY(file)
} else {
for (f in files) {
delete(f, deleteRootDir)
result = deleteResolveEBUSY(f)
}
}
if (deleteRootDir) {
result = deleteResolveEBUSY(file)
}
}
return result
}
/**
* bug: open failed: EBUSY (Device or resource busy)
* fix: http://stackoverflow.com/questions/11539657/open-failed-ebusy-device-or-resource-busy
*/
private fun deleteResolveEBUSY(file: File): Boolean {
// Before you delete a Directory or File: rename it!
val to = File(file.absolutePath + System.currentTimeMillis())
file.renameTo(to)
return to.delete()
}
/**
* 删除文件或目录
*/
@JvmOverloads
fun delete(path: String, deleteRootDir: Boolean = false): Boolean {
val file = File(path)
return if (file.exists()) {
delete(file, deleteRootDir)
} else false
}
/**
* 复制文件为另一个文件或复制某目录下的所有文件及目录到另一个目录下
*/
fun copy(src: String, tar: String): Boolean {
val srcFile = File(src)
return srcFile.exists() && copy(srcFile, File(tar))
}
/**
* 复制文件或目录
*/
fun copy(src: File, tar: File): Boolean {
try {
if (src.isFile) {
val `is` = FileInputStream(src)
val op = FileOutputStream(tar)
val bis = BufferedInputStream(`is`)
val bos = BufferedOutputStream(op)
val bt = ByteArray(1024 * 8)
while (true) {
val len = bis.read(bt)
if (len == -1) {
break
} else {
bos.write(bt, 0, len)
}
}
bis.close()
bos.close()
} else if (src.isDirectory) {
val files = src.listFiles()
tar.mkdirs()
for (file in files) {
copy(file.absoluteFile, File(tar.absoluteFile, file.name))
}
}
return true
} catch (e: Exception) {
return false
}
}
/**
* 移动文件或目录
*/
fun move(src: String, tar: String): Boolean {
return move(File(src), File(tar))
}
/**
* 移动文件或目录
*/
fun move(src: File, tar: File): Boolean {
return rename(src, tar)
}
/**
* 文件重命名
*/
fun rename(oldPath: String, newPath: String): Boolean {
return rename(File(oldPath), File(newPath))
}
/**
* 文件重命名
*/
fun rename(src: File, tar: File): Boolean {
return src.renameTo(tar)
}
/**
* 读取文本文件, 失败将返回空串
*/
@JvmOverloads
fun readText(filepath: String, charset: String = "utf-8"): String {
try {
val data = readBytes(filepath)
if (data != null) {
return String(data, Charset.forName(charset)).trim { it <= ' ' }
}
} catch (ignored: UnsupportedEncodingException) {
}
return ""
}
/**
* 读取文件内容, 失败将返回空串
*/
fun readBytes(filepath: String): ByteArray? {
var fis: FileInputStream? = null
try {
fis = FileInputStream(filepath)
val baos = ByteArrayOutputStream()
val buffer = ByteArray(1024)
while (true) {
val len = fis.read(buffer, 0, buffer.size)
if (len == -1) {
break
} else {
baos.write(buffer, 0, len)
}
}
val data = baos.toByteArray()
baos.close()
return data
} catch (e: IOException) {
return null
} finally {
closeSilently(fis)
}
}
/**
* 保存文本内容
*/
@JvmOverloads
fun writeText(filepath: String, content: String, charset: String = "utf-8"): Boolean {
try {
writeBytes(filepath, content.toByteArray(charset(charset)))
return true
} catch (e: UnsupportedEncodingException) {
return false
}
}
/**
* 保存文件内容
*/
fun writeBytes(filepath: String, data: ByteArray): Boolean {
val file = File(filepath)
var fos: FileOutputStream? = null
try {
if (!file.exists()) {
file.parentFile.mkdirs()
file.createNewFile()
}
fos = FileOutputStream(filepath)
fos.write(data)
return true
} catch (e: IOException) {
return false
} finally {
closeSilently(fos)
}
}
/**
* 追加文本内容
*/
fun appendText(path: String, content: String): Boolean {
val file = File(path)
var writer: FileWriter? = null
try {
if (!file.exists()) {
file.createNewFile()
}
writer = FileWriter(file, true)
writer.write(content)
return true
} catch (e: IOException) {
return false
} finally {
closeSilently(writer)
}
}
/**
* 获取文件大小
*/
fun getLength(path: String): Long {
val file = File(path)
return if (!file.isFile || !file.exists()) {
0
} else file.length()
}
/**
* 获取文件或网址的名称包括后缀
*/
fun getName(pathOrUrl: String?): String {
if (pathOrUrl == null) {
return ""
}
val pos = pathOrUrl.lastIndexOf('/')
return if (0 <= pos) {
pathOrUrl.substring(pos + 1)
} else {
System.currentTimeMillis().toString() + "." + getExtension(pathOrUrl)
}
}
/**
* 获取文件名不包括扩展名
*/
fun getNameExcludeExtension(path: String): String {
try {
var fileName = File(path).name
val lastIndexOf = fileName.lastIndexOf(".")
if (lastIndexOf != -1) {
fileName = fileName.substring(0, lastIndexOf)
}
return fileName
} catch (e: Exception) {
return ""
}
}
/**
* 获取格式化后的文件大小
*/
fun getSize(path: String): String {
val fileSize = getLength(path)
return ConvertUtils.toFileSizeString(fileSize)
}
/**
* 获取文件后缀,不包括.
*/
fun getExtension(pathOrUrl: String): String {
val dotPos = pathOrUrl.lastIndexOf('.')
return if (0 <= dotPos) {
pathOrUrl.substring(dotPos + 1)
} else {
"ext"
}
}
/**
* 获取文件的MIME类型
*/
fun getMimeType(pathOrUrl: String): String {
val ext = getExtension(pathOrUrl)
val map = MimeTypeMap.getSingleton()
return map.getMimeTypeFromExtension(ext) ?: "*/*"
}
/**
* 获取格式化后的文件/目录创建或最后修改时间
*/
@JvmOverloads
fun getDateTime(path: String, format: String = "yyyy年MM月dd日HH:mm"): String {
val file = File(path)
return getDateTime(file, format)
}
/**
* 获取格式化后的文件/目录创建或最后修改时间
*/
fun getDateTime(file: File, format: String): String {
val cal = Calendar.getInstance()
cal.timeInMillis = file.lastModified()
return SimpleDateFormat(format, Locale.PRC).format(cal.time)
}
/**
* 比较两个文件的最后修改时间
*/
fun compareLastModified(path1: String, path2: String): Int {
val stamp1 = File(path1).lastModified()
val stamp2 = File(path2).lastModified()
return if (stamp1 > stamp2) {
1
} else if (stamp1 < stamp2) {
-1
} else {
0
}
}
/**
* 创建多级别的目录
*/
fun makeDirs(path: String): Boolean {
return makeDirs(File(path))
}
/**
* 创建多级别的目录
*/
fun makeDirs(file: File): Boolean {
return file.mkdirs()
}
class SortByExtension : Comparator<File> {
override fun compare(f1: File?, f2: File?): Int {
return if (f1 == null || f2 == null) {
if (f1 == null) {
-1
} else {
1
}
} else {
if (f1.isDirectory && f2.isFile) {
-1
} else if (f1.isFile && f2.isDirectory) {
1
} else {
f1.name.compareTo(f2.name, ignoreCase = true)
}
}
}
}
class SortByName : Comparator<File> {
private var caseSensitive: Boolean = false
constructor(caseSensitive: Boolean) {
this.caseSensitive = caseSensitive
}
constructor() {
this.caseSensitive = false
}
override fun compare(f1: File?, f2: File?): Int {
if (f1 == null || f2 == null) {
return if (f1 == null) {
-1
} else {
1
}
} else {
return if (f1.isDirectory && f2.isFile) {
-1
} else if (f1.isFile && f2.isDirectory) {
1
} else {
val s1 = f1.name
val s2 = f2.name
if (caseSensitive) {
s1.compareTo(s2)
} else {
s1.compareTo(s2, ignoreCase = true)
}
}
}
}
}
class SortBySize : Comparator<File> {
override fun compare(f1: File?, f2: File?): Int {
return if (f1 == null || f2 == null) {
if (f1 == null) {
-1
} else {
1
}
} else {
if (f1.isDirectory && f2.isFile) {
-1
} else if (f1.isFile && f2.isDirectory) {
1
} else {
if (f1.length() < f2.length()) {
-1
} else {
1
}
}
}
}
}
class SortByTime : Comparator<File> {
override fun compare(f1: File?, f2: File?): Int {
return if (f1 == null || f2 == null) {
if (f1 == null) {
-1
} else {
1
}
} else {
if (f1.isDirectory && f2.isFile) {
-1
} else if (f1.isFile && f2.isDirectory) {
1
} else {
if (f1.lastModified() > f2.lastModified()) {
-1
} else {
1
}
}
}
}
}
}

@ -11,10 +11,12 @@ import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.VMBaseFragment
import io.legado.app.constant.BookType
import io.legado.app.constant.Bus
import io.legado.app.data.entities.Book
import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.accentColor
import io.legado.app.ui.audio.AudioPlayActivity
import io.legado.app.ui.book.info.BookInfoActivity
import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.main.MainViewModel
@ -84,18 +86,20 @@ class BooksFragment : VMBaseFragment<BooksViewModel>(R.layout.fragment_books),
-3 -> App.db.bookDao().observeAudio()
else -> App.db.bookDao().observeByGroup(groupId)
}
bookshelfLiveData?.observe(
this,
Observer {
val diffResult =
DiffUtil.calculateDiff(BooksDiffCallBack(booksAdapter.getItems(), it))
booksAdapter.setItems(it, false)
diffResult.dispatchUpdatesTo(booksAdapter)
})
bookshelfLiveData?.observe(this, Observer {
val diffResult =
DiffUtil.calculateDiff(BooksDiffCallBack(booksAdapter.getItems(), it))
booksAdapter.setItems(it, false)
diffResult.dispatchUpdatesTo(booksAdapter)
})
}
override fun open(book: Book) {
context?.startActivity<ReadBookActivity>(Pair("bookUrl", book.bookUrl))
when (book.type) {
BookType.audio ->
context?.startActivity<AudioPlayActivity>(Pair("bookUrl", book.bookUrl))
else -> context?.startActivity<ReadBookActivity>(Pair("bookUrl", book.bookUrl))
}
}
override fun openBookInfo(book: Book) {

@ -122,7 +122,7 @@ class RssArticlesActivity : VMBaseActivity<RssArticlesViewModel>(R.layout.activi
viewModel.read(rssArticle)
startActivity<ReadRssActivity>(
Pair("origin", rssArticle.origin),
Pair("title", rssArticle.title)
Pair("link", rssArticle.link)
)
}
}

@ -20,13 +20,13 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application) {
fun initData(intent: Intent) {
execute {
val origin = intent.getStringExtra("origin")
val title = intent.getStringExtra("title")
val link = intent.getStringExtra("link")
val rssSource = App.db.rssSourceDao().getByKey(origin)
rssSource?.let {
rssSourceLiveData.postValue(it)
}
if (origin != null && title != null) {
App.db.rssArticleDao().get(origin, title)?.let { rssArticle ->
if (origin != null && link != null) {
App.db.rssArticleDao().get(origin, link)?.let { rssArticle ->
rssArticleLiveData.postValue(rssArticle)
if (!rssArticle.description.isNullOrBlank()) {
contentLiveData.postValue(rssArticle.description)

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/tool_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_path"
android:layout_width="match_parent"
android:layout_height="24dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:background="@color/background"
android:elevation="5dp" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_file"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="@+id/tv_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:padding="5dp">
<ImageView
android:id="@+id/image_view"
android:layout_width="24dp"
android:layout_height="24dp"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical" />
</LinearLayout>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true">
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical" />
<ImageView
android:id="@+id/image_view"
android:layout_width="20dp"
android:layout_height="match_parent"
tools:ignore="ContentDescription" />
</LinearLayout>

@ -11,6 +11,7 @@
android:layout_height="36dp"
android:layout_margin="6dp"
android:layout_gravity="center"
android:visibility="gone"
app:loading_width="2dp" />
<TextView
@ -18,6 +19,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp"
android:background="?attr/selectableItemBackground"

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_ok"
android:title="@string/ok"
android:visible="false"
app:showAsAction="always" />
</menu>

@ -539,5 +539,8 @@
<string name="dur_pos">当前位置:</string>
<string name="precision_search">精准搜索</string>
<string name="service_starting">正在启动服务</string>
<string name="empty"></string>
<string name="file_chooser">文件选择</string>
<string name="folder_chooser">文件夹选择</string>
</resources>

Loading…
Cancel
Save