Merge pull request #3731 from gedoor/master
Sync changes from branch master
This commit is contained in:
commit
5975156a58
64 files changed
+1745
-867
No files matched your search
@@ -1,5 +1,8 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
|
- name: 简繁转化
|
||||||
|
url: https://github.com/liuyueyi/quick-chinese-transfer/issues/new
|
||||||
|
about: 简繁转化问题请优先到quick-chinese-transfer反馈
|
||||||
- name: 讨论 / Discussions
|
- name: 讨论 / Discussions
|
||||||
url: https://github.com/gedoor/legado/discussions
|
url: https://github.com/gedoor/legado/discussions
|
||||||
about: Please ask and answer questions here.
|
about: Please ask and answer questions here.
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ jobs:
|
|||||||
uses: gradle/actions/setup-gradle@v3
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
|
||||||
- name: Build With Gradle
|
- name: Build With Gradle
|
||||||
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
if [ ${{ env.type }} == 'release' ]; then
|
if [ ${{ env.type }} == 'release' ]; then
|
||||||
typeName="原包名"
|
typeName="原包名"
|
||||||
@@ -91,15 +92,22 @@ jobs:
|
|||||||
echo "开始${{ env.product }}$typeName构建"
|
echo "开始${{ env.product }}$typeName构建"
|
||||||
chmod +x gradlew
|
chmod +x gradlew
|
||||||
./gradlew assemble${{ env.product }}release --build-cache --parallel --daemon --warning-mode all
|
./gradlew assemble${{ env.product }}release --build-cache --parallel --daemon --warning-mode all
|
||||||
echo "修改文件名"
|
- name: Rename outputs apks And Move files
|
||||||
|
run: |
|
||||||
|
echo "修改APK文件名"
|
||||||
mkdir -p ${{ github.workspace }}/apk/
|
mkdir -p ${{ github.workspace }}/apk/
|
||||||
mkdir -p ${{ github.workspace }}/mapping/
|
|
||||||
for file in `ls ${{ github.workspace }}/app/build/outputs/apk/*/*/*.apk`; do
|
for file in `ls ${{ github.workspace }}/app/build/outputs/apk/*/*/*.apk`; do
|
||||||
mv "$file" ${{ github.workspace }}/apk/legado_${{ env.product }}_${{ env.VERSIONL }}_$typeName.apk
|
mv "$file" ${{ github.workspace }}/apk/legado_${{ env.product }}_${{ env.VERSIONL }}_$typeName.apk
|
||||||
done
|
done
|
||||||
|
echo "移动mapping文件"
|
||||||
|
mkdir -p ${{ github.workspace }}/mapping/
|
||||||
for file in `ls ${{ github.workspace }}/app/build/outputs/mapping/*/mapping.txt`; do
|
for file in `ls ${{ github.workspace }}/app/build/outputs/mapping/*/mapping.txt`; do
|
||||||
mv "$file" ${{ github.workspace }}/mapping/mapping.txt
|
mv "$file" ${{ github.workspace }}/mapping/mapping.txt
|
||||||
done
|
done
|
||||||
|
echo "移动missing_rules.txt文件"
|
||||||
|
for file in `ls ${{ github.workspace }}/app/build/outputs/mapping/*/missing_rules.txt`; do
|
||||||
|
mv "$file" ${{ github.workspace }}/mapping/missing_rules.txt
|
||||||
|
done
|
||||||
- name: Upload App To Artifact
|
- name: Upload App To Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -112,6 +120,12 @@ jobs:
|
|||||||
name: legado.${{ env.product }}.${{ env.type }}.mapping
|
name: legado.${{ env.product }}.${{ env.type }}.mapping
|
||||||
path: ${{ github.workspace }}/mapping/mapping.txt
|
path: ${{ github.workspace }}/mapping/mapping.txt
|
||||||
|
|
||||||
|
- name: Upload Missing Rules File To Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: legado.${{ env.product }}.${{ env.type }}.mapping.missing_rules
|
||||||
|
path: ${{ github.workspace }}/mapping/missing_rules.txt
|
||||||
|
|
||||||
lanzou:
|
lanzou:
|
||||||
needs: [ prepare, build ]
|
needs: [ prepare, build ]
|
||||||
if: ${{ github.event_name != 'pull_request' && needs.prepare.outputs.lanzou == 'yes' }}
|
if: ${{ github.event_name != 'pull_request' && needs.prepare.outputs.lanzou == 'yes' }}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 16
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v3
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
id: pnpm-install
|
id: pnpm-install
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -473,6 +473,15 @@
|
|||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/file_paths" />
|
android:resource="@xml/file_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
<provider
|
||||||
|
android:name="androidx.startup.InitializationProvider"
|
||||||
|
android:authorities="${applicationId}.androidx-startup"
|
||||||
|
android:exported="false"
|
||||||
|
tools:node="merge">
|
||||||
|
<meta-data
|
||||||
|
android:name="androidx.emoji2.text.EmojiCompatInitializer"
|
||||||
|
tools:node="remove" />
|
||||||
|
</provider>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="channel"
|
android:name="channel"
|
||||||
|
|||||||
@@ -117,7 +117,6 @@ object PreferKey {
|
|||||||
const val welcomeShowIconDark = "welcomeShowIconDark"
|
const val welcomeShowIconDark = "welcomeShowIconDark"
|
||||||
const val pageTouchSlop = "pageTouchSlop"
|
const val pageTouchSlop = "pageTouchSlop"
|
||||||
const val showAddToShelfAlert = "showAddToShelfAlert"
|
const val showAddToShelfAlert = "showAddToShelfAlert"
|
||||||
const val asyncLoadImage = "asyncLoadImage"
|
|
||||||
const val ignoreAudioFocus = "ignoreAudioFocus"
|
const val ignoreAudioFocus = "ignoreAudioFocus"
|
||||||
const val parallelExportBook = "parallelExportBook"
|
const val parallelExportBook = "parallelExportBook"
|
||||||
const val progressBarBehavior = "progressBarBehavior"
|
const val progressBarBehavior = "progressBarBehavior"
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package io.legado.app.help
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
val globalExecutor: ExecutorService by lazy { Executors.newSingleThreadExecutor() }
|
||||||
@@ -137,6 +137,9 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val textSelectAble: Boolean
|
||||||
|
get() = appCtx.getPrefBoolean(PreferKey.textSelectAble, true)
|
||||||
|
|
||||||
val isTransparentStatusBar: Boolean
|
val isTransparentStatusBar: Boolean
|
||||||
get() = appCtx.getPrefBoolean(PreferKey.transparentStatusBar, true)
|
get() = appCtx.getPrefBoolean(PreferKey.transparentStatusBar, true)
|
||||||
|
|
||||||
@@ -457,8 +460,6 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
|
|||||||
|
|
||||||
val showAddToShelfAlert get() = appCtx.getPrefBoolean(PreferKey.showAddToShelfAlert, true)
|
val showAddToShelfAlert get() = appCtx.getPrefBoolean(PreferKey.showAddToShelfAlert, true)
|
||||||
|
|
||||||
val asyncLoadImage get() = appCtx.getPrefBoolean(PreferKey.asyncLoadImage, false)
|
|
||||||
|
|
||||||
val ignoreAudioFocus get() = appCtx.getPrefBoolean(PreferKey.ignoreAudioFocus, false)
|
val ignoreAudioFocus get() = appCtx.getPrefBoolean(PreferKey.ignoreAudioFocus, false)
|
||||||
|
|
||||||
val onlyLatestBackup get() = appCtx.getPrefBoolean(PreferKey.onlyLatestBackup, true)
|
val onlyLatestBackup get() = appCtx.getPrefBoolean(PreferKey.onlyLatestBackup, true)
|
||||||
|
|||||||
@@ -567,7 +567,6 @@ object ReadBookConfig {
|
|||||||
textColorInt = color
|
textColorInt = color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ChapterProvider.upStyle()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun curTextColor(): Int {
|
fun curTextColor(): Int {
|
||||||
|
|||||||
@@ -163,7 +163,22 @@ private constructor(private val mContext: Context) : ThemeStoreInterface {
|
|||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object : SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
|
init {
|
||||||
|
prefs(appCtx).registerOnSharedPreferenceChangeListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accentColor = accentColor()
|
||||||
|
|
||||||
|
override fun onSharedPreferenceChanged(
|
||||||
|
sharedPreferences: SharedPreferences?,
|
||||||
|
key: String?
|
||||||
|
) {
|
||||||
|
when (key) {
|
||||||
|
ThemeStorePrefKeys.KEY_ACCENT_COLOR -> accentColor = accentColor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun editTheme(context: Context): ThemeStore {
|
fun editTheme(context: Context): ThemeStore {
|
||||||
return ThemeStore(context)
|
return ThemeStore(context)
|
||||||
|
|||||||
@@ -2,13 +2,10 @@ package io.legado.app.model
|
|||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.os.Build
|
|
||||||
import android.util.Size
|
import android.util.Size
|
||||||
import androidx.collection.LruCache
|
import androidx.collection.LruCache
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.constant.AppLog
|
|
||||||
import io.legado.app.constant.AppLog.putDebug
|
import io.legado.app.constant.AppLog.putDebug
|
||||||
import io.legado.app.constant.PageAnim
|
|
||||||
import io.legado.app.data.entities.Book
|
import io.legado.app.data.entities.Book
|
||||||
import io.legado.app.data.entities.BookSource
|
import io.legado.app.data.entities.BookSource
|
||||||
import io.legado.app.exception.NoStackTraceException
|
import io.legado.app.exception.NoStackTraceException
|
||||||
@@ -16,10 +13,8 @@ import io.legado.app.help.book.BookHelp
|
|||||||
import io.legado.app.help.book.isEpub
|
import io.legado.app.help.book.isEpub
|
||||||
import io.legado.app.help.book.isPdf
|
import io.legado.app.help.book.isPdf
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.help.coroutine.Coroutine
|
|
||||||
import io.legado.app.model.localBook.EpubFile
|
import io.legado.app.model.localBook.EpubFile
|
||||||
import io.legado.app.model.localBook.PdfFile
|
import io.legado.app.model.localBook.PdfFile
|
||||||
import io.legado.app.utils.BitmapCache
|
|
||||||
import io.legado.app.utils.BitmapUtils
|
import io.legado.app.utils.BitmapUtils
|
||||||
import io.legado.app.utils.FileUtils
|
import io.legado.app.utils.FileUtils
|
||||||
import io.legado.app.utils.SvgUtils
|
import io.legado.app.utils.SvgUtils
|
||||||
@@ -29,8 +24,6 @@ import kotlinx.coroutines.withContext
|
|||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
object ImageProvider {
|
object ImageProvider {
|
||||||
|
|
||||||
@@ -50,12 +43,6 @@ object ImageProvider {
|
|||||||
}
|
}
|
||||||
return AppConfig.bitmapCacheSize * M
|
return AppConfig.bitmapCacheSize * M
|
||||||
}
|
}
|
||||||
private val maxCacheSize = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
|
|
||||||
min(128 * M, Runtime.getRuntime().maxMemory().toInt())
|
|
||||||
} else {
|
|
||||||
256 * M
|
|
||||||
}
|
|
||||||
private val asyncLoadingImages = ConcurrentHashMap.newKeySet<String>()
|
|
||||||
val bitmapLruCache = object : LruCache<String, Bitmap>(cacheSize) {
|
val bitmapLruCache = object : LruCache<String, Bitmap>(cacheSize) {
|
||||||
|
|
||||||
override fun sizeOf(filePath: String, bitmap: Bitmap): Int {
|
override fun sizeOf(filePath: String, bitmap: Bitmap): Int {
|
||||||
@@ -70,8 +57,7 @@ object ImageProvider {
|
|||||||
) {
|
) {
|
||||||
//错误图片不能释放,占位用,防止一直重复获取图片
|
//错误图片不能释放,占位用,防止一直重复获取图片
|
||||||
if (oldBitmap != errorBitmap) {
|
if (oldBitmap != errorBitmap) {
|
||||||
BitmapCache.add(oldBitmap)
|
oldBitmap.recycle()
|
||||||
//oldBitmap.recycle()
|
|
||||||
//putDebug("ImageProvider: trigger bitmap recycle. URI: $filePath")
|
//putDebug("ImageProvider: trigger bitmap recycle. URI: $filePath")
|
||||||
//putDebug("ImageProvider : cacheUsage ${size()}bytes / ${maxSize()}bytes")
|
//putDebug("ImageProvider : cacheUsage ${size()}bytes / ${maxSize()}bytes")
|
||||||
}
|
}
|
||||||
@@ -160,9 +146,8 @@ object ImageProvider {
|
|||||||
book: Book,
|
book: Book,
|
||||||
src: String,
|
src: String,
|
||||||
width: Int,
|
width: Int,
|
||||||
height: Int? = null,
|
height: Int? = null
|
||||||
block: (() -> Unit)? = null
|
): Bitmap {
|
||||||
): Bitmap? {
|
|
||||||
//src为空白时 可能被净化替换掉了 或者规则失效
|
//src为空白时 可能被净化替换掉了 或者规则失效
|
||||||
if (book.getUseReplaceRule() && src.isBlank()) {
|
if (book.getUseReplaceRule() && src.isBlank()) {
|
||||||
book.setUseReplaceRule(false)
|
book.setUseReplaceRule(false)
|
||||||
@@ -174,32 +159,6 @@ object ImageProvider {
|
|||||||
//bitmapLruCache的key同一改成缓存文件的路径
|
//bitmapLruCache的key同一改成缓存文件的路径
|
||||||
val cacheBitmap = getNotRecycled(vFile.absolutePath)
|
val cacheBitmap = getNotRecycled(vFile.absolutePath)
|
||||||
if (cacheBitmap != null) return cacheBitmap
|
if (cacheBitmap != null) return cacheBitmap
|
||||||
if (height != null && AppConfig.asyncLoadImage && ReadBook.pageAnim() == PageAnim.scrollPageAnim) {
|
|
||||||
if (asyncLoadingImages.contains(vFile.absolutePath)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
asyncLoadingImages.add(vFile.absolutePath)
|
|
||||||
Coroutine.async {
|
|
||||||
BitmapUtils.decodeBitmap(vFile.absolutePath, width, height)
|
|
||||||
?: SvgUtils.createBitmap(vFile.absolutePath, width, height)
|
|
||||||
?: throw NoStackTraceException(appCtx.getString(R.string.error_decode_bitmap))
|
|
||||||
}.onSuccess {
|
|
||||||
bitmapLruCache.run {
|
|
||||||
if (maxSize() < maxCacheSize && size() + it.byteCount > maxSize() && putCount() - evictionCount() < 5) {
|
|
||||||
resize(min(maxCacheSize, maxSize() + it.byteCount))
|
|
||||||
AppLog.put("图片缓存太小,自动扩增至${(maxSize() / M)}MB。")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bitmapLruCache.put(vFile.absolutePath, it)
|
|
||||||
}.onError {
|
|
||||||
//错误图片占位,防止重复获取
|
|
||||||
bitmapLruCache.put(vFile.absolutePath, errorBitmap)
|
|
||||||
}.onFinally {
|
|
||||||
asyncLoadingImages.remove(vFile.absolutePath)
|
|
||||||
block?.invoke()
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return kotlin.runCatching {
|
return kotlin.runCatching {
|
||||||
val bitmap = BitmapUtils.decodeBitmap(vFile.absolutePath, width, height)
|
val bitmap = BitmapUtils.decodeBitmap(vFile.absolutePath, width, height)
|
||||||
?: SvgUtils.createBitmap(vFile.absolutePath, width, height)
|
?: SvgUtils.createBitmap(vFile.absolutePath, width, height)
|
||||||
@@ -212,4 +171,8 @@ object ImageProvider {
|
|||||||
}.getOrDefault(errorBitmap)
|
}.getOrDefault(errorBitmap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
bitmapLruCache.evictAll()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,7 @@ import io.legado.app.help.book.isLocal
|
|||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.help.config.ReadBookConfig
|
import io.legado.app.help.config.ReadBookConfig
|
||||||
import io.legado.app.help.coroutine.Coroutine
|
import io.legado.app.help.coroutine.Coroutine
|
||||||
|
import io.legado.app.help.globalExecutor
|
||||||
import io.legado.app.model.localBook.TextFile
|
import io.legado.app.model.localBook.TextFile
|
||||||
import io.legado.app.model.webBook.WebBook
|
import io.legado.app.model.webBook.WebBook
|
||||||
import io.legado.app.service.BaseReadAloudService
|
import io.legado.app.service.BaseReadAloudService
|
||||||
@@ -63,6 +64,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
val downloadFailChapters = hashMapOf<Int, Int>()
|
val downloadFailChapters = hashMapOf<Int, Int>()
|
||||||
var contentProcessor: ContentProcessor? = null
|
var contentProcessor: ContentProcessor? = null
|
||||||
val downloadScope = CoroutineScope(SupervisorJob() + IO)
|
val downloadScope = CoroutineScope(SupervisorJob() + IO)
|
||||||
|
val executor = globalExecutor
|
||||||
|
|
||||||
//暂时保存跳转前进度
|
//暂时保存跳转前进度
|
||||||
fun saveCurrentBookProcess() {
|
fun saveCurrentBookProcess() {
|
||||||
@@ -158,10 +160,10 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun upReadTime() {
|
fun upReadTime() {
|
||||||
|
executor.execute {
|
||||||
if (!AppConfig.enableReadRecord) {
|
if (!AppConfig.enableReadRecord) {
|
||||||
return
|
return@execute
|
||||||
}
|
}
|
||||||
Coroutine.async(executeContext = IO) {
|
|
||||||
readRecord.readTime = readRecord.readTime + System.currentTimeMillis() - readStartTime
|
readRecord.readTime = readRecord.readTime + System.currentTimeMillis() - readStartTime
|
||||||
readStartTime = System.currentTimeMillis()
|
readStartTime = System.currentTimeMillis()
|
||||||
readRecord.lastRead = System.currentTimeMillis()
|
readRecord.lastRead = System.currentTimeMillis()
|
||||||
@@ -204,7 +206,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
return hasPrevPage
|
return hasPrevPage
|
||||||
}
|
}
|
||||||
|
|
||||||
fun moveToNextChapter(upContent: Boolean): Boolean {
|
fun moveToNextChapter(upContent: Boolean, upContentInPlace: Boolean = true): Boolean {
|
||||||
if (durChapterIndex < chapterSize - 1) {
|
if (durChapterIndex < chapterSize - 1) {
|
||||||
durChapterPos = 0
|
durChapterPos = 0
|
||||||
durChapterIndex++
|
durChapterIndex++
|
||||||
@@ -213,11 +215,11 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
nextTextChapter = null
|
nextTextChapter = null
|
||||||
if (curTextChapter == null) {
|
if (curTextChapter == null) {
|
||||||
AppLog.putDebug("moveToNextChapter-章节未加载,开始加载")
|
AppLog.putDebug("moveToNextChapter-章节未加载,开始加载")
|
||||||
callBack?.upContent()
|
if (upContentInPlace) callBack?.upContent(resetPageOffset = false)
|
||||||
loadContent(durChapterIndex, upContent, resetPageOffset = false)
|
loadContent(durChapterIndex, upContent, resetPageOffset = false)
|
||||||
} else if (upContent) {
|
} else if (upContent && upContentInPlace) {
|
||||||
AppLog.putDebug("moveToNextChapter-章节已加载,刷新视图")
|
AppLog.putDebug("moveToNextChapter-章节已加载,刷新视图")
|
||||||
callBack?.upContent()
|
callBack?.upContent(resetPageOffset = false)
|
||||||
}
|
}
|
||||||
loadContent(durChapterIndex.plus(1), upContent, false)
|
loadContent(durChapterIndex.plus(1), upContent, false)
|
||||||
saveRead()
|
saveRead()
|
||||||
@@ -233,7 +235,8 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
|
|
||||||
fun moveToPrevChapter(
|
fun moveToPrevChapter(
|
||||||
upContent: Boolean,
|
upContent: Boolean,
|
||||||
toLast: Boolean = true
|
toLast: Boolean = true,
|
||||||
|
upContentInPlace: Boolean = true
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (durChapterIndex > 0) {
|
if (durChapterIndex > 0) {
|
||||||
durChapterPos = if (toLast) prevTextChapter?.lastReadLength ?: Int.MAX_VALUE else 0
|
durChapterPos = if (toLast) prevTextChapter?.lastReadLength ?: Int.MAX_VALUE else 0
|
||||||
@@ -242,10 +245,10 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
curTextChapter = prevTextChapter
|
curTextChapter = prevTextChapter
|
||||||
prevTextChapter = null
|
prevTextChapter = null
|
||||||
if (curTextChapter == null) {
|
if (curTextChapter == null) {
|
||||||
callBack?.upContent()
|
if (upContentInPlace) callBack?.upContent(resetPageOffset = false)
|
||||||
loadContent(durChapterIndex, upContent, resetPageOffset = false)
|
loadContent(durChapterIndex, upContent, resetPageOffset = false)
|
||||||
} else if (upContent) {
|
} else if (upContent && upContentInPlace) {
|
||||||
callBack?.upContent()
|
callBack?.upContent(resetPageOffset = false)
|
||||||
}
|
}
|
||||||
loadContent(durChapterIndex.minus(1), upContent, false)
|
loadContent(durChapterIndex.minus(1), upContent, false)
|
||||||
saveRead()
|
saveRead()
|
||||||
@@ -267,6 +270,16 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setPageIndex(index: Int) {
|
fun setPageIndex(index: Int) {
|
||||||
|
val textChapter = curTextChapter
|
||||||
|
if (textChapter != null) {
|
||||||
|
val pageIndex = durPageIndex
|
||||||
|
if (index > pageIndex) {
|
||||||
|
textChapter.getPage(index - 2)?.recycleRecorders()
|
||||||
|
}
|
||||||
|
if (index < pageIndex) {
|
||||||
|
textChapter.getPage(index + 3)?.recycleRecorders()
|
||||||
|
}
|
||||||
|
}
|
||||||
durChapterPos = curTextChapter?.getReadLength(index) ?: index
|
durChapterPos = curTextChapter?.getReadLength(index) ?: index
|
||||||
saveRead(true)
|
saveRead(true)
|
||||||
curPageChanged(true)
|
curPageChanged(true)
|
||||||
@@ -525,8 +538,8 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun saveRead(pageChanged: Boolean = false) {
|
fun saveRead(pageChanged: Boolean = false) {
|
||||||
Coroutine.async(executeContext = IO) {
|
executor.execute {
|
||||||
val book = book ?: return@async
|
val book = book ?: return@execute
|
||||||
book.lastCheckCount = 0
|
book.lastCheckCount = 0
|
||||||
book.durChapterTime = System.currentTimeMillis()
|
book.durChapterTime = System.currentTimeMillis()
|
||||||
val chapterChanged = book.durChapterIndex != durChapterIndex
|
val chapterChanged = book.durChapterIndex != durChapterIndex
|
||||||
@@ -549,14 +562,16 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
*/
|
*/
|
||||||
private fun preDownload() {
|
private fun preDownload() {
|
||||||
if (book?.isLocal == true) return
|
if (book?.isLocal == true) return
|
||||||
|
executor.execute {
|
||||||
if (AppConfig.preDownloadNum < 2) {
|
if (AppConfig.preDownloadNum < 2) {
|
||||||
return
|
return@execute
|
||||||
}
|
}
|
||||||
preDownloadTask?.cancel()
|
preDownloadTask?.cancel()
|
||||||
preDownloadTask = Coroutine.async(executeContext = IO) {
|
preDownloadTask = Coroutine.async(executeContext = IO) {
|
||||||
//预下载
|
//预下载
|
||||||
launch {
|
launch {
|
||||||
val maxChapterIndex = min(durChapterIndex + AppConfig.preDownloadNum, chapterSize)
|
val maxChapterIndex =
|
||||||
|
min(durChapterIndex + AppConfig.preDownloadNum, chapterSize)
|
||||||
for (i in durChapterIndex.plus(2)..maxChapterIndex) {
|
for (i in durChapterIndex.plus(2)..maxChapterIndex) {
|
||||||
if (downloadedChapters.contains(i)) continue
|
if (downloadedChapters.contains(i)) continue
|
||||||
if ((downloadFailChapters[i] ?: 0) >= 3) continue
|
if ((downloadFailChapters[i] ?: 0) >= 3) continue
|
||||||
@@ -573,6 +588,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册回调
|
* 注册回调
|
||||||
@@ -595,6 +611,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
coroutineContext.cancelChildren()
|
coroutineContext.cancelChildren()
|
||||||
downloadedChapters.clear()
|
downloadedChapters.clear()
|
||||||
downloadFailChapters.clear()
|
downloadFailChapters.clear()
|
||||||
|
ImageProvider.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CallBack {
|
interface CallBack {
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ class AnalyzeRule(
|
|||||||
}
|
}
|
||||||
if (sourceRule.replaceRegex.isNotEmpty() && result is List<*>) {
|
if (sourceRule.replaceRegex.isNotEmpty() && result is List<*>) {
|
||||||
val newList = ArrayList<String>()
|
val newList = ArrayList<String>()
|
||||||
for (item in result as List<*>) {
|
for (item in result) {
|
||||||
newList.add(replaceRegex(item.toString(), sourceRule))
|
newList.add(replaceRegex(item.toString(), sourceRule))
|
||||||
}
|
}
|
||||||
result = newList
|
result = newList
|
||||||
@@ -210,12 +210,12 @@ class AnalyzeRule(
|
|||||||
}
|
}
|
||||||
if (result == null) return null
|
if (result == null) return null
|
||||||
if (result is String) {
|
if (result is String) {
|
||||||
result = (result as String).split("\n")
|
result = result.split("\n")
|
||||||
}
|
}
|
||||||
if (isUrl) {
|
if (isUrl) {
|
||||||
val urlList = ArrayList<String>()
|
val urlList = ArrayList<String>()
|
||||||
if (result is List<*>) {
|
if (result is List<*>) {
|
||||||
for (url in result as List<*>) {
|
for (url in result) {
|
||||||
val absoluteURL = NetworkUtils.getAbsoluteURL(redirectUrl, url.toString())
|
val absoluteURL = NetworkUtils.getAbsoluteURL(redirectUrl, url.toString())
|
||||||
if (absoluteURL.isNotEmpty() && !urlList.contains(absoluteURL)) {
|
if (absoluteURL.isNotEmpty() && !urlList.contains(absoluteURL)) {
|
||||||
urlList.add(absoluteURL)
|
urlList.add(absoluteURL)
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|||||||
playIndexJob?.cancel()
|
playIndexJob?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playNext() {
|
private fun updateNextPos() {
|
||||||
readAloudNumber += contentList[nowSpeak].length + 1 - paragraphStartPos
|
readAloudNumber += contentList[nowSpeak].length + 1 - paragraphStartPos
|
||||||
paragraphStartPos = 0
|
paragraphStartPos = 0
|
||||||
if (nowSpeak < contentList.lastIndex) {
|
if (nowSpeak < contentList.lastIndex) {
|
||||||
@@ -355,14 +355,14 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|||||||
Player.STATE_ENDED -> {
|
Player.STATE_ENDED -> {
|
||||||
// 结束
|
// 结束
|
||||||
playErrorNo = 0
|
playErrorNo = 0
|
||||||
playNext()
|
updateNextPos()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||||
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED) return
|
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED) return
|
||||||
playNext()
|
updateNextPos()
|
||||||
upPlayPos()
|
upPlayPos()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,7 +375,8 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|||||||
AppLog.put("朗读连续5次错误, 最后一次错误代码(${error.localizedMessage})", error)
|
AppLog.put("朗读连续5次错误, 最后一次错误代码(${error.localizedMessage})", error)
|
||||||
ReadAloud.pause(this)
|
ReadAloud.pause(this)
|
||||||
} else {
|
} else {
|
||||||
playNext()
|
updateNextPos()
|
||||||
|
exoPlayer.seekToNextMediaItem()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,12 @@ abstract class BaseReadBookActivity :
|
|||||||
override val binding by viewBinding(ActivityBookReadBinding::inflate)
|
override val binding by viewBinding(ActivityBookReadBinding::inflate)
|
||||||
override val viewModel by viewModels<ReadBookViewModel>()
|
override val viewModel by viewModels<ReadBookViewModel>()
|
||||||
var bottomDialog = 0
|
var bottomDialog = 0
|
||||||
|
set(value) {
|
||||||
|
if (field != value) {
|
||||||
|
field = value
|
||||||
|
onBottomDialogChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
private val selectBookFolderResult = registerForActivityResult(HandleFileContract()) {
|
private val selectBookFolderResult = registerForActivityResult(HandleFileContract()) {
|
||||||
it.uri?.let { uri ->
|
it.uri?.let { uri ->
|
||||||
ReadBook.book?.let { book ->
|
ReadBook.book?.let { book ->
|
||||||
@@ -94,6 +100,21 @@ abstract class BaseReadBookActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onBottomDialogChange() {
|
||||||
|
when (bottomDialog) {
|
||||||
|
0 -> onMenuHide()
|
||||||
|
1 -> onMenuShow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun onMenuShow() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun onMenuHide() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
fun showPaddingConfig() {
|
fun showPaddingConfig() {
|
||||||
showDialogFragment<PaddingConfigDialog>()
|
showDialogFragment<PaddingConfigDialog>()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.content.Intent
|
|||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Looper
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.InputDevice
|
import android.view.InputDevice
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
@@ -76,7 +77,7 @@ import io.legado.app.ui.book.read.config.TipConfigDialog.Companion.TIP_DIVIDER_C
|
|||||||
import io.legado.app.ui.book.read.page.ContentTextView
|
import io.legado.app.ui.book.read.page.ContentTextView
|
||||||
import io.legado.app.ui.book.read.page.ReadView
|
import io.legado.app.ui.book.read.page.ReadView
|
||||||
import io.legado.app.ui.book.read.page.entities.PageDirection
|
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.read.page.provider.ChapterProvider
|
||||||
import io.legado.app.ui.book.searchContent.SearchContentActivity
|
import io.legado.app.ui.book.searchContent.SearchContentActivity
|
||||||
import io.legado.app.ui.book.searchContent.SearchResult
|
import io.legado.app.ui.book.searchContent.SearchResult
|
||||||
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
|
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
|
||||||
@@ -95,8 +96,8 @@ import io.legado.app.utils.ACache
|
|||||||
import io.legado.app.utils.Debounce
|
import io.legado.app.utils.Debounce
|
||||||
import io.legado.app.utils.LogUtils
|
import io.legado.app.utils.LogUtils
|
||||||
import io.legado.app.utils.StartActivityContract
|
import io.legado.app.utils.StartActivityContract
|
||||||
import io.legado.app.utils.SyncedRenderer
|
|
||||||
import io.legado.app.utils.applyOpenTint
|
import io.legado.app.utils.applyOpenTint
|
||||||
|
import io.legado.app.utils.buildMainHandler
|
||||||
import io.legado.app.utils.getPrefBoolean
|
import io.legado.app.utils.getPrefBoolean
|
||||||
import io.legado.app.utils.getPrefString
|
import io.legado.app.utils.getPrefString
|
||||||
import io.legado.app.utils.hexString
|
import io.legado.app.utils.hexString
|
||||||
@@ -191,7 +192,6 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
}
|
}
|
||||||
private var menu: Menu? = null
|
private var menu: Menu? = null
|
||||||
private var backupJob: Job? = null
|
private var backupJob: Job? = null
|
||||||
private var keepScreenJon: Job? = null
|
|
||||||
private var tts: TTS? = null
|
private var tts: TTS? = null
|
||||||
val textActionMenu: TextActionMenu by lazy {
|
val textActionMenu: TextActionMenu by lazy {
|
||||||
TextActionMenu(this, this)
|
TextActionMenu(this, this)
|
||||||
@@ -201,8 +201,7 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
}
|
}
|
||||||
override val isInitFinish: Boolean get() = viewModel.isInitFinish
|
override val isInitFinish: Boolean get() = viewModel.isInitFinish
|
||||||
override val isScroll: Boolean get() = binding.readView.isScroll
|
override val isScroll: Boolean get() = binding.readView.isScroll
|
||||||
override var autoPageProgress = 0
|
private val isAutoPage get() = binding.readView.isAutoPage
|
||||||
override var isAutoPage = false
|
|
||||||
override var isShowingSearchResult = false
|
override var isShowingSearchResult = false
|
||||||
override var isSelectingSearchResult = false
|
override var isSelectingSearchResult = false
|
||||||
set(value) {
|
set(value) {
|
||||||
@@ -211,16 +210,17 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
private val timeBatteryReceiver = TimeBatteryReceiver()
|
private val timeBatteryReceiver = TimeBatteryReceiver()
|
||||||
private var screenTimeOut: Long = 0
|
private var screenTimeOut: Long = 0
|
||||||
private var loadStates: Boolean = false
|
private var loadStates: Boolean = false
|
||||||
override val pageFactory: TextPageFactory get() = binding.readView.pageFactory
|
override val pageFactory get() = binding.readView.pageFactory
|
||||||
|
override val pageDelegate get() = binding.readView.pageDelegate
|
||||||
override val headerHeight: Int get() = binding.readView.curPage.headerHeight
|
override val headerHeight: Int get() = binding.readView.curPage.headerHeight
|
||||||
private val menuLayoutIsVisible get() = bottomDialog > 0 || binding.readMenu.isVisible
|
private val menuLayoutIsVisible get() = bottomDialog > 0 || binding.readMenu.isVisible
|
||||||
private val nextPageDebounce by lazy { Debounce { keyPage(PageDirection.NEXT) } }
|
private val nextPageDebounce by lazy { Debounce { keyPage(PageDirection.NEXT) } }
|
||||||
private val prevPageDebounce by lazy { Debounce { keyPage(PageDirection.PREV) } }
|
private val prevPageDebounce by lazy { Debounce { keyPage(PageDirection.PREV) } }
|
||||||
private var bookChanged = false
|
private var bookChanged = false
|
||||||
private var pageChanged = false
|
private var pageChanged = false
|
||||||
private var reloadContent = false
|
private val handler by lazy { buildMainHandler() }
|
||||||
private val autoPageRenderer by lazy { SyncedRenderer { doAutoPage(it) } }
|
private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } }
|
||||||
private var autoPageScrollOffset = 0.0
|
private val executor = ReadBook.executor
|
||||||
|
|
||||||
//恢复跳转前进度对话框的交互结果
|
//恢复跳转前进度对话框的交互结果
|
||||||
private var confirmRestoreProcess: Boolean? = null
|
private var confirmRestoreProcess: Boolean? = null
|
||||||
@@ -264,23 +264,9 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
|
|
||||||
override fun onPostCreate(savedInstanceState: Bundle?) {
|
override fun onPostCreate(savedInstanceState: Bundle?) {
|
||||||
super.onPostCreate(savedInstanceState)
|
super.onPostCreate(savedInstanceState)
|
||||||
viewModel.initData(intent) {
|
Looper.myQueue().addIdleHandler {
|
||||||
initDataSuccess()
|
viewModel.initData(intent) { upMenu() }
|
||||||
}
|
false
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent?) {
|
|
||||||
super.onNewIntent(intent)
|
|
||||||
viewModel.initData(intent ?: return) {
|
|
||||||
initDataSuccess()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initDataSuccess() {
|
|
||||||
upMenu()
|
|
||||||
if (reloadContent) {
|
|
||||||
reloadContent = false
|
|
||||||
ReadBook.loadContent(resetPageOffset = false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||