小说阅读接口 优化

添加 epub 未完成
pull/274/head
gedoor 4 years ago
parent 62a649052c
commit 6ab3346d97
  1. 2
      app/src/main/java/io/legado/app/base/BaseDialogFragment.kt
  2. 4
      app/src/main/java/io/legado/app/data/entities/Book.kt
  3. 4
      app/src/main/java/io/legado/app/help/BookHelp.kt
  4. 11
      app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt
  5. 162
      app/src/main/java/io/legado/app/model/localBook/EPUBFile.kt
  6. 17
      app/src/main/java/io/legado/app/model/localBook/LocalBook.kt
  7. 7
      app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt
  8. 7
      app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt
  9. 4
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  10. 7
      app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditDialog.kt
  11. 11
      app/src/main/java/io/legado/app/ui/rss/source/manage/GroupManageDialog.kt
  12. 7
      app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt
  13. 3
      app/src/main/res/layout/dialog_file_chooser.xml
  14. 7
      app/src/main/res/layout/dialog_text_view.xml

@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.View
import androidx.fragment.app.DialogFragment
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.theme.ThemeStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -21,6 +22,7 @@ abstract class BaseDialogFragment : DialogFragment(), CoroutineScope {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.setBackgroundColor(ThemeStore.backgroundColor())
onFragmentCreated(view, savedInstanceState)
observeLiveBus()
}

@ -60,6 +60,10 @@ data class Book(
return isLocalBook() && originName.endsWith(".txt", true)
}
fun isEpub(): Boolean {
return originName.endsWith(".epub", true)
}
fun isOnLineTxt(): Boolean {
return !isLocalBook() && type == 0
}

@ -6,7 +6,7 @@ import io.legado.app.constant.EventBus
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.model.localBook.AnalyzeTxtFile
import io.legado.app.model.localBook.LocalBook
import io.legado.app.utils.FileUtils
import io.legado.app.utils.MD5Utils
import io.legado.app.utils.postEvent
@ -81,7 +81,7 @@ object BookHelp {
fun getContent(book: Book, bookChapter: BookChapter): String? {
if (book.isLocalBook()) {
return AnalyzeTxtFile.getContent(book, bookChapter)
return LocalBook.getContext(book, bookChapter)
} else {
val file = FileUtils.getFile(
downloadDir,

@ -1,6 +1,5 @@
package io.legado.app.model.localBook
import android.content.Context
import android.net.Uri
import io.legado.app.App
import io.legado.app.data.entities.Book
@ -19,8 +18,8 @@ class AnalyzeTxtFile {
private lateinit var charset: Charset
@Throws(Exception::class)
fun analyze(context: Context, book: Book): ArrayList<BookChapter> {
val bookFile = getBookFile(context, book)
fun analyze(book: Book): ArrayList<BookChapter> {
val bookFile = getBookFile(book)
book.charset = EncodingDetect.getEncode(bookFile)
charset = book.fileCharset()
val rulePattern = if (book.tocUrl.isNotEmpty()) {
@ -253,7 +252,7 @@ class AnalyzeTxtFile {
}
fun getContent(book: Book, bookChapter: BookChapter): String {
val bookFile = getBookFile(App.INSTANCE, book)
val bookFile = getBookFile(book)
//获取文件流
val bookStream = RandomAccessFile(bookFile, "r")
val content = ByteArray((bookChapter.end!! - bookChapter.start!!).toInt())
@ -262,13 +261,13 @@ class AnalyzeTxtFile {
return String(content, book.fileCharset())
}
private fun getBookFile(context: Context, book: Book): File {
private fun getBookFile(book: Book): File {
if (book.bookUrl.isContentPath()) {
val uri = Uri.parse(book.bookUrl)
val bookFile = FileUtils.getFile(cacheFolder, book.originName, subDirs = *arrayOf())
if (!bookFile.exists()) {
bookFile.createNewFile()
DocumentUtils.readBytes(context, uri)?.let {
DocumentUtils.readBytes(App.INSTANCE, uri)?.let {
bookFile.writeBytes(it)
}
}

@ -1,8 +1,8 @@
package io.legado.app.model.localBook
import android.content.Context
import android.net.Uri
import android.text.TextUtils
import io.legado.app.App
import io.legado.app.data.entities.BookChapter
import io.legado.app.utils.htmlFormat
import io.legado.app.utils.isContentPath
@ -15,8 +15,30 @@ import java.io.IOException
import java.nio.charset.Charset
import java.util.*
class EPUBFile(context: Context, val book: io.legado.app.data.entities.Book) {
var epubBook: Book? = null
class EPUBFile(val book: io.legado.app.data.entities.Book) {
companion object {
private var eFile: EPUBFile? = null
@Synchronized
fun getEFile(book: io.legado.app.data.entities.Book): EPUBFile {
if (eFile == null || eFile?.book?.bookUrl == book.bookUrl) {
eFile = EPUBFile(book)
return eFile!!
}
return eFile!!
}
fun getChapterList(book: io.legado.app.data.entities.Book): ArrayList<BookChapter> {
return getEFile(book).getChapterList()
}
fun getContent(book: io.legado.app.data.entities.Book, chapter: BookChapter): String? {
return getEFile(book).getContent(chapter)
}
}
private var epubBook: Book? = null
private lateinit var mCharset: Charset
init {
@ -24,7 +46,7 @@ class EPUBFile(context: Context, val book: io.legado.app.data.entities.Book) {
val epubReader = EpubReader()
val inputStream = if (book.bookUrl.isContentPath()) {
val uri = Uri.parse(book.bookUrl)
context.contentResolver.openInputStream(uri)
App.INSTANCE.contentResolver.openInputStream(uri)
} else {
File(book.bookUrl).inputStream()
}
@ -33,88 +55,90 @@ class EPUBFile(context: Context, val book: io.legado.app.data.entities.Book) {
}
}
fun getContent(chapter: BookChapter): String {
val resource = epubBook!!.resources.getByHref(chapter.url)
val content = StringBuilder()
val doc = Jsoup.parse(String(resource.data, mCharset))
val elements = doc.allElements
for (element in elements) {
val contentEs = element.textNodes()
for (i in contentEs.indices) {
val text = contentEs[i].text().trim { it <= ' ' }.htmlFormat()
if (elements.size > 1) {
if (text.isNotEmpty()) {
if (content.isNotEmpty()) {
content.append("\r\n")
fun getContent(chapter: BookChapter): String? {
epubBook?.let { eBook ->
val resource = eBook.resources.getByHref(chapter.url)
val content = StringBuilder()
val doc = Jsoup.parse(String(resource.data, mCharset))
val elements = doc.allElements
for (element in elements) {
val contentEs = element.textNodes()
for (i in contentEs.indices) {
val text = contentEs[i].text().trim { it <= ' ' }.htmlFormat()
if (elements.size > 1) {
if (text.isNotEmpty()) {
if (content.isNotEmpty()) {
content.append("\r\n")
}
content.append("\u3000\u3000").append(text)
}
content.append("\u3000\u3000").append(text)
} else {
content.append(text)
}
} else {
content.append(text)
}
}
return content.toString()
}
return content.toString()
return null
}
fun getChapterList(epubBook: Book): ArrayList<BookChapter> {
val metadata = epubBook.metadata
book.name = metadata.firstTitle
if (metadata.authors.size > 0) {
val author =
metadata.authors[0].toString().replace("^, |, $".toRegex(), "")
book.author = author
}
if (metadata.descriptions.size > 0) {
book.intro = Jsoup.parse(metadata.descriptions[0]).text()
}
fun getChapterList(): ArrayList<BookChapter> {
val chapterList = ArrayList<BookChapter>()
val refs =
epubBook.tableOfContents.tocReferences
if (refs == null || refs.isEmpty()) {
val spineReferences =
epubBook.spine.spineReferences
var i = 0
val size = spineReferences.size
while (i < size) {
val resource =
spineReferences[i].resource
var title = resource.title
if (TextUtils.isEmpty(title)) {
try {
val doc =
Jsoup.parse(String(resource.data, mCharset))
val elements = doc.getElementsByTag("title")
if (elements.size > 0) {
title = elements[0].text()
epubBook?.let { eBook ->
val metadata = eBook.metadata
book.name = metadata.firstTitle
if (metadata.authors.size > 0) {
val author =
metadata.authors[0].toString().replace("^, |, $".toRegex(), "")
book.author = author
}
if (metadata.descriptions.size > 0) {
book.intro = Jsoup.parse(metadata.descriptions[0]).text()
}
val refs = eBook.tableOfContents.tocReferences
if (refs == null || refs.isEmpty()) {
val spineReferences = eBook.spine.spineReferences
var i = 0
val size = spineReferences.size
while (i < size) {
val resource =
spineReferences[i].resource
var title = resource.title
if (TextUtils.isEmpty(title)) {
try {
val doc =
Jsoup.parse(String(resource.data, mCharset))
val elements = doc.getElementsByTag("title")
if (elements.size > 0) {
title = elements[0].text()
}
} catch (e: IOException) {
e.printStackTrace()
}
} catch (e: IOException) {
e.printStackTrace()
}
val chapter = BookChapter()
chapter.index = i
chapter.bookUrl = book.bookUrl
chapter.url = resource.href
if (i == 0 && title.isEmpty()) {
chapter.title = "封面"
} else {
chapter.title = title
}
chapterList.add(chapter)
i++
}
val chapter = BookChapter()
chapter.index = i
chapter.bookUrl = book.bookUrl
chapter.url = resource.href
if (i == 0 && title.isEmpty()) {
chapter.title = "封面"
} else {
chapter.title = title
} else {
parseMenu(chapterList, refs, 0)
for (i in chapterList.indices) {
chapterList[i].index = i
}
chapterList.add(chapter)
i++
}
} else {
parseMenu(chapterList, refs, 0)
for (i in chapterList.indices) {
chapterList[i].index = i
}
}
return chapterList
}
private fun parseMenu(
chapterList: ArrayList<BookChapter>,
refs: List<TOCReference>?,

@ -4,6 +4,7 @@ import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import io.legado.app.App
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp
import io.legado.app.utils.FileUtils
import io.legado.app.utils.isContentPath
@ -12,6 +13,22 @@ import java.io.File
object LocalBook {
fun getChapterList(book: Book): ArrayList<BookChapter> {
return if (book.isEpub()) {
EPUBFile.getChapterList(book)
} else {
AnalyzeTxtFile().analyze(book)
}
}
fun getContext(book: Book, chapter: BookChapter): String? {
return if (book.isEpub()) {
EPUBFile.getContent(book, chapter)
} else {
AnalyzeTxtFile.getContent(book, chapter)
}
}
fun importFile(path: String) {
val fileName = if (path.isContentPath()) {
val doc = DocumentFile.fromSingleUri(App.INSTANCE, Uri.parse(path))

@ -10,7 +10,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ItemTouchHelper
@ -18,6 +17,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.constant.Theme
@ -41,7 +41,7 @@ import org.jetbrains.anko.sdk27.listeners.onClick
import java.util.*
import kotlin.collections.ArrayList
class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
class GroupManageDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener {
private lateinit var viewModel: GroupViewModel
private lateinit var adapter: GroupAdapter
private val callBack: CallBack? get() = parentFragment as? CallBack
@ -62,8 +62,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
return inflater.inflate(R.layout.dialog_recycler_view, container)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
tool_bar.title = getString(R.string.group_manage)
initData()
initMenu()

@ -10,7 +10,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.ItemTouchHelper
@ -18,6 +17,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.constant.Theme
@ -40,7 +40,7 @@ import kotlinx.android.synthetic.main.item_group_select.view.*
import org.jetbrains.anko.sdk27.listeners.onClick
import java.util.*
class GroupSelectDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
class GroupSelectDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener {
companion object {
const val tag = "groupSelectDialog"
@ -78,8 +78,7 @@ class GroupSelectDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
return inflater.inflate(R.layout.dialog_book_group_picker, container)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
callBack = activity as? CallBack
arguments?.let {
groupId = it.getInt("groupId")

@ -11,7 +11,7 @@ import io.legado.app.help.AppConfig
import io.legado.app.help.BookHelp
import io.legado.app.help.IntentDataHelp
import io.legado.app.model.WebBook
import io.legado.app.model.localBook.AnalyzeTxtFile
import io.legado.app.model.localBook.LocalBook
import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.help.ReadAloud
import io.legado.app.service.help.ReadBook
@ -109,7 +109,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
) {
execute {
if (book.isLocalBook()) {
AnalyzeTxtFile().analyze(context, book).let {
LocalBook.getChapterList(book).let {
App.db.bookChapterDao().delByBook(book.bookUrl)
App.db.bookChapterDao().insert(*it.toTypedArray())
App.db.bookDao().update(book)

@ -10,10 +10,10 @@ import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.EditText
import android.widget.PopupWindow
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.constant.AppConst
import io.legado.app.constant.Theme
import io.legado.app.data.entities.ReplaceRule
@ -24,7 +24,7 @@ import io.legado.app.utils.toast
import kotlinx.android.synthetic.main.dialog_replace_edit.*
import org.jetbrains.anko.sdk27.listeners.onFocusChange
class ReplaceEditDialog : DialogFragment(),
class ReplaceEditDialog : BaseDialogFragment(),
Toolbar.OnMenuItemClickListener,
KeyboardToolPop.CallBack {
@ -63,8 +63,7 @@ class ReplaceEditDialog : DialogFragment(),
return inflater.inflate(R.layout.dialog_replace_edit, container)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
mSoftKeyboardTool = KeyboardToolPop(requireContext(), AppConst.keyboardToolChars, this)
tool_bar.inflateMenu(R.menu.replace_edit)
tool_bar.menu.applyTint(requireContext(), Theme.getTheme())

@ -10,11 +10,11 @@ import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.constant.Theme
@ -30,7 +30,7 @@ import kotlinx.android.synthetic.main.dialog_recycler_view.*
import kotlinx.android.synthetic.main.item_group_manage.view.*
import org.jetbrains.anko.sdk27.listeners.onClick
class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
class GroupManageDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener {
private lateinit var viewModel: RssSourceViewModel
private lateinit var adapter: GroupAdapter
@ -50,12 +50,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
return inflater.inflate(R.layout.dialog_recycler_view, container)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initData()
}
private fun initData() {
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
tool_bar.title = getString(R.string.group_manage)
tool_bar.inflateMenu(R.menu.group_manage)
tool_bar.menu.applyTint(requireContext(), Theme.getTheme())

@ -10,12 +10,12 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import androidx.preference.ListPreference
import androidx.preference.PreferenceViewHolder
import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.utils.getCompatDrawable
@ -109,7 +109,7 @@ class IconListPreference(context: Context, attrs: AttributeSet) : ListPreference
return "icon_$key"
}
class IconDialog : DialogFragment() {
class IconDialog : BaseDialogFragment() {
var onChanged: ((value: String) -> Unit)? = null
var dialogValue: String? = null
@ -135,8 +135,7 @@ class IconListPreference(context: Context, attrs: AttributeSet) : ListPreference
return inflater.inflate(R.layout.dialog_recycler_view, container)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
tool_bar.setTitle(R.string.change_icon)
recycler_view.layoutManager = LinearLayoutManager(requireContext())
val adapter = Adapter(requireContext())

@ -2,7 +2,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:background="@color/background">
<androidx.appcompat.widget.Toolbar
android:id="@+id/tool_bar"

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
android:background="@color/background">
<io.legado.app.ui.widget.text.InertiaScrollTextView
android:id="@+id/text_view"
@ -17,8 +18,8 @@
android:layout_height="wrap_content"
android:layout_margin="3dp"
android:visibility="invisible"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent" />
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Loading…
Cancel
Save