修改弃用方法

pull/921/head
gedoor 4 years ago
parent 35f384c19f
commit fa92195933
  1. 6
      app/src/main/AndroidManifest.xml
  2. 2
      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. 61
      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. 293
      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. 31
      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:name=".ui.about.ReadRecordActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:hardwareAccelerated="true" /> android:hardwareAccelerated="true" />
<!-- 选择文件 -->
<activity
android:name=".ui.filepicker.FilePickerActivity"
android:configChanges="orientation|screenSize"
android:hardwareAccelerated="true"
android:theme="@style/AppTheme.Transparent" />
<!-- 文字处理 --> <!-- 文字处理 -->
<activity <activity
android:name=".receiver.SharedReceiverActivity" android:name=".receiver.SharedReceiverActivity"

@ -154,10 +154,12 @@ abstract class BaseActivity<VB : ViewBinding>(
} }
if (AppConfig.isGooglePlay) { if (AppConfig.isGooglePlay) {
ThemeConfig.getBgImage(this)?.let { ThemeConfig.getBgImage(this)?.let {
kotlin.runCatching {
window.decorView.background = it window.decorView.background = it
} }
} }
} }
}
private fun setupSystemBar() { private fun setupSystemBar() {
if (fullScreen && !isInMultiWindow) { if (fullScreen && !isInMultiWindow) {

@ -1,6 +1,5 @@
package io.legado.app.ui.book.cache package io.legado.app.ui.book.cache
import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu 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.lib.dialogs.alert
import io.legado.app.service.help.CacheBook import io.legado.app.service.help.CacheBook
import io.legado.app.ui.filepicker.FilePicker 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.ui.widget.dialog.TextListDialog
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -36,11 +35,25 @@ import kotlinx.coroutines.withContext
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArraySet import java.util.concurrent.CopyOnWriteArraySet
class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>(), class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>(),
FilePickerDialog.CallBack,
CacheAdapter.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" private val exportBookPathKey = "exportBookPath"
lateinit var adapter: CacheAdapter lateinit var adapter: CacheAdapter
private var groupLiveData: LiveData<List<BookGroup>>? = null private var groupLiveData: LiveData<List<BookGroup>>? = null
@ -215,9 +228,11 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
if (!path.isNullOrEmpty()) { if (!path.isNullOrEmpty()) {
default.add(path) default.add(path)
} }
FilePicker.selectFolder(this, exportRequestCode, otherActions = default) { exportDir.launch(
startExport(it) FilePickerParam(
} otherActions = default.toTypedArray()
)
)
} }
private fun startExport(path: String) { private fun startExport(path: String) {
@ -244,27 +259,4 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
}.show() }.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 package io.legado.app.ui.book.local
import android.app.Activity
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build 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.permission.PermissionsCompat
import io.legado.app.lib.theme.backgroundColor import io.legado.app.lib.theme.backgroundColor
import io.legado.app.ui.filepicker.FilePicker 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.ui.widget.SelectActionBar
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
@ -36,17 +34,31 @@ import java.util.*
* 导入本地书籍界面 * 导入本地书籍界面
*/ */
class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookViewModel>(), class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookViewModel>(),
FilePickerDialog.CallBack,
PopupMenu.OnMenuItemClickListener, PopupMenu.OnMenuItemClickListener,
SelectActionBar.CallBack, ImportBookAdapter.CallBack,
ImportBookAdapter.CallBack { SelectActionBar.CallBack {
private val requestCodeSelectFolder = 342
private var rootDoc: DocumentFile? = null private var rootDoc: DocumentFile? = null
private val subDocs = arrayListOf<DocumentFile>() private val subDocs = arrayListOf<DocumentFile>()
private lateinit var adapter: ImportBookAdapter private lateinit var adapter: ImportBookAdapter
private var localUriLiveData: LiveData<List<String>>? = null private var localUriLiveData: LiveData<List<String>>? = null
private var sdPath = FileUtils.getSdCardPath() private var sdPath = FileUtils.getSdCardPath()
private var path = sdPath 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 override val viewModel: ImportBookViewModel
by viewModels() by viewModels()
@ -69,7 +81,7 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { 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() R.id.menu_scan_folder -> scanFolder()
} }
return super.onCompatOptionsItemSelected(item) return super.onCompatOptionsItemSelected(item)
@ -129,14 +141,14 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
when { when {
lastPath.isNullOrEmpty() -> { lastPath.isNullOrEmpty() -> {
binding.tvEmptyMsg.visible() binding.tvEmptyMsg.visible()
FilePicker.selectFolder(this, requestCodeSelectFolder) selectFolder.launch(null)
} }
lastPath.isContentScheme() -> { lastPath.isContentScheme() -> {
val rootUri = Uri.parse(lastPath) val rootUri = Uri.parse(lastPath)
rootDoc = DocumentFile.fromTreeUri(this, rootUri) rootDoc = DocumentFile.fromTreeUri(this, rootUri)
if (rootDoc == null) { if (rootDoc == null) {
binding.tvEmptyMsg.visible() binding.tvEmptyMsg.visible()
FilePicker.selectFolder(this, requestCodeSelectFolder) selectFolder.launch(null)
} else { } else {
subDocs.clear() subDocs.clear()
upPath() upPath()
@ -144,7 +156,7 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
} }
Build.VERSION.SDK_INT > Build.VERSION_CODES.Q -> { Build.VERSION.SDK_INT > Build.VERSION_CODES.Q -> {
binding.tvEmptyMsg.visible() binding.tvEmptyMsg.visible()
FilePicker.selectFolder(this, requestCodeSelectFolder) selectFolder.launch(null)
} }
else -> { else -> {
binding.tvEmptyMsg.visible() 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 @Synchronized
override fun nextDoc(uri: Uri) { override fun nextDoc(uri: Uri) {
if (uri.toString().isContentScheme()) { if (uri.toString().isContentScheme()) {

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

@ -1,7 +1,6 @@
package io.legado.app.ui.book.source.edit package io.legado.app.ui.book.source.edit
import android.app.Activity import android.app.Activity
import android.content.Intent
import android.graphics.Rect import android.graphics.Rect
import android.os.Bundle import android.os.Bundle
import android.view.Gravity 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.lib.theme.backgroundColor
import io.legado.app.ui.book.source.debug.BookSourceDebugActivity import io.legado.app.ui.book.source.debug.BookSourceDebugActivity
import io.legado.app.ui.filepicker.FilePicker 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.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.KeyboardToolPop
import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.utils.* import io.legado.app.utils.*
@ -36,13 +35,10 @@ import kotlin.math.abs
class BookSourceEditActivity : class BookSourceEditActivity :
VMBaseActivity<ActivityBookSourceEditBinding, BookSourceEditViewModel>(false), VMBaseActivity<ActivityBookSourceEditBinding, BookSourceEditViewModel>(false),
FilePickerDialog.CallBack,
KeyboardToolPop.CallBack { KeyboardToolPop.CallBack {
override val viewModel: BookSourceEditViewModel override val viewModel: BookSourceEditViewModel
by viewModels() by viewModels()
private val qrRequestCode = 101
private val selectPathRequestCode = 102
private val adapter = BookSourceEditAdapter() private val adapter = BookSourceEditAdapter()
private val sourceEntities: ArrayList<EditEntity> = ArrayList() private val sourceEntities: ArrayList<EditEntity> = ArrayList()
private val searchEntities: ArrayList<EditEntity> = ArrayList() private val searchEntities: ArrayList<EditEntity> = ArrayList()
@ -50,6 +46,20 @@ class BookSourceEditActivity :
private val infoEntities: ArrayList<EditEntity> = ArrayList() private val infoEntities: ArrayList<EditEntity> = ArrayList()
private val tocEntities: ArrayList<EditEntity> = ArrayList() private val tocEntities: ArrayList<EditEntity> = ArrayList()
private val contentEntities: 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 mSoftKeyboardTool: PopupWindow? = null
private var mIsSoftKeyBoardShowing = false private var mIsSoftKeyBoardShowing = false
@ -98,7 +108,7 @@ class BookSourceEditActivity :
} }
R.id.menu_copy_source -> sendToClip(GSON.toJson(getSource())) R.id.menu_copy_source -> sendToClip(GSON.toJson(getSource()))
R.id.menu_paste_source -> viewModel.pasteSource { upRecyclerView(it) } 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_str -> share(GSON.toJson(getSource()))
R.id.menu_share_qr -> shareWithQr( R.id.menu_share_qr -> shareWithQr(
GSON.toJson(getSource()), GSON.toJson(getSource()),
@ -397,7 +407,11 @@ class BookSourceEditActivity :
0 -> insertText(AppConst.urlOption) 0 -> insertText(AppConst.urlOption)
1 -> showRuleHelp() 1 -> showRuleHelp()
2 -> showRegexHelp() 2 -> showRegexHelp()
3 -> FilePicker.selectFile(this, selectPathRequestCode) 3 -> selectDoc.launch(
FilePickerParam(
mode = FilePicker.FILE
)
)
} }
} }
} }
@ -425,28 +439,6 @@ class BookSourceEditActivity :
mSoftKeyboardTool?.dismiss() 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 { private inner class KeyboardOnGlobalChangeListener : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() { override fun onGlobalLayout() {
val rect = Rect() val rect = Rect()

@ -1,7 +1,6 @@
package io.legado.app.ui.book.source.manage package io.legado.app.ui.book.source.manage
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu 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.debug.BookSourceDebugActivity
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.filepicker.FilePicker 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.qrcode.QrCodeActivity import io.legado.app.ui.qrcode.QrCodeResult
import io.legado.app.ui.widget.SelectActionBar import io.legado.app.ui.widget.SelectActionBar
import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.ui.widget.recycler.DragSelectTouchHelper import io.legado.app.ui.widget.recycler.DragSelectTouchHelper
@ -45,15 +44,11 @@ import java.io.File
class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceViewModel>(), class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceViewModel>(),
PopupMenu.OnMenuItemClickListener, PopupMenu.OnMenuItemClickListener,
BookSourceAdapter.CallBack, BookSourceAdapter.CallBack,
FilePickerDialog.CallBack,
SelectActionBar.CallBack, SelectActionBar.CallBack,
SearchView.OnQueryTextListener { SearchView.OnQueryTextListener {
override val viewModel: BookSourceViewModel override val viewModel: BookSourceViewModel
by viewModels() by viewModels()
private val importRecordKey = "bookSourceRecordKey" private val importRecordKey = "bookSourceRecordKey"
private val qrRequestCode = 101
private val importRequestCode = 132
private val exportRequestCode = 65
private lateinit var adapter: BookSourceAdapter private lateinit var adapter: BookSourceAdapter
private lateinit var searchView: SearchView private lateinit var searchView: SearchView
private var bookSourceLiveDate: LiveData<List<BookSource>>? = null private var bookSourceLiveDate: LiveData<List<BookSource>>? = null
@ -62,6 +57,37 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
private var sort = Sort.Default private var sort = Sort.Default
private var sortAscending = true private var sortAscending = true
private var snackBar: Snackbar? = null 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 { override fun getViewBinding(): ActivityBookSourceBinding {
return ActivityBookSourceBinding.inflate(layoutInflater) return ActivityBookSourceBinding.inflate(layoutInflater)
@ -95,14 +121,18 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.menu_add_book_source -> startActivity<BookSourceEditActivity>() 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()) { R.id.menu_share_source -> viewModel.shareSelection(adapter.getSelection()) {
startActivity(Intent.createChooser(it, getString(R.string.share_selected_source))) startActivity(Intent.createChooser(it, getString(R.string.share_selected_source)))
} }
R.id.menu_group_manage -> R.id.menu_group_manage ->
GroupManageDialog().show(supportFragmentManager, "groupManage") GroupManageDialog().show(supportFragmentManager, "groupManage")
R.id.menu_import_source_local -> FilePicker R.id.menu_import_source_local -> importDoc.launch(
.selectFile(this, importRequestCode, allowExtensions = arrayOf("txt", "json")) FilePickerParam(
mode = FilePicker.FILE,
allowExtensions = arrayOf("txt", "json")
)
)
R.id.menu_import_source_onLine -> showImportDialog() R.id.menu_import_source_onLine -> showImportDialog()
R.id.menu_sort_manual -> { R.id.menu_sort_manual -> {
item.isChecked = true 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_bottom_sel -> viewModel.bottomSource(*adapter.getSelection().toTypedArray())
R.id.menu_add_group -> selectionAddToGroups() R.id.menu_add_group -> selectionAddToGroups()
R.id.menu_remove_group -> selectionRemoveFromGroups() 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 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() { override fun finish() {
if (searchView.query.isNullOrEmpty()) { if (searchView.query.isNullOrEmpty()) {
super.finish() super.finish()

@ -2,31 +2,105 @@ package io.legado.app.ui.config
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.InputType import android.text.InputType
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.documentfile.provider.DocumentFile
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BasePreferenceFragment import io.legado.app.base.BasePreferenceFragment
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.help.AppConfig
import io.legado.app.help.LocalConfig 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.help.storage.Restore
import io.legado.app.lib.dialogs.alert 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.ATH
import io.legado.app.lib.theme.accentColor 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.ui.widget.dialog.TextDialog
import io.legado.app.utils.applyTint import io.legado.app.utils.*
import io.legado.app.utils.getPrefString import kotlinx.coroutines.Dispatchers
import splitties.init.appCtx
class BackupConfigFragment : BasePreferenceFragment(), class BackupConfigFragment : BasePreferenceFragment(),
SharedPreferences.OnSharedPreferenceChangeListener, SharedPreferences.OnSharedPreferenceChangeListener {
FilePickerDialog.CallBack {
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?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_config_backup) addPreferencesFromResource(R.xml.pref_config_backup)
@ -53,7 +127,7 @@ class BackupConfigFragment : BasePreferenceFragment(),
upPreferenceSummary(PreferKey.webDavPassword, getPrefString(PreferKey.webDavPassword)) upPreferenceSummary(PreferKey.webDavPassword, getPrefString(PreferKey.webDavPassword))
upPreferenceSummary(PreferKey.backupPath, getPrefString(PreferKey.backupPath)) upPreferenceSummary(PreferKey.backupPath, getPrefString(PreferKey.backupPath))
findPreference<io.legado.app.ui.widget.prefs.Preference>("web_dav_restore") 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -135,11 +209,11 @@ class BackupConfigFragment : BasePreferenceFragment(),
override fun onPreferenceTreeClick(preference: Preference?): Boolean { override fun onPreferenceTreeClick(preference: Preference?): Boolean {
when (preference?.key) { when (preference?.key) {
PreferKey.backupPath -> BackupRestoreUi.selectBackupFolder(this) PreferKey.backupPath -> selectBackupPath.launch(null)
PreferKey.restoreIgnore -> restoreIgnore() PreferKey.restoreIgnore -> restoreIgnore()
"web_dav_backup" -> BackupRestoreUi.backup(this) "web_dav_backup" -> backup()
"web_dav_restore" -> BackupRestoreUi.restore(this) "web_dav_restore" -> restore()
"import_old" -> BackupRestoreUi.importOldData(this) "import_old" -> restoreOld.launch(null)
} }
return super.onPreferenceTreeClick(preference) return super.onPreferenceTreeClick(preference)
} }
@ -159,8 +233,81 @@ class BackupConfigFragment : BasePreferenceFragment(),
}.show() }.show()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) fun backup() {
BackupRestoreUi.onActivityResult(requestCode, resultCode, data) 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 package io.legado.app.ui.filepicker
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.net.Uri
import androidx.appcompat.app.AppCompatActivity import androidx.activity.result.contract.ActivityResultContract
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
@Suppress("unused") @Suppress("unused")
object FilePicker { class FilePicker : ActivityResultContract<FilePickerParam, Uri?>() {
fun selectFolder( companion object {
activity: AppCompatActivity, const val DIRECTORY = 0
requestCode: Int, const val FILE = 1
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( override fun createIntent(context: Context, input: FilePickerParam?): Intent {
fragment: Fragment, val intent = Intent(context, FilePickerActivity::class.java)
requestCode: Int, input?.let {
title: String = fragment.getString(R.string.select_folder), intent.putExtra("mode", it.mode)
otherActions: List<String>? = null, intent.putExtra("title", it.title)
otherFun: ((action: String) -> Unit)? = null intent.putExtra("allowExtensions", it.allowExtensions)
) { intent.putExtra("otherActions", it.otherActions)
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()
}
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))
}
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 return intent
} }
private fun checkPermissions(fragment: Fragment, success: (() -> Unit)? = null) { override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
PermissionsCompat.Builder(fragment) if (resultCode == RESULT_OK) {
.addPermissions(*Permissions.Group.STORAGE) return intent?.data
.rationale(R.string.tip_perm_request_storage)
.onGranted {
success?.invoke()
} }
.request() 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> { @Suppress("ArrayInDataClass")
val types = hashSetOf<String>() data class FilePickerParam(
if (allowExtensions.isNullOrEmpty()) { var mode: Int = 0,
types.add("*/*") var title: String? = null,
} else { var allowExtensions: Array<String> = arrayOf(),
allowExtensions.forEach { var otherActions: Array<String>? = null,
when (it) { )
"*" -> types.add("*/*")
"txt", "xml" -> types.add("text/*")
else -> types.add("application/$it")
}
}
}
return types.toTypedArray()
}
}

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

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

@ -1,6 +1,5 @@
package io.legado.app.ui.main.my package io.legado.app.ui.main.my
import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.view.Menu 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.DonateActivity
import io.legado.app.ui.about.ReadRecordActivity import io.legado.app.ui.about.ReadRecordActivity
import io.legado.app.ui.book.source.manage.BookSourceActivity 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.ConfigActivity
import io.legado.app.ui.config.ConfigViewModel 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.replace.ReplaceRuleActivity
import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.ui.widget.prefs.NameListPreference 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.*
import io.legado.app.utils.viewbindingdelegate.viewBinding 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) 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.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem 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.service.help.ReadBook
import io.legado.app.ui.association.ImportReplaceRuleActivity import io.legado.app.ui.association.ImportReplaceRuleActivity
import io.legado.app.ui.filepicker.FilePicker 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.qrcode.QrCodeActivity import io.legado.app.ui.qrcode.QrCodeResult
import io.legado.app.ui.replace.edit.ReplaceEditActivity import io.legado.app.ui.replace.edit.ReplaceEditActivity
import io.legado.app.ui.widget.SelectActionBar import io.legado.app.ui.widget.SelectActionBar
import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.ui.widget.dialog.TextDialog
@ -47,24 +46,50 @@ import java.io.File
class ReplaceRuleActivity : VMBaseActivity<ActivityReplaceRuleBinding, ReplaceRuleViewModel>(), class ReplaceRuleActivity : VMBaseActivity<ActivityReplaceRuleBinding, ReplaceRuleViewModel>(),
SearchView.OnQueryTextListener, SearchView.OnQueryTextListener,
PopupMenu.OnMenuItemClickListener, PopupMenu.OnMenuItemClickListener,
FilePickerDialog.CallBack,
SelectActionBar.CallBack, SelectActionBar.CallBack,
ReplaceRuleAdapter.CallBack { ReplaceRuleAdapter.CallBack {
override val viewModel: ReplaceRuleViewModel by viewModels() override val viewModel: ReplaceRuleViewModel by viewModels()
private val importRecordKey = "replaceRuleRecordKey" 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 adapter: ReplaceRuleAdapter
private lateinit var searchView: SearchView private lateinit var searchView: SearchView
private var groups = hashSetOf<String>() private var groups = hashSetOf<String>()
private var groupMenu: SubMenu? = null private var groupMenu: SubMenu? = null
private var replaceRuleLiveData: LiveData<List<ReplaceRule>>? = null private var replaceRuleLiveData: LiveData<List<ReplaceRule>>? = null
private var dataInit = false 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 { override fun getViewBinding(): ActivityReplaceRuleBinding {
return ActivityReplaceRuleBinding.inflate(layoutInflater) 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_del_selection -> viewModel.delSelection(adapter.getSelection())
R.id.menu_import_source_onLine -> showImportDialog() R.id.menu_import_source_onLine -> showImportDialog()
R.id.menu_import_source_local -> FilePicker R.id.menu_import_source_local -> importDoc.launch(
.selectFile(this, importRequestCode, allowExtensions = arrayOf("txt", "json")) FilePickerParam(
R.id.menu_import_source_qr -> startActivityForResult<QrCodeActivity>(importRequestCodeQr) mode = FilePicker.FILE,
allowExtensions = arrayOf("txt", "json")
)
)
R.id.menu_import_source_qr -> qrCodeResult.launch(null)
R.id.menu_help -> showHelp() R.id.menu_help -> showHelp()
else -> if (item.groupId == R.id.replace_group) { else -> if (item.groupId == R.id.replace_group) {
searchView.setQuery("group:${item.title}", true) searchView.setQuery("group:${item.title}", true)
@ -205,7 +234,7 @@ class ReplaceRuleActivity : VMBaseActivity<ActivityReplaceRuleBinding, ReplaceRu
when (item?.itemId) { when (item?.itemId) {
R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelection()) R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelection())
R.id.menu_disable_selection -> viewModel.disableSelection(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 return false
} }
@ -263,47 +292,6 @@ class ReplaceRuleActivity : VMBaseActivity<ActivityReplaceRuleBinding, ReplaceRu
return false 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() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
Coroutine.async { ReadBook.contentProcessor?.upReplaceRules() } 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.ImportReplaceRuleActivity
import io.legado.app.ui.association.ImportRssSourceActivity import io.legado.app.ui.association.ImportRssSourceActivity
import io.legado.app.ui.filepicker.FilePicker 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.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.apache.commons.text.StringEscapeUtils import org.apache.commons.text.StringEscapeUtils
@ -33,7 +33,6 @@ import splitties.systemservices.downloadManager
class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>(false), class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>(false),
FilePickerDialog.CallBack,
ReadRssViewModel.CallBack { ReadRssViewModel.CallBack {
override val viewModel: ReadRssViewModel override val viewModel: ReadRssViewModel
@ -44,6 +43,10 @@ class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>
private var ttsMenuItem: MenuItem? = null private var ttsMenuItem: MenuItem? = null
private var customWebViewCallback: WebChromeClient.CustomViewCallback? = null private var customWebViewCallback: WebChromeClient.CustomViewCallback? = null
private var webPic: String? = 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 { override fun getViewBinding(): ActivityRssReadBinding {
return ActivityRssReadBinding.inflate(layoutInflater) return ActivityRssReadBinding.inflate(layoutInflater)
@ -156,14 +159,11 @@ class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>
if (!path.isNullOrEmpty()) { if (!path.isNullOrEmpty()) {
default.add(path) default.add(path)
} }
FilePicker.selectFolder( saveImage.launch(
this, FilePickerParam(
savePathRequestCode, otherActions = default.toTypedArray()
getString(R.string.save_image), )
default )
) {
viewModel.saveImage(webPic, it)
}
} }
@SuppressLint("SetJavaScriptEnabled") @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() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
binding.webView.destroy() binding.webView.destroy()

@ -1,7 +1,6 @@
package io.legado.app.ui.rss.source.manage package io.legado.app.ui.rss.source.manage
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu 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.lib.theme.primaryTextColor
import io.legado.app.ui.association.ImportRssSourceActivity import io.legado.app.ui.association.ImportRssSourceActivity
import io.legado.app.ui.filepicker.FilePicker 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.qrcode.QrCodeActivity import io.legado.app.ui.qrcode.QrCodeResult
import io.legado.app.ui.rss.source.edit.RssSourceEditActivity import io.legado.app.ui.rss.source.edit.RssSourceEditActivity
import io.legado.app.ui.widget.SelectActionBar import io.legado.app.ui.widget.SelectActionBar
import io.legado.app.ui.widget.dialog.TextDialog 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 io.legado.app.utils.*
import java.io.File import java.io.File
/**
* 订阅源管理
*/
class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceViewModel>(), class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceViewModel>(),
PopupMenu.OnMenuItemClickListener, PopupMenu.OnMenuItemClickListener,
FilePickerDialog.CallBack,
SelectActionBar.CallBack, SelectActionBar.CallBack,
RssSourceAdapter.CallBack { RssSourceAdapter.CallBack {
override val viewModel: RssSourceViewModel override val viewModel: RssSourceViewModel
by viewModels() by viewModels()
private val importRecordKey = "rssSourceRecordKey" private val importRecordKey = "rssSourceRecordKey"
private val qrRequestCode = 101
private val importRequestCode = 124
private val exportRequestCode = 65
private lateinit var adapter: RssSourceAdapter private lateinit var adapter: RssSourceAdapter
private var sourceLiveData: LiveData<List<RssSource>>? = null private var sourceLiveData: LiveData<List<RssSource>>? = null
private var groups = hashSetOf<String>() private var groups = hashSetOf<String>()
private var groupMenu: SubMenu? = null 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 { override fun getViewBinding(): ActivityRssSourceBinding {
return ActivityRssSourceBinding.inflate(layoutInflater) return ActivityRssSourceBinding.inflate(layoutInflater)
@ -81,10 +108,14 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.menu_add -> startActivity<RssSourceEditActivity>() R.id.menu_add -> startActivity<RssSourceEditActivity>()
R.id.menu_import_source_local -> FilePicker R.id.menu_import_source_local -> importDoc.launch(
.selectFile(this, importRequestCode, allowExtensions = arrayOf("txt", "json")) FilePickerParam(
mode = FilePicker.FILE,
allowExtensions = arrayOf("txt", "json")
)
)
R.id.menu_import_source_onLine -> showImportDialog() 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() R.id.menu_group_manage -> GroupManageDialog()
.show(supportFragmentManager, "rssGroupManage") .show(supportFragmentManager, "rssGroupManage")
R.id.menu_share_source -> viewModel.shareSelection(adapter.getSelection()) { 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_enable_selection -> viewModel.enableSelection(adapter.getSelection())
R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection()) R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection())
R.id.menu_del_selection -> viewModel.delSelection(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_top_sel -> viewModel.topSource(*adapter.getSelection().toTypedArray())
R.id.menu_bottom_sel -> viewModel.bottomSource(*adapter.getSelection().toTypedArray()) R.id.menu_bottom_sel -> viewModel.bottomSource(*adapter.getSelection().toTypedArray())
} }
@ -260,46 +291,6 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
}.show() }.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) { override fun del(source: RssSource) {
viewModel.del(source) viewModel.del(source)
} }

@ -1,6 +1,5 @@
package io.legado.app.ui.widget.font package io.legado.app.ui.widget.font
import android.app.Activity.RESULT_OK
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle 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.permission.PermissionsCompat
import io.legado.app.lib.theme.primaryColor import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.filepicker.FilePicker 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.*
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
@ -32,16 +31,37 @@ import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
class FontSelectDialog : BaseDialogFragment(), class FontSelectDialog : BaseDialogFragment(),
FilePickerDialog.CallBack,
Toolbar.OnMenuItemClickListener, Toolbar.OnMenuItemClickListener,
FontAdapter.CallBack { FontAdapter.CallBack {
private val fontFolderRequestCode = 35485
private val fontRegex = Regex(".*\\.[ot]tf") private val fontRegex = Regex(".*\\.[ot]tf")
private val fontFolder by lazy { private val fontFolder by lazy {
FileUtils.createFolderIfNotExist(appCtx.filesDir, "Fonts") FileUtils.createFolderIfNotExist(appCtx.filesDir, "Fonts")
} }
private var adapter: FontAdapter? = null private var adapter: FontAdapter? = null
private val binding by viewBinding(DialogFontSelectBinding::bind) 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() { override fun onStart() {
super.onStart() super.onStart()
@ -108,19 +128,11 @@ class FontSelectDialog : BaseDialogFragment(),
private fun openFolder() { private fun openFolder() {
launch(Main) { launch(Main) {
val defaultPath = "SD${File.separator}Fonts" val defaultPath = "SD${File.separator}Fonts"
FilePicker.selectFolder( selectFontDir.launch(
this@FontSelectDialog, FilePickerParam(
fontFolderRequestCode, otherActions = arrayOf(defaultPath)
otherActions = arrayListOf(defaultPath) )
) { )
when (it) {
defaultPath -> {
val path = "${FileUtils.getSdCardPath()}${File.separator}Fonts"
putPrefString(PreferKey.fontFolder, path)
loadFontFilesByPermission(path)
}
}
}
} }
} }
@ -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() { private fun onDefaultFontChange() {
callBack?.selectFont("") callBack?.selectFont("")
} }

@ -10,10 +10,6 @@ import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.core.content.edit import androidx.core.content.edit
import androidx.fragment.app.Fragment 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) = fun Fragment.getPrefBoolean(key: String, defValue: Boolean = false) =
requireContext().defaultSharedPreferences.getBoolean(key, defValue) requireContext().defaultSharedPreferences.getBoolean(key, defValue)
@ -64,10 +60,3 @@ inline fun <reified T : Activity> Fragment.startActivity(
) { ) {
startActivity(Intent(requireContext(), T::class.java).apply(configIntent)) 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