添加LibArchive解压

This commit is contained in:
ag2s20150909 2023-09-14 16:28:40 +08:00
parent 4db0b50230
commit d7a79dde22
23 changed files with 554 additions and 36 deletions

View File

@ -250,6 +250,9 @@ dependencies {
//
implementation('com.jaredrummler:colorpicker:1.1.0')
//
implementation 'me.zhanghai.android.libarchive:library:1.0.1'
//apache
implementation('org.apache.commons:commons-text:1.10.0')
//noinspection GradleDependency,1.23.0

View File

@ -89,6 +89,7 @@ object PreferKey {
const val syncBookProgress = "syncBookProgress"
const val cronet = "Cronet"
const val antiAlias = "antiAlias"
const val useLibArchive = "useLibArchive"
const val bitmapCacheSize = "bitmapCacheSize"
const val preDownloadNum = "preDownloadNum"
const val autoRefresh = "auto_refresh"

View File

@ -7,13 +7,25 @@ import io.legado.app.BuildConfig
import io.legado.app.constant.AppConst
import io.legado.app.constant.PreferKey
import io.legado.app.data.appDb
import io.legado.app.utils.*
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefInt
import io.legado.app.utils.getPrefLong
import io.legado.app.utils.getPrefString
import io.legado.app.utils.isNightMode
import io.legado.app.utils.putPrefBoolean
import io.legado.app.utils.putPrefInt
import io.legado.app.utils.putPrefLong
import io.legado.app.utils.putPrefString
import io.legado.app.utils.removePref
import io.legado.app.utils.sysConfiguration
import io.legado.app.utils.toastOnUi
import splitties.init.appCtx
@Suppress("MemberVisibilityCanBePrivate")
object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
val isCronet = appCtx.getPrefBoolean(PreferKey.cronet)
val useAntiAlias = appCtx.getPrefBoolean(PreferKey.antiAlias)
var useAntiAlias = appCtx.getPrefBoolean(PreferKey.antiAlias)
var useLibArchive = appCtx.getPrefBoolean(PreferKey.useLibArchive)
var userAgent: String = getPrefUserAgent()
var isEInkMode = appCtx.getPrefString(PreferKey.themeMode) == "3"
var clickActionTL = appCtx.getPrefInt(PreferKey.clickActionTL, 2)
@ -63,6 +75,11 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
appCtx.getPrefBoolean(PreferKey.useZhLayout)
PreferKey.userAgent -> userAgent = getPrefUserAgent()
PreferKey.antiAlias -> useAntiAlias = appCtx.getPrefBoolean(PreferKey.antiAlias)
PreferKey.useLibArchive -> useLibArchive =
appCtx.getPrefBoolean(PreferKey.useLibArchive)
}
}

View File

@ -1104,7 +1104,7 @@ class PhotoView @JvmOverloads constructor(
}
override fun onFling(
e1: MotionEvent,
e1: MotionEvent?,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
@ -1139,7 +1139,7 @@ class PhotoView @JvmOverloads constructor(
}
override fun onScroll(
e1: MotionEvent,
e1: MotionEvent?,
e2: MotionEvent,
distanceX: Float,
distanceY: Float

View File

@ -8,7 +8,7 @@ import androidx.appcompat.widget.AppCompatTextView
class MultilineTextView(context: Context, attrs: AttributeSet?) :
AppCompatTextView(context, attrs) {
override fun onDraw(canvas: Canvas?) {
override fun onDraw(canvas: Canvas) {
calculateLines()
super.onDraw(canvas)
}

View File

@ -57,7 +57,7 @@ open class ScrollMultiAutoCompleteTextView @JvmOverloads constructor(
}
override fun onScroll(
e1: MotionEvent,
e1: MotionEvent?,
e2: MotionEvent,
distanceX: Float,
distanceY: Float

View File

@ -57,7 +57,7 @@ class ScrollTextView(context: Context, attrs: AttributeSet?) :
}
override fun onScroll(
e1: MotionEvent,
e1: MotionEvent?,
e2: MotionEvent,
distanceX: Float,
distanceY: Float

View File

@ -3,6 +3,8 @@ package io.legado.app.utils
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import io.legado.app.constant.AppPattern.archiveFileRegex
import io.legado.app.help.config.AppConfig
import io.legado.app.utils.compress.LibArchiveUtils
import io.legado.app.utils.compress.RarUtils
import io.legado.app.utils.compress.SevenZipUtils
import io.legado.app.utils.compress.ZipUtils
@ -14,6 +16,7 @@ import java.io.File
object ArchiveUtils {
const val TEMP_FOLDER_NAME = "ArchiveTemp"
// 临时目录 下次启动自动删除
val TEMP_PATH: String by lazy {
appCtx.externalCache.getFile(TEMP_FOLDER_NAME).createFolderReplace().absolutePath
@ -61,19 +64,68 @@ object ArchiveUtils {
checkAchieve(name)
val workPathFileDoc = getCacheFolderFileDoc(name, path)
val workPath = workPathFileDoc.toString()
return archiveFileDoc.openInputStream().getOrThrow().use {
when {
name.endsWith(".zip", ignoreCase = true) -> ZipUtils.unZipToPath(it, workPath, filter)
name.endsWith(".rar", ignoreCase = true) -> RarUtils.unRarToPath(it, workPath, filter)
name.endsWith(".7z", ignoreCase = true) -> SevenZipUtils.un7zToPath(it, workPath, filter)
else -> throw IllegalArgumentException("Unexpected archive format")
return if (AppConfig.useLibArchive) {
archiveFileDoc.openReadPfd().getOrThrow().use {
LibArchiveUtils.unArchive(it, File(workPath), filter)
}
} else {
archiveFileDoc.openInputStream().getOrThrow().use {
when {
name.endsWith(".zip", ignoreCase = true) -> ZipUtils.unZipToPath(
it,
workPath,
filter
)
name.endsWith(".rar", ignoreCase = true) -> RarUtils.unRarToPath(
it,
workPath,
filter
)
name.endsWith(".7z", ignoreCase = true) -> SevenZipUtils.un7zToPath(
it,
workPath,
filter
)
else -> throw IllegalArgumentException("Unexpected archive format")
}
}
}
}
/* 遍历目录获取文件名 */
fun getArchiveFilesName(fileUri: Uri, filter: ((String) -> Boolean)? = null): List<String> = getArchiveFilesName(FileDoc.fromUri(fileUri, false), filter)
fun getArchiveFilesName(fileUri: Uri, filter: ((String) -> Boolean)? = null): List<String> =
if (AppConfig.useLibArchive) {
getLibArchiveFilesName(FileDoc.fromUri(fileUri, false), filter)
} else {
getArchiveFilesName(FileDoc.fromUri(fileUri, false), filter)
}
fun getLibArchiveFilesName(
fileDoc: FileDoc,
filter: ((String) -> Boolean)? = null
): List<String> {
val name = fileDoc.name
checkAchieve(name)
return fileDoc.openReadPfd().getOrThrow().use {
try {
LibArchiveUtils.getFilesName(it, filter)
} catch (e: Exception) {
emptyList()
}
}
}
fun getArchiveFilesName(
fileDoc: FileDoc,
filter: ((String) -> Boolean)? = null
@ -85,14 +137,17 @@ object ArchiveUtils {
name.endsWith(".rar", ignoreCase = true) -> {
RarUtils.getFilesName(it, filter)
}
name.endsWith(".zip", ignoreCase = true) -> {
ZipUtils.getFilesName(it, filter)
}
name.endsWith(".7z", ignoreCase = true) -> {
SevenZipUtils.getFilesName(it, filter)
}
else -> emptyList()
}
}
}
}

View File

@ -6,6 +6,7 @@ import android.app.DownloadManager
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.ParcelFileDescriptor
import android.provider.DocumentsContract
import androidx.documentfile.provider.DocumentFile
import io.legado.app.exception.NoStackTraceException
@ -236,6 +237,14 @@ fun FileDoc.openOutputStream(): Result<OutputStream> {
return uri.outputStream(appCtx)
}
fun FileDoc.openReadPfd(): Result<ParcelFileDescriptor> {
return uri.toReadPfd(appCtx)
}
fun FileDoc.openWritePfd(): Result<ParcelFileDescriptor> {
return uri.toWritePfd(appCtx)
}
fun FileDoc.exists(
fileName: String,
vararg subDirs: String

View File

@ -96,7 +96,7 @@ object QRCodeUtils {
}
// 生成二维码图片的格式
var bitmap = Bitmap.createBitmap(heightPix, heightPix, Bitmap.Config.ARGB_8888)
var bitmap: Bitmap? = Bitmap.createBitmap(heightPix, heightPix, Bitmap.Config.ARGB_8888)
bitmap!!.setPixels(pixels, 0, heightPix, 0, 0, heightPix, heightPix)
if (logo != null) {
bitmap = addLogo(bitmap, logo, ratio)
@ -141,7 +141,7 @@ object QRCodeUtils {
//logo大小为二维码整体大小
val scaleFactor = srcWidth * ratio / logoWidth
var bitmap = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888)
var bitmap: Bitmap? = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888)
try {
val canvas = Canvas(bitmap!!)
canvas.drawBitmap(src, 0f, 0f, null)
@ -449,7 +449,7 @@ object QRCodeUtils {
if (srcWidth <= 0 || srcHeight <= 0) {
return null
}
var bitmap = Bitmap.createBitmap(
var bitmap: Bitmap? = Bitmap.createBitmap(
srcWidth,
srcHeight + textSize + offset * 2,
Bitmap.Config.ARGB_8888

View File

@ -2,6 +2,7 @@ package io.legado.app.utils
import android.content.Context
import android.net.Uri
import android.os.ParcelFileDescriptor
import androidx.appcompat.app.AppCompatActivity
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment
@ -232,6 +233,68 @@ fun Uri.outputStream(context: Context): Result<OutputStream> {
}
}
fun Uri.toReadPfd(context: Context): Result<ParcelFileDescriptor> {
val uri = this
return kotlin.runCatching {
try {
if (isContentScheme()) {
DocumentFile.fromSingleUri(context, uri)
?: throw NoStackTraceException("未获取到文件")
return@runCatching context.contentResolver.openFileDescriptor(uri, "r")!!
} else {
val path = RealPathUtil.getPath(context, uri)
?: throw NoStackTraceException("未获取到文件")
val file = File(path)
if (file.exists()) {
return@runCatching ParcelFileDescriptor.open(
file,
ParcelFileDescriptor.MODE_READ_ONLY
)
} else {
throw NoStackTraceException("文件不存在")
}
}
} catch (e: Exception) {
e.printOnDebug()
AppLog.put("读取inputStream失败${e.localizedMessage}", e)
throw e
}
}
}
fun Uri.toWritePfd(context: Context): Result<ParcelFileDescriptor> {
val uri = this
return kotlin.runCatching {
try {
if (isContentScheme()) {
DocumentFile.fromSingleUri(context, uri)
?: throw NoStackTraceException("未获取到文件")
return@runCatching context.contentResolver.openFileDescriptor(uri, "w")!!
} else {
val path = RealPathUtil.getPath(context, uri)
?: throw NoStackTraceException("未获取到文件")
val file = File(path)
if (file.exists()) {
return@runCatching ParcelFileDescriptor.open(
file,
ParcelFileDescriptor.MODE_WRITE_ONLY
)
} else {
throw NoStackTraceException("文件不存在")
}
}
} catch (e: Exception) {
e.printOnDebug()
AppLog.put("读取inputStream失败${e.localizedMessage}", e)
throw e
}
}
}
fun Uri.toRequestBody(contentType: MediaType? = null): RequestBody {
val uri = this
return object : RequestBody() {

View File

@ -0,0 +1,348 @@
package io.legado.app.utils.compress
import android.os.ParcelFileDescriptor
import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants
import android.system.OsConstants.S_IFDIR
import android.system.OsConstants.S_ISDIR
import io.legado.app.lib.icu4j.CharsetDetector
import me.zhanghai.android.libarchive.Archive
import me.zhanghai.android.libarchive.ArchiveEntry
import me.zhanghai.android.libarchive.ArchiveException
import java.io.File
import java.io.FileDescriptor
import java.io.IOException
import java.io.InterruptedIOException
import java.nio.ByteBuffer
import java.nio.channels.SeekableByteChannel
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
object LibArchiveUtils {
// @Throws(ArchiveException::class)
// fun openArchive(
// inputStream: InputStream,
// ): Long {
// val archive: Long = Archive.readNew()
// var successful = false
// try {
// Archive.setCharset(archive, StandardCharsets.UTF_8.name().toByteArray())
// Archive.readSupportFilterAll(archive)
// Archive.readSupportFormatAll(archive)
// Archive.readSetCallbackData(archive, null)
// val buffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE)
// Archive.readSetReadCallback<Any?>(archive) { _, _ ->
// buffer.clear()
// val bytesRead = try {
// inputStream.read(buffer.array())
// } catch (e: IOException) {
// throw ArchiveException(Archive.ERRNO_FATAL, "InputStream.read", e)
// }
// if (bytesRead != -1) {
// buffer.limit(bytesRead)
// buffer
// } else {
// null
// }
// }
// Archive.readSetSkipCallback<Any?>(archive) { _, _, request ->
// try {
// inputStream.skip(request)
// } catch (e: IOException) {
// throw ArchiveException(Archive.ERRNO_FATAL, "InputStream.skip", e)
// }
// }
// Archive.readOpen1(archive)
// successful = true
// return archive
//
//
// } finally {
// if (!successful) {
// Archive.free(archive)
// }
// }
//
//
// }
@Throws(ArchiveException::class)
private fun openArchive(
channel: SeekableByteChannel,
): Long {
val archive: Long = Archive.readNew()
var successful = false
try {
Archive.setCharset(archive, StandardCharsets.UTF_8.name().toByteArray())
Archive.readSupportFilterAll(archive)
Archive.readSupportFormatAll(archive)
Archive.readSetCallbackData(archive, null)
val buffer = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE)
Archive.readSetReadCallback<Any?>(archive) { _, _ ->
buffer.clear()
val bytesRead = try {
channel.read(buffer)
} catch (e: IOException) {
throw ArchiveException(Archive.ERRNO_FATAL, "SeekableByteChannel.read", e)
}
if (bytesRead != -1) {
buffer.flip()
buffer
} else {
null
}
}
Archive.readSetSkipCallback<Any?>(archive) { _, _, request ->
try {
channel.position(channel.position() + request)
} catch (e: IOException) {
throw ArchiveException(Archive.ERRNO_FATAL, "SeekableByteChannel.position", e)
}
request
}
Archive.readSetSeekCallback<Any?>(archive) { _, _, offset, whence ->
val newPosition: Long
try {
newPosition = when (whence) {
OsConstants.SEEK_SET -> offset
OsConstants.SEEK_CUR -> channel.position() + offset
OsConstants.SEEK_END -> channel.size() + offset
else -> throw ArchiveException(
Archive.ERRNO_FATAL,
"Unknown whence $whence"
)
}
channel.position(newPosition)
} catch (e: IOException) {
throw ArchiveException(Archive.ERRNO_FATAL, "SeekableByteChannel.position", e)
}
newPosition
}
Archive.readOpen1(archive)
successful = true
return archive
} finally {
if (!successful) {
Archive.free(archive)
}
}
}
@Throws(ArchiveException::class)
private fun openArchive(
pfd: ParcelFileDescriptor,
useCb: Boolean = true
): Long {
val archive: Long = Archive.readNew()
var successful = false
try {
Archive.setCharset(archive, StandardCharsets.UTF_8.name().toByteArray())
Archive.readSupportFilterAll(archive)
Archive.readSupportFormatAll(archive)
if (useCb) {
Archive.readSetCallbackData(archive, pfd.fileDescriptor)
val buffer = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE)
Archive.readSetReadCallback<Any>(
archive
) { _1: Long, fd: Any? ->
buffer.clear()
try {
Os.read(fd as FileDescriptor?, buffer)
} catch (e: ErrnoException) {
throw ArchiveException(Archive.ERRNO_FATAL, "Os.read", e)
} catch (e: InterruptedIOException) {
throw ArchiveException(Archive.ERRNO_FATAL, "Os.read", e)
}
buffer.flip()
buffer
}
Archive.readSetSkipCallback<Any>(
archive
) { _1: Long, fd: Any?, request: Long ->
try {
Os.lseek(
fd as FileDescriptor?, request, OsConstants.SEEK_CUR
)
} catch (e: ErrnoException) {
throw ArchiveException(Archive.ERRNO_FATAL, "Os.lseek", e)
}
request
}
Archive.readSetSeekCallback<Any>(
archive
) { _1: Long, fd: Any?, offset: Long, whence: Int ->
try {
return@readSetSeekCallback Os.lseek(
fd as FileDescriptor?, offset, whence
)
} catch (e: ErrnoException) {
throw ArchiveException(Archive.ERRNO_FATAL, "Os.lseek", e)
}
}
Archive.readOpen1(archive)
} else {
Archive.readOpenFd(archive, pfd.fd, DEFAULT_BUFFER_SIZE.toLong())
}
successful = true
return archive
} finally {
if (!successful) {
Archive.free(archive)
}
}
}
@Throws(NullPointerException::class, SecurityException::class)
fun unArchive(
pfd: ParcelFileDescriptor,
destDir: File,
filter: ((String) -> Boolean)?
): List<File> {
return unArchive(openArchive(pfd), destDir, filter)
}
@Throws(NullPointerException::class, SecurityException::class)
private fun unArchive(
archive: Long,
destDir: File?,
filter: ((String) -> Boolean)? = null
): List<File> {
destDir ?: throw NullPointerException("解决路径不能为空")
val files = arrayListOf<File>()
try {
var entry = Archive.readNextHeader(archive)
while (entry != 0L) {
val entryName =
getEntryString(ArchiveEntry.pathnameUtf8(entry), ArchiveEntry.pathname(entry))
?: continue
val entryFile = File(destDir, entryName)
if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath)) {
throw SecurityException("压缩文件只能解压到指定路径")
}
val entryStat = ArchiveEntry.stat(entry)
//判断是否是文件夹
if (S_ISDIR(entryStat.stMode)) {
if (!entryFile.exists()) {
entryFile.mkdirs()
}
entry = Archive.readNextHeader(archive)
continue
}
if (entryFile.parentFile?.exists() != true) {
entryFile.parentFile?.mkdirs()
}
if (filter != null && !filter.invoke(entryName)) continue
if (!entryFile.exists()) {
entryFile.createNewFile()
entryFile.setReadable(true)
entryFile.setExecutable(true)
}
ParcelFileDescriptor.open(entryFile, ParcelFileDescriptor.MODE_WRITE_ONLY).use {
Archive.readDataIntoFd(archive, it.fd)
files.add(entryFile)
}
entry = Archive.readNextHeader(archive)
}
} finally {
Archive.free(archive)
}
return files
}
fun getFilesName(pfd: ParcelFileDescriptor, filter: ((String) -> Boolean)?): List<String> {
return getFilesName(openArchive(pfd), filter)
}
@Throws(SecurityException::class)
private fun getFilesName(
archive: Long,
filter: ((String) -> Boolean)? = null
): List<String> {
val fileNames = mutableListOf<String>()
try {
//Archive.readOpenFd(archive, pfd.fd, 8192)
var entry = Archive.readNextHeader(archive)
//val formatName: String = newStringFromBytes(Archive.formatName(archive))
while (entry != 0L) {
val fileName =
getEntryString(ArchiveEntry.pathnameUtf8(entry), ArchiveEntry.pathname(entry))
?: continue
val entryStat = ArchiveEntry.stat(entry)
val fileType = entryStat.stMode and S_IFDIR
if (S_ISDIR(entryStat.stMode)) {
entry = Archive.readNextHeader(archive)
continue
}
if (filter != null && filter.invoke(fileName))
fileNames.add(fileName)
entry = Archive.readNextHeader(archive)
}
} finally {
Archive.free(archive)
}
return fileNames
}
private fun getEntryString(utf8: String?, bytes: ByteArray?): String? {
return utf8 ?: newStringFromBytes(bytes)
}
private fun newStringFromBytes(bytes: ByteArray?): String? {
bytes ?: return null
val cd = CharsetDetector()
cd.setText(bytes)
val c = cd.detectAll().first().name
return String(bytes, Charset.forName(c))
}
}

View File

@ -1120,7 +1120,7 @@
<string name="add_all_to_bookshelf">全部加入书架</string>
<string name="page_to">页至</string>
<string name="result_analyzed">Analyzed</string>
<string name="bookshelf_px_4" >Comprehensive</string>
<string name="bookshelf_px_4">Comprehensive</string>
<string name="effective_replaces">起效的替换</string>
<string name="export_book">导出书籍</string>
<string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
@ -1129,4 +1129,6 @@
<string name="open_book_info_by_click_title">点击书名打开详情</string>
<string name="export_wait">等待导出</string>
<string name="default_home_page">默认主页</string>
<string name="use_lib_archive">Use LibArchive</string>
<string name="pref_use_lib_archive_summary">Using libarchive to extract files, it supports extracting RAR5.</string>
</resources>

View File

@ -1123,7 +1123,7 @@
<string name="add_all_to_bookshelf">全部加入书架</string>
<string name="page_to">页至</string>
<string name="result_analyzed">Analyzed</string>
<string name="bookshelf_px_4" >Comprehensive</string>
<string name="bookshelf_px_4">Comprehensive</string>
<string name="effective_replaces">起效的替换</string>
<string name="export_book">导出书籍</string>
<string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
@ -1132,4 +1132,6 @@
<string name="open_book_info_by_click_title">点击书名打开详情</string>
<string name="export_wait">等待导出</string>
<string name="default_home_page">默认主页</string>
<string name="use_lib_archive">Use LibArchive</string>
<string name="pref_use_lib_archive_summary">Using libarchive to extract files, it supports extracting RAR5.</string>
</resources>

View File

@ -1123,7 +1123,7 @@
<string name="add_all_to_bookshelf">全部加入书架</string>
<string name="page_to">页至</string>
<string name="result_analyzed">Analyzed</string>
<string name="bookshelf_px_4" >Comprehensive</string>
<string name="bookshelf_px_4">Comprehensive</string>
<string name="effective_replaces">起效的替换</string>
<string name="export_book">导出书籍</string>
<string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
@ -1132,4 +1132,6 @@
<string name="open_book_info_by_click_title">点击书名打开详情</string>
<string name="export_wait">等待导出</string>
<string name="default_home_page">默认主页</string>
<string name="use_lib_archive">Use LibArchive</string>
<string name="pref_use_lib_archive_summary">Using libarchive to extract files, it supports extracting RAR5.</string>
</resources>

View File

@ -1117,9 +1117,9 @@ Còn </string>
<string name="show_wait_up_count">Hiển thị số bản cập nhật đang đợi</string>
<string name="exit_app">Thoát khỏi phần mềm</string>
<string name="add_all_to_bookshelf">Thêm tất cả vào giá sách</string>
<string name="page_to">Trang tới</string>
<string name="page_to">Trang tới</string>
<string name="result_analyzed">Analyzed</string>
<string name="bookshelf_px_4" >Comprehensive</string>
<string name="bookshelf_px_4">Comprehensive</string>
<string name="effective_replaces">起效的替换</string>
<string name="export_book">导出书籍</string>
<string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
@ -1127,4 +1127,6 @@ Còn </string>
<string name="change_source_delay">换源间隔</string>
<string name="open_book_info_by_click_title">点击书名打开详情</string>
<string name="export_wait">等待导出</string>
<string name="use_lib_archive">Use LibArchive</string>
<string name="pref_use_lib_archive_summary">Using libarchive to extract files, it supports extracting RAR5.</string>
</resources>

View File

@ -1120,7 +1120,7 @@
<string name="add_all_to_bookshelf">全部加入书架</string>
<string name="page_to">页至</string>
<string name="result_analyzed">解析示例</string>
<string name="bookshelf_px_4" >綜合排序</string>
<string name="bookshelf_px_4">綜合排序</string>
<string name="effective_replaces">起效的替换</string>
<string name="export_book">导出书籍</string>
<string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
@ -1128,4 +1128,6 @@
<string name="change_source_delay">换源间隔</string>
<string name="open_book_info_by_click_title">点击书名打开详情</string>
<string name="export_wait">等待导出</string>
<string name="use_lib_archive">使用LibArchive</string>
<string name="pref_use_lib_archive_summary">使用libarchive解壓檔案支持解壓RAR5。</string>
</resources>

View File

@ -1122,7 +1122,7 @@
<string name="add_all_to_bookshelf">全部加入书架</string>
<string name="page_to">页至</string>
<string name="result_analyzed">解析示例</string>
<string name="bookshelf_px_4" >綜合排序</string>
<string name="bookshelf_px_4">綜合排序</string>
<string name="effective_replaces">起效的替换</string>
<string name="export_book">导出书籍</string>
<string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
@ -1130,4 +1130,6 @@
<string name="change_source_delay">换源间隔</string>
<string name="open_book_info_by_click_title">点击书名打开详情</string>
<string name="export_wait">等待导出</string>
<string name="use_lib_archive">使用LibArchive</string>
<string name="pref_use_lib_archive_summary">使用libarchive解壓檔案支持解壓RAR5。</string>
</resources>

View File

@ -1122,7 +1122,7 @@
<string name="add_all_to_bookshelf">全部加入书架</string>
<string name="page_to">页至</string>
<string name="result_analyzed">解析示例</string>
<string name="bookshelf_px_4" >综合排序</string>
<string name="bookshelf_px_4">综合排序</string>
<string name="effective_replaces">起效的替换</string>
<string name="export_book">导出书籍</string>
<string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
@ -1130,4 +1130,6 @@
<string name="change_source_delay">换源间隔</string>
<string name="open_book_info_by_click_title">点击书名打开详情</string>
<string name="export_wait">等待导出</string>
<string name="use_lib_archive">使用LibArchive</string>
<string name="pref_use_lib_archive_summary">使用libarchive解压文件支持解压RAR5。</string>
</resources>

View File

@ -1123,7 +1123,7 @@
<string name="add_all_to_bookshelf">全部加入书架</string>
<string name="page_to">页至</string>
<string name="result_analyzed">Analyzed</string>
<string name="bookshelf_px_4" >Comprehensive</string>
<string name="bookshelf_px_4">Comprehensive</string>
<string name="effective_replaces">起效的替换</string>
<string name="export_book">导出书籍</string>
<string name="export_book_notification_content">正在导出(%1$s),还有%2$d本待导出</string>
@ -1132,4 +1132,6 @@
<string name="open_book_info_by_click_title">点击书名打开详情</string>
<string name="export_wait">等待导出</string>
<string name="default_home_page">默认主页</string>
<string name="use_lib_archive">Use LibArchive</string>
<string name="pref_use_lib_archive_summary">Using libarchive to extract files, it supports extracting RAR5.</string>
</resources>

View File

@ -98,6 +98,12 @@
android:summary="@string/pref_cronet_summary"
android:title="Cronet" />
<io.legado.app.lib.prefs.SwitchPreference
android:defaultValue="false"
android:key="useLibArchive"
android:summary="@string/pref_use_lib_archive_summary"
android:title="@string/use_lib_archive" />
<io.legado.app.lib.prefs.SwitchPreference
android:defaultValue="false"
android:key="antiAlias"

View File

@ -2,12 +2,12 @@
buildscript {
ext{
compile_sdk_version = 33
build_tool_version = '33.0.1'
kotlin_version = '1.9.0'
ksp_version="1.0.12"
agp_version = '8.1.0'
media3_version = "1.1.0"
compile_sdk_version = 34
build_tool_version = '34.0.0'
kotlin_version = '1.9.10'
ksp_version = "1.0.13"
agp_version = '8.1.1'
media3_version = "1.1.1"
splitties_version = '3.0.0'
room_version = '2.5.2'
}

View File

@ -1,6 +1,6 @@
#Sat Jul 22 18:39:27 CST 2023
#Tue Sep 12 01:36:42 CST 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists