Merge branch 'master' into master

pull/155/head
kunfei 5 years ago committed by GitHub
commit 27bcfd999d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      app/src/main/assets/updateLog.md
  2. 21
      app/src/main/java/io/legado/app/service/TTSReadAloudService.kt
  3. 37
      app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt
  4. 12
      app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt
  5. 12
      app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt
  6. 41
      app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt
  7. 66
      app/src/main/java/io/legado/app/utils/JsoupExtensions.kt

@ -6,6 +6,7 @@
* 更改书架控件,ViewPager2替换回2.0使用的ViewPager,解决下拉不流畅问题 * 更改书架控件,ViewPager2替换回2.0使用的ViewPager,解决下拉不流畅问题
* 修复点击作者搜索后,打开的详情页还是原来的书籍的bug * 修复点击作者搜索后,打开的详情页还是原来的书籍的bug
* 修改朗读菜单 * 修改朗读菜单
* 优化rss朗读
**2020/03/12** **2020/03/12**
* 导入本地添加需要权限模式 * 导入本地添加需要权限模式

@ -29,7 +29,7 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener
} }
} }
private var ttsIsSuccess: Boolean = false private var ttsInitFinish = false
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -42,22 +42,23 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener
clearTTS() clearTTS()
} }
@Synchronized
override fun onInit(status: Int) { override fun onInit(status: Int) {
launch { if (status == TextToSpeech.SUCCESS) {
if (status == TextToSpeech.SUCCESS) { textToSpeech?.language = Locale.CHINA
textToSpeech?.language = Locale.CHINA textToSpeech?.setOnUtteranceProgressListener(TTSUtteranceListener())
textToSpeech?.setOnUtteranceProgressListener(TTSUtteranceListener()) ttsInitFinish = true
ttsIsSuccess = true play()
play() } else {
} else { launch {
toast(R.string.tts_init_failed) toast(R.string.tts_init_failed)
} }
} }
} }
@Suppress("DEPRECATION") @Synchronized
override fun play() { override fun play() {
if (contentList.isEmpty() || !ttsIsSuccess) { if (contentList.isEmpty() || !ttsInitFinish) {
return return
} }
if (requestFocus()) { if (requestFocus()) {

@ -17,9 +17,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.EventBus
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.ReadBookConfig
import io.legado.app.help.permission.Permissions import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FileChooserDialog
@ -86,23 +84,6 @@ class ImportBookActivity : VMBaseActivity<ImportBookViewModel>(R.layout.activity
} }
}) })
// 没有权限就显示一个授权提示和按钮
if (PermissionsCompat.check(this, *Permissions.Group.STORAGE)) {
hint_per.visibility = View.GONE
} else {
hint_per.visibility = View.VISIBLE
tv_request_per.onClick {
PermissionsCompat.Builder(this)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
hint_per.visibility = View.GONE
initData()
upRootDoc()
}
.request()
}
}
} }
private fun initEvent() { private fun initEvent() {
@ -147,6 +128,24 @@ class ImportBookActivity : VMBaseActivity<ImportBookViewModel>(R.layout.activity
subDocs.clear() subDocs.clear()
path = it path = it
} }
} ?: let {
// 没有权限就显示一个授权提示和按钮
if (PermissionsCompat.check(this, *Permissions.Group.STORAGE)) {
hint_per.visibility = View.GONE
} else {
hint_per.visibility = View.VISIBLE
tv_request_per.onClick {
PermissionsCompat.Builder(this)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
hint_per.visibility = View.GONE
initData()
upRootDoc()
}
.request()
}
}
} }
upPath() upPath()
} }

@ -31,7 +31,7 @@ object FilePicker {
0 -> default?.invoke() 0 -> default?.invoke()
1 -> { 1 -> {
try { try {
val intent = getSelectDirIntent() val intent = createSelectDirIntent()
activity.startActivityForResult(intent, requestCode) activity.startActivityForResult(intent, requestCode)
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
e.printStackTrace() e.printStackTrace()
@ -68,7 +68,7 @@ object FilePicker {
0 -> default?.invoke() 0 -> default?.invoke()
1 -> { 1 -> {
try { try {
val intent = getSelectDirIntent() val intent = createSelectDirIntent()
fragment.startActivityForResult(intent, requestCode) fragment.startActivityForResult(intent, requestCode)
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
e.printStackTrace() e.printStackTrace()
@ -106,7 +106,7 @@ object FilePicker {
0 -> default?.invoke() 0 -> default?.invoke()
1 -> { 1 -> {
try { try {
val intent = getSelectFileIntent() val intent = createSelectFileIntent()
intent.type = type//设置类型 intent.type = type//设置类型
activity.startActivityForResult(intent, requestCode) activity.startActivityForResult(intent, requestCode)
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
@ -147,7 +147,7 @@ object FilePicker {
0 -> default?.invoke() 0 -> default?.invoke()
1 -> { 1 -> {
try { try {
val intent = getSelectFileIntent() val intent = createSelectFileIntent()
intent.type = type//设置类型 intent.type = type//设置类型
fragment.startActivityForResult(intent, requestCode) fragment.startActivityForResult(intent, requestCode)
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
@ -168,14 +168,14 @@ object FilePicker {
}.show() }.show()
} }
private fun getSelectFileIntent(): Intent { private fun createSelectFileIntent(): Intent {
val intent = Intent(Intent.ACTION_GET_CONTENT) val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE) intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
return intent return intent
} }
private fun getSelectDirIntent(): Intent { private fun createSelectDirIntent(): Intent {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
return intent return intent

@ -22,7 +22,6 @@ import org.apache.commons.text.StringEscapeUtils
import org.jetbrains.anko.share import org.jetbrains.anko.share
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.safety.Whitelist
class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_read), class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_read),
@ -246,18 +245,15 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
private fun readAloud() { private fun readAloud() {
if (viewModel.textToSpeech.isSpeaking) { if (viewModel.textToSpeech?.isSpeaking == true) {
viewModel.textToSpeech.stop() viewModel.textToSpeech?.stop()
upTtsMenu(false) upTtsMenu(false)
} else { } else {
web_view.settings.javaScriptEnabled = true web_view.settings.javaScriptEnabled = true
web_view.evaluateJavascript("document.documentElement.outerHTML") { web_view.evaluateJavascript("document.documentElement.outerHTML") {
val html = StringEscapeUtils.unescapeJson(it) val html = StringEscapeUtils.unescapeJson(it)
val text = Jsoup.clean(html, Whitelist.none()) Jsoup.parse(html).text()
.replace(Regex("""&\w+;"""), "") viewModel.readAloud(Jsoup.parse(html).textArray())
.trim()//朗读过程中总是听到一些杂音,清理一下
//longToast(需读内容)调试一下
viewModel.readAloud(text)
} }
} }
} }

@ -35,7 +35,9 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application),
val contentLiveData = MutableLiveData<String>() val contentLiveData = MutableLiveData<String>()
val urlLiveData = MutableLiveData<AnalyzeUrl>() val urlLiveData = MutableLiveData<AnalyzeUrl>()
var star = false var star = false
var textToSpeech: TextToSpeech = TextToSpeech(context, this) var textToSpeech: TextToSpeech? = null
private var ttsInitFinish = false
private var ttsTextList = arrayListOf<String>()
fun initData(intent: Intent) { fun initData(intent: Intent) {
execute { execute {
@ -143,28 +145,43 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application),
} }
} }
@Synchronized
override fun onInit(status: Int) { override fun onInit(status: Int) {
launch { if (status == TextToSpeech.SUCCESS) {
if (status == TextToSpeech.SUCCESS) { textToSpeech?.language = Locale.CHINA
textToSpeech.language = Locale.CHINA textToSpeech?.setOnUtteranceProgressListener(TTSUtteranceListener())
textToSpeech.setOnUtteranceProgressListener(TTSUtteranceListener()) ttsInitFinish = true
} else { play()
} else {
launch {
toast(R.string.tts_init_failed) toast(R.string.tts_init_failed)
} }
} }
} }
fun readAloud(text: String) { @Synchronized
textToSpeech.stop() private fun play() {
text.split("\n", " ", "  ").forEach { if (!ttsInitFinish) return
textToSpeech.speak(it, TextToSpeech.QUEUE_ADD, null, "rss") textToSpeech?.stop()
ttsTextList.forEach {
textToSpeech?.speak(it, TextToSpeech.QUEUE_ADD, null, "rss")
}
}
fun readAloud(textArray: Array<String>) {
ttsTextList.clear()
ttsTextList.addAll(textArray)
textToSpeech?.let {
play()
} ?: let {
textToSpeech = TextToSpeech(context, this)
} }
} }
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()
textToSpeech.stop() textToSpeech?.stop()
textToSpeech.shutdown() textToSpeech?.shutdown()
} }
/** /**

@ -0,0 +1,66 @@
package io.legado.app.utils
import org.jsoup.internal.StringUtil
import org.jsoup.nodes.CDataNode
import org.jsoup.nodes.Element
import org.jsoup.nodes.Node
import org.jsoup.nodes.TextNode
import org.jsoup.select.NodeTraversor
import org.jsoup.select.NodeVisitor
fun Element.textArray(): Array<String> {
val accum = StringUtil.borrowBuilder()
NodeTraversor.traverse(object : NodeVisitor {
override fun head(node: Node, depth: Int) {
if (node is TextNode) {
appendNormalisedText(accum, node)
} else if (node is Element) {
if (accum.isNotEmpty() &&
(node.isBlock || node.tag().name == "br") &&
!lastCharIsWhitespace(accum)
) accum.append("\n")
}
}
override fun tail(node: Node, depth: Int) {
if (node is Element) {
if (node.isBlock && node.nextSibling() is TextNode && !lastCharIsWhitespace(
accum
)
) accum.append("\n")
}
}
}, this)
val text = StringUtil.releaseBuilder(accum).trim { it <= ' ' }
return text.splitNotBlank("\n")
}
private fun appendNormalisedText(accum: StringBuilder, textNode: TextNode) {
val text = textNode.wholeText
if (preserveWhitespace(textNode.parentNode()) || textNode is CDataNode)
accum.append(text)
else StringUtil.appendNormalisedWhitespace(
accum,
text,
lastCharIsWhitespace(accum)
)
}
private fun preserveWhitespace(node: Node?): Boolean {
if (node is Element) {
var el = node as Element?
var i = 0
do {
if (el!!.tag().preserveWhitespace()) return true
el = el.parent()
i++
} while (i < 6 && el != null)
}
return false
}
private fun lastCharIsWhitespace(sb: java.lang.StringBuilder): Boolean {
return sb.isNotEmpty() && sb[sb.length - 1] == ' '
}
Loading…
Cancel
Save