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,解决下拉不流畅问题
* 修复点击作者搜索后,打开的详情页还是原来的书籍的bug
* 修改朗读菜单
* 优化rss朗读
**2020/03/12**
* 导入本地添加需要权限模式

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

@ -17,9 +17,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.EventBus
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.PermissionsCompat
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() {
@ -147,6 +128,24 @@ class ImportBookActivity : VMBaseActivity<ImportBookViewModel>(R.layout.activity
subDocs.clear()
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()
}

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

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

@ -35,7 +35,9 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application),
val contentLiveData = MutableLiveData<String>()
val urlLiveData = MutableLiveData<AnalyzeUrl>()
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) {
execute {
@ -143,28 +145,43 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application),
}
}
@Synchronized
override fun onInit(status: Int) {
launch {
if (status == TextToSpeech.SUCCESS) {
textToSpeech.language = Locale.CHINA
textToSpeech.setOnUtteranceProgressListener(TTSUtteranceListener())
} else {
if (status == TextToSpeech.SUCCESS) {
textToSpeech?.language = Locale.CHINA
textToSpeech?.setOnUtteranceProgressListener(TTSUtteranceListener())
ttsInitFinish = true
play()
} else {
launch {
toast(R.string.tts_init_failed)
}
}
}
fun readAloud(text: String) {
textToSpeech.stop()
text.split("\n", " ", "  ").forEach {
textToSpeech.speak(it, TextToSpeech.QUEUE_ADD, null, "rss")
@Synchronized
private fun play() {
if (!ttsInitFinish) return
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() {
super.onCleared()
textToSpeech.stop()
textToSpeech.shutdown()
textToSpeech?.stop()
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