diff --git a/app/cronetlib/cronet_api.jar b/app/cronetlib/cronet_api.jar index 25f32d25f..dd99596a1 100644 Binary files a/app/cronetlib/cronet_api.jar and b/app/cronetlib/cronet_api.jar differ diff --git a/app/cronetlib/cronet_impl_common_java.jar b/app/cronetlib/cronet_impl_common_java.jar index 76888114e..671743e16 100644 Binary files a/app/cronetlib/cronet_impl_common_java.jar and b/app/cronetlib/cronet_impl_common_java.jar differ diff --git a/app/cronetlib/cronet_impl_native_java.jar b/app/cronetlib/cronet_impl_native_java.jar index 63321b2e6..a7f0b6e3b 100644 Binary files a/app/cronetlib/cronet_impl_native_java.jar and b/app/cronetlib/cronet_impl_native_java.jar differ diff --git a/app/src/main/assets/cronet.json b/app/src/main/assets/cronet.json index be4351c62..b6954a7ad 100644 --- a/app/src/main/assets/cronet.json +++ b/app/src/main/assets/cronet.json @@ -1 +1 @@ -{"armeabi-v7a":"2001ec065243c76166fe5cc3c7ee2657","x86":"6d1301e1dc1c63eedd6ad9d57716972f","arm64-v8a":"ff861c88eaa622eba7f8a7af0672352b","x86_64":"5eb49453ef59221deaee4913a0d693b6","version":"103.0.5060.129"} \ No newline at end of file +{"armeabi-v7a":"a36bb21eec579e2c4bed0009eb0f1587","x86":"afce9bdd790c215374bb906919179ca5","arm64-v8a":"3ea9fa21d9b18860cfa12957c39dded1","x86_64":"717c1ebe0d1d3562f5c91c2b2606d2c2","version":"104.0.5112.69"} \ No newline at end of file diff --git a/app/src/main/assets/defaultData/txtTocRule.json b/app/src/main/assets/defaultData/txtTocRule.json index 57cfed6bf..d4f8c8670 100644 --- a/app/src/main/assets/defaultData/txtTocRule.json +++ b/app/src/main/assets/defaultData/txtTocRule.json @@ -159,4 +159,4 @@ "example": "标题后面数字没有括号124", "serialNumber": 18 } -] \ No newline at end of file +] diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index 0c4bccfcd..47c5ab60c 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -11,11 +11,13 @@ * 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则或简繁转换出现问题。 * 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源! -**2022/07/31** +**2022/08/02** +* 更新cronet: 104.0.5112.69 * 修复一些bug by 821938089 * 备份自定义直链上传规则 * 尝试修复epub加载OOM的问题 +* 图片添加保存按钮 * 一些优化 **2022/07/21** diff --git a/app/src/main/java/io/legado/app/constant/AppConst.kt b/app/src/main/java/io/legado/app/constant/AppConst.kt index 49220b429..2e8eb69c3 100644 --- a/app/src/main/java/io/legado/app/constant/AppConst.kt +++ b/app/src/main/java/io/legado/app/constant/AppConst.kt @@ -50,15 +50,17 @@ object AppConst { const val notificationIdDownload = -1122395 const val notificationIdCheckSource = -1122395 + const val imagePathKey = "imagePath" + val menuViewNames = arrayOf( "com.android.internal.view.menu.ListMenuItemView", "androidx.appcompat.view.menu.ListMenuItemView" ) @SuppressLint("PrivateResource") - val sysElevation = - appCtx.resources.getDimension(com.google.android.material.R.dimen.design_appbar_elevation) - .toInt() + val sysElevation = appCtx.resources + .getDimension(com.google.android.material.R.dimen.design_appbar_elevation) + .toInt() val androidId: String by lazy { Settings.System.getString(appCtx.contentResolver, Settings.Secure.ANDROID_ID) ?: "null" diff --git a/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt b/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt index 31a556e7d..73f8f9edf 100644 --- a/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt +++ b/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt @@ -148,7 +148,7 @@ class Coroutine( success?.let { dispatchCallback(this, value, it) } } catch (e: Throwable) { e.printOnDebug() - if (e is CancellationException && !isActive && e !is ActivelyCancelException) { + if (e is CancellationException && e !is ActivelyCancelException && isCancelled) { this@Coroutine.cancel() } val consume: Boolean = errorReturn?.value?.let { value -> diff --git a/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt b/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt index baeedd16e..d26934b1f 100644 --- a/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt +++ b/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt @@ -71,7 +71,7 @@ open class WebDav(val path: String, val authorization: Authorization) { private val webDavClient by lazy { val authInterceptor = Interceptor { chain -> var request = chain.request() - if (request.url.host == host) { + if (request.url.host.equals(host, true)) { request = request .newBuilder() .header(authorization.name, authorization.data) diff --git a/app/src/main/java/io/legado/app/ui/book/local/rule/TxtTocRuleActivity.kt b/app/src/main/java/io/legado/app/ui/book/local/rule/TxtTocRuleActivity.kt index 26b85d603..085ddc3bb 100644 --- a/app/src/main/java/io/legado/app/ui/book/local/rule/TxtTocRuleActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/local/rule/TxtTocRuleActivity.kt @@ -159,7 +159,7 @@ class TxtTocRuleActivity : VMBaseActivity = aCache .getAsString(importTocRuleKey) ?.splitNotBlank(",") diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index cb31722c0..f610bc3a1 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.app.Activity import android.content.Intent import android.content.res.Configuration +import android.net.Uri import android.os.Bundle import android.view.* import androidx.activity.result.contract.ActivityResultContracts @@ -14,10 +15,7 @@ import androidx.core.view.size import com.jaredrummler.android.colorpicker.ColorPickerDialogListener import io.legado.app.BuildConfig import io.legado.app.R -import io.legado.app.constant.BookType -import io.legado.app.constant.EventBus -import io.legado.app.constant.PreferKey -import io.legado.app.constant.Status +import io.legado.app.constant.* import io.legado.app.data.appDb import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookChapter @@ -58,6 +56,7 @@ import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.toc.TocActivityResult import io.legado.app.ui.browser.WebViewActivity import io.legado.app.ui.dict.DictDialog +import io.legado.app.ui.document.HandleFileContract import io.legado.app.ui.login.SourceLoginActivity import io.legado.app.ui.replace.ReplaceRuleActivity import io.legado.app.ui.replace.edit.ReplaceEditActivity @@ -129,6 +128,12 @@ class ReadBookActivity : BaseReadBookActivity(), } } } + private val selectImageDir = registerForActivityResult(HandleFileContract()) { + it.uri?.let { uri -> + ACache.get().put(AppConst.imagePathKey, uri.toString()) + viewModel.saveImage(it.value, uri) + } + } private var menu: Menu? = null private var autoPageJob: Job? = null private var backupJob: Job? = null @@ -1001,13 +1006,26 @@ class ReadBookActivity : BaseReadBookActivity(), popupAction.setItems( listOf( SelectItem(getString(R.string.show), "show"), - SelectItem(getString(R.string.refresh), "refresh") + SelectItem(getString(R.string.refresh), "refresh"), + SelectItem(getString(R.string.action_save), "save"), + SelectItem(getString(R.string.select_folder), "selectFolder") ) ) popupAction.onActionClick = { when (it) { "show" -> showDialogFragment(PhotoDialog(src)) "refresh" -> viewModel.refreshImage(src) + "save" -> { + val path = ACache.get().getAsString(AppConst.imagePathKey) + if (path.isNullOrEmpty()) { + selectImageDir.launch { + value = src + } + } else { + viewModel.saveImage(src, Uri.parse(path)) + } + } + "selectFolder" -> selectImageDir.launch() } popupAction.dismiss() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt index 7ae2a1589..803bc9587 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt @@ -2,6 +2,8 @@ package io.legado.app.ui.book.read import android.app.Application import android.content.Intent +import android.net.Uri +import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import io.legado.app.R @@ -28,12 +30,12 @@ import io.legado.app.service.BaseReadAloudService import io.legado.app.ui.book.read.page.entities.TextChapter import io.legado.app.ui.book.read.page.provider.ImageProvider import io.legado.app.ui.book.searchContent.SearchResult -import io.legado.app.utils.msg -import io.legado.app.utils.postEvent -import io.legado.app.utils.toStringArray -import io.legado.app.utils.toastOnUi +import io.legado.app.utils.* import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.launch +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream /** * 阅读界面数据处理 @@ -425,6 +427,34 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { } } + @Suppress("BlockingMethodInNonBlockingContext") + fun saveImage(src: String?, uri: Uri) { + src ?: return + val book = ReadBook.book ?: return + execute { + val image = BookHelp.getImage(book, src) + FileInputStream(image).use { input -> + if (uri.isContentScheme()) { + DocumentFile.fromTreeUri(context, uri)?.let { doc -> + val imageDoc = DocumentUtils.createFileIfNotExist(doc, image.name)!! + context.contentResolver.openOutputStream(imageDoc.uri)!!.use { output -> + input.copyTo(output) + } + } + } else { + val dir = File(uri.path ?: uri.toString()) + val file = FileUtils.createFileIfNotExist(dir, image.name) + FileOutputStream(file).use { output -> + input.copyTo(output) + } + } + } + }.onError { + AppLog.put("保存图片出错\n${it.localizedMessage}", it) + context.toastOnUi("保存图片出错\n${it.localizedMessage}") + } + } + /** * 替换规则变化 */ diff --git a/app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugActivity.kt index dac04ed2d..1aa54a875 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugActivity.kt @@ -109,6 +109,35 @@ class BookSourceDebugActivity : VMBaseActivity = arrayOf(), var otherActions: ArrayList>? = null, var fileData: Triple? = null, - var requestCode: Int = 0 + var requestCode: Int = 0, + var value: String? = null ) data class Result( val uri: Uri?, - val requestCode: Int + val requestCode: Int, + val value: String? ) } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt index 0e37f5787..9e9536741 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt @@ -14,9 +14,11 @@ import androidx.webkit.WebViewFeature import io.legado.app.R import io.legado.app.base.VMBaseActivity import io.legado.app.constant.AppConst +import io.legado.app.constant.AppConst.imagePathKey import io.legado.app.databinding.ActivityRssReadBinding import io.legado.app.help.config.AppConfig import io.legado.app.lib.dialogs.SelectItem +import io.legado.app.lib.dialogs.selector import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.primaryTextColor import io.legado.app.model.Download @@ -38,15 +40,13 @@ class ReadRssActivity : VMBaseActivity override val binding by viewBinding(ActivityRssReadBinding::inflate) override val viewModel by viewModels() - private val imagePathKey = "imagePath" private var starMenuItem: MenuItem? = null private var ttsMenuItem: MenuItem? = null private var customWebViewCallback: WebChromeClient.CustomViewCallback? = null - private var webPic: String? = null - private val saveImage = registerForActivityResult(HandleFileContract()) { + private val selectImageDir = registerForActivityResult(HandleFileContract()) { it.uri?.let { uri -> ACache.get().put(imagePathKey, uri.toString()) - viewModel.saveImage(webPic, uri) + viewModel.saveImage(it.value, uri) } } @@ -135,8 +135,18 @@ class ReadRssActivity : VMBaseActivity if (hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE || hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE ) { - hitTestResult.extra?.let { - saveImage(it) + hitTestResult.extra?.let { webPic -> + selector( + arrayListOf( + SelectItem(getString(R.string.action_save), "save"), + SelectItem(getString(R.string.select_folder), "selectFolder") + ) + ) { _, charSequence, i -> + when (charSequence.value) { + "save" -> saveImage(webPic) + "selectFolder" -> selectSaveFolder(null) + } + } return@setOnLongClickListener true } } @@ -153,23 +163,23 @@ class ReadRssActivity : VMBaseActivity } private fun saveImage(webPic: String) { - this.webPic = webPic val path = ACache.get().getAsString(imagePathKey) if (path.isNullOrEmpty()) { - selectSaveFolder() + selectSaveFolder(webPic) } else { viewModel.saveImage(webPic, Uri.parse(path)) } } - private fun selectSaveFolder() { + private fun selectSaveFolder(webPic: String?) { val default = arrayListOf>() val path = ACache.get().getAsString(imagePathKey) if (!path.isNullOrEmpty()) { default.add(SelectItem(path, -1)) } - saveImage.launch { + selectImageDir.launch { otherActions = default + value = webPic } } diff --git a/app/src/main/java/io/legado/app/utils/DocumentExtensions.kt b/app/src/main/java/io/legado/app/utils/DocumentExtensions.kt index a9c959549..5e2411706 100644 --- a/app/src/main/java/io/legado/app/utils/DocumentExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/DocumentExtensions.kt @@ -31,7 +31,7 @@ object DocumentUtils { vararg subDirs: String ): DocumentFile? { val parent: DocumentFile? = createFolderIfNotExist(root, *subDirs) - return parent?.createFile(mimeType, fileName) + return parent?.findFile(fileName) ?: parent?.createFile(mimeType, fileName) } fun createFolderIfNotExist(root: DocumentFile, vararg subDirs: String): DocumentFile? { diff --git a/app/src/main/res/layout/activity_source_debug.xml b/app/src/main/res/layout/activity_source_debug.xml index 5405684ab..d506b42f4 100644 --- a/app/src/main/res/layout/activity_source_debug.xml +++ b/app/src/main/res/layout/activity_source_debug.xml @@ -132,6 +132,7 @@ tools:ignore="HardcodedText" />