diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index e38e525d7..985505281 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -5,6 +5,7 @@ **2020/01/07** * 修复备份问题 +* 设置背景不再需要存储权限 **2020/01/06** * 适配Android 10 权限 diff --git a/app/src/main/java/io/legado/app/help/storage/OldRule.kt b/app/src/main/java/io/legado/app/help/storage/OldRule.kt index 14b4a9c53..c8036e559 100644 --- a/app/src/main/java/io/legado/app/help/storage/OldRule.kt +++ b/app/src/main/java/io/legado/app/help/storage/OldRule.kt @@ -3,6 +3,7 @@ package io.legado.app.help.storage import io.legado.app.constant.AppConst import io.legado.app.constant.BookType import io.legado.app.data.entities.BookSource +import io.legado.app.data.entities.ReplaceRule import io.legado.app.data.entities.rule.* import io.legado.app.help.storage.Restore.jsonPath import io.legado.app.utils.* @@ -205,5 +206,27 @@ object OldRule { return GSON.toJson(map) } - + fun jsonToReplaceRule(json: String): ReplaceRule? { + var replaceRule: ReplaceRule? = null + runCatching { + replaceRule = GSON.fromJsonObject(json.trim()) + } + runCatching { + if (replaceRule == null || replaceRule?.pattern.isNullOrBlank()) { + val jsonItem = jsonPath.parse(json.trim()) + val rule = ReplaceRule() + rule.id = jsonItem.readLong("$.id") ?: System.currentTimeMillis() + rule.pattern = jsonItem.readString("$.regex") ?: "" + if (rule.pattern.isEmpty()) return null + rule.name = jsonItem.readString("$.replaceSummary") ?: "" + rule.replacement = jsonItem.readString("$.replacement") ?: "" + rule.isRegex = jsonItem.readBool("$.isRegex") == true + rule.scope = jsonItem.readString("$.useTo") + rule.isEnabled = jsonItem.readBool("$.enable") == true + rule.order = jsonItem.readInt("$.serialNumber") ?: 0 + return rule + } + } + return replaceRule + } } diff --git a/app/src/main/java/io/legado/app/help/storage/Restore.kt b/app/src/main/java/io/legado/app/help/storage/Restore.kt index a95626228..15c14051c 100644 --- a/app/src/main/java/io/legado/app/help/storage/Restore.kt +++ b/app/src/main/java/io/legado/app/help/storage/Restore.kt @@ -226,20 +226,11 @@ object Restore { fun importOldReplaceRule(json: String): Int { val replaceRules = mutableListOf() val items: List> = jsonPath.parse(json).read("$") - val existingRules = App.db.replaceRuleDao().all.map { it.pattern }.toSet() - for ((index: Int, item: Map) in items.withIndex()) { + for (item in items) { val jsonItem = jsonPath.parse(item) - val rule = ReplaceRule() - rule.id = jsonItem.readLong("$.id") ?: System.currentTimeMillis().plus(index) - rule.pattern = jsonItem.readString("$.regex") ?: "" - if (rule.pattern.isEmpty() || rule.pattern in existingRules) continue - rule.name = jsonItem.readString("$.replaceSummary") ?: "" - rule.replacement = jsonItem.readString("$.replacement") ?: "" - rule.isRegex = jsonItem.readBool("$.isRegex") == true - rule.scope = jsonItem.readString("$.useTo") - rule.isEnabled = jsonItem.readBool("$.enable") == true - rule.order = jsonItem.readInt("$.serialNumber") ?: index - replaceRules.add(rule) + OldRule.jsonToReplaceRule(jsonItem.jsonString())?.let { + replaceRules.add(it) + } } App.db.replaceRuleDao().insert(*replaceRules.toTypedArray()) return replaceRules.size diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt index 82e7113a0..b19b86c91 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt @@ -12,6 +12,7 @@ import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.documentfile.provider.DocumentFile import androidx.fragment.app.DialogFragment import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -20,11 +21,13 @@ import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.constant.Bus +import io.legado.app.help.FileHelp import io.legado.app.help.ImageLoader import io.legado.app.help.ReadBookConfig import io.legado.app.help.permission.Permissions import io.legado.app.help.permission.PermissionsCompat import io.legado.app.ui.book.read.Help +import io.legado.app.utils.DocumentUtils import io.legado.app.utils.FileUtils import io.legado.app.utils.getCompatColor import io.legado.app.utils.postEvent @@ -32,6 +35,7 @@ import kotlinx.android.synthetic.main.dialog_read_bg_text.* import kotlinx.android.synthetic.main.item_bg_image.view.* import org.jetbrains.anko.sdk27.listeners.onCheckedChange import org.jetbrains.anko.sdk27.listeners.onClick +import java.io.File class BgTextConfigDialog : DialogFragment() { @@ -133,17 +137,10 @@ class BgTextConfigDialog : DialogFragment() { } private fun selectImage() { - PermissionsCompat.Builder(this) - .addPermissions(Permissions.READ_EXTERNAL_STORAGE, Permissions.WRITE_EXTERNAL_STORAGE) - .rationale(R.string.bg_image_per) - .onGranted { - val intent = Intent(Intent.ACTION_GET_CONTENT) - intent.addCategory(Intent.CATEGORY_OPENABLE) - intent.type = "image/*" - startActivityForResult(intent, resultSelectBg) - Unit - } - .request() + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.type = "image/*" + startActivityForResult(intent, resultSelectBg) } class BgAdapter(context: Context) : @@ -170,11 +167,37 @@ class BgTextConfigDialog : DialogFragment() { when (requestCode) { resultSelectBg -> { if (resultCode == RESULT_OK) { - data?.data?.let { - FileUtils.getPath(requireContext(), it)?.let { path -> - ReadBookConfig.getConfig().setBg(2, path) - ReadBookConfig.upBg() - postEvent(Bus.UP_CONFIG, false) + data?.data?.let { uri -> + if (DocumentFile.isDocumentUri(requireContext(), uri)) { + val doc = DocumentFile.fromSingleUri(requireContext(), uri) + doc?.let { + var file = requireContext().getExternalFilesDir(null) + ?: requireContext().filesDir + file = + FileHelp.getFile(file.absolutePath + File.separator + "bg" + File.separator + doc.name) + DocumentUtils.readBytes(requireContext(), uri)?.let { + file.writeBytes(it) + ReadBookConfig.getConfig().setBg(2, file.absolutePath) + ReadBookConfig.upBg() + postEvent(Bus.UP_CONFIG, false) + } + } + } else { + PermissionsCompat.Builder(this) + .addPermissions( + Permissions.READ_EXTERNAL_STORAGE, + Permissions.WRITE_EXTERNAL_STORAGE + ) + .rationale(R.string.bg_image_per) + .onGranted { + FileUtils.getPath(requireContext(), uri)?.let { path -> + ReadBookConfig.getConfig().setBg(2, path) + ReadBookConfig.upBg() + postEvent(Bus.UP_CONFIG, false) + } + Unit + } + .request() } } } 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 d2cc149e1..314eb3621 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 @@ -49,7 +49,7 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity get() = getViewModel(BookSourceViewModel::class.java) private val qrRequestCode = 101 - private val importSource = 13141 + private val importSource = 132 private lateinit var adapter: BookSourceAdapter private var bookSourceLiveDate: LiveData>? = null private var groups = hashSetOf() @@ -82,7 +82,7 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity supportFragmentManager, "groupManage" ) - R.id.menu_import_source_local -> selectFile() + R.id.menu_import_source_local -> selectFileSys() R.id.menu_select_all -> adapter.selectAll() R.id.menu_revert_selection -> adapter.revertSelection() R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelectionIds()) @@ -219,10 +219,15 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity } private fun selectFileSys() { - val intent = Intent(Intent.ACTION_GET_CONTENT) - intent.addCategory(Intent.CATEGORY_OPENABLE) - intent.type = "text/*"//设置类型 - startActivityForResult(intent, importSource) + try { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + intent.type = "text/*"//设置类型 + startActivityForResult(intent, importSource) + } catch (e: Exception) { + selectFile() + } } override fun onMenuClick(menu: String) { @@ -284,16 +289,17 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity } } importSource -> if (resultCode == Activity.RESULT_OK) { - data?.data?.let { - val path = FileUtils.getPath(this, it) - if (path != null) { - Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE) - .show() - viewModel.importSourceFromFilePath(path) { msg -> - title_bar.snackbar(msg) + data?.data?.let { uri -> + try { + uri.readText(this)?.let { + Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE) + .show() + viewModel.importSource(it) { msg -> + toast(msg) + } } - } else { - toast(R.string.uri_to_path_fail) + } catch (e: Exception) { + e.localizedMessage?.let { toast(it) } } } } 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 806336698..9ba3a5a0f 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 @@ -1,6 +1,8 @@ package io.legado.app.ui.replacerule +import android.annotation.SuppressLint import android.app.Activity +import android.content.Intent import android.os.Bundle import android.view.Menu import android.view.MenuItem @@ -13,18 +15,25 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.snackbar.Snackbar import io.legado.app.App import io.legado.app.R import io.legado.app.base.VMBaseActivity import io.legado.app.data.entities.ReplaceRule import io.legado.app.help.ItemTouchCallback +import io.legado.app.lib.dialogs.alert +import io.legado.app.lib.dialogs.cancelButton +import io.legado.app.lib.dialogs.customView +import io.legado.app.lib.dialogs.okButton import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.primaryTextColor +import io.legado.app.lib.theme.view.ATEAutoCompleteTextView import io.legado.app.ui.replacerule.edit.ReplaceEditDialog -import io.legado.app.utils.getViewModel -import io.legado.app.utils.splitNotBlank +import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_replace_rule.* +import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.view_search.* +import org.jetbrains.anko.toast class ReplaceRuleActivity : VMBaseActivity(R.layout.activity_replace_rule), @@ -32,7 +41,7 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi ReplaceRuleAdapter.CallBack { override val viewModel: ReplaceRuleViewModel get() = getViewModel(ReplaceRuleViewModel::class.java) - + private val importSource = 132 private lateinit var adapter: ReplaceRuleAdapter private var groups = hashSetOf() private var groupMenu: SubMenu? = null @@ -68,6 +77,8 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelectionIds()) R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelectionIds()) R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelectionIds()) + R.id.menu_import_source_onLine -> showImportDialog() + R.id.menu_import_source_local -> selectFileSys() R.id.menu_export_selection -> viewModel.exportSelection(adapter.getSelectionIds()) } return super.onCompatOptionsItemSelected(item) @@ -134,6 +145,52 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi } } + @SuppressLint("InflateParams") + private fun showImportDialog() { + val aCache = ACache.get(this, cacheDir = false) + val cacheUrls: MutableList = aCache + .getAsString("replaceRuleUrl") + ?.splitNotBlank(",") + ?.toMutableList() ?: mutableListOf() + alert(titleResource = R.string.import_replace_rule_on_line) { + var editText: ATEAutoCompleteTextView? = null + customView { + layoutInflater.inflate(R.layout.dialog_edit_text, null).apply { + editText = edit_view + edit_view.setFilterValues(cacheUrls) { + cacheUrls.remove(it) + aCache.put("replaceRuleUrl", cacheUrls.joinToString(",")) + } + } + } + okButton { + val text = editText?.text?.toString() + text?.let { + if (!cacheUrls.contains(it)) { + cacheUrls.add(0, it) + aCache.put("replaceRuleUrl", cacheUrls.joinToString(",")) + } + Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show() + viewModel.importSource(it) { msg -> + title_bar.snackbar(msg) + } + } + } + cancelButton() + }.show().applyTint() + } + + private fun selectFileSys() { + try { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + intent.type = "text/*"//设置类型 + startActivityForResult(intent, importSource) + } catch (e: Exception) { + + } + } override fun onQueryTextChange(newText: String?): Boolean { observeReplaceRuleData("%$newText%") @@ -144,6 +201,27 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi return false } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + importSource -> if (resultCode == Activity.RESULT_OK) { + data?.data?.let { uri -> + try { + uri.readText(this)?.let { + Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE) + .show() + viewModel.importSource(it) { msg -> + toast(msg) + } + } + } catch (e: Exception) { + e.localizedMessage?.let { toast(it) } + } + } + } + } + } + override fun update(vararg rule: ReplaceRule) { viewModel.update(*rule) } diff --git a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt index 7b80d5475..540900eec 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt @@ -3,10 +3,12 @@ package io.legado.app.ui.replacerule import android.app.Application import android.text.TextUtils import io.legado.app.App +import io.legado.app.R import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.ReplaceRule import io.legado.app.help.FileHelp import io.legado.app.help.storage.Backup +import io.legado.app.help.storage.Restore import io.legado.app.utils.GSON import io.legado.app.utils.splitNotBlank import org.jetbrains.anko.toast @@ -14,6 +16,15 @@ import java.io.File class ReplaceRuleViewModel(application: Application) : BaseViewModel(application) { + fun importSource(text: String, toast: (msg: String) -> Unit) { + execute { + Restore.importOldReplaceRule(text) + }.onError { + toast(it.localizedMessage ?: "ERROR") + }.onSuccess { + toast(context.getString(R.string.success)) + } + } fun update(vararg rule: ReplaceRule) { execute { @@ -68,7 +79,8 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application App.db.replaceRuleDao().findById(it) }.let { val json = GSON.toJson(it) - val file = FileHelp.getFile(Backup.exportPath + File.separator + "exportReplaceRule.json") + val file = + FileHelp.getFile(Backup.exportPath + File.separator + "exportReplaceRule.json") file.writeText(json) } }.onSuccess { diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt index a75134288..c8232669d 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt @@ -80,7 +80,7 @@ class RssSourceActivity : VMBaseActivity(R.layout.activity_r R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelectionIds()) R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelectionIds()) R.id.menu_export_selection -> viewModel.exportSelection(adapter.getSelectionIds()) - R.id.menu_import_source_local -> selectFile() + R.id.menu_import_source_local -> selectFileSys() R.id.menu_import_source_onLine -> showImportDialog() R.id.menu_import_source_qr -> startActivityForResult(qrRequestCode) R.id.menu_group_manage -> GroupManageDialog() @@ -203,10 +203,14 @@ class RssSourceActivity : VMBaseActivity(R.layout.activity_r } private fun selectFileSys() { - val intent = Intent(Intent.ACTION_GET_CONTENT) - intent.addCategory(Intent.CATEGORY_OPENABLE) - intent.type = "text/*"//设置类型 - startActivityForResult(intent, importSource) + try { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.type = "text/*"//设置类型 + startActivityForResult(intent, importSource) + } catch (e: Exception) { + selectFile() + } } override fun onMenuClick(menu: String) { @@ -229,11 +233,11 @@ class RssSourceActivity : VMBaseActivity(R.layout.activity_r super.onActivityResult(requestCode, resultCode, data) when (requestCode) { importSource -> if (resultCode == Activity.RESULT_OK) { - data?.data?.let { - FileUtils.getPath(this, it)?.let { path -> + data?.data?.let { uri -> + uri.readText(this)?.let { Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE) .show() - viewModel.importSourceFromFilePath(path) { msg -> + viewModel.importSource(it) { msg -> title_bar.snackbar(msg) } } diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt index 6ee254029..c47bb8c9d 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt @@ -115,7 +115,6 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application) } } - fun importSourceFromFilePath(path: String, finally: (msg: String) -> Unit) { execute { val file = File(path) diff --git a/app/src/main/java/io/legado/app/utils/DocumentUtils.kt b/app/src/main/java/io/legado/app/utils/DocumentUtils.kt index dcb128a8e..3929874ef 100644 --- a/app/src/main/java/io/legado/app/utils/DocumentUtils.kt +++ b/app/src/main/java/io/legado/app/utils/DocumentUtils.kt @@ -6,25 +6,24 @@ import android.net.Uri object DocumentUtils { @JvmStatic + @Throws(Exception::class) fun writeText(context: Context, data: String, fileUri: Uri): Boolean { return writeBytes(context, data.toByteArray(), fileUri) } @JvmStatic + @Throws(Exception::class) fun writeBytes(context: Context, data: ByteArray, fileUri: Uri): Boolean { - try { - context.contentResolver.openOutputStream(fileUri)?.let { - it.write(data) - it.close() - return true - } - } catch (e: java.lang.Exception) { - e.printStackTrace() + context.contentResolver.openOutputStream(fileUri)?.let { + it.write(data) + it.close() + return true } return false } @JvmStatic + @Throws(Exception::class) fun readText(context: Context, uri: Uri): String? { readBytes(context, uri)?.let { return String(it) @@ -33,17 +32,14 @@ object DocumentUtils { } @JvmStatic + @Throws(Exception::class) fun readBytes(context: Context, uri: Uri): ByteArray? { - try { - context.contentResolver.openInputStream(uri)?.let { - val len: Int = it.available() - val buffer = ByteArray(len) - it.read(buffer) - it.close() - return buffer - } - } catch (e: Exception) { - e.printStackTrace() + context.contentResolver.openInputStream(uri)?.let { + val len: Int = it.available() + val buffer = ByteArray(len) + it.read(buffer) + it.close() + return buffer } return null } diff --git a/app/src/main/java/io/legado/app/utils/UriExtensions.kt b/app/src/main/java/io/legado/app/utils/UriExtensions.kt new file mode 100644 index 000000000..297a5a612 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/UriExtensions.kt @@ -0,0 +1,61 @@ +package io.legado.app.utils + +import android.content.Context +import android.net.Uri +import androidx.documentfile.provider.DocumentFile +import java.io.File + + +@Throws(Exception::class) +fun Uri.readBytes(context: Context): ByteArray? { + if (DocumentFile.isDocumentUri(context, this)) { + return DocumentUtils.readBytes(context, this) + } else { + val path = FileUtils.getPath(context, this) + if (path?.isNotEmpty() == true) { + return File(path).readBytes() + } + } + return null +} + +@Throws(Exception::class) +fun Uri.readText(context: Context): String? { + if (DocumentFile.isDocumentUri(context, this)) { + return DocumentUtils.readText(context, this) + } else { + val path = FileUtils.getPath(context, this) + if (path?.isNotEmpty() == true) { + return File(path).readText() + } + } + return null +} + +@Throws(Exception::class) +fun Uri.writeBytes(context: Context, byteArray: ByteArray): Boolean { + if (DocumentFile.isDocumentUri(context, this)) { + return DocumentUtils.writeBytes(context, byteArray, this) + } else { + val path = FileUtils.getPath(context, this) + if (path?.isNotEmpty() == true) { + File(path).writeBytes(byteArray) + return true + } + } + return false +} + +@Throws(Exception::class) +fun Uri.writeText(context: Context, text: String): Boolean { + if (DocumentFile.isDocumentUri(context, this)) { + return DocumentUtils.writeText(context, text, this) + } else { + val path = FileUtils.getPath(context, this) + if (path?.isNotEmpty() == true) { + File(path).writeText(text) + return true + } + } + return false +} \ No newline at end of file