修改弃用方法

pull/921/head
gedoor 4 years ago
parent 35f384c19f
commit fa92195933
  1. 6
      app/src/main/AndroidManifest.xml
  2. 4
      app/src/main/java/io/legado/app/base/BaseActivity.kt
  3. 54
      app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt
  4. 55
      app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt
  5. 67
      app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt
  6. 52
      app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt
  7. 92
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt
  8. 173
      app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt
  9. 195
      app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt
  10. 297
      app/src/main/java/io/legado/app/ui/filepicker/FilePicker.kt
  11. 145
      app/src/main/java/io/legado/app/ui/filepicker/FilePickerActivity.kt
  12. 46
      app/src/main/java/io/legado/app/ui/filepicker/FilePickerDialog.kt
  13. 33
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt
  14. 10
      app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt
  15. 23
      app/src/main/java/io/legado/app/ui/qrcode/QrCodeResult.kt
  16. 100
      app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt
  17. 30
      app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt
  18. 95
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt
  19. 76
      app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt
  20. 11
      app/src/main/java/io/legado/app/utils/FragmentExtensions.kt

@ -294,6 +294,12 @@
android:name=".ui.about.ReadRecordActivity"
android:configChanges="orientation|screenSize"
android:hardwareAccelerated="true" />
<!-- 选择文件 -->
<activity
android:name=".ui.filepicker.FilePickerActivity"
android:configChanges="orientation|screenSize"
android:hardwareAccelerated="true"
android:theme="@style/AppTheme.Transparent" />
<!-- 文字处理 -->
<activity
android:name=".receiver.SharedReceiverActivity"

@ -154,7 +154,9 @@ abstract class BaseActivity<VB : ViewBinding>(
}
if (AppConfig.isGooglePlay) {
ThemeConfig.getBgImage(this)?.let {
window.decorView.background = it
kotlin.runCatching {
window.decorView.background = it
}
}
}
}

@ -1,6 +1,5 @@
package io.legado.app.ui.book.cache
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.Menu
@ -26,7 +25,7 @@ import io.legado.app.help.BookHelp
import io.legado.app.lib.dialogs.alert
import io.legado.app.service.help.CacheBook
import io.legado.app.ui.filepicker.FilePicker
import io.legado.app.ui.filepicker.FilePickerDialog
import io.legado.app.ui.filepicker.FilePickerParam
import io.legado.app.ui.widget.dialog.TextListDialog
import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers
@ -36,11 +35,25 @@ import kotlinx.coroutines.withContext
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArraySet
class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>(),
FilePickerDialog.CallBack,
CacheAdapter.CallBack {
private val exportRequestCode = 32
private val exportDir = registerForActivityResult(FilePicker()) { uri ->
uri ?: return@registerForActivityResult
if (uri.isContentScheme()) {
contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
ACache.get(this@CacheActivity).put(exportBookPathKey, uri.toString())
startExport(uri.toString())
} else {
uri.path?.let { path ->
ACache.get(this@CacheActivity).put(exportBookPathKey, path)
startExport(path)
}
}
}
private val exportBookPathKey = "exportBookPath"
lateinit var adapter: CacheAdapter
private var groupLiveData: LiveData<List<BookGroup>>? = null
@ -215,9 +228,11 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
if (!path.isNullOrEmpty()) {
default.add(path)
}
FilePicker.selectFolder(this, exportRequestCode, otherActions = default) {
startExport(it)
}
exportDir.launch(
FilePickerParam(
otherActions = default.toTypedArray()
)
)
}
private fun startExport(path: String) {
@ -244,27 +259,4 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
}.show()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
exportRequestCode -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let { uri ->
if (uri.isContentScheme()) {
contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
ACache.get(this@CacheActivity).put(exportBookPathKey, uri.toString())
startExport(uri.toString())
} else {
uri.path?.let { path ->
ACache.get(this@CacheActivity).put(exportBookPathKey, path)
startExport(path)
}
}
}
}
}
}
}

@ -1,6 +1,5 @@
package io.legado.app.ui.book.local
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Build
@ -22,7 +21,6 @@ import io.legado.app.lib.permission.Permissions
import io.legado.app.lib.permission.PermissionsCompat
import io.legado.app.lib.theme.backgroundColor
import io.legado.app.ui.filepicker.FilePicker
import io.legado.app.ui.filepicker.FilePickerDialog
import io.legado.app.ui.widget.SelectActionBar
import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.IO
@ -36,17 +34,31 @@ import java.util.*
* 导入本地书籍界面
*/
class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookViewModel>(),
FilePickerDialog.CallBack,
PopupMenu.OnMenuItemClickListener,
SelectActionBar.CallBack,
ImportBookAdapter.CallBack {
private val requestCodeSelectFolder = 342
ImportBookAdapter.CallBack,
SelectActionBar.CallBack {
private var rootDoc: DocumentFile? = null
private val subDocs = arrayListOf<DocumentFile>()
private lateinit var adapter: ImportBookAdapter
private var localUriLiveData: LiveData<List<String>>? = null
private var sdPath = FileUtils.getSdCardPath()
private var path = sdPath
private val selectFolder = registerForActivityResult(FilePicker()) { uri ->
uri ?: return@registerForActivityResult
if (uri.isContentScheme()) {
contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
AppConfig.importBookPath = uri.toString()
initRootDoc()
} else {
uri.path?.let { path ->
AppConfig.importBookPath = path
initRootDoc()
}
}
}
override val viewModel: ImportBookViewModel
by viewModels()
@ -69,7 +81,7 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_select_folder -> FilePicker.selectFolder(this, requestCodeSelectFolder)
R.id.menu_select_folder -> selectFolder.launch(null)
R.id.menu_scan_folder -> scanFolder()
}
return super.onCompatOptionsItemSelected(item)
@ -129,14 +141,14 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
when {
lastPath.isNullOrEmpty() -> {
binding.tvEmptyMsg.visible()
FilePicker.selectFolder(this, requestCodeSelectFolder)
selectFolder.launch(null)
}
lastPath.isContentScheme() -> {
val rootUri = Uri.parse(lastPath)
rootDoc = DocumentFile.fromTreeUri(this, rootUri)
if (rootDoc == null) {
binding.tvEmptyMsg.visible()
FilePicker.selectFolder(this, requestCodeSelectFolder)
selectFolder.launch(null)
} else {
subDocs.clear()
upPath()
@ -144,7 +156,7 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
}
Build.VERSION.SDK_INT > Build.VERSION_CODES.Q -> {
binding.tvEmptyMsg.visible()
FilePicker.selectFolder(this, requestCodeSelectFolder)
selectFolder.launch(null)
}
else -> {
binding.tvEmptyMsg.visible()
@ -274,29 +286,6 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
requestCodeSelectFolder -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let { uri ->
if (uri.isContentScheme()) {
contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
AppConfig.importBookPath = uri.toString()
initRootDoc()
} else {
uri.path?.let { path ->
AppConfig.importBookPath = path
initRootDoc()
}
}
}
}
}
}
@Synchronized
override fun nextDoc(uri: Uri) {
if (uri.toString().isContentScheme()) {

@ -1,9 +1,7 @@
package io.legado.app.ui.book.read.config
import android.annotation.SuppressLint
import android.app.Activity.RESULT_OK
import android.content.DialogInterface
import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
@ -26,14 +24,14 @@ import io.legado.app.lib.theme.getPrimaryTextColor
import io.legado.app.lib.theme.getSecondaryTextColor
import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.filepicker.FilePicker
import io.legado.app.ui.filepicker.FilePickerDialog
import io.legado.app.ui.filepicker.FilePickerParam
import io.legado.app.utils.*
import io.legado.app.utils.viewbindingdelegate.viewBinding
import rxhttp.wrapper.param.RxHttp
import rxhttp.wrapper.param.toByteArray
import java.io.File
class BgTextConfigDialog : BaseDialogFragment(), FilePickerDialog.CallBack {
class BgTextConfigDialog : BaseDialogFragment() {
companion object {
const val TEXT_COLOR = 121
@ -41,15 +39,27 @@ class BgTextConfigDialog : BaseDialogFragment(), FilePickerDialog.CallBack {
}
private val binding by viewBinding(DialogReadBgTextBinding::bind)
private val requestCodeExport = 131
private val requestCodeImport = 132
private val configFileName = "readConfig.zip"
private lateinit var adapter: BgAdapter
private var primaryTextColor = 0
private var secondaryTextColor = 0
private val importFormNet = "网络导入"
private val selectBgImage = registerForActivityResult(ActivityResultContracts.GetContent()) {
setBgFromUri(it)
}
private val selectExportDir = registerForActivityResult(FilePicker()) {
it?.let {
exportConfig(it)
}
}
private val selectImportDoc = registerForActivityResult(FilePicker()) {
it ?: return@registerForActivityResult
if (it.toString() == importFormNet) {
importNetConfigAlert()
} else {
importConfig(it)
}
}
override fun onStart() {
super.onStart()
@ -164,25 +174,20 @@ class BgTextConfigDialog : BaseDialogFragment(), FilePickerDialog.CallBack {
.show(requireActivity())
}
binding.ivImport.setOnClickListener {
val importFormNet = "网络导入"
val otherActions = arrayListOf(importFormNet)
FilePicker.selectFile(
this@BgTextConfigDialog,
requestCodeImport,
title = getString(R.string.import_str),
allowExtensions = arrayOf("zip"),
otherActions = otherActions
) { action ->
when (action) {
importFormNet -> importNetConfigAlert()
}
}
selectImportDoc.launch(
FilePickerParam(
mode = FilePicker.FILE,
title = getString(R.string.import_str),
allowExtensions = arrayOf("zip"),
otherActions = arrayOf(importFormNet)
)
)
}
binding.ivExport.setOnClickListener {
FilePicker.selectFolder(
this@BgTextConfigDialog,
requestCodeExport,
title = getString(R.string.export_str)
selectExportDir.launch(
FilePickerParam(
title = getString(R.string.export_str)
)
)
}
binding.ivDelete.setOnClickListener {
@ -368,22 +373,6 @@ class BgTextConfigDialog : BaseDialogFragment(), FilePickerDialog.CallBack {
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
requestCodeImport -> if (resultCode == RESULT_OK) {
data?.data?.let { uri ->
importConfig(uri)
}
}
requestCodeExport -> if (resultCode == RESULT_OK) {
data?.data?.let { uri ->
exportConfig(uri)
}
}
}
}
private fun setBgFromUri(uri: Uri) {
if (uri.toString().isContentScheme()) {
val doc = DocumentFile.fromSingleUri(requireContext(), uri)

@ -1,7 +1,6 @@
package io.legado.app.ui.book.source.edit
import android.app.Activity
import android.content.Intent
import android.graphics.Rect
import android.os.Bundle
import android.view.Gravity
@ -26,9 +25,9 @@ import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.backgroundColor
import io.legado.app.ui.book.source.debug.BookSourceDebugActivity
import io.legado.app.ui.filepicker.FilePicker
import io.legado.app.ui.filepicker.FilePickerDialog
import io.legado.app.ui.filepicker.FilePickerParam
import io.legado.app.ui.login.SourceLogin
import io.legado.app.ui.qrcode.QrCodeActivity
import io.legado.app.ui.qrcode.QrCodeResult
import io.legado.app.ui.widget.KeyboardToolPop
import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.utils.*
@ -36,13 +35,10 @@ import kotlin.math.abs
class BookSourceEditActivity :
VMBaseActivity<ActivityBookSourceEditBinding, BookSourceEditViewModel>(false),
FilePickerDialog.CallBack,
KeyboardToolPop.CallBack {
override val viewModel: BookSourceEditViewModel
by viewModels()
private val qrRequestCode = 101
private val selectPathRequestCode = 102
private val adapter = BookSourceEditAdapter()
private val sourceEntities: ArrayList<EditEntity> = ArrayList()
private val searchEntities: ArrayList<EditEntity> = ArrayList()
@ -50,6 +46,20 @@ class BookSourceEditActivity :
private val infoEntities: ArrayList<EditEntity> = ArrayList()
private val tocEntities: ArrayList<EditEntity> = ArrayList()
private val contentEntities: ArrayList<EditEntity> = ArrayList()
private val qrCodeResult = registerForActivityResult(QrCodeResult()) {
it ?: return@registerForActivityResult
viewModel.importSource(it) { source ->
upRecyclerView(source)
}
}
private val selectDoc = registerForActivityResult(FilePicker()) { uri ->
uri ?: return@registerForActivityResult
if (uri.isContentScheme()) {
sendText(uri.toString())
} else {
sendText(uri.path.toString())
}
}
private var mSoftKeyboardTool: PopupWindow? = null
private var mIsSoftKeyBoardShowing = false
@ -98,7 +108,7 @@ class BookSourceEditActivity :
}
R.id.menu_copy_source -> sendToClip(GSON.toJson(getSource()))
R.id.menu_paste_source -> viewModel.pasteSource { upRecyclerView(it) }
R.id.menu_qr_code_camera -> startActivityForResult<QrCodeActivity>(qrRequestCode)
R.id.menu_qr_code_camera -> qrCodeResult.launch(null)
R.id.menu_share_str -> share(GSON.toJson(getSource()))
R.id.menu_share_qr -> shareWithQr(
GSON.toJson(getSource()),
@ -397,7 +407,11 @@ class BookSourceEditActivity :
0 -> insertText(AppConst.urlOption)
1 -> showRuleHelp()
2 -> showRegexHelp()
3 -> FilePicker.selectFile(this, selectPathRequestCode)
3 -> selectDoc.launch(
FilePickerParam(
mode = FilePicker.FILE
)
)
}
}
}
@ -425,28 +439,6 @@ class BookSourceEditActivity :
mSoftKeyboardTool?.dismiss()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
qrRequestCode -> if (resultCode == RESULT_OK) {
data?.getStringExtra("result")?.let {
viewModel.importSource(it) { source ->
upRecyclerView(source)
}
}
}
selectPathRequestCode -> if (resultCode == RESULT_OK) {
data?.data?.let { uri ->
if (uri.isContentScheme()) {
sendText(uri.toString())
} else {
sendText(uri.path.toString())
}
}
}
}
}
private inner class KeyboardOnGlobalChangeListener : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
val rect = Rect()

@ -1,7 +1,6 @@
package io.legado.app.ui.book.source.manage
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.Menu
@ -32,8 +31,8 @@ import io.legado.app.ui.association.ImportBookSourceActivity
import io.legado.app.ui.book.source.debug.BookSourceDebugActivity
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.filepicker.FilePicker
import io.legado.app.ui.filepicker.FilePickerDialog
import io.legado.app.ui.qrcode.QrCodeActivity
import io.legado.app.ui.filepicker.FilePickerParam
import io.legado.app.ui.qrcode.QrCodeResult
import io.legado.app.ui.widget.SelectActionBar
import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.ui.widget.recycler.DragSelectTouchHelper
@ -45,15 +44,11 @@ import java.io.File
class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceViewModel>(),
PopupMenu.OnMenuItemClickListener,
BookSourceAdapter.CallBack,
FilePickerDialog.CallBack,
SelectActionBar.CallBack,
SearchView.OnQueryTextListener {
override val viewModel: BookSourceViewModel
by viewModels()
private val importRecordKey = "bookSourceRecordKey"
private val qrRequestCode = 101
private val importRequestCode = 132
private val exportRequestCode = 65
private lateinit var adapter: BookSourceAdapter
private lateinit var searchView: SearchView
private var bookSourceLiveDate: LiveData<List<BookSource>>? = null
@ -62,6 +57,37 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
private var sort = Sort.Default
private var sortAscending = true
private var snackBar: Snackbar? = null
private val qrResult = registerForActivityResult(QrCodeResult()) {
it ?: return@registerForActivityResult
startActivity<ImportBookSourceActivity> {
putExtra("source", it)
}
}
private val importDoc = registerForActivityResult(FilePicker()) { uri ->
uri ?: return@registerForActivityResult
try {
uri.readText(this)?.let {
val dataKey = IntentDataHelp.putData(it)
startActivity<ImportBookSourceActivity> {
putExtra("dataKey", dataKey)
}
}
} catch (e: Exception) {
toastOnUi("readTextError:${e.localizedMessage}")
}
}
private val exportDir = registerForActivityResult(FilePicker()) { uri ->
uri ?: return@registerForActivityResult
if (uri.isContentScheme()) {
DocumentFile.fromTreeUri(this, uri)?.let {
viewModel.exportSelection(adapter.getSelection(), it)
}
} else {
uri.path?.let {
viewModel.exportSelection(adapter.getSelection(), File(it))
}
}
}
override fun getViewBinding(): ActivityBookSourceBinding {
return ActivityBookSourceBinding.inflate(layoutInflater)
@ -95,14 +121,18 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_add_book_source -> startActivity<BookSourceEditActivity>()
R.id.menu_import_source_qr -> startActivityForResult<QrCodeActivity>(qrRequestCode)
R.id.menu_import_source_qr -> qrResult.launch(null)
R.id.menu_share_source -> viewModel.shareSelection(adapter.getSelection()) {
startActivity(Intent.createChooser(it, getString(R.string.share_selected_source)))
}
R.id.menu_group_manage ->
GroupManageDialog().show(supportFragmentManager, "groupManage")
R.id.menu_import_source_local -> FilePicker
.selectFile(this, importRequestCode, allowExtensions = arrayOf("txt", "json"))
R.id.menu_import_source_local -> importDoc.launch(
FilePickerParam(
mode = FilePicker.FILE,
allowExtensions = arrayOf("txt", "json")
)
)
R.id.menu_import_source_onLine -> showImportDialog()
R.id.menu_sort_manual -> {
item.isChecked = true
@ -292,7 +322,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
R.id.menu_bottom_sel -> viewModel.bottomSource(*adapter.getSelection().toTypedArray())
R.id.menu_add_group -> selectionAddToGroups()
R.id.menu_remove_group -> selectionRemoveFromGroups()
R.id.menu_export_selection -> FilePicker.selectFolder(this, exportRequestCode)
R.id.menu_export_selection -> exportDir.launch(null)
}
return true
}
@ -467,46 +497,6 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
qrRequestCode -> if (resultCode == RESULT_OK) {
data?.getStringExtra("result")?.let {
startActivity<ImportBookSourceActivity> {
putExtra("source", it)
}
}
}
importRequestCode -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let { uri ->
try {
uri.readText(this)?.let {
val dataKey = IntentDataHelp.putData(it)
startActivity<ImportBookSourceActivity> {
putExtra("dataKey", dataKey)
}
}
} catch (e: Exception) {
toastOnUi("readTextError:${e.localizedMessage}")
}
}
}
exportRequestCode -> {
data?.data?.let { uri ->
if (uri.isContentScheme()) {
DocumentFile.fromTreeUri(this, uri)?.let {
viewModel.exportSelection(adapter.getSelection(), it)
}
} else {
uri.path?.let {
viewModel.exportSelection(adapter.getSelection(), File(it))
}
}
}
}
}
}
override fun finish() {
if (searchView.query.isNullOrEmpty()) {
super.finish()

@ -2,31 +2,105 @@ package io.legado.app.ui.config
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Bundle
import android.text.InputType
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.documentfile.provider.DocumentFile
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.Preference
import io.legado.app.R
import io.legado.app.base.BasePreferenceFragment
import io.legado.app.constant.PreferKey
import io.legado.app.help.AppConfig
import io.legado.app.help.LocalConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.BookWebDav
import io.legado.app.help.storage.ImportOldData
import io.legado.app.help.storage.Restore
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.permission.Permissions
import io.legado.app.lib.permission.PermissionsCompat
import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.accentColor
import io.legado.app.ui.filepicker.FilePickerDialog
import io.legado.app.ui.filepicker.FilePicker
import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.utils.applyTint
import io.legado.app.utils.getPrefString
import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers
import splitties.init.appCtx
class BackupConfigFragment : BasePreferenceFragment(),
SharedPreferences.OnSharedPreferenceChangeListener,
FilePickerDialog.CallBack {
SharedPreferences.OnSharedPreferenceChangeListener {
private val selectBackupPath = registerForActivityResult(FilePicker()) { uri ->
uri ?: return@registerForActivityResult
if (uri.isContentScheme()) {
appCtx.contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
AppConfig.backupPath = uri.toString()
} else {
AppConfig.backupPath = uri.path
}
}
private val backupDir = registerForActivityResult(FilePicker()) { uri ->
uri ?: return@registerForActivityResult
if (uri.isContentScheme()) {
appCtx.contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
AppConfig.backupPath = uri.toString()
Coroutine.async {
Backup.backup(appCtx, uri.toString())
}.onSuccess {
appCtx.toastOnUi(R.string.backup_success)
}
} else {
uri.path?.let { path ->
AppConfig.backupPath = path
Coroutine.async {
Backup.backup(appCtx, path)
}.onSuccess {
appCtx.toastOnUi(R.string.backup_success)
}
}
}
}
private val restoreDir = registerForActivityResult(FilePicker()) { uri ->
uri ?: return@registerForActivityResult
if (uri.isContentScheme()) {
appCtx.contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
AppConfig.backupPath = uri.toString()
Coroutine.async {
Restore.restore(appCtx, uri.toString())
}
} else {
uri.path?.let { path ->
AppConfig.backupPath = path
Coroutine.async {
Restore.restore(appCtx, path)
}
}
}
}
private val restoreOld = registerForActivityResult(FilePicker()) { uri ->
uri?.let {
ImportOldData.importUri(appCtx, uri)
}
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_config_backup)
@ -53,7 +127,7 @@ class BackupConfigFragment : BasePreferenceFragment(),
upPreferenceSummary(PreferKey.webDavPassword, getPrefString(PreferKey.webDavPassword))
upPreferenceSummary(PreferKey.backupPath, getPrefString(PreferKey.backupPath))
findPreference<io.legado.app.ui.widget.prefs.Preference>("web_dav_restore")
?.onLongClick = { BackupRestoreUi.restoreByFolder(this) }
?.onLongClick = { restoreDir.launch(null) }
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -135,11 +209,11 @@ class BackupConfigFragment : BasePreferenceFragment(),
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
when (preference?.key) {
PreferKey.backupPath -> BackupRestoreUi.selectBackupFolder(this)
PreferKey.backupPath -> selectBackupPath.launch(null)
PreferKey.restoreIgnore -> restoreIgnore()
"web_dav_backup" -> BackupRestoreUi.backup(this)
"web_dav_restore" -> BackupRestoreUi.restore(this)
"import_old" -> BackupRestoreUi.importOldData(this)
"web_dav_backup" -> backup()
"web_dav_restore" -> restore()
"import_old" -> restoreOld.launch(null)
}
return super.onPreferenceTreeClick(preference)
}
@ -159,8 +233,81 @@ class BackupConfigFragment : BasePreferenceFragment(),
}.show()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
BackupRestoreUi.onActivityResult(requestCode, resultCode, data)
fun backup() {
val backupPath = AppConfig.backupPath
if (backupPath.isNullOrEmpty()) {
backupDir.launch(null)
} else {
if (backupPath.isContentScheme()) {
val uri = Uri.parse(backupPath)
val doc = DocumentFile.fromTreeUri(requireContext(), uri)
if (doc?.canWrite() == true) {
Coroutine.async {
Backup.backup(requireContext(), backupPath)
}.onSuccess {
toastOnUi(R.string.backup_success)
}
} else {
backupDir.launch(null)
}
} else {
backupUsePermission(backupPath)
}
}
}
private fun backupUsePermission(path: String) {
PermissionsCompat.Builder(this)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
Coroutine.async {
AppConfig.backupPath = path
Backup.backup(requireContext(), path)
}.onSuccess {
toastOnUi(R.string.backup_success)
}
}
.request()
}
fun restore() {
Coroutine.async(context = Dispatchers.Main) {
BookWebDav.showRestoreDialog(requireContext())
}.onError {
longToast("WebDavError:${it.localizedMessage}\n将从本地备份恢复。")
val backupPath = getPrefString(PreferKey.backupPath)
if (backupPath?.isNotEmpty() == true) {
if (backupPath.isContentScheme()) {
val uri = Uri.parse(backupPath)
val doc = DocumentFile.fromTreeUri(requireContext(), uri)
if (doc?.canWrite() == true) {
Restore.restore(requireContext(), backupPath)
} else {
restoreDir.launch(null)
}
} else {
restoreUsePermission(backupPath)
}
} else {
restoreDir.launch(null)
}
}
}
private fun restoreUsePermission(path: String) {
PermissionsCompat.Builder(this)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
Coroutine.async {
AppConfig.backupPath = path
Restore.restoreDatabase(path)
Restore.restoreConfig(path)
}
}
.request()
}
}

@ -1,195 +0,0 @@
package io.legado.app.ui.config
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment
import io.legado.app.R
import io.legado.app.constant.PreferKey
import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.BookWebDav
import io.legado.app.help.storage.ImportOldData
import io.legado.app.help.storage.Restore
import io.legado.app.lib.permission.Permissions
import io.legado.app.lib.permission.PermissionsCompat
import io.legado.app.ui.filepicker.FilePicker
import io.legado.app.utils.getPrefString
import io.legado.app.utils.isContentScheme
import io.legado.app.utils.longToast
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers.Main
import splitties.init.appCtx
object BackupRestoreUi {
private const val selectFolderRequestCode = 21
private const val backupSelectRequestCode = 22
private const val restoreSelectRequestCode = 33
private const val oldDataRequestCode = 11
fun backup(fragment: Fragment) {
val backupPath = AppConfig.backupPath
if (backupPath.isNullOrEmpty()) {
selectBackupFolder(fragment, backupSelectRequestCode)
} else {
if (backupPath.isContentScheme()) {
val uri = Uri.parse(backupPath)
val doc = DocumentFile.fromTreeUri(fragment.requireContext(), uri)
if (doc?.canWrite() == true) {
Coroutine.async {
Backup.backup(fragment.requireContext(), backupPath)
}.onSuccess {
fragment.toastOnUi(R.string.backup_success)
}
} else {
selectBackupFolder(fragment, backupSelectRequestCode)
}
} else {
backupUsePermission(fragment, backupPath)
}
}
}
private fun backupUsePermission(
fragment: Fragment,
path: String
) {
PermissionsCompat.Builder(fragment)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
Coroutine.async {
AppConfig.backupPath = path
Backup.backup(fragment.requireContext(), path)
}.onSuccess {
fragment.toastOnUi(R.string.backup_success)
}
}
.request()
}
fun selectBackupFolder(fragment: Fragment, requestCode: Int = selectFolderRequestCode) {
FilePicker.selectFolder(fragment, requestCode)
}
fun restore(fragment: Fragment) {
Coroutine.async(context = Main) {
BookWebDav.showRestoreDialog(fragment.requireContext())
}.onError {
fragment.longToast("WebDavError:${it.localizedMessage}\n将从本地备份恢复。")
val backupPath = fragment.getPrefString(PreferKey.backupPath)
if (backupPath?.isNotEmpty() == true) {
if (backupPath.isContentScheme()) {
val uri = Uri.parse(backupPath)
val doc = DocumentFile.fromTreeUri(fragment.requireContext(), uri)
if (doc?.canWrite() == true) {
Restore.restore(fragment.requireContext(), backupPath)
} else {
selectBackupFolder(fragment, restoreSelectRequestCode)
}
} else {
restoreUsePermission(fragment, backupPath)
}
} else {
selectBackupFolder(fragment, restoreSelectRequestCode)
}
}
}
fun restoreByFolder(fragment: Fragment) {
selectBackupFolder(fragment, restoreSelectRequestCode)
}
private fun restoreUsePermission(fragment: Fragment, path: String) {
PermissionsCompat.Builder(fragment)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
Coroutine.async {
AppConfig.backupPath = path
Restore.restoreDatabase(path)
Restore.restoreConfig(path)
}
}
.request()
}
fun importOldData(fragment: Fragment) {
FilePicker.selectFolder(fragment, oldDataRequestCode)
}
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
backupSelectRequestCode -> if (resultCode == RESULT_OK) {
data?.data?.let { uri ->
if (uri.isContentScheme()) {
appCtx.contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
AppConfig.backupPath = uri.toString()
Coroutine.async {
Backup.backup(appCtx, uri.toString())
}.onSuccess {
appCtx.toastOnUi(R.string.backup_success)
}
} else {
uri.path?.let { path ->
AppConfig.backupPath = path
Coroutine.async {
Backup.backup(appCtx, path)
}.onSuccess {
appCtx.toastOnUi(R.string.backup_success)
}
}
}
}
}
restoreSelectRequestCode -> if (resultCode == RESULT_OK) {
data?.data?.let { uri ->
if (uri.isContentScheme()) {
appCtx.contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
AppConfig.backupPath = uri.toString()
Coroutine.async {
Restore.restore(appCtx, uri.toString())
}
} else {
uri.path?.let { path ->
AppConfig.backupPath = path
Coroutine.async {
Restore.restore(appCtx, path)
}
}
}
}
}
selectFolderRequestCode -> if (resultCode == RESULT_OK) {
data?.data?.let { uri ->
if (uri.isContentScheme()) {
appCtx.contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
AppConfig.backupPath = uri.toString()
} else {
AppConfig.backupPath = uri.path
}
}
}
oldDataRequestCode -> if (resultCode == RESULT_OK) {
data?.data?.let { uri ->
ImportOldData.importUri(appCtx, uri)
}
}
}
}
}

@ -1,284 +1,43 @@
package io.legado.app.ui.filepicker
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import io.legado.app.R
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.permission.Permissions
import io.legado.app.lib.permission.PermissionsCompat
import android.net.Uri
import androidx.activity.result.contract.ActivityResultContract
@Suppress("unused")
object FilePicker {
class FilePicker : ActivityResultContract<FilePickerParam, Uri?>() {
fun selectFolder(
activity: AppCompatActivity,
requestCode: Int,
title: String = activity.getString(R.string.select_folder),
otherActions: List<String>? = null,
otherFun: ((action: String) -> Unit)? = null
) {
val selectList = arrayListOf(activity.getString(R.string.sys_folder_picker))
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
selectList.add(activity.getString(R.string.app_folder_picker))
}
otherActions?.let {
selectList.addAll(otherActions)
}
activity.alert(title = title) {
items(selectList) { _, index ->
when (index) {
0 -> {
kotlin.runCatching {
val intent = createSelectDirIntent()
activity.startActivityForResult(intent, requestCode)
}.onFailure {
checkPermissions(activity) {
FilePickerDialog.show(
activity.supportFragmentManager,
requestCode,
mode = FilePickerDialog.DIRECTORY
)
}
}
}
else -> {
val selectText = selectList[index]
if (selectText == activity.getString(R.string.app_folder_picker)) {
checkPermissions(activity) {
FilePickerDialog.show(
activity.supportFragmentManager,
requestCode,
mode = FilePickerDialog.DIRECTORY
)
}
} else {
otherFun?.invoke(selectText)
}
}
}
}
}.show()
}
fun selectFolder(
fragment: Fragment,
requestCode: Int,
title: String = fragment.getString(R.string.select_folder),
otherActions: List<String>? = null,
otherFun: ((action: String) -> Unit)? = null
) {
val selectList = arrayListOf(fragment.getString(R.string.sys_folder_picker))
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
selectList.add(fragment.getString(R.string.app_folder_picker))
}
otherActions?.let {
selectList.addAll(otherActions)
}
fragment.alert(title = title) {
items(selectList) { _, index ->
when (index) {
0 -> {
kotlin.runCatching {
val intent = createSelectDirIntent()
fragment.startActivityForResult(intent, requestCode)
}.onFailure {
checkPermissions(fragment) {
FilePickerDialog.show(
fragment.childFragmentManager,
requestCode,
mode = FilePickerDialog.DIRECTORY
)
}
}
}
else -> {
val selectText = selectList[index]
if (selectText == fragment.getString(R.string.app_folder_picker)) {
checkPermissions(fragment) {
FilePickerDialog.show(
fragment.childFragmentManager,
requestCode,
mode = FilePickerDialog.DIRECTORY
)
}
} else {
otherFun?.invoke(selectText)
}
}
}
}
}.show()
}
fun selectFile(
activity: AppCompatActivity,
requestCode: Int,
title: String = activity.getString(R.string.select_file),
allowExtensions: Array<String> = arrayOf(),
otherActions: List<String>? = null,
otherFun: ((action: String) -> Unit)? = null
) {
val selectList = arrayListOf(activity.getString(R.string.sys_file_picker))
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
selectList.add(activity.getString(R.string.app_file_picker))
}
otherActions?.let {
selectList.addAll(otherActions)
}
activity.alert(title = title) {
items(selectList) { _, index ->
when (index) {
0 -> {
kotlin.runCatching {
val intent = createSelectFileIntent()
intent.putExtra(
Intent.EXTRA_MIME_TYPES,
typesOfExtensions(allowExtensions)
)
activity.startActivityForResult(intent, requestCode)
}.onFailure {
checkPermissions(activity) {
FilePickerDialog.show(
activity.supportFragmentManager,
requestCode,
mode = FilePickerDialog.FILE,
allowExtensions = allowExtensions
)
}
}
}
else -> {
val selectText = selectList[index]
if (selectText == activity.getString(R.string.app_file_picker)) {
checkPermissions(activity) {
FilePickerDialog.show(
activity.supportFragmentManager,
requestCode,
mode = FilePickerDialog.FILE,
allowExtensions = allowExtensions
)
}
} else {
otherFun?.invoke(selectText)
}
}
}
}
}.show()
companion object {
const val DIRECTORY = 0
const val FILE = 1
}
fun selectFile(
fragment: Fragment,
requestCode: Int,
title: String = fragment.getString(R.string.select_file),
allowExtensions: Array<String> = arrayOf(),
otherActions: List<String>? = null,
otherFun: ((action: String) -> Unit)? = null
) {
val selectList = arrayListOf(fragment.getString(R.string.sys_file_picker))
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
selectList.add(fragment.getString(R.string.app_file_picker))
override fun createIntent(context: Context, input: FilePickerParam?): Intent {
val intent = Intent(context, FilePickerActivity::class.java)
input?.let {
intent.putExtra("mode", it.mode)
intent.putExtra("title", it.title)
intent.putExtra("allowExtensions", it.allowExtensions)
intent.putExtra("otherActions", it.otherActions)
}
otherActions?.let {
selectList.addAll(otherActions)
}
fragment.alert(title = title) {
items(selectList) { _, index ->
when (index) {
0 -> {
kotlin.runCatching {
val intent = createSelectFileIntent()
intent.putExtra(
Intent.EXTRA_MIME_TYPES,
typesOfExtensions(allowExtensions)
)
fragment.startActivityForResult(intent, requestCode)
}.onFailure {
checkPermissions(fragment) {
FilePickerDialog.show(
fragment.childFragmentManager,
requestCode,
mode = FilePickerDialog.FILE,
allowExtensions = allowExtensions
)
}
}
}
else -> {
val selectText = selectList[index]
if (selectText == fragment.getString(R.string.app_file_picker)) {
checkPermissions(fragment) {
FilePickerDialog.show(
fragment.childFragmentManager,
requestCode,
mode = FilePickerDialog.FILE,
allowExtensions = allowExtensions
)
}
} else {
otherFun?.invoke(selectText)
}
}
}
}
}.show()
}
private fun createSelectFileIntent(): Intent {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
intent.type = "*/*"
return intent
}
private fun createSelectDirIntent(): Intent {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
return intent
}
private fun checkPermissions(fragment: Fragment, success: (() -> Unit)? = null) {
PermissionsCompat.Builder(fragment)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
success?.invoke()
}
.request()
override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
if (resultCode == RESULT_OK) {
return intent?.data
}
return null
}
private fun checkPermissions(activity: AppCompatActivity, success: (() -> Unit)? = null) {
PermissionsCompat.Builder(activity)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
success?.invoke()
}
.request()
}
}
private fun typesOfExtensions(allowExtensions: Array<String>): Array<String> {
val types = hashSetOf<String>()
if (allowExtensions.isNullOrEmpty()) {
types.add("*/*")
} else {
allowExtensions.forEach {
when (it) {
"*" -> types.add("*/*")
"txt", "xml" -> types.add("text/*")
else -> types.add("application/$it")
}
}
}
return types.toTypedArray()
}
}
@Suppress("ArrayInDataClass")
data class FilePickerParam(
var mode: Int = 0,
var title: String? = null,
var allowExtensions: Array<String> = arrayOf(),
var otherActions: Array<String>? = null,
)

@ -0,0 +1,145 @@
package io.legado.app.ui.filepicker
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.CallSuper
import io.legado.app.R
import io.legado.app.base.BaseActivity
import io.legado.app.constant.Theme
import io.legado.app.databinding.ActivityTranslucenceBinding
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.permission.Permissions
import io.legado.app.lib.permission.PermissionsCompat
import io.legado.app.utils.isContentScheme
import java.io.File
class FilePickerActivity :
BaseActivity<ActivityTranslucenceBinding>(
theme = Theme.Transparent
), FilePickerDialog.CallBack {
private val selectDocTree =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) {
onResult(Intent().setData(it))
}
private val selectDoc = registerForActivityResult(GetContent()) {
onResult(Intent().setData(it))
}
override fun getViewBinding(): ActivityTranslucenceBinding {
return ActivityTranslucenceBinding.inflate(layoutInflater)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
val mode = intent.getIntExtra("mode", 0)
val allowExtensions = intent.getStringArrayExtra("allowExtensions")
val selectList = if (mode == FilePicker.DIRECTORY) {
arrayListOf(getString(R.string.sys_folder_picker))
} else {
arrayListOf(getString(R.string.sys_file_picker))
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
selectList.add(getString(R.string.app_folder_picker))
}
intent.getStringArrayListExtra("otherActions")?.let {
selectList.addAll(it)
}
val title = intent.getStringExtra("title") ?: let {
if (mode == FilePicker.DIRECTORY) {
return@let getString(R.string.select_folder)
} else {
return@let getString(R.string.select_file)
}
}
alert(title) {
items(selectList) { _, index ->
when (index) {
0 -> if (mode == FilePicker.DIRECTORY) {
selectDocTree.launch(null)
} else {
selectDoc.launch(typesOfExtensions(allowExtensions))
}
2 -> if (mode == FilePicker.DIRECTORY) {
checkPermissions {
FilePickerDialog.show(
supportFragmentManager,
mode = FilePicker.DIRECTORY
)
}
} else {
checkPermissions {
FilePickerDialog.show(
supportFragmentManager,
mode = FilePicker.FILE,
allowExtensions = allowExtensions
)
}
}
else -> {
val path = selectList[index]
val uri = if (path.isContentScheme()) {
Uri.fromFile(File(path))
} else {
Uri.parse(path)
}
onResult(Intent().setData(uri))
}
}
}
onCancelled {
finish()
}
}.show()
}
private fun checkPermissions(success: (() -> Unit)? = null) {
PermissionsCompat.Builder(this)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
success?.invoke()
}
.request()
}
private fun typesOfExtensions(allowExtensions: Array<String>?): Array<String> {
val types = hashSetOf<String>()
if (allowExtensions.isNullOrEmpty()) {
types.add("*/*")
} else {
allowExtensions.forEach {
when (it) {
"*" -> types.add("*/*")
"txt", "xml" -> types.add("text/*")
else -> types.add("application/$it")
}
}
}
return types.toTypedArray()
}
override fun onResult(data: Intent) {
setResult(RESULT_OK, data)
finish()
}
class GetContent : ActivityResultContract<Array<String>, Uri>() {
@CallSuper
override fun createIntent(context: Context, input: Array<String>): Intent {
return Intent(Intent.ACTION_GET_CONTENT)
.addCategory(Intent.CATEGORY_OPENABLE)
.putExtra(Intent.EXTRA_MIME_TYPES, input)
.setType("*/*")
}
override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
return if (intent == null || resultCode != RESULT_OK) null else intent.data
}
}
}

@ -1,6 +1,6 @@
package io.legado.app.ui.filepicker
import android.app.Activity
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
@ -8,7 +8,6 @@ import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
@ -16,8 +15,9 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R
import io.legado.app.databinding.DialogFileChooserBinding
import io.legado.app.lib.permission.Permissions
import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.filepicker.FilePicker.Companion.DIRECTORY
import io.legado.app.ui.filepicker.FilePicker.Companion.FILE
import io.legado.app.ui.filepicker.adapter.FileAdapter
import io.legado.app.ui.filepicker.adapter.PathAdapter
import io.legado.app.ui.widget.recycler.VerticalDivider
@ -33,12 +33,9 @@ class FilePickerDialog : DialogFragment(),
companion object {
const val tag = "FileChooserDialog"
const val DIRECTORY = 0
const val FILE = 1
fun show(
manager: FragmentManager,
requestCode: Int,
mode: Int = FILE,
title: String? = null,
initPath: String? = null,
@ -51,7 +48,6 @@ class FilePickerDialog : DialogFragment(),
FilePickerDialog().apply {
val bundle = Bundle()
bundle.putInt("mode", mode)
bundle.putInt("requestCode", requestCode)
bundle.putString("title", title)
bundle.putBoolean("isShowHomeDir", isShowHomeDir)
bundle.putBoolean("isShowUpDir", isShowUpDir)
@ -71,20 +67,6 @@ class FilePickerDialog : DialogFragment(),
override var isShowHomeDir: Boolean = false
override var isShowUpDir: Boolean = true
override var isShowHideDir: Boolean = false
private val queryPermission =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
var hasPermission = true
it.forEach { (t, u) ->
if (!u) {
hasPermission = false
toastOnUi(t)
}
}
if (hasPermission) {
refreshCurrentDirPath(initPath)
}
}
private var requestCode: Int = 0
var title: String? = null
private var initPath = FileUtils.getSdCardPath()
private var mode: Int = FILE
@ -111,7 +93,6 @@ class FilePickerDialog : DialogFragment(),
binding.toolBar.setBackgroundColor(primaryColor)
view.setBackgroundResource(R.color.background_card)
arguments?.let {
requestCode = it.getInt("requestCode")
mode = it.getInt("mode", FILE)
title = it.getString("title")
isShowHomeDir = it.getBoolean("isShowHomeDir")
@ -132,7 +113,7 @@ class FilePickerDialog : DialogFragment(),
}
initMenu()
initContentView()
queryPermission.launch(Permissions.Group.STORAGE)
refreshCurrentDirPath(initPath)
}
private fun initMenu() {
@ -168,11 +149,6 @@ class FilePickerDialog : DialogFragment(),
setData(it)
dismissAllowingStateLoss()
}
else -> item?.title?.let {
(parentFragment as? CallBack)?.onMenuClick(it.toString())
(activity as? CallBack)?.onMenuClick(it.toString())
dismissAllowingStateLoss()
}
}
return true
}
@ -225,14 +201,16 @@ class FilePickerDialog : DialogFragment(),
private fun setData(path: String) {
val data = Intent().setData(Uri.fromFile(File(path)))
(parentFragment as? CallBack)
?.onActivityResult(requestCode, Activity.RESULT_OK, data)
(activity as? CallBack)
?.onActivityResult(requestCode, Activity.RESULT_OK, data)
(parentFragment as? CallBack)?.onResult(data)
(activity as? CallBack)?.onResult(data)
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
activity?.finish()
}
interface CallBack {
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
fun onMenuClick(menu: String) {}
fun onResult(data: Intent)
}
}

@ -1,8 +1,6 @@
package io.legado.app.ui.main.bookshelf
import android.annotation.SuppressLint
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
@ -30,7 +28,7 @@ import io.legado.app.ui.book.group.GroupManageDialog
import io.legado.app.ui.book.local.ImportBookActivity
import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.filepicker.FilePicker
import io.legado.app.ui.filepicker.FilePickerDialog
import io.legado.app.ui.filepicker.FilePickerParam
import io.legado.app.ui.main.MainViewModel
import io.legado.app.ui.main.bookshelf.books.BooksFragment
import io.legado.app.utils.*
@ -41,10 +39,8 @@ import io.legado.app.utils.viewbindingdelegate.viewBinding
*/
class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_bookshelf),
TabLayout.OnTabSelectedListener,
FilePickerDialog.CallBack,
SearchView.OnQueryTextListener {
private val requestCodeImportBookshelf = 312
private val binding by viewBinding(FragmentBookshelfBinding::bind)
override val viewModel: BookshelfViewModel by viewModels()
private val activityViewModel: MainViewModel by activityViewModels()
@ -53,6 +49,11 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
private var bookGroupLiveData: LiveData<List<BookGroup>>? = null
private val bookGroups = mutableListOf<BookGroup>()
private val fragmentMap = hashMapOf<Long, BooksFragment>()
private val importBookshelf = registerForActivityResult(FilePicker()) {
it?.readText(requireContext())?.let { text ->
viewModel.importBookshelf(text, selectedGroup.groupId)
}
}
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
tabLayout = binding.titleBar.findViewById(R.id.tab_layout)
@ -229,28 +230,16 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
}
noButton()
neutralButton(R.string.select_file) {
FilePicker.selectFile(
this@BookshelfFragment,
requestCodeImportBookshelf,
allowExtensions = arrayOf("txt", "json")
importBookshelf.launch(
FilePickerParam(
mode = FilePicker.FILE,
allowExtensions = arrayOf("txt", "json")
)
)
}
}.show()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
requestCodeImportBookshelf -> if (resultCode == RESULT_OK) {
data?.data?.let { uri ->
uri.readText(requireContext())?.let {
viewModel.importBookshelf(it, selectedGroup.groupId)
}
}
}
}
}
private inner class TabFragmentPageAdapter(fm: FragmentManager) :
FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {

@ -1,6 +1,5 @@
package io.legado.app.ui.main.my
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.view.Menu
@ -21,10 +20,8 @@ import io.legado.app.ui.about.AboutActivity
import io.legado.app.ui.about.DonateActivity
import io.legado.app.ui.about.ReadRecordActivity
import io.legado.app.ui.book.source.manage.BookSourceActivity
import io.legado.app.ui.config.BackupRestoreUi
import io.legado.app.ui.config.ConfigActivity
import io.legado.app.ui.config.ConfigViewModel
import io.legado.app.ui.filepicker.FilePickerDialog
import io.legado.app.ui.replace.ReplaceRuleActivity
import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.ui.widget.prefs.NameListPreference
@ -33,7 +30,7 @@ import io.legado.app.ui.widget.prefs.SwitchPreference
import io.legado.app.utils.*
import io.legado.app.utils.viewbindingdelegate.viewBinding
class MyFragment : BaseFragment(R.layout.fragment_my_config), FilePickerDialog.CallBack {
class MyFragment : BaseFragment(R.layout.fragment_my_config) {
private val binding by viewBinding(FragmentMyConfigBinding::bind)
@ -59,11 +56,6 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config), FilePickerDialog.C
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
BackupRestoreUi.onActivityResult(requestCode, resultCode, data)
}
/**
* 配置
*/

@ -0,0 +1,23 @@
package io.legado.app.ui.qrcode
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
import androidx.activity.result.contract.ActivityResultContract
class QrCodeResult : ActivityResultContract<Unit, String?>() {
override fun createIntent(context: Context, input: Unit?): Intent {
return Intent(context, QrCodeActivity::class.java)
}
override fun parseResult(resultCode: Int, intent: Intent?): String? {
if (resultCode == RESULT_OK) {
intent?.getStringExtra("result")?.let {
return it
}
}
return null
}
}

@ -2,7 +2,6 @@ package io.legado.app.ui.replace
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
@ -30,8 +29,8 @@ import io.legado.app.lib.theme.primaryTextColor
import io.legado.app.service.help.ReadBook
import io.legado.app.ui.association.ImportReplaceRuleActivity
import io.legado.app.ui.filepicker.FilePicker
import io.legado.app.ui.filepicker.FilePickerDialog
import io.legado.app.ui.qrcode.QrCodeActivity
import io.legado.app.ui.filepicker.FilePickerParam
import io.legado.app.ui.qrcode.QrCodeResult
import io.legado.app.ui.replace.edit.ReplaceEditActivity
import io.legado.app.ui.widget.SelectActionBar
import io.legado.app.ui.widget.dialog.TextDialog
@ -47,24 +46,50 @@ import java.io.File
class ReplaceRuleActivity : VMBaseActivity<ActivityReplaceRuleBinding, ReplaceRuleViewModel>(),
SearchView.OnQueryTextListener,
PopupMenu.OnMenuItemClickListener,
FilePickerDialog.CallBack,
SelectActionBar.CallBack,
ReplaceRuleAdapter.CallBack {
override val viewModel: ReplaceRuleViewModel by viewModels()
private val importRecordKey = "replaceRuleRecordKey"
private val importRequestCode = 132
private val importRequestCodeQr = 133
private val exportRequestCode = 234
private val editActivity =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
setResult(RESULT_OK)
}
private lateinit var adapter: ReplaceRuleAdapter
private lateinit var searchView: SearchView
private var groups = hashSetOf<String>()
private var groupMenu: SubMenu? = null
private var replaceRuleLiveData: LiveData<List<ReplaceRule>>? = null
private var dataInit = false
private val qrCodeResult = registerForActivityResult(QrCodeResult()) {
it ?: return@registerForActivityResult
startActivity<ImportReplaceRuleActivity> {
putExtra("source", it)
}
}
private val editActivity =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
setResult(RESULT_OK)
}
private val importDoc = registerForActivityResult(FilePicker()) { uri ->
kotlin.runCatching {
uri?.readText(this)?.let {
val dataKey = IntentDataHelp.putData(it)
startActivity<ImportReplaceRuleActivity> {
putExtra("dataKey", dataKey)
}
}
}.onFailure {
toastOnUi("readTextError:${it.localizedMessage}")
}
}
private val exportDir = registerForActivityResult(FilePicker()) { uri ->
uri ?: return@registerForActivityResult
if (uri.isContentScheme()) {
DocumentFile.fromTreeUri(this, uri)?.let {
viewModel.exportSelection(adapter.getSelection(), it)
}
} else {
uri.path?.let {
viewModel.exportSelection(adapter.getSelection(), File(it))
}
}
}
override fun getViewBinding(): ActivityReplaceRuleBinding {
return ActivityReplaceRuleBinding.inflate(layoutInflater)
@ -190,9 +215,13 @@ class ReplaceRuleActivity : VMBaseActivity<ActivityReplaceRuleBinding, ReplaceRu
R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelection())
R.id.menu_import_source_onLine -> showImportDialog()
R.id.menu_import_source_local -> FilePicker
.selectFile(this, importRequestCode, allowExtensions = arrayOf("txt", "json"))
R.id.menu_import_source_qr -> startActivityForResult<QrCodeActivity>(importRequestCodeQr)
R.id.menu_import_source_local -> importDoc.launch(
FilePickerParam(
mode = FilePicker.FILE,
allowExtensions = arrayOf("txt", "json")
)
)
R.id.menu_import_source_qr -> qrCodeResult.launch(null)
R.id.menu_help -> showHelp()
else -> if (item.groupId == R.id.replace_group) {
searchView.setQuery("group:${item.title}", true)
@ -205,7 +234,7 @@ class ReplaceRuleActivity : VMBaseActivity<ActivityReplaceRuleBinding, ReplaceRu
when (item?.itemId) {
R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelection())
R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection())
R.id.menu_export_selection -> FilePicker.selectFolder(this, exportRequestCode)
R.id.menu_export_selection -> exportDir.launch(null)
}
return false
}
@ -263,47 +292,6 @@ class ReplaceRuleActivity : VMBaseActivity<ActivityReplaceRuleBinding, ReplaceRu
return false
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode != RESULT_OK) return
when (requestCode) {
importRequestCode -> {
data?.data?.let { uri ->
kotlin.runCatching {
uri.readText(this)?.let {
val dataKey = IntentDataHelp.putData(it)
startActivity<ImportReplaceRuleActivity> {
putExtra("dataKey", dataKey)
}
}
}.onFailure {
toastOnUi("readTextError:${it.localizedMessage}")
}
}
}
importRequestCodeQr -> {
data?.getStringExtra("result")?.let {
startActivity<ImportReplaceRuleActivity> {
putExtra("source", it)
}
}
}
exportRequestCode -> {
data?.data?.let { uri ->
if (uri.isContentScheme()) {
DocumentFile.fromTreeUri(this, uri)?.let {
viewModel.exportSelection(adapter.getSelection(), it)
}
} else {
uri.path?.let {
viewModel.exportSelection(adapter.getSelection(), File(it))
}
}
}
}
}
}
override fun onDestroy() {
super.onDestroy()
Coroutine.async { ReadBook.contentProcessor?.upReplaceRules() }

@ -24,7 +24,7 @@ import io.legado.app.ui.association.ImportBookSourceActivity
import io.legado.app.ui.association.ImportReplaceRuleActivity
import io.legado.app.ui.association.ImportRssSourceActivity
import io.legado.app.ui.filepicker.FilePicker
import io.legado.app.ui.filepicker.FilePickerDialog
import io.legado.app.ui.filepicker.FilePickerParam
import io.legado.app.utils.*
import kotlinx.coroutines.launch
import org.apache.commons.text.StringEscapeUtils
@ -33,7 +33,6 @@ import splitties.systemservices.downloadManager
class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>(false),
FilePickerDialog.CallBack,
ReadRssViewModel.CallBack {
override val viewModel: ReadRssViewModel
@ -44,6 +43,10 @@ class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>
private var ttsMenuItem: MenuItem? = null
private var customWebViewCallback: WebChromeClient.CustomViewCallback? = null
private var webPic: String? = null
private val saveImage = registerForActivityResult(FilePicker()) {
ACache.get(this).put(imagePathKey, it.toString())
viewModel.saveImage(webPic, it.toString())
}
override fun getViewBinding(): ActivityRssReadBinding {
return ActivityRssReadBinding.inflate(layoutInflater)
@ -156,14 +159,11 @@ class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>
if (!path.isNullOrEmpty()) {
default.add(path)
}
FilePicker.selectFolder(
this,
savePathRequestCode,
getString(R.string.save_image),
default
) {
viewModel.saveImage(webPic, it)
}
saveImage.launch(
FilePickerParam(
otherActions = default.toTypedArray()
)
)
}
@SuppressLint("SetJavaScriptEnabled")
@ -269,16 +269,6 @@ class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
savePathRequestCode -> data?.data?.let {
ACache.get(this).put(imagePathKey, it.toString())
viewModel.saveImage(webPic, it.toString())
}
}
}
override fun onDestroy() {
super.onDestroy()
binding.webView.destroy()

@ -1,7 +1,6 @@
package io.legado.app.ui.rss.source.manage
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.Menu
@ -26,8 +25,8 @@ import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.primaryTextColor
import io.legado.app.ui.association.ImportRssSourceActivity
import io.legado.app.ui.filepicker.FilePicker
import io.legado.app.ui.filepicker.FilePickerDialog
import io.legado.app.ui.qrcode.QrCodeActivity
import io.legado.app.ui.filepicker.FilePickerParam
import io.legado.app.ui.qrcode.QrCodeResult
import io.legado.app.ui.rss.source.edit.RssSourceEditActivity
import io.legado.app.ui.widget.SelectActionBar
import io.legado.app.ui.widget.dialog.TextDialog
@ -37,23 +36,51 @@ import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.*
import java.io.File
/**
* 订阅源管理
*/
class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceViewModel>(),
PopupMenu.OnMenuItemClickListener,
FilePickerDialog.CallBack,
SelectActionBar.CallBack,
RssSourceAdapter.CallBack {
override val viewModel: RssSourceViewModel
by viewModels()
private val importRecordKey = "rssSourceRecordKey"
private val qrRequestCode = 101
private val importRequestCode = 124
private val exportRequestCode = 65
private lateinit var adapter: RssSourceAdapter
private var sourceLiveData: LiveData<List<RssSource>>? = null
private var groups = hashSetOf<String>()
private var groupMenu: SubMenu? = null
private val qrCodeResult = registerForActivityResult(QrCodeResult()) {
it ?: return@registerForActivityResult
startActivity<ImportRssSourceActivity> {
putExtra("source", it)
}
}
private val importDoc = registerForActivityResult(FilePicker()) { uri ->
kotlin.runCatching {
uri?.readText(this)?.let {
val dataKey = IntentDataHelp.putData(it)
startActivity<ImportRssSourceActivity> {
putExtra("dataKey", dataKey)
}
}
}.onFailure {
toastOnUi("readTextError:${it.localizedMessage}")
}
}
private val exportDir = registerForActivityResult(FilePicker()) { uri ->
uri ?: return@registerForActivityResult
if (uri.isContentScheme()) {
DocumentFile.fromTreeUri(this, uri)?.let {
viewModel.exportSelection(adapter.getSelection(), it)
}
} else {
uri.path?.let {
viewModel.exportSelection(adapter.getSelection(), File(it))
}
}
}
override fun getViewBinding(): ActivityRssSourceBinding {
return ActivityRssSourceBinding.inflate(layoutInflater)
@ -81,10 +108,14 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_add -> startActivity<RssSourceEditActivity>()
R.id.menu_import_source_local -> FilePicker
.selectFile(this, importRequestCode, allowExtensions = arrayOf("txt", "json"))
R.id.menu_import_source_local -> importDoc.launch(
FilePickerParam(
mode = FilePicker.FILE,
allowExtensions = arrayOf("txt", "json")
)
)
R.id.menu_import_source_onLine -> showImportDialog()
R.id.menu_import_source_qr -> startActivityForResult<QrCodeActivity>(qrRequestCode)
R.id.menu_import_source_qr -> qrCodeResult.launch(null)
R.id.menu_group_manage -> GroupManageDialog()
.show(supportFragmentManager, "rssGroupManage")
R.id.menu_share_source -> viewModel.shareSelection(adapter.getSelection()) {
@ -105,7 +136,7 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelection())
R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection())
R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelection())
R.id.menu_export_selection -> FilePicker.selectFolder(this, exportRequestCode)
R.id.menu_export_selection -> exportDir.launch(null)
R.id.menu_top_sel -> viewModel.topSource(*adapter.getSelection().toTypedArray())
R.id.menu_bottom_sel -> viewModel.bottomSource(*adapter.getSelection().toTypedArray())
}
@ -260,46 +291,6 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
}.show()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
importRequestCode -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let { uri ->
kotlin.runCatching {
uri.readText(this)?.let {
val dataKey = IntentDataHelp.putData(it)
startActivity<ImportRssSourceActivity> {
putExtra("dataKey", dataKey)
}
}
}.onFailure {
toastOnUi("readTextError:${it.localizedMessage}")
}
}
}
qrRequestCode -> if (resultCode == RESULT_OK) {
data?.getStringExtra("result")?.let {
startActivity<ImportRssSourceActivity> {
putExtra("source", it)
}
}
}
exportRequestCode -> if (resultCode == RESULT_OK) {
data?.data?.let { uri ->
if (uri.isContentScheme()) {
DocumentFile.fromTreeUri(this, uri)?.let {
viewModel.exportSelection(adapter.getSelection(), it)
}
} else {
uri.path?.let {
viewModel.exportSelection(adapter.getSelection(), File(it))
}
}
}
}
}
}
override fun del(source: RssSource) {
viewModel.del(source)
}

@ -1,6 +1,5 @@
package io.legado.app.ui.widget.font
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.net.Uri
import android.os.Bundle
@ -21,7 +20,7 @@ import io.legado.app.lib.permission.Permissions
import io.legado.app.lib.permission.PermissionsCompat
import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.filepicker.FilePicker
import io.legado.app.ui.filepicker.FilePickerDialog
import io.legado.app.ui.filepicker.FilePickerParam
import io.legado.app.utils.*
import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.Dispatchers.Main
@ -32,16 +31,37 @@ import java.util.*
import kotlin.collections.ArrayList
class FontSelectDialog : BaseDialogFragment(),
FilePickerDialog.CallBack,
Toolbar.OnMenuItemClickListener,
FontAdapter.CallBack {
private val fontFolderRequestCode = 35485
private val fontRegex = Regex(".*\\.[ot]tf")
private val fontFolder by lazy {
FileUtils.createFolderIfNotExist(appCtx.filesDir, "Fonts")
}
private var adapter: FontAdapter? = null
private val binding by viewBinding(DialogFontSelectBinding::bind)
private val selectFontDir = registerForActivityResult(FilePicker()) { uri ->
uri ?: return@registerForActivityResult
if (uri.toString().isContentScheme()) {
putPrefString(PreferKey.fontFolder, uri.toString())
val doc = DocumentFile.fromTreeUri(requireContext(), uri)
if (doc != null) {
context?.contentResolver?.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
loadFontFiles(doc)
} else {
RealPathUtil.getPath(requireContext(), uri)?.let {
loadFontFilesByPermission(it)
}
}
} else {
uri.path?.let { path ->
putPrefString(PreferKey.fontFolder, path)
loadFontFilesByPermission(path)
}
}
}
override fun onStart() {
super.onStart()
@ -108,19 +128,11 @@ class FontSelectDialog : BaseDialogFragment(),
private fun openFolder() {
launch(Main) {
val defaultPath = "SD${File.separator}Fonts"
FilePicker.selectFolder(
this@FontSelectDialog,
fontFolderRequestCode,
otherActions = arrayListOf(defaultPath)
) {
when (it) {
defaultPath -> {
val path = "${FileUtils.getSdCardPath()}${File.separator}Fonts"
putPrefString(PreferKey.fontFolder, path)
loadFontFilesByPermission(path)
}
}
}
selectFontDir.launch(
FilePickerParam(
otherActions = arrayOf(defaultPath)
)
)
}
}
@ -227,36 +239,6 @@ class FontSelectDialog : BaseDialogFragment(),
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
fontFolderRequestCode -> if (resultCode == RESULT_OK) {
data?.data?.let { uri ->
if (uri.toString().isContentScheme()) {
putPrefString(PreferKey.fontFolder, uri.toString())
val doc = DocumentFile.fromTreeUri(requireContext(), uri)
if (doc != null) {
context?.contentResolver?.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
loadFontFiles(doc)
} else {
RealPathUtil.getPath(requireContext(), uri)?.let {
loadFontFilesByPermission(it)
}
}
} else {
uri.path?.let { path ->
putPrefString(PreferKey.fontFolder, path)
loadFontFilesByPermission(path)
}
}
}
}
}
}
private fun onDefaultFontChange() {
callBack?.selectFont("")
}

@ -10,10 +10,6 @@ import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.core.content.edit
import androidx.fragment.app.Fragment
import splitties.systemservices.connectivityManager
@Suppress("DEPRECATION")
fun Fragment.isOnline() = connectivityManager.activeNetworkInfo?.isConnected == true
fun Fragment.getPrefBoolean(key: String, defValue: Boolean = false) =
requireContext().defaultSharedPreferences.getBoolean(key, defValue)
@ -63,11 +59,4 @@ inline fun <reified T : Activity> Fragment.startActivity(
configIntent: Intent.() -> Unit = {}
) {
startActivity(Intent(requireContext(), T::class.java).apply(configIntent))
}
inline fun <reified T : Activity> Fragment.startActivityForResult(
requestCode: Int,
configIntent: Intent.() -> Unit = {}
) {
startActivityForResult(Intent(requireContext(), T::class.java).apply(configIntent), requestCode)
}
Loading…
Cancel
Save