Compare commits

...

9 Commits

Author SHA1 Message Date
Xwite
4b9fa993ca Bump cronet from 123.0.6312.80 to 126.0.6478.122
- Changes in the [Git log](https://chromium.googlesource.com/chromium/src/+log/123.0.6312.80..126.0.6478.122)
2024-07-01 01:32:15 +00:00
Horis
7422db5fde 优化 2024-06-28 12:34:00 +08:00
Horis
77238c16e2 优化 2024-06-28 12:20:18 +08:00
Horis
c2b0c200fb 优化 2024-06-27 20:33:10 +08:00
Horis
636c369351 优化 2024-06-26 06:36:40 +08:00
Horis
b0f76445f8 优化 2024-06-25 23:07:23 +08:00
Horis
fc99b7050e 优化 2024-06-25 08:10:28 +08:00
Horis
4f179d9c60 优化 2024-06-25 08:00:57 +08:00
Horis
cb02949c0a 优化 2024-06-25 07:45:33 +08:00
27 changed files with 373 additions and 74 deletions

View File

@ -11,7 +11,7 @@ body:
required: true required: true
- label: 最新[测试版](https://github.com/gedoor/legado/actions/workflows/test.yml)依然存在此问题 / Latest beta app does not work - label: 最新[测试版](https://github.com/gedoor/legado/actions/workflows/test.yml)依然存在此问题 / Latest beta app does not work
required: true required: true
- label: 此问题和Xposed、Lsposed、Magisk、手机主题、浏览器插件等无关 / Make sure your machine is not touched by hook frameworks, plugins etc - label: 此问题和Xposed、Lsposed、Magisk、手机主题、浏览器插件、无障碍服务等无关 / Make sure your machine is not touched by hook frameworks, plugins, accessibility etc
required: true required: true
- type: textarea - type: textarea

View File

@ -1,20 +1,19 @@
# -------- Config Path: base/android/proguard/chromium_code.flags -------- # -------- Config Path: base/android/proguard/shared_with_cronet.flags --------
# Copyright 2016 The Chromium Authors # Copyright 2016 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
# Contains flags that can be safely shared with Cronet, and thus would be # Contains flags that we want to apply not only to Chromium APKs, but also to
# appropriate for third-party apps to include. # third-party apps that bundle the Cronet library.
# Allow unused native methods to be removed, but prevent renaming on those that are kept. # WARNING: rules in this file are applied to entire third-party APKs, not just
-keepclasseswithmembernames,includedescriptorclasses,allowaccessmodification class !cr_allowunused,** { # Chromium code. They MUST be scoped appropriately to avoid side effects on app
native <methods>; # code that we do not own.
}
# Use assumevalues block instead of assumenosideeffects block because Google3 proguard cannot parse # Use assumevalues block instead of assumenosideeffects block because Google3
# assumenosideeffects blocks which overwrite return value. # proguard cannot parse assumenosideeffects blocks which overwrite return
# chromium_code.flags rather than remove_logging.flags so that it's included # value. Keep this in shared_with_cronet.flags rather than remove_logging.flags
# in cronet. # so that it's included in cronet.
-assumevalues class org.chromium.base.Log { -assumevalues class org.chromium.base.Log {
static boolean isDebug() return false; static boolean isDebug() return false;
} }
@ -37,21 +36,10 @@
public static **[] values(); public static **[] values();
} }
# -identifiernamestring doesn't keep the module impl around, we have to
# explicitly keep it.
-if @org.chromium.components.module_installer.builder.ModuleInterface interface *
-keep,allowobfuscation,allowaccessmodification class !cr_allowunused,** extends <1> {
<init>();
}
# Required to remove fields until b/274802355 is resolved. # Required to remove fields until b/274802355 is resolved.
-assumevalues class !cr_allowunused,** { -assumevalues class !cr_allowunused,** {
final org.chromium.base.ThreadUtils$ThreadChecker * return _NONNULL_; final org.chromium.base.ThreadUtils$ThreadChecker * return _NONNULL_;
} }
# TODO(agrieve): Remove once we start to use Android U SDK.
-dontwarn android.window.BackEvent
-dontwarn android.window.OnBackAnimationCallback
# -------- Config Path: build/android/chromium_annotations.flags -------- # -------- Config Path: build/android/chromium_annotations.flags --------
# Copyright 2022 The Chromium Authors # Copyright 2022 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
@ -113,6 +101,22 @@
-identifiernamestring class * { -identifiernamestring class * {
@org.chromium.build.annotations.IdentifierNameString *; @org.chromium.build.annotations.IdentifierNameString *;
} }
# Mark fields with this to help R8 figure out that they cannot be null.
# Use assumevalues in addition to assumenosideeffects block because Google3 proguard cannot parse
# assumenosideeffects blocks which overwrite return value.
-assumevalues class ** {
@org.chromium.build.annotations.AssumeNonNull *** *(...) return _NONNULL_;
}
-assumenosideeffects class ** {
@org.chromium.build.annotations.AssumeNonNull *** *(...);
}
-assumevalues class ** {
@org.chromium.build.annotations.AssumeNonNull *** * return _NONNULL_;
}
-assumenosideeffects class ** {
@org.chromium.build.annotations.AssumeNonNull *** *;
}
# -------- Config Path: components/cronet/android/cronet_impl_common_proguard.cfg -------- # -------- Config Path: components/cronet/android/cronet_impl_common_proguard.cfg --------
# Proguard config for apps that depend on cronet_impl_common_java.jar. # Proguard config for apps that depend on cronet_impl_common_java.jar.
@ -227,3 +231,10 @@
-keepclasseswithmembers,includedescriptorclasses,allowaccessmodification class ** { -keepclasseswithmembers,includedescriptorclasses,allowaccessmodification class ** {
@org.jni_zero.CalledByNativeUnchecked <methods>; @org.jni_zero.CalledByNativeUnchecked <methods>;
} }
# Allow unused native methods to be removed, but prevent renaming on those that
# are kept.
# TODO(crbug.com/315973491): Restrict the broad scope of this rule.
-keepclasseswithmembernames,includedescriptorclasses,allowaccessmodification class ** {
native <methods>;
}

Binary file not shown.

Binary file not shown.

View File

@ -1 +1 @@
{"arm64-v8a":"bbf2c50d1ebf0763d451b08e290a3244","armeabi-v7a":"ecb872f7b1b5342f4d7c36262bec0600","x86":"4da4832b89e2412d808c5b1ecdc24e3d","x86_64":"62080f051db02ed0e939affb39ce67fb","version":"123.0.6312.80"} {"x86_64":"cefc6b03fcd3e0a386fd309ba197a51d","arm64-v8a":"341eb99fcd8246a4a02e46c45b07b155","armeabi-v7a":"adc8d602ef0a89da206d48f3b6ccc9b5","x86":"2277832e16f522b2f589eecc0547865e","version":"126.0.6478.122"}

View File

@ -13,6 +13,7 @@
* 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源! * 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源!
**2024/02/27** **2024/02/27**
* 更新cronet: 126.0.6478.122
* 更新cronet: 123.0.6312.80 * 更新cronet: 123.0.6312.80
* 更新cronet: 123.0.6312.40 * 更新cronet: 123.0.6312.40

View File

@ -119,9 +119,9 @@ http代理
{ {
"proxy":"http://127.0.0.1:1080" "proxy":"http://127.0.0.1:1080"
} }
支持代理服务器验证 支持http代理服务器验证
{ {
"proxy":"socks5://127.0.0.1:1080@用户名@密码" "proxy":"http://127.0.0.1:1080@用户名@密码"
} }
注意:这些请求头是无意义的,会被忽略掉 注意:这些请求头是无意义的,会被忽略掉
``` ```
@ -143,9 +143,9 @@ https://www.baidu.com,{"js":"java.url=java.url+'yyyy'"}
"method":"POST", "method":"POST",
"body":"show=title&tempid=1&keyboard="+key "body":"show=title&tempid=1&keyboard="+key
}); });
return java.put('surl',String(java.connect(url).raw().request().url())); return source.put('surl',String(java.connect(url).raw().request().url()));
} else { } else {
return java.get('surl')+'&page='+(page-1) return source.get('surl')+'&page='+(page-1)
} }
})() })()
或者 或者
@ -154,9 +154,9 @@ https://www.baidu.com,{"js":"java.url=java.url+'yyyy'"}
if(page==1){ if(page==1){
let url=base+'index.php'; let url=base+'index.php';
let body='show=title&tempid=1&keyboard='+key; let body='show=title&tempid=1&keyboard='+key;
return base+java.put('surl',java.post(url,body,{}).header("Location")); return base+source.put('surl',java.post(url,body,{}).header("Location"));
} else { } else {
return base+java.get('surl')+'&page='+(page-1); return base+source.get('surl')+'&page='+(page-1);
} }
})() })()
``` ```

View File

@ -36,7 +36,9 @@ import io.legado.app.utils.ChineseUtils
import io.legado.app.utils.LogUtils import io.legado.app.utils.LogUtils
import io.legado.app.utils.defaultSharedPreferences import io.legado.app.utils.defaultSharedPreferences
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.isDebuggable
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.chromium.base.ThreadUtils
import splitties.init.appCtx import splitties.init.appCtx
import splitties.systemservices.notificationManager import splitties.systemservices.notificationManager
import java.net.URL import java.net.URL
@ -51,6 +53,9 @@ class App : Application() {
super.onCreate() super.onCreate()
LogUtils.d("App", "onCreate") LogUtils.d("App", "onCreate")
LogUtils.logDeviceInfo() LogUtils.logDeviceInfo()
if (isDebuggable) {
ThreadUtils.setThreadAssertsDisabledForTesting(true)
}
oldConfig = Configuration(resources.configuration) oldConfig = Configuration(resources.configuration)
CrashHandler(this) CrashHandler(this)
//预下载Cronet so //预下载Cronet so

View File

@ -7,12 +7,51 @@ import androidx.room.Database
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import io.legado.app.data.dao.* import io.legado.app.data.dao.BookChapterDao
import io.legado.app.data.entities.* import io.legado.app.data.dao.BookDao
import io.legado.app.data.dao.BookGroupDao
import io.legado.app.data.dao.BookSourceDao
import io.legado.app.data.dao.BookmarkDao
import io.legado.app.data.dao.CacheDao
import io.legado.app.data.dao.CookieDao
import io.legado.app.data.dao.DictRuleDao
import io.legado.app.data.dao.HttpTTSDao
import io.legado.app.data.dao.KeyboardAssistsDao
import io.legado.app.data.dao.ReadRecordDao
import io.legado.app.data.dao.ReplaceRuleDao
import io.legado.app.data.dao.RssArticleDao
import io.legado.app.data.dao.RssSourceDao
import io.legado.app.data.dao.RssStarDao
import io.legado.app.data.dao.RuleSubDao
import io.legado.app.data.dao.SearchBookDao
import io.legado.app.data.dao.SearchKeywordDao
import io.legado.app.data.dao.ServerDao
import io.legado.app.data.dao.TxtTocRuleDao
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookGroup
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.Bookmark
import io.legado.app.data.entities.Cache
import io.legado.app.data.entities.Cookie
import io.legado.app.data.entities.DictRule
import io.legado.app.data.entities.HttpTTS
import io.legado.app.data.entities.KeyboardAssist
import io.legado.app.data.entities.ReadRecord
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.data.entities.RssArticle
import io.legado.app.data.entities.RssReadRecord
import io.legado.app.data.entities.RssSource
import io.legado.app.data.entities.RssStar
import io.legado.app.data.entities.RuleSub
import io.legado.app.data.entities.SearchBook
import io.legado.app.data.entities.SearchKeyword
import io.legado.app.data.entities.Server
import io.legado.app.data.entities.TxtTocRule
import io.legado.app.help.DefaultData import io.legado.app.help.DefaultData
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
import splitties.init.appCtx import splitties.init.appCtx
import java.util.* import java.util.Locale
val appDb by lazy { val appDb by lazy {
Room.databaseBuilder(appCtx, AppDatabase::class.java, AppDatabase.DATABASE_NAME) Room.databaseBuilder(appCtx, AppDatabase::class.java, AppDatabase.DATABASE_NAME)
@ -89,6 +128,10 @@ abstract class AppDatabase : RoomDatabase() {
const val DATABASE_NAME = "legado.db" const val DATABASE_NAME = "legado.db"
const val BOOK_TABLE_NAME = "books"
const val BOOK_SOURCE_TABLE_NAME = "book_sources"
const val RSS_SOURCE_TABLE_NAME = "rssSources"
val dbCallback = object : Callback() { val dbCallback = object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) { override fun onCreate(db: SupportSQLiteDatabase) {

View File

@ -88,7 +88,7 @@ interface RssSourceDao {
fun flowGroupsUnProcessed(): Flow<List<String>> fun flowGroupsUnProcessed(): Flow<List<String>>
@Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> '' and enabled = 1") @Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> '' and enabled = 1")
fun flowGroupEnabled(): Flow<List<String>> fun flowEnabledGroupsUnProcessed(): Flow<List<String>>
@get:Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> ''") @get:Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> ''")
val allGroupsUnProcessed: List<String> val allGroupsUnProcessed: List<String>
@ -142,4 +142,11 @@ interface RssSourceDao {
dealGroups(list) dealGroups(list)
}.flowOn(IO) }.flowOn(IO)
} }
fun flowEnabledGroups(): Flow<List<String>> {
return flowEnabledGroupsUnProcessed().map { list ->
dealGroups(list)
}.flowOn(IO)
}
} }

View File

@ -10,6 +10,7 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.help.book.BookHelp import io.legado.app.help.book.BookHelp
import io.legado.app.utils.FileUtils import io.legado.app.utils.FileUtils
import io.legado.app.utils.HtmlFormatter import io.legado.app.utils.HtmlFormatter
import io.legado.app.utils.encodeURI
import io.legado.app.utils.isXml import io.legado.app.utils.isXml
import io.legado.app.utils.printOnDebug import io.legado.app.utils.printOnDebug
import me.ag2s.epublib.domain.EpubBook import me.ag2s.epublib.domain.EpubBook
@ -252,8 +253,10 @@ class EpubFile(var book: Book) {
} }
} }
bodyElement.select("img").forEach { bodyElement.select("img").forEach {
val src = it.attr("src") val src = it.attr("src").encodeURI()
it.attr("src", URI(res.href).resolve(src).toString()) val href = res.href.encodeURI()
val resolvedHref = URLDecoder.decode(URI(href).resolve(src).toString(), "UTF-8")
it.attr("src", resolvedHref)
} }
return bodyElement return bodyElement
} }

View File

@ -7,7 +7,6 @@ import android.view.MenuItem
import android.view.View import android.view.View
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
@ -17,6 +16,7 @@ import io.legado.app.constant.AppConst.charsets
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.constant.IntentAction import io.legado.app.constant.IntentAction
import io.legado.app.data.AppDatabase
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
@ -41,6 +41,7 @@ import io.legado.app.utils.applyTint
import io.legado.app.utils.checkWrite import io.legado.app.utils.checkWrite
import io.legado.app.utils.cnCompare import io.legado.app.utils.cnCompare
import io.legado.app.utils.enableCustomExport import io.legado.app.utils.enableCustomExport
import io.legado.app.utils.flowWithLifecycleAndDatabaseChange
import io.legado.app.utils.isContentScheme import io.legado.app.utils.isContentScheme
import io.legado.app.utils.observeEvent import io.legado.app.utils.observeEvent
import io.legado.app.utils.parseToUri import io.legado.app.utils.parseToUri
@ -221,7 +222,9 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
else -> booksDownload.sortedByDescending { it.durChapterTime } else -> booksDownload.sortedByDescending { it.durChapterTime }
} }
}.flowWithLifecycle(lifecycle).catch { }.flowWithLifecycleAndDatabaseChange(
lifecycle, table = AppDatabase.BOOK_TABLE_NAME
).catch {
AppLog.put("缓存管理界面获取书籍列表失败\n${it.localizedMessage}", it) AppLog.put("缓存管理界面获取书籍列表失败\n${it.localizedMessage}", it)
}.flowOn(IO).conflate().collect { books -> }.flowOn(IO).conflate().collect { books ->
adapter.setItems(books) adapter.setItems(books)

View File

@ -5,9 +5,10 @@ import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.adapter.DiffRecyclerAdapter
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.databinding.ItemDownloadBinding import io.legado.app.databinding.ItemDownloadBinding
import io.legado.app.help.book.isLocal import io.legado.app.help.book.isLocal
@ -16,7 +17,20 @@ import io.legado.app.utils.gone
import io.legado.app.utils.visible import io.legado.app.utils.visible
class CacheAdapter(context: Context, private val callBack: CallBack) : class CacheAdapter(context: Context, private val callBack: CallBack) :
RecyclerAdapter<Book, ItemDownloadBinding>(context) { DiffRecyclerAdapter<Book, ItemDownloadBinding>(context) {
override val diffItemCallback: DiffUtil.ItemCallback<Book>
get() = object : DiffUtil.ItemCallback<Book>() {
override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean {
return oldItem.bookUrl == newItem.bookUrl
}
override fun areContentsTheSame(oldItem: Book, newItem: Book): Boolean {
return oldItem.name == newItem.name
&& oldItem.author == newItem.author
}
}
override fun getViewBinding(parent: ViewGroup): ItemDownloadBinding { override fun getViewBinding(parent: ViewGroup): ItemDownloadBinding {
return ItemDownloadBinding.inflate(inflater, parent, false) return ItemDownloadBinding.inflate(inflater, parent, false)

View File

@ -16,7 +16,6 @@ import androidx.core.os.bundleOf
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
@ -25,6 +24,7 @@ import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.data.AppDatabase
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.BookSourcePart import io.legado.app.data.entities.BookSourcePart
import io.legado.app.databinding.ActivityBookSourceBinding import io.legado.app.databinding.ActivityBookSourceBinding
@ -52,7 +52,8 @@ import io.legado.app.utils.ACache
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
import io.legado.app.utils.cnCompare import io.legado.app.utils.cnCompare
import io.legado.app.utils.dpToPx import io.legado.app.utils.dpToPx
import io.legado.app.utils.flowWithLifecycleFirst import io.legado.app.utils.flowWithLifecycleAndDatabaseChange
import io.legado.app.utils.flowWithLifecycleAndDatabaseChangeFirst
import io.legado.app.utils.hideSoftInput import io.legado.app.utils.hideSoftInput
import io.legado.app.utils.isAbsUrl import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.launch import io.legado.app.utils.launch
@ -376,7 +377,10 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
else -> data.reversed() else -> data.reversed()
} }
} }
}.flowWithLifecycle(lifecycle).catch { }.flowWithLifecycleAndDatabaseChange(
lifecycle,
table = AppDatabase.BOOK_SOURCE_TABLE_NAME
).catch {
AppLog.put("书源界面更新书源出错", it) AppLog.put("书源界面更新书源出错", it)
}.flowOn(IO).conflate().collect { data -> }.flowOn(IO).conflate().collect { data ->
adapter.setItems(data, adapter.diffItemCallback, !Debug.isChecking) adapter.setItems(data, adapter.diffItemCallback, !Debug.isChecking)
@ -389,8 +393,14 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
private fun initLiveDataGroup() { private fun initLiveDataGroup() {
lifecycleScope.launch { lifecycleScope.launch {
appDb.bookSourceDao.flowGroups() appDb.bookSourceDao.flowGroups()
.flowWithLifecycle(lifecycle) .flowWithLifecycleAndDatabaseChange(
.flowWithLifecycleFirst(groupMenuLifecycleOwner.lifecycle) lifecycle,
table = AppDatabase.BOOK_SOURCE_TABLE_NAME
)
.flowWithLifecycleAndDatabaseChangeFirst(
groupMenuLifecycleOwner.lifecycle,
table = AppDatabase.BOOK_SOURCE_TABLE_NAME
)
.conflate() .conflate()
.distinctUntilChanged() .distinctUntilChanged()
.collect { .collect {

View File

@ -217,7 +217,7 @@ class BackupConfigFragment : PreferenceFragment(),
} }
PreferKey.webDavPassword -> PreferKey.webDavPassword ->
if (value.isNullOrBlank()) { if (value.isNullOrEmpty()) {
preference.summary = getString(R.string.web_dav_pw_s) preference.summary = getString(R.string.web_dav_pw_s)
} else { } else {
preference.summary = "*".repeat(value.toString().length) preference.summary = "*".repeat(value.toString().length)

View File

@ -15,6 +15,7 @@ import io.legado.app.R
import io.legado.app.base.BaseFragment import io.legado.app.base.BaseFragment
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.data.AppDatabase
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookGroup import io.legado.app.data.entities.BookGroup
@ -28,7 +29,7 @@ import io.legado.app.ui.book.info.BookInfoActivity
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.main.MainViewModel import io.legado.app.ui.main.MainViewModel
import io.legado.app.utils.cnCompare import io.legado.app.utils.cnCompare
import io.legado.app.utils.flowWithLifecycleFirst import io.legado.app.utils.flowWithLifecycleAndDatabaseChangeFirst
import io.legado.app.utils.observeEvent import io.legado.app.utils.observeEvent
import io.legado.app.utils.setEdgeEffectColor import io.legado.app.utils.setEdgeEffectColor
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
@ -175,7 +176,11 @@ class BooksFragment() : BaseFragment(R.layout.fragment_books),
else -> list.sortedByDescending { it.durChapterTime } else -> list.sortedByDescending { it.durChapterTime }
} }
}.flowWithLifecycleFirst(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED).catch { }.flowWithLifecycleAndDatabaseChangeFirst(
viewLifecycleOwner.lifecycle,
Lifecycle.State.RESUMED,
AppDatabase.BOOK_TABLE_NAME
).catch {
AppLog.put("书架更新出错", it) AppLog.put("书架更新出错", it)
}.conflate().flowOn(Dispatchers.Default).collect { list -> }.conflate().flowOn(Dispatchers.Default).collect { list ->
binding.tvEmptyMsg.isGone = list.isNotEmpty() binding.tvEmptyMsg.isGone = list.isNotEmpty()

View File

@ -9,13 +9,13 @@ import androidx.appcompat.widget.SearchView
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseFragment import io.legado.app.base.VMBaseFragment
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
import io.legado.app.data.AppDatabase
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.BookSourcePart import io.legado.app.data.entities.BookSourcePart
import io.legado.app.databinding.FragmentExploreBinding import io.legado.app.databinding.FragmentExploreBinding
@ -30,6 +30,7 @@ import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.main.MainFragmentInterface import io.legado.app.ui.main.MainFragmentInterface
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
import io.legado.app.utils.cnCompare import io.legado.app.utils.cnCompare
import io.legado.app.utils.flowWithLifecycleAndDatabaseChange
import io.legado.app.utils.setEdgeEffectColor import io.legado.app.utils.setEdgeEffectColor
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
@ -39,6 +40,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -126,11 +128,20 @@ class ExploreFragment() : VMBaseFragment<ExploreViewModel>(R.layout.fragment_exp
private fun initGroupData() { private fun initGroupData() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
appDb.bookSourceDao.flowExploreGroups().conflate().collect { appDb.bookSourceDao.flowExploreGroups()
groups.clear() .flowWithLifecycleAndDatabaseChange(
groups.addAll(it) viewLifecycleOwner.lifecycle,
upGroupsMenu() Lifecycle.State.RESUMED,
} AppDatabase.BOOK_SOURCE_TABLE_NAME
)
.conflate()
.distinctUntilChanged()
.collect {
groups.clear()
groups.addAll(it)
upGroupsMenu()
delay(500)
}
} }
} }
@ -150,7 +161,11 @@ class ExploreFragment() : VMBaseFragment<ExploreViewModel>(R.layout.fragment_exp
else -> { else -> {
appDb.bookSourceDao.flowExplore(searchKey) appDb.bookSourceDao.flowExplore(searchKey)
} }
}.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED).catch { }.flowWithLifecycleAndDatabaseChange(
viewLifecycleOwner.lifecycle,
Lifecycle.State.RESUMED,
AppDatabase.BOOK_SOURCE_TABLE_NAME
).catch {
AppLog.put("发现界面更新数据出错", it) AppLog.put("发现界面更新数据出错", it)
}.conflate().flowOn(IO).collect { }.conflate().flowOn(IO).collect {
binding.tvEmptyMsg.isGone = it.isNotEmpty() || searchView.query.isNotEmpty() binding.tvEmptyMsg.isGone = it.isNotEmpty() || searchView.query.isNotEmpty()

View File

@ -8,12 +8,11 @@ import android.view.View
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseFragment import io.legado.app.base.VMBaseFragment
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
import io.legado.app.constant.AppPattern import io.legado.app.data.AppDatabase
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.RssSource import io.legado.app.data.entities.RssSource
import io.legado.app.databinding.FragmentRssBinding import io.legado.app.databinding.FragmentRssBinding
@ -30,13 +29,14 @@ import io.legado.app.ui.rss.source.manage.RssSourceActivity
import io.legado.app.ui.rss.subscription.RuleSubActivity import io.legado.app.ui.rss.subscription.RuleSubActivity
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
import io.legado.app.utils.cnCompare import io.legado.app.utils.cnCompare
import io.legado.app.utils.flowWithLifecycleAndDatabaseChange
import io.legado.app.utils.openUrl import io.legado.app.utils.openUrl
import io.legado.app.utils.setEdgeEffectColor import io.legado.app.utils.setEdgeEffectColor
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
@ -145,16 +145,17 @@ class RssFragment() : VMBaseFragment<RssViewModel>(R.layout.fragment_rss),
private fun initGroupData() { private fun initGroupData() {
groupsFlowJob?.cancel() groupsFlowJob?.cancel()
groupsFlowJob = viewLifecycleOwner.lifecycleScope.launch { groupsFlowJob = viewLifecycleOwner.lifecycleScope.launch {
appDb.rssSourceDao.flowGroupEnabled().catch { appDb.rssSourceDao.flowEnabledGroups().catch {
AppLog.put("订阅界面获取分组数据失败\n${it.localizedMessage}", it) AppLog.put("订阅界面获取分组数据失败\n${it.localizedMessage}", it)
}.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED) }.flowWithLifecycleAndDatabaseChange(
.flowOn(IO).conflate().collect { viewLifecycleOwner.lifecycle,
groups.clear() Lifecycle.State.RESUMED,
it.map { group -> AppDatabase.RSS_SOURCE_TABLE_NAME
groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex)) ).conflate().collect {
} groups.clear()
upGroupsMenu() groups.addAll(it)
} upGroupsMenu()
}
} }
} }
@ -169,7 +170,11 @@ class RssFragment() : VMBaseFragment<RssViewModel>(R.layout.fragment_rss),
} }
else -> appDb.rssSourceDao.flowEnabled(searchKey) else -> appDb.rssSourceDao.flowEnabled(searchKey)
}.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED).catch { }.flowWithLifecycleAndDatabaseChange(
viewLifecycleOwner.lifecycle,
Lifecycle.State.RESUMED,
AppDatabase.RSS_SOURCE_TABLE_NAME
).catch {
AppLog.put("订阅界面更新数据出错", it) AppLog.put("订阅界面更新数据出错", it)
}.flowOn(IO).collect { }.flowOn(IO).collect {
adapter.setItems(it) adapter.setItems(it)

View File

@ -17,6 +17,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.content.res.Configuration import android.content.res.Configuration
@ -384,3 +385,6 @@ val Context.channel: String
} }
return "" return ""
} }
val Context.isDebuggable: Boolean
get() = applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0

View File

@ -2,6 +2,8 @@ package io.legado.app.utils
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.room.invalidationTrackerFlow
import io.legado.app.data.appDb
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.ensureActive import kotlinx.coroutines.ensureActive
@ -10,12 +12,14 @@ import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapMerge import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.produceIn
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.Semaphore
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
@ -204,3 +208,40 @@ fun <T> Flow<T>.flowWithLifecycleFirst(
} }
close() close()
} }
fun <T> Flow<T>.flowWithLifecycleAndDatabaseChange(
lifecycle: Lifecycle,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
table: String
): Flow<T> = callbackFlow {
val channel = appDb.invalidationTrackerFlow(table)
.conflate()
.produceIn(this)
lifecycle.repeatOnLifecycle(minActiveState) {
channel.receive()
this@flowWithLifecycleAndDatabaseChange.collect {
send(it)
}
}
close()
}
fun <T> Flow<T>.flowWithLifecycleAndDatabaseChangeFirst(
lifecycle: Lifecycle,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
table: String
): Flow<T> = callbackFlow {
if (!lifecycle.currentState.isAtLeast(minActiveState)) {
send(first())
}
val channel = appDb.invalidationTrackerFlow(table)
.conflate()
.produceIn(this)
lifecycle.repeatOnLifecycle(minActiveState) {
channel.receive()
this@flowWithLifecycleAndDatabaseChangeFirst.collect {
send(it)
}
}
close()
}

View File

@ -7,12 +7,13 @@ import android.icu.text.Collator
import android.icu.util.ULocale import android.icu.util.ULocale
import android.net.Uri import android.net.Uri
import android.text.Editable import android.text.Editable
import cn.hutool.core.net.URLEncodeUtil
import io.legado.app.constant.AppPattern import io.legado.app.constant.AppPattern
import io.legado.app.constant.AppPattern.dataUriRegex import io.legado.app.constant.AppPattern.dataUriRegex
import java.io.File import java.io.File
import java.lang.Character.codePointCount import java.lang.Character.codePointCount
import java.lang.Character.offsetByCodePoints import java.lang.Character.offsetByCodePoints
import java.util.* import java.util.Locale
import java.util.regex.Pattern import java.util.regex.Pattern
fun String?.safeTrim() = if (this.isNullOrBlank()) null else this.trim() fun String?.safeTrim() = if (this.isNullOrBlank()) null else this.trim()
@ -132,3 +133,4 @@ fun String.escapeRegex(): String {
return replace(AppPattern.regexCharRegex, "\\\\$0") return replace(AppPattern.regexCharRegex, "\\\\$0")
} }
fun String.encodeURI(): String = URLEncodeUtil.encodeQuery(this)

View File

@ -42,7 +42,7 @@ android.defaults.buildfeatures.shaders=false
# and none from the library's dependencies, thereby reducing the size of the R class for that library. # and none from the library's dependencies, thereby reducing the size of the R class for that library.
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
# https://chromiumdash.appspot.com/releases?platform=Android # https://chromiumdash.appspot.com/releases?platform=Android
CronetVersion=123.0.6312.80 CronetVersion=126.0.6478.122
CronetMainVersion=123.0.0.0 CronetMainVersion=126.0.0.0
android.injected.testOnly=false android.injected.testOnly=false
android.nonFinalResIds=true android.nonFinalResIds=true

View File

@ -35,6 +35,7 @@ import me.ag2s.epublib.domain.Spine;
import me.ag2s.epublib.domain.SpineReference; import me.ag2s.epublib.domain.SpineReference;
import me.ag2s.epublib.util.ResourceUtil; import me.ag2s.epublib.util.ResourceUtil;
import me.ag2s.epublib.util.StringUtil; import me.ag2s.epublib.util.StringUtil;
import me.ag2s.epublib.util.URLEncodeUtil;
/** /**
* Reads the opf package document as defined by namespace http://www.idpf.org/2007/opf * Reads the opf package document as defined by namespace http://www.idpf.org/2007/opf
@ -108,6 +109,7 @@ public class PackageDocumentReader extends PackageDocumentBase {
for (int i = 0; i < originItemElements.getLength(); i++) { for (int i = 0; i < originItemElements.getLength(); i++) {
Element itemElement = (Element) originItemElements.item(i).cloneNode(false); Element itemElement = (Element) originItemElements.item(i).cloneNode(false);
String href = DOMUtil.getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.href); String href = DOMUtil.getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.href);
href = URLEncodeUtil.encode(href);
String resolvedHref = packagePath.resolve(href).toString(); String resolvedHref = packagePath.resolve(href).toString();
itemElement.setAttribute("href", resolvedHref); itemElement.setAttribute("href", resolvedHref);
fixedElements.add(itemElement); fixedElements.add(itemElement);
@ -451,9 +453,9 @@ public class PackageDocumentReader extends PackageDocumentBase {
OPFTags.item, OPFAttributes.id, coverResourceId, OPFTags.item, OPFAttributes.id, coverResourceId,
OPFAttributes.href); OPFAttributes.href);
if (StringUtil.isNotBlank(coverHref)) { if (StringUtil.isNotBlank(coverHref)) {
result.add(packagePath.resolve(coverHref).toString()); result.add(resolvePath(packagePath, coverHref));
} else { } else {
String resolved = packagePath.resolve(coverResourceId).toString(); String resolved = resolvePath(packagePath, coverResourceId);
result.add( result.add(
resolved); // maybe there was a cover href put in the cover id attribute resolved); // maybe there was a cover href put in the cover id attribute
} }
@ -464,11 +466,21 @@ public class PackageDocumentReader extends PackageDocumentBase {
OPFTags.reference, OPFAttributes.type, OPFValues.reference_cover, OPFTags.reference, OPFAttributes.type, OPFValues.reference_cover,
OPFAttributes.href); OPFAttributes.href);
if (StringUtil.isNotBlank(coverHref)) { if (StringUtil.isNotBlank(coverHref)) {
result.add(packagePath.resolve(coverHref).toString()); result.add(resolvePath(packagePath, coverHref));
} }
return result; return result;
} }
private static String resolvePath(URI parentPath, String href) {
href = URLEncodeUtil.encode(href);
String resolved = parentPath.resolve(href).toString();
try {
return URLDecoder.decode(resolved, Constants.CHARACTER_ENCODING);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
/** /**
* Finds the cover resource in the packageDocument and adds it to the book if found. * Finds the cover resource in the packageDocument and adds it to the book if found.
* Keeps the cover resource in the resources map * Keeps the cover resource in the resources map

View File

@ -0,0 +1,118 @@
package me.ag2s.epublib.util;
import java.io.CharArrayWriter;
import java.nio.charset.Charset;
import java.util.BitSet;
import java.util.Objects;
import kotlin.text.Charsets;
public class URLEncodeUtil {
static BitSet dontNeedEncoding;
static final int caseDiff = ('a' - 'A');
static {
dontNeedEncoding = new BitSet(256);
int i;
for (i = 'a'; i <= 'z'; i++) {
dontNeedEncoding.set(i);
}
for (i = 'A'; i <= 'Z'; i++) {
dontNeedEncoding.set(i);
}
for (i = '0'; i <= '9'; i++) {
dontNeedEncoding.set(i);
}
String mark = "-_.!~*'()";
for (i = 0; i < mark.length(); i++) {
dontNeedEncoding.set(mark.charAt(i));
}
String reversed = ";/?:@&=+$,#";
for (i = 0; i < reversed.length(); i++) {
dontNeedEncoding.set(reversed.charAt(i));
}
}
public static String encode(String s, Charset charset) {
Objects.requireNonNull(charset, "charset");
boolean needToChange = false;
StringBuilder out = new StringBuilder(s.length());
CharArrayWriter charArrayWriter = new CharArrayWriter();
for (int i = 0; i < s.length(); ) {
int c = s.charAt(i);
//System.out.println("Examining character: " + c);
if (dontNeedEncoding.get(c)) {
//System.out.println("Storing: " + c);
out.append((char) c);
i++;
} else {
// convert to external encoding before hex conversion
do {
charArrayWriter.write(c);
/*
* If this character represents the start of a Unicode
* surrogate pair, then pass in two characters. It's not
* clear what should be done if a byte reserved in the
* surrogate pairs range occurs outside of a legal
* surrogate pair. For now, just treat it as if it were
* any other character.
*/
if (c >= 0xD800 && c <= 0xDBFF) {
/*
System.out.println(Integer.toHexString(c)
+ " is high surrogate");
*/
if ((i + 1) < s.length()) {
int d = (int) s.charAt(i + 1);
/*
System.out.println("\tExamining "
+ Integer.toHexString(d));
*/
if (d >= 0xDC00 && d <= 0xDFFF) {
/*
System.out.println("\t"
+ Integer.toHexString(d)
+ " is low surrogate");
*/
charArrayWriter.write(d);
i++;
}
}
}
i++;
} while (i < s.length() && !dontNeedEncoding.get((c = (int) s.charAt(i))));
charArrayWriter.flush();
String str = new String(charArrayWriter.toCharArray());
byte[] ba = str.getBytes(charset);
for (int j = 0; j < ba.length; j++) {
out.append('%');
char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16);
// converting to use uppercase letter as part of
// the hex value if ch is a letter.
if (Character.isLetter(ch)) {
ch -= caseDiff;
}
out.append(ch);
ch = Character.forDigit(ba[j] & 0xF, 16);
if (Character.isLetter(ch)) {
ch -= caseDiff;
}
out.append(ch);
}
charArrayWriter.reset();
needToChange = true;
}
}
return (needToChange ? out.toString() : s);
}
public static String encode(String s) {
return encode(s, Charsets.UTF_8);
}
}