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 96cc7567f..fb197b3bb 100644 --- a/app/src/main/java/io/legado/app/constant/AppConst.kt +++ b/app/src/main/java/io/legado/app/constant/AppConst.kt @@ -29,14 +29,18 @@ object AppConst { ScriptEngineManager().getEngineByName("rhino") } - val TIME_FORMAT: SimpleDateFormat by lazy { + val timeFormat: SimpleDateFormat by lazy { SimpleDateFormat("HH:mm") } - val DATE_FORMAT: SimpleDateFormat by lazy { + val dateFormat: SimpleDateFormat by lazy { SimpleDateFormat("yyyy/MM/dd HH:mm") } + val fileNameFormat: SimpleDateFormat by lazy { + SimpleDateFormat("yy-MM-dd-HH-mm-ss") + } + val keyboardToolChars: List by lazy { arrayListOf( "@", "&", "|", "%", "/", ":", "[", "]", "{", "}", "<", ">", "\\", "$", "#", "!", ".", diff --git a/app/src/main/java/io/legado/app/help/JsExtensions.kt b/app/src/main/java/io/legado/app/help/JsExtensions.kt index 7cfd763a1..493ede9c3 100644 --- a/app/src/main/java/io/legado/app/help/JsExtensions.kt +++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt @@ -2,7 +2,7 @@ package io.legado.app.help import android.util.Base64 import androidx.annotation.Keep -import io.legado.app.constant.AppConst.DATE_FORMAT +import io.legado.app.constant.AppConst.dateFormat import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.utils.EncoderUtils import io.legado.app.utils.MD5Utils @@ -50,6 +50,6 @@ interface JsExtensions { } fun timeFormat(time: Long): String { - return DATE_FORMAT.format(Date(time)) + return dateFormat.format(Date(time)) } } diff --git a/app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt b/app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt index f731806a7..fec1ed537 100644 --- a/app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt @@ -127,7 +127,7 @@ class DownloadActivity : VMBaseActivity(R.layout.activity_dow FilePicker.selectFolder(this, exportRequestCode) { val path = ACache.get(this@DownloadActivity).getAsString(exportBookPathKey) if (path.isNullOrEmpty()) { - toast("没有默认路径") + toast(R.string.no_default_path) } else { startExport(path) } diff --git a/app/src/main/java/io/legado/app/ui/book/local/ImportBookAdapter.kt b/app/src/main/java/io/legado/app/ui/book/local/ImportBookAdapter.kt index acc3b7265..153356754 100644 --- a/app/src/main/java/io/legado/app/ui/book/local/ImportBookAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/local/ImportBookAdapter.kt @@ -87,7 +87,7 @@ class ImportBookAdapter(context: Context, val callBack: CallBack) : ll_brief.visible() tv_tag.text = item.name.substringAfterLast(".") tv_size.text = StringUtils.toSize(item.size) - tv_date.text = AppConst.DATE_FORMAT.format(item.date) + tv_date.text = AppConst.dateFormat.format(item.date) cb_select.isChecked = selectedUris.contains(item.uri.toString()) } tv_name.text = item.name diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt index 8e0bf1d55..bcec98b1d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt @@ -7,7 +7,7 @@ import android.view.MotionEvent import android.widget.FrameLayout import com.github.houbb.opencc4j.core.impl.ZhConvertBootstrap import io.legado.app.R -import io.legado.app.constant.AppConst.TIME_FORMAT +import io.legado.app.constant.AppConst.timeFormat import io.legado.app.help.AppConfig import io.legado.app.help.ReadBookConfig import io.legado.app.ui.book.read.page.entities.TextPage @@ -85,7 +85,7 @@ class ContentView(context: Context) : FrameLayout(context) { } fun upTime() { - tv_top_left.text = TIME_FORMAT.format(Date(System.currentTimeMillis())) + tv_top_left.text = timeFormat.format(Date(System.currentTimeMillis())) } fun upBattery(battery: Int) { diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt index eba5950f4..385088640 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt @@ -85,7 +85,12 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity R.id.menu_group_manage -> GroupManageDialog().show(supportFragmentManager, "groupManage") R.id.menu_import_source_local -> FilePicker - .selectFile(this, importRequestCode, "text/*", arrayOf("txt", "json")) + .selectFile( + this, + importRequestCode, + type = "text/*", + allowExtensions = arrayOf("txt", "json") + ) R.id.menu_import_source_onLine -> showImportDialog() } if (item.groupId == R.id.source_group) { diff --git a/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt b/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt index 9260fc8bd..8f5549709 100644 --- a/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt +++ b/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt @@ -14,8 +14,13 @@ import org.jetbrains.anko.toast @Suppress("unused") object FilePicker { - fun selectFolder(activity: AppCompatActivity, requestCode: Int, default: (() -> Unit)? = null) { - activity.alert(titleResource = R.string.select_folder) { + fun selectFolder( + activity: AppCompatActivity, + requestCode: Int, + title: String = activity.getString(R.string.select_folder), + default: (() -> Unit)? = null + ) { + activity.alert(title = title) { val selectList = activity.resources.getStringArray(R.array.select_folder).toMutableList() default ?: let { @@ -45,9 +50,14 @@ object FilePicker { }.show() } - fun selectFolder(fragment: Fragment, requestCode: Int, default: (() -> Unit)? = null) { + fun selectFolder( + fragment: Fragment, + requestCode: Int, + title: String = fragment.getString(R.string.select_folder), + default: (() -> Unit)? = null + ) { fragment.requireContext() - .alert(titleResource = R.string.select_folder) { + .alert(title = title) { val selectList = fragment.resources.getStringArray(R.array.select_folder).toMutableList() default ?: let { @@ -80,11 +90,12 @@ object FilePicker { fun selectFile( activity: BaseActivity, requestCode: Int, + title: String = activity.getString(R.string.select_file), type: String, allowExtensions: Array?, default: (() -> Unit)? = null ) { - activity.alert(titleResource = R.string.select_file) { + activity.alert(title = title) { val selectList = activity.resources.getStringArray(R.array.select_folder).toMutableList() default ?: let { @@ -119,12 +130,13 @@ object FilePicker { fun selectFile( fragment: Fragment, requestCode: Int, + title: String = fragment.getString(R.string.select_file), type: String, allowExtensions: Array, default: (() -> Unit)? = null ) { fragment.requireContext() - .alert(titleResource = R.string.select_file) { + .alert(title = title) { val selectList = fragment.resources.getStringArray(R.array.select_folder).toMutableList() default ?: let { diff --git a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt index 16ffee4a1..fd0a591b6 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt @@ -180,7 +180,12 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelection()) R.id.menu_import_source_onLine -> showImportDialog() R.id.menu_import_source_local -> FilePicker - .selectFile(this, importRequestCode, "text/*", arrayOf("txt", "json")) + .selectFile( + this, + importRequestCode, + type = "text/*", + allowExtensions = arrayOf("txt", "json") + ) } return super.onCompatOptionsItemSelected(item) } 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 1764a1f32..bf3b67f5f 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 @@ -1,6 +1,7 @@ package io.legado.app.ui.rss.read import android.annotation.SuppressLint +import android.content.Intent import android.os.Bundle import android.view.KeyEvent import android.view.Menu @@ -14,6 +15,9 @@ import io.legado.app.R import io.legado.app.base.VMBaseActivity import io.legado.app.lib.theme.DrawableUtils import io.legado.app.lib.theme.primaryTextColor +import io.legado.app.ui.filechooser.FileChooserDialog +import io.legado.app.ui.filechooser.FilePicker +import io.legado.app.utils.ACache import io.legado.app.utils.NetworkUtils import io.legado.app.utils.getViewModel import io.legado.app.utils.openUrl @@ -21,17 +25,22 @@ import kotlinx.android.synthetic.main.activity_rss_read.* import kotlinx.coroutines.launch import org.apache.commons.text.StringEscapeUtils import org.jetbrains.anko.share +import org.jetbrains.anko.toast import org.jsoup.Jsoup import org.jsoup.safety.Whitelist + class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_read), + FileChooserDialog.CallBack, ReadRssViewModel.CallBack { override val viewModel: ReadRssViewModel get() = getViewModel(ReadRssViewModel::class.java) - + private val savePathRequestCode = 132 + private val imagePathKey = "" private var starMenuItem: MenuItem? = null private var ttsMenuItem: MenuItem? = null + var webPic: String? = null override fun onActivityCreated(savedInstanceState: Bundle?) { viewModel.callBack = this @@ -95,6 +104,30 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r domStorageEnabled = true allowContentAccess = true } + web_view.setOnLongClickListener { + val hitTestResult = web_view.hitTestResult + if (hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE || + hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE + ) { + hitTestResult.extra?.let { + webPic = it + saveImage() + return@setOnLongClickListener true + } + } + return@setOnLongClickListener false + } + } + + private fun saveImage() { + FilePicker.selectFolder(this, savePathRequestCode, getString(R.string.save_image)) { + val path = ACache.get(this).getAsString(imagePathKey) + if (path.isNullOrEmpty()) { + toast(R.string.no_default_path) + } else { + viewModel.saveImage(webPic, path) + } + } } @SuppressLint("SetJavaScriptEnabled") @@ -113,7 +146,6 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r url )//不想用baseUrl进else } else { - //webView.loadData(html, "text/html;charset=utf-8", "utf-8")//经测试可以解决中文乱码 web_view.loadDataWithBaseURL( null, html, @@ -203,6 +235,24 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r } } + override fun onFilePicked(requestCode: Int, currentPath: String) { + when (requestCode) { + savePathRequestCode -> { + ACache.get(this).put(imagePathKey, currentPath) + viewModel.saveImage(webPic, currentPath) + } + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + savePathRequestCode -> data?.data?.let { + onFilePicked(requestCode, it.toString()) + } + } + } + override fun onDestroy() { super.onDestroy() web_view.destroy() diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt index 17b66f41c..7cc96ef29 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt @@ -2,19 +2,29 @@ package io.legado.app.ui.rss.read import android.app.Application import android.content.Intent +import android.net.Uri import android.speech.tts.TextToSpeech import android.speech.tts.UtteranceProgressListener +import android.util.Base64 +import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.MutableLiveData import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseViewModel +import io.legado.app.constant.AppConst import io.legado.app.data.entities.RssArticle import io.legado.app.data.entities.RssSource import io.legado.app.model.Rss import io.legado.app.model.analyzeRule.AnalyzeUrl +import io.legado.app.utils.DocumentUtils +import io.legado.app.utils.FileUtils +import io.legado.app.utils.isContentPath +import io.legado.app.utils.writeBytes import kotlinx.coroutines.launch +import java.io.File import java.util.* + class ReadRssViewModel(application: Application) : BaseViewModel(application), TextToSpeech.OnInitListener { var callBack: CallBack? = null @@ -85,6 +95,31 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application), } } + fun saveImage(webPic: String?, path: String) { + webPic ?: return + execute { + val fileName = "${AppConst.fileNameFormat.format(Date(System.currentTimeMillis()))}.jpg" + webData2bitmap(webPic).let { biteArray -> + if (path.isContentPath()) { + val uri = Uri.parse(path) + DocumentFile.fromTreeUri(context, uri)?.let { doc -> + DocumentUtils.createFileIfNotExist(doc, fileName) + ?.writeBytes(context, biteArray) + } + } else { + val file = FileUtils.createFileIfNotExist(File(path), fileName) + file.writeBytes(biteArray) + } + } + }.onError { + toast("保存图片失败:${it.localizedMessage}") + } + } + + private fun webData2bitmap(data: String): ByteArray { + return Base64.decode(data.split(",").toTypedArray()[1], Base64.DEFAULT) + } + fun clHtml(content: String): String { return if (content.contains("