diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 86b75894f..25c89461a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -294,6 +294,12 @@ android:name=".ui.about.ReadRecordActivity" android:configChanges="orientation|screenSize" android:hardwareAccelerated="true" /> + + 【备份与恢复】,选择【导入旧版本数据】。 -**2021/03/26** +**2021/03/31** * 优化epubLib by ag2s20150909 +* 升级库,修改弃用方法 +* tts引擎添加导入导出功能 **2021/03/23** * 修复繁简转换“勐”“十”问题。使用了剥离HanLP简繁代码的民间库。APK减少6M左右 diff --git a/app/src/main/java/io/legado/app/base/BaseActivity.kt b/app/src/main/java/io/legado/app/base/BaseActivity.kt index 31deca8e4..ef9c23953 100644 --- a/app/src/main/java/io/legado/app/base/BaseActivity.kt +++ b/app/src/main/java/io/legado/app/base/BaseActivity.kt @@ -154,7 +154,9 @@ abstract class BaseActivity( } if (AppConfig.isGooglePlay) { ThemeConfig.getBgImage(this)?.let { - window.decorView.background = it + kotlin.runCatching { + window.decorView.background = it + } } } } diff --git a/app/src/main/java/io/legado/app/lib/permission/PermissionActivity.kt b/app/src/main/java/io/legado/app/lib/permission/PermissionActivity.kt index b98b16fde..887ca158a 100644 --- a/app/src/main/java/io/legado/app/lib/permission/PermissionActivity.kt +++ b/app/src/main/java/io/legado/app/lib/permission/PermissionActivity.kt @@ -13,7 +13,7 @@ import io.legado.app.utils.toastOnUi class PermissionActivity : AppCompatActivity() { - private val startSettingActivity = + private val settingActivityResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { RequestPlugins.sRequestCallback?.onSettingActivityResult() finish() @@ -37,7 +37,7 @@ class PermissionActivity : AppCompatActivity() { -> try { val settingIntent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) settingIntent.data = Uri.fromParts("package", packageName, null) - startSettingActivity.launch(settingIntent) + settingActivityResult.launch(settingIntent) } catch (e: Exception) { toastOnUi(R.string.tip_cannot_jump_setting_page) finish() diff --git a/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt b/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt index 8a809e701..2b50a9e61 100644 --- a/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt +++ b/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt @@ -62,19 +62,19 @@ class EpubFile(var book: Book) { } } - private var epubBook: EpubBook? = null private var mCharset: Charset = Charset.defaultCharset() + private var epubBook: EpubBook? = null + get() { + if (field != null) { + return field + } + field = readEpub() + return field + } init { try { - val inputStream = if (book.bookUrl.isContentScheme()) { - val uri = Uri.parse(book.bookUrl) - appCtx.contentResolver.openInputStream(uri) - } else { - File(book.bookUrl).inputStream() - } - epubBook = readEpub(inputStream) - if (epubBook != null) { + epubBook?.let { if (book.coverUrl.isNullOrEmpty()) { book.coverUrl = FileUtils.getPath( appCtx.externalFilesDir, @@ -84,8 +84,8 @@ class EpubFile(var book: Book) { } if (!File(book.coverUrl!!).exists()) { /*部分书籍DRM处理后,封面获取异常,待优化*/ - epubBook!!.coverImage?.inputStream?.use { - val cover = BitmapFactory.decodeStream(it) + it.coverImage?.inputStream?.use { input -> + val cover = BitmapFactory.decodeStream(input) val out = FileOutputStream(FileUtils.createFileIfNotExist(book.coverUrl!!)) cover.compress(Bitmap.CompressFormat.JPEG, 90, out) out.flush() @@ -99,9 +99,15 @@ class EpubFile(var book: Book) { } /*重写epub文件解析代码,直接读出压缩包文件生成Resources给epublib,这样的好处是可以逐一修改某些文件的格式错误*/ - private fun readEpub(input: InputStream?): EpubBook? { - if (input == null) return null + private fun readEpub(): EpubBook? { try { + val input = if (book.bookUrl.isContentScheme()) { + val uri = Uri.parse(book.bookUrl) + appCtx.contentResolver.openInputStream(uri) + } else { + File(book.bookUrl).inputStream() + } + input ?: return null val inZip = ZipInputStream(input) var zipEntry: ZipEntry? val resources = Resources() @@ -120,7 +126,7 @@ class EpubFile(var book: Book) { } resources.add(resource) } while (zipEntry != null) - if (resources.size() > 0) return EpubReader().readEpubBook(resources) + if (resources.size() > 0) return EpubReader().readEpub(resources) } catch (e: Exception) { e.printStackTrace() } @@ -129,9 +135,7 @@ class EpubFile(var book: Book) { private fun getContent(chapter: BookChapter): String? { /*获取当前章节文本*/ - val string = getChildChapter(chapter, chapter.url) - - return string + return getChildChapter(chapter, chapter.url) } private fun getChildChapter(chapter: BookChapter, href: String): String? { diff --git a/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt b/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt index a210aeb99..084180ca6 100644 --- a/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt +++ b/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt @@ -1,7 +1,6 @@ package io.legado.app.ui.audio import android.app.Activity -import android.content.Intent import android.graphics.drawable.Drawable import android.icu.text.SimpleDateFormat import android.os.Build @@ -25,7 +24,7 @@ import io.legado.app.help.ImageLoader import io.legado.app.lib.dialogs.alert import io.legado.app.service.help.AudioPlay import io.legado.app.ui.book.changesource.ChangeSourceDialog -import io.legado.app.ui.book.toc.ChapterListActivity +import io.legado.app.ui.book.toc.TocActivityResult import io.legado.app.ui.widget.image.CoverImageView import io.legado.app.ui.widget.seekbar.SeekBarChangeListener import io.legado.app.utils.* @@ -42,7 +41,6 @@ class AudioPlayActivity : override val viewModel: AudioPlayViewModel by viewModels() - private var requestCodeChapter = 8461 private var adjustProgress = false private val progressTimeFormat by lazy { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { @@ -51,6 +49,13 @@ class AudioPlayActivity : java.text.SimpleDateFormat("mm:ss", Locale.getDefault()) } } + private val tocActivityResult = registerForActivityResult(TocActivityResult()) { + it?.let { + if (it.first != AudioPlay.durChapterIndex) { + AudioPlay.skipTo(this, it.first) + } + } + } override fun getViewBinding(): ActivityAudioPlayBinding { return ActivityAudioPlayBinding.inflate(layoutInflater) @@ -107,9 +112,7 @@ class AudioPlayActivity : }) binding.ivChapter.setOnClickListener { AudioPlay.book?.let { - startActivityForResult(requestCodeChapter) { - putExtra("bookUrl", it.bookUrl) - } + tocActivityResult.launch(it.bookUrl) } } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { @@ -183,19 +186,6 @@ class AudioPlayActivity : } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (resultCode == Activity.RESULT_OK) { - when (requestCode) { - requestCodeChapter -> data?.getIntExtra("index", AudioPlay.durChapterIndex)?.let { - if (it != AudioPlay.durChapterIndex) { - AudioPlay.skipTo(this, it) - } - } - } - } - } - override fun observeLiveBus() { observeEvent(EventBus.MEDIA_BUTTON) { if (it) { diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt index 015c30399..e01100bfd 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt @@ -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 @@ -25,8 +24,8 @@ import io.legado.app.help.AppConfig 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.document.FilePicker +import io.legado.app.ui.document.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(), - 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>? = null @@ -215,9 +228,11 @@ class CacheActivity : VMBaseActivity() 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() }.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) - } - } - } - } - - } - } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt index 4d665fc32..5d06dc899 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt @@ -1,7 +1,6 @@ package io.legado.app.ui.book.info import android.annotation.SuppressLint -import android.app.Activity import android.content.Intent import android.graphics.drawable.Drawable import android.os.Bundle @@ -9,6 +8,7 @@ import android.view.Menu import android.view.MenuItem import android.widget.CheckBox import android.widget.LinearLayout +import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import com.bumptech.glide.RequestBuilder import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions @@ -35,7 +35,7 @@ import io.legado.app.ui.book.info.edit.BookInfoEditActivity import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.search.SearchActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity -import io.legado.app.ui.book.toc.ChapterListActivity +import io.legado.app.ui.book.toc.TocActivityResult import io.legado.app.ui.widget.image.CoverImageView import io.legado.app.utils.* @@ -47,9 +47,37 @@ class BookInfoActivity : ChangeSourceDialog.CallBack, ChangeCoverDialog.CallBack { - private val requestCodeChapterList = 568 - private val requestCodeInfoEdit = 562 - private val requestCodeRead = 432 + private val tocActivityResult = registerForActivityResult(TocActivityResult()) { + it?.let { + viewModel.bookData.value?.let { book -> + if (book.durChapterIndex != it.first) { + book.durChapterIndex = it.first + book.durChapterPos = it.second + } + startReadActivity(book) + } + } ?: let { + if (!viewModel.inBookshelf) { + viewModel.delBook() + } + } + } + private val readBookResult = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { + if (it.resultCode == RESULT_OK) { + viewModel.inBookshelf = true + upTvBookshelf() + } + } + private val infoEditResult = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { + if (it.resultCode == RESULT_OK) { + viewModel.upEditBook() + } + } + override val viewModel: BookInfoViewModel by viewModels() @@ -82,11 +110,10 @@ class BookInfoActivity : R.id.menu_edit -> { if (viewModel.inBookshelf) { viewModel.bookData.value?.let { - startActivityForResult( - requestCodeInfoEdit - ) { - putExtra("bookUrl", it.bookUrl) - } + infoEditResult.launch( + Intent(this, BookInfoEditActivity::class.java) + .putExtra("bookUrl", it.bookUrl) + ) } } else { toastOnUi(R.string.after_add_bookshelf) @@ -303,11 +330,7 @@ class BookInfoActivity : return } viewModel.bookData.value?.let { - startActivityForResult( - requestCodeChapterList - ) { - putExtra("bookUrl", it.bookUrl) - } + tocActivityResult.launch(it.bookUrl) } } @@ -327,19 +350,17 @@ class BookInfoActivity : private fun startReadActivity(book: Book) { when (book.type) { - BookType.audio -> startActivityForResult( - requestCodeRead - ) { - putExtra("bookUrl", book.bookUrl) - putExtra("inBookshelf", viewModel.inBookshelf) - } - else -> startActivityForResult( - requestCodeRead - ) { - putExtra("bookUrl", book.bookUrl) - putExtra("inBookshelf", viewModel.inBookshelf) - putExtra("key", IntentDataHelp.putData(book)) - } + BookType.audio -> readBookResult.launch( + Intent(this, AudioPlayActivity::class.java) + .putExtra("bookUrl", book.bookUrl) + .putExtra("inBookshelf", viewModel.inBookshelf) + ) + else -> readBookResult.launch( + Intent(this, ReadBookActivity::class.java) + .putExtra("bookUrl", book.bookUrl) + .putExtra("inBookshelf", viewModel.inBookshelf) + .putExtra("key", IntentDataHelp.putData(book)) + ) } } @@ -381,33 +402,4 @@ class BookInfoActivity : } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - when (requestCode) { - requestCodeInfoEdit -> - if (resultCode == Activity.RESULT_OK) { - viewModel.upEditBook() - } - requestCodeChapterList -> - if (resultCode == Activity.RESULT_OK) { - viewModel.bookData.value?.let { - data?.getIntExtra("index", it.durChapterIndex)?.let { index -> - if (it.durChapterIndex != index) { - it.durChapterIndex = index - it.durChapterPos = 0 - } - startReadActivity(it) - } - } - } else { - if (!viewModel.inBookshelf) { - viewModel.delBook() - } - } - requestCodeRead -> if (resultCode == Activity.RESULT_OK) { - viewModel.inBookshelf = true - upTvBookshelf() - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt b/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt index 202e62df0..366296bc8 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt @@ -1,11 +1,11 @@ package io.legado.app.ui.book.info.edit import android.app.Activity -import android.content.Intent import android.net.Uri import android.os.Bundle import android.view.Menu import android.view.MenuItem +import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.documentfile.provider.DocumentFile import io.legado.app.R @@ -22,7 +22,12 @@ class BookInfoEditActivity : VMBaseActivity(), ChangeCoverDialog.CallBack { - private val resultSelectCover = 132 + private val selectCoverResult = + registerForActivityResult(ActivityResultContracts.GetContent()) { + it?.let { uri -> + coverChangeTo(uri) + } + } override val viewModel: BookInfoEditViewModel by viewModels() @@ -60,7 +65,7 @@ class BookInfoEditActivity : } } tvSelectCover.setOnClickListener { - selectImage() + selectCoverResult.launch("image/*") } tvRefreshCover.setOnClickListener { viewModel.book?.customCoverUrl = tieCoverUrl.text?.toString() @@ -96,13 +101,6 @@ class BookInfoEditActivity : } } - private fun selectImage() { - val intent = Intent(Intent.ACTION_GET_CONTENT) - intent.addCategory(Intent.CATEGORY_OPENABLE) - intent.type = "image/*" - startActivityForResult(intent, resultSelectCover) - } - override fun coverChangeTo(coverUrl: String) { viewModel.book?.customCoverUrl = coverUrl binding.tieCoverUrl.setText(coverUrl) @@ -144,16 +142,4 @@ class BookInfoEditActivity : } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - when (requestCode) { - resultSelectCover -> { - if (resultCode == Activity.RESULT_OK) { - data?.data?.let { uri -> - coverChangeTo(uri) - } - } - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt index ea5858eda..185ba4f23 100644 --- a/app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt @@ -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 @@ -21,8 +20,7 @@ import io.legado.app.help.AppConfig 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.document.FilePicker 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(), - 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() private lateinit var adapter: ImportBookAdapter private var localUriLiveData: LiveData>? = 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 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 { 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 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 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()) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index cdd58cb13..159cced9d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -46,7 +46,7 @@ import io.legado.app.ui.book.read.page.entities.PageDirection import io.legado.app.ui.book.read.page.provider.TextPageFactory import io.legado.app.ui.book.searchContent.SearchContentActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity -import io.legado.app.ui.book.toc.ChapterListActivity +import io.legado.app.ui.book.toc.TocActivityResult import io.legado.app.ui.login.SourceLogin import io.legado.app.ui.replace.ReplaceRuleActivity import io.legado.app.ui.replace.edit.ReplaceEditActivity @@ -71,23 +71,26 @@ class ReadBookActivity : ReadBookBaseActivity(), ColorPickerDialogListener { private val tocActivity = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - it.data?.let { data -> - data.getIntExtra("index", ReadBook.durChapterIndex).let { index -> - if (index != ReadBook.durChapterIndex) { - val chapterPos = data.getIntExtra("chapterPos", 0) - viewModel.openChapter(index, chapterPos) - } + registerForActivityResult(TocActivityResult()) { + it?.let { + if (it.first != ReadBook.durChapterIndex) { + viewModel.openChapter(it.first, it.second) } } } private val sourceEditActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - upView() + if (it.resultCode == RESULT_OK) { + viewModel.upBookSource { + upView() + } + } } private val replaceActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - viewModel.replaceRuleChanged() + if (it.resultCode == RESULT_OK) { + viewModel.replaceRuleChanged() + } } private val searchContentActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { @@ -727,9 +730,7 @@ class ReadBookActivity : ReadBookBaseActivity(), */ override fun openChapterList() { ReadBook.book?.let { - tocActivity.launch(Intent(this, ChapterListActivity::class.java).apply { - putExtra("bookUrl", it.bookUrl) - }) + tocActivity.launch(it.bookUrl) } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt index 2d986a27b..51e288a13 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt @@ -1,13 +1,12 @@ 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 import android.view.* +import androidx.activity.result.contract.ActivityResultContracts import androidx.documentfile.provider.DocumentFile import com.jaredrummler.android.colorpicker.ColorPickerDialog import io.legado.app.R @@ -24,15 +23,15 @@ import io.legado.app.lib.theme.bottomBackground 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.document.FilePicker +import io.legado.app.ui.document.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 @@ -40,13 +39,27 @@ class BgTextConfigDialog : BaseDialogFragment(), FilePickerDialog.CallBack { } private val binding by viewBinding(DialogReadBgTextBinding::bind) - private val requestCodeBg = 123 - 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() @@ -108,7 +121,9 @@ class BgTextConfigDialog : BaseDialogFragment(), FilePickerDialog.CallBack { tvName.text = getString(R.string.select_image) ivBg.setImageResource(R.drawable.ic_image) ivBg.setColorFilter(primaryTextColor) - root.setOnClickListener { selectImage() } + root.setOnClickListener { + selectBgImage.launch("image/*") + } } } requireContext().assets.list("bg")?.let { @@ -159,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 { @@ -190,13 +200,6 @@ class BgTextConfigDialog : BaseDialogFragment(), FilePickerDialog.CallBack { } } - private fun selectImage() { - val intent = Intent(Intent.ACTION_GET_CONTENT) - intent.addCategory(Intent.CATEGORY_OPENABLE) - intent.type = "image/*" - startActivityForResult(intent, requestCodeBg) - } - @Suppress("BlockingMethodInNonBlockingContext") private fun exportConfig(uri: Uri) { val exportFileName = if (ReadBookConfig.config.name.isBlank()) { @@ -370,27 +373,6 @@ class BgTextConfigDialog : BaseDialogFragment(), FilePickerDialog.CallBack { } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - when (requestCode) { - requestCodeBg -> if (resultCode == RESULT_OK) { - data?.data?.let { uri -> - setBgFromUri(uri) - } - } - 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) diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt index bb4bde76c..44af2dd45 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt @@ -18,6 +18,7 @@ import io.legado.app.base.adapter.RecyclerAdapter import io.legado.app.constant.PreferKey import io.legado.app.data.appDb import io.legado.app.data.entities.HttpTTS +import io.legado.app.databinding.DialogEditTextBinding import io.legado.app.databinding.DialogHttpTtsEditBinding import io.legado.app.databinding.DialogRecyclerViewBinding import io.legado.app.databinding.ItemHttpTtsBinding @@ -25,6 +26,8 @@ import io.legado.app.lib.dialogs.alert import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.primaryColor import io.legado.app.service.help.ReadAloud +import io.legado.app.ui.document.FilePicker +import io.legado.app.ui.document.FilePickerParam import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.utils.* import io.legado.app.utils.viewbindingdelegate.viewBinding @@ -33,10 +36,21 @@ import splitties.init.appCtx class SpeakEngineDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { private val binding by viewBinding(DialogRecyclerViewBinding::bind) + private val ttsUrlKey = "ttsUrlKey" lateinit var adapter: Adapter private val viewModel: SpeakEngineViewModel by viewModels() private var httpTTSData: LiveData>? = null - var engineId = appCtx.getPrefLong(PreferKey.speakEngine) + private var engineId = appCtx.getPrefLong(PreferKey.speakEngine) + private val importDocResult = registerForActivityResult(FilePicker()) { + it?.let { + viewModel.importLocal(it) + } + } + private val exportDirResult = registerForActivityResult(FilePicker()) { + it?.let { + viewModel.export(it) + } + } override fun onStart() { super.onStart() @@ -100,10 +114,45 @@ class SpeakEngineDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener when (item?.itemId) { R.id.menu_add -> editHttpTTS() R.id.menu_default -> viewModel.importDefault() + R.id.menu_import_local -> importDocResult.launch( + FilePickerParam( + mode = FilePicker.FILE, + allowExtensions = arrayOf("txt", "json") + ) + ) + R.id.menu_import_onLine -> importAlert() + R.id.menu_export -> exportDirResult.launch(null) } return true } + private fun importAlert() { + val aCache = ACache.get(requireContext(), cacheDir = false) + val cacheUrls: MutableList = aCache + .getAsString(ttsUrlKey) + ?.splitNotBlank(",") + ?.toMutableList() ?: mutableListOf() + alert(R.string.import_on_line) { + val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply { + editView.setFilterValues(cacheUrls) + editView.delCallBack = { + cacheUrls.remove(it) + aCache.put(ttsUrlKey, cacheUrls.joinToString(",")) + } + } + customView { alertBinding.root } + okButton { + alertBinding.editView.text?.toString()?.let { url -> + if (!cacheUrls.contains(url)) { + cacheUrls.add(0, url) + aCache.put(ttsUrlKey, cacheUrls.joinToString(",")) + } + viewModel.importOnLine(url) + } + } + }.show() + } + @SuppressLint("InflateParams") private fun editHttpTTS(v: HttpTTS? = null) { val httpTTS = v?.copy() ?: HttpTTS() @@ -168,7 +217,6 @@ class SpeakEngineDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener } } - } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt index 3ebaa228d..c7eed9534 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt @@ -1,12 +1,12 @@ package io.legado.app.ui.book.read.config import android.app.Application +import android.net.Uri import io.legado.app.base.BaseViewModel import io.legado.app.data.appDb import io.legado.app.data.entities.HttpTTS import io.legado.app.help.DefaultData -import io.legado.app.utils.GSON -import io.legado.app.utils.fromJsonArray +import io.legado.app.utils.* import rxhttp.wrapper.param.RxHttp import rxhttp.wrapper.param.toText @@ -18,18 +18,52 @@ class SpeakEngineViewModel(application: Application) : BaseViewModel(application } } - fun importOnLine(url: String, finally: (msg: String) -> Unit) { + fun importOnLine(url: String) { execute { RxHttp.get(url).toText("utf-8").await().let { json -> - GSON.fromJsonArray(json)?.let { - appDb.httpTTSDao.insert(*it.toTypedArray()) - } + import(json) } }.onSuccess { - finally("导入成功") + toastOnUi("导入成功") }.onError { - finally("导入失败") + toastOnUi("导入失败") } } + fun importLocal(uri: Uri) { + execute { + uri.readText(context)?.let { + import(it) + } + }.onSuccess { + toastOnUi("导入成功") + }.onError { + toastOnUi("导入失败") + } + } + + fun import(text: String) { + when { + text.isJsonArray() -> { + GSON.fromJsonArray(text)?.let { + appDb.httpTTSDao.insert(*it.toTypedArray()) + } + } + text.isJsonObject() -> { + GSON.fromJsonObject(text)?.let { + appDb.httpTTSDao.insert(it) + } + } + else -> { + throw Exception("格式不对") + } + } + } + + fun export(uri: Uri) { + execute { + val httpTTS = appDb.httpTTSDao.all + uri.writeBytes(context, "httpTts.json", GSON.toJson(httpTTS).toByteArray()) + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt index 55b1a4ec4..c8d7f2045 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt @@ -12,7 +12,6 @@ import androidx.fragment.app.FragmentManager import androidx.fragment.app.viewModels import androidx.lifecycle.LiveData import androidx.recyclerview.widget.ItemTouchHelper -import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.snackbar.Snackbar import io.legado.app.R @@ -72,7 +71,6 @@ class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { private fun initView() = with(binding) { adapter = TocRegexAdapter(requireContext()) - recyclerView.layoutManager = LinearLayoutManager(requireContext()) recyclerView.addItemDecoration(VerticalDivider(requireContext())) recyclerView.adapter = adapter val itemTouchCallback = ItemTouchCallback(adapter) @@ -137,7 +135,7 @@ class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { if (!cacheUrls.contains(defaultUrl)) { cacheUrls.add(0, defaultUrl) } - requireContext().alert(titleResource = R.string.import_book_source_on_line) { + requireContext().alert(titleResource = R.string.import_on_line) { val alertBinding = DialogEditTextBinding.inflate(layoutInflater) alertBinding.apply { editView.setFilterValues(cacheUrls) diff --git a/app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugActivity.kt index fb11c3cfd..76e0b81f6 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugActivity.kt @@ -1,6 +1,5 @@ package io.legado.app.ui.book.source.debug -import android.content.Intent import android.os.Bundle import android.view.Menu import android.view.MenuItem @@ -12,10 +11,8 @@ import io.legado.app.databinding.ActivitySourceDebugBinding import io.legado.app.help.LocalConfig import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.accentColor -import io.legado.app.ui.qrcode.QrCodeActivity +import io.legado.app.ui.qrcode.QrCodeResult import io.legado.app.ui.widget.dialog.TextDialog -import io.legado.app.utils.startActivityForResult - import io.legado.app.utils.toastOnUi import kotlinx.coroutines.launch @@ -26,7 +23,11 @@ class BookSourceDebugActivity : VMBaseActivity { - startActivityForResult(qrRequestCode) + qrCodeResult.launch(null) } R.id.menu_help -> showHelp() } @@ -108,16 +109,4 @@ class BookSourceDebugActivity : VMBaseActivity { - if (resultCode == RESULT_OK) { - data?.getStringExtra("result")?.let { - startSearch(it) - } - } - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt index 0e6db0c39..89fa6dc21 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt @@ -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 @@ -25,10 +24,10 @@ import io.legado.app.lib.dialogs.selector 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.document.FilePicker +import io.legado.app.ui.document.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(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 = ArrayList() private val searchEntities: ArrayList = ArrayList() @@ -50,6 +46,20 @@ class BookSourceEditActivity : private val infoEntities: ArrayList = ArrayList() private val tocEntities: ArrayList = ArrayList() private val contentEntities: ArrayList = 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(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() diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt index f9655fdb8..f76e4705a 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt @@ -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 @@ -31,9 +30,9 @@ import io.legado.app.service.help.CheckSource 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.document.FilePicker +import io.legado.app.ui.document.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(), 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>? = null @@ -62,6 +57,37 @@ class BookSourceActivity : VMBaseActivity { + putExtra("source", it) + } + } + private val importDoc = registerForActivityResult(FilePicker()) { uri -> + uri ?: return@registerForActivityResult + try { + uri.readText(this)?.let { + val dataKey = IntentDataHelp.putData(it) + startActivity { + 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,15 +121,19 @@ class BookSourceActivity : VMBaseActivity startActivity() - R.id.menu_import_source_qr -> startActivityForResult(qrRequestCode) + R.id.menu_import_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_onLine -> showImportDialog() + R.id.menu_import_local -> importDoc.launch( + FilePickerParam( + mode = FilePicker.FILE, + allowExtensions = arrayOf("txt", "json") + ) + ) + R.id.menu_import_onLine -> showImportDialog() R.id.menu_sort_manual -> { item.isChecked = true sortCheck(Sort.Default) @@ -292,7 +322,7 @@ class BookSourceActivity : VMBaseActivity 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 } @@ -372,7 +402,7 @@ class BookSourceActivity : VMBaseActivity if (resultCode == RESULT_OK) { - data?.getStringExtra("result")?.let { - startActivity { - putExtra("source", it) - } - } - } - importRequestCode -> if (resultCode == Activity.RESULT_OK) { - data?.data?.let { uri -> - try { - uri.readText(this)?.let { - val dataKey = IntentDataHelp.putData(it) - startActivity { - 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() diff --git a/app/src/main/java/io/legado/app/ui/book/toc/BookmarkFragment.kt b/app/src/main/java/io/legado/app/ui/book/toc/BookmarkFragment.kt index 4c4188a3f..73235d31a 100644 --- a/app/src/main/java/io/legado/app/ui/book/toc/BookmarkFragment.kt +++ b/app/src/main/java/io/legado/app/ui/book/toc/BookmarkFragment.kt @@ -13,6 +13,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import io.legado.app.R import io.legado.app.base.VMBaseFragment import io.legado.app.data.appDb +import io.legado.app.data.entities.Book import io.legado.app.data.entities.Bookmark import io.legado.app.databinding.DialogBookmarkBinding import io.legado.app.databinding.FragmentBookmarkBinding @@ -35,7 +36,9 @@ class BookmarkFragment : VMBaseFragment(R.layout.fragment_ override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { viewModel.bookMarkCallBack = this initRecyclerView() - initData() + viewModel.bookData.observe(this) { + initData(it) + } } private fun initRecyclerView() { @@ -46,20 +49,20 @@ class BookmarkFragment : VMBaseFragment(R.layout.fragment_ binding.recyclerView.adapter = adapter } - private fun initData() { - viewModel.book?.let { book -> - bookmarkLiveData?.removeObservers(viewLifecycleOwner) - bookmarkLiveData = - LivePagedListBuilder( - appDb.bookmarkDao.observeByBook(book.bookUrl, book.name, book.author), 20 - ).build() - bookmarkLiveData?.observe(viewLifecycleOwner, { adapter.submitList(it) }) - } + private fun initData(book: Book) { + bookmarkLiveData?.removeObservers(viewLifecycleOwner) + bookmarkLiveData = + LivePagedListBuilder( + appDb.bookmarkDao.observeByBook(book.bookUrl, book.name, book.author), 20 + ).build() + bookmarkLiveData?.observe(viewLifecycleOwner, { adapter.submitList(it) }) } override fun startBookmarkSearch(newText: String?) { if (newText.isNullOrBlank()) { - initData() + viewModel.bookData.value?.let { + initData(it) + } } else { bookmarkLiveData?.removeObservers(viewLifecycleOwner) bookmarkLiveData = LivePagedListBuilder( diff --git a/app/src/main/java/io/legado/app/ui/book/toc/ChapterListActivity.kt b/app/src/main/java/io/legado/app/ui/book/toc/ChapterListActivity.kt index dfe49d6e7..4f429447a 100644 --- a/app/src/main/java/io/legado/app/ui/book/toc/ChapterListActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/toc/ChapterListActivity.kt @@ -6,16 +6,15 @@ import androidx.activity.viewModels import androidx.appcompat.widget.SearchView import androidx.core.view.isGone import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentPagerAdapter +import androidx.viewpager2.adapter.FragmentStateAdapter import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator import io.legado.app.R import io.legado.app.base.VMBaseActivity import io.legado.app.databinding.ActivityChapterListBinding import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.primaryTextColor - import io.legado.app.utils.gone import io.legado.app.utils.visible @@ -35,11 +34,15 @@ class ChapterListActivity : VMBaseActivity + when (position) { + 0 -> tab.setText(R.string.chapter_list) + else -> tab.setText(R.string.bookmark) } + }.attach() + intent.getStringExtra("bookUrl")?.let { + viewModel.initBook(it) } } @@ -72,23 +75,16 @@ class ChapterListActivity : VMBaseActivity BookmarkFragment() - else -> ChapterListFragment() - } - } + private inner class TabFragmentPageAdapter : FragmentStateAdapter(this) { - override fun getCount(): Int { + override fun getItemCount(): Int { return 2 } - override fun getPageTitle(position: Int): CharSequence { + override fun createFragment(position: Int): Fragment { return when (position) { - 1 -> getString(R.string.bookmark) - else -> getString(R.string.chapter_list) + 0 -> ChapterListFragment() + else -> BookmarkFragment() } } diff --git a/app/src/main/java/io/legado/app/ui/book/toc/ChapterListFragment.kt b/app/src/main/java/io/legado/app/ui/book/toc/ChapterListFragment.kt index 72bca9746..57ccc8305 100644 --- a/app/src/main/java/io/legado/app/ui/book/toc/ChapterListFragment.kt +++ b/app/src/main/java/io/legado/app/ui/book/toc/ChapterListFragment.kt @@ -49,7 +49,9 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragme ivChapterBottom.setColorFilter(btc) initRecyclerView() initView() - initBook() + viewModel.bookData.observe(this@ChapterListFragment) { + initBook(it) + } } private fun initRecyclerView() { @@ -73,15 +75,13 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragme } @SuppressLint("SetTextI18n") - private fun initBook() { + private fun initBook(book: Book) { launch { initDoc() - viewModel.book?.let { - durChapterIndex = it.durChapterIndex - binding.tvCurrentChapterInfo.text = - "${it.durChapterTitle}(${it.durChapterIndex + 1}/${it.totalChapterNum})" - initCacheFileNames(it) - } + durChapterIndex = book.durChapterIndex + binding.tvCurrentChapterInfo.text = + "${book.durChapterTitle}(${book.durChapterIndex + 1}/${book.totalChapterNum})" + initCacheFileNames(book) } } @@ -108,7 +108,7 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragme override fun observeLiveBus() { observeEvent(EventBus.SAVE_CONTENT) { chapter -> - viewModel.book?.bookUrl?.let { bookUrl -> + viewModel.bookData.value?.bookUrl?.let { bookUrl -> if (chapter.bookUrl == bookUrl) { adapter.cacheFileNames.add(chapter.getFileName()) adapter.notifyItemChanged(chapter.index, true) @@ -130,7 +130,7 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragme } override val isLocalBook: Boolean - get() = viewModel.book?.isLocalBook() == true + get() = viewModel.bookData.value?.isLocalBook() == true override fun durChapterIndex(): Int { return min(durChapterIndex, adapter.itemCount) diff --git a/app/src/main/java/io/legado/app/ui/book/toc/ChapterListViewModel.kt b/app/src/main/java/io/legado/app/ui/book/toc/ChapterListViewModel.kt index 27f6011ae..f85ff5749 100644 --- a/app/src/main/java/io/legado/app/ui/book/toc/ChapterListViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/toc/ChapterListViewModel.kt @@ -2,22 +2,23 @@ package io.legado.app.ui.book.toc import android.app.Application +import androidx.lifecycle.MutableLiveData import io.legado.app.base.BaseViewModel import io.legado.app.data.appDb import io.legado.app.data.entities.Book class ChapterListViewModel(application: Application) : BaseViewModel(application) { var bookUrl: String = "" - var book: Book? = null + var bookData = MutableLiveData() var chapterCallBack: ChapterListCallBack? = null var bookMarkCallBack: BookmarkCallBack? = null - fun initBook(bookUrl: String, success: () -> Unit) { + fun initBook(bookUrl: String) { this.bookUrl = bookUrl execute { - book = appDb.bookDao.getBook(bookUrl) - }.onSuccess { - success.invoke() + appDb.bookDao.getBook(bookUrl)?.let { + bookData.postValue(it) + } } } diff --git a/app/src/main/java/io/legado/app/ui/book/toc/TocActivityResult.kt b/app/src/main/java/io/legado/app/ui/book/toc/TocActivityResult.kt new file mode 100644 index 000000000..1e8f688de --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/toc/TocActivityResult.kt @@ -0,0 +1,26 @@ +package io.legado.app.ui.book.toc + +import android.app.Activity.RESULT_OK +import android.content.Context +import android.content.Intent +import androidx.activity.result.contract.ActivityResultContract + +class TocActivityResult : ActivityResultContract?>() { + + override fun createIntent(context: Context, input: String?): Intent { + return Intent(context, ChapterListActivity::class.java) + .putExtra("bookUrl", input) + } + + override fun parseResult(resultCode: Int, intent: Intent?): Pair? { + if (resultCode == RESULT_OK) { + intent?.let { + return Pair( + it.getIntExtra("index", 0), + it.getIntExtra("chapterPos", 0) + ) + } + } + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt index bd1865828..8359259d4 100644 --- a/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt @@ -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.document.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("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() + } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt b/app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt deleted file mode 100644 index 46fee049a..000000000 --- a/app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt +++ /dev/null @@ -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) - } - } - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt index e8f27277b..9c09e7620 100644 --- a/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt @@ -1,7 +1,6 @@ package io.legado.app.ui.config import android.annotation.SuppressLint -import android.app.Activity.RESULT_OK import android.content.ComponentName import android.content.Intent import android.content.SharedPreferences @@ -10,6 +9,7 @@ import android.net.Uri import android.os.Bundle import android.os.Process import android.view.View +import androidx.activity.result.contract.ActivityResultContracts import androidx.documentfile.provider.DocumentFile import androidx.preference.ListPreference import androidx.preference.Preference @@ -38,14 +38,16 @@ import java.io.File class OtherConfigFragment : BasePreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener { - private val requestCodeCover = 231 - private val packageManager = appCtx.packageManager private val componentName = ComponentName( appCtx, SharedReceiverActivity::class.java.name ) private val webPort get() = getPrefInt(PreferKey.webPort, 1122) + private val selectCoverImage = registerForActivityResult(ActivityResultContracts.GetContent()) { + it ?: return@registerForActivityResult + setCoverFromUri(it) + } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { putPrefBoolean(PreferKey.processText, isProcessTextEnabled()) @@ -88,13 +90,13 @@ class OtherConfigFragment : BasePreferenceFragment(), } PreferKey.cleanCache -> clearCache() PreferKey.defaultCover -> if (getPrefString(PreferKey.defaultCover).isNullOrEmpty()) { - selectImage(requestCodeCover) + selectCoverImage.launch("image/*") } else { selector(items = arrayListOf("删除图片", "选择图片")) { _, i -> if (i == 0) { removePref(PreferKey.defaultCover) } else { - selectImage(requestCodeCover) + selectCoverImage.launch("image/*") } } } @@ -183,14 +185,6 @@ class OtherConfigFragment : BasePreferenceFragment(), }.show() } - @Suppress("SameParameterValue") - private fun selectImage(requestCode: Int) { - val intent = Intent(Intent.ACTION_GET_CONTENT) - intent.addCategory(Intent.CATEGORY_OPENABLE) - intent.type = "image/*" - startActivityForResult(intent, requestCode) - } - private fun isProcessTextEnabled(): Boolean { return packageManager.getComponentEnabledSetting(componentName) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED } @@ -246,15 +240,4 @@ class OtherConfigFragment : BasePreferenceFragment(), } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - when (requestCode) { - requestCodeCover -> if (resultCode == RESULT_OK) { - data?.data?.let { uri -> - setCoverFromUri(uri) - } - } - } - } - } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt index 3b13342a7..4bcb6faf8 100644 --- a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt @@ -1,8 +1,6 @@ package io.legado.app.ui.config import android.annotation.SuppressLint -import android.app.Activity -import android.content.Intent import android.content.SharedPreferences import android.net.Uri import android.os.Build @@ -11,6 +9,7 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View +import androidx.activity.result.contract.ActivityResultContracts import androidx.documentfile.provider.DocumentFile import androidx.preference.Preference import io.legado.app.R @@ -38,8 +37,18 @@ import java.io.File class ThemeConfigFragment : BasePreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener { - private val requestCodeBgImage = 234 - private val requestCodeBgImageN = 342 + private val selectLightBg = registerForActivityResult(ActivityResultContracts.GetContent()) { + it ?: return@registerForActivityResult + setBgFromUri(it, PreferKey.bgImage) { + upTheme(false) + } + } + private val selectDarkBg = registerForActivityResult(ActivityResultContracts.GetContent()) { + it ?: return@registerForActivityResult + setBgFromUri(it, PreferKey.bgImageN) { + upTheme(true) + } + } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_config_theme) @@ -186,26 +195,26 @@ class ThemeConfigFragment : BasePreferenceFragment(), "themeList" -> ThemeListDialog().show(childFragmentManager, "themeList") "saveDayTheme", "saveNightTheme" -> saveThemeAlert(key) PreferKey.bgImage -> if (getPrefString(PreferKey.bgImage).isNullOrEmpty()) { - selectImage(requestCodeBgImage) + selectLightBg.launch("image/*") } else { selector(items = arrayListOf("删除图片", "选择图片")) { _, i -> if (i == 0) { removePref(PreferKey.bgImage) upTheme(false) } else { - selectImage(requestCodeBgImage) + selectLightBg.launch("image/*") } } } PreferKey.bgImageN -> if (getPrefString(PreferKey.bgImageN).isNullOrEmpty()) { - selectImage(requestCodeBgImageN) + selectDarkBg.launch("image/*") } else { selector(items = arrayListOf("删除图片", "选择图片")) { _, i -> if (i == 0) { removePref(PreferKey.bgImageN) upTheme(true) } else { - selectImage(requestCodeBgImageN) + selectDarkBg.launch("image/*") } } } @@ -213,13 +222,6 @@ class ThemeConfigFragment : BasePreferenceFragment(), return super.onPreferenceTreeClick(preference) } - private fun selectImage(requestCode: Int) { - val intent = Intent(Intent.ACTION_GET_CONTENT) - intent.addCategory(Intent.CATEGORY_OPENABLE) - intent.type = "image/*" - startActivityForResult(intent, requestCode) - } - @SuppressLint("InflateParams") private fun saveThemeAlert(key: String) { alert(R.string.theme_name) { @@ -302,24 +304,4 @@ class ThemeConfigFragment : BasePreferenceFragment(), } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - when (requestCode) { - requestCodeBgImage -> if (resultCode == Activity.RESULT_OK) { - data?.data?.let { uri -> - setBgFromUri(uri, PreferKey.bgImage) { - upTheme(false) - } - } - } - requestCodeBgImageN -> if (resultCode == Activity.RESULT_OK) { - data?.data?.let { uri -> - setBgFromUri(uri, PreferKey.bgImageN) { - upTheme(true) - } - } - } - } - } - } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/document/FilePicker.kt b/app/src/main/java/io/legado/app/ui/document/FilePicker.kt new file mode 100644 index 000000000..dfc0d5789 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/document/FilePicker.kt @@ -0,0 +1,43 @@ +package io.legado.app.ui.document + +import android.app.Activity.RESULT_OK +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.activity.result.contract.ActivityResultContract + +@Suppress("unused") +class FilePicker : ActivityResultContract() { + + companion object { + const val DIRECTORY = 0 + const val FILE = 1 + } + + 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) + } + return intent + } + + override fun parseResult(resultCode: Int, intent: Intent?): Uri? { + if (resultCode == RESULT_OK) { + return intent?.data + } + return null + } + +} + +@Suppress("ArrayInDataClass") +data class FilePickerParam( + var mode: Int = 0, + var title: String? = null, + var allowExtensions: Array = arrayOf(), + var otherActions: Array? = null, +) diff --git a/app/src/main/java/io/legado/app/ui/document/FilePickerActivity.kt b/app/src/main/java/io/legado/app/ui/document/FilePickerActivity.kt new file mode 100644 index 000000000..bcbf8dce7 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/document/FilePickerActivity.kt @@ -0,0 +1,137 @@ +package io.legado.app.ui.document + +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.webkit.MimeTypeMap +import androidx.activity.result.contract.ActivityResultContracts +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( + theme = Theme.Transparent + ), FilePickerDialog.CallBack { + + private val selectDocTree = + registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { + onResult(Intent().setData(it)) + } + + private val selectDoc = registerForActivityResult(ActivityResultContracts.OpenDocument()) { + 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)) + } + 1 -> 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?): Array { + val types = hashSetOf() + if (allowExtensions.isNullOrEmpty()) { + types.add("*/*") + } else { + allowExtensions.forEach { + when (it) { + "*" -> types.add("*/*") + "txt", "xml" -> types.add("text/*") + else -> { + val mime = MimeTypeMap.getSingleton() + .getMimeTypeFromExtension("json") + ?: "application/octet-stream" + types.add(mime) + } + } + } + } + return types.toTypedArray() + } + + override fun onResult(data: Intent) { + if (data.data != null) { + setResult(RESULT_OK, data) + } + finish() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/filepicker/FilePickerDialog.kt b/app/src/main/java/io/legado/app/ui/document/FilePickerDialog.kt similarity index 81% rename from app/src/main/java/io/legado/app/ui/filepicker/FilePickerDialog.kt rename to app/src/main/java/io/legado/app/ui/document/FilePickerDialog.kt index 1150a1580..af573efb0 100644 --- a/app/src/main/java/io/legado/app/ui/filepicker/FilePickerDialog.kt +++ b/app/src/main/java/io/legado/app/ui/document/FilePickerDialog.kt @@ -1,6 +1,6 @@ -package io.legado.app.ui.filepicker +package io.legado.app.ui.document -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,10 +15,11 @@ 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.adapter.FileAdapter -import io.legado.app.ui.filepicker.adapter.PathAdapter +import io.legado.app.ui.document.FilePicker.Companion.DIRECTORY +import io.legado.app.ui.document.FilePicker.Companion.FILE +import io.legado.app.ui.document.adapter.FileAdapter +import io.legado.app.ui.document.adapter.PathAdapter import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.* import io.legado.app.utils.viewbindingdelegate.viewBinding @@ -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) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/filepicker/adapter/FileAdapter.kt b/app/src/main/java/io/legado/app/ui/document/adapter/FileAdapter.kt similarity index 96% rename from app/src/main/java/io/legado/app/ui/filepicker/adapter/FileAdapter.kt rename to app/src/main/java/io/legado/app/ui/document/adapter/FileAdapter.kt index e4bedec58..97537753a 100644 --- a/app/src/main/java/io/legado/app/ui/filepicker/adapter/FileAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/document/adapter/FileAdapter.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.filepicker.adapter +package io.legado.app.ui.document.adapter import android.content.Context @@ -9,9 +9,9 @@ import io.legado.app.databinding.ItemFileFilepickerBinding import io.legado.app.help.AppConfig import io.legado.app.lib.theme.getPrimaryDisabledTextColor import io.legado.app.lib.theme.getPrimaryTextColor -import io.legado.app.ui.filepicker.entity.FileItem -import io.legado.app.ui.filepicker.utils.ConvertUtils -import io.legado.app.ui.filepicker.utils.FilePickerIcon +import io.legado.app.ui.document.entity.FileItem +import io.legado.app.ui.document.utils.ConvertUtils +import io.legado.app.ui.document.utils.FilePickerIcon import io.legado.app.utils.FileUtils import java.io.File diff --git a/app/src/main/java/io/legado/app/ui/filepicker/adapter/PathAdapter.kt b/app/src/main/java/io/legado/app/ui/document/adapter/PathAdapter.kt similarity index 93% rename from app/src/main/java/io/legado/app/ui/filepicker/adapter/PathAdapter.kt rename to app/src/main/java/io/legado/app/ui/document/adapter/PathAdapter.kt index 60ea3f648..65311c13c 100644 --- a/app/src/main/java/io/legado/app/ui/filepicker/adapter/PathAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/document/adapter/PathAdapter.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.filepicker.adapter +package io.legado.app.ui.document.adapter import android.content.Context import android.os.Environment @@ -6,8 +6,8 @@ import android.view.ViewGroup import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.RecyclerAdapter import io.legado.app.databinding.ItemPathFilepickerBinding -import io.legado.app.ui.filepicker.utils.ConvertUtils -import io.legado.app.ui.filepicker.utils.FilePickerIcon +import io.legado.app.ui.document.utils.ConvertUtils +import io.legado.app.ui.document.utils.FilePickerIcon import java.util.* diff --git a/app/src/main/java/io/legado/app/ui/filepicker/entity/FileItem.kt b/app/src/main/java/io/legado/app/ui/document/entity/FileItem.kt similarity index 87% rename from app/src/main/java/io/legado/app/ui/filepicker/entity/FileItem.kt rename to app/src/main/java/io/legado/app/ui/document/entity/FileItem.kt index a558fcc80..88cd6b6b2 100644 --- a/app/src/main/java/io/legado/app/ui/filepicker/entity/FileItem.kt +++ b/app/src/main/java/io/legado/app/ui/document/entity/FileItem.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.filepicker.entity +package io.legado.app.ui.document.entity import android.graphics.drawable.Drawable diff --git a/app/src/main/java/io/legado/app/ui/filepicker/entity/JavaBean.kt b/app/src/main/java/io/legado/app/ui/document/entity/JavaBean.kt similarity index 97% rename from app/src/main/java/io/legado/app/ui/filepicker/entity/JavaBean.kt rename to app/src/main/java/io/legado/app/ui/document/entity/JavaBean.kt index a6fb0c2bc..eb81f1c71 100644 --- a/app/src/main/java/io/legado/app/ui/filepicker/entity/JavaBean.kt +++ b/app/src/main/java/io/legado/app/ui/document/entity/JavaBean.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.filepicker.entity +package io.legado.app.ui.document.entity import java.io.Serializable import java.lang.reflect.Field diff --git a/app/src/main/java/io/legado/app/ui/filepicker/utils/ConvertUtils.kt b/app/src/main/java/io/legado/app/ui/document/utils/ConvertUtils.kt similarity index 98% rename from app/src/main/java/io/legado/app/ui/filepicker/utils/ConvertUtils.kt rename to app/src/main/java/io/legado/app/ui/document/utils/ConvertUtils.kt index b4f0f0b7e..8844b3f3e 100644 --- a/app/src/main/java/io/legado/app/ui/filepicker/utils/ConvertUtils.kt +++ b/app/src/main/java/io/legado/app/ui/document/utils/ConvertUtils.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.filepicker.utils +package io.legado.app.ui.document.utils import android.content.res.Resources import android.graphics.Bitmap diff --git a/app/src/main/java/io/legado/app/ui/filepicker/utils/FilePickerIcon.java b/app/src/main/java/io/legado/app/ui/document/utils/FilePickerIcon.java similarity index 99% rename from app/src/main/java/io/legado/app/ui/filepicker/utils/FilePickerIcon.java rename to app/src/main/java/io/legado/app/ui/document/utils/FilePickerIcon.java index 238b88c43..0f3447f6a 100644 --- a/app/src/main/java/io/legado/app/ui/filepicker/utils/FilePickerIcon.java +++ b/app/src/main/java/io/legado/app/ui/document/utils/FilePickerIcon.java @@ -1,4 +1,4 @@ -package io.legado.app.ui.filepicker.utils; +package io.legado.app.ui.document.utils; /** * Generated by https://github.com/gzu-liyujiang/Image2ByteVar diff --git a/app/src/main/java/io/legado/app/ui/filepicker/FilePicker.kt b/app/src/main/java/io/legado/app/ui/filepicker/FilePicker.kt deleted file mode 100644 index 1d784ec39..000000000 --- a/app/src/main/java/io/legado/app/ui/filepicker/FilePicker.kt +++ /dev/null @@ -1,284 +0,0 @@ -package io.legado.app.ui.filepicker - -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 - -@Suppress("unused") -object FilePicker { - - fun selectFolder( - activity: AppCompatActivity, - requestCode: Int, - title: String = activity.getString(R.string.select_folder), - otherActions: List? = 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? = 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 = arrayOf(), - otherActions: List? = 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 = arrayOf(), - otherActions: List? = 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 - } - - 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() - } - - 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): Array { - val types = hashSetOf() - 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() - } -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt index 4ff1e9b50..090edd420 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt @@ -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 @@ -29,8 +27,8 @@ import io.legado.app.ui.book.cache.CacheActivity 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.document.FilePicker +import io.legado.app.ui.document.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(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(R.layout.fragment_b private var bookGroupLiveData: LiveData>? = null private val bookGroups = mutableListOf() private val fragmentMap = hashMapOf() + 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(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) { diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt index 0f937abbc..87f048fda 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt @@ -73,6 +73,7 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application) val bookMap = hashMapOf() bookMap["name"] = it.name bookMap["author"] = it.author + bookMap["intro"] = it.getDisplayIntro() exportList.add(bookMap) } GSON.toJson(exportList) diff --git a/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt b/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt index 4ce39781a..bf09a6171 100644 --- a/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt @@ -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) - } - /** * 配置 */ diff --git a/app/src/main/java/io/legado/app/ui/qrcode/QrCodeResult.kt b/app/src/main/java/io/legado/app/ui/qrcode/QrCodeResult.kt new file mode 100644 index 000000000..b39f09791 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/qrcode/QrCodeResult.kt @@ -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() { + + 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 + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt b/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt index 13469d229..f0097b62b 100644 --- a/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt +++ b/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt @@ -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 @@ -29,9 +28,9 @@ import io.legado.app.lib.theme.ATH 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.document.FilePicker +import io.legado.app.ui.document.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,52 @@ import java.io.File class ReplaceRuleActivity : VMBaseActivity(), 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() private var groupMenu: SubMenu? = null private var replaceRuleLiveData: LiveData>? = null private var dataInit = false + private val qrCodeResult = registerForActivityResult(QrCodeResult()) { + it ?: return@registerForActivityResult + startActivity { + putExtra("source", it) + } + } + private val editActivity = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == RESULT_OK) { + setResult(RESULT_OK) + } + } + private val importDoc = registerForActivityResult(FilePicker()) { uri -> + kotlin.runCatching { + uri?.readText(this)?.let { + val dataKey = IntentDataHelp.putData(it) + startActivity { + 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) @@ -189,10 +216,14 @@ class ReplaceRuleActivity : VMBaseActivity 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(importRequestCodeQr) + R.id.menu_import_onLine -> showImportDialog() + R.id.menu_import_local -> importDoc.launch( + FilePickerParam( + mode = FilePicker.FILE, + allowExtensions = arrayOf("txt", "json") + ) + ) + R.id.menu_import_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 +236,7 @@ class ReplaceRuleActivity : VMBaseActivity 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 +294,6 @@ class ReplaceRuleActivity : VMBaseActivity { - data?.data?.let { uri -> - kotlin.runCatching { - uri.readText(this)?.let { - val dataKey = IntentDataHelp.putData(it) - startActivity { - putExtra("dataKey", dataKey) - } - } - }.onFailure { - toastOnUi("readTextError:${it.localizedMessage}") - } - } - } - importRequestCodeQr -> { - data?.getStringExtra("result")?.let { - startActivity { - 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() } diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssSortActivity.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssSortActivity.kt index 46d3480d7..3ebe5e74e 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssSortActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssSortActivity.kt @@ -1,38 +1,47 @@ package io.legado.app.ui.rss.article -import android.app.Activity import android.content.Intent import android.os.Bundle import android.view.Menu import android.view.MenuItem +import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentStatePagerAdapter +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.google.android.material.tabs.TabLayoutMediator import io.legado.app.R import io.legado.app.base.VMBaseActivity import io.legado.app.databinding.ActivityRssArtivlesBinding import io.legado.app.ui.rss.source.edit.RssSourceEditActivity import io.legado.app.utils.gone -import io.legado.app.utils.startActivityForResult import io.legado.app.utils.visible class RssSortActivity : VMBaseActivity() { override val viewModel: RssSortViewModel by viewModels() - private val editSource = 12319 private val fragments = linkedMapOf() private lateinit var adapter: TabFragmentPageAdapter + private val upSourceResult = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { + if (it.resultCode == RESULT_OK) { + viewModel.initData(intent) { + upFragments() + } + } + } override fun getViewBinding(): ActivityRssArtivlesBinding { return ActivityRssArtivlesBinding.inflate(layoutInflater) } override fun onActivityCreated(savedInstanceState: Bundle?) { - adapter = TabFragmentPageAdapter(supportFragmentManager) - binding.tabLayout.setupWithViewPager(binding.viewPager) + adapter = TabFragmentPageAdapter() binding.viewPager.adapter = adapter + TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position -> + tab.text = fragments.keys.elementAt(position) + }.attach() viewModel.titleLiveData.observe(this, { binding.titleBar.title = it }) @@ -49,9 +58,10 @@ class RssSortActivity : VMBaseActivity viewModel.rssSource?.sourceUrl?.let { - startActivityForResult(editSource) { - putExtra("data", it) - } + upSourceResult.launch( + Intent(this, RssSourceEditActivity::class.java) + .putExtra("data", it) + ) } R.id.menu_clear -> { viewModel.url?.let { @@ -79,35 +89,15 @@ class RssSortActivity : VMBaseActivity if (resultCode == Activity.RESULT_OK) { - viewModel.initData(intent) { - upFragments() - } - } - } - } - - private inner class TabFragmentPageAdapter(fm: FragmentManager) : - FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { - - override fun getItemPosition(`object`: Any): Int { - return POSITION_NONE - } + private inner class TabFragmentPageAdapter : FragmentStateAdapter(this) { - override fun getPageTitle(position: Int): CharSequence { - return fragments.keys.elementAt(position) + override fun getItemCount(): Int { + return fragments.size } - override fun getItem(position: Int): Fragment { + override fun createFragment(position: Int): Fragment { return fragments.values.elementAt(position) } - - override fun getCount(): Int { - return fragments.size - } } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt index 42372eaf5..05789e00a 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt @@ -23,8 +23,8 @@ import io.legado.app.service.help.Download 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.document.FilePicker +import io.legado.app.ui.document.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(false), - FilePickerDialog.CallBack, ReadRssViewModel.CallBack { override val viewModel: ReadRssViewModel @@ -44,6 +43,10 @@ class ReadRssActivity : VMBaseActivity 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 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 } } - 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() diff --git a/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt b/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt index c70525047..6b3f2f0de 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt @@ -1,7 +1,6 @@ package io.legado.app.ui.rss.source.edit import android.app.Activity -import android.content.Intent import android.graphics.Rect import android.os.Bundle import android.view.Gravity @@ -20,7 +19,7 @@ import io.legado.app.help.LocalConfig import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.selector import io.legado.app.lib.theme.ATH -import io.legado.app.ui.qrcode.QrCodeActivity +import io.legado.app.ui.qrcode.QrCodeResult import io.legado.app.ui.rss.source.debug.RssSourceDebugActivity import io.legado.app.ui.widget.KeyboardToolPop import io.legado.app.ui.widget.dialog.TextDialog @@ -34,9 +33,15 @@ class RssSourceEditActivity : private var mSoftKeyboardTool: PopupWindow? = null private var mIsSoftKeyBoardShowing = false - private val qrRequestCode = 101 private val adapter = RssSourceEditAdapter() private val sourceEntities: ArrayList = ArrayList() + private val qrCodeResult = registerForActivityResult(QrCodeResult()) { + it?.let { + viewModel.importSource(it) { source: RssSource -> + upRecyclerView(source) + } + } + } override fun getViewBinding(): ActivityRssSourceEditBinding { return ActivityRssSourceEditBinding.inflate(layoutInflater) @@ -107,7 +112,7 @@ class RssSourceEditActivity : } } R.id.menu_copy_source -> sendToClip(GSON.toJson(getRssSource())) - R.id.menu_qr_code_camera -> startActivityForResult(qrRequestCode) + R.id.menu_qr_code_camera -> qrCodeResult.launch(null) R.id.menu_paste_source -> viewModel.pasteSource { upRecyclerView(it) } R.id.menu_share_str -> share(GSON.toJson(getRssSource())) R.id.menu_share_qr -> shareWithQr( @@ -269,16 +274,4 @@ class RssSourceEditActivity : } } - 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: RssSource -> - upRecyclerView(source) - } - } - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt index 3a763c87b..6565ab242 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt @@ -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 @@ -25,9 +24,9 @@ import io.legado.app.lib.dialogs.alert 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.document.FilePicker +import io.legado.app.ui.document.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(), 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>? = null private var groups = hashSetOf() private var groupMenu: SubMenu? = null + private val qrCodeResult = registerForActivityResult(QrCodeResult()) { + it ?: return@registerForActivityResult + startActivity { + putExtra("source", it) + } + } + private val importDoc = registerForActivityResult(FilePicker()) { uri -> + kotlin.runCatching { + uri?.readText(this)?.let { + val dataKey = IntentDataHelp.putData(it) + startActivity { + 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 startActivity() - R.id.menu_import_source_local -> FilePicker - .selectFile(this, importRequestCode, allowExtensions = arrayOf("txt", "json")) - R.id.menu_import_source_onLine -> showImportDialog() - R.id.menu_import_source_qr -> startActivityForResult(qrRequestCode) + R.id.menu_import_local -> importDoc.launch( + FilePickerParam( + mode = FilePicker.FILE, + allowExtensions = arrayOf("txt", "json") + ) + ) + R.id.menu_import_onLine -> showImportDialog() + R.id.menu_import_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 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()) } @@ -235,7 +266,7 @@ class RssSourceActivity : VMBaseActivity if (resultCode == Activity.RESULT_OK) { - data?.data?.let { uri -> - kotlin.runCatching { - uri.readText(this)?.let { - val dataKey = IntentDataHelp.putData(it) - startActivity { - putExtra("dataKey", dataKey) - } - } - }.onFailure { - toastOnUi("readTextError:${it.localizedMessage}") - } - } - } - qrRequestCode -> if (resultCode == RESULT_OK) { - data?.getStringExtra("result")?.let { - startActivity { - 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) } diff --git a/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt b/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt index 6be1e4d65..e4b2d6724 100644 --- a/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt +++ b/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt @@ -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 @@ -20,8 +19,8 @@ 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.primaryColor -import io.legado.app.ui.filepicker.FilePicker -import io.legado.app.ui.filepicker.FilePickerDialog +import io.legado.app.ui.document.FilePicker +import io.legado.app.ui.document.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("") } diff --git a/app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScrollRecyclerView.kt b/app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScrollRecyclerView.kt index 35623f5e2..a7299f5e6 100644 --- a/app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScrollRecyclerView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScrollRecyclerView.kt @@ -150,7 +150,7 @@ class FastScrollRecyclerView : RecyclerView { super.onAttachedToWindow() mFastScroller.attachRecyclerView(this) val parent = parent - if (parent is ViewGroup) { + if (parent is ViewGroup && parent.indexOfChild(mFastScroller) == -1) { parent.addView(mFastScroller) mFastScroller.setLayoutParams(parent) } diff --git a/app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScroller.kt b/app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScroller.kt index a8372689e..857035158 100644 --- a/app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScroller.kt +++ b/app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScroller.kt @@ -152,12 +152,10 @@ class FastScroller : LinearLayout { fun attachRecyclerView(recyclerView: RecyclerView) { mRecyclerView = recyclerView - if (mRecyclerView != null) { - mRecyclerView!!.addOnScrollListener(mScrollListener) - post { - // set initial positions for bubble and handle - setViewPositions(getScrollProportion(mRecyclerView)) - } + mRecyclerView!!.addOnScrollListener(mScrollListener) + post { + // set initial positions for bubble and handle + setViewPositions(getScrollProportion(mRecyclerView)) } } diff --git a/app/src/main/java/io/legado/app/utils/ActivityExtensions.kt b/app/src/main/java/io/legado/app/utils/ActivityExtensions.kt index 37a46f74c..72840c097 100644 --- a/app/src/main/java/io/legado/app/utils/ActivityExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/ActivityExtensions.kt @@ -1,20 +1,11 @@ package io.legado.app.utils import android.app.Activity -import android.content.Intent import android.os.Build import android.util.DisplayMetrics import android.view.WindowInsets import android.view.WindowMetrics - -inline fun Activity.startActivityForResult( - requestCode: Int, - configIntent: Intent.() -> Unit = {} -) { - startActivityForResult(Intent(this, A::class.java).apply(configIntent), requestCode) -} - fun Activity.getSize(): DisplayMetrics { val displayMetrics = DisplayMetrics() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { diff --git a/app/src/main/java/io/legado/app/utils/FileUtils.kt b/app/src/main/java/io/legado/app/utils/FileUtils.kt index 6c88e70e0..40ef4e2b1 100644 --- a/app/src/main/java/io/legado/app/utils/FileUtils.kt +++ b/app/src/main/java/io/legado/app/utils/FileUtils.kt @@ -3,7 +3,7 @@ package io.legado.app.utils import android.os.Environment import android.webkit.MimeTypeMap import androidx.annotation.IntDef -import io.legado.app.ui.filepicker.utils.ConvertUtils +import io.legado.app.ui.document.utils.ConvertUtils import splitties.init.appCtx import java.io.* import java.nio.charset.Charset diff --git a/app/src/main/java/io/legado/app/utils/FragmentExtensions.kt b/app/src/main/java/io/legado/app/utils/FragmentExtensions.kt index 5a086cf29..1883dd7e0 100644 --- a/app/src/main/java/io/legado/app/utils/FragmentExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/FragmentExtensions.kt @@ -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 Fragment.startActivity( configIntent: Intent.() -> Unit = {} ) { startActivity(Intent(requireContext(), T::class.java).apply(configIntent)) -} - -inline fun Fragment.startActivityForResult( - requestCode: Int, - configIntent: Intent.() -> Unit = {} -) { - startActivityForResult(Intent(requireContext(), T::class.java).apply(configIntent), requestCode) } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/UriExtensions.kt b/app/src/main/java/io/legado/app/utils/UriExtensions.kt index 509271fb2..3e99bd0a2 100644 --- a/app/src/main/java/io/legado/app/utils/UriExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/UriExtensions.kt @@ -2,13 +2,14 @@ package io.legado.app.utils import android.content.Context import android.net.Uri +import androidx.documentfile.provider.DocumentFile import java.io.File fun Uri.isContentScheme() = this.scheme == "content" @Throws(Exception::class) fun Uri.readBytes(context: Context): ByteArray? { - if (this.toString().isContentScheme()) { + if (this.isContentScheme()) { return DocumentUtils.readBytes(context, this) } else { val path = RealPathUtil.getPath(context, this) @@ -32,7 +33,7 @@ fun Uri.writeBytes( context: Context, byteArray: ByteArray ): Boolean { - if (this.toString().isContentScheme()) { + if (this.isContentScheme()) { return DocumentUtils.writeBytes(context, byteArray, this) } else { val path = RealPathUtil.getPath(context, this) @@ -48,3 +49,22 @@ fun Uri.writeBytes( fun Uri.writeText(context: Context, text: String): Boolean { return writeBytes(context, text.toByteArray()) } + +fun Uri.writeBytes( + context: Context, + fileName: String, + byteArray: ByteArray +): Boolean { + if (this.isContentScheme()) { + DocumentFile.fromTreeUri(context, this)?.let { pDoc -> + DocumentUtils.createFileIfNotExist(pDoc, fileName)?.let { + return it.uri.writeBytes(context, byteArray) + } + } + } else { + FileUtils.createFileWithReplace(path + File.separatorChar + fileName) + .writeBytes(byteArray) + return true + } + return false +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_chapter_list.xml b/app/src/main/res/layout/activity_chapter_list.xml index c78973583..ff03b765e 100644 --- a/app/src/main/res/layout/activity_chapter_list.xml +++ b/app/src/main/res/layout/activity_chapter_list.xml @@ -12,7 +12,7 @@ app:contentInsetStartWithNavigation="0dp" app:contentLayout="@layout/view_tab_layout" /> - diff --git a/app/src/main/res/layout/activity_rss_artivles.xml b/app/src/main/res/layout/activity_rss_artivles.xml index 50e503a9e..981ce1b6d 100644 --- a/app/src/main/res/layout/activity_rss_artivles.xml +++ b/app/src/main/res/layout/activity_rss_artivles.xml @@ -16,7 +16,7 @@ android:layout_height="wrap_content" app:tabMode="scrollable" /> - diff --git a/app/src/main/res/layout/dialog_toc_regex.xml b/app/src/main/res/layout/dialog_toc_regex.xml index 95317f693..2712cabf4 100644 --- a/app/src/main/res/layout/dialog_toc_regex.xml +++ b/app/src/main/res/layout/dialog_toc_regex.xml @@ -18,7 +18,8 @@ android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_weight="1" /> + android:layout_weight="1" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> diff --git a/app/src/main/res/menu/replace_rule.xml b/app/src/main/res/menu/replace_rule.xml index f650ab792..9b49c60db 100644 --- a/app/src/main/res/menu/replace_rule.xml +++ b/app/src/main/res/menu/replace_rule.xml @@ -31,19 +31,19 @@ app:showAsAction="never" /> diff --git a/app/src/main/res/menu/rss_source.xml b/app/src/main/res/menu/rss_source.xml index 739b68d58..4425cd424 100644 --- a/app/src/main/res/menu/rss_source.xml +++ b/app/src/main/res/menu/rss_source.xml @@ -31,19 +31,19 @@ app:showAsAction="never" /> diff --git a/app/src/main/res/menu/speak_engine.xml b/app/src/main/res/menu/speak_engine.xml index 9ca269d05..6956a1ebc 100644 --- a/app/src/main/res/menu/speak_engine.xml +++ b/app/src/main/res/menu/speak_engine.xml @@ -15,4 +15,19 @@ android:title="@string/import_default_rule" app:showAsAction="never" /> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index c7132dec8..59a6ea723 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -188,8 +188,8 @@ 跟隨系統 添加 導入書源 - 本地導入 - 網絡導入 + 本地導入 + 網絡導入 替換淨化 替換規則編輯 替換規則 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 3c9461915..6ea300b43 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -190,8 +190,8 @@ 跟隨系統 新增 匯入書源 - 本機匯入 - 網路匯入 + 本機匯入 + 網路匯入 取代淨化 取代規則編輯 取代規則 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index a6925aa16..910a08008 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -190,8 +190,8 @@ 跟随系统 添加 导入书源 - 本地导入 - 网络导入 + 本地导入 + 网络导入 替换净化 替换规则编辑 替换规则 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 08b4f8ef6..57077eafb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -191,8 +191,8 @@ Follow system Add Import book sources - Import local sources - Import online sources + Import local + Import online Replacement Edit replacement rule Pattern diff --git a/build.gradle b/build.gradle index 4b2f93727..ed41f24c3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.4.31' + ext.kotlin_version = '1.4.32' repositories { google() maven { url "https://maven.aliyun.com/nexus/content/groups/public/" } diff --git a/epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationEvent.java b/epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationEvent.java index 25d5fd46d..9b52e01cb 100644 --- a/epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationEvent.java +++ b/epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationEvent.java @@ -2,7 +2,7 @@ package me.ag2s.epublib.browsersupport; import java.util.EventObject; -import me.ag2s.epublib.domain.Book; +import me.ag2s.epublib.domain.EpubBook; import me.ag2s.epublib.domain.Resource; import me.ag2s.epublib.util.StringUtil; @@ -20,9 +20,9 @@ public class NavigationEvent extends EventObject { private Resource oldResource; private int oldSpinePos; - private Navigator navigator; - private Book oldBook; - private int oldSectionPos; + private Navigator navigator; + private EpubBook oldBook; + private int oldSectionPos; private String oldFragmentId; public NavigationEvent(Object source) { @@ -61,9 +61,9 @@ public class NavigationEvent extends EventObject { this.oldFragmentId = oldFragmentId; } - public Book getOldBook() { - return oldBook; - } + public EpubBook getOldBook() { + return oldBook; + } // package void setOldPagePos(int oldPagePos) { @@ -124,13 +124,13 @@ public class NavigationEvent extends EventObject { } - public void setOldBook(Book oldBook) { - this.oldBook = oldBook; - } + public void setOldBook(EpubBook oldBook) { + this.oldBook = oldBook; + } - public Book getCurrentBook() { - return getNavigator().getBook(); - } + public EpubBook getCurrentBook() { + return getNavigator().getBook(); + } public boolean isResourceChanged() { return oldResource != getCurrentResource(); diff --git a/epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationHistory.java b/epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationHistory.java index eec56dd6f..710f0240c 100644 --- a/epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationHistory.java +++ b/epublib/src/main/java/me/ag2s/epublib/browsersupport/NavigationHistory.java @@ -1,15 +1,15 @@ package me.ag2s.epublib.browsersupport; -import me.ag2s.epublib.domain.Book; -import me.ag2s.epublib.domain.Resource; import java.util.ArrayList; import java.util.List; +import me.ag2s.epublib.domain.EpubBook; +import me.ag2s.epublib.domain.Resource; + /** * A history of the user's locations with the epub. * * @author paul.siegmann - * */ public class NavigationHistory implements NavigationEventListener { @@ -58,7 +58,7 @@ public class NavigationHistory implements NavigationEventListener { return currentSize; } - public void initBook(Book book) { + public void initBook(EpubBook book) { if (book == null) { return; } diff --git a/epublib/src/main/java/me/ag2s/epublib/browsersupport/Navigator.java b/epublib/src/main/java/me/ag2s/epublib/browsersupport/Navigator.java index e73fbe166..84319e65e 100644 --- a/epublib/src/main/java/me/ag2s/epublib/browsersupport/Navigator.java +++ b/epublib/src/main/java/me/ag2s/epublib/browsersupport/Navigator.java @@ -1,14 +1,15 @@ package me.ag2s.epublib.browsersupport; -import me.ag2s.epublib.domain.Book; -import me.ag2s.epublib.domain.Resource; import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import me.ag2s.epublib.domain.EpubBook; +import me.ag2s.epublib.domain.Resource; + /** * A helper class for epub browser applications. - * + *

* It helps moving from one resource to the other, from one resource * to the other and keeping other elements of the application up-to-date * by calling the NavigationEventListeners. @@ -18,7 +19,7 @@ import java.util.List; public class Navigator implements Serializable { private static final long serialVersionUID = 1076126986424925474L; - private Book book; + private EpubBook book; private int currentSpinePos; private Resource currentResource; private int currentPagePos; @@ -30,7 +31,7 @@ public class Navigator implements Serializable { this(null); } - public Navigator(Book book) { + public Navigator(EpubBook book) { this.book = book; this.currentSpinePos = 0; if (book != null) { @@ -158,7 +159,7 @@ public class Navigator implements Serializable { return gotoSpineSection(book.getSpine().size() - 1, source); } - public void gotoBook(Book book, Object source) { + public void gotoBook(EpubBook book, Object source) { NavigationEvent navigationEvent = new NavigationEvent(source, this); this.book = book; this.currentFragmentId = null; @@ -193,7 +194,7 @@ public class Navigator implements Serializable { this.currentResource = book.getSpine().getResource(currentIndex); } - public Book getBook() { + public EpubBook getBook() { return book; } diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/Book.java b/epublib/src/main/java/me/ag2s/epublib/domain/Book.java deleted file mode 100644 index 24e79be79..000000000 --- a/epublib/src/main/java/me/ag2s/epublib/domain/Book.java +++ /dev/null @@ -1,323 +0,0 @@ -package me.ag2s.epublib.domain; - - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * Representation of a Book. - *

- * All resources of a Book (html, css, xml, fonts, images) are represented - * as Resources. See getResources() for access to these.
- * A Book as 3 indexes into these Resources, as per the epub specification.
- *

- *
Spine
- *
these are the Resources to be shown when a user reads the book from - * start to finish.
- *
Table of Contents
- *
The table of contents. Table of Contents references may be in a - * different order and contain different Resources than the spine, and often do. - *
Guide
- *
The Guide has references to a set of special Resources like the - * cover page, the Glossary, the copyright page, etc. - *
- *

- * The complication is that these 3 indexes may and usually do point to - * different pages. - * A chapter may be split up in 2 pieces to fit it in to memory. Then the - * spine will contain both pieces, but the Table of Contents only the first. - *

- * The Content page may be in the Table of Contents, the Guide, but not - * in the Spine. - * Etc. - *

- *

- * Please see the illustration at: doc/schema.svg - * - * @author paul - * @author jake - */ -public class Book implements Serializable { - - private static final long serialVersionUID = 2068355170895770100L; - - private Resources resources = new Resources(); - private Metadata metadata = new Metadata(); - private Spine spine = new Spine(); - private TableOfContents tableOfContents = new TableOfContents(); - private final Guide guide = new Guide(); - private Resource opfResource; - private Resource ncxResource; - private Resource coverImage; - - - private String version="2.0"; - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - public boolean isEpub3() { - return this.version.startsWith("3."); - } - - @SuppressWarnings("UnusedReturnValue") - public TOCReference addSection( - TOCReference parentSection, String sectionTitle, Resource resource) { - return addSection(parentSection, sectionTitle, resource, null); - } - - /** - * Adds the resource to the table of contents of the book as a child - * section of the given parentSection - * - * @param parentSection parentSection - * @param sectionTitle sectionTitle - * @param resource resource - * @param fragmentId fragmentId - * @return The table of contents - */ - public TOCReference addSection( - TOCReference parentSection, String sectionTitle, Resource resource, - String fragmentId) { - getResources().add(resource); - if (spine.findFirstResourceById(resource.getId()) < 0) { - spine.addSpineReference(new SpineReference(resource)); - } - return parentSection.addChildSection( - new TOCReference(sectionTitle, resource, fragmentId)); - } - - public TOCReference addSection(String title, Resource resource) { - return addSection(title, resource, null); - } - - /** - * Adds a resource to the book's set of resources, table of contents and - * if there is no resource with the id in the spine also adds it to the spine. - * - * @param title title - * @param resource resource - * @param fragmentId fragmentId - * @return The table of contents - */ - public TOCReference addSection( - String title, Resource resource, String fragmentId) { - getResources().add(resource); - TOCReference tocReference = tableOfContents - .addTOCReference(new TOCReference(title, resource, fragmentId)); - if (spine.findFirstResourceById(resource.getId()) < 0) { - spine.addSpineReference(new SpineReference(resource)); - } - return tocReference; - } - - @SuppressWarnings("unused") - public void generateSpineFromTableOfContents() { - Spine spine = new Spine(tableOfContents); - - // in case the tocResource was already found and assigned - spine.setTocResource(this.spine.getTocResource()); - - this.spine = spine; - } - - /** - * The Book's metadata (titles, authors, etc) - * - * @return The Book's metadata (titles, authors, etc) - */ - public Metadata getMetadata() { - return metadata; - } - - public void setMetadata(Metadata metadata) { - this.metadata = metadata; - } - - - public void setResources(Resources resources) { - this.resources = resources; - } - - @SuppressWarnings("unused") - public Resource addResource(Resource resource) { - return resources.add(resource); - } - - /** - * The collection of all images, chapters, sections, xhtml files, - * stylesheets, etc that make up the book. - * - * @return The collection of all images, chapters, sections, xhtml files, - * stylesheets, etc that make up the book. - */ - public Resources getResources() { - return resources; - } - - - /** - * The sections of the book that should be shown if a user reads the book - * from start to finish. - * - * @return The Spine - */ - public Spine getSpine() { - return spine; - } - - - public void setSpine(Spine spine) { - this.spine = spine; - } - - - /** - * The Table of Contents of the book. - * - * @return The Table of Contents of the book. - */ - public TableOfContents getTableOfContents() { - return tableOfContents; - } - - - public void setTableOfContents(TableOfContents tableOfContents) { - this.tableOfContents = tableOfContents; - } - - /** - * The book's cover page as a Resource. - * An XHTML document containing a link to the cover image. - * - * @return The book's cover page as a Resource - */ - public Resource getCoverPage() { - Resource coverPage = guide.getCoverPage(); - if (coverPage == null) { - coverPage = spine.getResource(0); - } - return coverPage; - } - - - public void setCoverPage(Resource coverPage) { - if (coverPage == null) { - return; - } - if (resources.notContainsByHref(coverPage.getHref())) { - resources.add(coverPage); - } - guide.setCoverPage(coverPage); - } - - /** - * Gets the first non-blank title from the book's metadata. - * - * @return the first non-blank title from the book's metadata. - */ - public String getTitle() { - return getMetadata().getFirstTitle(); - } - - - /** - * The book's cover image. - * - * @return The book's cover image. - */ - public Resource getCoverImage() { - return coverImage; - } - - public void setCoverImage(Resource coverImage) { - if (coverImage == null) { - return; - } - if (resources.notContainsByHref(coverImage.getHref())) { - resources.add(coverImage); - } - this.coverImage = coverImage; - } - - /** - * The guide; contains references to special sections of the book like - * colophon, glossary, etc. - * - * @return The guide; contains references to special sections of the book - * like colophon, glossary, etc. - */ - public Guide getGuide() { - return guide; - } - - /** - * All Resources of the Book that can be reached via the Spine, the - * TableOfContents or the Guide. - *

- * Consists of a list of "reachable" resources: - *

    - *
  • The coverpage
  • - *
  • The resources of the Spine that are not already in the result
  • - *
  • The resources of the Table of Contents that are not already in the - * result
  • - *
  • The resources of the Guide that are not already in the result
  • - *
- * To get all html files that make up the epub file use - * {@link #getResources()} - * - * @return All Resources of the Book that can be reached via the Spine, - * the TableOfContents or the Guide. - */ - public List getContents() { - Map result = new LinkedHashMap<>(); - addToContentsResult(getCoverPage(), result); - - for (SpineReference spineReference : getSpine().getSpineReferences()) { - addToContentsResult(spineReference.getResource(), result); - } - - for (Resource resource : getTableOfContents().getAllUniqueResources()) { - addToContentsResult(resource, result); - } - - for (GuideReference guideReference : getGuide().getReferences()) { - addToContentsResult(guideReference.getResource(), result); - } - - return new ArrayList<>(result.values()); - } - - private static void addToContentsResult(Resource resource, - Map allReachableResources) { - if (resource != null && (!allReachableResources - .containsKey(resource.getHref()))) { - allReachableResources.put(resource.getHref(), resource); - } - } - - public Resource getOpfResource() { - return opfResource; - } - - public void setOpfResource(Resource opfResource) { - this.opfResource = opfResource; - } - - public void setNcxResource(Resource ncxResource) { - this.ncxResource = ncxResource; - } - - public Resource getNcxResource() { - return ncxResource; - } -} - diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/EpubBook.java b/epublib/src/main/java/me/ag2s/epublib/domain/EpubBook.java index a0feb3f98..bae1ede47 100644 --- a/epublib/src/main/java/me/ag2s/epublib/domain/EpubBook.java +++ b/epublib/src/main/java/me/ag2s/epublib/domain/EpubBook.java @@ -1,9 +1,323 @@ package me.ag2s.epublib.domain; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + /** - * 这个是用于其它与Book类同名时替换的 + * Representation of a Book. + *

+ * All resources of a Book (html, css, xml, fonts, images) are represented + * as Resources. See getResources() for access to these.
+ * A Book as 3 indexes into these Resources, as per the epub specification.
+ *

+ *
Spine
+ *
these are the Resources to be shown when a user reads the book from + * start to finish.
+ *
Table of Contents
+ *
The table of contents. Table of Contents references may be in a + * different order and contain different Resources than the spine, and often do. + *
Guide
+ *
The Guide has references to a set of special Resources like the + * cover page, the Glossary, the copyright page, etc. + *
+ *

+ * The complication is that these 3 indexes may and usually do point to + * different pages. + * A chapter may be split up in 2 pieces to fit it in to memory. Then the + * spine will contain both pieces, but the Table of Contents only the first. + *

+ * The Content page may be in the Table of Contents, the Guide, but not + * in the Spine. + * Etc. + *

+ *

+ * Please see the illustration at: doc/schema.svg + * + * @author paul + * @author jake */ -@SuppressWarnings("unused declaration") -public class EpubBook extends Book { +public class EpubBook implements Serializable { + + private static final long serialVersionUID = 2068355170895770100L; + + private Resources resources = new Resources(); + private Metadata metadata = new Metadata(); + private Spine spine = new Spine(); + private TableOfContents tableOfContents = new TableOfContents(); + private final Guide guide = new Guide(); + private Resource opfResource; + private Resource ncxResource; + private Resource coverImage; + + + private String version = "2.0"; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public boolean isEpub3() { + return this.version.startsWith("3."); + } + + @SuppressWarnings("UnusedReturnValue") + public TOCReference addSection( + TOCReference parentSection, String sectionTitle, Resource resource) { + return addSection(parentSection, sectionTitle, resource, null); + } + + /** + * Adds the resource to the table of contents of the book as a child + * section of the given parentSection + * + * @param parentSection parentSection + * @param sectionTitle sectionTitle + * @param resource resource + * @param fragmentId fragmentId + * @return The table of contents + */ + public TOCReference addSection( + TOCReference parentSection, String sectionTitle, Resource resource, + String fragmentId) { + getResources().add(resource); + if (spine.findFirstResourceById(resource.getId()) < 0) { + spine.addSpineReference(new SpineReference(resource)); + } + return parentSection.addChildSection( + new TOCReference(sectionTitle, resource, fragmentId)); + } + + public TOCReference addSection(String title, Resource resource) { + return addSection(title, resource, null); + } + + /** + * Adds a resource to the book's set of resources, table of contents and + * if there is no resource with the id in the spine also adds it to the spine. + * + * @param title title + * @param resource resource + * @param fragmentId fragmentId + * @return The table of contents + */ + public TOCReference addSection( + String title, Resource resource, String fragmentId) { + getResources().add(resource); + TOCReference tocReference = tableOfContents + .addTOCReference(new TOCReference(title, resource, fragmentId)); + if (spine.findFirstResourceById(resource.getId()) < 0) { + spine.addSpineReference(new SpineReference(resource)); + } + return tocReference; + } + + @SuppressWarnings("unused") + public void generateSpineFromTableOfContents() { + Spine spine = new Spine(tableOfContents); + + // in case the tocResource was already found and assigned + spine.setTocResource(this.spine.getTocResource()); + + this.spine = spine; + } + + /** + * The Book's metadata (titles, authors, etc) + * + * @return The Book's metadata (titles, authors, etc) + */ + public Metadata getMetadata() { + return metadata; + } + + public void setMetadata(Metadata metadata) { + this.metadata = metadata; + } + + + public void setResources(Resources resources) { + this.resources = resources; + } + + @SuppressWarnings("unused") + public Resource addResource(Resource resource) { + return resources.add(resource); + } + + /** + * The collection of all images, chapters, sections, xhtml files, + * stylesheets, etc that make up the book. + * + * @return The collection of all images, chapters, sections, xhtml files, + * stylesheets, etc that make up the book. + */ + public Resources getResources() { + return resources; + } + + + /** + * The sections of the book that should be shown if a user reads the book + * from start to finish. + * + * @return The Spine + */ + public Spine getSpine() { + return spine; + } + + public void setSpine(Spine spine) { + this.spine = spine; + } + + + /** + * The Table of Contents of the book. + * + * @return The Table of Contents of the book. + */ + public TableOfContents getTableOfContents() { + return tableOfContents; + } + + + public void setTableOfContents(TableOfContents tableOfContents) { + this.tableOfContents = tableOfContents; + } + + /** + * The book's cover page as a Resource. + * An XHTML document containing a link to the cover image. + * + * @return The book's cover page as a Resource + */ + public Resource getCoverPage() { + Resource coverPage = guide.getCoverPage(); + if (coverPage == null) { + coverPage = spine.getResource(0); + } + return coverPage; + } + + + public void setCoverPage(Resource coverPage) { + if (coverPage == null) { + return; + } + if (resources.notContainsByHref(coverPage.getHref())) { + resources.add(coverPage); + } + guide.setCoverPage(coverPage); + } + + /** + * Gets the first non-blank title from the book's metadata. + * + * @return the first non-blank title from the book's metadata. + */ + public String getTitle() { + return getMetadata().getFirstTitle(); + } + + + /** + * The book's cover image. + * + * @return The book's cover image. + */ + public Resource getCoverImage() { + return coverImage; + } + + public void setCoverImage(Resource coverImage) { + if (coverImage == null) { + return; + } + if (resources.notContainsByHref(coverImage.getHref())) { + resources.add(coverImage); + } + this.coverImage = coverImage; + } + + /** + * The guide; contains references to special sections of the book like + * colophon, glossary, etc. + * + * @return The guide; contains references to special sections of the book + * like colophon, glossary, etc. + */ + public Guide getGuide() { + return guide; + } + + /** + * All Resources of the Book that can be reached via the Spine, the + * TableOfContents or the Guide. + *

+ * Consists of a list of "reachable" resources: + *

    + *
  • The coverpage
  • + *
  • The resources of the Spine that are not already in the result
  • + *
  • The resources of the Table of Contents that are not already in the + * result
  • + *
  • The resources of the Guide that are not already in the result
  • + *
+ * To get all html files that make up the epub file use + * {@link #getResources()} + * + * @return All Resources of the Book that can be reached via the Spine, + * the TableOfContents or the Guide. + */ + public List getContents() { + Map result = new LinkedHashMap<>(); + addToContentsResult(getCoverPage(), result); + + for (SpineReference spineReference : getSpine().getSpineReferences()) { + addToContentsResult(spineReference.getResource(), result); + } + + for (Resource resource : getTableOfContents().getAllUniqueResources()) { + addToContentsResult(resource, result); + } + + for (GuideReference guideReference : getGuide().getReferences()) { + addToContentsResult(guideReference.getResource(), result); + } + + return new ArrayList<>(result.values()); + } + + private static void addToContentsResult(Resource resource, + Map allReachableResources) { + if (resource != null && (!allReachableResources + .containsKey(resource.getHref()))) { + allReachableResources.put(resource.getHref(), resource); + } + } + + public Resource getOpfResource() { + return opfResource; + } + + public void setOpfResource(Resource opfResource) { + this.opfResource = opfResource; + } + + public void setNcxResource(Resource ncxResource) { + this.ncxResource = ncxResource; + } + + public Resource getNcxResource() { + return ncxResource; + } } + diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/BookProcessor.java b/epublib/src/main/java/me/ag2s/epublib/epub/BookProcessor.java index 5a44dfdcf..d45b9d891 100644 --- a/epublib/src/main/java/me/ag2s/epublib/epub/BookProcessor.java +++ b/epublib/src/main/java/me/ag2s/epublib/epub/BookProcessor.java @@ -1,6 +1,6 @@ package me.ag2s.epublib.epub; -import me.ag2s.epublib.domain.Book; +import me.ag2s.epublib.domain.EpubBook; /** * Post-processes a book. @@ -16,5 +16,5 @@ public interface BookProcessor { */ BookProcessor IDENTITY_BOOKPROCESSOR = book -> book; - Book processBook(Book book); + EpubBook processBook(EpubBook book); } diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/BookProcessorPipeline.java b/epublib/src/main/java/me/ag2s/epublib/epub/BookProcessorPipeline.java index ee9ff3ba5..35256370c 100644 --- a/epublib/src/main/java/me/ag2s/epublib/epub/BookProcessorPipeline.java +++ b/epublib/src/main/java/me/ag2s/epublib/epub/BookProcessorPipeline.java @@ -2,14 +2,15 @@ package me.ag2s.epublib.epub; import android.util.Log; -import me.ag2s.epublib.domain.Book; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import me.ag2s.epublib.domain.EpubBook; + /** * A book processor that combines several other bookprocessors - * + *

* Fixes coverpage/coverimage. * Cleans up the XHTML. * @@ -30,7 +31,7 @@ public class BookProcessorPipeline implements BookProcessor { } @Override - public Book processBook(Book book) { + public EpubBook processBook(EpubBook book) { if (bookProcessors == null) { return book; } @@ -38,7 +39,7 @@ public class BookProcessorPipeline implements BookProcessor { try { book = bookProcessor.processBook(book); } catch (Exception e) { - Log.e(TAG,e.getMessage(), e); + Log.e(TAG, e.getMessage(), e); } } return book; diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/EpubReader.java b/epublib/src/main/java/me/ag2s/epublib/epub/EpubReader.java index 361fe8527..78fd6ab94 100644 --- a/epublib/src/main/java/me/ag2s/epublib/epub/EpubReader.java +++ b/epublib/src/main/java/me/ag2s/epublib/epub/EpubReader.java @@ -13,7 +13,6 @@ import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; import me.ag2s.epublib.Constants; -import me.ag2s.epublib.domain.Book; import me.ag2s.epublib.domain.EpubBook; import me.ag2s.epublib.domain.MediaType; import me.ag2s.epublib.domain.MediaTypes; @@ -27,22 +26,21 @@ import me.ag2s.epublib.util.StringUtil; * * @author paul */ +@SuppressWarnings("ALL") public class EpubReader { private static final String TAG = EpubReader.class.getName(); private final BookProcessor bookProcessor = BookProcessor.IDENTITY_BOOKPROCESSOR; - @SuppressWarnings("unused") - public Book readEpub(InputStream in) throws IOException { + + public EpubBook readEpub(InputStream in) throws IOException { return readEpub(in, Constants.CHARACTER_ENCODING); } - @SuppressWarnings("unused") - public Book readEpub(ZipInputStream in) throws IOException { + public EpubBook readEpub(ZipInputStream in) throws IOException { return readEpub(in, Constants.CHARACTER_ENCODING); } - @SuppressWarnings("unused") - public Book readEpub(ZipFile zipfile) throws IOException { + public EpubBook readEpub(ZipFile zipfile) throws IOException { return readEpub(zipfile, Constants.CHARACTER_ENCODING); } @@ -54,7 +52,7 @@ public class EpubReader { * @return the Book as read from the inputstream * @throws IOException IOException */ - public Book readEpub(InputStream in, String encoding) throws IOException { + public EpubBook readEpub(InputStream in, String encoding) throws IOException { return readEpub(new ZipInputStream(in), encoding); } @@ -67,18 +65,17 @@ public class EpubReader { * @return this Book without loading all resources into memory. * @throws IOException IOException */ - @SuppressWarnings("unused") - public Book readEpubLazy(ZipFile zipFile, String encoding) + public EpubBook readEpubLazy(ZipFile zipFile, String encoding) throws IOException { return readEpubLazy(zipFile, encoding, Arrays.asList(MediaTypes.mediaTypes)); } - public Book readEpub(ZipInputStream in, String encoding) throws IOException { + public EpubBook readEpub(ZipInputStream in, String encoding) throws IOException { return readEpub(ResourcesLoader.loadResources(in, encoding)); } - public Book readEpub(ZipFile in, String encoding) throws IOException { + public EpubBook readEpub(ZipFile in, String encoding) throws IOException { return readEpub(ResourcesLoader.loadResources(in, encoding)); } @@ -91,24 +88,20 @@ public class EpubReader { * @return this Book without loading all resources into memory. * @throws IOException IOException */ - public Book readEpubLazy(ZipFile zipFile, String encoding, - List lazyLoadedTypes) throws IOException { + public EpubBook readEpubLazy(ZipFile zipFile, String encoding, + List lazyLoadedTypes) throws IOException { Resources resources = ResourcesLoader .loadResources(zipFile, encoding, lazyLoadedTypes); return readEpub(resources); } - @SuppressWarnings("unused") - public Book readEpub(Resources resources) { - return readEpub(resources, new Book()); - } - public EpubBook readEpubBook(Resources resources) { - return (EpubBook) readEpub(resources, new Book()); + public EpubBook readEpub(Resources resources) { + return readEpub(resources, new EpubBook()); } - public Book readEpub(Resources resources, Book result) { + public EpubBook readEpub(Resources resources, EpubBook result) { if (result == null) { - result = new Book(); + result = new EpubBook(); } handleMimeType(result, resources); String packageResourceHref = getPackageResourceHref(resources); @@ -122,14 +115,14 @@ public class EpubReader { } - private Book postProcessBook(Book book) { + private EpubBook postProcessBook(EpubBook book) { if (bookProcessor != null) { book = bookProcessor.processBook(book); } return book; } - private Resource processNcxResource(Resource packageResource, Book book) { + private Resource processNcxResource(Resource packageResource, EpubBook book) { Log.d(TAG, "OPF:getHref()" + packageResource.getHref()); if (book.isEpub3()) { return NCXDocumentV3.read(book, this); @@ -139,7 +132,7 @@ public class EpubReader { } - private Resource processPackageResource(String packageResourceHref, Book book, + private Resource processPackageResource(String packageResourceHref, EpubBook book, Resources resources) { Resource packageResource = resources.remove(packageResourceHref); try { @@ -173,8 +166,7 @@ public class EpubReader { return result; } - @SuppressWarnings("unused") - private void handleMimeType(Book result, Resources resources) { + private void handleMimeType(EpubBook result, Resources resources) { resources.remove("mimetype"); //result.setResources(resources); } diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/EpubWriter.java b/epublib/src/main/java/me/ag2s/epublib/epub/EpubWriter.java index 76c84c94c..11d716b68 100644 --- a/epublib/src/main/java/me/ag2s/epublib/epub/EpubWriter.java +++ b/epublib/src/main/java/me/ag2s/epublib/epub/EpubWriter.java @@ -2,10 +2,8 @@ package me.ag2s.epublib.epub; import android.util.Log; -import me.ag2s.epublib.domain.Book; -import me.ag2s.epublib.domain.MediaTypes; -import me.ag2s.epublib.domain.Resource; -import me.ag2s.epublib.util.IOUtil; +import org.xmlpull.v1.XmlSerializer; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -14,7 +12,11 @@ import java.io.Writer; import java.util.zip.CRC32; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import org.xmlpull.v1.XmlSerializer; + +import me.ag2s.epublib.domain.EpubBook; +import me.ag2s.epublib.domain.MediaTypes; +import me.ag2s.epublib.domain.Resource; +import me.ag2s.epublib.util.IOUtil; /** * Generates an epub file. Not thread-safe, single use object. @@ -40,7 +42,7 @@ public class EpubWriter { } - public void write(Book book, OutputStream out) throws IOException { + public void write(EpubBook book, OutputStream out) throws IOException { book = processBook(book); ZipOutputStream resultStream = new ZipOutputStream(out); writeMimeType(resultStream); @@ -51,19 +53,19 @@ public class EpubWriter { resultStream.close(); } - private Book processBook(Book book) { + private EpubBook processBook(EpubBook book) { if (bookProcessor != null) { book = bookProcessor.processBook(book); } return book; } - private void initTOCResource(Book book) { + private void initTOCResource(EpubBook book) { Resource tocResource; try { - if(book.isEpub3()){ + if (book.isEpub3()) { tocResource = NCXDocumentV3.createNCXResource(book); - }else { + } else { tocResource = NCXDocumentV2.createNCXResource(book); } @@ -81,7 +83,7 @@ public class EpubWriter { } - private void writeResources(Book book, ZipOutputStream resultStream) { + private void writeResources(EpubBook book, ZipOutputStream resultStream) { for (Resource resource : book.getResources().getAll()) { writeResource(resource, resultStream); } @@ -108,11 +110,11 @@ public class EpubWriter { } - private void writePackageDocument(Book book, ZipOutputStream resultStream) - throws IOException { + private void writePackageDocument(EpubBook book, ZipOutputStream resultStream) + throws IOException { resultStream.putNextEntry(new ZipEntry("OEBPS/content.opf")); XmlSerializer xmlSerializer = EpubProcessorSupport - .createXmlSerializer(resultStream); + .createXmlSerializer(resultStream); PackageDocumentWriter.write(this, xmlSerializer, book); xmlSerializer.flush(); // String resultAsString = result.toString(); diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV2.java b/epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV2.java index 6147e0004..572f7f97b 100644 --- a/epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV2.java +++ b/epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV2.java @@ -19,7 +19,7 @@ import java.util.zip.ZipOutputStream; import me.ag2s.epublib.Constants; import me.ag2s.epublib.domain.Author; -import me.ag2s.epublib.domain.Book; +import me.ag2s.epublib.domain.EpubBook; import me.ag2s.epublib.domain.Identifier; import me.ag2s.epublib.domain.MediaTypes; import me.ag2s.epublib.domain.Resource; @@ -77,7 +77,7 @@ public class NCXDocumentV2 { } @SuppressWarnings("unused") - public static Resource read(Book book, EpubReader epubReader) { + public static Resource read(EpubBook book, EpubReader epubReader) { Resource ncxResource = null; if (book.getSpine().getTocResource() == null) { Log.e(TAG, "Book does not contain a table of contents file"); @@ -107,7 +107,7 @@ public class NCXDocumentV2 { } static List readTOCReferences(NodeList navpoints, - Book book) { + EpubBook book) { if (navpoints == null) { return new ArrayList<>(); } @@ -127,7 +127,7 @@ public class NCXDocumentV2 { return result; } - static TOCReference readTOCReference(Element navpointElement, Book book) { + static TOCReference readTOCReference(Element navpointElement, EpubBook book) { String label = readNavLabel(navpointElement); //Log.d(TAG,"label:"+label); String tocResourceRoot = StringUtil @@ -184,8 +184,9 @@ public class NCXDocumentV2 { return DOMUtil.getTextChildrenContent(DOMUtil .getFirstElementByTagNameNS(navLabel, NAMESPACE_NCX, NCXTags.text)); } + @SuppressWarnings("unused") - public static void write(EpubWriter epubWriter, Book book, + public static void write(EpubWriter epubWriter, EpubBook book, ZipOutputStream resultStream) throws IOException { resultStream .putNextEntry(new ZipEntry(book.getSpine().getTocResource().getHref())); @@ -200,17 +201,17 @@ public class NCXDocumentV2 { * * @param xmlSerializer the serializer used * @param book the book to serialize - * @throws IOException IOException - * @throws IllegalStateException IllegalStateException + * @throws IOException IOException + * @throws IllegalStateException IllegalStateException * @throws IllegalArgumentException IllegalArgumentException */ - public static void write(XmlSerializer xmlSerializer, Book book) + public static void write(XmlSerializer xmlSerializer, EpubBook book) throws IllegalArgumentException, IllegalStateException, IOException { write(xmlSerializer, book.getMetadata().getIdentifiers(), book.getTitle(), book.getMetadata().getAuthors(), book.getTableOfContents()); } - public static Resource createNCXResource(Book book) + public static Resource createNCXResource(EpubBook book) throws IllegalArgumentException, IllegalStateException, IOException { return createNCXResource(book.getMetadata().getIdentifiers(), book.getTitle(), book.getMetadata().getAuthors(), diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV3.java b/epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV3.java index 2de8185d4..a538a43f2 100644 --- a/epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV3.java +++ b/epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV3.java @@ -17,7 +17,7 @@ import java.util.List; import me.ag2s.epublib.Constants; import me.ag2s.epublib.domain.Author; -import me.ag2s.epublib.domain.Book; +import me.ag2s.epublib.domain.EpubBook; import me.ag2s.epublib.domain.Identifier; import me.ag2s.epublib.domain.MediaType; import me.ag2s.epublib.domain.MediaTypes; @@ -96,7 +96,7 @@ public class NCXDocumentV3 { * @return Resource */ @SuppressWarnings("unused") - public static Resource read(Book book, EpubReader epubReader) { + public static Resource read(EpubBook book, EpubReader epubReader) { Resource ncxResource = null; if (book.getSpine().getTocResource() == null) { Log.e(TAG, "Book does not contain a table of contents file"); @@ -126,7 +126,7 @@ public class NCXDocumentV3 { return ncxResource; } - private static List doToc(Node n, Book book) { + private static List doToc(Node n, EpubBook book) { List result = new ArrayList<>(); if (n == null || n.getNodeType() != Document.ELEMENT_NODE) { @@ -143,7 +143,7 @@ public class NCXDocumentV3 { static List readTOCReferences(NodeList navpoints, - Book book) { + EpubBook book) { if (navpoints == null) { return new ArrayList<>(); } @@ -169,7 +169,7 @@ public class NCXDocumentV3 { } - static TOCReference readTOCReference(Element navpointElement, Book book) { + static TOCReference readTOCReference(Element navpointElement, EpubBook book) { //章节的名称 String label = readNavLabel(navpointElement); //Log.d(TAG, "label:" + label); @@ -260,7 +260,7 @@ public class NCXDocumentV3 { } - public static Resource createNCXResource(Book book) + public static Resource createNCXResource(EpubBook book) throws IllegalArgumentException, IllegalStateException, IOException { return createNCXResource(book.getMetadata().getIdentifiers(), book.getTitle(), book.getMetadata().getAuthors(), @@ -289,7 +289,7 @@ public class NCXDocumentV3 { * @throws IllegalStateException IllegalStateException * @throws IllegalArgumentException IllegalArgumentException */ - public static void write(XmlSerializer xmlSerializer, Book book) + public static void write(XmlSerializer xmlSerializer, EpubBook book) throws IllegalArgumentException, IllegalStateException, IOException { write(xmlSerializer, book.getMetadata().getIdentifiers(), book.getTitle(), book.getMetadata().getAuthors(), book.getTableOfContents()); diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentMetadataWriter.java b/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentMetadataWriter.java index 80caed08c..f4811a6c7 100644 --- a/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentMetadataWriter.java +++ b/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentMetadataWriter.java @@ -1,39 +1,42 @@ package me.ag2s.epublib.epub; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import javax.xml.namespace.QName; + import me.ag2s.epublib.Constants; import me.ag2s.epublib.domain.Author; -import me.ag2s.epublib.domain.Book; import me.ag2s.epublib.domain.Date; +import me.ag2s.epublib.domain.EpubBook; import me.ag2s.epublib.domain.Identifier; import me.ag2s.epublib.util.StringUtil; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import javax.xml.namespace.QName; -import org.xmlpull.v1.XmlSerializer; public class PackageDocumentMetadataWriter extends PackageDocumentBase { /** * Writes the book's metadata. * - * @param book book + * @param book book * @param serializer serializer - * @throws IOException IOException - * @throws IllegalStateException IllegalStateException + * @throws IOException IOException + * @throws IllegalStateException IllegalStateException * @throws IllegalArgumentException IllegalArgumentException */ - public static void writeMetaData(Book book, XmlSerializer serializer) - throws IllegalArgumentException, IllegalStateException, IOException { + public static void writeMetaData(EpubBook book, XmlSerializer serializer) + throws IllegalArgumentException, IllegalStateException, IOException { serializer.startTag(NAMESPACE_OPF, OPFTags.metadata); serializer.setPrefix(PREFIX_DUBLIN_CORE, NAMESPACE_DUBLIN_CORE); serializer.setPrefix(PREFIX_OPF, NAMESPACE_OPF); writeIdentifiers(book.getMetadata().getIdentifiers(), serializer); writeSimpleMetdataElements(DCTags.title, book.getMetadata().getTitles(), - serializer); + serializer); writeSimpleMetdataElements(DCTags.subject, book.getMetadata().getSubjects(), - serializer); + serializer); writeSimpleMetdataElements(DCTags.description, book.getMetadata().getDescriptions(), serializer); writeSimpleMetdataElements(DCTags.publisher, diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentReader.java b/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentReader.java index 145febb02..0b0c83848 100644 --- a/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentReader.java +++ b/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentReader.java @@ -20,7 +20,7 @@ import java.util.Map; import java.util.Set; import me.ag2s.epublib.Constants; -import me.ag2s.epublib.domain.Book; +import me.ag2s.epublib.domain.EpubBook; import me.ag2s.epublib.domain.Guide; import me.ag2s.epublib.domain.GuideReference; import me.ag2s.epublib.domain.MediaType; @@ -46,8 +46,8 @@ public class PackageDocumentReader extends PackageDocumentBase { public static void read( - Resource packageResource, EpubReader epubReader, Book book, - Resources resources) + Resource packageResource, EpubReader epubReader, EpubBook book, + Resources resources) throws SAXException, IOException { Document packageDocument = ResourceUtil.getAsDocument(packageResource); String packageHref = packageResource.getHref(); @@ -155,17 +155,17 @@ public class PackageDocumentReader extends PackageDocumentBase { */ @SuppressWarnings("unused") private static void readGuide(Document packageDocument, - EpubReader epubReader, Book book, Resources resources) { - Element guideElement = DOMUtil - .getFirstElementByTagNameNS(packageDocument.getDocumentElement(), - NAMESPACE_OPF, OPFTags.guide); - if (guideElement == null) { - return; - } - Guide guide = book.getGuide(); - NodeList guideReferences = guideElement - .getElementsByTagNameNS(NAMESPACE_OPF, OPFTags.reference); - for (int i = 0; i < guideReferences.getLength(); i++) { + EpubReader epubReader, EpubBook book, Resources resources) { + Element guideElement = DOMUtil + .getFirstElementByTagNameNS(packageDocument.getDocumentElement(), + NAMESPACE_OPF, OPFTags.guide); + if (guideElement == null) { + return; + } + Guide guide = book.getGuide(); + NodeList guideReferences = guideElement + .getElementsByTagNameNS(NAMESPACE_OPF, OPFTags.reference); + for (int i = 0; i < guideReferences.getLength(); i++) { Element referenceElement = (Element) guideReferences.item(i); String resourceHref = DOMUtil .getAttribute(referenceElement, NAMESPACE_OPF, OPFAttributes.href); @@ -400,23 +400,24 @@ public class PackageDocumentReader extends PackageDocumentBase { return result; } - /** - * Finds the cover resource in the packageDocument and adds it to the book if found. - * Keeps the cover resource in the resources map - * @param packageDocument s - * @param book x - */ - private static void readCover(Document packageDocument, Book book) { - - Collection coverHrefs = findCoverHrefs(packageDocument); - for (String coverHref : coverHrefs) { - Resource resource = book.getResources().getByHref(coverHref); - if (resource == null) { - Log.e(TAG,"Cover resource " + coverHref + " not found"); - continue; - } - if (resource.getMediaType() == MediaTypes.XHTML) { - book.setCoverPage(resource); + /** + * Finds the cover resource in the packageDocument and adds it to the book if found. + * Keeps the cover resource in the resources map + * + * @param packageDocument s + * @param book x + */ + private static void readCover(Document packageDocument, EpubBook book) { + + Collection coverHrefs = findCoverHrefs(packageDocument); + for (String coverHref : coverHrefs) { + Resource resource = book.getResources().getByHref(coverHref); + if (resource == null) { + Log.e(TAG, "Cover resource " + coverHref + " not found"); + continue; + } + if (resource.getMediaType() == MediaTypes.XHTML) { + book.setCoverPage(resource); } else if (MediaTypes.isBitmapImage(resource.getMediaType())) { book.setCoverImage(resource); } diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentWriter.java b/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentWriter.java index 28bc676b9..49db82ae6 100644 --- a/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentWriter.java +++ b/epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentWriter.java @@ -2,8 +2,15 @@ package me.ag2s.epublib.epub; import android.util.Log; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import me.ag2s.epublib.Constants; -import me.ag2s.epublib.domain.Book; +import me.ag2s.epublib.domain.EpubBook; import me.ag2s.epublib.domain.Guide; import me.ag2s.epublib.domain.GuideReference; import me.ag2s.epublib.domain.MediaTypes; @@ -12,13 +19,6 @@ import me.ag2s.epublib.domain.Spine; import me.ag2s.epublib.domain.SpineReference; import me.ag2s.epublib.util.StringUtil; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.xmlpull.v1.XmlSerializer; - /** * Writes the opf package document as defined by namespace http://www.idpf.org/2007/opf * @@ -29,7 +29,7 @@ public class PackageDocumentWriter extends PackageDocumentBase { private static final String TAG = PackageDocumentWriter.class.getName(); public static void write(EpubWriter epubWriter, XmlSerializer serializer, - Book book) { + EpubBook book) { try { serializer.startDocument(Constants.CHARACTER_ENCODING, false); serializer.setPrefix(PREFIX_OPF, NAMESPACE_OPF); @@ -66,7 +66,7 @@ public class PackageDocumentWriter extends PackageDocumentBase { * @throws IllegalArgumentException 1@throws XMLStreamException */ @SuppressWarnings("unused") - private static void writeSpine(Book book, EpubWriter epubWriter, + private static void writeSpine(EpubBook book, EpubWriter epubWriter, XmlSerializer serializer) throws IllegalArgumentException, IllegalStateException, IOException { serializer.startTag(NAMESPACE_OPF, OPFTags.spine); @@ -93,7 +93,7 @@ public class PackageDocumentWriter extends PackageDocumentBase { } - private static void writeManifest(Book book, EpubWriter epubWriter, + private static void writeManifest(EpubBook book, EpubWriter epubWriter, XmlSerializer serializer) throws IllegalArgumentException, IllegalStateException, IOException { serializer.startTag(NAMESPACE_OPF, OPFTags.manifest); @@ -102,7 +102,7 @@ public class PackageDocumentWriter extends PackageDocumentBase { //For EPUB3 if (book.isEpub3()) { - serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.properties,NCXDocumentV3.V3_NCX_PROPERTIES); + serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.properties, NCXDocumentV3.V3_NCX_PROPERTIES); serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.id, NCXDocumentV3.NCX_ITEM_ID); serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.href, NCXDocumentV3.DEFAULT_NCX_HREF); serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.media_type, NCXDocumentV3.V3_NCX_MEDIATYPE.getName()); @@ -124,7 +124,7 @@ public class PackageDocumentWriter extends PackageDocumentBase { serializer.endTag(NAMESPACE_OPF, OPFTags.manifest); } - private static List getAllResourcesSortById(Book book) { + private static List getAllResourcesSortById(EpubBook book) { List allResources = new ArrayList<>( book.getResources().getAll()); Collections.sort(allResources, (resource1, resource2) -> resource1.getId().compareToIgnoreCase(resource2.getId())); @@ -134,13 +134,13 @@ public class PackageDocumentWriter extends PackageDocumentBase { /** * Writes a resources as an item element * - * @param resource g + * @param resource g * @param serializer g - * @throws IOException g - * @throws IllegalStateException g + * @throws IOException g + * @throws IllegalStateException g * @throws IllegalArgumentException 1@throws XMLStreamException */ - private static void writeItem(Book book, Resource resource, + private static void writeItem(EpubBook book, Resource resource, XmlSerializer serializer) throws IllegalArgumentException, IllegalStateException, IOException { if (resource == null || @@ -204,7 +204,7 @@ public class PackageDocumentWriter extends PackageDocumentBase { } } - private static void writeGuide(Book book, EpubWriter epubWriter, + private static void writeGuide(EpubBook book, EpubWriter epubWriter, XmlSerializer serializer) throws IllegalArgumentException, IllegalStateException, IOException { serializer.startTag(NAMESPACE_OPF, OPFTags.guide);