备份/恢复路径支持自定义

master
fengyuecanzhu 3 years ago
parent 86385aaf90
commit c12df80004
  1. 3
      .idea/misc.xml
  2. 2
      app/src/main/java/xyz/fycz/myreader/model/audio/ReadAloudService.java
  3. 2
      app/src/main/java/xyz/fycz/myreader/model/storage/Backup.kt
  4. 167
      app/src/main/java/xyz/fycz/myreader/model/storage/BackupRestoreUi.kt
  5. 24
      app/src/main/java/xyz/fycz/myreader/ui/activity/MoreSettingActivity.java
  6. 20
      app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java
  7. 2
      app/src/main/java/xyz/fycz/myreader/util/SharedPreUtils.java
  8. 1
      app/src/main/java/xyz/fycz/myreader/util/help/JsExtensions.java
  9. 4
      app/src/main/java/xyz/fycz/myreader/webapi/crawler/source/find/ThirdFindCrawler.java
  10. 246
      app/src/main/java/xyz/fycz/myreader/widget/filepicker/adapter/FileAdapter.java
  11. 103
      app/src/main/java/xyz/fycz/myreader/widget/filepicker/adapter/PathAdapter.java
  12. 30
      app/src/main/java/xyz/fycz/myreader/widget/filepicker/drawable/StateBaseDrawable.java
  13. 25
      app/src/main/java/xyz/fycz/myreader/widget/filepicker/drawable/StateColorDrawable.java
  14. 58
      app/src/main/java/xyz/fycz/myreader/widget/filepicker/entity/FileItem.java
  15. 52
      app/src/main/java/xyz/fycz/myreader/widget/filepicker/entity/JavaBean.java
  16. 283
      app/src/main/java/xyz/fycz/myreader/widget/filepicker/icons/FilePickerIcon.java
  17. 238
      app/src/main/java/xyz/fycz/myreader/widget/filepicker/picker/FilePicker.java
  18. 308
      app/src/main/java/xyz/fycz/myreader/widget/filepicker/popup/BasicPopup.java
  19. 407
      app/src/main/java/xyz/fycz/myreader/widget/filepicker/popup/ConfirmPopup.java
  20. 617
      app/src/main/java/xyz/fycz/myreader/widget/filepicker/util/ConvertUtils.java
  21. 226
      app/src/main/java/xyz/fycz/myreader/widget/filepicker/util/DateUtils.java
  22. 783
      app/src/main/java/xyz/fycz/myreader/widget/filepicker/util/FileUtils.java
  23. 71
      app/src/main/java/xyz/fycz/myreader/widget/filepicker/util/ScreenUtils.java
  24. 161
      app/src/main/java/xyz/fycz/myreader/widget/filepicker/util/StorageUtils.java
  25. 54
      app/src/main/res/layout/activity_more_setting.xml
  26. 22
      app/src/main/res/layout/item_file_filepicker.xml
  27. 22
      app/src/main/res/layout/item_path_filepicker.xml
  28. 35
      app/src/main/res/layout/view_file_picker.xml
  29. 8
      app/src/main/res/values/strings.xml

@ -3,7 +3,8 @@
<component name="DesignSurface"> <component name="DesignSurface">
<option name="filePathToZoomLevelMap"> <option name="filePathToZoomLevelMap">
<map> <map>
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_more_setting.xml" value="0.12132725430597771" /> <entry key="..\:/android/FYReader/app/src/main/res/layout/activity_more_setting.xml" value="0.264" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/view_file_picker.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/menu/menu_book.xml" value="0.13703703703703704" /> <entry key="..\:/android/FYReader/app/src/main/res/menu/menu_book.xml" value="0.13703703703703704" />
</map> </map>
</option> </option>

@ -208,7 +208,7 @@ public class ReadAloudService extends Service {
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
running = true; running = true;
preference = SharedPreUtils.getInstance().getSharedReadable(); preference = SharedPreUtils.getInstance().getSharedPreferences();
fadeTts = preference.getBoolean("fadeTTS", false); fadeTts = preference.getBoolean("fadeTTS", false);
dsRunnable = this::doDs; dsRunnable = this::doDs;
initBroadcastReceiver(); initBroadcastReceiver();

@ -27,7 +27,7 @@ object Backup {
val backupPath = App.getApplication().filesDir.absolutePath + File.separator + "backup" val backupPath = App.getApplication().filesDir.absolutePath + File.separator + "backup"
val defaultPath by lazy { val defaultPath: String by lazy {
APPCONST.BACKUP_FILE_DIR APPCONST.BACKUP_FILE_DIR
} }

@ -1,13 +1,20 @@
package xyz.fycz.myreader.model.storage package xyz.fycz.myreader.model.storage
import android.app.Activity import android.app.Activity
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.net.Uri import android.net.Uri
import android.text.TextUtils import android.text.TextUtils
import androidx.core.content.ContextCompat
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.kongzue.dialogx.dialogs.BottomMenu
import com.kongzue.dialogx.interfaces.OnMenuItemClickListener
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.SingleEmitter import io.reactivex.SingleEmitter
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import xyz.fycz.myreader.R
import xyz.fycz.myreader.application.App
import xyz.fycz.myreader.application.SysManager import xyz.fycz.myreader.application.SysManager
import xyz.fycz.myreader.base.observer.MySingleObserver import xyz.fycz.myreader.base.observer.MySingleObserver
import xyz.fycz.myreader.common.APPCONST import xyz.fycz.myreader.common.APPCONST
@ -15,6 +22,8 @@ import xyz.fycz.myreader.model.storage.WebDavHelp.getWebDavFileNames
import xyz.fycz.myreader.model.storage.WebDavHelp.showRestoreDialog import xyz.fycz.myreader.model.storage.WebDavHelp.showRestoreDialog
import xyz.fycz.myreader.util.SharedPreUtils import xyz.fycz.myreader.util.SharedPreUtils
import xyz.fycz.myreader.util.ToastUtils import xyz.fycz.myreader.util.ToastUtils
import xyz.fycz.myreader.util.utils.StoragePermissionUtils
import xyz.fycz.myreader.widget.filepicker.picker.FilePicker
import java.util.* import java.util.*
object BackupRestoreUi : Backup.CallBack, Restore.CallBack { object BackupRestoreUi : Backup.CallBack, Restore.CallBack {
@ -23,7 +32,7 @@ object BackupRestoreUi : Backup.CallBack, Restore.CallBack {
private const val restoreSelectRequestCode = 33 private const val restoreSelectRequestCode = 33
private fun getBackupPath(): String? { private fun getBackupPath(): String? {
return SharedPreUtils.getInstance().getString("backupPath", APPCONST.BACKUP_FILE_DIR) return SharedPreUtils.getInstance().getString("backupPath", null)
} }
private fun setBackupPath(path: String?) { private fun setBackupPath(path: String?) {
@ -54,8 +63,8 @@ object BackupRestoreUi : Backup.CallBack, Restore.CallBack {
fun backup(activity: Activity) { fun backup(activity: Activity) {
val backupPath = getBackupPath() val backupPath = getBackupPath()
if (backupPath.isNullOrEmpty()) { if (backupPath.isNullOrEmpty()) {
// selectBackupFolder(activity) selectBackupFolder(activity)
ToastUtils.showError("backupPath.isNullOrEmpty") // ToastUtils.showError("backupPath.isNullOrEmpty")
} else { } else {
if (backupPath.isContentPath()) { if (backupPath.isContentPath()) {
val uri = Uri.parse(backupPath) val uri = Uri.parse(backupPath)
@ -63,32 +72,27 @@ object BackupRestoreUi : Backup.CallBack, Restore.CallBack {
if (doc?.canWrite() == true) { if (doc?.canWrite() == true) {
Backup.backup(activity, backupPath, this) Backup.backup(activity, backupPath, this)
} else { } else {
// selectBackupFolder(activity) selectBackupFolder(activity)
ToastUtils.showError("doc?.canWrite() != true") // ToastUtils.showError("doc?.canWrite() != true")
} }
} else { } else {
// backupUsePermission(activity) backupUsePermission(activity)
ToastUtils.showError("backupPath.isNotContentPath") // ToastUtils.showError("backupPath.isNotContentPath")
} }
} }
} }
/*private fun backupUsePermission(activity: Activity, path: String = Backup.defaultPath) { private fun backupUsePermission(activity: Activity, path: String = Backup.defaultPath) {
PermissionsCompat.Builder(activity) StoragePermissionUtils.request(activity) { _, _ ->
.addPermissions(*Permissions.Group.STORAGE) setBackupPath(path)
.rationale(R.string.get_storage_per) Backup.backup(activity, path, this)
.onGranted { }
setBackupPath(path)
Backup.backup(activity, path, this)
}
.request()
} }
fun selectBackupFolder(activity: Activity) { fun selectBackupFolder(activity: Activity) {
activity.alert { BottomMenu.show("选择文件夹", activity.resources.getStringArray(R.array.select_folder))
titleResource = R.string.select_folder .setOnMenuItemClickListener { _, _, which ->
items(activity.resources.getStringArray(R.array.select_folder).toList()) { _, index -> when (which) {
when (index) {
0 -> { 0 -> {
setBackupPath(Backup.defaultPath) setBackupPath(Backup.defaultPath)
backupUsePermission(activity) backupUsePermission(activity)
@ -100,27 +104,23 @@ object BackupRestoreUi : Backup.CallBack, Restore.CallBack {
activity.startActivityForResult(intent, backupSelectRequestCode) activity.startActivityForResult(intent, backupSelectRequestCode)
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
e.printStackTrace() e.printStackTrace()
activity.toast(e.localizedMessage ?: "ERROR") ToastUtils.showError(e.localizedMessage ?: "ERROR")
} }
} }
2 -> { 2 -> {
PermissionsCompat.Builder(activity) StoragePermissionUtils.request(activity) { _, _ ->
.addPermissions(*Permissions.Group.STORAGE) selectBackupFolderApp(activity, false)
.rationale(R.string.get_storage_per) }
.onGranted {
selectBackupFolderApp(activity, false)
}
.request()
} }
} }
} false
}.show() }.setCancelButton(R.string.cancel)
} }
private fun selectBackupFolderApp(activity: Activity, isRestore: Boolean) { private fun selectBackupFolderApp(activity: Activity, isRestore: Boolean) {
val picker = FilePicker(activity, FilePicker.DIRECTORY) val picker = FilePicker(activity, FilePicker.DIRECTORY)
picker.setBackgroundColor(ContextCompat.getColor(activity, R.color.background)) picker.setBackgroundColor(ContextCompat.getColor(activity, R.color.colorBackground))
picker.setTopBackgroundColor(ContextCompat.getColor(activity, R.color.background)) picker.setTopBackgroundColor(ContextCompat.getColor(activity, R.color.colorBackground))
picker.setItemHeight(30) picker.setItemHeight(30)
picker.setOnFilePickListener { currentPath: String -> picker.setOnFilePickListener { currentPath: String ->
setBackupPath(currentPath) setBackupPath(currentPath)
@ -131,56 +131,51 @@ object BackupRestoreUi : Backup.CallBack, Restore.CallBack {
} }
} }
picker.show() picker.show()
}*/ }
fun restore(activity: Activity) { fun restore(activity: Activity) {
Single.create { emitter: SingleEmitter<ArrayList<String>?> -> Single.create { emitter: SingleEmitter<ArrayList<String>?> ->
emitter.onSuccess(getWebDavFileNames()) emitter.onSuccess(getWebDavFileNames())
}.subscribeOn(Schedulers.io()) }.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(object : MySingleObserver<ArrayList<String>?>() { .subscribe(object : MySingleObserver<ArrayList<String>?>() {
override fun onSuccess(strings: ArrayList<String>) { override fun onSuccess(strings: ArrayList<String>) {
if (!showRestoreDialog(activity, strings, this@BackupRestoreUi)) { if (!showRestoreDialog(activity, strings, this@BackupRestoreUi)) {
val path = getBackupPath() val path = getBackupPath()
if (TextUtils.isEmpty(path)) { if (TextUtils.isEmpty(path)) {
//selectRestoreFolder(activity) selectRestoreFolder(activity)
ToastUtils.showError("TextUtils.isEmpty(path)") // ToastUtils.showError("TextUtils.isEmpty(path)")
} else { } else {
if (path.isContentPath()) { if (path.isContentPath()) {
val uri = Uri.parse(path) val uri = Uri.parse(path)
val doc = DocumentFile.fromTreeUri(activity, uri) val doc = DocumentFile.fromTreeUri(activity, uri)
if (doc?.canWrite() == true) { if (doc?.canWrite() == true) {
Restore.restore(activity, Uri.parse(path), this@BackupRestoreUi) Restore.restore(activity, Uri.parse(path), this@BackupRestoreUi)
} else {
// selectRestoreFolder(activity)
ToastUtils.showError("doc?.canWrite() != true")
}
} else { } else {
// restoreUsePermission(activity) selectRestoreFolder(activity)
ToastUtils.showError("path.isNotContentPath") // ToastUtils.showError("doc?.canWrite() != true")
} }
} else {
restoreUsePermission(activity)
// ToastUtils.showError("path.isNotContentPath")
} }
} }
} }
}) }
})
} }
/*private fun restoreUsePermission(activity: Activity, path: String = Backup.defaultPath) { private fun restoreUsePermission(activity: Activity, path: String = Backup.defaultPath) {
PermissionsCompat.Builder(activity) StoragePermissionUtils.request(activity) { _, _ ->
.addPermissions(*Permissions.Group.STORAGE) setBackupPath(path)
.rationale(R.string.get_storage_per) Restore.restore(path, this)
.onGranted { }
setBackupPath(path)
Restore.restore(path, this)
}
.request()
} }
private fun selectRestoreFolder(activity: Activity) { private fun selectRestoreFolder(activity: Activity) {
activity.alert { BottomMenu.show("选择文件夹", activity.resources.getStringArray(R.array.select_folder))
titleResource = R.string.select_folder .setOnMenuItemClickListener { _, _, which ->
items(activity.resources.getStringArray(R.array.select_folder).toList()) { _, index -> when (which) {
when (index) {
0 -> restoreUsePermission(activity) 0 -> restoreUsePermission(activity)
1 -> { 1 -> {
try { try {
@ -189,47 +184,43 @@ object BackupRestoreUi : Backup.CallBack, Restore.CallBack {
activity.startActivityForResult(intent, restoreSelectRequestCode) activity.startActivityForResult(intent, restoreSelectRequestCode)
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
e.printStackTrace() e.printStackTrace()
activity.toast(e.localizedMessage ?: "ERROR") ToastUtils.showError(e.localizedMessage ?: "ERROR")
} }
} }
2 -> { 2 -> {
PermissionsCompat.Builder(activity) StoragePermissionUtils.request(activity) { _, _ ->
.addPermissions(*Permissions.Group.STORAGE) selectBackupFolderApp(activity, true)
.rationale(R.string.get_storage_per) }
.onGranted {
selectBackupFolderApp(activity, true)
}
.request()
} }
} }
} false
}.show() }.setCancelButton(R.string.cancel)
}*/ }
/*fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) { when (requestCode) {
backupSelectRequestCode -> if (resultCode == RESULT_OK) { backupSelectRequestCode -> if (resultCode == RESULT_OK) {
data?.data?.let { uri -> data?.data?.let { uri ->
MApplication.getInstance().contentResolver.takePersistableUriPermission( App.getmContext().contentResolver.takePersistableUriPermission(
uri, uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
) )
setBackupPath(uri.toString()) setBackupPath(uri.toString())
Backup.backup(MApplication.getInstance(), uri.toString(), this) Backup.backup(App.getmContext(), uri.toString(), this)
} }
} }
restoreSelectRequestCode -> if (resultCode == RESULT_OK) { restoreSelectRequestCode -> if (resultCode == RESULT_OK) {
data?.data?.let { uri -> data?.data?.let { uri ->
MApplication.getInstance().contentResolver.takePersistableUriPermission( App.getmContext().contentResolver.takePersistableUriPermission(
uri, uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
) )
setBackupPath(uri.toString()) setBackupPath(uri.toString())
Restore.restore(MApplication.getInstance(), uri, this) Restore.restore(App.getmContext(), uri, this)
} }
} }
} }
}*/ }
} }

@ -2,7 +2,9 @@ package xyz.fycz.myreader.ui.activity;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -12,6 +14,7 @@ import android.widget.ArrayAdapter;
import android.widget.NumberPicker; import android.widget.NumberPicker;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
@ -33,6 +36,9 @@ import xyz.fycz.myreader.databinding.ActivityMoreSettingBinding;
import xyz.fycz.myreader.entity.Setting; import xyz.fycz.myreader.entity.Setting;
import xyz.fycz.myreader.greendao.entity.Book; import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.service.BookService; import xyz.fycz.myreader.greendao.service.BookService;
import xyz.fycz.myreader.model.storage.Backup;
import xyz.fycz.myreader.model.storage.BackupRestoreUi;
import xyz.fycz.myreader.model.storage.BackupRestoreUiKt;
import xyz.fycz.myreader.ui.dialog.DialogCreator; import xyz.fycz.myreader.ui.dialog.DialogCreator;
import xyz.fycz.myreader.ui.dialog.MultiChoiceDialog; import xyz.fycz.myreader.ui.dialog.MultiChoiceDialog;
import xyz.fycz.myreader.ui.dialog.MyAlertDialog; import xyz.fycz.myreader.ui.dialog.MyAlertDialog;
@ -49,7 +55,7 @@ import static xyz.fycz.myreader.common.APPCONST.BOOK_CACHE_PATH;
* 阅读界面的更多设置 * 阅读界面的更多设置
*/ */
public class MoreSettingActivity extends BaseActivity { public class MoreSettingActivity extends BaseActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
private ActivityMoreSettingBinding binding; private ActivityMoreSettingBinding binding;
@ -148,6 +154,7 @@ public class MoreSettingActivity extends BaseActivity {
} else if (sortStyle == 2) { } else if (sortStyle == 2) {
binding.tvBookSort.setText(getString(R.string.book_name_sort)); binding.tvBookSort.setText(getString(R.string.book_name_sort));
} }
binding.tvBackupPath.setText(SharedPreUtils.getInstance().getString("backupPath", APPCONST.BACKUP_FILE_DIR));
if (isMatchChapter) { if (isMatchChapter) {
binding.rlMatchChapterSuitability.setVisibility(View.VISIBLE); binding.rlMatchChapterSuitability.setVisibility(View.VISIBLE);
} else { } else {
@ -158,6 +165,7 @@ public class MoreSettingActivity extends BaseActivity {
@Override @Override
protected void processLogic() { protected void processLogic() {
SharedPreUtils.getInstance().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
if (isWebDav) { if (isWebDav) {
binding.llWebdav.performClick(); binding.llWebdav.performClick();
} }
@ -339,6 +347,8 @@ public class MoreSettingActivity extends BaseActivity {
}); });
}); });
binding.llBackupPath.setOnClickListener(v -> BackupRestoreUi.INSTANCE.selectBackupFolder(this));
binding.rlAutoRefresh.setOnClickListener( binding.rlAutoRefresh.setOnClickListener(
(v) -> { (v) -> {
autoRefresh = !autoRefresh; autoRefresh = !autoRefresh;
@ -689,4 +699,16 @@ public class MoreSettingActivity extends BaseActivity {
setUpToolbar(); setUpToolbar();
} }
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if ("backupPath".equals(key)){
binding.tvBackupPath.setText(SharedPreUtils.getInstance().getString("backupPath", APPCONST.BACKUP_FILE_DIR));
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
BackupRestoreUi.INSTANCE.onActivityResult(requestCode, resultCode, data);
}
} }

@ -42,6 +42,7 @@ import xyz.fycz.myreader.greendao.service.BookService;
import xyz.fycz.myreader.model.backup.UserService; import xyz.fycz.myreader.model.backup.UserService;
import xyz.fycz.myreader.model.storage.Backup; import xyz.fycz.myreader.model.storage.Backup;
import xyz.fycz.myreader.model.storage.BackupRestoreUi; import xyz.fycz.myreader.model.storage.BackupRestoreUi;
import xyz.fycz.myreader.model.storage.BackupRestoreUiKt;
import xyz.fycz.myreader.model.storage.Restore; import xyz.fycz.myreader.model.storage.Restore;
import xyz.fycz.myreader.model.storage.WebDavHelp; import xyz.fycz.myreader.model.storage.WebDavHelp;
import xyz.fycz.myreader.ui.activity.AboutActivity; import xyz.fycz.myreader.ui.activity.AboutActivity;
@ -380,15 +381,15 @@ public class MineFragment extends BaseFragment {
ToastUtils.showWarring("当前书架无任何书籍,无法备份!"); ToastUtils.showWarring("当前书架无任何书籍,无法备份!");
return; return;
} }
StoragePermissionUtils.request(this, (permissions, all) -> { /*StoragePermissionUtils.request(this, (permissions, all) -> {
DialogCreator.createCommonDialog(getContext(), "确认备份吗?", "新备份会替换原有备份!", true, DialogCreator.createCommonDialog(getContext(), "确认备份吗?", "新备份会替换原有备份!", true,
(dialogInterface, i) -> { (dialogInterface, i) -> {
dialogInterface.dismiss(); dialogInterface.dismiss();
/*if (mBackupAndRestore.backup("localBackup")) { *//*if (mBackupAndRestore.backup("localBackup")) {
DialogCreator.createTipDialog(getContext(), "备份成功,备份文件路径:" + APPCONST.BACKUP_FILE_DIR); DialogCreator.createTipDialog(getContext(), "备份成功,备份文件路径:" + APPCONST.BACKUP_FILE_DIR);
} else { } else {
DialogCreator.createTipDialog(getContext(), "未给予储存权限,备份失败!"); DialogCreator.createTipDialog(getContext(), "未给予储存权限,备份失败!");
}*/ }*//*
Backup.INSTANCE.backup(App.getmContext(), APPCONST.BACKUP_FILE_DIR, new Backup.CallBack() { Backup.INSTANCE.backup(App.getmContext(), APPCONST.BACKUP_FILE_DIR, new Backup.CallBack() {
@Override @Override
public void backupSuccess() { public void backupSuccess() {
@ -401,18 +402,19 @@ public class MineFragment extends BaseFragment {
} }
}, false); }, false);
}, (dialogInterface, i) -> dialogInterface.dismiss()); }, (dialogInterface, i) -> dialogInterface.dismiss());
}); });*/
BackupRestoreUi.INSTANCE.backup(getActivity());
} }
/** /**
* 恢复 * 恢复
*/ */
private void restore() { private void restore() {
StoragePermissionUtils.request(this, (permissions, all) -> { /*StoragePermissionUtils.request(this, (permissions, all) -> {
DialogCreator.createCommonDialog(getContext(), "确认恢复吗?", "恢复书架会覆盖原有书架!", true, DialogCreator.createCommonDialog(getContext(), "确认恢复吗?", "恢复书架会覆盖原有书架!", true,
(dialogInterface, i) -> { (dialogInterface, i) -> {
dialogInterface.dismiss(); dialogInterface.dismiss();
/*if (mBackupAndRestore.restore("localBackup")) { *//*if (mBackupAndRestore.restore("localBackup")) {
mHandler.sendMessage(mHandler.obtainMessage(7)); mHandler.sendMessage(mHandler.obtainMessage(7));
// DialogCreator.createTipDialog(mMainActivity, // DialogCreator.createTipDialog(mMainActivity,
// "恢复成功!\n注意:本功能属于实验功能,书架恢复后,书籍初次加载时可能加载失败,返回重新加载即可!"); // "恢复成功!\n注意:本功能属于实验功能,书架恢复后,书籍初次加载时可能加载失败,返回重新加载即可!");
@ -420,7 +422,7 @@ public class MineFragment extends BaseFragment {
ToastUtils.showSuccess("书架恢复成功!"); ToastUtils.showSuccess("书架恢复成功!");
} else { } else {
DialogCreator.createTipDialog(getContext(), "未找到备份文件或未给予储存权限,恢复失败!"); DialogCreator.createTipDialog(getContext(), "未找到备份文件或未给予储存权限,恢复失败!");
}*/ }*//*
Restore.INSTANCE.restore(APPCONST.BACKUP_FILE_DIR, new Restore.CallBack() { Restore.INSTANCE.restore(APPCONST.BACKUP_FILE_DIR, new Restore.CallBack() {
@Override @Override
public void restoreSuccess() { public void restoreSuccess() {
@ -441,7 +443,8 @@ public class MineFragment extends BaseFragment {
} }
}); });
}, (dialogInterface, i) -> dialogInterface.dismiss()); }, (dialogInterface, i) -> dialogInterface.dismiss());
}); });*/
BackupRestoreUi.INSTANCE.restore(getActivity());
} }
/** /**
@ -542,6 +545,7 @@ public class MineFragment extends BaseFragment {
@Override @Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
BackupRestoreUi.INSTANCE.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
switch (requestCode) { switch (requestCode) {
/*case APPCONST.REQUEST_LOGIN: /*case APPCONST.REQUEST_LOGIN:

@ -34,7 +34,7 @@ public class SharedPreUtils {
return sInstance; return sInstance;
} }
public SharedPreferences getSharedReadable(){ public SharedPreferences getSharedPreferences(){
return sharedReadable; return sharedReadable;
} }

@ -32,6 +32,7 @@ import xyz.fycz.myreader.util.utils.MD5Utils;
import xyz.fycz.myreader.util.utils.OkHttpUtils; import xyz.fycz.myreader.util.utils.OkHttpUtils;
import xyz.fycz.myreader.util.utils.StringUtils; import xyz.fycz.myreader.util.utils.StringUtils;
@Keep @Keep
@SuppressWarnings({"unused"}) @SuppressWarnings({"unused"})
public interface JsExtensions { public interface JsExtensions {

@ -93,7 +93,9 @@ public class ThirdFindCrawler extends BaseFindCrawler {
children.add(findKindBean); children.add(findKindBean);
} }
} }
kindsMap.put(groupName, children); if (children.size() > 0) {
kindsMap.put(groupName, children);
}
} }
emitter.onNext(true); emitter.onNext(true);
} catch (Exception exception) { } catch (Exception exception) {

@ -0,0 +1,246 @@
package xyz.fycz.myreader.widget.filepicker.adapter;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.io.File;
import java.util.ArrayList;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.widget.filepicker.entity.FileItem;
import xyz.fycz.myreader.widget.filepicker.icons.FilePickerIcon;
import xyz.fycz.myreader.widget.filepicker.util.ConvertUtils;
import xyz.fycz.myreader.widget.filepicker.util.FileUtils;
public class FileAdapter extends RecyclerView.Adapter<FileAdapter.MyViewHolder> {
public static final String DIR_ROOT = ".";
public static final String DIR_PARENT = "..";
private ArrayList<FileItem> data = new ArrayList<>();
private String rootPath = null;
private String currentPath = null;
private String[] allowExtensions = null;//允许的扩展名
private boolean onlyListDir = false;//是否仅仅读取目录
private boolean showHomeDir = false;//是否显示返回主目录
private boolean showUpDir = true;//是否显示返回上一级
private boolean showHideDir = true;//是否显示隐藏的目录(以“.”开头)
private int itemHeight = 40;// dp
private Drawable homeIcon = null;
private Drawable upIcon = null;
private Drawable folderIcon = null;
private Drawable fileIcon = null;
private CallBack callBack;
public void setCallBack(CallBack callBack) {
this.callBack = callBack;
}
public FileItem getItem(int pos) {
return data.get(pos);
}
public String getCurrentPath() {
return currentPath;
}
public void setFileIcon(Drawable fileIcon) {
this.fileIcon = fileIcon;
}
public void setFolderIcon(Drawable folderIcon) {
this.folderIcon = folderIcon;
}
public void setHomeIcon(Drawable homeIcon) {
this.homeIcon = homeIcon;
}
public void setUpIcon(Drawable upIcon) {
this.upIcon = upIcon;
}
/**
* 允许的扩展名
*/
public void setAllowExtensions(String[] allowExtensions) {
this.allowExtensions = allowExtensions;
}
/**
* 是否仅仅读取目录
*/
public void setOnlyListDir(boolean onlyListDir) {
this.onlyListDir = onlyListDir;
}
public boolean isOnlyListDir() {
return onlyListDir;
}
/**
* 是否显示返回主目录
*/
public void setShowHomeDir(boolean showHomeDir) {
this.showHomeDir = showHomeDir;
}
public boolean isShowHomeDir() {
return showHomeDir;
}
/**
* 是否显示返回上一级
*/
public void setShowUpDir(boolean showUpDir) {
this.showUpDir = showUpDir;
}
public boolean isShowUpDir() {
return showUpDir;
}
/**
* 是否显示隐藏的目录.开头
*/
public void setShowHideDir(boolean showHideDir) {
this.showHideDir = showHideDir;
}
public boolean isShowHideDir() {
return showHideDir;
}
public void setItemHeight(int itemHeight) {
this.itemHeight = itemHeight;
}
public void loadData(String path) {
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());
}
ArrayList<FileItem> datas = new ArrayList<FileItem>();
if (rootPath == null) {
rootPath = path;
}
currentPath = path;
if (showHomeDir) {
//添加“返回主目录”
FileItem fileRoot = new FileItem();
fileRoot.setDirectory(true);
fileRoot.setIcon(homeIcon);
fileRoot.setName(DIR_ROOT);
fileRoot.setSize(0);
fileRoot.setPath(rootPath);
datas.add(fileRoot);
}
if (showUpDir && !path.equals("/")) {
//添加“返回上一级目录”
FileItem fileParent = new FileItem();
fileParent.setDirectory(true);
fileParent.setIcon(upIcon);
fileParent.setName(DIR_PARENT);
fileParent.setSize(0);
fileParent.setPath(new File(path).getParent());
datas.add(fileParent);
}
File[] files;
if (allowExtensions == null) {
if (onlyListDir) {
files = FileUtils.listDirs(currentPath);
} else {
files = FileUtils.listDirsAndFiles(currentPath);
}
} else {
if (onlyListDir) {
files = FileUtils.listDirs(currentPath, allowExtensions);
} else {
files = FileUtils.listDirsAndFiles(currentPath, allowExtensions);
}
}
if (files != null) {
for (File file : files) {
if (!showHideDir && file.getName().startsWith(".")) {
continue;
}
FileItem fileItem = new FileItem();
boolean isDirectory = file.isDirectory();
fileItem.setDirectory(isDirectory);
if (isDirectory) {
fileItem.setIcon(folderIcon);
fileItem.setSize(0);
} else {
fileItem.setIcon(fileIcon);
fileItem.setSize(file.length());
}
fileItem.setName(file.getName());
fileItem.setPath(file.getAbsolutePath());
datas.add(fileItem);
}
}
data.clear();
data.addAll(datas);
notifyDataSetChanged();
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_file_filepicker, parent, false));
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, final int position) {
FileItem fileItem = data.get(position);
holder.imageView.setImageDrawable(fileItem.getIcon());
holder.textView.setText(fileItem.getName());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (callBack != null) {
callBack.onFileClick(position);
}
}
});
}
@Override
public int getItemCount() {
return data.size();
}
class MyViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
TextView textView;
MyViewHolder(@NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.image_view);
textView = itemView.findViewById(R.id.text_view);
}
}
public interface CallBack {
void onFileClick(int position);
}
}

@ -0,0 +1,103 @@
package xyz.fycz.myreader.widget.filepicker.adapter;
import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.Collections;
import java.util.LinkedList;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.widget.filepicker.icons.FilePickerIcon;
import xyz.fycz.myreader.widget.filepicker.util.ConvertUtils;
public class PathAdapter extends RecyclerView.Adapter<PathAdapter.MyViewHolder> {
private static final String ROOT_HINT = "SD";
private LinkedList<String> paths = new LinkedList<>();
private Drawable arrowIcon = null;
private String sdCardDirectory = Environment.getExternalStorageDirectory().getAbsolutePath();
private CallBack callBack;
public void setCallBack(CallBack callBack) {
this.callBack = callBack;
}
public String getItem(int position) {
StringBuilder tmp = new StringBuilder(sdCardDirectory + "/");
//忽略根目录
if (position == 0) {
return tmp.toString();
}
for (int i = 1; i <= position; i++) {
tmp.append(paths.get(i)).append("/");
}
return tmp.toString();
}
public void setArrowIcon(Drawable arrowIcon) {
this.arrowIcon = arrowIcon;
}
public void updatePath(String path) {
path = path.replace(sdCardDirectory, "");
if (arrowIcon == null) {
arrowIcon = ConvertUtils.toDrawable(FilePickerIcon.getARROW());
}
paths.clear();
if (!path.equals("/") && !path.equals("")) {
String[] tmps = path.substring(path.indexOf("/") + 1).split("/");
Collections.addAll(paths, tmps);
}
paths.addFirst(ROOT_HINT);
notifyDataSetChanged();
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_path_filepicker, parent, false));
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, final int position) {
holder.textView.setText(paths.get(position));
holder.imageView.setImageDrawable(arrowIcon);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (callBack != null) {
callBack.onPathClick(position);
}
}
});
}
@Override
public int getItemCount() {
return paths.size();
}
class MyViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
TextView textView;
MyViewHolder(@NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.image_view);
textView = itemView.findViewById(R.id.text_view);
}
}
public interface CallBack {
void onPathClick(int position);
}
}

@ -0,0 +1,30 @@
package xyz.fycz.myreader.widget.filepicker.drawable;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
/**
* 按下状态与普通状态下显示不同的图片或颜色
* <br />
* Author:李玉江[QQ:1032694760]
* DateTime:2017/01/01 05:30
* Builder:Android Studio
*/
public abstract class StateBaseDrawable extends StateListDrawable {
protected void addState(Drawable pressed) {
addState(new ColorDrawable(Color.TRANSPARENT), pressed);
}
protected void addState(Drawable normal, Drawable pressed) {
addState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled}, pressed);
addState(new int[]{android.R.attr.state_enabled, android.R.attr.state_focused}, pressed);
addState(new int[]{android.R.attr.state_enabled}, normal);
addState(new int[]{android.R.attr.state_focused}, pressed);
addState(new int[]{android.R.attr.state_window_focused}, normal);
addState(new int[]{}, normal);
}
}

@ -0,0 +1,25 @@
package xyz.fycz.myreader.widget.filepicker.drawable;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import androidx.annotation.ColorInt;
/**
* 按下状态与普通状态下显示不同的颜色
* <br />
* Author:李玉江[QQ:1032694760]
* DateTime:2017/01/01 05:30
* Builder:Android Studio
*/
public class StateColorDrawable extends StateBaseDrawable {
public StateColorDrawable(@ColorInt int pressedColor) {
this(Color.TRANSPARENT, pressedColor);
}
public StateColorDrawable(@ColorInt int normalColor, @ColorInt int pressedColor) {
addState(new ColorDrawable(normalColor), new ColorDrawable(pressedColor));
}
}

@ -0,0 +1,58 @@
package xyz.fycz.myreader.widget.filepicker.entity;
import android.graphics.drawable.Drawable;
/**
* 文件项信息
*
* @author 李玉江[QQ:1032694760]
* @since 2014-05-23 18:02
*/
public class FileItem extends JavaBean {
private Drawable icon;
private String name;
private String path = "/";
private long size = 0;
private boolean isDirectory = false;
public void setIcon(Drawable icon) {
this.icon = icon;
}
public Drawable getIcon() {
return icon;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
public boolean isDirectory() {
return isDirectory;
}
public void setDirectory(boolean isDirectory) {
this.isDirectory = isDirectory;
}
}

@ -0,0 +1,52 @@
package xyz.fycz.myreader.widget.filepicker.entity;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
/**
* JavaBean类
*
* @author 李玉江[QQ:1032694760]
* @since 2014-04-23 16:14
*/
public class JavaBean implements Serializable {
private static final long serialVersionUID = -6111323241670458039L;
/**
* 反射出所有字段值
*/
@Override
public String toString() {
ArrayList<Field> list = new ArrayList<>();
Class<?> clazz = getClass();
list.addAll(Arrays.asList(clazz.getDeclaredFields()));//得到自身的所有字段
StringBuilder sb = new StringBuilder();
while (clazz != Object.class) {
clazz = clazz.getSuperclass();//得到继承自父类的字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
int modifier = field.getModifiers();
if (Modifier.isPublic(modifier) || Modifier.isProtected(modifier)) {
list.add(field);
}
}
}
Field[] fields = list.toArray(new Field[0]);
for (Field field : fields) {
String fieldName = field.getName();
try {
Object obj = field.get(this);
sb.append(fieldName);
sb.append("=");
sb.append(obj);
sb.append("\n");
} catch (IllegalAccessException ignored) {
}
}
return sb.toString();
}
}

@ -0,0 +1,283 @@
package xyz.fycz.myreader.widget.filepicker.icons;
/**
* 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,238 @@
package xyz.fycz.myreader.widget.filepicker.picker;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.widget.filepicker.adapter.FileAdapter;
import xyz.fycz.myreader.widget.filepicker.adapter.PathAdapter;
import xyz.fycz.myreader.widget.filepicker.entity.FileItem;
import xyz.fycz.myreader.widget.filepicker.popup.ConfirmPopup;
import xyz.fycz.myreader.widget.filepicker.util.StorageUtils;
/**
* 文件目录选择器
*
* @author 李玉江[QQ:1032694760]
* @since 2015/9/29, 2017/01/01, 2017/01/08
*/
public class FilePicker extends ConfirmPopup<LinearLayout> implements FileAdapter.CallBack, PathAdapter.CallBack {
public static final int DIRECTORY = 0;
public static final int FILE = 1;
private String initPath;
private FileAdapter adapter = new FileAdapter();
private PathAdapter pathAdapter = new PathAdapter();
private TextView emptyView;
private OnFilePickListener onFilePickListener;
private int mode;
private CharSequence emptyHint = java.util.Locale.getDefault().getDisplayLanguage().contains("中文") ? "<空>" : "<Empty>";
@IntDef(value = {DIRECTORY, FILE})
@Retention(RetentionPolicy.SOURCE)
public @interface Mode {
}
/**
* @see #FILE
* @see #DIRECTORY
*/
public FilePicker(Activity activity, @Mode int mode) {
super(activity);
setHalfScreen(true);
try {
this.initPath = StorageUtils.getDownloadPath();
} catch (RuntimeException e) {
this.initPath = StorageUtils.getInternalRootPath(activity);
}
this.mode = mode;
adapter.setOnlyListDir(mode == DIRECTORY);
adapter.setShowHideDir(false);
adapter.setShowHomeDir(false);
adapter.setShowUpDir(false);
adapter.setCallBack(this);
pathAdapter.setCallBack(this);
}
@Override
@NonNull
protected LinearLayout makeCenterView() {
@SuppressLint("InflateParams") LinearLayout rootLayout = (LinearLayout) LayoutInflater.from(activity).inflate(R.layout.view_file_picker, null);
RecyclerView recyclerView = rootLayout.findViewById(R.id.rv_file);
recyclerView.addItemDecoration(new DividerItemDecoration(activity, LinearLayout.VERTICAL));
recyclerView.setLayoutManager(new LinearLayoutManager(activity));
recyclerView.setAdapter(adapter);
emptyView = rootLayout.findViewById(R.id.tv_empty);
RecyclerView pathView = rootLayout.findViewById(R.id.rv_path);
pathView.setLayoutManager(new LinearLayoutManager(activity, RecyclerView.HORIZONTAL, false));
pathView.setAdapter(pathAdapter);
return rootLayout;
}
public void setRootPath(String initPath) {
this.initPath = initPath;
}
public void setAllowExtensions(String[] allowExtensions) {
adapter.setAllowExtensions(allowExtensions);
}
public void setShowUpDir(boolean showUpDir) {
adapter.setShowUpDir(showUpDir);
}
public void setShowHomeDir(boolean showHomeDir) {
adapter.setShowHomeDir(showHomeDir);
}
public void setShowHideDir(boolean showHideDir) {
adapter.setShowHideDir(showHideDir);
}
public void setFileIcon(Drawable fileIcon) {
adapter.setFileIcon(fileIcon);
}
public void setFolderIcon(Drawable folderIcon) {
adapter.setFolderIcon(folderIcon);
}
public void setHomeIcon(Drawable homeIcon) {
adapter.setHomeIcon(homeIcon);
}
public void setUpIcon(Drawable upIcon) {
adapter.setUpIcon(upIcon);
}
public void setArrowIcon(Drawable arrowIcon) {
pathAdapter.setArrowIcon(arrowIcon);
}
public void setItemHeight(int itemHeight) {
adapter.setItemHeight(itemHeight);
}
public void setEmptyHint(CharSequence emptyHint) {
this.emptyHint = emptyHint;
}
@Override
protected void setContentViewBefore() {
boolean isPickFile = mode == FILE;
setCancelVisible(!isPickFile);
if (isPickFile) {
setSubmitText(activity.getString(android.R.string.cancel));
} else {
setSubmitText(activity.getString(android.R.string.ok));
}
}
@Override
protected void setContentViewAfter(View contentView) {
refreshCurrentDirPath(initPath);
}
@Override
protected void onSubmit() {
if (mode != FILE) {
String currentPath = adapter.getCurrentPath();
if (onFilePickListener != null) {
onFilePickListener.onFilePicked(currentPath);
}
}
}
@Override
public void dismiss() {
super.dismiss();
}
public FileAdapter getAdapter() {
return adapter;
}
public PathAdapter getPathAdapter() {
return pathAdapter;
}
public String getCurrentPath() {
return adapter.getCurrentPath();
}
/**
* 响应选择器的列表项点击事件
*/
@Override
public void onFileClick(int position) {
FileItem fileItem = adapter.getItem(position);
if (fileItem.isDirectory()) {
refreshCurrentDirPath(fileItem.getPath());
} else {
String clickPath = fileItem.getPath();
if (mode != DIRECTORY) {
dismiss();
if (onFilePickListener != null) {
onFilePickListener.onFilePicked(clickPath);
}
}
}
}
@Override
public void onPathClick(int position) {
refreshCurrentDirPath(pathAdapter.getItem(position));
}
private void refreshCurrentDirPath(String currentPath) {
if (currentPath.equals("/")) {
pathAdapter.updatePath("/");
} else {
pathAdapter.updatePath(currentPath);
}
adapter.loadData(currentPath);
int adapterCount = adapter.getItemCount();
if (adapter.isShowHomeDir()) {
adapterCount--;
}
if (adapter.isShowUpDir()) {
adapterCount--;
}
if (adapterCount < 1) {
emptyView.setVisibility(View.VISIBLE);
emptyView.setText(emptyHint);
} else {
emptyView.setVisibility(View.GONE);
}
}
public void setOnFilePickListener(OnFilePickListener listener) {
this.onFilePickListener = listener;
}
public interface OnFilePickListener {
void onFilePicked(String currentPath);
}
}

@ -0,0 +1,308 @@
package xyz.fycz.myreader.widget.filepicker.popup;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.FrameLayout;
import androidx.annotation.StyleRes;
import xyz.fycz.myreader.widget.filepicker.util.ScreenUtils;
/**
* 弹窗基类
*
* @param <V> 弹窗的内容视图类型
* @author 李玉江[QQ:1023694760]
* @since 2015/7/19
*/
public abstract class BasicPopup<V extends View> implements DialogInterface.OnKeyListener,
DialogInterface.OnDismissListener {
public static final int MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT;
public static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;
protected Activity activity;
protected int screenWidthPixels;
protected int screenHeightPixels;
private Dialog dialog;
private FrameLayout contentLayout;
private boolean isPrepared = false;
public BasicPopup(Activity activity) {
this.activity = activity;
DisplayMetrics metrics = ScreenUtils.displayMetrics(activity);
screenWidthPixels = metrics.widthPixels;
screenHeightPixels = metrics.heightPixels;
initDialog();
}
private void initDialog() {
contentLayout = new FrameLayout(activity);
contentLayout.setLayoutParams(new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
contentLayout.setFocusable(true);
contentLayout.setFocusableInTouchMode(true);
dialog = new Dialog(activity);
dialog.setCanceledOnTouchOutside(true);//触摸屏幕取消窗体
dialog.setCancelable(true);//按返回键取消窗体
dialog.setOnKeyListener(this);
dialog.setOnDismissListener(this);
Window window = dialog.getWindow();
if (window != null) {
window.setGravity(Gravity.BOTTOM);
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
//AndroidRuntimeException: requestFeature() must be called before adding content
window.requestFeature(Window.FEATURE_NO_TITLE);
window.setContentView(contentLayout);
}
setSize(screenWidthPixels, WRAP_CONTENT);
}
public int getScreenWidthPixels() {
return screenWidthPixels;
}
public int getScreenHeightPixels() {
return screenHeightPixels;
}
/**
* 创建弹窗的内容视图
*
* @return the view
*/
protected abstract V makeContentView();
/**
* 固定高度为屏幕的高
*
* @param fillScreen true为全屏
*/
public void setFillScreen(boolean fillScreen) {
if (fillScreen) {
setSize(screenWidthPixels, (int) (screenHeightPixels * 0.85f));
}
}
/**
* 固定高度为屏幕的一半
*
* @param halfScreen true为半屏
*/
public void setHalfScreen(boolean halfScreen) {
if (halfScreen) {
setSize(screenWidthPixels, screenHeightPixels / 2);
}
}
/**
* 位于屏幕何处
*
* @see Gravity
*/
public void setGravity(int gravity) {
Window window = dialog.getWindow();
if (window != null) {
window.setGravity(gravity);
}
if (gravity == Gravity.CENTER) {
//居于屏幕正中间时,宽度不允许填充屏幕
setWidth((int) (screenWidthPixels * 0.7f));
}
}
/**
* 设置弹窗的内容视图之前执行
*/
protected void setContentViewBefore() {
}
/**
* 设置弹窗的内容视图之后执行
*
* @param contentView 弹窗的内容视图
*/
protected void setContentViewAfter(V contentView) {
}
public void setContentView(View view) {
contentLayout.removeAllViews();
contentLayout.addView(view);
}
public void setFitsSystemWindows(boolean flag) {
contentLayout.setFitsSystemWindows(flag);
}
public void setAnimationStyle(@StyleRes int animRes) {
Window window = dialog.getWindow();
if (window != null) {
window.setWindowAnimations(animRes);
}
}
public void setCanceledOnTouchOutside(boolean flag) {
dialog.setCanceledOnTouchOutside(flag);
}
public void setCancelable(boolean flag) {
dialog.setCancelable(flag);
}
public void setOnDismissListener(final DialogInterface.OnDismissListener onDismissListener) {
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
BasicPopup.this.onDismiss(dialog);
onDismissListener.onDismiss(dialog);
}
});
}
public void setOnKeyListener(final DialogInterface.OnKeyListener onKeyListener) {
dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
BasicPopup.this.onKey(dialog, keyCode, event);
return onKeyListener.onKey(dialog, keyCode, event);
}
});
}
/**
* 设置弹窗的宽和高
*
* @param width
* @param height
*/
public void setSize(int width, int height) {
if (width == MATCH_PARENT) {
//360奇酷等手机对话框MATCH_PARENT时两边还会有边距,故强制填充屏幕宽
width = screenWidthPixels;
}
if (width == 0 && height == 0) {
width = screenWidthPixels;
height = WRAP_CONTENT;
} else if (width == 0) {
width = screenWidthPixels;
} else if (height == 0) {
height = WRAP_CONTENT;
}
ViewGroup.LayoutParams params = contentLayout.getLayoutParams();
if (params == null) {
params = new ViewGroup.LayoutParams(width, height);
} else {
params.width = width;
params.height = height;
}
contentLayout.setLayoutParams(params);
}
/**
* 设置弹窗的宽
*
* @param width
* @see #setSize(int, int)
*/
public void setWidth(int width) {
setSize(width, 0);
}
/**
* 设置弹窗的高
*
* @param height
* @see #setSize(int, int)
*/
public void setHeight(int height) {
setSize(0, height);
}
/**
* 设置是否需要重新初始化视图可用于数据刷新
*/
public void setPrepared(boolean prepared) {
isPrepared = prepared;
}
public boolean isShowing() {
return dialog.isShowing();
}
public final void show() {
if (isPrepared) {
dialog.show();
showAfter();
return;
}
setContentViewBefore();
V view = makeContentView();
setContentView(view);// 设置弹出窗体的布局
setContentViewAfter(view);
isPrepared = true;
dialog.show();
showAfter();
}
protected void showAfter() {
}
public void dismiss() {
dismissImmediately();
}
protected final void dismissImmediately() {
dialog.dismiss();
}
public boolean onBackPress() {
dismiss();
return false;
}
@Override
public final boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_BACK) {
onBackPress();
}
return false;
}
@Override
public void onDismiss(DialogInterface dialog) {
dismiss();
}
public Context getContext() {
return dialog.getContext();
}
public Window getWindow() {
return dialog.getWindow();
}
/**
* 弹框的内容视图
*/
public View getContentView() {
// IllegalStateException: The specified child already has a parent.
// You must call removeView() on the child's parent first.
return contentLayout.getChildAt(0);
}
/**
* 弹框的根视图
*/
public ViewGroup getRootView() {
return contentLayout;
}
}

@ -0,0 +1,407 @@
package xyz.fycz.myreader.widget.filepicker.popup;
import android.app.Activity;
import android.graphics.Color;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import xyz.fycz.myreader.widget.filepicker.util.ConvertUtils;
/**
* 带确定及取消按钮的弹窗
*
* @author 李玉江[QQ:1032694760]
* @since 2015/10/21
*/
@SuppressWarnings("WeakerAccess")
public abstract class ConfirmPopup<V extends View> extends BasicPopup<View> {
protected boolean topLineVisible = true;
protected int topLineColor = 0xFF33B5E5;
protected int topLineHeightPixels = 1;//px
protected int topBackgroundColor = Color.WHITE;
protected int topHeight = 40;//dp
protected int topPadding = 15;//dp
protected int contentLeftAndRightPadding = 0;//dp
protected int contentTopAndBottomPadding = 0;//dp
protected boolean cancelVisible = true;
protected CharSequence cancelText = "";
protected CharSequence submitText = "";
protected CharSequence titleText = "";
protected int cancelTextColor = 0xFF33B5E5;
protected int submitTextColor = 0xFF33B5E5;
protected int titleTextColor = Color.BLACK;
protected int pressedTextColor = 0XFF0288CE;
protected int cancelTextSize = 0;
protected int submitTextSize = 0;
protected int titleTextSize = 0;
protected int backgroundColor = Color.WHITE;
protected TextView cancelButton, submitButton;
protected View titleView;
protected View headerView, centerView, footerView;
public ConfirmPopup(Activity activity) {
super(activity);
cancelText = activity.getString(android.R.string.cancel);
submitText = activity.getString(android.R.string.ok);
}
/**
* 设置顶部标题栏下划线颜色
*/
public void setTopLineColor(@ColorInt int topLineColor) {
this.topLineColor = topLineColor;
}
/**
* 设置顶部标题栏下划线高度单位为px
*/
public void setTopLineHeight(int topLineHeightPixels) {
this.topLineHeightPixels = topLineHeightPixels;
}
/**
* 设置顶部标题栏背景颜色
*/
public void setTopBackgroundColor(@ColorInt int topBackgroundColor) {
this.topBackgroundColor = topBackgroundColor;
}
/**
* 设置顶部标题栏高度单位为dp
*/
public void setTopHeight(@IntRange(from = 10, to = 80) int topHeight) {
this.topHeight = topHeight;
}
/**
* 设置顶部按钮左边及右边边距单位为dp
*/
public void setTopPadding(int topPadding) {
this.topPadding = topPadding;
}
/**
* 设置顶部标题栏下划线是否显示
*/
public void setTopLineVisible(boolean topLineVisible) {
this.topLineVisible = topLineVisible;
}
/**
* 设置内容上下左右边距单位为dp
*/
public void setContentPadding(int leftAndRight, int topAndBottom) {
this.contentLeftAndRightPadding = leftAndRight;
this.contentTopAndBottomPadding = topAndBottom;
}
/**
* 设置顶部标题栏取消按钮是否显示
*/
public void setCancelVisible(boolean cancelVisible) {
if (null != cancelButton) {
cancelButton.setVisibility(cancelVisible ? View.VISIBLE : View.GONE);
} else {
this.cancelVisible = cancelVisible;
}
}
/**
* 设置顶部标题栏取消按钮文字
*/
public void setCancelText(CharSequence cancelText) {
if (null != cancelButton) {
cancelButton.setText(cancelText);
} else {
this.cancelText = cancelText;
}
}
/**
* 设置顶部标题栏取消按钮文字
*/
public void setCancelText(@StringRes int textRes) {
setCancelText(activity.getString(textRes));
}
/**
* 设置顶部标题栏确定按钮文字
*/
public void setSubmitText(CharSequence submitText) {
if (null != submitButton) {
submitButton.setText(submitText);
} else {
this.submitText = submitText;
}
}
/**
* 设置顶部标题栏确定按钮文字
*/
public void setSubmitText(@StringRes int textRes) {
setSubmitText(activity.getString(textRes));
}
/**
* 设置顶部标题栏标题文字
*/
public void setTitleText(CharSequence titleText) {
if (titleView != null && titleView instanceof TextView) {
((TextView) titleView).setText(titleText);
} else {
this.titleText = titleText;
}
}
/**
* 设置顶部标题栏标题文字
*/
public void setTitleText(@StringRes int textRes) {
setTitleText(activity.getString(textRes));
}
/**
* 设置顶部标题栏取消按钮文字颜色
*/
public void setCancelTextColor(@ColorInt int cancelTextColor) {
if (null != cancelButton) {
cancelButton.setTextColor(cancelTextColor);
} else {
this.cancelTextColor = cancelTextColor;
}
}
/**
* 设置顶部标题栏确定按钮文字颜色
*/
public void setSubmitTextColor(@ColorInt int submitTextColor) {
if (null != submitButton) {
submitButton.setTextColor(submitTextColor);
} else {
this.submitTextColor = submitTextColor;
}
}
/**
* 设置顶部标题栏标题文字颜色
*/
public void setTitleTextColor(@ColorInt int titleTextColor) {
if (null != titleView && titleView instanceof TextView) {
((TextView) titleView).setTextColor(titleTextColor);
} else {
this.titleTextColor = titleTextColor;
}
}
/**
* 设置按下时的文字颜色
*/
public void setPressedTextColor(int pressedTextColor) {
this.pressedTextColor = pressedTextColor;
}
/**
* 设置顶部标题栏取消按钮文字大小单位为sp
*/
public void setCancelTextSize(@IntRange(from = 10, to = 40) int cancelTextSize) {
this.cancelTextSize = cancelTextSize;
}
/**
* 设置顶部标题栏确定按钮文字大小单位为sp
*/
public void setSubmitTextSize(@IntRange(from = 10, to = 40) int submitTextSize) {
this.submitTextSize = submitTextSize;
}
/**
* 设置顶部标题栏标题文字大小单位为sp
*/
public void setTitleTextSize(@IntRange(from = 10, to = 40) int titleTextSize) {
this.titleTextSize = titleTextSize;
}
/**
* 设置选择器主体背景颜色
*/
public void setBackgroundColor(@ColorInt int backgroundColor) {
this.backgroundColor = backgroundColor;
}
public TextView getCancelButton() {
if (null == cancelButton) {
throw new NullPointerException("please call show at first");
}
return cancelButton;
}
public TextView getSubmitButton() {
if (null == submitButton) {
throw new NullPointerException("please call show at first");
}
return submitButton;
}
/**
* @see #makeHeaderView()
* @see #makeCenterView()
* @see #makeFooterView()
*/
@Override
protected final View makeContentView() {
LinearLayout rootLayout = new LinearLayout(activity);
rootLayout.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
rootLayout.setBackgroundColor(backgroundColor);
rootLayout.setOrientation(LinearLayout.VERTICAL);
rootLayout.setGravity(Gravity.CENTER);
rootLayout.setPadding(0, 0, 0, 0);
rootLayout.setClipToPadding(false);
View headerView = makeHeaderView();
if (headerView != null) {
rootLayout.addView(headerView);
}
if (topLineVisible) {
View lineView = new View(activity);
lineView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, topLineHeightPixels));
lineView.setBackgroundColor(topLineColor);
rootLayout.addView(lineView);
}
if (centerView == null) {
centerView = makeCenterView();
}
int lr = 0;
int tb = 0;
if (contentLeftAndRightPadding > 0) {
lr = ConvertUtils.toPx(activity, contentLeftAndRightPadding);
}
if (contentTopAndBottomPadding > 0) {
tb = ConvertUtils.toPx(activity, contentTopAndBottomPadding);
}
centerView.setPadding(lr, tb, lr, tb);
ViewGroup vg = (ViewGroup) centerView.getParent();
if (vg != null) {
//IllegalStateException: The specified child already has a parent
vg.removeView(centerView);
}
rootLayout.addView(centerView, new LinearLayout.LayoutParams(MATCH_PARENT, 0, 1.0f));
View footerView = makeFooterView();
if (footerView != null) {
rootLayout.addView(footerView);
}
return rootLayout;
}
@Nullable
protected View makeHeaderView() {
if (null != headerView) {
return headerView;
}
RelativeLayout topButtonLayout = new RelativeLayout(activity);
int height = ConvertUtils.toPx(activity, topHeight);
topButtonLayout.setLayoutParams(new RelativeLayout.LayoutParams(MATCH_PARENT, height));
topButtonLayout.setBackgroundColor(topBackgroundColor);
topButtonLayout.setGravity(Gravity.CENTER_VERTICAL);
cancelButton = new TextView(activity);
cancelButton.setVisibility(cancelVisible ? View.VISIBLE : View.GONE);
RelativeLayout.LayoutParams cancelParams = new RelativeLayout.LayoutParams(WRAP_CONTENT, MATCH_PARENT);
cancelParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
cancelParams.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
cancelButton.setLayoutParams(cancelParams);
cancelButton.setBackgroundColor(Color.TRANSPARENT);
cancelButton.setGravity(Gravity.CENTER);
int padding = ConvertUtils.toPx(activity, topPadding);
cancelButton.setPadding(padding, 0, padding, 0);
if (!TextUtils.isEmpty(cancelText)) {
cancelButton.setText(cancelText);
}
cancelButton.setTextColor(ConvertUtils.toColorStateList(cancelTextColor, pressedTextColor));
if (cancelTextSize != 0) {
cancelButton.setTextSize(cancelTextSize);
}
cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
onCancel();
}
});
topButtonLayout.addView(cancelButton);
if (null == titleView) {
TextView textView = new TextView(activity);
RelativeLayout.LayoutParams titleParams = new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
int margin = ConvertUtils.toPx(activity, topPadding);
titleParams.leftMargin = margin;
titleParams.rightMargin = margin;
titleParams.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
titleParams.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
textView.setLayoutParams(titleParams);
textView.setGravity(Gravity.CENTER);
if (!TextUtils.isEmpty(titleText)) {
textView.setText(titleText);
}
textView.setTextColor(titleTextColor);
if (titleTextSize != 0) {
textView.setTextSize(titleTextSize);
}
titleView = textView;
}
topButtonLayout.addView(titleView);
submitButton = new TextView(activity);
RelativeLayout.LayoutParams submitParams = new RelativeLayout.LayoutParams(WRAP_CONTENT, MATCH_PARENT);
submitParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
submitParams.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
submitButton.setLayoutParams(submitParams);
submitButton.setBackgroundColor(Color.TRANSPARENT);
submitButton.setGravity(Gravity.CENTER);
submitButton.setPadding(padding, 0, padding, 0);
if (!TextUtils.isEmpty(submitText)) {
submitButton.setText(submitText);
}
submitButton.setTextColor(ConvertUtils.toColorStateList(submitTextColor, pressedTextColor));
if (submitTextSize != 0) {
submitButton.setTextSize(submitTextSize);
}
submitButton.setOnClickListener(v -> {
dismiss();
onSubmit();
});
topButtonLayout.addView(submitButton);
return topButtonLayout;
}
@NonNull
protected abstract V makeCenterView();
@Nullable
protected View makeFooterView() {
if (null != footerView) {
return footerView;
}
return null;
}
protected void onSubmit() {
}
protected void onCancel() {
}
}

@ -0,0 +1,617 @@
package xyz.fycz.myreader.widget.filepicker.util;
import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.view.View;
import android.widget.ListView;
import android.widget.ScrollView;
import androidx.annotation.ColorInt;
import androidx.annotation.FloatRange;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.List;
/**
* 数据类型转换单位转换
*
* @author 李玉江[QQ:1023694760]
* @since 2014-4-18
*/
public class ConvertUtils {
public static final long GB = 1073741824;
public static final long MB = 1048576;
public static final long KB = 1024;
public static int toInt(Object obj) {
try {
return Integer.parseInt(obj.toString());
} catch (NumberFormatException e) {
return -1;
}
}
public static int toInt(byte[] bytes) {
int result = 0;
byte abyte;
for (int i = 0; i < bytes.length; i++) {
abyte = bytes[i];
result += (abyte & 0xFF) << (8 * i);
}
return result;
}
public static int toShort(byte first, byte second) {
return (first << 8) + (second & 0xFF);
}
public static long toLong(Object obj) {
try {
return Long.parseLong(obj.toString());
} catch (NumberFormatException e) {
return -1L;
}
}
public static float toFloat(Object obj) {
try {
return Float.parseFloat(obj.toString());
} catch (NumberFormatException e) {
return -1f;
}
}
/**
* int占4字节
*
* @param i the
* @return byte [ ]
*/
public static byte[] toByteArray(int i) {
// byte[] bytes = new byte[4];
// bytes[0] = (byte) (0xff & i);
// bytes[1] = (byte) ((0xff00 & i) >> 8);
// bytes[2] = (byte) ((0xff0000 & i) >> 16);
// bytes[3] = (byte) ((0xff000000 & i) >> 24);
// return bytes;
return ByteBuffer.allocate(4).putInt(i).array();
}
public static byte[] toByteArray(String hexData, boolean isHex) {
if (hexData == null || hexData.equals("")) {
return null;
}
if (!isHex) {
return hexData.getBytes();
}
hexData = hexData.replaceAll("\\s+", "");
String hexDigits = "0123456789ABCDEF";
ByteArrayOutputStream baos = new ByteArrayOutputStream(
hexData.length() / 2);
// 将每2位16进制整数组装成一个字节
for (int i = 0; i < hexData.length(); i += 2) {
baos.write((hexDigits.indexOf(hexData.charAt(i)) << 4 | hexDigits
.indexOf(hexData.charAt(i + 1))));
}
byte[] bytes = baos.toByteArray();
try {
baos.close();
} catch (IOException e) {
}
return bytes;
}
public static String toHexString(String str) {
if (TextUtils.isEmpty(str))
return "";
StringBuilder builder = new StringBuilder();
byte[] bytes = str.getBytes();
for (byte aByte : bytes) {
builder.append(Integer.toHexString(0xFF & aByte));
builder.append(" ");
}
return builder.toString();
}
/**
* To hex string string.
*
* @param bytes the bytes
* @return the string
*/
public static String toHexString(byte... bytes) {
char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
// 参见:http://www.oschina.net/code/snippet_116768_9019
char[] buffer = new char[bytes.length * 2];
for (int i = 0, j = 0; i < bytes.length; ++i) {
int u = bytes[i] < 0 ? bytes[i] + 256 : bytes[i];//转无符号整型
buffer[j++] = DIGITS[u >>> 4];
buffer[j++] = DIGITS[u & 0xf];
}
return new String(buffer);
}
/**
* To hex string string.
*
* @param num the num
* @return the string
*/
public static String toHexString(int num) {
String hexString = Integer.toHexString(num);
return hexString;
}
/**
* To binary string string.
*
* @param bytes the bytes
* @return the string
*/
public static String toBinaryString(byte... bytes) {
char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
// 参见:http://www.oschina.net/code/snippet_116768_9019
char[] buffer = new char[bytes.length * 8];
for (int i = 0, j = 0; i < bytes.length; ++i) {
int u = bytes[i] < 0 ? bytes[i] + 256 : bytes[i];//转无符号整型
buffer[j++] = DIGITS[(u >>> 7) & 0x1];
buffer[j++] = DIGITS[(u >>> 6) & 0x1];
buffer[j++] = DIGITS[(u >>> 5) & 0x1];
buffer[j++] = DIGITS[(u >>> 4) & 0x1];
buffer[j++] = DIGITS[(u >>> 3) & 0x1];
buffer[j++] = DIGITS[(u >>> 2) & 0x1];
buffer[j++] = DIGITS[(u >>> 1) & 0x1];
buffer[j++] = DIGITS[u & 0x1];
}
return new String(buffer);
}
/**
* To binary string string.
*
* @param num the num
* @return the string
*/
public static String toBinaryString(int num) {
String binaryString = Integer.toBinaryString(num);
return binaryString;
}
public static String toSlashString(String str) {
String result = "";
char[] chars = str.toCharArray();
for (char chr : chars) {
if (chr == '"' || chr == '\'' || chr == '\\') {
result += "\\";//符合“"”“'”“\”这三个符号的前面加一个“\”
}
result += chr;
}
return result;
}
public static <T> T[] toArray(List<T> list) {
//noinspection unchecked
return (T[]) list.toArray();
}
public static <T> List<T> toList(T[] array) {
return Arrays.asList(array);
}
public static String toString(Object[] objects) {
return Arrays.deepToString(objects);
}
public static String toString(Object[] objects, String tag) {
StringBuilder sb = new StringBuilder();
for (Object object : objects) {
sb.append(object);
sb.append(tag);
}
return sb.toString();
}
public static byte[] toByteArray(InputStream is) {
if (is == null) {
return null;
}
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] buff = new byte[100];
while (true) {
int len = is.read(buff, 0, 100);
if (len == -1) {
break;
} else {
os.write(buff, 0, len);
}
}
byte[] bytes = os.toByteArray();
os.close();
is.close();
return bytes;
} catch (IOException e) {
}
return null;
}
public static byte[] toByteArray(Bitmap bitmap) {
if (bitmap == null) {
return null;
}
ByteArrayOutputStream os = new ByteArrayOutputStream();
// 将Bitmap压缩成PNG编码,质量为100%存储,除了PNG还有很多常见格式,如jpeg等。
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
byte[] bytes = os.toByteArray();
try {
os.close();
} catch (IOException e) {
}
return bytes;
}
public static Bitmap toBitmap(byte[] bytes, int width, int height) {
Bitmap bitmap = null;
if (bytes.length != 0) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
// 不进行图片抖动处理
options.inDither = false;
// 设置让解码器以最佳方式解码
options.inPreferredConfig = null;
if (width > 0 && height > 0) {
options.outWidth = width;
options.outHeight = height;
}
bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
bitmap.setDensity(96);// 96 dpi
} catch (Exception e) {
}
}
return bitmap;
}
public static Bitmap toBitmap(byte[] bytes) {
return toBitmap(bytes, -1, -1);
}
/**
* 将Drawable转换为Bitmap
* 参考http://kylines.iteye.com/blog/1660184
*/
public static Bitmap toBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
} else if (drawable instanceof ColorDrawable) {
//color
Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(((ColorDrawable) drawable).getColor());
return bitmap;
} else if (drawable instanceof NinePatchDrawable) {
//.9.png
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
drawable.draw(canvas);
return bitmap;
}
return null;
}
/**
* 从第三方文件选择器获取路径
* 参见http://blog.csdn.net/zbjdsbj/article/details/42387551
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
public static String toPath(Context context, Uri uri) {
if (uri == null) {
return "";
}
String path = uri.getPath();
String scheme = uri.getScheme();
String authority = uri.getAuthority();
//是否是4.4及以上版本
boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
String docId = DocumentsContract.getDocumentId(uri);
String[] split = docId.split(":");
String type = split[0];
Uri contentUri = null;
switch (authority) {
// ExternalStorageProvider
case "com.android.externalstorage.documents":
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
break;
// DownloadsProvider
case "com.android.providers.downloads.documents":
contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
return _queryPathFromMediaStore(context, contentUri, null, null);
// MediaProvider
case "com.android.providers.media.documents":
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
String selection = "_id=?";
String[] selectionArgs = new String[]{split[1]};
return _queryPathFromMediaStore(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else {
if ("content".equalsIgnoreCase(scheme)) {
// Return the remote address
if (authority.equals("com.google.android.apps.photos.content")) {
return uri.getLastPathSegment();
}
return _queryPathFromMediaStore(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(scheme)) {
return uri.getPath();
}
}
return path;
}
private static String _queryPathFromMediaStore(Context context, Uri uri, String selection, String[] selectionArgs) {
String filePath = null;
try {
String[] projection = {MediaStore.Images.Media.DATA};
Cursor cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null) {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
filePath = cursor.getString(column_index);
cursor.close();
}
} catch (IllegalArgumentException e) {
}
return filePath;
}
/**
* 把view转化为bitmap截图
* 参见http://www.cnblogs.com/lee0oo0/p/3355468.html
*/
public static Bitmap toBitmap(View view) {
int width = view.getWidth();
int height = view.getHeight();
if (view instanceof ListView) {
height = 0;
// 获取listView实际高度
ListView listView = (ListView) view;
for (int i = 0; i < listView.getChildCount(); i++) {
height += listView.getChildAt(i).getHeight();
}
} else if (view instanceof ScrollView) {
height = 0;
// 获取scrollView实际高度
ScrollView scrollView = (ScrollView) view;
for (int i = 0; i < scrollView.getChildCount(); i++) {
height += scrollView.getChildAt(i).getHeight();
}
}
view.setDrawingCacheEnabled(true);
view.clearFocus();
view.setPressed(false);
boolean willNotCache = view.willNotCacheDrawing();
view.setWillNotCacheDrawing(false);
// Reset the drawing cache background color to fully transparent for the duration of this operation
int color = view.getDrawingCacheBackgroundColor();
view.setDrawingCacheBackgroundColor(Color.WHITE);//截图去黑色背景(透明像素)
if (color != Color.WHITE) {
view.destroyDrawingCache();
}
view.buildDrawingCache();
Bitmap cacheBitmap = view.getDrawingCache();
if (cacheBitmap == null) {
return null;
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(cacheBitmap, 0, 0, null);
canvas.save();
canvas.restore();
if (!bitmap.isRecycled()) {
bitmap.recycle();
}
// Restore the view
view.destroyDrawingCache();
view.setWillNotCacheDrawing(willNotCache);
view.setDrawingCacheBackgroundColor(color);
return bitmap;
}
public static Drawable toDrawable(Bitmap bitmap) {
return bitmap == null ? null : new BitmapDrawable(Resources.getSystem(), bitmap);
}
public static byte[] toByteArray(Drawable drawable) {
return toByteArray(toBitmap(drawable));
}
public static Drawable toDrawable(byte[] bytes) {
return toDrawable(toBitmap(bytes));
}
/**
* dp转换为px
*/
public static int toPx(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
int pxValue = (int) (dpValue * scale + 0.5f);
return pxValue;
}
/**
* px转换为dp
*/
public static int toDp(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
int dpValue = (int) (pxValue / scale + 0.5f);
return dpValue;
}
/**
* px转换为sp
*/
public static int toSp(Context context, float pxValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
int spValue = (int) (pxValue / fontScale + 0.5f);
return spValue;
}
public static String toGbk(String str) {
try {
return new String(str.getBytes("utf-8"), "gbk");
} catch (UnsupportedEncodingException e) {
return str;
}
}
public static String toFileSizeString(long fileSize) {
DecimalFormat df = new DecimalFormat("0.00");
String fileSizeString;
if (fileSize < KB) {
fileSizeString = fileSize + "B";
} else if (fileSize < MB) {
fileSizeString = df.format((double) fileSize / KB) + "K";
} else if (fileSize < GB) {
fileSizeString = df.format((double) fileSize / MB) + "M";
} else {
fileSizeString = df.format((double) fileSize / GB) + "G";
}
return fileSizeString;
}
public static String toString(InputStream is, String charset) {
StringBuilder sb = new StringBuilder();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(is, charset));
while (true) {
String line = reader.readLine();
if (line == null) {
break;
} else {
sb.append(line).append("\n");
}
}
reader.close();
is.close();
} catch (IOException e) {
}
return sb.toString();
}
public static String toString(InputStream is) {
return toString(is, "utf-8");
}
public static int toDarkenColor(@ColorInt int color) {
return toDarkenColor(color, 0.8f);
}
public static int toDarkenColor(@ColorInt int color, @FloatRange(from = 0f, to = 1f) float value) {
float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
hsv[2] *= value;//HSV指Hue、Saturation、Value,即色调、饱和度和亮度,此处表示修改亮度
return Color.HSVToColor(hsv);
}
/**
* 转换为6位十六进制颜色代码不含#
*/
public static String toColorString(@ColorInt int color) {
return toColorString(color, false);
}
/**
* 转换为6位十六进制颜色代码不含#
*/
public static String toColorString(@ColorInt int color, boolean includeAlpha) {
String alpha = Integer.toHexString(Color.alpha(color));
String red = Integer.toHexString(Color.red(color));
String green = Integer.toHexString(Color.green(color));
String blue = Integer.toHexString(Color.blue(color));
if (alpha.length() == 1) {
alpha = "0" + alpha;
}
if (red.length() == 1) {
red = "0" + red;
}
if (green.length() == 1) {
green = "0" + green;
}
if (blue.length() == 1) {
blue = "0" + blue;
}
String colorString;
if (includeAlpha) {
colorString = alpha + red + green + blue;
} else {
colorString = red + green + blue;
}
return colorString;
}
/**
* 对TextViewButton等设置不同状态时其文字颜色
* 参见http://blog.csdn.net/sodino/article/details/6797821
* Modified by liyujiang at 2015.08.13
*/
public static ColorStateList toColorStateList(@ColorInt int normalColor, @ColorInt int pressedColor,
@ColorInt int focusedColor, @ColorInt int unableColor) {
int[] colors = new int[]{pressedColor, focusedColor, normalColor, focusedColor, unableColor, normalColor};
int[][] states = new int[6][];
states[0] = new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled};
states[1] = new int[]{android.R.attr.state_enabled, android.R.attr.state_focused};
states[2] = new int[]{android.R.attr.state_enabled};
states[3] = new int[]{android.R.attr.state_focused};
states[4] = new int[]{android.R.attr.state_window_focused};
states[5] = new int[]{};
return new ColorStateList(states, colors);
}
public static ColorStateList toColorStateList(@ColorInt int normalColor, @ColorInt int pressedColor) {
return toColorStateList(normalColor, pressedColor, pressedColor, normalColor);
}
}

@ -0,0 +1,226 @@
package xyz.fycz.myreader.widget.filepicker.util;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
/**
* 日期时间工具类
*
* @author 李玉江[QQ:1023694760]
* @since 2015/8/5
*/
public class DateUtils extends android.text.format.DateUtils {
public static final int Second = 0;
public static final int Minute = 1;
public static final int Hour = 2;
public static final int Day = 3;
@IntDef(value = {Second, Minute, Hour, Day})
@Retention(RetentionPolicy.SOURCE)
public @interface DifferenceMode {
}
public static long calculateDifferentSecond(Date startDate, Date endDate) {
return calculateDifference(startDate, endDate, Second);
}
public static long calculateDifferentMinute(Date startDate, Date endDate) {
return calculateDifference(startDate, endDate, Minute);
}
public static long calculateDifferentHour(Date startDate, Date endDate) {
return calculateDifference(startDate, endDate, Hour);
}
public static long calculateDifferentDay(Date startDate, Date endDate) {
return calculateDifference(startDate, endDate, Day);
}
public static long calculateDifferentSecond(long startTimeMillis, long endTimeMillis) {
return calculateDifference(startTimeMillis, endTimeMillis, Second);
}
public static long calculateDifferentMinute(long startTimeMillis, long endTimeMillis) {
return calculateDifference(startTimeMillis, endTimeMillis, Minute);
}
public static long calculateDifferentHour(long startTimeMillis, long endTimeMillis) {
return calculateDifference(startTimeMillis, endTimeMillis, Hour);
}
public static long calculateDifferentDay(long startTimeMillis, long endTimeMillis) {
return calculateDifference(startTimeMillis, endTimeMillis, Day);
}
/**
* 计算两个时间戳之间相差的时间戳数
*/
public static long calculateDifference(long startTimeMillis, long endTimeMillis, @DifferenceMode int mode) {
return calculateDifference(new Date(startTimeMillis), new Date(endTimeMillis), mode);
}
/**
* 计算两个日期之间相差的时间戳数
*/
public static long calculateDifference(Date startDate, Date endDate, @DifferenceMode int mode) {
long[] different = calculateDifference(startDate, endDate);
if (mode == Minute) {
return different[2];
} else if (mode == Hour) {
return different[1];
} else if (mode == Day) {
return different[0];
} else {
return different[3];
}
}
private static long[] calculateDifference(Date startDate, Date endDate) {
return calculateDifference(endDate.getTime() - startDate.getTime());
}
private static long[] calculateDifference(long differentMilliSeconds) {
long secondsInMilli = 1000;//1s==1000ms
long minutesInMilli = secondsInMilli * 60;
long hoursInMilli = minutesInMilli * 60;
long daysInMilli = hoursInMilli * 24;
long elapsedDays = differentMilliSeconds / daysInMilli;
differentMilliSeconds = differentMilliSeconds % daysInMilli;
long elapsedHours = differentMilliSeconds / hoursInMilli;
differentMilliSeconds = differentMilliSeconds % hoursInMilli;
long elapsedMinutes = differentMilliSeconds / minutesInMilli;
differentMilliSeconds = differentMilliSeconds % minutesInMilli;
long elapsedSeconds = differentMilliSeconds / secondsInMilli;
return new long[]{elapsedDays, elapsedHours, elapsedMinutes, elapsedSeconds};
}
/**
* 计算每月的天数
*/
public static int calculateDaysInMonth(int month) {
return calculateDaysInMonth(0, month);
}
/**
* 根据年份及月份计算每月的天数
*/
public static int calculateDaysInMonth(int year, int month) {
// 添加大小月月份并将其转换为list,方便之后的判断
String[] bigMonths = {"1", "3", "5", "7", "8", "10", "12"};
String[] littleMonths = {"4", "6", "9", "11"};
List<String> bigList = Arrays.asList(bigMonths);
List<String> littleList = Arrays.asList(littleMonths);
// 判断大小月及是否闰年,用来确定"日"的数据
if (bigList.contains(String.valueOf(month))) {
return 31;
} else if (littleList.contains(String.valueOf(month))) {
return 30;
} else {
if (year <= 0) {
return 29;
}
// 是否闰年
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
return 29;
} else {
return 28;
}
}
}
/**
* 月日时分秒0-9前补0
*/
@NonNull
public static String fillZero(int number) {
return number < 10 ? "0" + number : "" + number;
}
/**
* 截取掉前缀0以便转换为整数
*
* @see #fillZero(int)
*/
public static int trimZero(@NonNull String text) {
try {
if (text.startsWith("0")) {
text = text.substring(1);
}
return Integer.parseInt(text);
} catch (NumberFormatException e) {
return 0;
}
}
/**
* 功能判断日期是否和当前date对象在同一天
* 参见http://www.cnblogs.com/myzhijie/p/3330970.html
*
* @param date 比较的日期
* @return boolean 如果在返回true否则返回false
*/
public static boolean isSameDay(Date date) {
if (date == null) {
throw new IllegalArgumentException("date is null");
}
Calendar nowCalendar = Calendar.getInstance();
Calendar newCalendar = Calendar.getInstance();
newCalendar.setTime(date);
return (nowCalendar.get(Calendar.ERA) == newCalendar.get(Calendar.ERA) &&
nowCalendar.get(Calendar.YEAR) == newCalendar.get(Calendar.YEAR) &&
nowCalendar.get(Calendar.DAY_OF_YEAR) == newCalendar.get(Calendar.DAY_OF_YEAR));
}
/**
* 将yyyy-MM-dd HH:mm:ss字符串转换成日期<br/>
*
* @param dateStr 时间字符串
* @param dataFormat 当前时间字符串的格式
* @return Date 日期 ,转换异常时返回null
*/
public static Date parseDate(String dateStr, String dataFormat) {
try {
SimpleDateFormat dateFormat = new SimpleDateFormat(dataFormat, Locale.PRC);
Date date = dateFormat.parse(dateStr);
return new Date(date.getTime());
} catch (ParseException e) {
return null;
}
}
/**
* 将yyyy-MM-dd HH:mm:ss字符串转换成日期<br/>
*
* @param dateStr yyyy-MM-dd HH:mm:ss字符串
* @return Date 日期 ,转换异常时返回null
*/
public static Date parseDate(String dateStr) {
return parseDate(dateStr, "yyyy-MM-dd HH:mm:ss");
}
/**
* 将指定的日期转换为一定格式的字符串
*/
public static String formatDate(Date date, String format) {
SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.PRC);
return sdf.format(date);
}
/**
* 将当前日期转换为一定格式的字符串
*/
public static String formatDate(String format) {
return formatDate(Calendar.getInstance(Locale.CHINA).getTime(), format);
}
}

@ -0,0 +1,783 @@
package xyz.fycz.myreader.widget.filepicker.util;
import android.webkit.MimeTypeMap;
import androidx.annotation.IntDef;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.regex.Pattern;
/**
* 文件处理
* <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
* <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
*
* @author 李玉江[QQ:1023694760]
* @since 2014-4-18
*/
public final class FileUtils {
public static final int BY_NAME_ASC = 0;
public static final int BY_NAME_DESC = 1;
public static final int BY_TIME_ASC = 2;
public static final int BY_TIME_DESC = 3;
public static final int BY_SIZE_ASC = 4;
public static final int BY_SIZE_DESC = 5;
public static final int BY_EXTENSION_ASC = 6;
public static final int 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
})
@Retention(RetentionPolicy.SOURCE)
public @interface SortType {
}
/**
* 将目录分隔符统一为平台默认的分隔符并为目录结尾添加分隔符
*/
public static String separator(String path) {
String separator = File.separator;
path = path.replace("\\", separator);
if (!path.endsWith(separator)) {
path += separator;
}
return path;
}
public static void closeSilently(Closeable c) {
if (c == null) {
return;
}
try {
c.close();
} catch (IOException ignored) {
}
}
/**
* 列出指定目录下的所有子目录
*/
public static File[] listDirs(String startDirPath, String[] excludeDirs, @SortType int sortType) {
ArrayList<File> dirList = new ArrayList<File>();
File startDir = new File(startDirPath);
if (!startDir.isDirectory()) {
return new File[0];
}
File[] dirs = startDir.listFiles(new FileFilter() {
public boolean accept(File f) {
if (f == null) {
return false;
}
//noinspection RedundantIfStatement
if (f.isDirectory()) {
return true;
}
return false;
}
});
if (dirs == null) {
return new File[0];
}
if (excludeDirs == null) {
excludeDirs = new String[0];
}
for (File dir : dirs) {
File file = dir.getAbsoluteFile();
if (!ConvertUtils.toString(excludeDirs).contains(file.getName())) {
dirList.add(file);
}
}
if (sortType == BY_NAME_ASC) {
Collections.sort(dirList, new SortByName());
} else if (sortType == BY_NAME_DESC) {
Collections.sort(dirList, new SortByName());
Collections.reverse(dirList);
} else if (sortType == BY_TIME_ASC) {
Collections.sort(dirList, new SortByTime());
} else if (sortType == BY_TIME_DESC) {
Collections.sort(dirList, new SortByTime());
Collections.reverse(dirList);
} else if (sortType == BY_SIZE_ASC) {
Collections.sort(dirList, new SortBySize());
} else if (sortType == BY_SIZE_DESC) {
Collections.sort(dirList, new SortBySize());
Collections.reverse(dirList);
} else if (sortType == BY_EXTENSION_ASC) {
Collections.sort(dirList, new SortByExtension());
} else if (sortType == BY_EXTENSION_DESC) {
Collections.sort(dirList, new SortByExtension());
Collections.reverse(dirList);
}
return dirList.toArray(new File[dirList.size()]);
}
/**
* 列出指定目录下的所有子目录
*/
public static File[] listDirs(String startDirPath, String[] excludeDirs) {
return listDirs(startDirPath, excludeDirs, BY_NAME_ASC);
}
/**
* 列出指定目录下的所有子目录
*/
public static File[] listDirs(String startDirPath) {
return listDirs(startDirPath, null, BY_NAME_ASC);
}
/**
* 列出指定目录下的所有子目录及所有文件
*/
public static File[] listDirsAndFiles(String startDirPath, String[] allowExtensions) {
File[] dirs, files, dirsAndFiles;
dirs = listDirs(startDirPath);
if (allowExtensions == null) {
files = listFiles(startDirPath);
} else {
files = listFiles(startDirPath, allowExtensions);
}
if (dirs == null || files == null) {
return null;
}
dirsAndFiles = new File[dirs.length + files.length];
System.arraycopy(dirs, 0, dirsAndFiles, 0, dirs.length);
System.arraycopy(files, 0, dirsAndFiles, dirs.length, files.length);
return dirsAndFiles;
}
/**
* 列出指定目录下的所有子目录及所有文件
*/
public static File[] listDirsAndFiles(String startDirPath) {
return listDirsAndFiles(startDirPath, null);
}
/**
* 列出指定目录下的所有文件
*/
public static File[] listFiles(String startDirPath, final Pattern filterPattern, @SortType int sortType) {
ArrayList<File> fileList = new ArrayList<File>();
File f = new File(startDirPath);
if (!f.isDirectory()) {
return new File[0];
}
File[] files = f.listFiles(new FileFilter() {
public boolean accept(File f) {
if (f == null) {
return false;
}
if (f.isDirectory()) {
return false;
}
//noinspection SimplifiableIfStatement
if (filterPattern == null) {
return true;
}
return filterPattern.matcher(f.getName()).find();
}
});
if (files == null) {
return new File[0];
}
for (File file : files) {
fileList.add(file.getAbsoluteFile());
}
if (sortType == BY_NAME_ASC) {
Collections.sort(fileList, new SortByName());
} else if (sortType == BY_NAME_DESC) {
Collections.sort(fileList, new SortByName());
Collections.reverse(fileList);
} else if (sortType == BY_TIME_ASC) {
Collections.sort(fileList, new SortByTime());
} else if (sortType == BY_TIME_DESC) {
Collections.sort(fileList, new SortByTime());
Collections.reverse(fileList);
} else if (sortType == BY_SIZE_ASC) {
Collections.sort(fileList, new SortBySize());
} else if (sortType == BY_SIZE_DESC) {
Collections.sort(fileList, new SortBySize());
Collections.reverse(fileList);
} else if (sortType == BY_EXTENSION_ASC) {
Collections.sort(fileList, new SortByExtension());
} else if (sortType == BY_EXTENSION_DESC) {
Collections.sort(fileList, new SortByExtension());
Collections.reverse(fileList);
}
return fileList.toArray(new File[fileList.size()]);
}
/**
* 列出指定目录下的所有文件
*/
public static File[] listFiles(String startDirPath, Pattern filterPattern) {
return listFiles(startDirPath, filterPattern, BY_NAME_ASC);
}
/**
* 列出指定目录下的所有文件
*/
public static File[] listFiles(String startDirPath) {
return listFiles(startDirPath, null, BY_NAME_ASC);
}
/**
* 列出指定目录下的所有文件
*/
public static File[] listFiles(String startDirPath, final String[] allowExtensions) {
File file = new File(startDirPath);
return file.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
//返回当前目录所有以某些扩展名结尾的文件
String extension = FileUtils.getExtension(name);
return ConvertUtils.toString(allowExtensions).contains(extension);
}
});
}
/**
* 列出指定目录下的所有文件
*/
public static File[] listFiles(String startDirPath, String allowExtension) {
return listFiles(startDirPath, new String[]{allowExtension});
}
/**
* 判断文件或目录是否存在
*/
public static boolean exist(String path) {
File file = new File(path);
return file.exists();
}
/**
* 删除文件或目录
*/
public static boolean delete(File file, boolean deleteRootDir) {
boolean result = false;
if (file.isFile()) {
//是文件
result = deleteResolveEBUSY(file);
} else {
//是目录
File[] files = file.listFiles();
if (files == null) {
return false;
}
if (files.length == 0) {
result = deleteRootDir && deleteResolveEBUSY(file);
} else {
for (File f : 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 static boolean deleteResolveEBUSY(File file) {
// Before you delete a Directory or File: rename it!
final File to = new File(file.getAbsolutePath() + System.currentTimeMillis());
//noinspection ResultOfMethodCallIgnored
file.renameTo(to);
return to.delete();
}
/**
* 删除文件或目录
*/
public static boolean delete(String path, boolean deleteRootDir) {
File file = new File(path);
//noinspection SimplifiableIfStatement
if (file.exists()) {
return delete(file, deleteRootDir);
}
return false;
}
/**
* 删除文件或目录, 不删除最顶层目录
*/
public static boolean delete(String path) {
return delete(path, false);
}
/**
* 删除文件或目录, 不删除最顶层目录
*/
public static boolean delete(File file) {
return delete(file, false);
}
/**
* 复制文件为另一个文件或复制某目录下的所有文件及目录到另一个目录下
*/
public static boolean copy(String src, String tar) {
File srcFile = new File(src);
return srcFile.exists() && copy(srcFile, new File(tar));
}
/**
* 复制文件或目录
*/
public static boolean copy(File src, File tar) {
try {
if (src.isFile()) {
InputStream is = new FileInputStream(src);
OutputStream op = new FileOutputStream(tar);
BufferedInputStream bis = new BufferedInputStream(is);
BufferedOutputStream bos = new BufferedOutputStream(op);
byte[] bt = new byte[1024 * 8];
while (true) {
int len = bis.read(bt);
if (len == -1) {
break;
} else {
bos.write(bt, 0, len);
}
}
bis.close();
bos.close();
} else if (src.isDirectory()) {
File[] files = src.listFiles();
//noinspection ResultOfMethodCallIgnored
tar.mkdirs();
for (File file : files) {
copy(file.getAbsoluteFile(), new File(tar.getAbsoluteFile(), file.getName()));
}
}
return true;
} catch (Exception e) {
return false;
}
}
/**
* 移动文件或目录
*/
public static boolean move(String src, String tar) {
return move(new File(src), new File(tar));
}
/**
* 移动文件或目录
*/
public static boolean move(File src, File tar) {
return rename(src, tar);
}
/**
* 文件重命名
*/
public static boolean rename(String oldPath, String newPath) {
return rename(new File(oldPath), new File(newPath));
}
/**
* 文件重命名
*/
public static boolean rename(File src, File tar) {
return src.renameTo(tar);
}
/**
* 读取文本文件, 失败将返回空串
*/
public static String readText(String filepath, String charset) {
try {
byte[] data = readBytes(filepath);
if (data != null) {
return new String(data, charset).trim();
}
} catch (UnsupportedEncodingException ignored) {
}
return "";
}
/**
* 读取文本文件, 失败将返回空串
*/
public static String readText(String filepath) {
return readText(filepath, "utf-8");
}
/**
* 读取文件内容, 失败将返回空串
*/
public static byte[] readBytes(String filepath) {
FileInputStream fis = null;
try {
fis = new FileInputStream(filepath);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
while (true) {
int len = fis.read(buffer, 0, buffer.length);
if (len == -1) {
break;
} else {
baos.write(buffer, 0, len);
}
}
byte[] data = baos.toByteArray();
baos.close();
return data;
} catch (IOException e) {
return null;
} finally {
closeSilently(fis);
}
}
/**
* 保存文本内容
*/
public static boolean writeText(String filepath, String content, String charset) {
try {
writeBytes(filepath, content.getBytes(charset));
return true;
} catch (UnsupportedEncodingException e) {
return false;
}
}
/**
* 保存文本内容
*/
public static boolean writeText(String filepath, String content) {
return writeText(filepath, content, "utf-8");
}
/**
* 保存文件内容
*/
public static boolean writeBytes(String filepath, byte[] data) {
File file = new File(filepath);
FileOutputStream fos = null;
try {
if (!file.exists()) {
//noinspection ResultOfMethodCallIgnored
file.getParentFile().mkdirs();
//noinspection ResultOfMethodCallIgnored
file.createNewFile();
}
fos = new FileOutputStream(filepath);
fos.write(data);
return true;
} catch (IOException e) {
return false;
} finally {
closeSilently(fos);
}
}
/**
* 追加文本内容
*/
public static boolean appendText(String path, String content) {
File file = new File(path);
FileWriter writer = null;
try {
if (!file.exists()) {
//noinspection ResultOfMethodCallIgnored
file.createNewFile();
}
writer = new FileWriter(file, true);
writer.write(content);
return true;
} catch (IOException e) {
return false;
} finally {
closeSilently(writer);
}
}
/**
* 获取文件大小
*/
public static long getLength(String path) {
File file = new File(path);
if (!file.isFile() || !file.exists()) {
return 0;
}
return file.length();
}
/**
* 获取文件或网址的名称包括后缀
*/
public static String getName(String pathOrUrl) {
if (pathOrUrl == null) {
return "";
}
int pos = pathOrUrl.lastIndexOf('/');
if (0 <= pos) {
return pathOrUrl.substring(pos + 1);
} else {
return String.valueOf(System.currentTimeMillis()) + "." + getExtension(pathOrUrl);
}
}
/**
* 获取文件名不包括扩展名
*/
public static String getNameExcludeExtension(String path) {
try {
String fileName = (new File(path)).getName();
int lastIndexOf = fileName.lastIndexOf(".");
if (lastIndexOf != -1) {
fileName = fileName.substring(0, lastIndexOf);
}
return fileName;
} catch (Exception e) {
return "";
}
}
/**
* 获取格式化后的文件大小
*/
public static String getSize(String path) {
long fileSize = getLength(path);
return ConvertUtils.toFileSizeString(fileSize);
}
/**
* 获取文件后缀,不包括.
*/
public static String getExtension(String pathOrUrl) {
int dotPos = pathOrUrl.lastIndexOf('.');
if (0 <= dotPos) {
return pathOrUrl.substring(dotPos + 1);
} else {
return "ext";
}
}
/**
* 获取文件的MIME类型
*/
public static String getMimeType(String pathOrUrl) {
String ext = getExtension(pathOrUrl);
MimeTypeMap map = MimeTypeMap.getSingleton();
String mimeType;
if (map.hasExtension(ext)) {
mimeType = map.getMimeTypeFromExtension(ext);
} else {
mimeType = "*/*";
}
return mimeType;
}
/**
* 获取格式化后的文件/目录创建或最后修改时间
*/
public static String getDateTime(String path) {
return getDateTime(path, "yyyy年MM月dd日HH:mm");
}
/**
* 获取格式化后的文件/目录创建或最后修改时间
*/
public static String getDateTime(String path, String format) {
File file = new File(path);
return getDateTime(file, format);
}
/**
* 获取格式化后的文件/目录创建或最后修改时间
*/
public static String getDateTime(File file, String format) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(file.lastModified());
return DateUtils.formatDate(cal.getTime(), format);
}
/**
* 比较两个文件的最后修改时间
*/
public static int compareLastModified(String path1, String path2) {
long stamp1 = (new File(path1)).lastModified();
long stamp2 = (new File(path2)).lastModified();
if (stamp1 > stamp2) {
return 1;
} else if (stamp1 < stamp2) {
return -1;
} else {
return 0;
}
}
/**
* 创建多级别的目录
*/
public static boolean makeDirs(String path) {
return makeDirs(new File(path));
}
/**
* 创建多级别的目录
*/
public static boolean makeDirs(File file) {
return file.mkdirs();
}
public static class SortByExtension implements Comparator<File> {
public SortByExtension() {
super();
}
public int compare(File f1, File f2) {
if (f1 == null || f2 == null) {
if (f1 == null) {
return -1;
} else {
return 1;
}
} else {
if (f1.isDirectory() && f2.isFile()) {
return -1;
} else if (f1.isFile() && f2.isDirectory()) {
return 1;
} else {
return f1.getName().compareToIgnoreCase(f2.getName());
}
}
}
}
public static class SortByName implements Comparator<File> {
private boolean caseSensitive;
public SortByName(boolean caseSensitive) {
this.caseSensitive = caseSensitive;
}
public SortByName() {
this.caseSensitive = false;
}
public int compare(File f1, File f2) {
if (f1 == null || f2 == null) {
if (f1 == null) {
return -1;
} else {
return 1;
}
} else {
if (f1.isDirectory() && f2.isFile()) {
return -1;
} else if (f1.isFile() && f2.isDirectory()) {
return 1;
} else {
String s1 = f1.getName();
String s2 = f2.getName();
if (caseSensitive) {
return s1.compareTo(s2);
} else {
return s1.compareToIgnoreCase(s2);
}
}
}
}
}
public static class SortBySize implements Comparator<File> {
public SortBySize() {
super();
}
public int compare(File f1, File f2) {
if (f1 == null || f2 == null) {
if (f1 == null) {
return -1;
} else {
return 1;
}
} else {
if (f1.isDirectory() && f2.isFile()) {
return -1;
} else if (f1.isFile() && f2.isDirectory()) {
return 1;
} else {
if (f1.length() < f2.length()) {
return -1;
} else {
return 1;
}
}
}
}
}
public static class SortByTime implements Comparator<File> {
public SortByTime() {
super();
}
public int compare(File f1, File f2) {
if (f1 == null || f2 == null) {
if (f1 == null) {
return -1;
} else {
return 1;
}
} else {
if (f1.isDirectory() && f2.isFile()) {
return -1;
} else if (f1.isFile() && f2.isDirectory()) {
return 1;
} else {
if (f1.lastModified() > f2.lastModified()) {
return -1;
} else {
return 1;
}
}
}
}
}
}

@ -0,0 +1,71 @@
package xyz.fycz.myreader.widget.filepicker.util;
import android.app.Activity;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.Window;
import android.view.WindowManager;
/**
* 获取屏幕宽高等信息全屏切换保持屏幕常亮截屏等
*
* @author liyujiang[QQ:1032694760]
* @since 2015/11/26
*/
public final class ScreenUtils {
private static boolean isFullScreen = false;
private static DisplayMetrics dm = null;
public static DisplayMetrics displayMetrics(Context context) {
if (null != dm) {
return dm;
}
DisplayMetrics dm = new DisplayMetrics();
WindowManager windowManager = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
windowManager.getDefaultDisplay().getMetrics(dm);
return dm;
}
public static int widthPixels(Context context) {
return displayMetrics(context).widthPixels;
}
public static int heightPixels(Context context) {
return displayMetrics(context).heightPixels;
}
public static float density(Context context) {
return displayMetrics(context).density;
}
public static int densityDpi(Context context) {
return displayMetrics(context).densityDpi;
}
public static boolean isFullScreen() {
return isFullScreen;
}
public static void toggleFullScreen(Activity activity) {
Window window = activity.getWindow();
int flagFullscreen = WindowManager.LayoutParams.FLAG_FULLSCREEN;
if (isFullScreen) {
window.clearFlags(flagFullscreen);
isFullScreen = false;
} else {
window.setFlags(flagFullscreen, flagFullscreen);
isFullScreen = true;
}
}
/**
* 保持屏幕常亮
*/
public static void keepBright(Activity activity) {
//需在setContentView前调用
int keepScreenOn = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
activity.getWindow().setFlags(keepScreenOn, keepScreenOn);
}
}

@ -0,0 +1,161 @@
package xyz.fycz.myreader.widget.filepicker.util;
import android.content.Context;
import android.os.Environment;
import android.text.TextUtils;
import java.io.File;
import java.io.IOException;
/**
* 存储设备工具类
* <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
* <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
*
* @author 李玉江[QQ:1023694760]
* @since 2013-11-2
*/
public final class StorageUtils {
/**
* 判断外置存储是否可用
*
* @return the boolean
*/
public static boolean externalMounted() {
String state = Environment.getExternalStorageState();
if (state.equals(Environment.MEDIA_MOUNTED)) {
return true;
}
return false;
}
/**
* 返回以/结尾的内部存储根目录
*/
public static String getInternalRootPath(Context context, String type) {
File file;
if (TextUtils.isEmpty(type)) {
file = context.getFilesDir();
} else {
file = new File(FileUtils.separator(context.getFilesDir().getAbsolutePath()) + type);
//noinspection ResultOfMethodCallIgnored
file.mkdirs();
}
String path = "";
if (file != null) {
path = FileUtils.separator(file.getAbsolutePath());
}
return path;
}
public static String getInternalRootPath(Context context) {
return getInternalRootPath(context, null);
}
/**
* 返回以/结尾的外部存储根目录外置卡不可用则返回空字符串
*/
public static String getExternalRootPath(String type) {
File file = null;
if (externalMounted()) {
file = Environment.getExternalStorageDirectory();
}
if (file != null && !TextUtils.isEmpty(type)) {
file = new File(file, type);
//noinspection ResultOfMethodCallIgnored
file.mkdirs();
}
String path = "";
if (file != null) {
path = FileUtils.separator(file.getAbsolutePath());
}
return path;
}
public static String getExternalRootPath() {
return getExternalRootPath(null);
}
/**
* 各种类型的文件的专用的保存路径/结尾
*
* @return 诸如/mnt/sdcard/Android/data/[package]/files/[type]/
*/
public static String getExternalPrivatePath(Context context, String type) {
File file = null;
if (externalMounted()) {
file = context.getExternalFilesDir(type);
}
//高频触发java.lang.NullPointerException,是SD卡不可用或暂时繁忙么?
String path = "";
if (file != null) {
path = FileUtils.separator(file.getAbsolutePath());
}
return path;
}
public static String getExternalPrivatePath(Context context) {
return getExternalPrivatePath(context, null);
}
/**
* 下载的文件的保存路径必须为外部存储/结尾
*
* @return 诸如 /mnt/sdcard/Download/
*/
public static String getDownloadPath() throws RuntimeException {
File file;
if (externalMounted()) {
file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
} else {
throw new RuntimeException("外置存储不可用!");
}
return FileUtils.separator(file.getAbsolutePath());
}
/**
* 各种类型的文件的专用的缓存存储保存路径优先使用外置存储/结尾
*/
public static String getCachePath(Context context, String type) {
File file;
if (externalMounted()) {
file = context.getExternalCacheDir();
} else {
file = context.getCacheDir();
}
if (!TextUtils.isEmpty(type)) {
file = new File(file, type);
//noinspection ResultOfMethodCallIgnored
file.mkdirs();
}
String path = "";
if (file != null) {
path = FileUtils.separator(file.getAbsolutePath());
}
return path;
}
public static String getCachePath(Context context) {
return getCachePath(context, null);
}
/**
* 返回以/结尾的临时存储目录
*/
public static String getTempDirPath(Context context) {
return getExternalPrivatePath(context, "temporary");
}
/**
* 返回临时存储文件路径
*/
public static String getTempFilePath(Context context) {
try {
return File.createTempFile("lyj_", ".tmp", context.getCacheDir()).getAbsolutePath();
} catch (IOException e) {
return getTempDirPath(context) + "lyj.tmp";
}
}
}

@ -383,6 +383,34 @@
</RelativeLayout> </RelativeLayout>
<LinearLayout
android:id="@+id/ll_backup_path"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@drawable/selector_common_bg"
android:orientation="vertical"
android:paddingLeft="20dp"
android:paddingTop="8dp"
android:paddingRight="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/backup_path"
android:textColor="@color/textSecondary"
android:textSize="@dimen/text_normal_size" />
<TextView
android:id="@+id/tv_backup_path"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="5dp"
android:text="@string/backup_path"
android:maxLines="1"
android:ellipsize="end"
android:textColor="@color/textAssist" />
</LinearLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/rl_auto_refresh" android:id="@+id/rl_auto_refresh"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -617,6 +645,32 @@
android:textColor="@color/textSecondary" /> android:textColor="@color/textSecondary" />
</RelativeLayout> </RelativeLayout>
<LinearLayout
android:id="@+id/ll_cathe_path"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@drawable/selector_common_bg"
android:orientation="vertical"
android:paddingLeft="20dp"
android:paddingTop="8dp"
android:paddingRight="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cathe_path"
android:textColor="@color/textSecondary"
android:textSize="@dimen/text_normal_size" />
<TextView
android:id="@+id/tv_cathe_path"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="5dp"
android:text="@string/download_all_tip"
android:textColor="@color/textAssist" />
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/ll_download_all" android:id="@+id/ll_download_all"
android:layout_width="match_parent" android:layout_width="match_parent"

@ -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>

@ -0,0 +1,35 @@
<?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.recyclerview.widget.RecyclerView
android:id="@+id/rv_path"
android:layout_width="match_parent"
android:layout_height="30dp" />
<View
android:layout_width="match_parent"
android:layout_height="0.2dp"
android:background="@color/btn_bg_press_2" />
<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>

@ -503,6 +503,8 @@
<string name="browser">浏览器</string> <string name="browser">浏览器</string>
<string name="add_url">添加网址</string> <string name="add_url">添加网址</string>
<string name="re_seg">章节内容智能分段</string> <string name="re_seg">章节内容智能分段</string>
<string name="backup_path">备份路径</string>
<string name="cathe_path">缓存路径</string>
<string-array name="reset_screen_time"> <string-array name="reset_screen_time">
@ -588,4 +590,10 @@
<item>JsonPath</item> <item>JsonPath</item>
<item>第三方</item> <item>第三方</item>
</string-array> </string-array>
<string-array name="select_folder">
<item>默认路径</item>
<item>系统文件夹选择器</item>
<item>自带选择器</item>
</string-array>
</resources> </resources>

Loading…
Cancel
Save