diff --git a/app/src/main/java/io/legado/app/constant/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt new file mode 100644 index 000000000..11480d669 --- /dev/null +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -0,0 +1,8 @@ +package io.legado.app.constant + +object PreferKey { + + const val downloadPath = "downloadPath" + + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt b/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt index a38fa15d9..dff07d1ab 100644 --- a/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt @@ -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> diff --git a/app/src/main/java/io/legado/app/data/entities/RssArticle.kt b/app/src/main/java/io/legado/app/data/entities/RssArticle.kt index e3810036f..b44949155 100644 --- a/app/src/main/java/io/legado/app/data/entities/RssArticle.kt +++ b/app/src/main/java/io/legado/app/data/entities/RssArticle.kt @@ -6,7 +6,7 @@ import androidx.room.Ignore @Entity( tableName = "rssArticles", - primaryKeys = ["origin", "title"] + primaryKeys = ["origin", "link"] ) data class RssArticle( var origin: String = "", diff --git a/app/src/main/java/io/legado/app/help/BookHelp.kt b/app/src/main/java/io/legado/app/help/BookHelp.kt index e198a316f..53eae7501 100644 --- a/app/src/main/java/io/legado/app/help/BookHelp.kt +++ b/app/src/main/java/io/legado/app/help/BookHelp.kt @@ -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 } diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt index 33658de3c..c5b9423df 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt @@ -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)) { diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt index 4547594e6..c731a4778 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt @@ -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 diff --git a/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt b/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt index 23e2dd03b..3fd5a8965 100644 --- a/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt +++ b/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt @@ -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(R.layout.activity_audio_play) { override val viewModel: AudioPlayViewModel get() = getViewModel(AudioPlayViewModel::class.java) override fun onActivityCreated(savedInstanceState: Bundle?) { - + setSupportActionBar(toolbar) + viewModel.initData(intent) } diff --git a/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt b/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt index aebf4da98..9367b5922 100644 --- a/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt @@ -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() + val chapterListFinish = MutableLiveData() + 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) -> Unit)? = null + ) { + execute { + webBook?.getBookInfo(book, this) + ?.onSuccess { + loadChapterList(book, changeDruChapterIndex) + } + } + } + + private fun loadChapterList( + book: Book, + changeDruChapterIndex: ((chapters: List) -> 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) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/config/ConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/ConfigFragment.kt index 5269ac291..3d5c43896 100644 --- a/app/src/main/java/io/legado/app/ui/config/ConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/ConfigFragment.kt @@ -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(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) + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt b/app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt index f3fce06f9..6899a388d 100644 --- a/app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt +++ b/app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt @@ -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? = 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) {} + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/filechooser/FilePickerIcon.java b/app/src/main/java/io/legado/app/ui/filechooser/FilePickerIcon.java new file mode 100644 index 000000000..4ab481938 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/filechooser/FilePickerIcon.java @@ -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}; + +} diff --git a/app/src/main/java/io/legado/app/ui/filechooser/adapter/FileAdapter.kt b/app/src/main/java/io/legado/app/ui/filechooser/adapter/FileAdapter.kt new file mode 100644 index 000000000..a34c705b9 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/filechooser/adapter/FileAdapter.kt @@ -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(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() + 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? = 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) { + 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? + /** + * 是否仅仅读取目录 + */ + val isOnlyListDir: Boolean + /** + * 是否显示返回主目录 + */ + var isShowHomeDir: Boolean + /** + * 是否显示返回上一级 + */ + var isShowUpDir: Boolean + /** + * 是否显示隐藏的目录(以“.”开头) + */ + var isShowHideDir: Boolean + } + + companion object { + const val DIR_ROOT = "." + const val DIR_PARENT = ".." + } + +} + diff --git a/app/src/main/java/io/legado/app/ui/filechooser/adapter/PathAdapter.kt b/app/src/main/java/io/legado/app/ui/filechooser/adapter/PathAdapter.kt new file mode 100644 index 000000000..afa8ab4e6 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/filechooser/adapter/PathAdapter.kt @@ -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(context, R.layout.item_path_filepicker) { + private val paths = LinkedList() + 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) { + 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" + } +} diff --git a/app/src/main/java/io/legado/app/ui/filechooser/entity/FileItem.kt b/app/src/main/java/io/legado/app/ui/filechooser/entity/FileItem.kt new file mode 100644 index 000000000..bf4a00229 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/filechooser/entity/FileItem.kt @@ -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 +} diff --git a/app/src/main/java/io/legado/app/ui/filechooser/entity/JavaBean.kt b/app/src/main/java/io/legado/app/ui/filechooser/entity/JavaBean.kt new file mode 100644 index 000000000..33760dce6 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/filechooser/entity/JavaBean.kt @@ -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() + 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 + } + +} diff --git a/app/src/main/java/io/legado/app/ui/filechooser/utils/ConvertUtils.kt b/app/src/main/java/io/legado/app/ui/filechooser/utils/ConvertUtils.kt new file mode 100644 index 000000000..25ad9d56b --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/filechooser/utils/ConvertUtils.kt @@ -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, 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() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/filechooser/utils/FileUtils.kt b/app/src/main/java/io/legado/app/ui/filechooser/utils/FileUtils.kt new file mode 100644 index 000000000..71e838cae --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/filechooser/utils/FileUtils.kt @@ -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 + +/** + * 文件处理 + * + * + * + * @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? = null, @SortType sortType: Int = BY_NAME_ASC + ): Array { + var excludeDirs = excludeDirs + val dirList = ArrayList() + 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? = null + ): Array? { + val dirs: Array? + val files: Array? = if (allowExtensions == null) { + listFiles(startDirPath) + } else { + listFiles(startDirPath, allowExtensions) + } + val dirsAndFiles: Array + 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 { + val fileList = ArrayList() + 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): Array? { + val file = File(startDirPath) + return file.listFiles { dir, name -> + //返回当前目录所有以某些扩展名结尾的文件 + val extension = getExtension(name) + allowExtensions.contentDeepToString().contains(extension) + } + } + + /** + * 列出指定目录下的所有文件 + */ + fun listFiles(startDirPath: String, allowExtension: String?): Array? { + 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 { + + 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 { + 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 { + + 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 { + + 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 + } + } + } + } + + } + +} + diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BooksFragment.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BooksFragment.kt index 3a4657dbf..2bef1ad1d 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BooksFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BooksFragment.kt @@ -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(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(Pair("bookUrl", book.bookUrl)) + when (book.type) { + BookType.audio -> + context?.startActivity(Pair("bookUrl", book.bookUrl)) + else -> context?.startActivity(Pair("bookUrl", book.bookUrl)) + } } override fun openBookInfo(book: Book) { diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt index 6e3ac4318..8e32ad867 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt @@ -122,7 +122,7 @@ class RssArticlesActivity : VMBaseActivity(R.layout.activi viewModel.read(rssArticle) startActivity( Pair("origin", rssArticle.origin), - Pair("title", rssArticle.title) + Pair("link", rssArticle.link) ) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt index a8c0d2963..c50127ebd 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt @@ -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) diff --git a/app/src/main/res/layout/dialog_file_chooser.xml b/app/src/main/res/layout/dialog_file_chooser.xml new file mode 100644 index 000000000..91ab61537 --- /dev/null +++ b/app/src/main/res/layout/dialog_file_chooser.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_file_filepicker.xml b/app/src/main/res/layout/item_file_filepicker.xml new file mode 100644 index 000000000..fb4626dac --- /dev/null +++ b/app/src/main/res/layout/item_file_filepicker.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_path_filepicker.xml b/app/src/main/res/layout/item_path_filepicker.xml new file mode 100644 index 000000000..c6c47c49e --- /dev/null +++ b/app/src/main/res/layout/item_path_filepicker.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_load_more.xml b/app/src/main/res/layout/view_load_more.xml index a55372d30..996f7dd6c 100644 --- a/app/src/main/res/layout/view_load_more.xml +++ b/app/src/main/res/layout/view_load_more.xml @@ -11,6 +11,7 @@ android:layout_height="36dp" android:layout_margin="6dp" android:layout_gravity="center" + android:visibility="gone" app:loading_width="2dp" /> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6314982bf..7409c4ec4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -539,5 +539,8 @@ 当前位置: 精准搜索 正在启动服务 + + 文件选择 + 文件夹选择